diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 5908d70c64..a53d532590 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -98,9 +98,6 @@ pub struct DocumentMessageHandler { /// Tracks which layer occurrences are collapsed in the Layers panel, keyed by tree path. #[serde(deserialize_with = "deserialize_collapsed_layers", default)] pub collapsed: CollapsedLayers, - /// The node IDs whose section is collapsed in the Properties panel. - #[serde(default)] - pub properties_panel_collapsed_sections: Vec, /// The full Git commit hash of the Graphite repository that was used to build the editor. /// We save this to provide a hint about which version of the editor was used to create the document. pub commit_hash: String, @@ -178,7 +175,6 @@ impl Default for DocumentMessageHandler { network_interface: default_document_network_interface(), resources: ResourceMessageHandler::default(), collapsed: CollapsedLayers::default(), - properties_panel_collapsed_sections: Vec::new(), commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(), document_ptz: PTZ::default(), render_mode: RenderMode::default(), @@ -253,7 +249,7 @@ impl MessageHandler> for DocumentMes document_name: self.name.as_str(), fonts, properties_panel_open, - properties_panel_collapsed_sections: &self.properties_panel_collapsed_sections, + properties_panel_collapsed_sections: &[], }; self.properties_panel_message_handler.process_message(message, responses, context); } @@ -277,7 +273,6 @@ impl MessageHandler> for DocumentMes breadcrumb_network_path: &self.breadcrumb_network_path, document_id, collapsed: &mut self.collapsed, - properties_panel_collapsed_sections: &mut self.properties_panel_collapsed_sections, ipp, graph_view_overlay_open: self.graph_view_overlay_open, graph_fade_artwork_percentage: self.graph_fade_artwork_percentage, @@ -1411,12 +1406,12 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::SendGraph); } DocumentMessage::ToggleNodePropertiesSectionExpanded { node_id } => { - if let Some(index) = self.properties_panel_collapsed_sections.iter().position(|id| *id == node_id) { - self.properties_panel_collapsed_sections.remove(index); - } else { - self.properties_panel_collapsed_sections.push(node_id); - } - responses.add(PropertiesPanelMessage::Refresh); + let collapsed = !self.network_interface.is_collapsed(&node_id, &[]); + responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed }); + responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { + node_ids: vec![node_id], + network_path: vec![], + }); } DocumentMessage::ToggleSelectedLocked => responses.add(NodeGraphMessage::ToggleSelectedLocked), DocumentMessage::ToggleSelectedVisibility => { diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index d792346fe0..ea587fdcac 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -65,6 +65,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier { let new_merge_node = resolve_network_node_type("Merge").expect("Merge node").default_node_template(); self.network_interface.insert_node(new_id, new_merge_node, &[]); + self.responses.add(PropertiesPanelMessage::SetSectionExpanded { node_id: new_id.0, expanded: false }); LayerNodeIdentifier::new(new_id, self.network_interface) } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 3ab0dcc0f1..7ea47d1eff 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -23,6 +23,8 @@ use graphene_std::vector::Vector; use graphene_std::*; use std::collections::{HashMap, VecDeque}; +pub const MERGE_NODE_IDENTIFIER: &str = "Merge"; + pub struct NodePropertiesContext<'a> { pub responses: &'a mut VecDeque, pub executor: &'a mut NodeGraphExecutor, @@ -145,7 +147,7 @@ fn document_node_definitions() -> HashMap NodeTemplate { self.node_template_input_override(self.node_template.inputs.clone().into_iter().map(Some)) } 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 d7df7271fc..5884dae448 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 @@ -237,6 +237,10 @@ pub enum NodeGraphMessage { node_id: NodeId, pinned: bool, }, + SetCollapsed { + node_id: NodeId, + collapsed: bool, + }, SetVisibility { node_id: NodeId, network_path: Vec, 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 13632c96a9..dc4fcd39fe 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 @@ -39,7 +39,6 @@ pub struct NodeGraphMessageContext<'a> { pub breadcrumb_network_path: &'a [NodeId], pub document_id: DocumentId, pub collapsed: &'a mut CollapsedLayers, - pub properties_panel_collapsed_sections: &'a mut Vec, pub ipp: &'a InputPreprocessorMessageHandler, pub graph_view_overlay_open: bool, pub graph_fade_artwork_percentage: f64, @@ -111,7 +110,6 @@ impl<'a> MessageHandler> for NodeG breadcrumb_network_path, document_id, collapsed, - properties_panel_collapsed_sections, ipp, graph_view_overlay_open, graph_fade_artwork_percentage, @@ -193,10 +191,6 @@ impl<'a> MessageHandler> for NodeG // Prune the Layers panel collapsed state for any layer tree paths whose nodes no longer exist, so it doesn't accumulate across loads collapsed.0.retain(|path| path.iter().all(|&node_id| network_interface.document_network().nodes.contains_key(&node_id))); - - // Prune the Properties panel node section collapsed state for any nodes (in any nested network) that no longer exist, so it doesn't accumulate across loads - let existing_nodes = network_interface.document_network().recursive_nodes().map(|(node_id, ..)| *node_id).collect::>(); - properties_panel_collapsed_sections.retain(|node_id| existing_nodes.contains(node_id)); } NodeGraphMessage::SelectedNodesUpdated => { let selected_layers = network_interface.selected_nodes().selected_layers(network_interface.document_metadata()).collect::>(); @@ -2029,6 +2023,9 @@ impl<'a> MessageHandler> for NodeG NodeGraphMessage::SetPinned { node_id, pinned } => { network_interface.set_pinned(&node_id, selection_network_path, pinned); } + NodeGraphMessage::SetCollapsed { node_id, collapsed } => { + network_interface.set_collapsed(&node_id, selection_network_path, collapsed); + } NodeGraphMessage::SetVisibility { node_id, network_path, visible } => { network_interface.set_visibility(&node_id, &network_path, visible); } @@ -2038,6 +2035,8 @@ impl<'a> MessageHandler> for NodeG } responses.add(NodeGraphMessage::UpdateActionButtons); responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::UpdateLayerPanel); + responses.add(PortfolioMessage::AutoSaveActiveDocument); responses.add(PropertiesPanelMessage::Refresh); responses.add(DataPanelMessage::Refresh); diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 2efeb8b207..233c5f8e8b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -2347,7 +2347,6 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper if layout.is_empty() { layout = node_no_properties(node_id, context); } - let display_name = context .network_interface .node_metadata(&node_id, context.selection_network_path) @@ -2374,7 +2373,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper let visible = context.network_interface.is_visible(&node_id, context.selection_network_path); let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path); - let expanded = !context.properties_panel_collapsed_sections.contains(&node_id); + let expanded = !context.network_interface.is_collapsed(&node_id, context.selection_network_path); LayoutGroup::section(name, description, visible, pinned, expanded, node_id.0, Layout(layout)) } diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs index a87d8fb07d..954c909dee 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs @@ -6,4 +6,6 @@ pub enum PropertiesPanelMessage { // Messages Clear, Refresh, + SetAllSectionsExpanded { expanded: bool }, + SetSectionExpanded { node_id: u64, expanded: bool }, } diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index 77e7742fc1..1e08b5096c 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -65,6 +65,39 @@ impl MessageHandler> f layout_target: LayoutTarget::PropertiesPanel, }); } + PropertiesPanelMessage::SetAllSectionsExpanded { expanded } => { + let mut layout = { + let mut node_properties_context = NodePropertiesContext { + responses, + executor, + network_interface, + resources, + fonts, + selection_network_path, + document_name, + properties_panel_collapsed_sections, + }; + Layout(NodeGraphMessageHandler::collate_properties(&mut node_properties_context)) + }; + + let node_ids = Self::update_all_section_expansion_recursive(&mut layout.0, expanded, responses); + if !node_ids.is_empty() { + responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids, network_path: vec![] }); + } + + responses.add(LayoutMessage::SendLayout { + layout, + layout_target: LayoutTarget::PropertiesPanel, + }); + } + PropertiesPanelMessage::SetSectionExpanded { node_id, expanded } => { + let node_id = NodeId(node_id); + responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed: !expanded }); + responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { + node_ids: vec![node_id], + network_path: vec![], + }); + } } } @@ -72,3 +105,22 @@ impl MessageHandler> f actions!(PropertiesMessageDiscriminant;) } } + +impl PropertiesPanelMessageHandler { + fn update_all_section_expansion_recursive(layout: &mut [LayoutGroup], expanded: bool, responses: &mut VecDeque) -> Vec { + let mut node_ids = Vec::new(); + for group in layout { + if let LayoutGroup::Section(WidgetSection { + id, layout, expanded: group_expanded, .. + }) = group + { + *group_expanded = expanded; + let node_id = NodeId(*id); + node_ids.push(node_id); + responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed: !expanded }); + node_ids.extend(Self::update_all_section_expansion_recursive(&mut layout.0, expanded, responses)); + } + } + node_ids + } +} diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs index 20927b20b0..099e2a2033 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/deserialization.rs @@ -146,6 +146,7 @@ impl From for DocumentNodePersist output_names: old.output_names, locked: old.locked, pinned: old.pinned, + collapsed: None, node_type_metadata: old.node_type_metadata, network_metadata: old.network_metadata, } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs index 1b2b873985..0db8652d38 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs @@ -1393,6 +1393,16 @@ impl NodeNetworkInterface { self.transaction_modified(); } + pub fn set_collapsed(&mut self, node_id: &NodeId, network_path: &[NodeId], collapsed: bool) { + let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get node {node_id} in set_collapsed"); + return; + }; + + node_metadata.persistent_metadata.collapsed = Some(collapsed); + self.transaction_modified(); + } + /// Reorders a pinned node within its network's Properties panel display order so it ends up at `insert_index` among the /// pinned nodes (0 being the topmost). Rebuilds the order from the list as currently shown, which also drops stale entries. pub fn reorder_pinned_node(&mut self, node_id: NodeId, insert_index: usize, network_path: &[NodeId]) { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs index bf888f4ca5..ad5962414c 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs @@ -832,6 +832,10 @@ impl NodeNetworkInterface { self.query(network_path, "is_pinned", |view| view.is_pinned(node_id)).unwrap_or_default() } + pub fn is_collapsed(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + self.query(network_path, "is_collapsed", |view| view.is_collapsed(node_id)).unwrap_or_default() + } + /// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last. pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec { self.query(network_path, "ordered_pinned_nodes", |view| Ok(view.ordered_pinned_nodes())).unwrap_or_default() diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs index 3fccaba631..31c10a36a4 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs @@ -119,6 +119,7 @@ impl NodeTemplate { output_names, locked, pinned, + collapsed: _, node_type_metadata, network_metadata, } = persistent_node_metadata; @@ -201,6 +202,7 @@ impl NodeTemplate { output_names, locked, pinned, + collapsed: None, node_type_metadata, network_metadata, }; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs index 74361697b7..9f872da646 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs @@ -640,6 +640,8 @@ pub struct DocumentNodePersistentMetadata { /// 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, + #[serde(default)] + pub collapsed: Option, /// 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, diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs index 6c99aca531..13a568375d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs @@ -192,6 +192,12 @@ impl<'a, 'p> NetworkView<'a, 'p> { Ok(self.node_metadata(node_id)?.persistent_metadata.pinned) } + pub fn is_collapsed(&self, node_id: &NodeId) -> Result { + let node_metadata = self.node_metadata(node_id)?; + let collapsed = node_metadata.persistent_metadata.collapsed.unwrap_or_else(|| self.implementation_name(node_id) == "Merge"); + Ok(collapsed) + } + pub fn is_visible(&self, node_id: &NodeId) -> Result { Ok(self.node(node_id)?.visible) }