diff --git a/editor/src/communication/dispatcher.rs b/editor/src/communication/dispatcher.rs index 5b346ce2..ec75305a 100644 --- a/editor/src/communication/dispatcher.rs +++ b/editor/src/communication/dispatcher.rs @@ -438,7 +438,7 @@ mod test { #[test] fn check_if_graphite_file_version_upgrade_is_needed() { - use crate::layout::widgets::{LayoutRow, TextLabel, Widget}; + use crate::layout::widgets::{LayoutGroup, TextLabel, Widget}; init_logger(); set_uuid_seed(0); @@ -451,7 +451,7 @@ mod test { for response in responses { if let FrontendMessage::UpdateDialogDetails { layout_target: _, layout } = response { - if let LayoutRow::Row { widgets } = &layout[0] { + if let LayoutGroup::Row { widgets } = &layout[0] { if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget { println!(); println!("-------------------------------------------------"); diff --git a/editor/src/dialog/dialogs/about_dialog.rs b/editor/src/dialog/dialogs/about_dialog.rs index 61ad6489..aef39f25 100644 --- a/editor/src/dialog/dialogs/about_dialog.rs +++ b/editor/src/dialog/dialogs/about_dialog.rs @@ -8,7 +8,7 @@ pub struct AboutGraphite { } impl PropertyHolder for AboutGraphite { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let links = [ ("Website", "https://graphite.rs"), ("Credits", "https://github.com/GraphiteEditor/Graphite/graphs/contributors"), @@ -25,28 +25,28 @@ impl PropertyHolder for AboutGraphite { })) }) .collect(); - WidgetLayout::new(vec![ - LayoutRow::Row { + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Graphite".to_string(), bold: true, ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: release_series(), ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: commit_info_localized(self.localized_commit_date.as_str()), multiline: true, ..Default::default() }))], }, - LayoutRow::Row { widgets: link_widgets }, - ]) + LayoutGroup::Row { widgets: link_widgets }, + ])) } } diff --git a/editor/src/dialog/dialogs/close_all_documents_dialog.rs b/editor/src/dialog/dialogs/close_all_documents_dialog.rs index 811a8cbb..98845e46 100644 --- a/editor/src/dialog/dialogs/close_all_documents_dialog.rs +++ b/editor/src/dialog/dialogs/close_all_documents_dialog.rs @@ -5,7 +5,7 @@ use crate::message_prelude::{DialogMessage, FrontendMessage, PortfolioMessage}; pub struct CloseAllDocuments; impl PropertyHolder for CloseAllDocuments { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let button_widgets = vec![ WidgetHolder::new(Widget::TextButton(TextButton { label: "Discard All".to_string(), @@ -26,22 +26,22 @@ impl PropertyHolder for CloseAllDocuments { })), ]; - WidgetLayout::new(vec![ - LayoutRow::Row { + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Close all documents?".to_string(), bold: true, ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Unsaved work will be lost!".to_string(), multiline: true, ..Default::default() }))], }, - LayoutRow::Row { widgets: button_widgets }, - ]) + LayoutGroup::Row { widgets: button_widgets }, + ])) } } diff --git a/editor/src/dialog/dialogs/close_document_dialog.rs b/editor/src/dialog/dialogs/close_document_dialog.rs index aae56091..d9df3a44 100644 --- a/editor/src/dialog/dialogs/close_document_dialog.rs +++ b/editor/src/dialog/dialogs/close_document_dialog.rs @@ -8,7 +8,7 @@ pub struct CloseDocument { } impl PropertyHolder for CloseDocument { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let document_id = self.document_id; let button_widgets = vec![ @@ -43,22 +43,22 @@ impl PropertyHolder for CloseDocument { })), ]; - WidgetLayout::new(vec![ - LayoutRow::Row { + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Save changes before closing?".to_string(), bold: true, ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: self.document_name.clone(), multiline: true, ..Default::default() }))], }, - LayoutRow::Row { widgets: button_widgets }, - ]) + LayoutGroup::Row { widgets: button_widgets }, + ])) } } diff --git a/editor/src/dialog/dialogs/coming_soon_dialog.rs b/editor/src/dialog/dialogs/coming_soon_dialog.rs index f2ce4120..bb1967be 100644 --- a/editor/src/dialog/dialogs/coming_soon_dialog.rs +++ b/editor/src/dialog/dialogs/coming_soon_dialog.rs @@ -8,7 +8,7 @@ pub struct ComingSoon { } impl PropertyHolder for ComingSoon { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let mut details = "This feature is not implemented yet".to_string(); let mut buttons = vec![WidgetHolder::new(Widget::TextButton(TextButton { label: "OK".to_string(), @@ -31,22 +31,22 @@ impl PropertyHolder for ComingSoon { ..Default::default() }))); } - WidgetLayout::new(vec![ - LayoutRow::Row { + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Coming soon".to_string(), bold: true, ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: details, multiline: true, ..Default::default() }))], }, - LayoutRow::Row { widgets: buttons }, - ]) + LayoutGroup::Row { widgets: buttons }, + ])) } } diff --git a/editor/src/dialog/dialogs/error_dialog.rs b/editor/src/dialog/dialogs/error_dialog.rs index 57e63afb..6d6bc578 100644 --- a/editor/src/dialog/dialogs/error_dialog.rs +++ b/editor/src/dialog/dialogs/error_dialog.rs @@ -7,23 +7,23 @@ pub struct Error { } impl PropertyHolder for Error { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![ - LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: self.title.clone(), bold: true, ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: self.description.clone(), multiline: true, ..Default::default() }))], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextButton(TextButton { label: "OK".to_string(), emphasized: true, @@ -32,6 +32,6 @@ impl PropertyHolder for Error { ..Default::default() }))], }, - ]) + ])) } } diff --git a/editor/src/dialog/dialogs/export_dialog.rs b/editor/src/dialog/dialogs/export_dialog.rs index 1a4e5058..e792992f 100644 --- a/editor/src/dialog/dialogs/export_dialog.rs +++ b/editor/src/dialog/dialogs/export_dialog.rs @@ -18,7 +18,7 @@ pub struct Export { } impl PropertyHolder for Export { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let file_name = vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "File Name".into(), @@ -132,20 +132,20 @@ impl PropertyHolder for Export { })), ]; - WidgetLayout::new(vec![ - LayoutRow::Row { + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Export".to_string(), bold: true, ..Default::default() }))], }, - LayoutRow::Row { widgets: file_name }, - LayoutRow::Row { widgets: export_type }, - LayoutRow::Row { widgets: resolution }, - LayoutRow::Row { widgets: export_area }, - LayoutRow::Row { widgets: button_widgets }, - ]) + LayoutGroup::Row { widgets: file_name }, + LayoutGroup::Row { widgets: export_type }, + LayoutGroup::Row { widgets: resolution }, + LayoutGroup::Row { widgets: export_area }, + LayoutGroup::Row { widgets: button_widgets }, + ])) } } diff --git a/editor/src/dialog/dialogs/new_document_dialog.rs b/editor/src/dialog/dialogs/new_document_dialog.rs index 985c6deb..425a4d9f 100644 --- a/editor/src/dialog/dialogs/new_document_dialog.rs +++ b/editor/src/dialog/dialogs/new_document_dialog.rs @@ -14,7 +14,7 @@ pub struct NewDocument { } impl PropertyHolder for NewDocument { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let title = vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "New document".into(), bold: true, @@ -112,13 +112,13 @@ impl PropertyHolder for NewDocument { })), ]; - WidgetLayout::new(vec![ - LayoutRow::Row { widgets: title }, - LayoutRow::Row { widgets: name }, - LayoutRow::Row { widgets: infinite }, - LayoutRow::Row { widgets: scale }, - LayoutRow::Row { widgets: button_widgets }, - ]) + Layout::WidgetLayout(WidgetLayout::new(vec![ + LayoutGroup::Row { widgets: title }, + LayoutGroup::Row { widgets: name }, + LayoutGroup::Row { widgets: infinite }, + LayoutGroup::Row { widgets: scale }, + LayoutGroup::Row { widgets: button_widgets }, + ])) } } diff --git a/editor/src/document/document_message_handler.rs b/editor/src/document/document_message_handler.rs index 126324e8..cc8731e1 100644 --- a/editor/src/document/document_message_handler.rs +++ b/editor/src/document/document_message_handler.rs @@ -10,8 +10,8 @@ use crate::frontend::utility_types::{FileType, FrontendImageData}; use crate::input::InputPreprocessorMessageHandler; use crate::layout::layout_message::LayoutTarget; use crate::layout::widgets::{ - DropdownEntryData, DropdownInput, IconButton, LayoutRow, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, - SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout, + DropdownEntryData, DropdownInput, IconButton, Layout, LayoutGroup, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, RadioEntryData, RadioInput, Separator, + SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout, }; use crate::message_prelude::*; use crate::viewport_tools::vector_editor::vector_shape::VectorShape; @@ -519,7 +519,7 @@ impl DocumentMessageHandler { } pub fn update_document_widgets(&self, responses: &mut VecDeque) { - let document_bar_layout = WidgetLayout::new(vec![LayoutRow::Row { + let document_bar_layout = WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::OptionalInput(OptionalInput { checked: self.snapping_enabled, @@ -657,7 +657,7 @@ impl DocumentMessageHandler { ], }]); - let document_mode_layout = WidgetLayout::new(vec![LayoutRow::Row { + let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::DropdownInput(DropdownInput { entries: vec![vec![ @@ -693,7 +693,7 @@ impl DocumentMessageHandler { responses.push_back( LayoutMessage::SendLayout { - layout: document_bar_layout, + layout: Layout::WidgetLayout(document_bar_layout), layout_target: LayoutTarget::DocumentBar, } .into(), @@ -701,7 +701,7 @@ impl DocumentMessageHandler { responses.push_back( LayoutMessage::SendLayout { - layout: document_mode_layout, + layout: Layout::WidgetLayout(document_mode_layout), layout_target: LayoutTarget::DocumentMode, } .into(), @@ -762,7 +762,7 @@ impl DocumentMessageHandler { }) .collect(); - let layer_tree_options = WidgetLayout::new(vec![LayoutRow::Row { + let layer_tree_options = WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::DropdownInput(DropdownInput { entries: blend_mode_menu_entries, @@ -815,7 +815,7 @@ impl DocumentMessageHandler { responses.push_back( LayoutMessage::SendLayout { - layout: layer_tree_options, + layout: Layout::WidgetLayout(layer_tree_options), layout_target: LayoutTarget::LayerTreeOptions, } .into(), diff --git a/editor/src/document/menu_bar_message.rs b/editor/src/document/menu_bar_message.rs new file mode 100644 index 00000000..db09a12d --- /dev/null +++ b/editor/src/document/menu_bar_message.rs @@ -0,0 +1,10 @@ +use crate::message_prelude::*; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, PortfolioMessage, MenuBar)] +#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)] +pub enum MenuBarMessage { + SendLayout, +} diff --git a/editor/src/document/menu_bar_message_handler.rs b/editor/src/document/menu_bar_message_handler.rs new file mode 100644 index 00000000..b8a76716 --- /dev/null +++ b/editor/src/document/menu_bar_message_handler.rs @@ -0,0 +1,313 @@ +use super::MenuBarMessage; +use crate::input::keyboard::Key; +use crate::layout::layout_message::LayoutTarget; +use crate::layout::widgets::*; +use crate::message_prelude::*; + +use std::collections::VecDeque; + +#[derive(Debug, Clone, Default)] +pub struct MenuBarMessageHandler {} + +impl MessageHandler for MenuBarMessageHandler { + #[remain::check] + fn process_action(&mut self, message: MenuBarMessage, _data: (), responses: &mut VecDeque) { + use MenuBarMessage::*; + + #[remain::sorted] + match message { + SendLayout => self.register_properties(responses, LayoutTarget::MenuBar), + } + } + + fn actions(&self) -> ActionList { + actions!(MenuBarMessageDiscriminant;) + } +} + +impl PropertyHolder for MenuBarMessageHandler { + fn properties(&self) -> Layout { + Layout::MenuLayout(MenuLayout::new(vec![ + MenuColumn { + label: "File".into(), + children: vec![ + vec![ + MenuEntry { + label: "New…".into(), + icon: Some("File".into()), + action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()), + shortcut: Some(vec![Key::KeyControl, Key::KeyN]), + children: None, + }, + MenuEntry { + label: "Open…".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyO]), + action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Open Recent".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyO]), + action: MenuEntry::no_action(), + icon: None, + children: Some(vec![ + vec![ + MenuEntry { + label: "Reopen Last Closed".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyT]), + ..MenuEntry::default() + }, + MenuEntry { + label: "Clear Recently Opened".into(), + ..MenuEntry::default() + }, + ], + vec![ + MenuEntry { + label: "Some Recent File.gdd".into(), + ..MenuEntry::default() + }, + MenuEntry { + label: "Another Recent File.gdd".into(), + ..MenuEntry::default() + }, + MenuEntry { + label: "An Older File.gdd".into(), + ..MenuEntry::default() + }, + MenuEntry { + label: "Some Other Older File.gdd".into(), + ..MenuEntry::default() + }, + MenuEntry { + label: "Yet Another Older File.gdd".into(), + ..MenuEntry::default() + }, + ], + ]), + }, + ], + vec![ + MenuEntry { + label: "Close".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyW]), + action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Close All".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyW]), + action: MenuEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()), + ..MenuEntry::default() + }, + ], + vec![ + MenuEntry { + label: "Save".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyS]), + action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Save As…".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyS]), + action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Save All".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyS]), + ..MenuEntry::default() + }, + MenuEntry { + label: "Auto-Save".into(), + icon: Some("CheckboxChecked".into()), + ..MenuEntry::default() + }, + ], + vec![ + MenuEntry { + label: "Import…".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyI]), + ..MenuEntry::default() + }, + MenuEntry { + label: "Export…".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyE]), + action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()), + ..MenuEntry::default() + }, + ], + vec![MenuEntry { + label: "Quit".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyQ]), + ..MenuEntry::default() + }], + ], + }, + MenuColumn { + label: "Edit".into(), + children: vec![ + vec![ + MenuEntry { + label: "Undo".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyZ]), + action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Redo".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyZ]), + action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()), + ..MenuEntry::default() + }, + ], + vec![ + MenuEntry { + label: "Cut".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyX]), + action: MenuEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Copy".into(), + icon: Some("Copy".into()), + shortcut: Some(vec![Key::KeyControl, Key::KeyC]), + action: MenuEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()), + ..MenuEntry::default() + }, + // TODO: Fix this + // { label: "Paste", icon: "Paste", shortcut: ["KeyControl", "KeyV"], action: async (): Promise => editor.instance.paste() }, + ], + ], + }, + MenuColumn { + label: "Layer".into(), + children: vec![vec![ + MenuEntry { + label: "Select All".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyA]), + action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Deselect All".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyA]), + action: MenuEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Order".into(), + action: MenuEntry::no_action(), + children: Some(vec![vec![ + MenuEntry { + label: "Raise To Front".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyLeftBracket]), + action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX }.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Raise".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyRightBracket]), + action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 }.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Lower".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyLeftBracket]), + action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 }.into()), + ..MenuEntry::default() + }, + MenuEntry { + label: "Lower to Back".into(), + shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyRightBracket]), + action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MIN }.into()), + ..MenuEntry::default() + }, + ]]), + ..MenuEntry::default() + }, + ]], + }, + MenuColumn { + label: "Document".into(), + children: vec![vec![MenuEntry { + label: "Menu entries coming soon".into(), + ..MenuEntry::default() + }]], + }, + MenuColumn { + label: "View".into(), + children: vec![vec![MenuEntry { + label: "Show/Hide Node Graph (In Development)".into(), + action: MenuEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()), + ..MenuEntry::default() + }]], + }, + MenuColumn { + label: "Help".into(), + children: vec![ + vec![MenuEntry { + label: "About Graphite".into(), + action: MenuEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()), + ..MenuEntry::default() + }], + vec![ + MenuEntry { + label: "Report a Bug".into(), + action: MenuEntry::create_action(|_| { + FrontendMessage::TriggerVisitLink { + url: "https://github.com/GraphiteEditor/Graphite/issues/new".into(), + } + .into() + }), + ..MenuEntry::default() + }, + MenuEntry { + label: "Visit on GitHub".into(), + action: MenuEntry::create_action(|_| { + FrontendMessage::TriggerVisitLink { + url: "https://github.com/GraphiteEditor/Graphite".into(), + } + .into() + }), + ..MenuEntry::default() + }, + ], + vec![ + MenuEntry { + label: "Debug: Set Log Level".into(), + action: MenuEntry::no_action(), + children: Some(vec![vec![ + MenuEntry { + label: "Log Level Info".into(), + action: MenuEntry::create_action(|_| GlobalMessage::LogInfo.into()), + shortcut: Some(vec![Key::Key1]), + ..MenuEntry::default() + }, + MenuEntry { + label: "Log Level Debug".into(), + action: MenuEntry::create_action(|_| GlobalMessage::LogDebug.into()), + shortcut: Some(vec![Key::Key2]), + ..MenuEntry::default() + }, + MenuEntry { + label: "Log Level Trace".into(), + action: MenuEntry::create_action(|_| GlobalMessage::LogTrace.into()), + shortcut: Some(vec![Key::Key3]), + ..MenuEntry::default() + }, + ]]), + ..MenuEntry::default() + }, + MenuEntry { + label: "Debug: Panic (DANGER)".into(), + action: MenuEntry::create_action(|_| panic!()), + ..MenuEntry::default() + }, + ], + ], + }, + ])) + } +} diff --git a/editor/src/document/mod.rs b/editor/src/document/mod.rs index 2e7717c9..e2056df7 100644 --- a/editor/src/document/mod.rs +++ b/editor/src/document/mod.rs @@ -8,6 +8,8 @@ mod artboard_message; mod artboard_message_handler; mod document_message; mod document_message_handler; +mod menu_bar_message; +mod menu_bar_message_handler; mod movement_message; mod movement_message_handler; mod overlays_message; @@ -34,6 +36,11 @@ pub use movement_message::{MovementMessage, MovementMessageDiscriminant}; #[doc(inline)] pub use movement_message_handler::MovementMessageHandler; +#[doc(inline)] +pub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant}; +#[doc(inline)] +pub use menu_bar_message_handler::MenuBarMessageHandler; + #[doc(inline)] pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant}; #[doc(inline)] diff --git a/editor/src/document/portfolio_message.rs b/editor/src/document/portfolio_message.rs index b0f0a9eb..5cf987cf 100644 --- a/editor/src/document/portfolio_message.rs +++ b/editor/src/document/portfolio_message.rs @@ -14,6 +14,9 @@ pub enum PortfolioMessage { #[remain::unsorted] #[child] Document(DocumentMessage), + #[remain::unsorted] + #[child] + MenuBar(MenuBarMessage), // Messages AutoSaveActiveDocument, diff --git a/editor/src/document/portfolio_message_handler.rs b/editor/src/document/portfolio_message_handler.rs index c1dc95df..88ca085f 100644 --- a/editor/src/document/portfolio_message_handler.rs +++ b/editor/src/document/portfolio_message_handler.rs @@ -1,5 +1,5 @@ use super::clipboards::{CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; -use super::DocumentMessageHandler; +use super::{DocumentMessageHandler, MenuBarMessageHandler}; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; use crate::frontend::utility_types::FrontendDocumentDetails; use crate::input::InputPreprocessorMessageHandler; @@ -15,6 +15,7 @@ use std::collections::{HashMap, VecDeque}; #[derive(Debug, Clone)] pub struct PortfolioMessageHandler { + menu_bar_message_handler: MenuBarMessageHandler, documents: HashMap, document_ids: Vec, active_document_id: u64, @@ -130,6 +131,7 @@ impl Default for PortfolioMessageHandler { copy_buffer: [EMPTY_VEC; INTERNAL_CLIPBOARD_COUNT as usize], active_document_id: starting_key, font_cache: Default::default(), + menu_bar_message_handler: MenuBarMessageHandler::default(), } } } @@ -145,6 +147,8 @@ impl MessageHandler for Port // Sub-messages #[remain::unsorted] Document(message) => self.documents.get_mut(&self.active_document_id).unwrap().process_action(message, (ipp, &self.font_cache), responses), + #[remain::unsorted] + MenuBar(message) => self.menu_bar_message_handler.process_action(message, (), responses), // Messages AutoSaveActiveDocument => responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into()), diff --git a/editor/src/document/properties_panel_message_handler.rs b/editor/src/document/properties_panel_message_handler.rs index a76d4cc7..7b07cd96 100644 --- a/editor/src/document/properties_panel_message_handler.rs +++ b/editor/src/document/properties_panel_message_handler.rs @@ -3,8 +3,8 @@ use super::utility_types::TargetDocument; use crate::document::properties_panel_message::TransformOp; use crate::layout::layout_message::LayoutTarget; use crate::layout::widgets::{ - ColorInput, FontInput, IconLabel, LayoutRow, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput, TextLabel, Widget, - WidgetCallback, WidgetHolder, WidgetLayout, + ColorInput, FontInput, IconLabel, Layout, LayoutGroup, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput, TextLabel, + Widget, WidgetCallback, WidgetHolder, WidgetLayout, }; use crate::message_prelude::*; @@ -141,14 +141,14 @@ impl<'a> MessageHandler { responses.push_back( LayoutMessage::SendLayout { - layout: WidgetLayout::new(vec![]), + layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), layout_target: LayoutTarget::PropertiesOptions, } .into(), ); responses.push_back( LayoutMessage::SendLayout { - layout: WidgetLayout::new(vec![]), + layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), layout_target: LayoutTarget::PropertiesSections, } .into(), @@ -212,14 +212,14 @@ impl<'a> MessageHandler MessageHandler, font_cache: &FontCache) { - let options_bar = vec![LayoutRow::Row { + let options_bar = vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::IconLabel(IconLabel { icon: "NodeArtboard".into(), @@ -288,10 +288,10 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque, font_cache: &FontCache) { - let options_bar = vec![LayoutRow::Row { + let options_bar = vec![LayoutGroup::Row { widgets: vec![ match &layer.data { LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel { @@ -497,25 +497,25 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque LayoutRow { - LayoutRow::Section { +fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutGroup { + LayoutGroup::Section { name: "Transform".into(), layout: vec![ - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Location".into(), @@ -557,7 +557,7 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Rotation".into(), @@ -582,7 +582,7 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Scale".into(), @@ -624,7 +624,7 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Dimensions".into(), @@ -670,13 +670,13 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow { } } -fn node_section_font(layer: &TextLayer) -> LayoutRow { +fn node_section_font(layer: &TextLayer) -> LayoutGroup { let font = layer.font.clone(); let size = layer.size; - LayoutRow::Section { + LayoutGroup::Section { name: "Font".into(), layout: vec![ - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Text".into(), @@ -692,7 +692,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Font".into(), @@ -717,7 +717,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Style".into(), @@ -742,7 +742,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Size".into(), @@ -772,7 +772,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow { } } -fn node_gradient_type(gradient: &Gradient) -> LayoutRow { +fn node_gradient_type(gradient: &Gradient) -> LayoutGroup { let selected_index = match gradient.gradient_type { GradientType::Linear => 0, GradientType::Radial => 1, @@ -781,7 +781,7 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutRow { cloned_gradient_linear.gradient_type = GradientType::Linear; let mut cloned_gradient_radial = gradient.clone(); cloned_gradient_radial.gradient_type = GradientType::Radial; - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Gradient Type".into(), @@ -824,10 +824,10 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutRow { } } -fn node_gradient_color(gradient: &Gradient, percent_label: &'static str, position: usize) -> LayoutRow { +fn node_gradient_color(gradient: &Gradient, percent_label: &'static str, position: usize) -> LayoutGroup { let gradient_clone = Rc::new(gradient.clone()); let send_fill_message = move |new_gradient: Gradient| PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into(); - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: format!("Gradient: {}", percent_label), @@ -860,11 +860,11 @@ fn node_gradient_color(gradient: &Gradient, percent_label: &'static str, positio } } -fn node_section_fill(fill: &Fill) -> Option { +fn node_section_fill(fill: &Fill) -> Option { match fill { - Fill::Solid(_) | Fill::None => Some(LayoutRow::Section { + Fill::Solid(_) | Fill::None => Some(LayoutGroup::Section { name: "Fill".into(), - layout: vec![LayoutRow::Row { + layout: vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Color".into(), @@ -893,14 +893,14 @@ fn node_section_fill(fill: &Fill) -> Option { ], }], }), - Fill::Gradient(gradient) => Some(LayoutRow::Section { + Fill::Gradient(gradient) => Some(LayoutGroup::Section { name: "Fill".into(), layout: vec![node_gradient_type(gradient), node_gradient_color(gradient, "0%", 0), node_gradient_color(gradient, "100%", 1)], }), } } -fn node_section_stroke(stroke: &Stroke) -> LayoutRow { +fn node_section_stroke(stroke: &Stroke) -> LayoutGroup { // We have to make multiple variables because they get moved into different closures. let internal_stroke1 = stroke.clone(); let internal_stroke2 = stroke.clone(); @@ -914,10 +914,10 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { let internal_stroke10 = stroke.clone(); let internal_stroke11 = stroke.clone(); - LayoutRow::Section { + LayoutGroup::Section { name: "Stroke".into(), layout: vec![ - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Color".into(), @@ -939,7 +939,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Weight".into(), @@ -964,7 +964,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Dash Lengths".into(), @@ -985,7 +985,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Dash Offset".into(), @@ -1010,7 +1010,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Line Cap".into(), @@ -1057,7 +1057,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { })), ], }, - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Line Join".into(), @@ -1105,7 +1105,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow { ], }, // TODO: Gray out this row when Line Join isn't set to Miter - LayoutRow::Row { + LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Miter Limit".into(), diff --git a/editor/src/frontend/frontend_message.rs b/editor/src/frontend/frontend_message.rs index 8ae40e58..ead55727 100644 --- a/editor/src/frontend/frontend_message.rs +++ b/editor/src/frontend/frontend_message.rs @@ -1,7 +1,7 @@ use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon}; use crate::document::layer_panel::{LayerPanelEntry, RawBuffer}; use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::SubLayout; +use crate::layout::widgets::{MenuColumn, SubLayout}; use crate::message_prelude::*; use crate::misc::HintData; use crate::Color; @@ -49,6 +49,7 @@ pub enum FrontendMessage { UpdateImageData { image_data: Vec }, UpdateInputHints { hint_data: HintData }, UpdateLayerTreeOptionsLayout { layout_target: LayoutTarget, layout: SubLayout }, + UpdateMenuBarLayout { layout_target: LayoutTarget, layout: Vec }, UpdateMouseCursor { cursor: MouseCursorIcon }, UpdateNodeGraphVisibility { visible: bool }, UpdateOpenDocumentsList { open_documents: Vec }, diff --git a/editor/src/layout/layout_message.rs b/editor/src/layout/layout_message.rs index e465ec52..3d90c560 100644 --- a/editor/src/layout/layout_message.rs +++ b/editor/src/layout/layout_message.rs @@ -1,4 +1,4 @@ -use super::widgets::WidgetLayout; +use super::widgets::Layout; use crate::message_prelude::*; use serde::{Deserialize, Serialize}; @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; #[impl_message(Message, Layout)] #[derive(PartialEq, Clone, Deserialize, Serialize, Debug)] pub enum LayoutMessage { - SendLayout { layout: WidgetLayout, layout_target: LayoutTarget }, + SendLayout { layout: Layout, layout_target: LayoutTarget }, UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value }, } @@ -19,6 +19,7 @@ pub enum LayoutTarget { DocumentBar, DocumentMode, LayerTreeOptions, + MenuBar, PropertiesOptions, PropertiesSections, ToolOptions, diff --git a/editor/src/layout/layout_message_handler.rs b/editor/src/layout/layout_message_handler.rs index 31dd84a3..098d229b 100644 --- a/editor/src/layout/layout_message_handler.rs +++ b/editor/src/layout/layout_message_handler.rs @@ -1,5 +1,5 @@ use super::layout_message::LayoutTarget; -use super::widgets::WidgetLayout; +use super::widgets::Layout; use crate::layout::widgets::Widget; use crate::message_prelude::*; @@ -10,46 +10,50 @@ use std::collections::VecDeque; #[derive(Debug, Clone, Default)] pub struct LayoutMessageHandler { - layouts: [WidgetLayout; LayoutTarget::LayoutTargetLength as usize], + layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } impl LayoutMessageHandler { #[remain::check] fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque) { - let widget_layout = &self.layouts[layout_target as usize]; + let layout = &self.layouts[layout_target as usize]; #[remain::sorted] let message = match layout_target { LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, + }, + LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout { + layout_target, + layout: layout.clone().unwrap_menu_layout().layout, }, LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, - layout: widget_layout.layout.clone(), + layout: layout.clone().unwrap_widget_layout().layout, }, #[remain::unsorted] @@ -127,6 +131,10 @@ impl MessageHandler for LayoutMessageHandler { responses.push_back(callback_message); } Widget::IconLabel(_) => {} + Widget::Invisible(invisible) => { + let callback_message = (invisible.on_update.callback)(&()); + responses.push_back(callback_message); + } Widget::NumberInput(number_input) => match value { Value::Number(num) => { let update_value = num.as_f64().unwrap(); diff --git a/editor/src/layout/widgets.rs b/editor/src/layout/widgets.rs index 71be6d5d..de8b04fd 100644 --- a/editor/src/layout/widgets.rs +++ b/editor/src/layout/widgets.rs @@ -1,14 +1,14 @@ use std::rc::Rc; use super::layout_message::LayoutTarget; -use crate::message_prelude::*; +use crate::{input::keyboard::Key, message_prelude::*}; use derivative::*; use serde::{Deserialize, Serialize}; pub trait PropertyHolder { - fn properties(&self) -> WidgetLayout { - WidgetLayout::default() + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::default()) } fn register_properties(&self, responses: &mut VecDeque, layout_target: LayoutTarget) { @@ -22,6 +22,149 @@ pub trait PropertyHolder { } } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Layout { + WidgetLayout(WidgetLayout), + MenuLayout(MenuLayout), +} + +impl Layout { + pub fn unwrap_widget_layout(self) -> WidgetLayout { + if let Layout::WidgetLayout(widget_layout) = self { + widget_layout + } else { + panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self) + } + } + + pub fn unwrap_menu_layout(self) -> MenuLayout { + if let Layout::MenuLayout(menu_layout) = self { + menu_layout + } else { + panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self) + } + } + + pub fn iter(&self) -> Box + '_> { + match self { + Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter()), + Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter()), + } + } + + pub fn iter_mut(&mut self) -> Box + '_> { + match self { + Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter_mut()), + Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()), + } + } +} + +impl Default for Layout { + fn default() -> Self { + Layout::WidgetLayout(WidgetLayout::default()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MenuEntry { + pub label: String, + pub icon: Option, + pub children: Option>>, + pub action: WidgetHolder, + pub shortcut: Option>, +} + +impl MenuEntry { + pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder { + WidgetHolder::new(Widget::Invisible(Invisible { + on_update: WidgetCallback::new(callback), + })) + } + + pub fn no_action() -> WidgetHolder { + MenuEntry::create_action(|_| Message::NoOp) + } +} + +impl Default for MenuEntry { + fn default() -> Self { + Self { + action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()), + label: "".into(), + icon: None, + children: None, + shortcut: None, + } + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct MenuColumn { + pub label: String, + pub children: Vec>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct MenuLayout { + pub layout: Vec, +} + +impl MenuLayout { + pub fn new(layout: Vec) -> Self { + Self { layout } + } + + pub fn iter(&self) -> impl Iterator + '_ { + MenuLayoutIter { + stack: self.layout.iter().flat_map(|column| column.children.iter()).flat_map(|group| group.iter()).collect(), + } + } + + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + MenuLayoutIterMut { + stack: self.layout.iter_mut().flat_map(|column| column.children.iter_mut()).flat_map(|group| group.iter_mut()).collect(), + } + } +} + +#[derive(Debug, Default)] +pub struct MenuLayoutIter<'a> { + pub stack: Vec<&'a MenuEntry>, +} + +impl<'a> Iterator for MenuLayoutIter<'a> { + type Item = &'a WidgetHolder; + + fn next(&mut self) -> Option { + match self.stack.pop() { + Some(menu_entry) => { + self.stack.extend(menu_entry.children.iter().flat_map(|group| group.iter()).flat_map(|entry| entry.iter())); + Some(&menu_entry.action) + } + None => None, + } + } +} + +pub struct MenuLayoutIterMut<'a> { + pub stack: Vec<&'a mut MenuEntry>, +} + +impl<'a> Iterator for MenuLayoutIterMut<'a> { + type Item = &'a mut WidgetHolder; + + fn next(&mut self) -> Option { + match self.stack.pop() { + Some(menu_entry) => { + self.stack.extend(menu_entry.children.iter_mut().flat_map(|group| group.iter_mut()).flat_map(|entry| entry.iter_mut())); + Some(&mut menu_entry.action) + } + None => None, + } + } +} + #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct WidgetLayout { pub layout: SubLayout, @@ -47,12 +190,11 @@ impl WidgetLayout { } } -pub type SubLayout = Vec; +pub type SubLayout = Vec; -// TODO: Rename LayoutRow to something more generic #[remain::sorted] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum LayoutRow { +pub enum LayoutGroup { Column { #[serde(rename = "columnWidgets")] widgets: Vec, @@ -69,7 +211,7 @@ pub enum LayoutRow { #[derive(Debug, Default)] pub struct WidgetIter<'a> { - pub stack: Vec<&'a LayoutRow>, + pub stack: Vec<&'a LayoutGroup>, pub current_slice: Option<&'a [WidgetHolder]>, } @@ -83,15 +225,15 @@ impl<'a> Iterator for WidgetIter<'a> { } match self.stack.pop() { - Some(LayoutRow::Column { widgets }) => { + Some(LayoutGroup::Column { widgets }) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutRow::Row { widgets }) => { + Some(LayoutGroup::Row { widgets }) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutRow::Section { name: _, layout }) => { + Some(LayoutGroup::Section { name: _, layout }) => { for layout_row in layout { self.stack.push(layout_row); } @@ -104,7 +246,7 @@ impl<'a> Iterator for WidgetIter<'a> { #[derive(Debug, Default)] pub struct WidgetIterMut<'a> { - pub stack: Vec<&'a mut LayoutRow>, + pub stack: Vec<&'a mut LayoutGroup>, pub current_slice: Option<&'a mut [WidgetHolder]>, } @@ -118,15 +260,15 @@ impl<'a> Iterator for WidgetIterMut<'a> { }; match self.stack.pop() { - Some(LayoutRow::Column { widgets }) => { + Some(LayoutGroup::Column { widgets }) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutRow::Row { widgets }) => { + Some(LayoutGroup::Row { widgets }) => { self.current_slice = Some(widgets); self.next() } - Some(LayoutRow::Section { name: _, layout }) => { + Some(LayoutGroup::Section { name: _, layout }) => { for layout_row in layout { self.stack.push(layout_row); } @@ -175,6 +317,7 @@ pub enum Widget { FontInput(FontInput), IconButton(IconButton), IconLabel(IconLabel), + Invisible(Invisible), NumberInput(NumberInput), OptionalInput(OptionalInput), PopoverButton(PopoverButton), @@ -425,3 +568,14 @@ pub struct TextLabel { #[serde(rename = "tableAlign")] pub table_align: bool, } + +// This widget allows for the flexible use of the layout system +// In a custom layout one can define a widget that is just used to trigger code on the backend +// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct Invisible { + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback<()>, +} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 56958fd8..07fe8fab 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -64,6 +64,7 @@ pub mod message_prelude { pub use crate::dialog::messages::*; pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant}; pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant}; + pub use crate::document::{MenuBarMessage, MenuBarMessageDiscriminant}; pub use crate::document::{MovementMessage, MovementMessageDiscriminant}; pub use crate::document::{OverlaysMessage, OverlaysMessageDiscriminant}; pub use crate::document::{PortfolioMessage, PortfolioMessageDiscriminant}; diff --git a/editor/src/viewport_tools/tool.rs b/editor/src/viewport_tools/tool.rs index 71794f93..b86155da 100644 --- a/editor/src/viewport_tools/tool.rs +++ b/editor/src/viewport_tools/tool.rs @@ -2,7 +2,7 @@ use super::tools::*; use crate::communication::message_handler::MessageHandler; use crate::document::DocumentMessageHandler; use crate::input::InputPreprocessorMessageHandler; -use crate::layout::widgets::{IconButton, LayoutRow, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use graphene::color::Color; @@ -58,7 +58,7 @@ impl ToolData { } impl PropertyHolder for ToolData { - fn properties(&self) -> WidgetLayout { + fn properties(&self) -> Layout { let tool_groups_layout = ToolType::list_tools_in_groups() .iter() .flat_map(|group| { @@ -88,9 +88,9 @@ impl PropertyHolder for ToolData { .skip(1) .collect(); - WidgetLayout { - layout: vec![LayoutRow::Column { widgets: tool_groups_layout }], - } + Layout::WidgetLayout(WidgetLayout { + layout: vec![LayoutGroup::Column { widgets: tool_groups_layout }], + }) } } diff --git a/editor/src/viewport_tools/tools/freehand_tool.rs b/editor/src/viewport_tools/tools/freehand_tool.rs index f2e49ce4..b5826613 100644 --- a/editor/src/viewport_tools/tools/freehand_tool.rs +++ b/editor/src/viewport_tools/tools/freehand_tool.rs @@ -1,6 +1,6 @@ use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::MouseMotion; -use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo}; use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; @@ -56,8 +56,8 @@ enum FreehandToolFsmState { } impl PropertyHolder for FreehandTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { unit: " px".into(), label: "Weight".into(), @@ -67,7 +67,7 @@ impl PropertyHolder for FreehandTool { on_update: WidgetCallback::new(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), ..NumberInput::default() }))], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/gradient_tool.rs b/editor/src/viewport_tools/tools/gradient_tool.rs index 154dddc3..545502bd 100644 --- a/editor/src/viewport_tools/tools/gradient_tool.rs +++ b/editor/src/viewport_tools/tools/gradient_tool.rs @@ -2,7 +2,7 @@ use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE, V use crate::document::DocumentMessageHandler; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::{LayoutRow, PropertyHolder, RadioEntryData, RadioInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{Layout, LayoutGroup, PropertyHolder, RadioEntryData, RadioInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; @@ -90,8 +90,8 @@ impl<'a> MessageHandler> for GradientTool } impl PropertyHolder for GradientTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::RadioInput(RadioInput { selected_index: if self.options.gradient_type == GradientType::Radial { 1 } else { 0 }, entries: vec![ @@ -111,7 +111,7 @@ impl PropertyHolder for GradientTool { }, ], }))], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/line_tool.rs b/editor/src/viewport_tools/tools/line_tool.rs index 2346cd8f..905b190f 100644 --- a/editor/src/viewport_tools/tools/line_tool.rs +++ b/editor/src/viewport_tools/tools/line_tool.rs @@ -2,7 +2,7 @@ use crate::consts::{DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE}; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::mouse::ViewportPosition; -use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; @@ -57,8 +57,8 @@ pub enum LineOptionsUpdate { } impl PropertyHolder for LineTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { unit: " px".into(), label: "Weight".into(), @@ -68,7 +68,7 @@ impl PropertyHolder for LineTool { on_update: WidgetCallback::new(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), ..NumberInput::default() }))], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/pen_tool.rs b/editor/src/viewport_tools/tools/pen_tool.rs index d439d8e4..8e106ab8 100644 --- a/editor/src/viewport_tools/tools/pen_tool.rs +++ b/editor/src/viewport_tools/tools/pen_tool.rs @@ -3,7 +3,7 @@ use crate::document::DocumentMessageHandler; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::InputPreprocessorMessageHandler; -use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; @@ -68,8 +68,8 @@ pub enum PenOptionsUpdate { } impl PropertyHolder for PenTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { unit: " px".into(), label: "Weight".into(), @@ -79,7 +79,7 @@ impl PropertyHolder for PenTool { on_update: WidgetCallback::new(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), ..NumberInput::default() }))], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/select_tool.rs b/editor/src/viewport_tools/tools/select_tool.rs index 62c6128e..e3f18f5b 100644 --- a/editor/src/viewport_tools/tools/select_tool.rs +++ b/editor/src/viewport_tools/tools/select_tool.rs @@ -4,7 +4,7 @@ use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::mouse::ViewportPosition; -use crate::layout::widgets::{IconButton, LayoutRow, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::{self, SnapHandler}; @@ -57,8 +57,8 @@ pub enum SelectToolMessage { } impl PropertyHolder for SelectTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::IconButton(IconButton { icon: "AlignLeft".into(), @@ -224,7 +224,7 @@ impl PropertyHolder for SelectTool { text: "The contents of this popover menu are coming soon".into(), })), ], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/shape_tool.rs b/editor/src/viewport_tools/tools/shape_tool.rs index 1ffd04bb..97b29b43 100644 --- a/editor/src/viewport_tools/tools/shape_tool.rs +++ b/editor/src/viewport_tools/tools/shape_tool.rs @@ -2,7 +2,7 @@ use super::shared::resize::Resize; use crate::consts::DRAG_THRESHOLD; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; @@ -55,8 +55,8 @@ pub enum ShapeOptionsUpdate { } impl PropertyHolder for ShapeTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { label: "Sides".into(), value: Some(self.options.vertices as f64), @@ -66,7 +66,7 @@ impl PropertyHolder for ShapeTool { on_update: WidgetCallback::new(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()), ..NumberInput::default() }))], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/spline_tool.rs b/editor/src/viewport_tools/tools/spline_tool.rs index 6601b920..8bd5d04a 100644 --- a/editor/src/viewport_tools/tools/spline_tool.rs +++ b/editor/src/viewport_tools/tools/spline_tool.rs @@ -1,7 +1,7 @@ use crate::consts::DRAG_THRESHOLD; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; @@ -60,8 +60,8 @@ pub enum SplineOptionsUpdate { } impl PropertyHolder for SplineTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { unit: " px".into(), label: "Weight".into(), @@ -71,7 +71,7 @@ impl PropertyHolder for SplineTool { on_update: WidgetCallback::new(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), ..NumberInput::default() }))], - }]) + }])) } } diff --git a/editor/src/viewport_tools/tools/text_tool.rs b/editor/src/viewport_tools/tools/text_tool.rs index dae4c070..97fd5dc2 100644 --- a/editor/src/viewport_tools/tools/text_tool.rs +++ b/editor/src/viewport_tools/tools/text_tool.rs @@ -3,7 +3,7 @@ use crate::document::DocumentMessageHandler; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::{FontInput, LayoutRow, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::layout::widgets::{FontInput, Layout, LayoutGroup, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; @@ -71,8 +71,8 @@ pub enum TextOptionsUpdate { } impl PropertyHolder for TextTool { - fn properties(&self) -> WidgetLayout { - WidgetLayout::new(vec![LayoutRow::Row { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::FontInput(FontInput { is_style_picker: false, @@ -116,7 +116,7 @@ impl PropertyHolder for TextTool { ..NumberInput::default() })), ], - }]) + }])) } } diff --git a/frontend/src/components/widgets/WidgetLayout.vue b/frontend/src/components/widgets/WidgetLayout.vue index bda7a80e..1e03dc18 100644 --- a/frontend/src/components/widgets/WidgetLayout.vue +++ b/frontend/src/components/widgets/WidgetLayout.vue @@ -2,7 +2,7 @@ @@ -18,7 +18,7 @@