diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index dedd70a0..cd4f7b72 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -232,6 +232,7 @@ impl Dispatcher { Message::MenuBar(message) => { let menu_bar_message_handler = &mut self.message_handlers.menu_bar_message_handler; + menu_bar_message_handler.focus_document = self.message_handlers.portfolio_message_handler.focus_document; menu_bar_message_handler.data_panel_open = self.message_handlers.portfolio_message_handler.data_panel_open; menu_bar_message_handler.layers_panel_open = self.message_handlers.portfolio_message_handler.layers_panel_open; menu_bar_message_handler.properties_panel_open = self.message_handlers.portfolio_message_handler.properties_panel_open; diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index cc94c276..44ce4eec 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -334,7 +334,8 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping { entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors), entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }), entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }), - entry!(KeyDownNoRepeat(Tab); action_dispatch=ToolMessage::ToggleSelectVsPath), + // TODO: Change to KeyDownNoRepeat when https://github.com/GraphiteEditor/Graphite/issues/2266 is resolved + entry!(KeyDown(Tab); action_dispatch=ToolMessage::ToggleSelectVsPath), // // DocumentMessage entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle), @@ -448,6 +449,7 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping { entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers), entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen), + entry!(KeyDown(Enter); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleFocusDocument), // // DialogMessage entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog), diff --git a/editor/src/messages/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/menu_bar/menu_bar_message_handler.rs index c867b509..b52cc259 100644 --- a/editor/src/messages/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/menu_bar/menu_bar_message_handler.rs @@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler { pub data_panel_open: bool, pub layers_panel_open: bool, pub properties_panel_open: bool, + pub focus_document: bool, } #[message_handler_data] @@ -628,23 +629,31 @@ impl LayoutHolder for MenuBarMessageHandler { .icon("FullscreenEnter") .tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::Fullscreen)) .on_commit(|_| AppWindowMessage::Fullscreen.into()), + MenuListEntry::new("Focus Document") + .label("Focus Document") + .icon(if self.focus_document { "CheckboxChecked" } else { "CheckboxUnchecked" }) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleFocusDocument)) + .on_commit(|_| PortfolioMessage::ToggleFocusDocument.into()), ], vec![ MenuListEntry::new("Properties") .label("Properties") .icon(if self.properties_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }) .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen)) - .on_commit(|_| PortfolioMessage::TogglePropertiesPanelOpen.into()), + .on_commit(|_| PortfolioMessage::TogglePropertiesPanelOpen.into()) + .disabled(self.focus_document), MenuListEntry::new("Layers") .label("Layers") .icon(if self.layers_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }) .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen)) - .on_commit(|_| PortfolioMessage::ToggleLayersPanelOpen.into()), + .on_commit(|_| PortfolioMessage::ToggleLayersPanelOpen.into()) + .disabled(self.focus_document), MenuListEntry::new("Data") .label("Data") .icon(if self.data_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }) .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleDataPanelOpen)) - .on_commit(|_| PortfolioMessage::ToggleDataPanelOpen.into()), + .on_commit(|_| PortfolioMessage::ToggleDataPanelOpen.into()) + .disabled(self.focus_document), ], ]) .widget_instance(), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 180910ba..a0176d79 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -316,8 +316,10 @@ impl MessageHandler> for DocumentMes } DocumentMessage::ClearLayersPanel => { // Send an empty layer list - let data_buffer: RawBuffer = Self::default().serialize_root(); - responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); + if layers_panel_open { + let data_buffer: RawBuffer = Self::default().serialize_root(); + responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); + } // Clear the control bar responses.add(LayoutMessage::SendLayout { diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 30edbd6c..b161bfa4 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -149,6 +149,7 @@ pub enum PortfolioMessage { ignore_hash: bool, }, ToggleResetNodesToDefinitionsOnOpen, + ToggleFocusDocument, ToggleDataPanelOpen, TogglePropertiesPanelOpen, ToggleLayersPanelOpen, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index a43dcb7f..d95a78e2 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -60,11 +60,12 @@ pub struct PortfolioMessageHandler { pub executor: NodeGraphExecutor, pub selection_mode: SelectionMode, pub reset_node_definitions_on_open: bool, - pub data_panel_open: bool, - #[derivative(Default(value = "true"))] - pub layers_panel_open: bool, + pub focus_document: bool, #[derivative(Default(value = "true"))] pub properties_panel_open: bool, + #[derivative(Default(value = "true"))] + pub layers_panel_open: bool, + pub data_panel_open: bool, } #[message_handler_data] @@ -94,9 +95,9 @@ impl MessageHandler> for Portfolio current_tool, preferences, viewport, - data_panel_open: self.data_panel_open, - layers_panel_open: self.layers_panel_open, - properties_panel_open: self.properties_panel_open, + data_panel_open: self.data_panel_open && !self.focus_document, + layers_panel_open: self.layers_panel_open && !self.focus_document, + properties_panel_open: self.properties_panel_open && !self.focus_document, }; document.process_message(message, responses, document_inputs) } @@ -158,9 +159,9 @@ impl MessageHandler> for Portfolio current_tool, preferences, viewport, - data_panel_open: self.data_panel_open, - layers_panel_open: self.layers_panel_open, - properties_panel_open: self.properties_panel_open, + data_panel_open: self.data_panel_open && !self.focus_document, + layers_panel_open: self.layers_panel_open && !self.focus_document, + properties_panel_open: self.properties_panel_open && !self.focus_document, }; document.process_message(message, responses, document_inputs) } @@ -452,7 +453,7 @@ impl MessageHandler> for Portfolio responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); } - self.load_document(new_document, document_id, self.layers_panel_open, responses, false); + self.load_document(new_document, document_id, responses, false); responses.add(PortfolioMessage::SelectDocument { document_id }); } PortfolioMessage::NextDocument => { @@ -655,7 +656,7 @@ impl MessageHandler> for Portfolio } // Load the document into the portfolio so it opens in the editor - self.load_document(document, document_id, self.layers_panel_open, responses, to_front); + self.load_document(document, document_id, responses, to_front); if select_after_open { responses.add(PortfolioMessage::SelectDocument { document_id }); @@ -1150,7 +1151,7 @@ impl MessageHandler> for Portfolio let node_to_inspect = self.node_to_inspect(); let Some(document) = self.documents.get_mut(&document_id) else { - log::error!("Tried to render non-existent document"); + log::error!("Tried to render non-existent document {:?}", document_id); return; }; @@ -1177,7 +1178,101 @@ impl MessageHandler> for Portfolio Ok(message) => responses.add_front(message), } } + PortfolioMessage::ToggleFocusDocument => { + self.focus_document = !self.focus_document; + responses.add(MenuBarMessage::SendLayout); + + if self.focus_document { + if self.properties_panel_open { + responses.add(PropertiesPanelMessage::Clear); + responses.add(FrontendMessage::UpdatePropertiesPanelState { open: false }); + } + + if self.layers_panel_open { + responses.add(DocumentMessage::ClearLayersPanel); + responses.add(FrontendMessage::UpdateLayersPanelState { open: false }); + } + + if self.data_panel_open { + responses.add(DataPanelMessage::ClearLayout); + responses.add(FrontendMessage::UpdateDataPanelState { open: false }); + } + } else { + if self.properties_panel_open { + responses.add(FrontendMessage::UpdatePropertiesPanelState { open: true }); + } + if self.layers_panel_open { + responses.add(FrontendMessage::UpdateLayersPanelState { open: true }); + } + if self.data_panel_open { + responses.add(FrontendMessage::UpdateDataPanelState { open: true }); + } + + // Run the graph to grab the data + if self.properties_panel_open || self.layers_panel_open || self.data_panel_open { + responses.add(NodeGraphMessage::RunDocumentGraph); + } + + if self.properties_panel_open { + responses.add(PropertiesPanelMessage::Refresh); + } + if self.layers_panel_open && self.active_document_id.is_some() { + responses.add(DeferMessage::AfterGraphRun { + messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()], + }); + } + } + } + PortfolioMessage::TogglePropertiesPanelOpen => { + if self.focus_document { + return; + } + + self.properties_panel_open = !self.properties_panel_open; + responses.add(MenuBarMessage::SendLayout); + + // Run the graph to grab the data + if self.properties_panel_open { + responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open }); + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PropertiesPanelMessage::Refresh); + } else { + responses.add(PropertiesPanelMessage::Clear); + responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open }); + } + } + PortfolioMessage::ToggleLayersPanelOpen => { + if self.focus_document { + return; + } + + self.layers_panel_open = !self.layers_panel_open; + responses.add(MenuBarMessage::SendLayout); + + // Run the graph to grab the data + if self.layers_panel_open { + // When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display + responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + if self.active_document_id.is_some() { + responses.add(DeferMessage::AfterGraphRun { + messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()], + }); + } + } else { + // If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened + responses.add(DocumentMessage::ClearLayersPanel); + + // When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed + responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open }); + } + } PortfolioMessage::ToggleDataPanelOpen => { + if self.focus_document { + return; + } + self.data_panel_open = !self.data_panel_open; responses.add(MenuBarMessage::SendLayout); @@ -1195,40 +1290,6 @@ impl MessageHandler> for Portfolio responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open }); } } - PortfolioMessage::TogglePropertiesPanelOpen => { - self.properties_panel_open = !self.properties_panel_open; - responses.add(MenuBarMessage::SendLayout); - - responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open }); - - // Run the graph to grab the data - if self.properties_panel_open { - responses.add(NodeGraphMessage::RunDocumentGraph); - } - - responses.add(PropertiesPanelMessage::Refresh); - } - PortfolioMessage::ToggleLayersPanelOpen => { - self.layers_panel_open = !self.layers_panel_open; - responses.add(MenuBarMessage::SendLayout); - - // Run the graph to grab the data - if self.layers_panel_open { - // When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display - responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open }); - - responses.add(NodeGraphMessage::RunDocumentGraph); - responses.add(DeferMessage::AfterGraphRun { - messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()], - }); - } else { - // If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened - responses.add(DocumentMessage::ClearLayersPanel); - - // When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed - responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open }); - } - } PortfolioMessage::ToggleRulers => { if let Some(document) = self.active_document_mut() { document.rulers_visible = !document.rulers_visible; @@ -1280,7 +1341,7 @@ impl MessageHandler> for Portfolio fn actions(&self) -> ActionList { let mut common = actions!(PortfolioMessageDiscriminant; Open, - ToggleDataPanelOpen, + ToggleFocusDocument, ); // Extend with actions that require an active document @@ -1305,6 +1366,15 @@ impl MessageHandler> for Portfolio } } + // Extend with actions that are disabled when focusing the document + if !self.focus_document { + common.extend(actions!(PortfolioMessageDiscriminant; + TogglePropertiesPanelOpen, + ToggleLayersPanelOpen, + ToggleDataPanelOpen, + )); + } + common } } @@ -1385,14 +1455,14 @@ impl PortfolioMessageHandler { } } - fn load_document(&mut self, mut new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque, to_front: bool) { + fn load_document(&mut self, mut new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque, to_front: bool) { if to_front { self.document_ids.push_front(document_id); } else { self.document_ids.push_back(document_id); } - new_document.update_layers_panel_control_bar_widgets(layers_panel_open, responses); - new_document.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses); + new_document.update_layers_panel_control_bar_widgets(self.layers_panel_open && !self.focus_document, responses); + new_document.update_layers_panel_bottom_bar_widgets(self.layers_panel_open && !self.focus_document, responses); self.documents.insert(document_id, new_document); @@ -1439,7 +1509,7 @@ impl PortfolioMessageHandler { /// Get the ID of the selected node that should be used as the current source for the Data panel. pub fn node_to_inspect(&self) -> Option { // Skip if the Data panel is not open - if !self.data_panel_open { + if !self.data_panel_open || self.focus_document { return None; } diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index b2a73c0b..e1189ff8 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -93,7 +93,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // Don't redirect tab or enter if not in canvas (to allow navigating elements) potentiallyRestoreCanvasFocus(e); - if (!canvasFocused && !targetIsTextField(e.target || undefined) && ["Tab", "Enter", "NumpadEnter", "Space", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowUp"].includes(key)) return false; + if ( + !canvasFocused && + !targetIsTextField(e.target || undefined) && + ["Tab", "Enter", "NumpadEnter", "Space", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowUp"].includes(key) && + !(e.ctrlKey || e.metaKey || e.altKey) + ) + return false; // Don't redirect if a MenuList is open if (window.document.querySelector("[data-floating-menu-content]")) return false;