diff --git a/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs b/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs index a321876f..3f0855e9 100644 --- a/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs @@ -37,12 +37,14 @@ pub struct MenuBarEntry { pub shortcut: Option, pub action: WidgetHolder, pub children: MenuBarEntryChildren, + pub disabled: bool, } impl MenuBarEntry { - pub fn new_root(label: String, children: MenuBarEntryChildren) -> Self { + pub fn new_root(label: String, disabled: bool, children: MenuBarEntryChildren) -> Self { Self { label, + disabled, children, ..Default::default() } @@ -67,6 +69,7 @@ impl Default for MenuBarEntry { shortcut: None, action: MenuBarEntry::no_action(), children: MenuBarEntryChildren::empty(), + disabled: false, } } } diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index d147ef74..e4e09d50 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -6,12 +6,15 @@ use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; #[derive(Debug, Clone, Default)] -pub struct MenuBarMessageHandler {} +pub struct MenuBarMessageHandler { + no_active_document: bool, +} -impl MessageHandler for MenuBarMessageHandler { +impl MessageHandler for MenuBarMessageHandler { #[remain::check] - fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, has_active_document: bool) { use MenuBarMessage::*; + self.no_active_document = !has_active_document; #[remain::sorted] match message { @@ -26,75 +29,13 @@ impl MessageHandler for MenuBarMessageHandler { impl PropertyHolder for MenuBarMessageHandler { fn properties(&self) -> Layout { - Layout::MenuLayout(MenuLayout::new(vec![ - MenuBarEntry { - icon: Some("GraphiteLogo".into()), - action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.rs".into() }.into()), - ..Default::default() - }, - MenuBarEntry::new_root( - "File".into(), - MenuBarEntryChildren(vec![ - vec![ - MenuBarEntry { - label: "New…".into(), - icon: Some("File".into()), - action: MenuBarEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()), - shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog), - children: MenuBarEntryChildren::empty(), - }, - MenuBarEntry { - label: "Open…".into(), - shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument), - action: MenuBarEntry::create_action(|_| PortfolioMessage::OpenDocument.into()), - ..MenuBarEntry::default() - }, - ], - vec![ - MenuBarEntry { - label: "Close".into(), - shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation), - action: MenuBarEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()), - ..MenuBarEntry::default() - }, - MenuBarEntry { - label: "Close All".into(), - shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation), - action: MenuBarEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()), - ..MenuBarEntry::default() - }, - ], - vec![MenuBarEntry { - label: "Save".into(), - shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument), - action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()), - ..MenuBarEntry::default() - }], - vec![ - MenuBarEntry { - label: "Import…".into(), - shortcut: action_keys!(PortfolioMessageDiscriminant::Import), - action: MenuBarEntry::create_action(|_| PortfolioMessage::Import.into()), - ..MenuBarEntry::default() - }, - MenuBarEntry { - label: "Export…".into(), - shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog), - action: MenuBarEntry::create_action(|_| DialogMessage::RequestExportDialog.into()), - ..MenuBarEntry::default() - }, - ], - vec![MenuBarEntry { - label: "Preferences…".into(), - icon: Some("Settings".into()), - shortcut: action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog), - action: MenuBarEntry::create_action(|_| DialogMessage::RequestPreferencesDialog.into()), - ..MenuBarEntry::default() - }], - ]), - ), + let no_active_document = self.no_active_document; + + // enable these only if there's an active document + let conditional_menu_entries = vec![ MenuBarEntry::new_root( "Edit".into(), + no_active_document, MenuBarEntryChildren(vec![ vec![ MenuBarEntry { @@ -136,6 +77,7 @@ impl PropertyHolder for MenuBarMessageHandler { ), MenuBarEntry::new_root( "Layer".into(), + no_active_document, MenuBarEntryChildren(vec![ vec![ MenuBarEntry { @@ -213,6 +155,7 @@ impl PropertyHolder for MenuBarMessageHandler { ), MenuBarEntry::new_root( "Document".into(), + no_active_document, MenuBarEntryChildren(vec![vec![MenuBarEntry { label: "Clear Artboards".into(), action: MenuBarEntry::create_action(|_| ArtboardMessage::ClearArtboards.into()), @@ -221,6 +164,7 @@ impl PropertyHolder for MenuBarMessageHandler { ), MenuBarEntry::new_root( "View".into(), + no_active_document, MenuBarEntryChildren(vec![vec![ MenuBarEntry { label: "Zoom to Selected".into(), @@ -248,8 +192,84 @@ impl PropertyHolder for MenuBarMessageHandler { }, ]]), ), + ]; + + let mut menu_bar_entries = vec![ + MenuBarEntry { + icon: Some("GraphiteLogo".into()), + action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.rs".into() }.into()), + ..Default::default() + }, + MenuBarEntry::new_root( + "File".into(), + false, + MenuBarEntryChildren(vec![ + vec![ + MenuBarEntry { + label: "New…".into(), + icon: Some("File".into()), + action: MenuBarEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()), + shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog), + children: MenuBarEntryChildren::empty(), + ..MenuBarEntry::default() + }, + MenuBarEntry { + label: "Open…".into(), + shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument), + action: MenuBarEntry::create_action(|_| PortfolioMessage::OpenDocument.into()), + ..MenuBarEntry::default() + }, + ], + vec![ + MenuBarEntry { + label: "Close".into(), + shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation), + action: MenuBarEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()), + disabled: no_active_document, + ..MenuBarEntry::default() + }, + MenuBarEntry { + label: "Close All".into(), + shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation), + action: MenuBarEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()), + disabled: no_active_document, + ..MenuBarEntry::default() + }, + ], + vec![MenuBarEntry { + label: "Save".into(), + shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument), + action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()), + disabled: no_active_document, + ..MenuBarEntry::default() + }], + vec![ + MenuBarEntry { + label: "Import…".into(), + shortcut: action_keys!(PortfolioMessageDiscriminant::Import), + action: MenuBarEntry::create_action(|_| PortfolioMessage::Import.into()), + ..MenuBarEntry::default() + }, + MenuBarEntry { + label: "Export…".into(), + shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog), + action: MenuBarEntry::create_action(|_| DialogMessage::RequestExportDialog.into()), + disabled: no_active_document, + ..MenuBarEntry::default() + }, + ], + vec![MenuBarEntry { + label: "Preferences…".into(), + icon: Some("Settings".into()), + shortcut: action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog), + action: MenuBarEntry::create_action(|_| DialogMessage::RequestPreferencesDialog.into()), + ..MenuBarEntry::default() + }], + ]), + ), MenuBarEntry::new_root( "Help".into(), + true, MenuBarEntryChildren(vec![ vec![MenuBarEntry { label: "About Graphite".into(), @@ -327,6 +347,11 @@ impl PropertyHolder for MenuBarMessageHandler { ], ]), ), - ])) + ]; + + if !no_active_document { + menu_bar_entries.splice(2..2, conditional_menu_entries); + } + Layout::MenuLayout(MenuLayout::new(menu_bar_entries)) } } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index b62c6774..3edb8cba 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -33,11 +33,13 @@ pub struct PortfolioMessageHandler { impl MessageHandler for PortfolioMessageHandler { #[remain::check] fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, (ipp, preferences): (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)) { + let has_active_document = self.active_document_id.is_some(); + #[remain::sorted] match message { // Sub-messages #[remain::unsorted] - PortfolioMessage::MenuBar(message) => self.menu_bar_message_handler.process_message(message, responses, ()), + PortfolioMessage::MenuBar(message) => self.menu_bar_message_handler.process_message(message, responses, has_active_document), #[remain::unsorted] PortfolioMessage::Document(message) => { if let Some(document_id) = self.active_document_id { @@ -109,6 +111,7 @@ impl MessageHandler self.active_document_id = Some(document_id), + PortfolioMessage::SetActiveDocument { document_id } => { + self.active_document_id = Some(document_id); + responses.add(MenuBarMessage::SendLayout); + } PortfolioMessage::SetImageBlobUrl { document_id, layer_path, diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 2f953d33..ab686753 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -7,6 +7,7 @@ use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHan use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::intersection::Quad; +use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::layers::style::Fill; use glam::DVec2; @@ -103,6 +104,17 @@ impl Fsm for FillToolFsmState { let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); if let Some(path) = document.document_legacy.intersects_quad_root(quad, render_data).last() { + let is_bitmap = document + .document_legacy + .layer(path) + .ok() + .and_then(|layer| layer.as_layer().ok()) + .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_))); + + if is_bitmap { + return self; + } + let color = match lmb_or_rmb { LeftPointerDown => global_tool_data.primary_color, RightPointerDown => global_tool_data.secondary_color, diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 613aeeda..efffb9fc 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -11,6 +11,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::intersection::Quad; use document_legacy::layers::layer_info::Layer; +use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::layers::style::{Fill, Gradient, GradientType, PathStyle, RenderData, Stroke}; use document_legacy::LayerId; use document_legacy::Operation; @@ -420,9 +421,6 @@ impl Fsm for GradientToolFsmState { } for path in document.selected_visible_layers() { - if !document.document_legacy.multiply_transforms(path).unwrap().inverse().is_finite() { - continue; - } let layer = document.document_legacy.layer(path).unwrap(); if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) { @@ -568,7 +566,18 @@ impl Fsm for GradientToolFsmState { let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop(); + // the intersection is the layer where the gradient is being applied if let Some(intersection) = intersection { + let is_bitmap = document + .document_legacy + .layer(&intersection) + .ok() + .and_then(|layer| layer.as_layer().ok()) + .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_))); + if is_bitmap { + return self; + } + if !document.selected_layers_contains(&intersection) { let replacement_selected_layers = vec![intersection.clone()]; diff --git a/frontend/src/components/widgets/inputs/MenuBarInput.svelte b/frontend/src/components/widgets/inputs/MenuBarInput.svelte index a1dce1f4..7ca66876 100644 --- a/frontend/src/components/widgets/inputs/MenuBarInput.svelte +++ b/frontend/src/components/widgets/inputs/MenuBarInput.svelte @@ -63,7 +63,7 @@ // New fields in `MenuListEntry` shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined, value: undefined, - disabled: undefined, + disabled: entry.disabled ?? undefined, font: undefined, ref: undefined, }); diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 81addb4c..7dcdb1fe 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -774,6 +774,7 @@ type MenuEntryCommon = { export type MenuBarEntry = MenuEntryCommon & { action: Widget; children?: MenuBarEntry[][]; + disabled?: boolean, }; // An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets @@ -1300,6 +1301,7 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] { ...entry, action: hoistWidgetHolders([entry.action])[0], children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined, + disabled: entry.disabled ?? false, })) ); }