From f1b0d8fa8732196bc9c8f5c2759f117bcb4b5455 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 4 Nov 2024 12:41:53 -0800 Subject: [PATCH] Improve the node graph with revamped top bar and disabling tools when graph is open (#2093) * Add "Fade Artwork" slider and disable tools when graph is open * Add navigation and layer/node management buttons to graph top bar * Reduce code duplication --- editor/src/dispatcher.rs | 9 +- .../src/messages/frontend/frontend_message.rs | 9 +- .../messages/input_mapper/input_mappings.rs | 3 + .../portfolio/document/document_message.rs | 3 + .../document/document_message_handler.rs | 242 +++++++------ .../navigation/navigation_message_handler.rs | 40 ++- .../document/node_graph/node_graph_message.rs | 4 +- .../node_graph/node_graph_message_handler.rs | 337 +++++++++++------- .../utility_types/network_interface.rs | 8 + .../portfolio/portfolio_message_handler.rs | 13 +- .../src/messages/tool/tool_message_handler.rs | 12 +- .../messages/tool/tool_messages/pen_tool.rs | 3 - editor/src/messages/tool/utility_types.rs | 2 +- .../src/components/panels/Document.svelte | 2 +- frontend/src/components/views/Graph.svelte | 6 +- .../components/widgets/WidgetSection.svelte | 22 +- frontend/src/state-providers/document.ts | 14 +- frontend/src/wasm-communication/messages.ts | 25 +- frontend/wasm/src/editor_api.rs | 11 +- 19 files changed, 480 insertions(+), 285 deletions(-) diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index aaac88d5..4225b0c6 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -199,10 +199,11 @@ impl Dispatcher { Message::Portfolio(message) => { let ipp = &self.message_handlers.input_preprocessor_message_handler; let preferences = &self.message_handlers.preferences_message_handler; + let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type; self.message_handlers .portfolio_message_handler - .process_message(message, &mut queue, PortfolioMessageData { ipp, preferences }); + .process_message(message, &mut queue, PortfolioMessageData { ipp, preferences, current_tool }); } Message::Preferences(message) => { self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ()); @@ -244,8 +245,10 @@ impl Dispatcher { list.extend(self.message_handlers.input_preprocessor_message_handler.actions()); list.extend(self.message_handlers.key_mapping_message_handler.actions()); list.extend(self.message_handlers.debug_message_handler.actions()); - if self.message_handlers.portfolio_message_handler.active_document().is_some() { - list.extend(self.message_handlers.tool_message_handler.actions()); + if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() { + if !document.graph_view_overlay_open { + list.extend(self.message_handlers.tool_message_handler.actions()); + } } list.extend(self.message_handlers.portfolio_message_handler.actions()); list diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 7cc8f3d3..7b0dc9ab 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -84,9 +84,6 @@ pub enum FrontendMessage { #[serde(rename = "isDefault")] is_default: bool, }, - TriggerGraphViewOverlay { - open: bool, - }, TriggerImport, TriggerIndexedDbRemoveDocument { #[serde(rename = "documentId")] @@ -153,6 +150,9 @@ pub enum FrontendMessage { #[serde(rename = "clickTargets")] click_targets: Option, }, + UpdateGraphViewOverlay { + open: bool, + }, UpdateLayerWidths { #[serde(rename = "layerWidths")] layer_widths: HashMap, @@ -221,6 +221,9 @@ pub enum FrontendMessage { #[serde(rename = "setColorChoice")] set_color_choice: Option, }, + UpdateGraphFadeArtwork { + percentage: f64, + }, UpdateInputHints { #[serde(rename = "hintData")] hint_data: HintData, diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 654310be..8f3b18aa 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -52,6 +52,7 @@ pub fn input_mappings() -> Mapping { // // Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction) entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop), + // // NodeGraphMessage entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}), entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}), @@ -69,6 +70,8 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut), entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy), entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes), + entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility), + entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked), entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes), entry!(KeyDown(KeyC); modifiers=[Shift], action_dispatch=NodeGraphMessage::PrintSelectedNodeCoordinates), entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=NodeGraphMessage::SendClickTargets), diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index f1149337..27fb1a22 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -131,6 +131,9 @@ pub enum DocumentMessage { SetBlendModeForSelectedLayers { blend_mode: BlendMode, }, + SetGraphFadeArtwork { + percentage: f64, + }, SetNodePinned { node_id: NodeId, pinned: bool, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index ff33c288..5ec6352c 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -39,44 +39,7 @@ pub struct DocumentMessageData<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub persistent_data: &'a PersistentData, pub executor: &'a mut NodeGraphExecutor, -} - -// TODO: Eventually remove this (probably starting late 2024) -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct OldDocumentMessageHandler { - // ============================================ - // Fields that are saved in the document format - // ============================================ - // - /// The node graph that generates this document's artwork. - /// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content. - pub network: OldNodeNetwork, - /// List of the [`NodeId`]s that are currently selected by the user. - pub selected_nodes: SelectedNodes, - /// 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 name of the document, which is displayed in the tab and title bar of the editor. - pub name: String, - /// 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, - /// The current pan, tilt, and zoom state of the viewport's view of the document canvas. - pub document_ptz: PTZ, - /// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools. - pub document_mode: DocumentMode, - /// The current view mode that the user has set for rendering the document within the viewport. - /// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently. - pub view_mode: ViewMode, - /// Sets whether or not all the viewport overlays should be drawn on top of the artwork. - /// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more. - pub overlays_visible: bool, - /// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area. - pub rulers_visible: bool, - /// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden. - pub graph_view_overlay_open: bool, - /// The current user choices for snapping behavior, including whether snapping is enabled at all. - pub snapping_state: SnappingState, + pub current_tool: &'a ToolType, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -121,10 +84,12 @@ pub struct DocumentMessageHandler { pub overlays_visible: bool, /// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area. pub rulers_visible: bool, - /// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden. - pub graph_view_overlay_open: bool, /// The current user choices for snapping behavior, including whether snapping is enabled at all. pub snapping_state: SnappingState, + /// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden. + pub graph_view_overlay_open: bool, + /// The current opacity of the faded node graph background that covers up the artwork. + pub graph_fade_artwork_percentage: f64, // ============================================= // Fields omitted from the saved document format @@ -178,6 +143,7 @@ impl Default for DocumentMessageHandler { rulers_visible: true, graph_view_overlay_open: false, snapping_state: SnappingState::default(), + graph_fade_artwork_percentage: 80., // ============================================= // Fields omitted from the saved document format // ============================================= @@ -199,6 +165,7 @@ impl MessageHandler> for DocumentMessag ipp, persistent_data, executor, + current_tool, } = data; let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path); @@ -247,6 +214,8 @@ impl MessageHandler> for DocumentMessag collapsed: &mut self.collapsed, ipp, graph_view_overlay_open: self.graph_view_overlay_open, + graph_fade_artwork_percentage: self.graph_fade_artwork_percentage, + navigation_handler: &self.navigation_handler, }, ); } @@ -477,15 +446,25 @@ impl MessageHandler> for DocumentMessag DocumentMessage::GraphViewOverlay { open } => { self.graph_view_overlay_open = open; - responses.add(FrontendMessage::TriggerGraphViewOverlay { open }); + responses.add(FrontendMessage::UpdateGraphViewOverlay { open }); + responses.add(FrontendMessage::UpdateGraphFadeArtwork { + percentage: self.graph_fade_artwork_percentage, + }); + // Update the tilt menu bar buttons to be disabled when the graph is open responses.add(MenuBarMessage::SendLayout); + responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); if open { + responses.add(ToolMessage::DeactivateTools); + responses.add(OverlaysMessage::Draw); // Clear the overlays responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. }); responses.add(NodeGraphMessage::SetGridAlignedEdges); + responses.add(NodeGraphMessage::UpdateGraphBarRight); responses.add(NodeGraphMessage::SendGraph); + } else { + responses.add(ToolMessage::ActivateTool { tool_type: *current_tool }); } } DocumentMessage::GraphViewOverlayToggle => { @@ -1019,6 +998,10 @@ impl MessageHandler> for DocumentMessag responses.add(GraphOperationMessage::BlendModeSet { layer, blend_mode }); } } + DocumentMessage::SetGraphFadeArtwork { percentage } => { + self.graph_fade_artwork_percentage = percentage; + responses.add(FrontendMessage::UpdateGraphFadeArtwork { percentage }); + } DocumentMessage::SetNodePinned { node_id, pinned } => { responses.add(DocumentMessage::StartTransaction); responses.add(NodeGraphMessage::SetPinned { node_id, pinned }); @@ -1338,11 +1321,13 @@ impl MessageHandler> for DocumentMessag SelectedLayersRaise, SelectedLayersRaiseToFront, UngroupSelectedLayers, - ToggleSelectedVisibility, ToggleSelectedLocked ); if !self.graph_view_overlay_open { - select.extend(actions!(DocumentMessageDiscriminant; NudgeSelectedLayers)); + select.extend(actions!(DocumentMessageDiscriminant; + NudgeSelectedLayers, + ToggleSelectedVisibility, + )); } common.extend(select); } @@ -1825,68 +1810,10 @@ impl DocumentMessageHandler { ]) .widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), - IconButton::new("ZoomIn", 24) - .tooltip("Zoom In") - .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease)) - .on_update(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) - .widget_holder(), - IconButton::new("ZoomOut", 24) - .tooltip("Zoom Out") - .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease)) - .on_update(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) - .widget_holder(), - IconButton::new("ZoomReset", 24) - .tooltip("Reset Tilt and Zoom to 100%") - .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent)) - .on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into()) - .disabled(self.document_ptz.tilt.abs() < 1e-4 && (self.document_ptz.zoom() - 1.).abs() < 1e-4) - .widget_holder(), - PopoverButton::new() - .popover_layout(vec![ - LayoutGroup::Row { - widgets: vec![TextLabel::new("Canvas Navigation").bold(true).widget_holder()], - }, - LayoutGroup::Row { - widgets: vec![TextLabel::new( - " - Interactive controls in this\n\ - menu are coming soon.\n\ - \n\ - Pan:\n\ - • Middle Click Drag\n\ - \n\ - Tilt:\n\ - • Alt + Middle Click Drag\n\ - \n\ - Zoom:\n\ - • Shift + Middle Click Drag\n\ - • Ctrl + Scroll Wheel Roll - " - .trim(), - ) - .multiline(true) - .widget_holder()], - }, - ]) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - NumberInput::new(Some(self.navigation_handler.snapped_zoom(self.document_ptz.zoom()) * 100.)) - .unit("%") - .min(0.000001) - .max(1000000.) - .tooltip("Document zoom within the viewport") - .on_update(|number_input: &NumberInput| { - NavigationMessage::CanvasZoomSet { - zoom_factor: number_input.value.unwrap() / 100., - } - .into() - }) - .increment_behavior(NumberInputIncrementBehavior::Callback) - .increment_callback_decrease(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) - .increment_callback_increase(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) - .widget_holder(), ]; + widgets.extend(navigation_controls(&self.document_ptz, &self.navigation_handler, "Canvas")); + let tilt_value = self.navigation_handler.snapped_tilt(self.document_ptz.tilt) / (std::f64::consts::PI / 180.); if tilt_value.abs() > 0.00001 { widgets.extend([ @@ -2059,15 +1986,15 @@ impl DocumentMessageHandler { IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24) .hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into())) .tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" }) - .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedLocked)) + .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked)) .on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into()) .disabled(!has_selection) .widget_holder(), IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24) .hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into())) .tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" }) - .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility)) - .on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into()) + .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility)) + .on_update(|_| DocumentMessage::ToggleSelectedVisibility.into()) .disabled(!has_selection) .widget_holder(), ], @@ -2235,6 +2162,71 @@ impl<'a> ClickXRayIter<'a> { } } +pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHandler, tooltip_name: &str) -> [WidgetHolder; 6] { + [ + IconButton::new("ZoomIn", 24) + .tooltip("Zoom In") + .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease)) + .on_update(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) + .widget_holder(), + IconButton::new("ZoomOut", 24) + .tooltip("Zoom Out") + .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease)) + .on_update(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) + .widget_holder(), + IconButton::new("ZoomReset", 24) + .tooltip("Reset Tilt and Zoom to 100%") + .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent)) + .on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into()) + .disabled(ptz.tilt.abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4) + .widget_holder(), + PopoverButton::new() + .popover_layout(vec![ + LayoutGroup::Row { + widgets: vec![TextLabel::new(format!("{tooltip_name} Navigation")).bold(true).widget_holder()], + }, + LayoutGroup::Row { + widgets: vec![TextLabel::new({ + let tilt = if tooltip_name == "Canvas" { "Tilt:\n• Alt + Middle Click Drag\n\n" } else { "" }; + format!( + " + Interactive controls in this\n\ + menu are coming soon.\n\ + \n\ + Pan:\n\ + • Middle Click Drag\n\ + \n\ + {tilt}Zoom:\n\ + • Shift + Middle Click Drag\n\ + • Ctrl + Scroll Wheel Roll + " + ) + .trim() + }) + .multiline(true) + .widget_holder()], + }, + ]) + .widget_holder(), + Separator::new(SeparatorType::Related).widget_holder(), + NumberInput::new(Some(navigation_handler.snapped_zoom(ptz.zoom()) * 100.)) + .unit("%") + .min(0.000001) + .max(1000000.) + .tooltip(format!("{tooltip_name} Zoom")) + .on_update(|number_input: &NumberInput| { + NavigationMessage::CanvasZoomSet { + zoom_factor: number_input.value.unwrap() / 100., + } + .into() + }) + .increment_behavior(NumberInputIncrementBehavior::Callback) + .increment_callback_decrease(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) + .increment_callback_increase(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) + .widget_holder(), + ] +} + impl<'a> Iterator for ClickXRayIter<'a> { type Item = LayerNodeIdentifier; @@ -2266,3 +2258,41 @@ impl<'a> Iterator for ClickXRayIter<'a> { None } } + +// TODO: Eventually remove this (probably starting late 2024) +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct OldDocumentMessageHandler { + // ============================================ + // Fields that are saved in the document format + // ============================================ + // + /// The node graph that generates this document's artwork. + /// It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content. + pub network: OldNodeNetwork, + /// List of the [`NodeId`]s that are currently selected by the user. + pub selected_nodes: SelectedNodes, + /// 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 name of the document, which is displayed in the tab and title bar of the editor. + pub name: String, + /// 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, + /// The current pan, tilt, and zoom state of the viewport's view of the document canvas. + pub document_ptz: PTZ, + /// The current mode that the document is in, which starts out as Design Mode. This choice affects the editing behavior of the tools. + pub document_mode: DocumentMode, + /// The current view mode that the user has set for rendering the document within the viewport. + /// This is usually "Normal" but can be set to "Outline" or "Pixels" to see the canvas differently. + pub view_mode: ViewMode, + /// Sets whether or not all the viewport overlays should be drawn on top of the artwork. + /// This includes tool interaction visualizations (like the transform cage and path anchors/handles), the grid, and more. + pub overlays_visible: bool, + /// Sets whether or not the rulers should be drawn along the top and left edges of the viewport area. + pub rulers_visible: bool, + /// Sets whether or not the node graph is drawn (as an overlay) on top of the viewport area, or otherwise if it's hidden. + pub graph_view_overlay_open: bool, + /// The current user choices for snapping behavior, including whether snapping is enabled at all. + pub snapping_state: SnappingState, +} diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index a1929f95..bef8a6a8 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -181,7 +181,11 @@ impl MessageHandler> for Navigation }; ptz.tilt = 0.; ptz.set_zoom(1.); - responses.add(PortfolioMessage::UpdateDocumentWidgets); + if graph_view_overlay_open { + responses.add(NodeGraphMessage::UpdateGraphBarRight); + } else { + responses.add(PortfolioMessage::UpdateDocumentWidgets); + } responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); } @@ -192,6 +196,9 @@ impl MessageHandler> for Navigation }; ptz.tilt = angle_radians; responses.add(DocumentMessage::PTZUpdate); + if !graph_view_overlay_open { + responses.add(PortfolioMessage::UpdateDocumentWidgets); + } } NavigationMessage::CanvasZoomDecrease { center_on_mouse } => { let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { @@ -252,7 +259,11 @@ impl MessageHandler> for Navigation let zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, ipp); ptz.set_zoom(zoom); - responses.add(PortfolioMessage::UpdateDocumentWidgets); + if graph_view_overlay_open { + responses.add(NodeGraphMessage::UpdateGraphBarRight); + } else { + responses.add(PortfolioMessage::UpdateDocumentWidgets); + } responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); } @@ -281,6 +292,11 @@ impl MessageHandler> for Navigation ptz.tilt = self.snapped_tilt(ptz.tilt); ptz.set_zoom(self.snapped_zoom(ptz.zoom())); responses.add(DocumentMessage::PTZUpdate); + if graph_view_overlay_open { + responses.add(NodeGraphMessage::UpdateGraphBarRight); + } else { + responses.add(PortfolioMessage::UpdateDocumentWidgets); + } responses.add(NodeGraphMessage::SetGridAlignedEdges); // Reset the navigation operation now that it's done self.navigation_operation = NavigationOperation::None; @@ -336,7 +352,11 @@ impl MessageHandler> for Navigation ptz.set_zoom(1.); } - responses.add(PortfolioMessage::UpdateDocumentWidgets); + if graph_view_overlay_open { + responses.add(NodeGraphMessage::UpdateGraphBarRight); + } else { + responses.add(PortfolioMessage::UpdateDocumentWidgets); + } responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); } @@ -478,11 +498,7 @@ impl NavigationMessageHandler { } pub fn snapped_zoom(&self, zoom: f64) -> f64 { - if matches!(self.navigation_operation, NavigationOperation::Zoom { snap: true, .. }) { - *VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom) - } else { - zoom - } + snapped_zoom(&self.navigation_operation, zoom) } pub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 { @@ -523,3 +539,11 @@ impl NavigationMessageHandler { VIEWPORT_ZOOM_MIN_FRACTION_COVER / scale_factor } } + +pub fn snapped_zoom(navigation_operation: &NavigationOperation, zoom: f64) -> f64 { + if matches!(navigation_operation, NavigationOperation::Zoom { snap: true, .. }) { + *VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom) + } else { + zoom + } +} 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 7f109673..30f6d730 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 @@ -30,8 +30,7 @@ pub enum NodeGraphMessage { CreateNodeFromContextMenu { node_id: Option, node_type: String, - x: i32, - y: i32, + xy: Option<(i32, i32)>, }, CreateWire { output_connector: OutputConnector, @@ -182,6 +181,7 @@ pub enum NodeGraphMessage { node_graph_errors: GraphErrors, }, UpdateActionButtons, + UpdateGraphBarRight, UpdateInSelectedNetwork, SendSelectedNodes, } 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 38798a05..b6575ca8 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 @@ -1,7 +1,9 @@ use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath}; use super::{document_node_definitions, node_properties}; +use crate::consts::GRID_SIZE; 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; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType}; @@ -28,6 +30,8 @@ pub struct NodeGraphHandlerData<'a> { pub collapsed: &'a mut CollapsedLayers, pub ipp: &'a InputPreprocessorMessageHandler, pub graph_view_overlay_open: bool, + pub graph_fade_artwork_percentage: f64, + pub navigation_handler: &'a NavigationMessageHandler, } #[derive(Debug, Clone)] @@ -76,8 +80,10 @@ impl<'a> MessageHandler> for NodeGrap breadcrumb_network_path, document_id, collapsed, - graph_view_overlay_open, ipp, + graph_view_overlay_open, + graph_fade_artwork_percentage, + navigation_handler, } = data; match message { @@ -149,7 +155,15 @@ impl<'a> MessageHandler> for NodeGrap responses.add(PropertiesPanelMessage::Refresh); responses.add(NodeGraphMessage::RunDocumentGraph); } - NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, x, y } => { + NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => { + let (x, y) = if let Some((x, y)) = xy { + (x, y) + } else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) { + ((-node_graph_ptz.pan.x / GRID_SIZE as f64) as i32, (-node_graph_ptz.pan.y / GRID_SIZE as f64) as i32) + } else { + (0, 0) + }; + let node_id = node_id.unwrap_or_else(NodeId::new); let Some(document_node_type) = document_node_definitions::resolve_document_node_type(&node_type) else { @@ -1374,9 +1388,14 @@ impl<'a> MessageHandler> for NodeGrap } NodeGraphMessage::UpdateActionButtons => { if selection_network_path == breadcrumb_network_path { - self.update_selection_action_buttons(network_interface, breadcrumb_network_path, responses); + self.update_graph_bar_left(network_interface, breadcrumb_network_path, responses); + self.send_node_bar_layout(responses); } } + NodeGraphMessage::UpdateGraphBarRight => { + self.update_graph_bar_right(graph_fade_artwork_percentage, network_interface, breadcrumb_network_path, navigation_handler); + self.send_node_bar_layout(responses); + } NodeGraphMessage::UpdateInSelectedNetwork => responses.add(FrontendMessage::UpdateInSelectedNetwork { in_selected_network: selection_network_path == breadcrumb_network_path, }), @@ -1435,7 +1454,7 @@ impl NodeGraphMessageHandler { } /// Updates the buttons for visibility, locked, and preview - fn update_selection_action_buttons(&mut self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId], responses: &mut VecDeque) { + fn update_graph_bar_left(&mut self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId], responses: &mut VecDeque) { let Some(subgraph_path_names) = Self::collect_subgraph_names(network_interface, breadcrumb_network_path) else { // If a node in a nested network could not be found, exit the nested network let breadcrumb_network_path_len = breadcrumb_network_path.len(); @@ -1457,100 +1476,172 @@ impl NodeGraphMessageHandler { return; }; - let subgraph_path_names_length = subgraph_path_names.len(); - - let breadcrumb_trail = BreadcrumbTrailButtons::new(subgraph_path_names).on_update(move |index| { - DocumentMessage::ExitNestedNetwork { - steps_back: subgraph_path_names_length - (*index as usize) - 1, + let has_selection = selected_nodes.has_selected_nodes(); + let selection_includes_layers = network_interface.selected_nodes(&[]).unwrap().selected_layers(network_interface.document_metadata()).count() > 0; + let selection_all_locked = network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(network_interface).count() == 0; + let selection_all_visible = selected_nodes.selected_nodes().all(|id| { + if let Some(node) = network.nodes.get(id) { + node.visible + } else { + error!("Could not get node {id} in update_selection_action_buttons"); + true } - .into() }); - let mut widgets = if subgraph_path_names_length >= 2 { - vec![breadcrumb_trail.widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()] + let mut widgets = vec![ + PopoverButton::new() + .icon(Some("Node".to_string())) + .tooltip("Add a new node") + .popover_layout({ + let node_chooser = NodeCatalog::new() + .on_update(move |node_type| { + let node_id = NodeId::new(); + + Message::Batched(Box::new([ + NodeGraphMessage::CreateNodeFromContextMenu { + node_id: Some(node_id), + node_type: node_type.clone(), + xy: None, + } + .into(), + NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), + ])) + }) + .widget_holder(); + vec![LayoutGroup::Row { widgets: vec![node_chooser] }] + }) + .widget_holder(), + // + Separator::new(SeparatorType::Unrelated).widget_holder(), + // + IconButton::new("NewLayer", 24) + .tooltip("New Layer") + .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder)) + .on_update(|_| DocumentMessage::CreateEmptyFolder.into()) + .widget_holder(), + IconButton::new("Folder", 24) + .tooltip("Group Selected") + .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers)) + .on_update(|_| DocumentMessage::GroupSelectedLayers.into()) + .disabled(!has_selection) + .widget_holder(), + IconButton::new("Trash", 24) + .tooltip("Delete Selected") + .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers)) + .on_update(|_| DocumentMessage::DeleteSelectedLayers.into()) + .disabled(!has_selection) + .widget_holder(), + // + Separator::new(SeparatorType::Unrelated).widget_holder(), + // + IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24) + .hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into())) + .tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" }) + .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedLocked)) + .on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into()) + .disabled(!has_selection || !selection_includes_layers) + .widget_holder(), + IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24) + .hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into())) + .tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" }) + .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility)) + .on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into()) + .disabled(!has_selection) + .widget_holder(), + ]; + + let mut selection = selected_nodes.selected_nodes(); + let (selection, no_other_selections) = (selection.next(), selection.count() == 0); + let previewing = if matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) { + network.exports.iter().find_map(|export| { + let NodeInput::Node { node_id, .. } = export else { return None }; + Some(*node_id) + }) } else { - Vec::new() + None }; - let mut selection = selected_nodes.selected_nodes(); - // 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| { - if let Some(node) = network.nodes.get(id) { - node.visible - } else { - error!("Could not get node {id} in update_selection_action_buttons"); - true - } - }); - - // Check if multiple nodes are selected - let multiple_nodes = selection.next().is_some(); - - // Generate the visible/hidden button accordingly - 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" } 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(); - widgets.push(hide_button); - - 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()) { - // Is this node the current output - let is_output = network.outputs_contain(node_id); - let is_previewing = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }); - - let output_button = TextButton::new(if is_output && is_previewing { "End Preview" } else { "Preview" }) + if let Some(node_id) = previewing { + let button = TextButton::new("End Preview") .icon(Some("Rescale".to_string())) - .tooltip(if is_output { "Restore preview to the graph output" } else { "Preview selected node/layer" }.to_string() + " (Shortcut: Alt-click node/layer)") + .tooltip("Restore preview to the graph output") .on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into()) .widget_holder(); - widgets.push(output_button); + widgets.extend([Separator::new(SeparatorType::Unrelated).widget_holder(), button]); + } else if let Some(&node_id) = selection { + let selection_is_not_already_the_output = !network + .exports + .iter() + .any(|export| matches!(export, NodeInput::Node { node_id: export_node_id, .. } if *export_node_id == node_id)); + if selection_is_not_already_the_output && no_other_selections { + let button = TextButton::new("Preview") + .icon(Some("Rescale".to_string())) + .tooltip("Preview selected node/layer (Shortcut: Alt-click node/layer)") + .on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into()) + .widget_holder(); + widgets.extend([Separator::new(SeparatorType::Unrelated).widget_holder(), button]); + } + } + + let subgraph_path_names_length = subgraph_path_names.len(); + if subgraph_path_names_length >= 2 { + widgets.extend([ + Separator::new(SeparatorType::Unrelated).widget_holder(), + BreadcrumbTrailButtons::new(subgraph_path_names) + .on_update(move |index| { + DocumentMessage::ExitNestedNetwork { + steps_back: subgraph_path_names_length - (*index as usize) - 1, + } + .into() + }) + .widget_holder(), + ]); } self.widgets[0] = LayoutGroup::Row { widgets }; - self.send_node_bar_layout(responses); + } + + fn update_graph_bar_right( + &mut self, + graph_fade_artwork_percentage: f64, + network_interface: &NodeNetworkInterface, + breadcrumb_network_path: &[NodeId], + navigation_handler: &NavigationMessageHandler, + ) { + let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) else { + log::error!("Could not get node graph PTZ"); + return; + }; + + let mut widgets = vec![ + NumberInput::new(Some(graph_fade_artwork_percentage)) + .percentage() + .display_decimal_places(0) + .label("Fade Artwork") + .tooltip("Opacity of the graph background that covers the artwork") + .on_update(move |number_input: &NumberInput| { + DocumentMessage::SetGraphFadeArtwork { + percentage: number_input.value.unwrap_or(graph_fade_artwork_percentage), + } + .into() + }) + .widget_holder(), + Separator::new(SeparatorType::Unrelated).widget_holder(), + ]; + widgets.extend(navigation_controls(node_graph_ptz, navigation_handler, "Node Graph")); + widgets.extend([ + Separator::new(SeparatorType::Unrelated).widget_holder(), + TextButton::new("Node Graph") + .icon(Some("GraphViewOpen".into())) + .hover_icon(Some("GraphViewClosed".into())) + .tooltip("Hide Node Graph") + .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle)) + .on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into()) + .widget_holder(), + ]); + + self.widgets[1] = LayoutGroup::Row { widgets }; } /// Collate the properties panel sections for a node graph @@ -1587,7 +1678,18 @@ impl NodeGraphMessageHandler { 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, false, context))) + .filter_map(|node_id| { + network.nodes.get(node_id).map(|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 + }; + + node_properties::generate_node_properties(node, *node_id, pinned, context) + }) + }) .collect::>(); if !selected_nodes.is_empty() { return selected_nodes; @@ -1618,7 +1720,7 @@ impl NodeGraphMessageHandler { }; if pinned { - Some(node_properties::generate_node_properties(node, *node_id, true, context)) + Some(node_properties::generate_node_properties(node, *node_id, pinned, context)) } else { None } @@ -1655,23 +1757,22 @@ impl NodeGraphMessageHandler { }) .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), - { - let node_chooser = NodeCatalog::new() - .on_update(move |node_type| { - NodeGraphMessage::CreateNodeInLayerWithTransaction { - node_type: node_type.clone(), - layer: LayerNodeIdentifier::new_unchecked(layer), - } - .into() - }) - .widget_holder(); - let popover_layout = vec![LayoutGroup::Row { widgets: vec![node_chooser] }]; - PopoverButton::new() - .icon(Some("Node".to_string())) - .tooltip("Add an operation to the end of this layer's chain of nodes") - .popover_layout(popover_layout) - .widget_holder() - }, + PopoverButton::new() + .icon(Some("Node".to_string())) + .tooltip("Add an operation to the end of this layer's chain of nodes") + .popover_layout({ + let node_chooser = NodeCatalog::new() + .on_update(move |node_type| { + NodeGraphMessage::CreateNodeInLayerWithTransaction { + node_type: node_type.clone(), + layer: LayerNodeIdentifier::new_unchecked(layer), + } + .into() + }) + .widget_holder(); + vec![LayoutGroup::Row { widgets: vec![node_chooser] }] + }) + .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), ], }]; @@ -1689,7 +1790,16 @@ impl NodeGraphMessageHandler { } }) .filter_map(|(_, node_id)| network.nodes.get(&node_id).map(|node| (node, node_id))) - .map(|(node, node_id)| node_properties::generate_node_properties(node, node_id, false, context)) + .map(|(node, node_id)| { + 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 + }; + + node_properties::generate_node_properties(node, node_id, pinned, context) + }) .collect::>(); layer_properties.extend(node_properties); @@ -2125,7 +2235,7 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: // Skip not exposed inputs for efficiency let Some(value) = value else { continue }; - // Resolve the type (done in a seperate loop because it requires a mutable reference to the `network_interface`) + // Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`) let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path); value.ty = ty; value.type_source = type_source; @@ -2136,24 +2246,11 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: impl Default for NodeGraphMessageHandler { fn default() -> Self { - let right_side_widgets = vec![ - // TODO: Replace this with an "Add Node" button, also next to an "Add Layer" button - TextLabel::new("Right Click in Graph to Add Nodes").italic(true).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextButton::new("Node Graph") - .icon(Some("GraphViewOpen".into())) - .hover_icon(Some("GraphViewClosed".into())) - .tooltip("Hide Node Graph") - .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle)) - .on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into()) - .widget_holder(), - ]; - Self { network: Vec::new(), node_graph_errors: Vec::new(), has_selection: false, - widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: right_side_widgets }], + widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: Vec::new() }], drag_start: None, begin_dragging: false, drag_occurred: false, 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 023829ab..a6d62053 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -2569,6 +2569,14 @@ impl NodeNetworkInterface { && (input_count <= 2) } + pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> { + let Some(network_metadata) = self.network_metadata(network_path) else { + log::error!("Could not get nested network_metadata in node_graph_ptz_mut"); + return None; + }; + Some(&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz) + } + pub fn node_graph_ptz_mut(&mut self, network_path: &[NodeId]) -> Option<&mut PTZ> { let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get nested network_metadata in node_graph_ptz_mut"); diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 2a8fcbd7..2171cc00 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -10,7 +10,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::DocumentMessageData; use crate::messages::prelude::*; -use crate::messages::tool::utility_types::{HintData, HintGroup}; +use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use graph_craft::document::value::TaggedValue; @@ -25,6 +25,7 @@ use std::vec; pub struct PortfolioMessageData<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub preferences: &'a PreferencesMessageHandler, + pub current_tool: &'a ToolType, } #[derive(Debug, Default)] @@ -41,7 +42,7 @@ pub struct PortfolioMessageHandler { impl MessageHandler> for PortfolioMessageHandler { fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, data: PortfolioMessageData) { - let PortfolioMessageData { ipp, preferences } = data; + let PortfolioMessageData { ipp, preferences, current_tool } = data; match message { // Sub-messages @@ -73,6 +74,7 @@ impl MessageHandler> for PortfolioMes ipp, persistent_data: &self.persistent_data, executor: &mut self.executor, + current_tool, }; document.process_message(message, responses, document_inputs) } @@ -87,6 +89,7 @@ impl MessageHandler> for PortfolioMes ipp, persistent_data: &self.persistent_data, executor: &mut self.executor, + current_tool, }; document.process_message(message, responses, document_inputs) } @@ -754,10 +757,14 @@ impl MessageHandler> for PortfolioMes responses.add(OverlaysMessage::Draw); responses.add(BroadcastEvent::ToolAbort); responses.add(BroadcastEvent::SelectionChanged); - responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); + if node_graph_open { + responses.add(NodeGraphMessage::UpdateGraphBarRight); + } else { + responses.add(PortfolioMessage::UpdateDocumentWidgets); + } } PortfolioMessage::SubmitDocumentExport { file_name, diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 696c6ecb..ca855b90 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -25,6 +25,7 @@ pub struct ToolMessageHandler { pub tool_state: ToolFsmState, pub transform_layer_handler: TransformLayerMessageHandler, pub shape_editor: ShapeState, + pub tool_is_active: bool, } impl MessageHandler> for ToolMessageHandler { @@ -69,9 +70,10 @@ impl MessageHandler> for ToolMessageHandler { let old_tool = tool_data.active_tool_type; // Do nothing if switching to the same tool - if tool_type == old_tool { + if self.tool_is_active && tool_type == old_tool { return; } + self.tool_is_active = true; // Send the old and new tools a transition to their FSM Abort states let mut send_abort_to_tool = |tool_type, update_hints_and_cursor: bool| { @@ -85,6 +87,7 @@ impl MessageHandler> for ToolMessageHandler { shape_editor: &mut self.shape_editor, node_graph, }; + if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort { tool.process_message(tool_abort_message, responses, &mut data); } @@ -133,6 +136,11 @@ impl MessageHandler> for ToolMessageHandler { responses.add(BroadcastMessage::UnsubscribeEvent { message, on }); responses.add(OverlaysMessage::RemoveProvider(ARTBOARD_OVERLAY_PROVIDER)); + + responses.add(FrontendMessage::UpdateInputHints { hint_data: Default::default() }); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: Default::default() }); + + self.tool_is_active = false; } ToolMessage::InitTools => { // Subscribe the transform layer to selection change events @@ -141,6 +149,8 @@ impl MessageHandler> for ToolMessageHandler { send: Box::new(TransformLayerMessage::SelectionChanged.into()), }); + self.tool_is_active = true; + let tool_data = &mut self.tool_state.tool_data; let document_data = &self.tool_state.document_tool_data; let active_tool = &tool_data.active_tool_type; diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 827b4d83..870e827e 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -539,7 +539,6 @@ impl Fsm for PenToolFsmState { // Perform extension of an existing path let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); if let Some((layer, point, position)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) { - log::debug!("Should extend: {:?}", layer); tool_data.add_point(LastPoint { id: point, pos: position, @@ -550,7 +549,6 @@ impl Fsm for PenToolFsmState { tool_data.next_point = position; tool_data.next_handle_start = position; } else if let (Some(layer), None) = (selected_layers.next(), selected_layers.next()) { - log::debug!("Adding to layer: {:?}", layer); // Add the first point to a new layer // Generate first point let id = PointId::generate(); @@ -566,7 +564,6 @@ impl Fsm for PenToolFsmState { tool_data.next_point = pos; tool_data.next_handle_start = pos; } else { - log::debug!("Creating new layer"); // New path layer let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); let nodes = vec![(NodeId(0), node_type.default_node_template())]; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index c51734f1..561c38db 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -477,7 +477,7 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis } } -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct HintData(pub Vec); #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index 8faa6b3f..52139b5b 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -504,7 +504,7 @@ -
+
diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index c9ad9ae4..30c49b35 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -989,7 +989,7 @@ } &.disabled { - background: var(--color-3-darkgray); + background: rgba(var(--color-4-dimgray-rgb), 0.33); color: var(--color-a-softgray); .icon-label { @@ -1041,10 +1041,10 @@ } &.selected { - background: rgba(var(--color-5-dullgray-rgb), 0.5); + background: rgba(var(--color-5-dullgray-rgb), 0.33); &.in-selected-network { - background: rgba(var(--color-6-lowergray-rgb), 0.5); + background: rgba(var(--color-6-lowergray-rgb), 0.33); } } diff --git a/frontend/src/components/widgets/WidgetSection.svelte b/frontend/src/components/widgets/WidgetSection.svelte index 9f50d0e8..2dd66c27 100644 --- a/frontend/src/components/widgets/WidgetSection.svelte +++ b/frontend/src/components/widgets/WidgetSection.svelte @@ -27,18 +27,16 @@