Switch to the widget builder pattern on all remaining layouts (#1346)

* Prefer widget builder pattern

* Nits

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-08-01 07:21:42 +01:00 committed by GitHub
parent 40f9a7d051
commit de27f2c006
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 714 additions and 1365 deletions

8
Cargo.lock generated
View File

@ -4791,8 +4791,8 @@ dependencies = [
[[package]]
name = "specta"
version = "1.0.4"
source = "git+https://github.com/0HyperCube/specta.git?rev=d28cd72ea4ea2f6eaa85e0622d3a98340dbb43d3#d28cd72ea4ea2f6eaa85e0622d3a98340dbb43d3"
version = "1.0.5"
source = "git+https://github.com/0HyperCube/specta.git?rev=c47a22b4c0863d27bc47529f300de3969480c66d#c47a22b4c0863d27bc47529f300de3969480c66d"
dependencies = [
"document-features",
"glam",
@ -4807,8 +4807,8 @@ dependencies = [
[[package]]
name = "specta-macros"
version = "1.0.4"
source = "git+https://github.com/0HyperCube/specta.git?rev=d28cd72ea4ea2f6eaa85e0622d3a98340dbb43d3#d28cd72ea4ea2f6eaa85e0622d3a98340dbb43d3"
version = "1.0.5"
source = "git+https://github.com/0HyperCube/specta.git?rev=c47a22b4c0863d27bc47529f300de3969480c66d#c47a22b4c0863d27bc47529f300de3969480c66d"
dependencies = [
"Inflector",
"itertools",

View File

@ -28,7 +28,7 @@ resolver = "2"
exclude = ["node-graph/gpu-compiler"]
[workspace.dependencies]
specta = { git = "https://github.com/0HyperCube/specta.git", rev = "d28cd72ea4ea2f6eaa85e0622d3a98340dbb43d3", features = [
specta = { git = "https://github.com/0HyperCube/specta.git", rev = "c47a22b4c0863d27bc47529f300de3969480c66d", features = [
"glam",
] }
xxhash-rust = { version = "0.8", features = ["xxh3"] }

View File

@ -1,9 +1,6 @@
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, DropdownEntryData, DropdownInput, NumberInput, RadioEntryData, RadioInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use document_legacy::LayerId;
@ -47,46 +44,22 @@ impl MessageHandler<ExportDialogMessage, ()> for ExportDialogMessageHandler {
impl PropertyHolder for ExportDialogMessageHandler {
fn properties(&self) -> Layout {
let file_name = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "File Name".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
value: self.file_name.clone(),
on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogMessage::FileName(text_input.value.clone()).into()),
..Default::default()
})),
TextLabel::new("File Name").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
TextInput::new(&self.file_name)
.on_update(|text_input: &TextInput| ExportDialogMessage::FileName(text_input.value.clone()).into())
.widget_holder(),
];
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
.into_iter()
.map(|(val, name)| RadioEntryData {
label: name.into(),
on_update: WidgetCallback::new(move |_| ExportDialogMessage::FileType(val).into()),
..RadioEntryData::default()
})
.map(|(val, name)| RadioEntryData::new(name).on_update(move |_| ExportDialogMessage::FileType(val).into()))
.collect();
let export_type = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "File Type".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::RadioInput(RadioInput {
selected_index: self.file_type as u32,
entries,
..Default::default()
})),
TextLabel::new("File Type").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
RadioInput::new(entries).selected_index(self.file_type as u32).widget_holder(),
];
let artboards = self.artboards.iter().map(|(&val, name)| (ExportBounds::Artboard(val), name.to_string(), false));
@ -98,98 +71,52 @@ impl PropertyHolder for ExportDialogMessageHandler {
let index = export_area_options.iter().position(|(val, _, _)| val == &self.bounds).unwrap();
let entries = vec![export_area_options
.into_iter()
.map(|(val, name, disabled)| DropdownEntryData {
label: name,
on_update: WidgetCallback::new(move |_| ExportDialogMessage::ExportBounds(val).into()),
disabled,
..Default::default()
})
.map(|(val, name, disabled)| DropdownEntryData::new(name).on_update(move |_| ExportDialogMessage::ExportBounds(val).into()).disabled(disabled))
.collect()];
let export_area = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Bounds".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
selected_index: Some(index as u32),
entries,
..Default::default()
})),
TextLabel::new("Bounds").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
];
let transparent_background = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Transparency".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: self.transparent_background,
disabled: self.file_type == FileType::Jpg,
on_update: WidgetCallback::new(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into()),
..Default::default()
})),
TextLabel::new("Transparency").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
CheckboxInput::new(self.transparent_background)
.disabled(self.file_type == FileType::Jpg)
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
.widget_holder(),
];
let resolution = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Scale Factor".into(),
table_align: true,
..TextLabel::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some(self.scale_factor),
label: "".into(),
unit: " ".into(),
min: Some(0.),
disabled: self.file_type == FileType::Svg,
on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
TextLabel::new("Scale Factor").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
NumberInput::new(Some(self.scale_factor))
.unit(" ")
.min(0.)
.disabled(self.file_type == FileType::Svg)
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
.widget_holder(),
];
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Export".to_string(),
min_width: 96,
emphasized: true,
on_update: WidgetCallback::new(|_| {
TextButton::new("Export")
.min_width(96)
.emphasized(true)
.on_update(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![ExportDialogMessage::Submit.into()],
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
})
.widget_holder(),
TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder(),
];
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Export".to_string(),
bold: true,
..Default::default()
}))],
widgets: vec![TextLabel::new("Export").bold(true).widget_holder()],
},
LayoutGroup::Row { widgets: file_name },
LayoutGroup::Row { widgets: export_type },

View File

@ -1,8 +1,5 @@
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, NumberInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use graphene_core::uuid::generate_uuid;
@ -54,103 +51,60 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
impl PropertyHolder for NewDocumentDialogMessageHandler {
fn properties(&self) -> Layout {
let title = vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "New document".into(),
bold: true,
..Default::default()
}))];
let title = vec![TextLabel::new("New document").bold(true).widget_holder()];
let name = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Name".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
value: self.name.clone(),
on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into()),
..Default::default()
})),
TextLabel::new("Name").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
TextInput::new(&self.name)
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into())
.widget_holder(),
];
let infinite = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Infinite Canvas".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: self.infinite,
on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into()),
..Default::default()
})),
TextLabel::new("Infinite Canvas").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
CheckboxInput::new(self.infinite)
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
.widget_holder(),
];
let scale = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Dimensions".into(),
table_align: true,
..TextLabel::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
label: "W".into(),
unit: " px".into(),
value: Some(self.dimensions.x as f64),
min: Some(0.),
is_integer: true,
disabled: self.infinite,
min_width: 100,
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
label: "H".into(),
unit: " px".into(),
value: Some(self.dimensions.y as f64),
min: Some(0.),
is_integer: true,
disabled: self.infinite,
min_width: 100,
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
TextLabel::new("Dimensions").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
NumberInput::new(Some(self.dimensions.x as f64))
.label("W")
.unit(" px")
.min(0.)
.is_integer(true)
.disabled(self.infinite)
.min_width(100)
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into())
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(Some(self.dimensions.y as f64))
.label("H")
.unit(" px")
.min(0.)
.is_integer(true)
.disabled(self.infinite)
.min_width(100)
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into())
.widget_holder(),
];
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {
label: "OK".to_string(),
min_width: 96,
emphasized: true,
on_update: WidgetCallback::new(|_| {
TextButton::new("OK")
.min_width(96)
.emphasized(true)
.on_update(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![NewDocumentDialogMessage::Submit.into()],
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
})
.widget_holder(),
TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder(),
];
Layout::WidgetLayout(WidgetLayout::new(vec![

View File

@ -1,8 +1,5 @@
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, NumberInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
/// A dialog to allow users to customize Graphite editor options
@ -31,107 +28,62 @@ impl PreferencesDialogMessageHandler {
fn properties(&self, preferences: &PreferencesMessageHandler) -> Layout {
let zoom_with_scroll = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Input".into(),
min_width: 60,
italic: true,
..Default::default()
})),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Zoom with Scroll".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: preferences.zoom_with_scroll,
tooltip: "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)".into(),
on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| {
TextLabel::new("Input").min_width(60).italic(true).widget_holder(),
TextLabel::new("Zoom with Scroll").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
CheckboxInput::new(preferences.zoom_with_scroll)
.tooltip("Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)")
.on_update(|checkbox_input: &CheckboxInput| {
PreferencesMessage::ModifyLayout {
zoom_with_scroll: checkbox_input.checked,
}
.into()
}),
..Default::default()
})),
})
.widget_holder(),
];
let imaginate_server_hostname = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Imaginate".into(),
min_width: 60,
italic: true,
..Default::default()
})),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Server Hostname".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
value: preferences.imaginate_server_hostname.clone(),
min_width: 200,
on_update: WidgetCallback::new(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into()),
..Default::default()
})),
TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(),
TextLabel::new("Server Hostname").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
TextInput::new(&preferences.imaginate_server_hostname)
.min_width(200)
.on_update(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into())
.widget_holder(),
];
let imaginate_refresh_frequency = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { min_width: 60, ..Default::default() })),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Refresh Frequency".into(),
table_align: true,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " seconds".into(),
value: Some(preferences.imaginate_refresh_frequency),
min: Some(0.),
min_width: 200,
on_update: WidgetCallback::new(|number_input: &NumberInput| PreferencesMessage::ImaginateRefreshFrequency { seconds: number_input.value.unwrap() }.into()),
..Default::default()
})),
TextLabel::new("").min_width(60).widget_holder(),
TextLabel::new("Refresh Frequency").table_align(true).widget_holder(),
WidgetHolder::unrelated_separator(),
NumberInput::new(Some(preferences.imaginate_refresh_frequency))
.unit(" seconds")
.min(0.)
.min_width(200)
.on_update(|number_input: &NumberInput| PreferencesMessage::ImaginateRefreshFrequency { seconds: number_input.value.unwrap() }.into())
.widget_holder(),
];
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Ok".to_string(),
min_width: 96,
emphasized: true,
on_update: WidgetCallback::new(|_| {
TextButton::new("Ok")
.min_width(96)
.emphasized(true)
.on_update(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![PreferencesDialogMessage::Confirm.into()],
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Reset to Defaults".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| PreferencesMessage::ResetToDefaults.into()),
..Default::default()
})),
})
.widget_holder(),
TextButton::new("Reset to Defaults")
.min_width(96)
.on_update(|_| PreferencesMessage::ResetToDefaults.into())
.widget_holder(),
];
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Editor Preferences".to_string(),
bold: true,
..Default::default()
}))],
widgets: vec![TextLabel::new("Editor Preferences").bold(true).widget_holder()],
},
LayoutGroup::Row { widgets: zoom_with_scroll },
LayoutGroup::Row { widgets: imaginate_server_hostname },

View File

@ -1,7 +1,5 @@
use crate::application::{commit_info_localized, release_series};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar.
@ -19,34 +17,17 @@ impl PropertyHolder for AboutGraphiteDialog {
];
let link_widgets = links
.into_iter()
.map(|(label, url)| {
WidgetHolder::new(Widget::TextButton(TextButton {
label: label.to_string(),
on_update: WidgetCallback::new(|_| FrontendMessage::TriggerVisitLink { url: url.to_string() }.into()),
..Default::default()
}))
})
.map(|(label, url)| TextButton::new(label).on_update(|_| FrontendMessage::TriggerVisitLink { url: url.to_string() }.into()).widget_holder())
.collect();
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Graphite".to_string(),
bold: true,
..Default::default()
}))],
widgets: vec![TextLabel::new("Graphite".to_string()).bold(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: release_series(),
..Default::default()
}))],
widgets: vec![TextLabel::new(release_series()).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: commit_info_localized(self.localized_commit_date.as_str()),
multiline: true,
..Default::default()
}))],
widgets: vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_holder()],
},
LayoutGroup::Row { widgets: link_widgets },
]))

View File

@ -1,6 +1,4 @@
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
@ -8,42 +6,25 @@ pub struct CloseAllDocumentsDialog;
impl PropertyHolder for CloseAllDocumentsDialog {
fn properties(&self) -> Layout {
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Discard All".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![PortfolioMessage::CloseAllDocuments.into()],
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
];
let discard = TextButton::new("Discard All")
.min_width(96)
.on_update(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![PortfolioMessage::CloseAllDocuments.into()],
}
.into()
})
.widget_holder();
let cancel = TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Close all documents?".to_string(),
bold: true,
..Default::default()
}))],
widgets: vec![TextLabel::new("Close all documents?").multiline(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Unsaved work will be lost!".to_string(),
multiline: true,
..Default::default()
}))],
widgets: vec![TextLabel::new("Unsaved work will be lost!").multiline(true).widget_holder()],
},
LayoutGroup::Row { widgets: button_widgets },
LayoutGroup::Row { widgets: vec![discard, cancel] },
]))
}
}

View File

@ -1,7 +1,5 @@
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
/// A dialog for confirming the closing a document with unsaved changes.
@ -15,51 +13,34 @@ impl PropertyHolder for CloseDocumentDialog {
let document_id = self.document_id;
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Save".to_string(),
min_width: 96,
emphasized: true,
on_update: WidgetCallback::new(|_| {
TextButton::new("Save")
.min_width(96)
.emphasized(true)
.on_update(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![DocumentMessage::SaveDocument.into()],
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Discard".to_string(),
min_width: 96,
on_update: WidgetCallback::new(move |_| {
})
.widget_holder(),
TextButton::new("Discard")
.min_width(96)
.on_update(move |_| {
DialogMessage::CloseDialogAndThen {
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
})
.widget_holder(),
TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder(),
];
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()
}))],
widgets: vec![TextLabel::new("Save changes before closing?").bold(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: self.document_name.clone(),
multiline: true,
..Default::default()
}))],
widgets: vec![TextLabel::new(&self.document_name).multiline(true).widget_holder()],
},
LayoutGroup::Row { widgets: button_widgets },
]))

View File

@ -1,6 +1,4 @@
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use std::fmt::Write;
@ -13,41 +11,33 @@ pub struct ComingSoonDialog {
impl PropertyHolder for ComingSoonDialog {
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(),
emphasized: true,
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
}))];
let mut buttons = vec![TextButton::new("OK")
.emphasized(true)
.min_width(96)
.on_update(|_| FrontendMessage::DisplayDialogDismiss.into())
.widget_holder()];
if let Some(issue) = self.issue {
let _ = write!(details, "— but you can help add it!\nSee issue #{issue} on GitHub.");
buttons.push(WidgetHolder::new(Widget::TextButton(TextButton {
label: format!("Issue #{issue}"),
min_width: 96,
on_update: WidgetCallback::new(move |_| {
FrontendMessage::TriggerVisitLink {
url: format!("https://github.com/GraphiteEditor/Graphite/issues/{issue}"),
}
.into()
}),
..Default::default()
})));
buttons.push(
TextButton::new(format!("Issue #{issue}"))
.min_width(96)
.on_update(move |_| {
FrontendMessage::TriggerVisitLink {
url: format!("https://github.com/GraphiteEditor/Graphite/issues/{issue}"),
}
.into()
})
.widget_holder(),
);
}
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Coming soon".to_string(),
bold: true,
..Default::default()
}))],
widgets: vec![TextLabel::new("Coming soon").bold(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: details,
multiline: true,
..Default::default()
}))],
widgets: vec![TextLabel::new(details).multiline(true).widget_holder()],
},
LayoutGroup::Row { widgets: buttons },
]))

View File

@ -1,6 +1,4 @@
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
/// A dialog to notify users of a non-fatal error.
@ -13,27 +11,17 @@ impl PropertyHolder for ErrorDialog {
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()
}))],
widgets: vec![TextLabel::new(&self.title).bold(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: self.description.clone(),
multiline: true,
..Default::default()
}))],
widgets: vec![TextLabel::new(&self.description).multiline(true).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextButton(TextButton {
label: "OK".to_string(),
emphasized: true,
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
}))],
widgets: vec![TextButton::new("OK")
.emphasized(true)
.min_width(96)
.on_update(|_| FrontendMessage::DisplayDialogDismiss.into())
.widget_holder()],
},
]))
}

View File

@ -452,6 +452,7 @@ pub struct WidgetHolder {
}
impl WidgetHolder {
#[deprecated(since = "0.0.0", note = "Please use the builder pattern, e.g. TextLabel::new(\"hello\").widget_holder()")]
pub fn new(widget: Widget) -> Self {
Self { widget_id: generate_uuid(), widget }
}

View File

@ -4,16 +4,27 @@ use serde::{Deserialize, Serialize};
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize, specta::Type)]
#[repr(u8)]
pub enum LayoutTarget {
/// Contains the contents of the dialog, including the title and action buttons. Must be shown with the `FrontendMessage::DisplayDialog` message.
DialogDetails,
/// Contains the widgets located directly above the canvas to the right, for example the zoom in and out buttons.
DocumentBar,
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
DocumentMode,
/// Options for opacity seen at the top of the Layers panel.
LayerTreeOptions,
/// The dropdown menu at the very top of the application: File, Edit, etc.
MenuBar,
/// Bar at the top of the node graph containing the location and the 'preview' and 'hide' buttons.
NodeGraphBar,
/// The bar at the top of the Properties panel containing the layer name and icon.
PropertiesOptions,
/// The body of the Properties panel containing many collapsable sections.
PropertiesSections,
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
ToolOptions,
/// The vertical buttons for all of the tools on the left of the canvas.
ToolShelf,
/// The color swatch for the working colors and a flip and reset button found at the bottom of the tool shelf.
WorkingColors,
// KEEP THIS ENUM LAST

View File

@ -1,7 +1,6 @@
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::WidgetHolder;
use crate::messages::layout::utility_types::layout_widget::{Widget, WidgetCallback};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
@ -51,9 +50,7 @@ impl MenuBarEntry {
}
pub fn create_action(callback: impl Fn(&()) -> Message + 'static + Send + Sync) -> WidgetHolder {
WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput {
on_update: WidgetCallback::new(callback),
}))
InvisibleStandinInput::new().on_update(callback).widget_holder()
}
pub fn no_action() -> WidgetHolder {

View File

@ -5,19 +5,12 @@ use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX,
use crate::messages::frontend::utility_types::ExportBounds;
use crate::messages::frontend::utility_types::FileType;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{CheckboxInput, TextLabel};
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
use crate::messages::layout::utility_types::widgets::input_widgets::{
DropdownEntryData, DropdownInput, NumberInput, NumberInputIncrementBehavior, NumberInputMode, OptionalInput, RadioEntryData, RadioInput,
};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerMetadata, LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::misc::DocumentMode;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, DocumentSave, FlipAxis};
use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
@ -1589,11 +1582,9 @@ impl DocumentMessageHandler {
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
let snapping_state = self.snapping_state.clone();
let mut widgets = vec![
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
checked: snapping_state.snapping_enabled,
icon: "Snapping".into(),
tooltip: "Snapping".into(),
on_update: WidgetCallback::new(move |optional_input: &OptionalInput| {
OptionalInput::new(snapping_state.snapping_enabled, "Snapping")
.tooltip("Snapping")
.on_update(move |optional_input: &OptionalInput| {
let snapping_enabled = optional_input.checked;
DocumentMessage::SetSnapping {
snapping_enabled: Some(snapping_enabled),
@ -1601,261 +1592,159 @@ impl DocumentMessageHandler {
node_snapping: Some(snapping_state.node_snapping),
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Snapping".into(),
text: "Select the vectors to snap to.".into(), // TODO: check whether this is an apt description
options_widget: vec![
})
.widget_holder(),
PopoverButton::new("Snapping", "Snap customization settings")
.options_widget(vec![
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
tooltip: SnappingOptions::BoundingBoxes.to_string(),
checked: snapping_state.bounding_box_snapping,
on_update: WidgetCallback::new(move |input: &CheckboxInput| {
CheckboxInput::new(snapping_state.bounding_box_snapping)
.tooltip(SnappingOptions::BoundingBoxes.to_string())
.on_update(move |input: &CheckboxInput| {
DocumentMessage::SetSnapping {
snapping_enabled: None,
bounding_box_snapping: Some(input.checked),
node_snapping: None,
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Unrelated,
})),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: SnappingOptions::BoundingBoxes.to_string(),
table_align: false,
min_width: 60,
..Default::default()
})),
// adds appropriate space between row elements
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Vertical,
separator_type: SeparatorType::Related,
})),
})
.widget_holder(),
WidgetHolder::unrelated_separator(),
TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).table_align(false).min_width(60).widget_holder(),
WidgetHolder::related_separator(),
],
},
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: self.snapping_state.node_snapping,
tooltip: SnappingOptions::Points.to_string(),
on_update: WidgetCallback::new(|input: &CheckboxInput| {
CheckboxInput::new(self.snapping_state.node_snapping)
.tooltip(SnappingOptions::Points.to_string())
.on_update(|input: &CheckboxInput| {
DocumentMessage::SetSnapping {
snapping_enabled: None,
bounding_box_snapping: None,
node_snapping: Some(input.checked),
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Unrelated,
})),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: SnappingOptions::Points.to_string(),
table_align: false,
min_width: 60,
..Default::default()
})),
})
.widget_holder(),
WidgetHolder::unrelated_separator(),
TextLabel::new(SnappingOptions::Points.to_string()).table_align(false).min_width(60).widget_holder(),
],
},
],
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
checked: true,
icon: "Grid".into(),
tooltip: "Grid".into(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()),
..Default::default()
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Grid".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
checked: self.overlays_visible,
icon: "Overlays".into(),
tooltip: "Overlays".into(),
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()),
..Default::default()
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Overlays".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::RadioInput(RadioInput {
selected_index: match self.view_mode {
ViewMode::Normal => 0,
_ => 1,
},
entries: vec![
RadioEntryData {
value: "normal".into(),
icon: "ViewModeNormal".into(),
tooltip: "View Mode: Normal".into(),
on_update: WidgetCallback::new(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Normal }.into()),
..RadioEntryData::default()
},
RadioEntryData {
value: "outline".into(),
icon: "ViewModeOutline".into(),
tooltip: "View Mode: Outline".into(),
on_update: WidgetCallback::new(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Outline }.into()),
..RadioEntryData::default()
},
RadioEntryData {
value: "pixels".into(),
icon: "ViewModePixels".into(),
tooltip: "View Mode: Pixels".into(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(320) }.into()),
..RadioEntryData::default()
},
],
..Default::default()
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "View Mode".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Section,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::IconButton(IconButton {
size: 24,
icon: "ZoomIn".into(),
tooltip: "Zoom In".into(),
tooltip_shortcut: action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom),
on_update: WidgetCallback::new(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
size: 24,
icon: "ZoomOut".into(),
tooltip: "Zoom Out".into(),
tooltip_shortcut: action_keys!(NavigationMessageDiscriminant::DecreaseCanvasZoom),
on_update: WidgetCallback::new(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
size: 24,
icon: "ZoomReset".into(),
tooltip: "Zoom to 100%".into(),
tooltip_shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
on_update: WidgetCallback::new(|_| NavigationMessage::SetCanvasZoom { zoom_factor: 1. }.into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: "%".into(),
value: Some(self.navigation_handler.snapped_scale() * 100.),
min: Some(0.000001),
max: Some(1000000.),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
])
.widget_holder(),
WidgetHolder::unrelated_separator(),
OptionalInput::new(true, "Grid")
.tooltip("Grid")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into())
.widget_holder(),
PopoverButton::new("Grid", "Coming soon").widget_holder(),
WidgetHolder::unrelated_separator(),
OptionalInput::new(self.overlays_visible, "Overlays")
.tooltip("Overlays")
.on_update(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
.widget_holder(),
PopoverButton::new("Overlays", "Coming soon").widget_holder(),
WidgetHolder::unrelated_separator(),
RadioInput::new(vec![
RadioEntryData::default()
.value("normal")
.icon("ViewModeNormal")
.tooltip("View Mode: Normal")
.on_update(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Normal }.into()),
RadioEntryData::default()
.value("outline")
.icon("ViewModeOutline")
.tooltip("View Mode: Outline")
.on_update(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Outline }.into()),
RadioEntryData::default()
.value("pixels")
.icon("ViewModePixels")
.tooltip("View Mode: Pixels")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(320) }.into()),
])
.selected_index(match self.view_mode {
ViewMode::Normal => 0,
_ => 1,
})
.widget_holder(),
PopoverButton::new("View Mode", "Coming soon").widget_holder(),
WidgetHolder::section_separator(),
IconButton::new("ZoomIn", 24)
.tooltip("Zoom In")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom))
.on_update(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into())
.widget_holder(),
IconButton::new("ZoomOut", 24)
.tooltip("Zoom Out")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::DecreaseCanvasZoom))
.on_update(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into())
.widget_holder(),
IconButton::new("ZoomReset", 24)
.tooltip("Zoom to 100%")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent))
.on_update(|_| NavigationMessage::SetCanvasZoom { zoom_factor: 1. }.into())
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(Some(self.navigation_handler.snapped_scale() * 100.))
.unit("%")
.min(0.000001)
.max(1000000.)
.mode_increment()
.on_update(|number_input: &NumberInput| {
NavigationMessage::SetCanvasZoom {
zoom_factor: number_input.value.unwrap() / 100.,
}
.into()
}),
increment_behavior: NumberInputIncrementBehavior::Callback,
increment_callback_decrease: WidgetCallback::new(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()),
increment_callback_increase: WidgetCallback::new(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()),
..NumberInput::default()
})),
})
.increment_behavior(NumberInputIncrementBehavior::Callback)
.increment_callback_decrease(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into())
.increment_callback_increase(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into())
.widget_holder(),
];
let rotation_value = self.navigation_handler.snapped_angle() / (std::f64::consts::PI / 180.);
if rotation_value.abs() > 0.00001 {
widgets.extend([
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: "°".into(),
value: Some(rotation_value),
step: 15.,
on_update: WidgetCallback::new(|number_input: &NumberInput| {
WidgetHolder::related_separator(),
NumberInput::new(Some(rotation_value))
.unit("°")
.step(15.)
.on_update(|number_input: &NumberInput| {
NavigationMessage::SetCanvasRotation {
angle_radians: number_input.value.unwrap() * (std::f64::consts::PI / 180.),
}
.into()
}),
..NumberInput::default()
})),
})
.widget_holder(),
]);
}
widgets.extend([
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Canvas Navigation".into(),
text: "Interactive options in this popover menu are coming soon.\nZoom with Shift + MMB Drag or Ctrl + Scroll Wheel Roll.\nRotate with Ctrl + MMB Drag.".into(),
..Default::default()
})),
WidgetHolder::related_separator(),
PopoverButton::new(
"Canvas Navigation",
"Interactive options in this popover menu are coming soon.\nZoom with Shift + MMB Drag or Ctrl + Scroll Wheel Roll.\nRotate with Ctrl + MMB Drag.",
)
.widget_holder(),
]);
let document_bar_layout = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
entries: vec![vec![
DropdownEntryData {
label: DocumentMode::DesignMode.to_string(),
icon: DocumentMode::DesignMode.icon_name(),
..DropdownEntryData::default()
},
DropdownEntryData {
label: DocumentMode::SelectMode.to_string(),
icon: DocumentMode::SelectMode.icon_name(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()),
..DropdownEntryData::default()
},
DropdownEntryData {
label: DocumentMode::GuideMode.to_string(),
icon: DocumentMode::GuideMode.icon_name(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(331) }.into()),
..DropdownEntryData::default()
},
]],
selected_index: Some(self.document_mode as u32),
draw_icon: true,
interactive: false, // TODO: set to true when dialogs are not spawned
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Section,
direction: SeparatorDirection::Horizontal,
})),
DropdownInput::new(
vec![vec![
DropdownEntryData::new(DocumentMode::DesignMode.to_string()).icon(DocumentMode::DesignMode.icon_name()),
DropdownEntryData::new(DocumentMode::SelectMode.to_string())
.icon(DocumentMode::SelectMode.icon_name())
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()),
DropdownEntryData::new(DocumentMode::GuideMode.to_string())
.icon(DocumentMode::GuideMode.icon_name())
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(331) }.into()),
]])
.selected_index( Some(self.document_mode as u32))
.draw_icon( true)
.interactive( false) // TODO: set to true when dialogs are not spawned
.widget_holder(),
WidgetHolder::section_separator(),
],
}]);
@ -1914,11 +1803,10 @@ impl DocumentMessageHandler {
.map(|modes| {
modes
.iter()
.map(|mode| DropdownEntryData {
label: mode.to_string(),
value: mode.to_string(),
on_update: WidgetCallback::new(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into()),
..Default::default()
.map(|mode| {
DropdownEntryData::new(mode.to_string())
.value(mode.to_string())
.on_update(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into())
})
.collect()
})
@ -1926,57 +1814,41 @@ impl DocumentMessageHandler {
let layer_tree_options = WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
entries: blend_mode_menu_entries,
selected_index: blend_mode.map(|blend_mode| blend_mode as u32),
disabled: blend_mode.is_none() && !blend_mode_is_mixed,
draw_icon: false,
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
label: "Opacity".into(),
unit: "%".into(),
display_decimal_places: 2,
disabled: opacity.is_none() && !opacity_is_mixed,
value: opacity.map(|opacity| opacity * 100.),
min: Some(0.),
max: Some(100.),
range_min: Some(0.),
range_max: Some(100.),
mode: NumberInputMode::Range,
on_update: WidgetCallback::new(|number_input: &NumberInput| {
DropdownInput::new(blend_mode_menu_entries)
.selected_index(blend_mode.map(|blend_mode| blend_mode as u32))
.disabled(blend_mode.is_none() && !blend_mode_is_mixed)
.draw_icon(false)
.widget_holder(),
WidgetHolder::related_separator(),
NumberInput::new(opacity.map(|opacity| opacity * 100.))
.label("Opacity")
.unit("%")
.display_decimal_places(2)
.disabled(opacity.is_none() && !opacity_is_mixed)
.min(0.)
.max(100.)
.range_min(Some(0.))
.range_max(Some(100.))
.mode(NumberInputMode::Range)
.on_update(|number_input: &NumberInput| {
if let Some(value) = number_input.value {
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
} else {
Message::NoOp
}
}),
..NumberInput::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Section,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "Folder".into(),
tooltip: "New Folder".into(),
tooltip_shortcut: action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()),
..Default::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "Trash".into(),
tooltip: "Delete Selected".into(),
tooltip_shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()),
..Default::default()
})),
})
.widget_holder(),
WidgetHolder::section_separator(),
IconButton::new("Folder", 24)
.tooltip("New Folder")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.on_update(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into())
.widget_holder(),
IconButton::new("Trash", 24)
.tooltip("Delete Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
.widget_holder(),
],
}]);

View File

@ -245,12 +245,10 @@ impl NodeGraphMessageHandler {
// Don't show stop previewing button on the original output node
if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) {
let output_button = WidgetHolder::new(Widget::TextButton(TextButton {
label: if is_output { "End Preview" } else { "Preview" }.to_string(),
tooltip: if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (Shortcut: Alt-click node)",
on_update: WidgetCallback::new(move |_| NodeGraphMessage::TogglePreview { node_id }.into()),
..Default::default()
}));
let output_button = TextButton::new(if is_output { "End Preview" } else { "Preview" })
.tooltip(if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (Shortcut: Alt-click node)")
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
.widget_holder();
widgets.push(output_button);
}
}

View File

@ -1,5 +1,6 @@
use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, IconButton, RadioEntryData, RadioInput, TextLabel, WidgetHolder};
use crate::messages::prelude::Message;
use graphene_core::Color;
@ -12,6 +13,7 @@ pub enum ToolColorType {
Custom,
}
/// Color selector widgets seen in [`LayoutTarget::ToolOptions`] bar.
pub struct ToolColorOptions {
pub custom_color: Option<Color>,
pub primary_working_color: Option<Color>,
@ -62,19 +64,19 @@ impl ToolColorOptions {
&self,
label_text: impl Into<String>,
color_allow_none: bool,
reset_callback: WidgetCallback<IconButton>,
reset_callback: impl Fn(&IconButton) -> Message + 'static + Send + Sync,
radio_callback: fn(ToolColorType) -> WidgetCallback<()>,
color_callback: WidgetCallback<ColorInput>,
color_callback: impl Fn(&ColorInput) -> Message + 'static + Send + Sync,
) -> Vec<WidgetHolder> {
let mut widgets = vec![TextLabel::new(label_text).widget_holder()];
if !color_allow_none {
widgets.push(WidgetHolder::unrelated_separator());
} else {
let mut reset = IconButton::new("CloseX", 12)
let reset = IconButton::new("CloseX", 12)
.disabled(self.custom_color.is_none() && self.color_type == ToolColorType::Custom)
.tooltip("Clear Color");
reset.on_update = reset_callback;
.tooltip("Clear Color")
.on_update(reset_callback);
widgets.push(WidgetHolder::related_separator());
widgets.push(reset.widget_holder());
@ -97,8 +99,7 @@ impl ToolColorOptions {
widgets.push(radio);
widgets.push(WidgetHolder::related_separator());
let mut color_input = ColorInput::new(self.active_color()).allow_none(color_allow_none);
color_input.on_update = color_callback;
let color_input = ColorInput::new(self.active_color()).allow_none(color_allow_none).on_update(color_callback);
widgets.push(color_input.widget_holder());
widgets

View File

@ -3,7 +3,6 @@ use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
@ -190,9 +189,9 @@ impl PropertyHolder for BrushTool {
widgets.append(&mut self.options.color.create_widgets(
"Color",
false,
WidgetCallback::new(|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into()),
|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value)).into()),
|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value)).into(),
));
widgets.push(WidgetHolder::related_separator());
@ -255,10 +254,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -96,9 +96,9 @@ impl PropertyHolder for EllipseTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into()),
|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(WidgetHolder::section_separator());
@ -106,9 +106,9 @@ impl PropertyHolder for EllipseTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into()),
|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value)).into(),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -140,10 +140,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Ellipse
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -1,9 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -102,9 +100,9 @@ impl PropertyHolder for FreehandTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into()),
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(WidgetHolder::section_separator());
@ -112,9 +110,9 @@ impl PropertyHolder for FreehandTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into()),
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value)).into(),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -146,10 +144,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Freehan
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -2,8 +2,7 @@ use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::{RadioEntryData, RadioInput};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};

View File

@ -2,10 +2,8 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -97,9 +95,9 @@ impl PropertyHolder for LineTool {
let mut widgets = self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into()),
|_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value)).into(),
);
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -124,10 +122,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineToo
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -3,7 +3,7 @@ use std::vec;
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, OpposingHandleLengths, ShapeState};

View File

@ -1,16 +1,13 @@
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@ -122,9 +119,9 @@ impl PropertyHolder for PenTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into()),
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(WidgetHolder::section_separator());
@ -132,9 +129,9 @@ impl PropertyHolder for PenTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into()),
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value)).into(),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -166,10 +163,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -1,8 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, NumberInput, WidgetHolder};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -83,9 +82,9 @@ impl PropertyHolder for RectangleTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into()),
|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(WidgetHolder::section_separator());
@ -93,9 +92,9 @@ impl PropertyHolder for RectangleTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into()),
|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value)).into(),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -127,10 +126,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Rectang
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -4,11 +4,8 @@ use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::assist_widgets::{PivotAssist, PivotPosition};
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
use crate::messages::layout::utility_types::widgets::input_widgets::{DropdownEntryData, DropdownInput};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*;
@ -222,11 +219,7 @@ impl PropertyHolder for SelectTool {
.on_update(|_| SelectToolMessage::FlipVertical.into())
.widget_holder(),
WidgetHolder::related_separator(),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Flip".into(),
text: "Coming soon".into(),
..Default::default()
})),
PopoverButton::new("Flip", "Coming soon").widget_holder(),
WidgetHolder::section_separator(),
IconButton::new("BooleanUnion", 24)
.tooltip("Boolean Union (coming soon)")

View File

@ -1,8 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, NumberInput, RadioEntryData, RadioInput, WidgetHolder};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -135,9 +134,9 @@ impl PropertyHolder for ShapeTool {
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into()),
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value)).into(),
));
widgets.push(WidgetHolder::section_separator());
@ -145,9 +144,9 @@ impl PropertyHolder for ShapeTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into()),
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value)).into(),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -180,10 +179,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTo
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -1,10 +1,8 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -105,9 +103,9 @@ impl PropertyHolder for SplineTool {
let mut widgets = self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into()),
|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(WidgetHolder::section_separator());
@ -115,9 +113,9 @@ impl PropertyHolder for SplineTool {
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",
true,
WidgetCallback::new(|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(None)).into()),
|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value)).into()),
|color: &ColorInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value)).into(),
));
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight));
@ -149,10 +147,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineT
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -4,9 +4,8 @@ use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::input_widgets::{ColorInput, FontInput, NumberInput};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::new_text_network;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
@ -99,34 +98,26 @@ impl ToolMetadata for TextTool {
}
fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
let font = FontInput {
is_style_picker: false,
font_family: tool.options.font_name.clone(),
font_style: tool.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
let font = FontInput::new(&tool.options.font_name, &tool.options.font_style)
.is_style_picker(false)
.on_update(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let style = FontInput {
is_style_picker: true,
font_family: tool.options.font_name.clone(),
font_style: tool.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
})
.widget_holder();
let style = FontInput::new(&tool.options.font_name, &tool.options.font_style)
.is_style_picker(true)
.on_update(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
})
.widget_holder();
let size = NumberInput::new(Some(tool.options.font_size as f64))
.unit(" px")
.label("Size")
@ -146,9 +137,9 @@ impl PropertyHolder for TextTool {
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
true,
WidgetCallback::new(|_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into()),
|_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into(),
|color_type: ToolColorType| WidgetCallback::new(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColorType(color_type.clone())).into()),
WidgetCallback::new(|color: &ColorInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value)).into()),
|color: &ColorInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value)).into(),
));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
@ -177,10 +168,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
}
}
responses.add(LayoutMessage::SendLayout {
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
self.register_properties(responses, LayoutTarget::ToolOptions);
return;
}

View File

@ -7,11 +7,8 @@ use crate::messages::broadcast::BroadcastMessage;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::IconButton;
use crate::messages::layout::utility_types::widgets::input_widgets::SwatchPairInput;
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
@ -144,29 +141,20 @@ impl DocumentToolData {
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
let layout = WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput {
primary: self.primary_color,
secondary: self.secondary_color,
}))],
widgets: vec![SwatchPairInput::new(self.primary_color, self.secondary_color).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::IconButton(IconButton {
size: 16,
icon: "Swap".into(),
tooltip: "Swap".into(),
tooltip_shortcut: action_keys!(ToolMessageDiscriminant::SwapColors),
on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()),
..Default::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
size: 16,
icon: "WorkingColors".into(),
tooltip: "Reset".into(),
tooltip_shortcut: action_keys!(ToolMessageDiscriminant::ResetColors),
on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()),
..Default::default()
})),
IconButton::new("Swap", 16)
.tooltip("Swap")
.tooltip_shortcut(action_keys!(ToolMessageDiscriminant::SwapColors))
.on_update(|_| ToolMessage::SwapColors.into())
.widget_holder(),
IconButton::new("WorkingColors", 16)
.tooltip("Reset")
.tooltip_shortcut(action_keys!(ToolMessageDiscriminant::ResetColors))
.on_update(|_| ToolMessage::ResetColors.into())
.widget_holder(),
],
},
]);
@ -259,36 +247,29 @@ impl PropertyHolder for ToolData {
.iter()
.map(|tool_group| tool_group.iter().map(|tool_availability| {
match tool_availability {
ToolAvailability::Available(tool) => ToolEntry {
tooltip: tool.tooltip(),
tooltip_shortcut: action_keys!(tool_type_to_activate_tool_message(tool.tool_type())),
icon_name: tool.icon_name(),
tool_type: tool.tool_type(),
},
ToolAvailability::Available(tool) => ToolEntry::new( tool.tool_type(), tool.icon_name())
.tooltip( tool.tooltip())
.tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(tool.tool_type())))
,
ToolAvailability::ComingSoon(tool) => tool.clone(),
}
}).collect::<Vec<_>>())
.flat_map(|group| {
let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Vertical,
separator_type: SeparatorType::Section,
})));
let separator = std::iter::once(Separator::new(SeparatorDirection::Vertical, SeparatorType::Section).widget_holder());
let buttons = group.into_iter().map(|ToolEntry { tooltip, tooltip_shortcut, tool_type, icon_name }| {
WidgetHolder::new(Widget::IconButton(IconButton {
icon: icon_name,
size: 32,
disabled: false,
active: self.active_tool_type == tool_type,
tooltip: tooltip.clone(),
tooltip_shortcut,
on_update: WidgetCallback::new(move |_| {
IconButton::new(icon_name, 32)
.disabled( false)
.active( self.active_tool_type == tool_type)
.tooltip( tooltip.clone())
.tooltip_shortcut(tooltip_shortcut)
.on_update(move |_| {
if !tooltip.contains("Coming Soon") {
ToolMessage::ActivateTool { tool_type }.into()
} else {
DialogMessage::RequestComingSoonDialog { issue: None }.into()
}
}),
}))
}).widget_holder()
});
separator.chain(buttons)
@ -303,12 +284,15 @@ impl PropertyHolder for ToolData {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default, WidgetBuilder)]
#[widget_builder(not_widget_holder)]
pub struct ToolEntry {
#[widget_builder(constructor)]
pub tool_type: ToolType,
#[widget_builder(constructor)]
pub icon_name: String,
pub tooltip: String,
pub tooltip_shortcut: Option<ActionKeys>,
pub icon_name: String,
pub tool_type: ToolType,
}
#[derive(Debug)]
@ -346,9 +330,10 @@ impl ToolFsmState {
}
#[repr(usize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, specta::Type)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default, specta::Type)]
pub enum ToolType {
// General tool group
#[default]
Select,
Artboard,
Navigate,
@ -412,36 +397,11 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
ToolAvailability::Available(Box::<frame_tool::FrameTool>::default()),
ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
ToolAvailability::Available(Box::<brush_tool::BrushTool>::default()),
ToolAvailability::ComingSoon(ToolEntry {
tool_type: ToolType::Heal,
icon_name: "RasterHealTool".into(),
tooltip: "Coming Soon: Heal Tool (J)".into(),
tooltip_shortcut: None,
}),
ToolAvailability::ComingSoon(ToolEntry {
tool_type: ToolType::Clone,
icon_name: "RasterCloneTool".into(),
tooltip: "Coming Soon: Clone Tool (C)".into(),
tooltip_shortcut: None,
}),
ToolAvailability::ComingSoon(ToolEntry {
tool_type: ToolType::Patch,
icon_name: "RasterPatchTool".into(),
tooltip: "Coming Soon: Patch Tool".into(),
tooltip_shortcut: None,
}),
ToolAvailability::ComingSoon(ToolEntry {
tool_type: ToolType::Detail,
icon_name: "RasterDetailTool".into(),
tooltip: "Coming Soon: Detail Tool (D)".into(),
tooltip_shortcut: None,
}),
ToolAvailability::ComingSoon(ToolEntry {
tool_type: ToolType::Relight,
icon_name: "RasterRelightTool".into(),
tooltip: "Coming Soon: Relight Tool (O)".into(),
tooltip_shortcut: None,
}),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Heal, "RasterHealTool").tooltip("Coming Soon: Heal Tool (J)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Clone, "RasterCloneTool").tooltip("Coming Soon: Clone Tool (C)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool").tooltip("Coming Soon: Patch Tool")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Detail, "RasterDetailTool").tooltip("Coming Soon: Detail Tool (D)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Relight, "RasterRelightTool").tooltip("Coming Soon: Relight Tool (O)")),
],
]
}