Move node graph from panel to overlay on viewport

This commit is contained in:
Keavon Chambers 2023-08-19 01:01:01 -07:00
parent d74e4b2ab3
commit 185106132d
29 changed files with 776 additions and 640 deletions

View File

@ -9,7 +9,7 @@ on:
- master - master
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.outbound-links.file-downloads.js"></script> INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.js"></script>
jobs: jobs:
build: build:

View File

@ -18,7 +18,7 @@ jobs:
RUSTC_WRAPPER: /usr/bin/sccache RUSTC_WRAPPER: /usr/bin/sccache
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
SCCACHE_DIR: /var/lib/github-actions/.cache SCCACHE_DIR: /var/lib/github-actions/.cache
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="editor.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.outbound-links.file-downloads.js"></script> INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="editor.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.js"></script>
steps: steps:
- name: 📥 Clone and checkout repository - name: 📥 Clone and checkout repository

View File

@ -13,7 +13,7 @@ on:
- website/** - website/**
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.outbound-links.file-downloads.js"></script> INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.js"></script>
jobs: jobs:
build: build:

View File

@ -255,7 +255,6 @@ impl Dispatcher {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::application::Editor; use crate::application::Editor;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::test_utils::EditorTestUtils; use crate::test_utils::EditorTestUtils;

View File

@ -72,6 +72,9 @@ pub enum FrontendMessage {
#[serde(rename = "isDefault")] #[serde(rename = "isDefault")]
is_default: bool, is_default: bool,
}, },
TriggerGraphViewOverlay {
open: bool,
},
TriggerImport, TriggerImport,
TriggerIndexedDbRemoveDocument { TriggerIndexedDbRemoveDocument {
#[serde(rename = "documentId")] #[serde(rename = "documentId")]
@ -177,6 +180,11 @@ pub enum FrontendMessage {
#[serde(rename = "setColorChoice")] #[serde(rename = "setColorChoice")]
set_color_choice: Option<String>, set_color_choice: Option<String>,
}, },
UpdateGraphViewOverlayButtonLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdateImageData { UpdateImageData {
#[serde(rename = "documentId")] #[serde(rename = "documentId")]
document_id: u64, document_id: u64,

View File

@ -330,13 +330,17 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection), entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection),
// //
// PortfolioMessage // PortfolioMessage
entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument), entry!(KeyUp(Space); action_dispatch=PortfolioMessage::GraphViewOverlayToggle),
entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import), entry!(KeyDownNoRepeat(Space); action_dispatch=PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: false }),
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument), entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument), entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), 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(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { 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), entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
// //
// DialogMessage // DialogMessage
@ -351,7 +355,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames), entry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames),
entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents), 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 // 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]; 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())); 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 { for sublist in list {
sort(sublist); sort(sublist);
} }
@ -379,6 +383,8 @@ pub fn default_mapping() -> Mapping {
Mapping { Mapping {
key_up, key_up,
key_down, key_down,
key_up_no_repeat,
key_down_no_repeat,
double_click, double_click,
wheel_scroll, wheel_scroll,
pointer_move, pointer_move,

View File

@ -14,6 +14,12 @@ pub enum InputMapperMessage {
#[remain::unsorted] #[remain::unsorted]
#[child] #[child]
KeyUp(Key), KeyUp(Key),
#[remain::unsorted]
#[child]
KeyDownNoRepeat(Key),
#[remain::unsorted]
#[child]
KeyUpNoRepeat(Key),
// Messages // Messages
DoubleClick, DoubleClick,

View File

@ -50,6 +50,16 @@ macro_rules! entry {
input: InputMapperMessage::KeyUp(Key::$refresh), input: InputMapperMessage::KeyUp(Key::$refresh),
modifiers: modifiers!(), 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),* $(,)?] => {{ [$($entry:expr),* $(,)?] => {{
let mut key_up = KeyMappingEntries::key_array(); let mut key_up = KeyMappingEntries::key_array();
let mut key_down = 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 double_click = KeyMappingEntries::new();
let mut wheel_scroll = KeyMappingEntries::new(); let mut wheel_scroll = KeyMappingEntries::new();
let mut pointer_move = KeyMappingEntries::new(); let mut pointer_move = KeyMappingEntries::new();
@ -77,6 +89,8 @@ macro_rules! mapping {
let corresponding_list = match entry.input { let corresponding_list = match entry.input {
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize], InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
InputMapperMessage::KeyUp(key) => &mut key_up[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::DoubleClick => &mut double_click,
InputMapperMessage::WheelScroll => &mut wheel_scroll, InputMapperMessage::WheelScroll => &mut wheel_scroll,
InputMapperMessage::PointerMove => &mut pointer_move, 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)
}}; }};
} }

View File

@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize};
pub struct Mapping { pub struct Mapping {
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS], pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
pub key_down: [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 double_click: KeyMappingEntries,
pub wheel_scroll: KeyMappingEntries, pub wheel_scroll: KeyMappingEntries,
pub pointer_move: KeyMappingEntries, pub pointer_move: KeyMappingEntries,
@ -40,6 +42,8 @@ impl Mapping {
match message { match message {
InputMapperMessage::KeyDown(key) => &self.key_down[*key as usize], InputMapperMessage::KeyDown(key) => &self.key_down[*key as usize],
InputMapperMessage::KeyUp(key) => &self.key_up[*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::DoubleClick => &self.double_click,
InputMapperMessage::WheelScroll => &self.wheel_scroll, InputMapperMessage::WheelScroll => &self.wheel_scroll,
InputMapperMessage::PointerMove => &self.pointer_move, InputMapperMessage::PointerMove => &self.pointer_move,
@ -50,6 +54,8 @@ impl Mapping {
match message { match message {
InputMapperMessage::KeyDown(key) => &mut self.key_down[*key as usize], InputMapperMessage::KeyDown(key) => &mut self.key_down[*key as usize],
InputMapperMessage::KeyUp(key) => &mut self.key_up[*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::DoubleClick => &mut self.double_click,
InputMapperMessage::WheelScroll => &mut self.wheel_scroll, InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
InputMapperMessage::PointerMove => &mut self.pointer_move, InputMapperMessage::PointerMove => &mut self.pointer_move,

View File

@ -12,8 +12,8 @@ use serde::{Deserialize, Serialize};
pub enum InputPreprocessorMessage { pub enum InputPreprocessorMessage {
BoundsOfViewports { bounds_of_viewports: Vec<ViewportBounds> }, BoundsOfViewports { bounds_of_viewports: Vec<ViewportBounds> },
DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
KeyDown { key: Key, modifier_keys: ModifierKeys }, KeyDown { key: Key, key_repeat: bool, modifier_keys: ModifierKeys },
KeyUp { key: Key, modifier_keys: ModifierKeys }, KeyUp { key: Key, key_repeat: bool, modifier_keys: ModifierKeys },
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },

View File

@ -39,14 +39,20 @@ impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputP
responses.add(InputMapperMessage::DoubleClick); 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.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
self.keyboard.set(key as usize); self.keyboard.set(key as usize);
if !key_repeat {
responses.add(InputMapperMessage::KeyDownNoRepeat(key));
}
responses.add(InputMapperMessage::KeyDown(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.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
self.keyboard.unset(key as usize); self.keyboard.unset(key as usize);
if !key_repeat {
responses.add(InputMapperMessage::KeyUpNoRepeat(key));
}
responses.add(InputMapperMessage::KeyUp(key)); responses.add(InputMapperMessage::KeyUp(key));
} }
InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => { InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => {
@ -218,8 +224,9 @@ mod test {
input_preprocessor.keyboard.set(Key::Control as usize); input_preprocessor.keyboard.set(Key::Control as usize);
let key = Key::KeyA; let key = Key::KeyA;
let key_repeat = false;
let modifier_keys = ModifierKeys::empty(); 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(); let mut responses = VecDeque::new();
@ -234,8 +241,9 @@ mod test {
let mut input_preprocessor = InputPreprocessorMessageHandler::default(); let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let key = Key::KeyS; let key = Key::KeyS;
let key_repeat = false;
let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT; 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(); let mut responses = VecDeque::new();

View File

@ -291,6 +291,7 @@ impl LayoutMessageHandler {
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { layout_target, diff }, LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { layout_target, diff },
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff }, LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { 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::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { layout_target, diff },
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"), LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff }, LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff },

View File

@ -21,11 +21,13 @@ pub enum LayoutTarget {
DocumentBar, DocumentBar,
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas. /// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
DocumentMode, 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. /// Options for opacity seen at the top of the Layers panel.
LayerTreeOptions, LayerTreeOptions,
/// The dropdown menu at the very top of the application: File, Edit, etc. /// The dropdown menu at the very top of the application: File, Edit, etc.
MenuBar, 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, NodeGraphBar,
/// The bar at the top of the Properties panel containing the layer name and icon. /// The bar at the top of the Properties panel containing the layer name and icon.
PropertiesOptions, PropertiesOptions,

View File

@ -233,6 +233,9 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
TranslateCanvasBegin => { TranslateCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
responses.add(FrontendMessage::UpdateInputHints { hint_data: HintData(Vec::new()) }); 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.panning = true;
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;

View File

@ -54,6 +54,13 @@ pub enum PortfolioMessage {
data: Vec<u8>, data: Vec<u8>,
is_default: bool, is_default: bool,
}, },
GraphViewOverlay {
open: bool,
},
GraphViewOverlayToggle,
GraphViewOverlayToggleDisabled {
disabled: bool,
},
ImaginateCheckServerStatus, ImaginateCheckServerStatus,
ImaginatePollServerStatus, ImaginatePollServerStatus,
ImaginatePreferences, ImaginatePreferences,

View File

@ -3,6 +3,7 @@ use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
use crate::messages::dialog::simple_dialogs; use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails; 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::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::prelude::*; use crate::messages::prelude::*;
@ -24,10 +25,12 @@ pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler, menu_bar_message_handler: MenuBarMessageHandler,
documents: HashMap<u64, DocumentMessageHandler>, documents: HashMap<u64, DocumentMessageHandler>,
document_ids: Vec<u64>, document_ids: Vec<u64>,
pub executor: NodeGraphExecutor,
active_document_id: Option<u64>, active_document_id: Option<u64>,
graph_view_overlay_open: bool,
graph_view_overlay_toggle_disabled: bool,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize], copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
pub persistent_data: PersistentData, pub persistent_data: PersistentData,
pub executor: NodeGraphExecutor,
} }
impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)> for PortfolioMessageHandler { impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)> for PortfolioMessageHandler {
@ -220,6 +223,31 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
self.persistent_data.font_cache.insert(font, preview_url, data, is_default); self.persistent_data.font_cache.insert(font, preview_url, data, is_default);
self.executor.update_font_cache(self.persistent_data.font_cache.clone()); self.executor.update_font_cache(self.persistent_data.font_cache.clone());
} }
PortfolioMessage::GraphViewOverlay { open } => {
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 => { PortfolioMessage::ImaginateCheckServerStatus => {
let server_status = self.persistent_data.imaginate.server_status().clone(); let server_status = self.persistent_data.imaginate.server_status().clone();
self.persistent_data.imaginate.poll_server_check(); self.persistent_data.imaginate.poll_server_check();
@ -519,6 +547,8 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
fn actions(&self) -> ActionList { fn actions(&self) -> ActionList {
let mut common = actions!(PortfolioMessageDiscriminant; let mut common = actions!(PortfolioMessageDiscriminant;
GraphViewOverlayToggle,
GraphViewOverlayToggleDisabled,
CloseActiveDocumentWithConfirmation, CloseActiveDocumentWithConfirmation,
CloseAllDocuments, CloseAllDocuments,
Import, Import,
@ -618,6 +648,7 @@ impl PortfolioMessageHandler {
responses.add(PortfolioMessage::SelectDocument { document_id }); responses.add(PortfolioMessage::SelectDocument { document_id });
responses.add(PortfolioMessage::LoadDocumentResources { document_id }); responses.add(PortfolioMessage::LoadDocumentResources { document_id });
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(PortfolioMessage::GraphViewOverlay { open: self.graph_view_overlay_open });
responses.add(ToolMessage::InitTools); responses.add(ToolMessage::InitTools);
responses.add(PropertiesPanelMessage::Init); responses.add(PropertiesPanelMessage::Init);
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });

View File

@ -194,13 +194,13 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
document_data.primary_color = Color::BLACK; document_data.primary_color = Color::BLACK;
document_data.secondary_color = Color::WHITE; document_data.secondary_color = Color::WHITE;
document_data.update_working_colors(responses); document_data.update_working_colors(responses); // TODO: Make this an event
} }
ToolMessage::SelectPrimaryColor { color } => { ToolMessage::SelectPrimaryColor { color } => {
let document_data = &mut self.tool_state.document_tool_data; let document_data = &mut self.tool_state.document_tool_data;
document_data.primary_color = color; 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 => { ToolMessage::SelectRandomPrimaryColor => {
// Select a random primary color (rgba) based on an UUID // Select a random primary color (rgba) based on an UUID
@ -213,20 +213,20 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
let random_color = Color::from_rgba8_srgb(r, g, b, 255); let random_color = Color::from_rgba8_srgb(r, g, b, 255);
document_data.primary_color = random_color; document_data.primary_color = random_color;
document_data.update_working_colors(responses); document_data.update_working_colors(responses); // TODO: Make this an event
} }
ToolMessage::SelectSecondaryColor { color } => { ToolMessage::SelectSecondaryColor { color } => {
let document_data = &mut self.tool_state.document_tool_data; let document_data = &mut self.tool_state.document_tool_data;
document_data.secondary_color = color; document_data.secondary_color = color;
document_data.update_working_colors(responses); document_data.update_working_colors(responses); // TODO: Make this an event
} }
ToolMessage::SwapColors => { ToolMessage::SwapColors => {
let document_data = &mut self.tool_state.document_tool_data; let document_data = &mut self.tool_state.document_tool_data;
std::mem::swap(&mut document_data.primary_color, &mut document_data.secondary_color); 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 // Sub-messages

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M6,9.5l4-2.4c0,0.5,0.5,0.9,1,0.9h3c0.6,0,1-0.4,1-1V3c0-0.6-0.4-1-1-1h-3c-0.6,0-1,0.4-1,1H6c0-0.6-0.4-1-1-1H2C1.4,2,1,2.4,1,3v2c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1V4h4v1.9L5.8,8.4C5.6,8.2,5.3,8,5,8H2C1.4,8,1,8.4,1,9v2c0,0.6,0.4,1,1,1h3c0.4,0,0.8-0.2,0.9-0.6L10,13v1c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1v-2c0-0.6-0.4-1-1-1h-3c-0.5,0-1,0.4-1,1l-4-1.6V9.5z M11,3h3v4h-3V3z M5,5H2V3h3V5z M5,11H2V9h3V11z M11,12h3v2h-3V12z" />
</svg>

After

Width:  |  Height:  |  Size: 495 B

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M6,9.5l4-2.4c0,0.5,0.5,0.9,1,0.9h3c0.6,0,1-0.4,1-1V3c0-0.6-0.4-1-1-1h-3c-0.6,0-1,0.4-1,1H6c0-0.6-0.4-1-1-1H2C1.4,2,1,2.4,1,3v2c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1V4h4v1.9L5.8,8.4C5.6,8.2,5.3,8,5,8H2C1.4,8,1,8.4,1,9v2c0,0.6,0.4,1,1,1h3c0.4,0,0.8-0.2,0.9-0.6L10,13v1c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1v-2c0-0.6-0.4-1-1-1h-3c-0.5,0-1,0.4-1,1l-4-1.6V9.5z" />
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@ -20,11 +20,13 @@
UpdateMouseCursor, UpdateMouseCursor,
UpdateDocumentNodeRender, UpdateDocumentNodeRender,
UpdateDocumentTransform, UpdateDocumentTransform,
TriggerGraphViewOverlay,
} from "@graphite/wasm-communication/messages"; } from "@graphite/wasm-communication/messages";
import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte"; import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.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 CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte";
import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte"; import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
@ -38,6 +40,9 @@
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
const document = getContext<DocumentState>("document"); const document = getContext<DocumentState>("document");
// Graph view overlay
let graphViewOverlayOpen = false;
// Interactive text editing // Interactive text editing
let textInput: undefined | HTMLDivElement = undefined; let textInput: undefined | HTMLDivElement = undefined;
let showTextInput: boolean; let showTextInput: boolean;
@ -135,6 +140,7 @@
// Replace the placeholders with the actual canvas elements // Replace the placeholders with the actual canvas elements
placeholders.forEach((placeholder) => { placeholders.forEach((placeholder) => {
const canvasName = placeholder.getAttribute("data-canvas-placeholder"); const canvasName = placeholder.getAttribute("data-canvas-placeholder");
if (!canvasName) return;
// Get the canvas element from the global storage // Get the canvas element from the global storage
const canvas = (window as any).imageCanvases[canvasName]; const canvas = (window as any).imageCanvases[canvasName];
placeholder.replaceWith(canvas); placeholder.replaceWith(canvas);
@ -332,6 +338,11 @@
} }
onMount(() => { onMount(() => {
// Show or hide the graph view overlay
editor.subscriptions.subscribeJsMessage(TriggerGraphViewOverlay, (triggerGraphViewOverlay) => {
graphViewOverlayOpen = triggerGraphViewOverlay.open;
});
// Update rendered SVGs // Update rendered SVGs
editor.subscriptions.subscribeJsMessage(UpdateDocumentArtwork, async (data) => { editor.subscriptions.subscribeJsMessage(UpdateDocumentArtwork, async (data) => {
await tick(); await tick();
@ -425,23 +436,30 @@
</script> </script>
<LayoutCol class="document"> <LayoutCol class="document">
<LayoutRow class="options-bar" scrollableX={true}> <LayoutRow class="options-bar" classes={{ "for-graph": graphViewOverlayOpen }} scrollableX={true}>
<WidgetLayout layout={$document.documentModeLayout} /> {#if !graphViewOverlayOpen}
<WidgetLayout layout={$document.toolOptionsLayout} /> <WidgetLayout layout={$document.documentModeLayout} />
<WidgetLayout layout={$document.toolOptionsLayout} />
<LayoutRow class="spacer" /> <LayoutRow class="spacer" />
<WidgetLayout layout={$document.documentBarLayout} /> <WidgetLayout layout={$document.documentBarLayout} />
{:else}
<WidgetLayout layout={$document.nodeGraphBarLayout} />
{/if}
</LayoutRow> </LayoutRow>
<LayoutRow class="shelf-and-viewport"> <LayoutRow class="shelf-and-viewport">
<LayoutCol class="shelf"> <LayoutCol class="shelf">
<LayoutCol class="tools" scrollableY={true}> {#if !graphViewOverlayOpen}
<WidgetLayout layout={$document.toolShelfLayout} /> <LayoutCol class="tools" scrollableY={true}>
</LayoutCol> <WidgetLayout layout={$document.toolShelfLayout} />
</LayoutCol>
{/if}
<LayoutCol class="spacer" /> <LayoutCol class="spacer" />
<LayoutCol class="working-colors"> <LayoutCol class="widgets-below-shelf">
<WidgetLayout layout={$document.graphViewOverlayButtonLayout} />
<WidgetLayout layout={$document.workingColorsLayout} /> <WidgetLayout layout={$document.workingColorsLayout} />
</LayoutCol> </LayoutCol>
</LayoutCol> </LayoutCol>
@ -485,6 +503,9 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="graph-view" class:open={graphViewOverlayOpen} style:--fade-artwork="80%">
<Graph />
</div>
</LayoutCol> </LayoutCol>
<LayoutCol class="bar-area right-scrollbar"> <LayoutCol class="bar-area right-scrollbar">
<PersistentScrollbar <PersistentScrollbar
@ -521,6 +542,12 @@
.spacer { .spacer {
min-width: 40px; min-width: 40px;
} }
&.for-graph .widget-layout {
flex-direction: row;
flex-grow: 1;
justify-content: space-between;
}
} }
.shelf-and-viewport { .shelf-and-viewport {
@ -557,21 +584,30 @@
.spacer { .spacer {
flex: 1 0 auto; flex: 1 0 auto;
min-height: 8px; min-height: 20px;
} }
.working-colors { .widgets-below-shelf {
flex: 0 0 auto; flex: 0 0 auto;
.widget-row { .widget-layout:first-of-type {
min-height: 0; height: auto;
align-items: center;
}
.swatch-pair { .widget-layout:last-of-type {
margin: 0; height: auto;
}
.icon-button { .widget-row {
--widget-height: 0; min-height: 0;
.swatch-pair {
margin: 0;
}
.icon-button {
--widget-height: 0;
}
} }
} }
} }
@ -580,11 +616,6 @@
.viewport { .viewport {
flex: 1 1 100%; flex: 1 1 100%;
.canvas-area {
flex: 1 1 100%;
position: relative;
}
.bar-area { .bar-area {
flex: 0 0 auto; flex: 0 0 auto;
} }
@ -602,51 +633,89 @@
margin-right: 16px; margin-right: 16px;
} }
.canvas { .canvas-area {
background: var(--color-2-mildblack); flex: 1 1 100%;
width: 100%;
height: 100%;
// Allows the SVG to be placed at explicit integer values of width and height to prevent non-pixel-perfect SVG scaling
position: relative; position: relative;
overflow: hidden;
svg { .canvas {
position: absolute; background: var(--color-2-mildblack);
// Fallback values if JS hasn't set these to integers yet
width: 100%; width: 100%;
height: 100%; height: 100%;
// Allows dev tools to select the artwork without being blocked by the SVG containers // Allows the SVG to be placed at explicit integer values of width and height to prevent non-pixel-perfect SVG scaling
pointer-events: none; position: relative;
overflow: hidden;
canvas { svg {
position: absolute;
// Fallback values if JS hasn't set these to integers yet
width: 100%; width: 100%;
height: 100%; height: 100%;
// Allows dev tools to select the artwork without being blocked by the SVG containers
pointer-events: none;
canvas {
width: 100%;
height: 100%;
}
// Prevent inheritance from reaching the child elements
> * {
pointer-events: auto;
}
} }
// Prevent inheritance from reaching the child elements .text-input div {
> * { cursor: text;
pointer-events: auto; 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 { .graph-view {
cursor: text; pointer-events: none;
background: none; transition: opacity 0.1s ease-in-out;
border: none; opacity: 0;
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 { &.open {
border: none; cursor: auto;
outline: none; // Ok for contenteditable element pointer-events: auto;
margin: -1px; 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%;
} }
} }
} }

View File

@ -2,7 +2,6 @@
import Document from "@graphite/components/panels/Document.svelte"; import Document from "@graphite/components/panels/Document.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import LayerTree from "@graphite/components/panels/LayerTree.svelte"; import LayerTree from "@graphite/components/panels/LayerTree.svelte";
import NodeGraph from "@graphite/components/panels/NodeGraph.svelte";
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte"; import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
import Properties from "@graphite/components/panels/Properties.svelte"; import Properties from "@graphite/components/panels/Properties.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte"; import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
@ -10,7 +9,6 @@
const PANEL_COMPONENTS = { const PANEL_COMPONENTS = {
Document, Document,
LayerTree, LayerTree,
NodeGraph,
Properties, Properties,
}; };
type PanelTypes = keyof typeof PANEL_COMPONENTS; type PanelTypes = keyof typeof PANEL_COMPONENTS;

View File

@ -14,8 +14,7 @@
const PANEL_SIZES = { const PANEL_SIZES = {
/**/ root: 100, /**/ root: 100,
/* ├── */ content: 80, /* ├── */ content: 80,
/* │ ├── */ document: 50, /* │ ├── */ document: 100,
/* │ └── */ graph: 50,
/* └── */ details: 20, /* └── */ details: 20,
/* ├── */ properties: 45, /* ├── */ properties: 45,
/* └── */ layers: 55, /* └── */ layers: 55,
@ -111,12 +110,6 @@
bind:this={documentPanel} bind:this={documentPanel}
/> />
</LayoutRow> </LayoutRow>
{#if $portfolio.documents.length > 0}
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={resizePanel} />
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["graph"] }} data-subdivision-name="graph">
<Panel panelType="NodeGraph" tabLabels={[{ name: "Node Graph" }]} tabActiveIndex={0} />
</LayoutRow>
{/if}
</LayoutCol> </LayoutCol>
<LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} /> <LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} />
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details"> <LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details">

View File

@ -103,7 +103,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, document
if (await shouldRedirectKeyboardEventToBackend(e)) { if (await shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault(); e.preventDefault();
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.onKeyDown(key, modifiers); editor.instance.onKeyDown(key, modifiers, e.repeat);
return; return;
} }
@ -118,7 +118,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, document
if (await shouldRedirectKeyboardEventToBackend(e)) { if (await shouldRedirectKeyboardEventToBackend(e)) {
e.preventDefault(); e.preventDefault();
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.onKeyUp(key, modifiers); editor.instance.onKeyUp(key, modifiers, e.repeat);
} }
} }

View File

@ -11,6 +11,8 @@ import {
UpdateToolOptionsLayout, UpdateToolOptionsLayout,
UpdateToolShelfLayout, UpdateToolShelfLayout,
UpdateWorkingColorsLayout, UpdateWorkingColorsLayout,
UpdateGraphViewOverlayButtonLayout,
UpdateNodeGraphBarLayout,
} from "@graphite/wasm-communication/messages"; } from "@graphite/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -21,7 +23,9 @@ export function createDocumentState(editor: Editor) {
toolOptionsLayout: defaultWidgetLayout(), toolOptionsLayout: defaultWidgetLayout(),
documentBarLayout: defaultWidgetLayout(), documentBarLayout: defaultWidgetLayout(),
toolShelfLayout: defaultWidgetLayout(), toolShelfLayout: defaultWidgetLayout(),
graphViewOverlayButtonLayout: defaultWidgetLayout(),
workingColorsLayout: defaultWidgetLayout(), workingColorsLayout: defaultWidgetLayout(),
nodeGraphBarLayout: defaultWidgetLayout(),
}); });
const { subscribe, update } = state; const { subscribe, update } = state;
@ -62,6 +66,14 @@ export function createDocumentState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateGraphViewOverlayButtonLayout, async (updateGraphViewOverlayButtonLayout) => {
await tick();
update((state) => {
patchWidgetLayout(state.graphViewOverlayButtonLayout, updateGraphViewOverlayButtonLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => { editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => {
await tick(); await tick();
@ -71,6 +83,14 @@ export function createDocumentState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
update((state) => {
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
return state;
});
});
// Other
editor.subscriptions.subscribeJsMessage(TriggerRefreshBoundsOfViewports, async () => { editor.subscriptions.subscribeJsMessage(TriggerRefreshBoundsOfViewports, async () => {
// Wait to display the unpopulated document panel (missing: tools, options bar content, scrollbar positioning, and canvas) // Wait to display the unpopulated document panel (missing: tools, options bar content, scrollbar positioning, and canvas)
await tick(); await tick();

View File

@ -8,10 +8,7 @@ import {
type FrontendNodeType, type FrontendNodeType,
UpdateNodeGraph, UpdateNodeGraph,
UpdateNodeTypes, UpdateNodeTypes,
UpdateNodeGraphBarLayout,
UpdateZoomWithScroll, UpdateZoomWithScroll,
defaultWidgetLayout,
patchWidgetLayout,
} from "@graphite/wasm-communication/messages"; } from "@graphite/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -20,7 +17,6 @@ export function createNodeGraphState(editor: Editor) {
nodes: [] as FrontendNode[], nodes: [] as FrontendNode[],
links: [] as FrontendNodeLink[], links: [] as FrontendNodeLink[],
nodeTypes: [] as FrontendNodeType[], nodeTypes: [] as FrontendNodeType[],
nodeGraphBarLayout: defaultWidgetLayout(),
zoomWithScroll: false as boolean, zoomWithScroll: false as boolean,
}); });
@ -38,12 +34,6 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
update((state) => {
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => { editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
update((state) => { update((state) => {
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll; state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;

View File

@ -105,6 +105,8 @@ import FlipHorizontal from "@graphite-frontend/assets/icon-16px-solid/flip-horiz
import FlipVertical from "@graphite-frontend/assets/icon-16px-solid/flip-vertical.svg"; import FlipVertical from "@graphite-frontend/assets/icon-16px-solid/flip-vertical.svg";
import Folder from "@graphite-frontend/assets/icon-16px-solid/folder.svg"; import Folder from "@graphite-frontend/assets/icon-16px-solid/folder.svg";
import GraphiteLogo from "@graphite-frontend/assets/icon-16px-solid/graphite-logo.svg"; import GraphiteLogo from "@graphite-frontend/assets/icon-16px-solid/graphite-logo.svg";
import GraphViewClosed from "@graphite-frontend/assets/icon-16px-solid/graph-view-closed.svg";
import GraphViewOpen from "@graphite-frontend/assets/icon-16px-solid/graph-view-open.svg";
import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg"; import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg";
import NodeArtboard from "@graphite-frontend/assets/icon-16px-solid/node-artboard.svg"; import NodeArtboard from "@graphite-frontend/assets/icon-16px-solid/node-artboard.svg";
import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg"; import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg";
@ -166,6 +168,8 @@ const SOLID_16PX = {
FlipVertical: { svg: FlipVertical, size: 16 }, FlipVertical: { svg: FlipVertical, size: 16 },
Folder: { svg: Folder, size: 16 }, Folder: { svg: Folder, size: 16 },
GraphiteLogo: { svg: GraphiteLogo, size: 16 }, GraphiteLogo: { svg: GraphiteLogo, size: 16 },
GraphViewClosed: { svg: GraphViewClosed, size: 16 },
GraphViewOpen: { svg: GraphViewOpen, size: 16 },
Layer: { svg: Layer, size: 16 }, Layer: { svg: Layer, size: 16 },
NodeArtboard: { svg: NodeArtboard, size: 16 }, NodeArtboard: { svg: NodeArtboard, size: 16 },
NodeBlur: { svg: NodeBlur, size: 16 }, NodeBlur: { svg: NodeBlur, size: 16 },

View File

@ -725,6 +725,10 @@ export class TriggerFontLoad extends JsMessage {
isDefault!: boolean; isDefault!: boolean;
} }
export class TriggerGraphViewOverlay extends JsMessage {
open!: boolean;
}
export class TriggerVisitLink extends JsMessage { export class TriggerVisitLink extends JsMessage {
url!: string; url!: string;
} }
@ -1199,7 +1203,7 @@ export function defaultWidgetLayout(): WidgetLayout {
}; };
} }
// Updates a widget layout based on a list of updates, returning the new layout // Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument
export function patchWidgetLayout(/* mut */ layout: WidgetLayout, updates: WidgetDiffUpdate): void { export function patchWidgetLayout(/* mut */ layout: WidgetLayout, updates: WidgetDiffUpdate): void {
layout.layoutTarget = updates.layoutTarget; layout.layoutTarget = updates.layoutTarget;
@ -1300,24 +1304,15 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
// WIDGET LAYOUTS // WIDGET LAYOUTS
export class UpdateDialogDetails extends WidgetDiffUpdate { } export class UpdateDialogDetails extends WidgetDiffUpdate { }
export class UpdateDocumentModeLayout extends WidgetDiffUpdate { }
export class UpdateToolOptionsLayout extends WidgetDiffUpdate { }
export class UpdateDocumentBarLayout extends WidgetDiffUpdate { } export class UpdateDocumentBarLayout extends WidgetDiffUpdate { }
export class UpdateToolShelfLayout extends WidgetDiffUpdate { } export class UpdateDocumentModeLayout extends WidgetDiffUpdate { }
export class UpdateWorkingColorsLayout extends WidgetDiffUpdate { } export class UpdateGraphViewOverlayButtonLayout extends WidgetDiffUpdate { }
export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate { }
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate { }
export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate { } export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate { }
export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate { } // Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
export class UpdateMenuBarLayout extends JsMessage { export class UpdateMenuBarLayout extends JsMessage {
layoutTarget!: unknown; layoutTarget!: unknown;
@ -1327,6 +1322,18 @@ export class UpdateMenuBarLayout extends JsMessage {
layout!: MenuBarEntry[]; layout!: MenuBarEntry[];
} }
export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate { }
export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate { }
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate { }
export class UpdateToolOptionsLayout extends WidgetDiffUpdate { }
export class UpdateToolShelfLayout extends WidgetDiffUpdate { }
export class UpdateWorkingColorsLayout extends WidgetDiffUpdate { }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] { function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
return menuBarEntry.map((entry) => ({ return menuBarEntry.map((entry) => ({
@ -1364,6 +1371,7 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerDownloadRaster, TriggerDownloadRaster,
TriggerDownloadTextFile, TriggerDownloadTextFile,
TriggerFontLoad, TriggerFontLoad,
TriggerGraphViewOverlay,
TriggerImport, TriggerImport,
TriggerIndexedDbRemoveDocument, TriggerIndexedDbRemoveDocument,
TriggerIndexedDbWriteDocument, TriggerIndexedDbWriteDocument,
@ -1382,17 +1390,18 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateActiveDocument, UpdateActiveDocument,
UpdateDialogDetails, UpdateDialogDetails,
UpdateDocumentArtboards, UpdateDocumentArtboards,
UpdateDocumentNodeRender,
UpdateDocumentArtwork, UpdateDocumentArtwork,
UpdateDocumentBarLayout, UpdateDocumentBarLayout,
UpdateDocumentLayerDetails, UpdateDocumentLayerDetails,
UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure, UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure,
UpdateDocumentModeLayout, UpdateDocumentModeLayout,
UpdateDocumentNodeRender,
UpdateDocumentOverlays, UpdateDocumentOverlays,
UpdateDocumentRulers, UpdateDocumentRulers,
UpdateDocumentScrollbars, UpdateDocumentScrollbars,
UpdateDocumentTransform, UpdateDocumentTransform,
UpdateEyedropperSamplingState, UpdateEyedropperSamplingState,
UpdateGraphViewOverlayButtonLayout,
UpdateImageData, UpdateImageData,
UpdateInputHints, UpdateInputHints,
UpdateLayerTreeOptionsLayout, UpdateLayerTreeOptionsLayout,

View File

@ -420,25 +420,25 @@ impl JsEditorHandle {
/// A keyboard button depressed within screenspace the bounds of the viewport /// A keyboard button depressed within screenspace the bounds of the viewport
#[wasm_bindgen(js_name = onKeyDown)] #[wasm_bindgen(js_name = onKeyDown)]
pub fn on_key_down(&self, name: String, modifiers: u8) { pub fn on_key_down(&self, name: String, modifiers: u8, key_repeat: bool) {
let key = translate_key(&name); let key = translate_key(&name);
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
trace!("Key down {:?}, name: {}, modifiers: {:?}", key, name, modifiers); trace!("Key down {:?}, name: {}, modifiers: {:?}, key repeat: {}", key, name, modifiers, key_repeat);
let message = InputPreprocessorMessage::KeyDown { key, modifier_keys }; let message = InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys };
self.dispatch(message); self.dispatch(message);
} }
/// A keyboard button released /// A keyboard button released
#[wasm_bindgen(js_name = onKeyUp)] #[wasm_bindgen(js_name = onKeyUp)]
pub fn on_key_up(&self, name: String, modifiers: u8) { pub fn on_key_up(&self, name: String, modifiers: u8, key_repeat: bool) {
let key = translate_key(&name); let key = translate_key(&name);
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
trace!("Key up {:?}, name: {}, modifiers: {:?}", key, name, modifier_keys); trace!("Key up {:?}, name: {}, modifiers: {:?}, key repeat: {}", key, name, modifier_keys, key_repeat);
let message = InputPreprocessorMessage::KeyUp { key, modifier_keys }; let message = InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys };
self.dispatch(message); self.dispatch(message);
} }