diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index ce43a36c..b0eea647 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -382,8 +382,8 @@ impl MessageHandler> for DocumentMessag let name = self.network_interface.frontend_display_name(&layer.to_node(), &[]); - let (_, angle, translation) = self.metadata().document_to_viewport.to_scale_angle_translation(); - let translation = translation + bounds[0].min(bounds[1]) - DVec2::Y * 4.; + let (scale, angle, translation) = self.metadata().document_to_viewport.to_scale_angle_translation(); + let translation = translation + scale * bounds[0].min(bounds[1]) - DVec2::Y * 4.; let transform = DAffine2::from_angle_translation(angle, translation); overlay_context.text_with_transform(&name, COLOR_OVERLAY_GRAY, None, transform, Pivot::BottomLeft); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 8f7f016d..2f31a9f4 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -139,10 +139,15 @@ pub enum NodeGraphMessage { node_id: NodeId, locked: bool, }, + ToggleSelectedIsPinned, ToggleSelectedVisibility, ToggleVisibility { node_id: NodeId, }, + SetPinned { + node_id: NodeId, + pinned: bool, + }, SetVisibility { node_id: NodeId, visible: bool, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 6eb0e23d..4349081d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1219,6 +1219,28 @@ impl<'a> MessageHandler> for NodeGrap NodeGraphMessage::SetLocked { node_id, locked } => { network_interface.set_locked(&node_id, selection_network_path, locked); } + NodeGraphMessage::ToggleSelectedIsPinned => { + let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else { + log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedIsPinned"); + return; + }; + let node_ids = selected_nodes.selected_nodes().cloned().collect::>(); + + // If any of the selected nodes are pinned, unpin them all. Otherwise, pin them all. + let pinned = !node_ids.iter().all(|node_id| { + if let Some(node) = network_interface.node_metadata(node_id, breadcrumb_network_path) { + node.persistent_metadata.pinned + } else { + false + } + }); + + responses.add(DocumentMessage::AddTransaction); + for node_id in &node_ids { + responses.add(NodeGraphMessage::SetPinned { node_id: *node_id, pinned }); + } + responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids }); + } NodeGraphMessage::ToggleSelectedVisibility => { let Some(network) = network_interface.network(selection_network_path) else { return; @@ -1227,7 +1249,6 @@ impl<'a> MessageHandler> for NodeGrap log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked"); return; }; - let node_ids = selected_nodes.selected_nodes().cloned().collect::>(); // If any of the selected nodes are hidden, show them all. Otherwise, hide them all. @@ -1255,6 +1276,9 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::SetVisibility { node_id, visible }); responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] }); } + NodeGraphMessage::SetPinned { node_id, pinned } => { + network_interface.set_pinned(&node_id, selection_network_path, pinned); + } NodeGraphMessage::SetVisibility { node_id, visible } => { network_interface.set_visibility(&node_id, selection_network_path, visible); } @@ -1459,8 +1483,7 @@ impl NodeGraphMessageHandler { }; let mut selection = selected_nodes.selected_nodes(); - - // If there is at least one other selected node then show the hide or show button + // If there is at least one selected node then show the hide or show button if selection.next().is_some() { // Check if any of the selected nodes are disabled let all_visible = selected_nodes.selected_nodes().all(|id| { @@ -1479,7 +1502,7 @@ impl NodeGraphMessageHandler { let (hide_show_label, hide_show_icon) = if all_visible { ("Make Hidden", "EyeVisible") } else { ("Make Visible", "EyeHidden") }; let hide_button = TextButton::new(hide_show_label) .icon(Some(hide_show_icon.to_string())) - .tooltip(if all_visible { "Hide selected nodes/layers" } else { "Show selected nodes/layers" }.to_string() + if multiple_nodes { "s" } else { "" }) + .tooltip(if all_visible { "Hide" } else { "Show" }.to_string() + " selected " + if multiple_nodes { "nodes/layers" } else { "node/layer" }) .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility)) .on_update(move |_| NodeGraphMessage::ToggleSelectedVisibility.into()) .widget_holder(); @@ -1488,6 +1511,39 @@ impl NodeGraphMessageHandler { widgets.push(Separator::new(SeparatorType::Related).widget_holder()); } + let mut selection = selected_nodes.selected_nodes(); + // If there is at least one selected node then show the pin or unpin button + if selection.next().is_some() { + // Check if any of the selected nodes are pinned + let all_unpinned = !selected_nodes.selected_nodes().all(|id| { + if let Some(node) = network_interface.node_metadata(id, breadcrumb_network_path) { + node.persistent_metadata.pinned + } else { + error!("Could not get node {id} in update_selection_action_buttons"); + false + } + }); + + // Check if multiple nodes are selected + let multiple_nodes = selection.next().is_some(); + + // Generate the visible/hidden button accordingly + let (pin_unpin_label, pin_unpin_icon) = if all_unpinned { ("Pin", "CheckboxUnchecked") } else { ("Unpin", "CheckboxChecked") }; + let pin_button = TextButton::new(pin_unpin_label) + .icon(Some(pin_unpin_icon.to_string())) + .tooltip( + if all_unpinned { "Pin" } else { "Unpin" }.to_string() + + " selected " + if multiple_nodes { "nodes/layers" } else { "node/layer" } + + " in the Properties panel when nothing is selected", + ) + .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedIsPinned)) + .on_update(move |_| NodeGraphMessage::ToggleSelectedIsPinned.into()) + .widget_holder(); + widgets.push(pin_button); + + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + } + let mut selection = selected_nodes.selected_nodes(); // If only one node is selected then show the preview or stop previewing button if let (Some(&node_id), None) = (selection.next(), selection.next()) { @@ -1518,10 +1574,12 @@ impl NodeGraphMessageHandler { warn!("No selected nodes in collate_properties"); return Vec::new(); }; + // We want: // - If only nodes (no layers) are selected: display each node's properties - // - If one layer is selected, and zero or more of its upstream nodes: display the properties for the layer and its upstream nodes + // - If one layer is selected, and zero or more of its (primary flow) upstream nodes: display the properties for the layer and all its upstream nodes // - If multiple layers are selected, or one node plus other non-upstream nodes: display nothing + // - If nothing is selected, display any pinned nodes/layers // First, we filter all the selections into layers and nodes let (mut layers, mut nodes) = (Vec::new(), Vec::new()); @@ -1536,10 +1594,35 @@ impl NodeGraphMessageHandler { // Next, we decide what to display based on the number of layers and nodes selected match layers.len() { // If no layers are selected, show properties for all selected nodes - 0 => nodes - .iter() - .filter_map(|node_id| network.nodes.get(node_id).map(|node| node_properties::generate_node_properties(node, *node_id, context))) - .collect(), + 0 => { + let selected_nodes = nodes + .iter() + .filter_map(|node_id| network.nodes.get(node_id).map(|node| node_properties::generate_node_properties(node, *node_id, context))) + .collect::>(); + if !selected_nodes.is_empty() { + return selected_nodes; + } + + // And if no nodes are selected, show properties for all pinned nodes + network + .nodes + .iter() + .filter_map(|(node_id, node)| { + let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) { + node.persistent_metadata.pinned + } else { + error!("Could not get node {node_id} in collate_properties"); + false + }; + + if pinned { + Some(node_properties::generate_node_properties(node, *node_id, context)) + } else { + None + } + }) + .collect::>() + } // If one layer is selected, filter out all selected nodes that are not upstream of it. If there are no nodes left, show properties for the layer. Otherwise, show nothing. 1 => { let nodes_not_upstream_of_layer = nodes.into_iter().filter(|&selected_node_id| { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index bff8a67d..9fc1f9ca 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3749,11 +3749,20 @@ impl NodeNetworkInterface { self.unload_node_click_targets(node_id, network_path); } + pub fn set_pinned(&mut self, node_id: &NodeId, network_path: &[NodeId], pinned: bool) { + let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get node {node_id} in set_pinned"); + return; + }; + + node_metadata.persistent_metadata.pinned = pinned; + self.transaction_modified(); + } + pub fn set_visibility(&mut self, node_id: &NodeId, network_path: &[NodeId], is_visible: bool) { let Some(network) = self.network_mut(network_path) else { return; }; - let Some(node) = network.nodes.get_mut(node_id) else { log::error!("Could not get node {node_id} in set_visibility"); return; @@ -5301,6 +5310,9 @@ pub struct DocumentNodePersistentMetadata { /// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI. #[serde(default)] pub locked: bool, + /// Indicates that the node will be shown in the Properties panel when it would otherwise be empty, letting a user easily edit its properties by just deselecting everything. + #[serde(default)] + pub pinned: bool, /// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer. /// All fields in NodeTypePersistentMetadata should automatically be updated by using the network interface API pub node_type_metadata: NodeTypePersistentMetadata, @@ -5316,6 +5328,7 @@ impl Default for DocumentNodePersistentMetadata { input_names: Vec::new(), output_names: Vec::new(), has_primary_output: true, + pinned: false, locked: false, node_type_metadata: NodeTypePersistentMetadata::default(), network_metadata: None,