diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index fef1061f27..02302290d7 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -340,6 +340,7 @@ pub enum LayoutGroup { description: String, visible: bool, pinned: bool, + expanded: bool, id: u64, layout: Layout, }, @@ -439,6 +440,7 @@ impl Diffable for LayoutGroup { description: current_description, visible: current_visible, pinned: current_pinned, + expanded: current_expanded, id: current_id, layout: current_layout, }, @@ -447,6 +449,7 @@ impl Diffable for LayoutGroup { description: new_description, visible: new_visible, pinned: new_pinned, + expanded: new_expanded, id: new_id, layout: new_layout, }, @@ -458,6 +461,7 @@ impl Diffable for LayoutGroup { || *current_description != new_description || *current_visible != new_visible || *current_pinned != new_pinned + || *current_expanded != new_expanded || *current_id != new_id { // Update self to reflect new changes @@ -465,6 +469,7 @@ impl Diffable for LayoutGroup { current_description.clone_from(&new_description); *current_visible = new_visible; *current_pinned = new_pinned; + *current_expanded = new_expanded; *current_id = new_id; current_layout.clone_from(&new_layout); @@ -474,6 +479,7 @@ impl Diffable for LayoutGroup { description: new_description, visible: new_visible, pinned: new_pinned, + expanded: new_expanded, id: new_id, layout: new_layout, } diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 461e443a35..5eed333dd9 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -85,6 +85,7 @@ pub struct DocumentMessageHandler { /// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel. /// Collapsed means that the expansion arrow isn't set to show the children of these layers. pub collapsed: CollapsedLayers, + /// 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, @@ -159,6 +160,7 @@ impl Default for DocumentMessageHandler { // ============================================ network_interface: default_document_network_interface(), collapsed: CollapsedLayers::default(), + commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(), document_ptz: PTZ::default(), render_mode: RenderMode::default(), @@ -229,6 +231,7 @@ impl MessageHandler> for DocumentMes document_name: self.name.as_str(), executor, persistent_data, + properties_panel_open, }; self.properties_panel_message_handler.process_message(message, responses, context); 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 cb8c4608c0..c870df4241 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -125,6 +125,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 ed7f440839..5be4dba2a7 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 @@ -28,6 +28,8 @@ use graphene_std::*; use serde_json::Value; use std::collections::{HashMap, VecDeque}; +pub const MERGE_NODE_IDENTIFIER: &str = "Merge"; + pub struct NodePropertiesContext<'a> { pub persistent_data: &'a PersistentData, pub responses: &'a mut VecDeque, @@ -213,7 +215,7 @@ fn document_node_definitions() -> HashMap NodeTemplate { self.node_template_input_override(self.node_template.document_node.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 4ec91d9981..8dcb373356 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 @@ -213,6 +213,10 @@ pub enum NodeGraphMessage { node_id: NodeId, pinned: bool, }, + SetCollapsed { + node_id: NodeId, + collapsed: 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 433a282bbe..d1acdf0543 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 @@ -1904,6 +1904,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, visible } => { network_interface.set_visibility(&node_id, selection_network_path, visible); } @@ -1913,6 +1916,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); } 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 eb08935e7c..e449257577 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1830,7 +1830,17 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper if layout.is_empty() { layout = node_no_properties(node_id, context); } - let name = context.network_interface.implementation_name(&node_id, context.selection_network_path); + let mut name = context.network_interface.implementation_name(&node_id, context.selection_network_path); + if name == "Custom Node" { + if let Some(display_name) = context + .network_interface + .node_metadata(&node_id, context.selection_network_path) + .map(|metadata| metadata.persistent_metadata.display_name.clone()) + .filter(|name| !name.is_empty()) + { + name = display_name; + } + } let description = context .network_interface @@ -1843,12 +1853,15 @@ 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 collapsed = context.network_interface.is_collapsed(&node_id, context.selection_network_path); + let expanded = !collapsed; LayoutGroup::Section { name, description, visible, pinned, + expanded, id: node_id.0, layout: 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 b5706ec1e5..e9579240ae 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 @@ -60,6 +60,36 @@ impl MessageHandler> f layout_target: LayoutTarget::PropertiesPanel, }); } + PropertiesPanelMessage::SetAllSectionsExpanded { expanded } => { + let mut layout = { + let mut node_properties_context = NodePropertiesContext { + persistent_data, + responses, + network_interface, + selection_network_path, + document_name, + executor, + }; + Layout(NodeGraphMessageHandler::collate_properties(&mut node_properties_context)) + }; + + responses.add(DocumentMessage::AddTransaction); + 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 }); + } + + responses.add(LayoutMessage::SendLayout { + layout, + layout_target: LayoutTarget::PropertiesPanel, + }); + } + PropertiesPanelMessage::SetSectionExpanded { node_id, expanded } => { + let node_id = NodeId(node_id); + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::SetCollapsed { node_id, collapsed: !expanded }); + responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] }); + } } } @@ -67,3 +97,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 { + 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.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 22680231e5..abfc839b80 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1044,6 +1044,14 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.pinned } + pub fn is_collapsed(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + let Some(node_metadata) = self.node_metadata(node_id, network_path) else { + log::error!("Could not get persistent node metadata in is_collapsed for node {node_id}"); + return false; + }; + node_metadata.persistent_metadata.collapsed + } + pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { let Some(node) = self.document_node(node_id, network_path) else { log::error!("Could not get node in is_visible"); @@ -4487,6 +4495,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 = collapsed; + 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; @@ -6242,6 +6260,9 @@ 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, + /// Whether the properties body is expanded or collapsed. Defaults to `false` when not specified. + #[serde(default)] + pub collapsed: 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, 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 5e824e2ad7..2f7da3d81f 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 @@ -147,6 +147,7 @@ impl From for DocumentNodePersist output_names: old.output_names, locked: old.locked, pinned: old.pinned, + collapsed: false, node_type_metadata: old.node_type_metadata, network_metadata: old.network_metadata, } diff --git a/frontend/src/components/widgets/WidgetSection.svelte b/frontend/src/components/widgets/WidgetSection.svelte index af55fe7f3d..d01b46e579 100644 --- a/frontend/src/components/widgets/WidgetSection.svelte +++ b/frontend/src/components/widgets/WidgetSection.svelte @@ -3,6 +3,7 @@ import type { Editor } from "@graphite/editor"; import { isWidgetSpanRow, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages, type LayoutTarget } from "@graphite/messages"; + import { operatingSystem } from "@graphite/utility-functions/platform"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; @@ -16,14 +17,24 @@ export { className as class }; export let classes: Record = {}; - let expanded = true; + $: expanded = widgetData.expanded; const editor = getContext("editor"); - -