From 185106132df395b10582800270d72f807e882b2d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 19 Aug 2023 01:01:01 -0700 Subject: [PATCH] Move node graph from panel to overlay on viewport --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy.yml | 2 +- .github/workflows/website.yml | 2 +- editor/src/dispatcher.rs | 1 - .../src/messages/frontend/frontend_message.rs | 8 + .../messages/input_mapper/default_mapping.rs | 14 +- .../input_mapper/input_mapper_message.rs | 6 + .../input_mapper/utility_types/macros.rs | 16 +- .../input_mapper/utility_types/misc.rs | 6 + .../input_preprocessor_message.rs | 4 +- .../input_preprocessor_message_handler.rs | 16 +- .../messages/layout/layout_message_handler.rs | 1 + .../layout/utility_types/layout_widget.rs | 4 +- .../navigation/navigation_message_handler.rs | 3 + .../messages/portfolio/portfolio_message.rs | 7 + .../portfolio/portfolio_message_handler.rs | 33 +- .../src/messages/tool/tool_message_handler.rs | 10 +- .../icon-16px-solid/graph-view-closed.svg | 3 + .../icon-16px-solid/graph-view-open.svg | 3 + .../src/components/panels/Document.svelte | 177 ++- .../NodeGraph.svelte => views/Graph.svelte} | 1000 ++++++++--------- .../components/window/workspace/Panel.svelte | 2 - .../window/workspace/Workspace.svelte | 9 +- frontend/src/io-managers/input.ts | 4 +- frontend/src/state-providers/document.ts | 20 + frontend/src/state-providers/node-graph.ts | 10 - frontend/src/utility-functions/icons.ts | 4 + frontend/src/wasm-communication/messages.ts | 37 +- frontend/wasm/src/editor_api.rs | 12 +- 29 files changed, 776 insertions(+), 640 deletions(-) create mode 100644 frontend/assets/icon-16px-solid/graph-view-closed.svg create mode 100644 frontend/assets/icon-16px-solid/graph-view-open.svg rename frontend/src/components/{panels/NodeGraph.svelte => views/Graph.svelte} (64%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 921ee9a42..176a2bb14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: - master env: CARGO_TERM_COLOR: always - INDEX_HTML_HEAD_REPLACEMENT: + INDEX_HTML_HEAD_REPLACEMENT: jobs: build: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e12753d3c..281eaa669 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,7 +18,7 @@ jobs: RUSTC_WRAPPER: /usr/bin/sccache CARGO_INCREMENTAL: 0 SCCACHE_DIR: /var/lib/github-actions/.cache - INDEX_HTML_HEAD_REPLACEMENT: + INDEX_HTML_HEAD_REPLACEMENT: steps: - name: 📥 Clone and checkout repository diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 298f33dfd..3281fbe06 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -13,7 +13,7 @@ on: - website/** env: CARGO_TERM_COLOR: always - INDEX_HTML_HEAD_REPLACEMENT: + INDEX_HTML_HEAD_REPLACEMENT: jobs: build: diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index d6cbb3c87..a8a3d7b3f 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -255,7 +255,6 @@ impl Dispatcher { #[cfg(test)] mod test { use crate::application::Editor; - use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; use crate::test_utils::EditorTestUtils; diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 1fd155542..1475b4974 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -72,6 +72,9 @@ pub enum FrontendMessage { #[serde(rename = "isDefault")] is_default: bool, }, + TriggerGraphViewOverlay { + open: bool, + }, TriggerImport, TriggerIndexedDbRemoveDocument { #[serde(rename = "documentId")] @@ -177,6 +180,11 @@ pub enum FrontendMessage { #[serde(rename = "setColorChoice")] set_color_choice: Option, }, + UpdateGraphViewOverlayButtonLayout { + #[serde(rename = "layoutTarget")] + layout_target: LayoutTarget, + diff: Vec, + }, UpdateImageData { #[serde(rename = "documentId")] document_id: u64, diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index ec83e10bc..9a2ff8c7f 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -330,13 +330,17 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection), // // PortfolioMessage - entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument), - entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import), + entry!(KeyUp(Space); action_dispatch=PortfolioMessage::GraphViewOverlayToggle), + entry!(KeyDownNoRepeat(Space); action_dispatch=PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: false }), entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument), entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument), entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), + entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument), + entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import), entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), + // + // FrontendMessage entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste), // // DialogMessage @@ -351,7 +355,7 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames), entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents), ]; - let (mut key_up, mut key_down, mut double_click, mut wheel_scroll, mut pointer_move) = mappings; + let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings; // TODO: Hardcode these 10 lines into 10 lines of declarations, or make this use a macro to do all 10 in one line const NUMBER_KEYS: [Key; 10] = [Digit0, Digit1, Digit2, Digit3, Digit4, Digit5, Digit6, Digit7, Digit8, Digit9]; @@ -367,7 +371,7 @@ pub fn default_mapping() -> Mapping { } let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones())); - for list in [&mut key_up, &mut key_down] { + for list in [&mut key_up, &mut key_down, &mut key_up_no_repeat, &mut key_down_no_repeat] { for sublist in list { sort(sublist); } @@ -379,6 +383,8 @@ pub fn default_mapping() -> Mapping { Mapping { key_up, key_down, + key_up_no_repeat, + key_down_no_repeat, double_click, wheel_scroll, pointer_move, diff --git a/editor/src/messages/input_mapper/input_mapper_message.rs b/editor/src/messages/input_mapper/input_mapper_message.rs index 67d77bdb1..32ec8e0e4 100644 --- a/editor/src/messages/input_mapper/input_mapper_message.rs +++ b/editor/src/messages/input_mapper/input_mapper_message.rs @@ -14,6 +14,12 @@ pub enum InputMapperMessage { #[remain::unsorted] #[child] KeyUp(Key), + #[remain::unsorted] + #[child] + KeyDownNoRepeat(Key), + #[remain::unsorted] + #[child] + KeyUpNoRepeat(Key), // Messages DoubleClick, diff --git a/editor/src/messages/input_mapper/utility_types/macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs index fb61db896..3f2c6337b 100644 --- a/editor/src/messages/input_mapper/utility_types/macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -50,6 +50,16 @@ macro_rules! entry { input: InputMapperMessage::KeyUp(Key::$refresh), modifiers: modifiers!(), }, + MappingEntry { + action: $action_dispatch.into(), + input: InputMapperMessage::KeyDownNoRepeat(Key::$refresh), + modifiers: modifiers!(), + }, + MappingEntry { + action: $action_dispatch.into(), + input: InputMapperMessage::KeyUpNoRepeat(Key::$refresh), + modifiers: modifiers!(), + }, )* )* ]] @@ -65,6 +75,8 @@ macro_rules! mapping { [$($entry:expr),* $(,)?] => {{ let mut key_up = KeyMappingEntries::key_array(); let mut key_down = KeyMappingEntries::key_array(); + let mut key_up_no_repeat = KeyMappingEntries::key_array(); + let mut key_down_no_repeat = KeyMappingEntries::key_array(); let mut double_click = KeyMappingEntries::new(); let mut wheel_scroll = KeyMappingEntries::new(); let mut pointer_move = KeyMappingEntries::new(); @@ -77,6 +89,8 @@ macro_rules! mapping { let corresponding_list = match entry.input { InputMapperMessage::KeyDown(key) => &mut key_down[key as usize], InputMapperMessage::KeyUp(key) => &mut key_up[key as usize], + InputMapperMessage::KeyDownNoRepeat(key) => &mut key_down_no_repeat[key as usize], + InputMapperMessage::KeyUpNoRepeat(key) => &mut key_up_no_repeat[key as usize], InputMapperMessage::DoubleClick => &mut double_click, InputMapperMessage::WheelScroll => &mut wheel_scroll, InputMapperMessage::PointerMove => &mut pointer_move, @@ -87,7 +101,7 @@ macro_rules! mapping { } )* - (key_up, key_down, double_click, wheel_scroll, pointer_move) + (key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move) }}; } diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index d48b4da7e..7e3d3cecf 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize}; pub struct Mapping { pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS], pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS], + pub key_up_no_repeat: [KeyMappingEntries; NUMBER_OF_KEYS], + pub key_down_no_repeat: [KeyMappingEntries; NUMBER_OF_KEYS], pub double_click: KeyMappingEntries, pub wheel_scroll: KeyMappingEntries, pub pointer_move: KeyMappingEntries, @@ -40,6 +42,8 @@ impl Mapping { match message { InputMapperMessage::KeyDown(key) => &self.key_down[*key as usize], InputMapperMessage::KeyUp(key) => &self.key_up[*key as usize], + InputMapperMessage::KeyDownNoRepeat(key) => &self.key_down_no_repeat[*key as usize], + InputMapperMessage::KeyUpNoRepeat(key) => &self.key_up_no_repeat[*key as usize], InputMapperMessage::DoubleClick => &self.double_click, InputMapperMessage::WheelScroll => &self.wheel_scroll, InputMapperMessage::PointerMove => &self.pointer_move, @@ -50,6 +54,8 @@ impl Mapping { match message { InputMapperMessage::KeyDown(key) => &mut self.key_down[*key as usize], InputMapperMessage::KeyUp(key) => &mut self.key_up[*key as usize], + InputMapperMessage::KeyDownNoRepeat(key) => &mut self.key_down_no_repeat[*key as usize], + InputMapperMessage::KeyUpNoRepeat(key) => &mut self.key_up_no_repeat[*key as usize], InputMapperMessage::DoubleClick => &mut self.double_click, InputMapperMessage::WheelScroll => &mut self.wheel_scroll, InputMapperMessage::PointerMove => &mut self.pointer_move, diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs index 943dd145a..ca68300f2 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs @@ -12,8 +12,8 @@ use serde::{Deserialize, Serialize}; pub enum InputPreprocessorMessage { BoundsOfViewports { bounds_of_viewports: Vec }, DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, - KeyDown { key: Key, modifier_keys: ModifierKeys }, - KeyUp { key: Key, modifier_keys: ModifierKeys }, + KeyDown { key: Key, key_repeat: bool, modifier_keys: ModifierKeys }, + KeyUp { key: Key, key_repeat: bool, modifier_keys: ModifierKeys }, PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 4cccae9d2..600f95774 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -39,14 +39,20 @@ impl MessageHandler for InputP responses.add(InputMapperMessage::DoubleClick); } - InputPreprocessorMessage::KeyDown { key, modifier_keys } => { + InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); self.keyboard.set(key as usize); + if !key_repeat { + responses.add(InputMapperMessage::KeyDownNoRepeat(key)); + } responses.add(InputMapperMessage::KeyDown(key)); } - InputPreprocessorMessage::KeyUp { key, modifier_keys } => { + InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); self.keyboard.unset(key as usize); + if !key_repeat { + responses.add(InputMapperMessage::KeyUpNoRepeat(key)); + } responses.add(InputMapperMessage::KeyUp(key)); } InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => { @@ -218,8 +224,9 @@ mod test { input_preprocessor.keyboard.set(Key::Control as usize); let key = Key::KeyA; + let key_repeat = false; let modifier_keys = ModifierKeys::empty(); - let message = InputPreprocessorMessage::KeyDown { key, modifier_keys }; + let message = InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys }; let mut responses = VecDeque::new(); @@ -234,8 +241,9 @@ mod test { let mut input_preprocessor = InputPreprocessorMessageHandler::default(); let key = Key::KeyS; + let key_repeat = false; let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT; - let message = InputPreprocessorMessage::KeyUp { key, modifier_keys }; + let message = InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys }; let mut responses = VecDeque::new(); diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index f32f3bc4b..656bb30c3 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -291,6 +291,7 @@ impl LayoutMessageHandler { LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { layout_target, diff }, LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff }, LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff }, + LayoutTarget::GraphViewOverlayButton => FrontendMessage::UpdateGraphViewOverlayButtonLayout { layout_target, diff }, LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { layout_target, diff }, LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"), LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff }, diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 762d94a31..6a3e49b75 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -21,11 +21,13 @@ pub enum LayoutTarget { DocumentBar, /// Contains the dropdown for design / select / guide mode found on the top left of the canvas. DocumentMode, + /// The button below the tool shelf and directly above the working colors which lets the user toggle the node graph overlaid on the canvas. + GraphViewOverlayButton, /// Options for opacity seen at the top of the Layers panel. LayerTreeOptions, /// The dropdown menu at the very top of the application: File, Edit, etc. MenuBar, - /// Bar at the top of the node graph containing the location and the 'preview' and 'hide' buttons. + /// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons. NodeGraphBar, /// The bar at the top of the Properties panel containing the layer name and icon. PropertiesOptions, 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 c5d41338e..344fa0a9f 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -233,6 +233,9 @@ impl MessageHandler, &InputPre TranslateCanvasBegin => { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); responses.add(FrontendMessage::UpdateInputHints { hint_data: HintData(Vec::new()) }); + // Because the pan key shares the Spacebar with toggling the graph view overlay, now that we've begun panning, + // we need to prevent the graph view overlay from toggling when the Spacebar is released. + responses.add(PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: true }); self.panning = true; self.mouse_position = ipp.mouse.position; diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index ed7840c96..a2323553d 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -54,6 +54,13 @@ pub enum PortfolioMessage { data: Vec, is_default: bool, }, + GraphViewOverlay { + open: bool, + }, + GraphViewOverlayToggle, + GraphViewOverlayToggleDisabled { + disabled: bool, + }, ImaginateCheckServerStatus, ImaginatePollServerStatus, ImaginatePreferences, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 18ca9d521..3cff381d4 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -3,6 +3,7 @@ use crate::application::generate_uuid; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; use crate::messages::dialog::simple_dialogs; use crate::messages::frontend::utility_types::FrontendDocumentDetails; +use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::prelude::*; @@ -24,10 +25,12 @@ pub struct PortfolioMessageHandler { menu_bar_message_handler: MenuBarMessageHandler, documents: HashMap, document_ids: Vec, - pub executor: NodeGraphExecutor, active_document_id: Option, + graph_view_overlay_open: bool, + graph_view_overlay_toggle_disabled: bool, copy_buffer: [Vec; INTERNAL_CLIPBOARD_COUNT as usize], pub persistent_data: PersistentData, + pub executor: NodeGraphExecutor, } impl MessageHandler for PortfolioMessageHandler { @@ -220,6 +223,31 @@ impl MessageHandler { + self.graph_view_overlay_open = open; + + let layout = WidgetLayout::new(vec![LayoutGroup::Row { + widgets: vec![IconButton::new(if open { "GraphViewOpen" } else { "GraphViewClosed" }, 32) + .tooltip(if open { "Hide Node Graph" } else { "Show Node Graph" }) + .tooltip_shortcut(action_keys!(PortfolioMessageDiscriminant::GraphViewOverlayToggle)) + .on_update(move |_| PortfolioMessage::GraphViewOverlay { open: !open }.into()) + .widget_holder()], + }]); + responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(layout), + layout_target: LayoutTarget::GraphViewOverlayButton, + }); + + responses.add(FrontendMessage::TriggerGraphViewOverlay { open }); + } + PortfolioMessage::GraphViewOverlayToggle => { + if !self.graph_view_overlay_toggle_disabled { + responses.add(PortfolioMessage::GraphViewOverlay { open: !self.graph_view_overlay_open }); + } + } + PortfolioMessage::GraphViewOverlayToggleDisabled { disabled } => { + self.graph_view_overlay_toggle_disabled = disabled; + } PortfolioMessage::ImaginateCheckServerStatus => { let server_status = self.persistent_data.imaginate.server_status().clone(); self.persistent_data.imaginate.poll_server_check(); @@ -519,6 +547,8 @@ impl MessageHandler ActionList { let mut common = actions!(PortfolioMessageDiscriminant; + GraphViewOverlayToggle, + GraphViewOverlayToggleDisabled, CloseActiveDocumentWithConfirmation, CloseAllDocuments, Import, @@ -618,6 +648,7 @@ impl PortfolioMessageHandler { responses.add(PortfolioMessage::SelectDocument { document_id }); responses.add(PortfolioMessage::LoadDocumentResources { document_id }); responses.add(PortfolioMessage::UpdateDocumentWidgets); + responses.add(PortfolioMessage::GraphViewOverlay { open: self.graph_view_overlay_open }); responses.add(ToolMessage::InitTools); responses.add(PropertiesPanelMessage::Init); responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 73d7008ef..4ddfe7f52 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -194,13 +194,13 @@ impl MessageHandler { let document_data = &mut self.tool_state.document_tool_data; document_data.primary_color = color; - self.tool_state.document_tool_data.update_working_colors(responses); + self.tool_state.document_tool_data.update_working_colors(responses); // TODO: Make this an event } ToolMessage::SelectRandomPrimaryColor => { // Select a random primary color (rgba) based on an UUID @@ -213,20 +213,20 @@ impl MessageHandler { let document_data = &mut self.tool_state.document_tool_data; document_data.secondary_color = color; - document_data.update_working_colors(responses); + document_data.update_working_colors(responses); // TODO: Make this an event } ToolMessage::SwapColors => { let document_data = &mut self.tool_state.document_tool_data; std::mem::swap(&mut document_data.primary_color, &mut document_data.secondary_color); - document_data.update_working_colors(responses); + document_data.update_working_colors(responses); // TODO: Make this an event } // Sub-messages diff --git a/frontend/assets/icon-16px-solid/graph-view-closed.svg b/frontend/assets/icon-16px-solid/graph-view-closed.svg new file mode 100644 index 000000000..a38e14fd1 --- /dev/null +++ b/frontend/assets/icon-16px-solid/graph-view-closed.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icon-16px-solid/graph-view-open.svg b/frontend/assets/icon-16px-solid/graph-view-open.svg new file mode 100644 index 000000000..2220dd11c --- /dev/null +++ b/frontend/assets/icon-16px-solid/graph-view-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index 7215ed780..0b347b86d 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -20,11 +20,13 @@ UpdateMouseCursor, UpdateDocumentNodeRender, UpdateDocumentTransform, + TriggerGraphViewOverlay, } from "@graphite/wasm-communication/messages"; import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; + import Graph from "@graphite/components/views/Graph.svelte"; import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte"; import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; @@ -38,6 +40,9 @@ const editor = getContext("editor"); const document = getContext("document"); + // Graph view overlay + let graphViewOverlayOpen = false; + // Interactive text editing let textInput: undefined | HTMLDivElement = undefined; let showTextInput: boolean; @@ -135,6 +140,7 @@ // Replace the placeholders with the actual canvas elements placeholders.forEach((placeholder) => { const canvasName = placeholder.getAttribute("data-canvas-placeholder"); + if (!canvasName) return; // Get the canvas element from the global storage const canvas = (window as any).imageCanvases[canvasName]; placeholder.replaceWith(canvas); @@ -332,6 +338,11 @@ } onMount(() => { + // Show or hide the graph view overlay + editor.subscriptions.subscribeJsMessage(TriggerGraphViewOverlay, (triggerGraphViewOverlay) => { + graphViewOverlayOpen = triggerGraphViewOverlay.open; + }); + // Update rendered SVGs editor.subscriptions.subscribeJsMessage(UpdateDocumentArtwork, async (data) => { await tick(); @@ -425,23 +436,30 @@ - - - + + {#if !graphViewOverlayOpen} + + - + - + + {:else} + + {/if} - - - + {#if !graphViewOverlayOpen} + + + + {/if} - + + @@ -485,6 +503,9 @@ {/if} +
+ +
* { + pointer-events: auto; + } } - // Prevent inheritance from reaching the child elements - > * { - pointer-events: auto; + .text-input div { + cursor: text; + background: none; + border: none; + margin: 0; + padding: 0; + overflow: visible; + white-space: pre-wrap; + display: inline-block; + // Workaround to force Chrome to display the flashing text entry cursor when text is empty + padding-left: 1px; + margin-left: -1px; + + &:focus { + border: none; + outline: none; // Ok for contenteditable element + margin: -1px; + } } } - .text-input div { - cursor: text; - background: none; - border: none; - margin: 0; - padding: 0; - overflow: visible; - white-space: pre-wrap; - display: inline-block; - // Workaround to force Chrome to display the flashing text entry cursor when text is empty - padding-left: 1px; - margin-left: -1px; + .graph-view { + pointer-events: none; + transition: opacity 0.1s ease-in-out; + opacity: 0; - &:focus { - border: none; - outline: none; // Ok for contenteditable element - margin: -1px; + &.open { + cursor: auto; + pointer-events: auto; + opacity: 1; } + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--color-2-mildblack); + opacity: var(--fade-artwork); + pointer-events: none; + } + } + + .fade-artwork, + .graph { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } } } diff --git a/frontend/src/components/panels/NodeGraph.svelte b/frontend/src/components/views/Graph.svelte similarity index 64% rename from frontend/src/components/panels/NodeGraph.svelte rename to frontend/src/components/views/Graph.svelte index c7fb6f031..22a8f74d6 100644 --- a/frontend/src/components/panels/NodeGraph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -10,7 +10,6 @@ import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; - import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; import type { Editor } from "@graphite/wasm-communication/editor"; import type { NodeGraphState } from "@graphite/state-providers/node-graph"; @@ -45,7 +44,6 @@ $: gridSpacing = calculateGridSpacing(transform.scale); $: dotRadius = 1 + Math.floor(transform.scale - 0.5 + 0.001) / 2; - $: nodeGraphBarLayout = $nodeGraph.nodeGraphBarLayout; $: nodeCategories = buildNodeCategories($nodeGraph.nodeTypes, searchTerm); $: nodeListX = ((nodeListLocation?.x || 0) * GRID_SIZE + transform.x) * transform.scale; $: nodeListY = ((nodeListLocation?.y || 0) * GRID_SIZE + transform.y) * transform.scale; @@ -569,92 +567,169 @@ }); - - - - -
- - {#if nodeListLocation} - - (searchTerm = detail)} bind:this={nodeSearchInput} /> -
- {#each nodeCategories as nodeCategory} -
- - - {nodeCategory[0]} - - {#each nodeCategory[1].nodes as nodeType} - createNode(nodeType.name)} /> - {/each} -
- {:else} -
No search results
- {/each} -
-
- {/if} - -
- - {#each linkPaths as { pathString, dataType, thick }} - + + + {#if nodeListLocation} + + (searchTerm = detail)} bind:this={nodeSearchInput} /> +
+ {#each nodeCategories as nodeCategory} +
+ + + {nodeCategory[0]} + + {#each nodeCategory[1].nodes as nodeType} + createNode(nodeType.name)} /> + {/each} +
+ {:else} +
No search results
{/each} - -
- -
- - {#each $nodeGraph.nodes.filter((node) => node.displayName === "Layer") as node (String(node.id))} - {@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]} - {@const clipPathId = `${Math.random()}`.substring(2)} - {@const stackDatainput = node.exposedInputs[0]} -
-
- -
+
+ + {/if} + +
+ + {#each linkPaths as { pathString, dataType, thick }} + + {/each} + +
+ +
+ + {#each $nodeGraph.nodes.filter((node) => node.displayName === "Layer") as node (String(node.id))} + {@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]} + {@const clipPathId = `${Math.random()}`.substring(2)} + {@const stackDatainput = node.exposedInputs[0]} +
+
+ +
+ + + +
+
+ {@html node.thumbnailSvg} + {#if node.primaryOutput} + + + {/if} + + + +
+
+ {node.displayName} +
+ + + + + + + + +
+ {/each} + + {#each $nodeGraph.nodes.filter((node) => node.displayName !== "Layer") as node (String(node.id))} + {@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]} + {@const clipPathId = `${Math.random()}`.substring(2)} +
+ +
+ + {node.displayName} +
+ + {#if exposedInputsOutputs.length > 0} +
+ {#each exposedInputsOutputs as parameter, index} +
+
+ {parameter.name} +
+ {/each} +
+ {/if} + +
+ {#if node.primaryInput} + -
-
- {@html node.thumbnailSvg} - {#if node.primaryOutput} - - - - {/if} - - - -
-
- {node.displayName} -
- - - - - - - - -
- {/each} - - {#each $nodeGraph.nodes.filter((node) => node.displayName !== "Layer") as node (String(node.id))} - {@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]} - {@const clipPathId = `${Math.random()}`.substring(2)} -
- -
- - {node.displayName} -
- - {#if exposedInputsOutputs.length > 0} -
- {#each exposedInputsOutputs as parameter, index} -
-
- {parameter.name} -
- {/each} -
{/if} - -
- {#if node.primaryInput} - - - - {/if} - {#each node.exposedInputs as parameter, index} - {#if index < node.exposedInputs.length} - - - - {/if} - {/each} -
- -
- {#if node.primaryOutput} - - - - {/if} - {#each node.exposedOutputs as parameter} + {#each node.exposedInputs as parameter, index} + {#if index < node.exposedInputs.length} - {/each} -
- - - - - - - + {/if} + {/each}
- {/each} -
- - + +
+ {#if node.primaryOutput} + + + + {/if} + {#each node.exposedOutputs as parameter} + + + + {/each} +
+ + + + + + + +
+ {/each} +
+