WIP: Defer message

This commit is contained in:
Adam
2025-09-08 19:40:37 -07:00
parent 8caf9317a5
commit c994cdeced
15 changed files with 401 additions and 1047 deletions
+1
View File
@@ -4,6 +4,7 @@ pub const EXPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
pub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120;
pub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
pub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120;
pub const TOOLTIP_DELAY: u32 = 800;
// VIEWPORT
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
+1
View File
@@ -140,6 +140,7 @@ impl Dispatcher {
Message::Defer(message) => {
let context = DeferMessageContext {
portfolio: &self.message_handlers.portfolio_message_handler,
time: self.message_handlers.input_preprocessor_message_handler.time,
};
self.message_handlers.defer_message_handler.process_message(message, &mut queue, context);
}
@@ -1,3 +1,5 @@
use std::time::Duration;
use crate::messages::prelude::*;
#[impl_message(Message, Defer)]
@@ -8,4 +10,6 @@ pub enum DeferMessage {
AfterGraphRun { messages: Vec<Message> },
TriggerNavigationReady,
AfterNavigationReady { messages: Vec<Message> },
RequestDeferredMessage { timeout: Duration, message: Box<Message> },
CheckDeferredMessages,
}
@@ -1,8 +1,15 @@
use std::{
collections::BTreeMap,
ops::Bound,
time::{Duration, Instant},
};
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct DeferMessageContext<'a> {
pub portfolio: &'a PortfolioMessageHandler,
pub time: Instant,
}
#[derive(Debug, Default, ExtractField)]
@@ -10,6 +17,7 @@ pub struct DeferMessageHandler {
after_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>,
after_viewport_resize: Vec<Message>,
current_graph_submission_id: u64,
after_time_elapsed: BTreeMap<Instant, Message>,
}
#[message_handler_data]
@@ -49,6 +57,15 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
responses.add_front(message);
}
}
DeferMessage::RequestDeferredMessage { timeout, message } => {
self.after_time_elapsed.insert(context.time + timeout, *message);
}
DeferMessage::CheckDeferredMessages => {
let after_current_time = self.after_time_elapsed.split_off((Bound::Unbounded, Bound::Excluded(context.time)));
for (_, message) in std::mem::replace(self.after_time_elapsed, after_current_time) {
responses.add(message);
}
}
}
}
@@ -305,6 +305,10 @@ pub enum FrontendMessage {
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdateTooltip {
position: Option<FrontendXY>,
text: String,
},
UpdateWirePathInProgress {
#[serde(rename = "wirePathInProgress")]
wire_path_in_progress: Option<WirePathInProgress>,
@@ -4,7 +4,7 @@ use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo;
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use glam::DVec2;
use std::time::Duration;
use std::time::{Duration, Instant};
#[derive(ExtractField)]
pub struct InputPreprocessorMessageContext {
@@ -14,7 +14,7 @@ pub struct InputPreprocessorMessageContext {
#[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo,
pub time: u64,
pub time: Instant,
pub keyboard: KeyStates,
pub mouse: MouseState,
pub viewport_bounds: ViewportBounds,
@@ -114,7 +114,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
}
InputPreprocessorMessage::CurrentTime { timestamp } => {
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
self.time = timestamp;
self.time = Instant::from(timestamp);
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
}
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
@@ -215,6 +215,7 @@ pub enum NodeGraphMessage {
SetLockedOrVisibilitySideEffects {
node_ids: Vec<NodeId>,
},
TryDisplayTooltip,
UpdateBoxSelection,
UpdateImportsExports,
UpdateLayerPanel,
@@ -1,6 +1,6 @@
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart};
use super::{document_node_definitions, node_properties};
use crate::consts::GRID_SIZE;
use crate::consts::{GRID_SIZE, TOOLTIP_DELAY};
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
@@ -94,6 +94,12 @@ pub struct NodeGraphMessageHandler {
end_index: Option<usize>,
// The rendered string for each thumbnail
pub thumbnails: HashMap<NodeId, Graphic>,
// If an input is being hovered. Used for tooltip
hovering_input: bool,
// If an output is being hovered. Used for tooltip
hovering_output: bool,
// If a node is being hovered. Used for tooltip
hovering_node: bool,
}
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
@@ -1143,6 +1149,44 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),
);
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
} else if !self.hovering_input && !self.hovering_output {
if network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_input = true;
responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
timeout: Duration::from_millis(TOOLTIP_DELAY),
});
}
if network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_output = true;
responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
timeout: Duration::from_millis(TOOLTIP_DELAY),
})
}
} else if self.hovering_input {
if !network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_input = false;
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
}
} else if self.hovering_output {
if !network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_output = false;
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
}
} else if !self.hovering_node {
if network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
self.hovering_node = true;
responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
timeout: Duration::from_millis(TOOLTIP_DELAY),
})
}
} else if self.hovering_node {
if !network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
self.hovering_node = false;
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
}
}
}
NodeGraphMessage::PointerUp => {
@@ -1595,8 +1639,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::UpdateActionButtons);
responses.add(FrontendMessage::RequestNativeNodeGraphRender);
responses.add(NodeGraphMessage::UpdateImportsExports);
self.update_node_graph_hints(responses);
}
@@ -1807,6 +1849,54 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
}
NodeGraphMessage::TryDisplayTooltip => {
if let Some(input) = network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path) {
let text = network_interface.input_tooltip_text(&input, breadcrumb_network_path);
if let Some(position) = network_interface.input_position(&input, breadcrumb_network_path) {
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return;
};
let position = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(position);
let xy = FrontendXY {
x: position.x as i32,
y: position.y as i32,
};
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
}
} else if let Some(output) = network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path) {
let text = network_interface.output_tooltip_text(&output, breadcrumb_network_path);
if let Some(position) = network_interface.output_position(&output, breadcrumb_network_path) {
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return;
};
let position = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(position);
let xy = FrontendXY {
x: position.x as i32,
y: position.y as i32,
};
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
}
} else if let Some(node_id) = network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
let text = network_interface.node_tooltip_text(&node_id, breadcrumb_network_path);
if let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) {
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return;
};
let position = network_metadata
.persistent_metadata
.navigation_metadata
.node_graph_to_viewport
.transform_point2(DVec2::new(position.x as f64 * 24., position.y as f64 * 24.));
let xy = FrontendXY {
x: position.x as i32,
y: position.y as i32,
};
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
}
}
}
NodeGraphMessage::SetPinned { node_id, pinned } => {
network_interface.set_pinned(&node_id, selection_network_path, pinned);
}
@@ -144,24 +144,9 @@ impl NodeNetworkInterface {
}
let input_type = self.input_type(input_connector, network_path);
let data_type = input_type.displayed_type();
let resolved_type = input_type.resolved_type_name();
let connected_to = self
.upstream_output_connector(input_connector, network_path)
.map(|output_connector| match output_connector {
OutputConnector::Node { node_id, output_index } => {
let mut name = self.display_name(&node_id, network_path);
if cfg!(debug_assertions) {
name.push_str(&format!(" (id: {node_id})"));
}
format!("{name} output {output_index}")
}
OutputConnector::Import(import_index) => format!("Import index {import_index}"),
})
.unwrap_or("nothing".to_string());
let (name, description) = match input_connector {
InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path),
let name = match input_connector {
InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path).0,
InputConnector::Export(export_index) => {
// Get export name from parent node metadata input, which must match the number of exports.
// Empty string means to use type, or "Export + index" if type is empty determined
@@ -173,44 +158,26 @@ impl NodeNetworkInterface {
.unwrap_or_default()
};
let export_name = if !export_name.is_empty() {
if !export_name.is_empty() {
export_name
} else if let Some(export_type_name) = input_type.compiled_nested_type_name() {
export_type_name
} else {
format!("Export index {}", export_index)
};
(export_name, String::new())
}
}
};
// TODO: Move in separate Tooltip overlay
// let valid_types = match self.valid_input_types(&input_connector, network_path) {
// Ok(input_types) => input_types.iter().map(|ty| ty.to_string()).collect(),
// Err(e) => {
// log::error!("Error getting valid types for input {input_connector:?}: {e}");
// Vec::new()
// }
// };
let connected_to_node = self.upstream_output_connector(input_connector, network_path).and_then(|output_connector| output_connector.node_id());
Some(FrontendGraphInput {
data_type,
resolved_type,
name,
description,
connected_to,
connected_to_node,
})
Some(FrontendGraphInput { data_type, name, connected_to_node })
}
/// Returns None if there is an error, it is the document network, a hidden primary output or import
pub fn frontend_output_from_connector(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<FrontendGraphOutput> {
let output_type = self.output_type(output_connector, network_path);
let (name, description) = match output_connector {
let name = match output_connector {
OutputConnector::Node { node_id, output_index } => {
// Do not display the primary output port for a node if it is a network node with a hidden primary export
if *output_index == 0 && self.hidden_primary_output(node_id, network_path) {
@@ -220,8 +187,7 @@ impl NodeNetworkInterface {
let node_metadata = self.node_metadata(node_id, network_path)?;
let output_name = node_metadata.persistent_metadata.output_names.get(*output_index).cloned().unwrap_or_default();
let output_name = if !output_name.is_empty() { output_name } else { output_type.resolved_type_name() };
(output_name, String::new())
if !output_name.is_empty() { output_name } else { output_type.resolved_type_name() }
}
OutputConnector::Import(import_index) => {
// Get the import name from the encapsulating node input metadata
@@ -233,53 +199,19 @@ impl NodeNetworkInterface {
if *import_index == 0 && self.hidden_primary_import(network_path) {
return None;
};
let (import_name, description) = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path);
let import_name = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path).0;
let import_name = if !import_name.is_empty() {
if !import_name.is_empty() {
import_name
} else if let Some(import_type_name) = output_type.compiled_nested_type_name() {
import_type_name
} else {
format!("Import index {}", *import_index)
};
(import_name, description)
}
}
};
let data_type = output_type.displayed_type();
let resolved_type = output_type.resolved_type_name();
let mut connected_to = self
.outward_wires(network_path)
.and_then(|outward_wires| outward_wires.get(output_connector))
.cloned()
.unwrap_or_else(|| {
log::error!("Could not get {output_connector:?} in outward wires");
Vec::new()
})
.iter()
.map(|input| match input {
InputConnector::Node { node_id, input_index } => {
let mut name = self.display_name(node_id, network_path);
if cfg!(debug_assertions) {
name.push_str(&format!(" (id: {node_id})"));
}
format!("{name} input {input_index}")
}
InputConnector::Export(export_index) => format!("Export index {export_index}"),
})
.collect::<Vec<_>>();
if connected_to.is_empty() {
connected_to.push("nothing".to_string());
}
Some(FrontendGraphOutput {
data_type,
resolved_type,
name,
description,
connected_to,
})
Some(FrontendGraphOutput { data_type, name })
}
pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 {
@@ -545,4 +477,90 @@ impl NodeNetworkInterface {
Some(vector_wire)
}
pub fn input_tooltip_text(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> String {
let input_type = self.input_type(input_connector, network_path);
let data_type_str = format!("Data Type: {input_type:?}");
let connected_to = self
.upstream_output_connector(input_connector, network_path)
.map(|output_connector| match output_connector {
OutputConnector::Node { node_id, output_index } => {
let mut name = self.display_name(&node_id, network_path);
if cfg!(debug_assertions) {
name.push_str(&format!(" (id: {node_id})"));
}
format!("{name} output {output_index}")
}
OutputConnector::Import(import_index) => format!("Import index {import_index}"),
})
.unwrap_or("nothing".to_string());
let connected_to_str = format!("Connected to: {connected_to}");
let valid_types = match self.valid_input_types(input_connector, network_path) {
Ok(valid) => valid,
Err(e) => {
log::error!("Could not get valid types in input tooltip text: {e}");
return String::new();
}
};
let valid_types_str = if !valid_types.is_empty() {
let mut strings = valid_types.iter().map(|x| format!("{x}")).collect::<Vec<_>>();
strings.sort();
strings.join("\n")
} else {
"None".to_string()
};
let valid_types_str = format!("Valid Types:\n{}", valid_types_str);
format!("{data_type_str}\n\n{connected_to_str}\n\n{valid_types_str}")
}
pub fn output_tooltip_text(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> String {
let output_type = self.output_type(output_connector, network_path);
let data_type_str = format!("Data Type: {output_type:?}");
let mut connected_to = self
.outward_wires(network_path)
.and_then(|outward_wires| outward_wires.get(output_connector))
.cloned()
.unwrap_or_else(|| {
log::error!("Could not get {output_connector:?} in outward wires");
Vec::new()
})
.iter()
.map(|input| match input {
InputConnector::Node { node_id, input_index } => {
let mut name = self.display_name(node_id, network_path);
if cfg!(debug_assertions) {
name.push_str(&format!(" (id: {node_id})"));
}
format!("{name} input {input_index}")
}
InputConnector::Export(export_index) => format!("Export index {export_index}"),
})
.collect::<Vec<_>>();
connected_to.sort();
if connected_to.is_empty() {
connected_to.push("nothing".to_string());
}
let connected_to = connected_to.join("\n");
let connected_to_str = format!("Connected to:\n{connected_to}");
format!("{data_type_str}\n\n{connected_to_str}")
}
pub fn node_tooltip_text(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> String {
let display_name = self.display_name(node_id, network_path);
let description = self.description(node_id, network_path);
let Some(reference) = self.reference(node_id, network_path) else {
log::error!("Could not get referende in node_tooltip_text for {node_id}");
return String::new();
};
format!("{display_name}\nReference: {reference}\n\n{description}")
}
}
@@ -562,7 +562,7 @@ struct PathToolData {
saved_selection_before_handle_drag: HashMap<LayerNodeIdentifier, (HashSet<ManipulatorPointId>, HashSet<SegmentId>)>,
handle_drag_toggle: bool,
saved_points_before_anchor_convert_smooth_sharp: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>,
last_click_time: u64,
last_click_time: Instant,
dragging_state: DraggingState,
angle: f64,
pivot_gizmo: PivotGizmo,