mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-02 20:42:16 +00:00
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:
parent
9bd27ec3f8
commit
5d6d2b22bc
35 changed files with 781 additions and 475 deletions
|
@ -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!("-------------------------------------------------");
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}))],
|
||||
},
|
||||
])
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
10
editor/src/document/menu_bar_message.rs
Normal file
10
editor/src/document/menu_bar_message.rs
Normal 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,
|
||||
}
|
313
editor/src/document/menu_bar_message_handler.rs
Normal file
313
editor/src/document/menu_bar_message_handler.rs
Normal 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()
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
]))
|
||||
}
|
||||
}
|
|
@ -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)]
|
||||
|
|
|
@ -14,6 +14,9 @@ pub enum PortfolioMessage {
|
|||
#[remain::unsorted]
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
MenuBar(MenuBarMessage),
|
||||
|
||||
// Messages
|
||||
AutoSaveActiveDocument,
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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> },
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<()>,
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 }],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}))],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
},
|
||||
],
|
||||
}))],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}))],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}))],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
})),
|
||||
],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}))],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}))],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
})),
|
||||
],
|
||||
}])
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<template>
|
||||
<div class="widget-layout">
|
||||
<component :is="layoutRowType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layout_target" v-for="(layoutRow, index) in layout.layout" :key="index" />
|
||||
<component :is="LayoutGroupType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layout_target" v-for="(layoutRow, index) in layout.layout" :key="index" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { isWidgetColumn, isWidgetRow, isWidgetSection, LayoutRow, WidgetLayout } from "@/wasm-communication/messages";
|
||||
import { isWidgetColumn, isWidgetRow, isWidgetSection, LayoutGroup, WidgetLayout } from "@/wasm-communication/messages";
|
||||
|
||||
import WidgetSection from "@/components/widgets/groups/WidgetSection.vue";
|
||||
import WidgetRow from "@/components/widgets/WidgetRow.vue";
|
||||
|
@ -28,7 +28,7 @@ export default defineComponent({
|
|||
layout: { type: Object as PropType<WidgetLayout>, required: true },
|
||||
},
|
||||
methods: {
|
||||
layoutRowType(layoutRow: LayoutRow): unknown {
|
||||
LayoutGroupType(layoutRow: LayoutGroup): unknown {
|
||||
if (isWidgetColumn(layoutRow)) return WidgetRow;
|
||||
if (isWidgetRow(layoutRow)) return WidgetRow;
|
||||
if (isWidgetSection(layoutRow)) return WidgetSection;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<TextLabel :bold="true">{{ widgetData.name }}</TextLabel>
|
||||
</button>
|
||||
<LayoutCol class="body" v-if="expanded">
|
||||
<component :is="layoutRowType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layoutTarget" v-for="(layoutRow, index) in widgetData.layout" :key="index"></component>
|
||||
<component :is="layoutGroupType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layoutTarget" v-for="(layoutRow, index) in widgetData.layout" :key="index"></component>
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
</template>
|
||||
|
@ -72,7 +72,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { isWidgetRow, isWidgetSection, LayoutRow as LayoutSystemRow, WidgetSection as WidgetSectionFromJsMessages } from "@/wasm-communication/messages";
|
||||
import { isWidgetRow, isWidgetSection, LayoutGroup, WidgetSection as WidgetSectionFromJsMessages } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
|
@ -96,9 +96,9 @@ const WidgetSection = defineComponent({
|
|||
updateLayout(widgetId: bigint, value: unknown) {
|
||||
this.editor.instance.update_layout(this.layoutTarget, widgetId, value);
|
||||
},
|
||||
layoutRowType(layoutRow: LayoutSystemRow): unknown {
|
||||
if (isWidgetRow(layoutRow)) return WidgetRow;
|
||||
if (isWidgetSection(layoutRow)) return WidgetSection;
|
||||
layoutGroupType(layoutGroup: LayoutGroup): unknown {
|
||||
if (isWidgetRow(layoutGroup)) return WidgetRow;
|
||||
if (isWidgetSection(layoutGroup)) return WidgetSection;
|
||||
|
||||
throw new Error("Layout row type does not exist");
|
||||
},
|
||||
|
|
|
@ -72,151 +72,42 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { Editor } from "@/wasm-communication/editor";
|
||||
import { MenuEntry, UpdateMenuBarLayout } from "@/wasm-communication/messages";
|
||||
|
||||
import MenuList, { MenuListEntry, MenuListEntries } from "@/components/floating-menus/MenuList.vue";
|
||||
import MenuList, { MenuListEntry } from "@/components/floating-menus/MenuList.vue";
|
||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
|
||||
function makeEntries(editor: Editor): MenuListEntries {
|
||||
return [
|
||||
{
|
||||
label: "File",
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "New…", icon: "File", shortcut: ["KeyControl", "KeyN"], shortcutRequiresLock: true, action: (): void => editor.instance.request_new_document_dialog() },
|
||||
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: (): void => editor.instance.open_document() },
|
||||
{
|
||||
label: "Open Recent",
|
||||
shortcut: ["KeyControl", "KeyShift", "KeyO"],
|
||||
action: (): void => undefined,
|
||||
children: [
|
||||
[{ label: "Reopen Last Closed", shortcut: ["KeyControl", "KeyShift", "KeyT"], shortcutRequiresLock: true }, { label: "Clear Recently Opened" }],
|
||||
[
|
||||
{ label: "Some Recent File.gdd" },
|
||||
{ label: "Another Recent File.gdd" },
|
||||
{ label: "An Older File.gdd" },
|
||||
{ label: "Some Other Older File.gdd" },
|
||||
{ label: "Yet Another Older File.gdd" },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "Close", shortcut: ["KeyControl", "KeyW"], shortcutRequiresLock: true, action: async (): Promise<void> => editor.instance.close_active_document_with_confirmation() },
|
||||
{ label: "Close All", shortcut: ["KeyControl", "KeyAlt", "KeyW"], action: async (): Promise<void> => editor.instance.close_all_documents_with_confirmation() },
|
||||
],
|
||||
[
|
||||
{ label: "Save", shortcut: ["KeyControl", "KeyS"], action: async (): Promise<void> => editor.instance.save_document() },
|
||||
{ label: "Save As…", shortcut: ["KeyControl", "KeyShift", "KeyS"], action: async (): Promise<void> => editor.instance.save_document() },
|
||||
{ label: "Save All", shortcut: ["KeyControl", "KeyAlt", "KeyS"] },
|
||||
{ label: "Auto-Save", icon: "CheckboxChecked" },
|
||||
],
|
||||
[
|
||||
{ label: "Import…", shortcut: ["KeyControl", "KeyI"] },
|
||||
{ label: "Export…", shortcut: ["KeyControl", "KeyE"], action: async (): Promise<void> => editor.instance.export_document() },
|
||||
],
|
||||
[{ label: "Quit", shortcut: ["KeyControl", "KeyQ"] }],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Edit",
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "Undo", shortcut: ["KeyControl", "KeyZ"], action: async (): Promise<void> => editor.instance.undo() },
|
||||
{ label: "Redo", shortcut: ["KeyControl", "KeyShift", "KeyZ"], action: async (): Promise<void> => editor.instance.redo() },
|
||||
],
|
||||
[
|
||||
{ label: "Cut", shortcut: ["KeyControl", "KeyX"], action: async (): Promise<void> => editor.instance.cut() },
|
||||
{ label: "Copy", icon: "Copy", shortcut: ["KeyControl", "KeyC"], action: async (): Promise<void> => editor.instance.copy() },
|
||||
// TODO: Fix this
|
||||
// { label: "Paste", icon: "Paste", shortcut: ["KeyControl", "KeyV"], action: async (): Promise<void> => editor.instance.paste() },
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Layer",
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "Select All", shortcut: ["KeyControl", "KeyA"], action: async (): Promise<void> => editor.instance.select_all_layers() },
|
||||
{ label: "Deselect All", shortcut: ["KeyControl", "KeyAlt", "KeyA"], action: async (): Promise<void> => editor.instance.deselect_all_layers() },
|
||||
{
|
||||
label: "Order",
|
||||
action: (): void => undefined,
|
||||
children: [
|
||||
[
|
||||
{
|
||||
label: "Raise To Front",
|
||||
shortcut: ["KeyControl", "KeyShift", "KeyLeftBracket"],
|
||||
action: async (): Promise<void> => editor.instance.reorder_selected_layers(editor.instance.i32_max()),
|
||||
},
|
||||
{ label: "Raise", shortcut: ["KeyControl", "KeyRightBracket"], action: async (): Promise<void> => editor.instance.reorder_selected_layers(1) },
|
||||
{ label: "Lower", shortcut: ["KeyControl", "KeyLeftBracket"], action: async (): Promise<void> => editor.instance.reorder_selected_layers(-1) },
|
||||
{
|
||||
label: "Lower to Back",
|
||||
shortcut: ["KeyControl", "KeyShift", "KeyRightBracket"],
|
||||
action: async (): Promise<void> => editor.instance.reorder_selected_layers(editor.instance.i32_min()),
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Document",
|
||||
ref: undefined,
|
||||
children: [[{ label: "Menu entries coming soon" }]],
|
||||
},
|
||||
{
|
||||
label: "View",
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{
|
||||
label: "Show/Hide Node Graph (In Development)",
|
||||
action: async (): Promise<void> => editor.instance.toggle_node_graph_visibility(),
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Help",
|
||||
ref: undefined,
|
||||
children: [
|
||||
[
|
||||
{
|
||||
label: "About Graphite",
|
||||
action: async (): Promise<void> => editor.instance.request_about_graphite_dialog(),
|
||||
},
|
||||
],
|
||||
[
|
||||
{ label: "Report a Bug", action: (): unknown => window.open("https://github.com/GraphiteEditor/Graphite/issues/new", "_blank") },
|
||||
{ label: "Visit on GitHub", action: (): unknown => window.open("https://github.com/GraphiteEditor/Graphite", "_blank") },
|
||||
],
|
||||
[
|
||||
{
|
||||
label: "Debug: Set Log Level",
|
||||
action: (): void => undefined,
|
||||
children: [
|
||||
[
|
||||
{ label: "Log Level Info", action: async (): Promise<void> => editor.instance.log_level_info(), shortcut: ["Key1"] },
|
||||
{ label: "Log Level Debug", action: async (): Promise<void> => editor.instance.log_level_debug(), shortcut: ["Key2"] },
|
||||
{ label: "Log Level Trace", action: async (): Promise<void> => editor.instance.log_level_trace(), shortcut: ["Key3"] },
|
||||
],
|
||||
],
|
||||
},
|
||||
{ label: "Debug: Panic (DANGER)", action: async (): Promise<void> => editor.instance.intentional_panic() },
|
||||
],
|
||||
],
|
||||
},
|
||||
const LOCK_REQUIRING_SHORTCUTS = [
|
||||
["KeyControl", "KeyN"],
|
||||
["KeyControl", "KeyShift", "KeyT"],
|
||||
["KeyControl", "KeyW"],
|
||||
];
|
||||
}
|
||||
|
||||
type FrontendMenuColumn = {
|
||||
label: string;
|
||||
children: FrontendMenuEntry[][];
|
||||
};
|
||||
type FrontendMenuEntry = Omit<MenuEntry, "action" | "children"> & { shortcutRequiresLock: boolean | undefined; action: () => void; children: FrontendMenuEntry[][] | undefined };
|
||||
|
||||
export default defineComponent({
|
||||
inject: ["editor"],
|
||||
mounted() {
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
|
||||
const shortcutRequiresLock = (shortcut: string[]): boolean => LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => shortcut.every((shortcutKey, index) => shortcutKey === lockKeyCombo[index]));
|
||||
|
||||
const menuEntryToFrontendMenuEntry = (subLayout: MenuEntry[][]): FrontendMenuEntry[][] =>
|
||||
subLayout.map((group) =>
|
||||
group.map((entry) => ({
|
||||
...entry,
|
||||
children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined,
|
||||
action: (): void => this.editor.instance.update_layout(updateMenuBarLayout.layout_target, entry.action.widget_id, undefined),
|
||||
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut) : undefined,
|
||||
}))
|
||||
);
|
||||
|
||||
this.entries = updateMenuBarLayout.layout.map((column) => ({ ...column, children: menuEntryToFrontendMenuEntry(column.children) }));
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onClick(menuEntry: MenuListEntry, target: EventTarget | null) {
|
||||
// Focus the target so that keyboard inputs are sent to the dropdown
|
||||
|
@ -236,7 +127,7 @@ export default defineComponent({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
entries: makeEntries(this.editor),
|
||||
entries: [] as FrontendMenuColumn[],
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -74,9 +74,9 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
|
||||
// Don't redirect debugging tools
|
||||
if (key === "f12" || key === "f8") return false;
|
||||
if (e.ctrlKey && e.shiftKey && key === "c") return false;
|
||||
if (e.ctrlKey && e.shiftKey && key === "i") return false;
|
||||
if (e.ctrlKey && e.shiftKey && key === "j") return false;
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "c") return false;
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "i") return false;
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "j") return false;
|
||||
|
||||
// Don't redirect tab or enter if not in canvas (to allow navigating elements)
|
||||
if (!canvasFocused && !targetIsTextField(e.target) && ["tab", "enter", " ", "arrowdown", "arrowup", "arrowleft", "arrowright"].includes(key.toLowerCase())) return false;
|
||||
|
|
|
@ -370,7 +370,7 @@ export class TriggerVisitLink extends JsMessage {
|
|||
}
|
||||
|
||||
export interface WidgetLayout {
|
||||
layout: LayoutRow[];
|
||||
layout: LayoutGroup[];
|
||||
layout_target: unknown;
|
||||
}
|
||||
|
||||
|
@ -381,21 +381,20 @@ export function defaultWidgetLayout(): WidgetLayout {
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: Rename LayoutRow to something more generic
|
||||
export type LayoutRow = WidgetRow | WidgetColumn | WidgetSection;
|
||||
export type LayoutGroup = WidgetRow | WidgetColumn | WidgetSection;
|
||||
|
||||
export type WidgetColumn = { columnWidgets: Widget[] };
|
||||
export function isWidgetColumn(layoutColumn: LayoutRow): layoutColumn is WidgetColumn {
|
||||
export function isWidgetColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetColumn {
|
||||
return Boolean((layoutColumn as WidgetColumn).columnWidgets);
|
||||
}
|
||||
|
||||
export type WidgetRow = { rowWidgets: Widget[] };
|
||||
export function isWidgetRow(layoutRow: LayoutRow): layoutRow is WidgetRow {
|
||||
export function isWidgetRow(layoutRow: LayoutGroup): layoutRow is WidgetRow {
|
||||
return Boolean((layoutRow as WidgetRow).rowWidgets);
|
||||
}
|
||||
|
||||
export type WidgetSection = { name: string; layout: LayoutRow[] };
|
||||
export function isWidgetSection(layoutRow: LayoutRow): layoutRow is WidgetSection {
|
||||
export type WidgetSection = { name: string; layout: LayoutGroup[] };
|
||||
export function isWidgetSection(layoutRow: LayoutGroup): layoutRow is WidgetSection {
|
||||
return Boolean((layoutRow as WidgetSection).layout);
|
||||
}
|
||||
|
||||
|
@ -427,86 +426,72 @@ export class UpdateDialogDetails extends JsMessage implements WidgetLayout {
|
|||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateDocumentModeLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateToolOptionsLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateDocumentBarLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateToolShelfLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdatePropertyPanelOptionsLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdatePropertyPanelSectionsLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateLayerTreeOptionsLayout extends JsMessage implements WidgetLayout {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createWidgetLayout(value))
|
||||
layout!: LayoutRow[];
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
// Unpacking rust types to more usable type in the frontend
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createWidgetLayout(widgetLayout: any[]): LayoutRow[] {
|
||||
return widgetLayout.map((layoutType): LayoutRow => {
|
||||
function createWidgetLayout(widgetLayout: any[]): LayoutGroup[] {
|
||||
return widgetLayout.map((layoutType): LayoutGroup => {
|
||||
if (layoutType.Column) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const columnWidgets = layoutType.Column.columnWidgets.map((widgetHolder: any) => {
|
||||
const { widget_id } = widgetHolder;
|
||||
const kind = Object.keys(widgetHolder.widget)[0];
|
||||
const props = widgetHolder.widget[kind];
|
||||
|
||||
return { widget_id, kind, props };
|
||||
});
|
||||
|
||||
const columnWidgets = hoistWidgetHolders(layoutType.Column.columnWidgets);
|
||||
const result: WidgetColumn = { columnWidgets };
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layoutType.Row) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rowWidgets = layoutType.Row.rowWidgets.map((widgetHolder: any) => {
|
||||
const { widget_id } = widgetHolder;
|
||||
const kind = Object.keys(widgetHolder.widget)[0];
|
||||
const props = widgetHolder.widget[kind];
|
||||
|
||||
return { widget_id, kind, props };
|
||||
});
|
||||
|
||||
const rowWidgets = hoistWidgetHolders(layoutType.Row.rowWidgets);
|
||||
const result: WidgetRow = { rowWidgets };
|
||||
return result;
|
||||
}
|
||||
|
@ -522,6 +507,52 @@ function createWidgetLayout(widgetLayout: any[]): LayoutRow[] {
|
|||
throw new Error("Layout row type does not exist");
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function hoistWidgetHolders(widgetHolders: any[]): Widget[] {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return widgetHolders.map((widgetHolder: any) => {
|
||||
const { widget_id } = widgetHolder;
|
||||
const kind = Object.keys(widgetHolder.widget)[0];
|
||||
const props = widgetHolder.widget[kind];
|
||||
|
||||
return { widget_id, kind, props } as Widget;
|
||||
});
|
||||
}
|
||||
|
||||
export class UpdateMenuBarLayout extends JsMessage {
|
||||
layout_target!: unknown;
|
||||
|
||||
@Transform(({ value }) => createMenuLayout(value))
|
||||
layout!: MenuColumn[];
|
||||
}
|
||||
|
||||
export type MenuEntry = {
|
||||
shortcut: string[] | undefined;
|
||||
action: Widget;
|
||||
label: string;
|
||||
icon: string | undefined;
|
||||
children: undefined | MenuEntry[][];
|
||||
};
|
||||
|
||||
export type MenuColumn = {
|
||||
label: string;
|
||||
children: MenuEntry[][];
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createMenuLayout(menuLayout: any[]): MenuColumn[] {
|
||||
return menuLayout.map((column) => ({ ...column, children: createMenuLayoutRecursive(column.children) }));
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createMenuLayoutRecursive(subLayout: any[][]): MenuEntry[][] {
|
||||
return subLayout.map((groups) =>
|
||||
groups.map((entry) => ({
|
||||
...entry,
|
||||
action: hoistWidgetHolders([entry.action])[0],
|
||||
children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
export class TriggerTextCommit extends JsMessage {}
|
||||
|
||||
|
@ -579,5 +610,6 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateDocumentModeLayout,
|
||||
UpdateToolOptionsLayout,
|
||||
UpdateWorkingColors,
|
||||
UpdateMenuBarLayout,
|
||||
} as const;
|
||||
export type JsMessageType = keyof typeof messageMakers;
|
||||
|
|
|
@ -117,6 +117,9 @@ impl JsEditorHandle {
|
|||
|
||||
let message = MovementMessage::TranslateCanvas { delta: (0., 0.).into() };
|
||||
self.dispatch(message);
|
||||
|
||||
let message = MenuBarMessage::SendLayout;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Displays a dialog with an error message
|
||||
|
@ -125,11 +128,6 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Intentionally panic for debugging purposes
|
||||
pub fn intentional_panic(&self) {
|
||||
panic!();
|
||||
}
|
||||
|
||||
/// Answer whether or not the editor has crashed
|
||||
pub fn has_crashed(&self) -> bool {
|
||||
EDITOR_HAS_CRASHED.load(Ordering::SeqCst)
|
||||
|
@ -147,23 +145,6 @@ impl JsEditorHandle {
|
|||
GRAPHITE_DOCUMENT_VERSION.to_string()
|
||||
}
|
||||
|
||||
/// Get the constant `i32::MAX`
|
||||
#[wasm_bindgen]
|
||||
pub fn i32_max(&self) -> i32 {
|
||||
i32::MAX
|
||||
}
|
||||
|
||||
/// Get the constant `i32::MIN`
|
||||
#[wasm_bindgen]
|
||||
pub fn i32_min(&self) -> i32 {
|
||||
i32::MIN
|
||||
}
|
||||
|
||||
/// Request that the Node Graph panel be shown or hidden by toggling the visibility state
|
||||
pub fn toggle_node_graph_visibility(&self) {
|
||||
self.dispatch(WorkspaceMessage::NodeGraphToggleVisibility);
|
||||
}
|
||||
|
||||
/// Update layout of a given UI
|
||||
pub fn update_layout(&self, layout_target: JsValue, widget_id: u64, value: JsValue) -> Result<(), JsValue> {
|
||||
match (from_value(layout_target), from_value(value)) {
|
||||
|
@ -181,16 +162,6 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn request_new_document_dialog(&self) {
|
||||
let message = DialogMessage::RequestNewDocumentDialog;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn open_document(&self) {
|
||||
let message = PortfolioMessage::OpenDocument;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn open_document_file(&self, document_name: String, document_serialized_content: String) {
|
||||
let message = PortfolioMessage::OpenDocumentFile {
|
||||
document_name,
|
||||
|
@ -209,49 +180,16 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn save_document(&self) {
|
||||
let message = DocumentMessage::SaveDocument;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn trigger_auto_save(&self, document_id: u64) {
|
||||
let message = PortfolioMessage::AutoSaveDocument { document_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn close_document(&self, document_id: u64) {
|
||||
let message = ToolMessage::AbortCurrentTool;
|
||||
self.dispatch(message);
|
||||
|
||||
let message = PortfolioMessage::CloseDocument { document_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn close_all_documents(&self) {
|
||||
let message = PortfolioMessage::CloseAllDocuments;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn close_active_document_with_confirmation(&self) {
|
||||
let message = PortfolioMessage::CloseActiveDocumentWithConfirmation;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn close_document_with_confirmation(&self, document_id: u64) {
|
||||
let message = PortfolioMessage::CloseDocumentWithConfirmation { document_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn close_all_documents_with_confirmation(&self) {
|
||||
let message = DialogMessage::CloseAllDocumentsWithConfirmation;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn request_about_graphite_dialog(&self) {
|
||||
let message = DialogMessage::RequestAboutGraphiteDialog;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn request_about_graphite_dialog_with_localized_commit_date(&self, localized_commit_date: String) {
|
||||
let message = DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date };
|
||||
self.dispatch(message);
|
||||
|
@ -262,21 +200,6 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn log_level_info(&self) {
|
||||
let message = GlobalMessage::LogInfo;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn log_level_debug(&self) {
|
||||
let message = GlobalMessage::LogDebug;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
pub fn log_level_trace(&self) {
|
||||
let message = GlobalMessage::LogTrace;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Send new bounds when document panel viewports get resized or moved within the editor
|
||||
/// [left, top, right, bottom]...
|
||||
pub fn bounds_of_viewports(&self, bounds_of_viewports: &[f64]) {
|
||||
|
@ -426,30 +349,6 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Undo history one step
|
||||
pub fn undo(&self) {
|
||||
let message = DocumentMessage::Undo;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Redo history one step
|
||||
pub fn redo(&self) {
|
||||
let message = DocumentMessage::Redo;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Cut selected layers
|
||||
pub fn cut(&self) {
|
||||
let message = PortfolioMessage::Cut { clipboard: Clipboard::Device };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Copy selected layers
|
||||
pub fn copy(&self) {
|
||||
let message = PortfolioMessage::Copy { clipboard: Clipboard::Device };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Paste layers from a serialized json representation
|
||||
pub fn paste_serialized_data(&self, data: String) {
|
||||
let message = PortfolioMessage::PasteSerializedData { data };
|
||||
|
@ -462,24 +361,12 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Select all layers
|
||||
pub fn select_all_layers(&self) {
|
||||
let message = DocumentMessage::SelectAllLayers;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Deselect all layers
|
||||
pub fn deselect_all_layers(&self) {
|
||||
let message = DocumentMessage::DeselectAllLayers;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Reorder selected layer
|
||||
pub fn reorder_selected_layers(&self, relative_index_offset: isize) {
|
||||
let message = DocumentMessage::ReorderSelectedLayers { relative_index_offset };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Move a layer to be next to the specified neighbor
|
||||
pub fn move_layer_in_tree(&self, folder_path: Vec<LayerId>, insert_index: isize) {
|
||||
let message = DocumentMessage::MoveSelectedLayersTo {
|
||||
|
@ -496,12 +383,6 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Export the document
|
||||
pub fn export_document(&self) {
|
||||
let message = DialogMessage::RequestExportDialog;
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Translates document (in viewport coords)
|
||||
pub fn translate_canvas(&self, delta_x: f64, delta_y: f64) {
|
||||
let message = MovementMessage::TranslateCanvas { delta: (delta_x, delta_y).into() };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue