Menu bar definition in backend (#681)

* initial menu layout working

* removed api.rs functions

* no action shortcut for no-op

* code review

* nitpicks
This commit is contained in:
mfish33
2022-06-18 18:46:23 -06:00
committed by Keavon Chambers
parent 9bd27ec3f8
commit 5d6d2b22bc
35 changed files with 781 additions and 475 deletions

View File

@@ -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!("-------------------------------------------------");

View File

@@ -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 },
]))
}
}

View File

@@ -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 },
]))
}
}

View File

@@ -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 },
]))
}
}

View File

@@ -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 },
]))
}
}

View File

@@ -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()
}))],
},
])
]))
}
}

View File

@@ -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 },
]))
}
}

View File

@@ -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 },
]))
}
}

View File

@@ -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<Message>) {
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(),

View File

@@ -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,
}

View File

@@ -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<MenuBarMessage, ()> for MenuBarMessageHandler {
#[remain::check]
fn process_action(&mut self, message: MenuBarMessage, _data: (), responses: &mut VecDeque<Message>) {
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<void> => 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()
},
],
],
},
]))
}
}

View File

@@ -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)]

View File

@@ -14,6 +14,9 @@ pub enum PortfolioMessage {
#[remain::unsorted]
#[child]
Document(DocumentMessage),
#[remain::unsorted]
#[child]
MenuBar(MenuBarMessage),
// Messages
AutoSaveActiveDocument,

View File

@@ -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<u64, DocumentMessageHandler>,
document_ids: Vec<u64>,
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<PortfolioMessage, &InputPreprocessorMessageHandler> 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()),

View File

@@ -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<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
ClearSelection => {
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<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesOptions,
layout: WidgetLayout::default(),
layout: Layout::WidgetLayout(WidgetLayout::default()),
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesSections,
layout: WidgetLayout::default(),
layout: Layout::WidgetLayout(WidgetLayout::default()),
}
.into(),
);
@@ -243,7 +243,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
}
fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, 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<Me
panic!("Artboard must have a solid fill")
};
vec![LayoutRow::Section {
vec![LayoutGroup::Section {
name: "Artboard".into(),
layout: vec![
LayoutRow::Row {
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Location".into(),
@@ -333,7 +333,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
})),
],
},
LayoutRow::Row {
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Dimensions".into(),
@@ -375,7 +375,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
})),
],
},
LayoutRow::Row {
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Background".into(),
@@ -409,14 +409,14 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(options_bar),
layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)),
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(properties_body),
layout: Layout::WidgetLayout(WidgetLayout::new(properties_body)),
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
@@ -424,7 +424,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
}
fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, 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<Mes
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(options_bar),
layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)),
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(properties_body),
layout: Layout::WidgetLayout(WidgetLayout::new(properties_body)),
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
);
}
fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> 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<LayoutRow> {
fn node_section_fill(fill: &Fill) -> Option<LayoutGroup> {
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<LayoutRow> {
],
}],
}),
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(),

View File

@@ -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<FrontendImageData> },
UpdateInputHints { hint_data: HintData },
UpdateLayerTreeOptionsLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateMenuBarLayout { layout_target: LayoutTarget, layout: Vec<MenuColumn> },
UpdateMouseCursor { cursor: MouseCursorIcon },
UpdateNodeGraphVisibility { visible: bool },
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> },

View File

@@ -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,

View File

@@ -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<Message>) {
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<LayoutMessage, ()> 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();

View File

@@ -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<Message>, 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<dyn Iterator<Item = &WidgetHolder> + '_> {
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<dyn Iterator<Item = &mut WidgetHolder> + '_> {
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<String>,
pub children: Option<Vec<Vec<MenuEntry>>>,
pub action: WidgetHolder,
pub shortcut: Option<Vec<Key>>,
}
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<Vec<MenuEntry>>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuLayout {
pub layout: Vec<MenuColumn>,
}
impl MenuLayout {
pub fn new(layout: Vec<MenuColumn>) -> Self {
Self { layout }
}
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
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<Item = &mut WidgetHolder> + '_ {
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<Self::Item> {
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<Self::Item> {
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<LayoutRow>;
pub type SubLayout = Vec<LayoutGroup>;
// 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<WidgetHolder>,
@@ -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<()>,
}

View File

@@ -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};

View File

@@ -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 }],
})
}
}

View File

@@ -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()
}))],
}])
}]))
}
}

View File

@@ -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<ToolMessage, ToolActionHandlerData<'a>> 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 {
},
],
}))],
}])
}]))
}
}

View File

@@ -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()
}))],
}])
}]))
}
}

View File

@@ -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()
}))],
}])
}]))
}
}

View File

@@ -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(),
})),
],
}])
}]))
}
}

View File

@@ -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()
}))],
}])
}]))
}
}

View File

@@ -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()
}))],
}])
}]))
}
}

View File

@@ -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()
})),
],
}])
}]))
}
}