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

View File

@ -28,7 +28,7 @@ resolver = "2"
exclude = ["node-graph/gpu-compiler"] exclude = ["node-graph/gpu-compiler"]
[workspace.dependencies] [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", "glam",
] } ] }
xxhash-rust = { version = "0.8", features = ["xxh3"] } xxhash-rust = { version = "0.8", features = ["xxh3"] }

View File

@ -1,9 +1,6 @@
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; use crate::messages::layout::utility_types::widget_prelude::*;
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::prelude::*; use crate::messages::prelude::*;
use document_legacy::LayerId; use document_legacy::LayerId;
@ -47,46 +44,22 @@ impl MessageHandler<ExportDialogMessage, ()> for ExportDialogMessageHandler {
impl PropertyHolder for ExportDialogMessageHandler { impl PropertyHolder for ExportDialogMessageHandler {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let file_name = vec![ let file_name = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("File Name").table_align(true).widget_holder(),
value: "File Name".into(), WidgetHolder::unrelated_separator(),
table_align: true, TextInput::new(&self.file_name)
..Default::default() .on_update(|text_input: &TextInput| ExportDialogMessage::FileName(text_input.value.clone()).into())
})), .widget_holder(),
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()
})),
]; ];
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")] let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
.into_iter() .into_iter()
.map(|(val, name)| RadioEntryData { .map(|(val, name)| RadioEntryData::new(name).on_update(move |_| ExportDialogMessage::FileType(val).into()))
label: name.into(),
on_update: WidgetCallback::new(move |_| ExportDialogMessage::FileType(val).into()),
..RadioEntryData::default()
})
.collect(); .collect();
let export_type = vec![ let export_type = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("File Type").table_align(true).widget_holder(),
value: "File Type".into(), WidgetHolder::unrelated_separator(),
table_align: true, RadioInput::new(entries).selected_index(self.file_type as u32).widget_holder(),
..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()
})),
]; ];
let artboards = self.artboards.iter().map(|(&val, name)| (ExportBounds::Artboard(val), name.to_string(), false)); 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 index = export_area_options.iter().position(|(val, _, _)| val == &self.bounds).unwrap();
let entries = vec![export_area_options let entries = vec![export_area_options
.into_iter() .into_iter()
.map(|(val, name, disabled)| DropdownEntryData { .map(|(val, name, disabled)| DropdownEntryData::new(name).on_update(move |_| ExportDialogMessage::ExportBounds(val).into()).disabled(disabled))
label: name,
on_update: WidgetCallback::new(move |_| ExportDialogMessage::ExportBounds(val).into()),
disabled,
..Default::default()
})
.collect()]; .collect()];
let export_area = vec![ let export_area = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Bounds").table_align(true).widget_holder(),
value: "Bounds".into(), WidgetHolder::unrelated_separator(),
table_align: true, DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
..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()
})),
]; ];
let transparent_background = vec![ let transparent_background = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Transparency").table_align(true).widget_holder(),
value: "Transparency".into(), WidgetHolder::unrelated_separator(),
table_align: true, CheckboxInput::new(self.transparent_background)
..Default::default() .disabled(self.file_type == FileType::Jpg)
})), .on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
WidgetHolder::new(Widget::Separator(Separator { .widget_holder(),
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()
})),
]; ];
let resolution = vec![ let resolution = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Scale Factor").table_align(true).widget_holder(),
value: "Scale Factor".into(), WidgetHolder::unrelated_separator(),
table_align: true, NumberInput::new(Some(self.scale_factor))
..TextLabel::default() .unit(" ")
})), .min(0.)
WidgetHolder::new(Widget::Separator(Separator { .disabled(self.file_type == FileType::Svg)
separator_type: SeparatorType::Unrelated, .on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
direction: SeparatorDirection::Horizontal, .widget_holder(),
})),
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()
})),
]; ];
let button_widgets = vec![ let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton { TextButton::new("Export")
label: "Export".to_string(), .min_width(96)
min_width: 96, .emphasized(true)
emphasized: true, .on_update(|_| {
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen { DialogMessage::CloseDialogAndThen {
followups: vec![ExportDialogMessage::Submit.into()], followups: vec![ExportDialogMessage::Submit.into()],
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder(),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
]; ];
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Export").bold(true).widget_holder()],
value: "Export".to_string(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { widgets: file_name }, LayoutGroup::Row { widgets: file_name },
LayoutGroup::Row { widgets: export_type }, 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; use crate::messages::layout::utility_types::widget_prelude::*;
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::prelude::*; use crate::messages::prelude::*;
use graphene_core::uuid::generate_uuid; use graphene_core::uuid::generate_uuid;
@ -54,103 +51,60 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
impl PropertyHolder for NewDocumentDialogMessageHandler { impl PropertyHolder for NewDocumentDialogMessageHandler {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let title = vec![WidgetHolder::new(Widget::TextLabel(TextLabel { let title = vec![TextLabel::new("New document").bold(true).widget_holder()];
value: "New document".into(),
bold: true,
..Default::default()
}))];
let name = vec![ let name = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Name").table_align(true).widget_holder(),
value: "Name".into(), WidgetHolder::unrelated_separator(),
table_align: true, TextInput::new(&self.name)
..Default::default() .on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into())
})), .widget_holder(),
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()
})),
]; ];
let infinite = vec![ let infinite = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Infinite Canvas").table_align(true).widget_holder(),
value: "Infinite Canvas".into(), WidgetHolder::unrelated_separator(),
table_align: true, CheckboxInput::new(self.infinite)
..Default::default() .on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
})), .widget_holder(),
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()
})),
]; ];
let scale = vec![ let scale = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Dimensions").table_align(true).widget_holder(),
value: "Dimensions".into(), WidgetHolder::unrelated_separator(),
table_align: true, NumberInput::new(Some(self.dimensions.x as f64))
..TextLabel::default() .label("W")
})), .unit(" px")
WidgetHolder::new(Widget::Separator(Separator { .min(0.)
separator_type: SeparatorType::Unrelated, .is_integer(true)
direction: SeparatorDirection::Horizontal, .disabled(self.infinite)
})), .min_width(100)
WidgetHolder::new(Widget::NumberInput(NumberInput { .on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into())
label: "W".into(), .widget_holder(),
unit: " px".into(), WidgetHolder::related_separator(),
value: Some(self.dimensions.x as f64), NumberInput::new(Some(self.dimensions.y as f64))
min: Some(0.), .label("H")
is_integer: true, .unit(" px")
disabled: self.infinite, .min(0.)
min_width: 100, .is_integer(true)
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into()), .disabled(self.infinite)
..NumberInput::default() .min_width(100)
})), .on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into())
WidgetHolder::new(Widget::Separator(Separator { .widget_holder(),
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()
})),
]; ];
let button_widgets = vec![ let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton { TextButton::new("OK")
label: "OK".to_string(), .min_width(96)
min_width: 96, .emphasized(true)
emphasized: true, .on_update(|_| {
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen { DialogMessage::CloseDialogAndThen {
followups: vec![NewDocumentDialogMessage::Submit.into()], followups: vec![NewDocumentDialogMessage::Submit.into()],
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder(),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
]; ];
Layout::WidgetLayout(WidgetLayout::new(vec![ 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; use crate::messages::layout::utility_types::widget_prelude::*;
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::prelude::*; use crate::messages::prelude::*;
/// A dialog to allow users to customize Graphite editor options /// A dialog to allow users to customize Graphite editor options
@ -31,107 +28,62 @@ impl PreferencesDialogMessageHandler {
fn properties(&self, preferences: &PreferencesMessageHandler) -> Layout { fn properties(&self, preferences: &PreferencesMessageHandler) -> Layout {
let zoom_with_scroll = vec![ let zoom_with_scroll = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Input").min_width(60).italic(true).widget_holder(),
value: "Input".into(), TextLabel::new("Zoom with Scroll").table_align(true).widget_holder(),
min_width: 60, WidgetHolder::unrelated_separator(),
italic: true, CheckboxInput::new(preferences.zoom_with_scroll)
..Default::default() .tooltip("Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)")
})), .on_update(|checkbox_input: &CheckboxInput| {
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| {
PreferencesMessage::ModifyLayout { PreferencesMessage::ModifyLayout {
zoom_with_scroll: checkbox_input.checked, zoom_with_scroll: checkbox_input.checked,
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})),
]; ];
let imaginate_server_hostname = vec![ let imaginate_server_hostname = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(),
value: "Imaginate".into(), TextLabel::new("Server Hostname").table_align(true).widget_holder(),
min_width: 60, WidgetHolder::unrelated_separator(),
italic: true, TextInput::new(&preferences.imaginate_server_hostname)
..Default::default() .min_width(200)
})), .on_update(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into())
WidgetHolder::new(Widget::TextLabel(TextLabel { .widget_holder(),
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()
})),
]; ];
let imaginate_refresh_frequency = vec![ let imaginate_refresh_frequency = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel { min_width: 60, ..Default::default() })), TextLabel::new("").min_width(60).widget_holder(),
WidgetHolder::new(Widget::TextLabel(TextLabel { TextLabel::new("Refresh Frequency").table_align(true).widget_holder(),
value: "Refresh Frequency".into(), WidgetHolder::unrelated_separator(),
table_align: true, NumberInput::new(Some(preferences.imaginate_refresh_frequency))
..Default::default() .unit(" seconds")
})), .min(0.)
WidgetHolder::new(Widget::Separator(Separator { .min_width(200)
separator_type: SeparatorType::Unrelated, .on_update(|number_input: &NumberInput| PreferencesMessage::ImaginateRefreshFrequency { seconds: number_input.value.unwrap() }.into())
direction: SeparatorDirection::Horizontal, .widget_holder(),
})),
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()
})),
]; ];
let button_widgets = vec![ let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton { TextButton::new("Ok")
label: "Ok".to_string(), .min_width(96)
min_width: 96, .emphasized(true)
emphasized: true, .on_update(|_| {
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen { DialogMessage::CloseDialogAndThen {
followups: vec![PreferencesDialogMessage::Confirm.into()], followups: vec![PreferencesDialogMessage::Confirm.into()],
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), TextButton::new("Reset to Defaults")
WidgetHolder::new(Widget::TextButton(TextButton { .min_width(96)
label: "Reset to Defaults".to_string(), .on_update(|_| PreferencesMessage::ResetToDefaults.into())
min_width: 96, .widget_holder(),
on_update: WidgetCallback::new(|_| PreferencesMessage::ResetToDefaults.into()),
..Default::default()
})),
]; ];
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Editor Preferences").bold(true).widget_holder()],
value: "Editor Preferences".to_string(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { widgets: zoom_with_scroll }, LayoutGroup::Row { widgets: zoom_with_scroll },
LayoutGroup::Row { widgets: imaginate_server_hostname }, LayoutGroup::Row { widgets: imaginate_server_hostname },

View File

@ -1,7 +1,5 @@
use crate::application::{commit_info_localized, release_series}; 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::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*; use crate::messages::prelude::*;
/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar. /// 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 let link_widgets = links
.into_iter() .into_iter()
.map(|(label, url)| { .map(|(label, url)| TextButton::new(label).on_update(|_| FrontendMessage::TriggerVisitLink { url: url.to_string() }.into()).widget_holder())
WidgetHolder::new(Widget::TextButton(TextButton {
label: label.to_string(),
on_update: WidgetCallback::new(|_| FrontendMessage::TriggerVisitLink { url: url.to_string() }.into()),
..Default::default()
}))
})
.collect(); .collect();
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Graphite".to_string()).bold(true).widget_holder()],
value: "Graphite".to_string(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new(release_series()).widget_holder()],
value: release_series(),
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_holder()],
value: commit_info_localized(self.localized_commit_date.as_str()),
multiline: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { widgets: link_widgets }, 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::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*; use crate::messages::prelude::*;
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar. /// 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 { impl PropertyHolder for CloseAllDocumentsDialog {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let button_widgets = vec![ let discard = TextButton::new("Discard All")
WidgetHolder::new(Widget::TextButton(TextButton { .min_width(96)
label: "Discard All".to_string(), .on_update(|_| {
min_width: 96, DialogMessage::CloseDialogAndThen {
on_update: WidgetCallback::new(|_| { followups: vec![PortfolioMessage::CloseAllDocuments.into()],
DialogMessage::CloseDialogAndThen { }
followups: vec![PortfolioMessage::CloseAllDocuments.into()], .into()
} })
.into() .widget_holder();
}), let cancel = TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder();
..Default::default()
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
];
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Close all documents?").multiline(true).widget_holder()],
value: "Close all documents?".to_string(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Unsaved work will be lost!").multiline(true).widget_holder()],
value: "Unsaved work will be lost!".to_string(),
multiline: true,
..Default::default()
}))],
}, },
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::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::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*; use crate::messages::prelude::*;
/// A dialog for confirming the closing a document with unsaved changes. /// 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 document_id = self.document_id;
let button_widgets = vec![ let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton { TextButton::new("Save")
label: "Save".to_string(), .min_width(96)
min_width: 96, .emphasized(true)
emphasized: true, .on_update(|_| {
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen { DialogMessage::CloseDialogAndThen {
followups: vec![DocumentMessage::SaveDocument.into()], followups: vec![DocumentMessage::SaveDocument.into()],
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), TextButton::new("Discard")
WidgetHolder::new(Widget::TextButton(TextButton { .min_width(96)
label: "Discard".to_string(), .on_update(move |_| {
min_width: 96,
on_update: WidgetCallback::new(move |_| {
DialogMessage::CloseDialogAndThen { DialogMessage::CloseDialogAndThen {
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()], followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), TextButton::new("Cancel").min_width(96).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder(),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Cancel".to_string(),
min_width: 96,
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
..Default::default()
})),
]; ];
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Save changes before closing?").bold(true).widget_holder()],
value: "Save changes before closing?".to_string(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new(&self.document_name).multiline(true).widget_holder()],
value: self.document_name.clone(),
multiline: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { widgets: button_widgets }, 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::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use std::fmt::Write; use std::fmt::Write;
@ -13,41 +11,33 @@ pub struct ComingSoonDialog {
impl PropertyHolder for ComingSoonDialog { impl PropertyHolder for ComingSoonDialog {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
let mut details = "This feature is not implemented yet".to_string(); let mut details = "This feature is not implemented yet".to_string();
let mut buttons = vec![WidgetHolder::new(Widget::TextButton(TextButton {
label: "OK".to_string(), let mut buttons = vec![TextButton::new("OK")
emphasized: true, .emphasized(true)
min_width: 96, .min_width(96)
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()), .on_update(|_| FrontendMessage::DisplayDialogDismiss.into())
..Default::default() .widget_holder()];
}))];
if let Some(issue) = self.issue { if let Some(issue) = self.issue {
let _ = write!(details, "— but you can help add it!\nSee issue #{issue} on GitHub."); let _ = write!(details, "— but you can help add it!\nSee issue #{issue} on GitHub.");
buttons.push(WidgetHolder::new(Widget::TextButton(TextButton { buttons.push(
label: format!("Issue #{issue}"), TextButton::new(format!("Issue #{issue}"))
min_width: 96, .min_width(96)
on_update: WidgetCallback::new(move |_| { .on_update(move |_| {
FrontendMessage::TriggerVisitLink { FrontendMessage::TriggerVisitLink {
url: format!("https://github.com/GraphiteEditor/Graphite/issues/{issue}"), url: format!("https://github.com/GraphiteEditor/Graphite/issues/{issue}"),
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
}))); );
} }
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new("Coming soon").bold(true).widget_holder()],
value: "Coming soon".to_string(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new(details).multiline(true).widget_holder()],
value: details,
multiline: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { widgets: buttons }, 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::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*; use crate::messages::prelude::*;
/// A dialog to notify users of a non-fatal error. /// A dialog to notify users of a non-fatal error.
@ -13,27 +11,17 @@ impl PropertyHolder for ErrorDialog {
fn properties(&self) -> Layout { fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new(&self.title).bold(true).widget_holder()],
value: self.title.clone(),
bold: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![TextLabel::new(&self.description).multiline(true).widget_holder()],
value: self.description.clone(),
multiline: true,
..Default::default()
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextButton(TextButton { widgets: vec![TextButton::new("OK")
label: "OK".to_string(), .emphasized(true)
emphasized: true, .min_width(96)
min_width: 96, .on_update(|_| FrontendMessage::DisplayDialogDismiss.into())
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()), .widget_holder()],
..Default::default()
}))],
}, },
])) ]))
} }

View File

@ -452,6 +452,7 @@ pub struct WidgetHolder {
} }
impl 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 { pub fn new(widget: Widget) -> Self {
Self { widget_id: generate_uuid(), widget } 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)] #[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize, specta::Type)]
#[repr(u8)] #[repr(u8)]
pub enum LayoutTarget { pub enum LayoutTarget {
/// Contains the contents of the dialog, including the title and action buttons. Must be shown with the `FrontendMessage::DisplayDialog` message.
DialogDetails, DialogDetails,
/// Contains the widgets located directly above the canvas to the right, for example the zoom in and out buttons.
DocumentBar, DocumentBar,
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
DocumentMode, DocumentMode,
/// Options for opacity seen at the top of the Layers panel.
LayerTreeOptions, LayerTreeOptions,
/// The dropdown menu at the very top of the application: File, Edit, etc.
MenuBar, MenuBar,
/// Bar at the top of the node graph containing the location and the 'preview' and 'hide' buttons.
NodeGraphBar, NodeGraphBar,
/// The bar at the top of the Properties panel containing the layer name and icon.
PropertiesOptions, PropertiesOptions,
/// The body of the Properties panel containing many collapsable sections.
PropertiesSections, PropertiesSections,
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
ToolOptions, ToolOptions,
/// The vertical buttons for all of the tools on the left of the canvas.
ToolShelf, ToolShelf,
/// The color swatch for the working colors and a flip and reset button found at the bottom of the tool shelf.
WorkingColors, WorkingColors,
// KEEP THIS ENUM LAST // 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::input_keyboard::KeysGroup;
use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::WidgetHolder; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::layout::utility_types::layout_widget::{Widget, WidgetCallback};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -51,9 +50,7 @@ impl MenuBarEntry {
} }
pub fn create_action(callback: impl Fn(&()) -> Message + 'static + Send + Sync) -> WidgetHolder { pub fn create_action(callback: impl Fn(&()) -> Message + 'static + Send + Sync) -> WidgetHolder {
WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput { InvisibleStandinInput::new().on_update(callback).widget_holder()
on_update: WidgetCallback::new(callback),
}))
} }
pub fn no_action() -> WidgetHolder { 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::ExportBounds;
use crate::messages::frontend::utility_types::FileType; use crate::messages::frontend::utility_types::FileType;
use crate::messages::input_mapper::utility_types::macros::action_keys; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{CheckboxInput, TextLabel}; use crate::messages::layout::utility_types::widget_prelude::*;
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::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; 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::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerMetadata, LayerPanelEntry, RawBuffer}; 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, DocumentMode, DocumentSave, FlipAxis};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis};
use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata; use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata;
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
@ -1589,11 +1582,9 @@ impl DocumentMessageHandler {
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) { pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
let snapping_state = self.snapping_state.clone(); let snapping_state = self.snapping_state.clone();
let mut widgets = vec![ let mut widgets = vec![
WidgetHolder::new(Widget::OptionalInput(OptionalInput { OptionalInput::new(snapping_state.snapping_enabled, "Snapping")
checked: snapping_state.snapping_enabled, .tooltip("Snapping")
icon: "Snapping".into(), .on_update(move |optional_input: &OptionalInput| {
tooltip: "Snapping".into(),
on_update: WidgetCallback::new(move |optional_input: &OptionalInput| {
let snapping_enabled = optional_input.checked; let snapping_enabled = optional_input.checked;
DocumentMessage::SetSnapping { DocumentMessage::SetSnapping {
snapping_enabled: Some(snapping_enabled), snapping_enabled: Some(snapping_enabled),
@ -1601,261 +1592,159 @@ impl DocumentMessageHandler {
node_snapping: Some(snapping_state.node_snapping), node_snapping: Some(snapping_state.node_snapping),
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), PopoverButton::new("Snapping", "Snap customization settings")
WidgetHolder::new(Widget::PopoverButton(PopoverButton { .options_widget(vec![
header: "Snapping".into(),
text: "Select the vectors to snap to.".into(), // TODO: check whether this is an apt description
options_widget: vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![ widgets: vec![
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput { CheckboxInput::new(snapping_state.bounding_box_snapping)
tooltip: SnappingOptions::BoundingBoxes.to_string(), .tooltip(SnappingOptions::BoundingBoxes.to_string())
checked: snapping_state.bounding_box_snapping, .on_update(move |input: &CheckboxInput| {
on_update: WidgetCallback::new(move |input: &CheckboxInput| {
DocumentMessage::SetSnapping { DocumentMessage::SetSnapping {
snapping_enabled: None, snapping_enabled: None,
bounding_box_snapping: Some(input.checked), bounding_box_snapping: Some(input.checked),
node_snapping: None, node_snapping: None,
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::Separator(Separator { TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).table_align(false).min_width(60).widget_holder(),
direction: SeparatorDirection::Horizontal, WidgetHolder::related_separator(),
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,
})),
], ],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![ widgets: vec![
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput { CheckboxInput::new(self.snapping_state.node_snapping)
checked: self.snapping_state.node_snapping, .tooltip(SnappingOptions::Points.to_string())
tooltip: SnappingOptions::Points.to_string(), .on_update(|input: &CheckboxInput| {
on_update: WidgetCallback::new(|input: &CheckboxInput| {
DocumentMessage::SetSnapping { DocumentMessage::SetSnapping {
snapping_enabled: None, snapping_enabled: None,
bounding_box_snapping: None, bounding_box_snapping: None,
node_snapping: Some(input.checked), node_snapping: Some(input.checked),
} }
.into() .into()
}), })
..Default::default() .widget_holder(),
})), WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::Separator(Separator { TextLabel::new(SnappingOptions::Points.to_string()).table_align(false).min_width(60).widget_holder(),
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(),
..Default::default() WidgetHolder::unrelated_separator(),
})), OptionalInput::new(true, "Grid")
WidgetHolder::new(Widget::Separator(Separator { .tooltip("Grid")
separator_type: SeparatorType::Unrelated, .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into())
direction: SeparatorDirection::Horizontal, .widget_holder(),
})), PopoverButton::new("Grid", "Coming soon").widget_holder(),
WidgetHolder::new(Widget::OptionalInput(OptionalInput { WidgetHolder::unrelated_separator(),
checked: true, OptionalInput::new(self.overlays_visible, "Overlays")
icon: "Grid".into(), .tooltip("Overlays")
tooltip: "Grid".into(), .on_update(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()), .widget_holder(),
..Default::default() PopoverButton::new("Overlays", "Coming soon").widget_holder(),
})), WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::PopoverButton(PopoverButton { RadioInput::new(vec![
header: "Grid".into(), RadioEntryData::default()
text: "Coming soon".into(), .value("normal")
..Default::default() .icon("ViewModeNormal")
})), .tooltip("View Mode: Normal")
WidgetHolder::new(Widget::Separator(Separator { .on_update(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Normal }.into()),
separator_type: SeparatorType::Unrelated, RadioEntryData::default()
direction: SeparatorDirection::Horizontal, .value("outline")
})), .icon("ViewModeOutline")
WidgetHolder::new(Widget::OptionalInput(OptionalInput { .tooltip("View Mode: Outline")
checked: self.overlays_visible, .on_update(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Outline }.into()),
icon: "Overlays".into(), RadioEntryData::default()
tooltip: "Overlays".into(), .value("pixels")
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()), .icon("ViewModePixels")
..Default::default() .tooltip("View Mode: Pixels")
})), .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(320) }.into()),
WidgetHolder::new(Widget::PopoverButton(PopoverButton { ])
header: "Overlays".into(), .selected_index(match self.view_mode {
text: "Coming soon".into(), ViewMode::Normal => 0,
..Default::default() _ => 1,
})), })
WidgetHolder::new(Widget::Separator(Separator { .widget_holder(),
separator_type: SeparatorType::Unrelated, PopoverButton::new("View Mode", "Coming soon").widget_holder(),
direction: SeparatorDirection::Horizontal, WidgetHolder::section_separator(),
})), IconButton::new("ZoomIn", 24)
WidgetHolder::new(Widget::RadioInput(RadioInput { .tooltip("Zoom In")
selected_index: match self.view_mode { .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom))
ViewMode::Normal => 0, .on_update(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into())
_ => 1, .widget_holder(),
}, IconButton::new("ZoomOut", 24)
entries: vec![ .tooltip("Zoom Out")
RadioEntryData { .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::DecreaseCanvasZoom))
value: "normal".into(), .on_update(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into())
icon: "ViewModeNormal".into(), .widget_holder(),
tooltip: "View Mode: Normal".into(), IconButton::new("ZoomReset", 24)
on_update: WidgetCallback::new(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Normal }.into()), .tooltip("Zoom to 100%")
..RadioEntryData::default() .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent))
}, .on_update(|_| NavigationMessage::SetCanvasZoom { zoom_factor: 1. }.into())
RadioEntryData { .widget_holder(),
value: "outline".into(), WidgetHolder::related_separator(),
icon: "ViewModeOutline".into(), NumberInput::new(Some(self.navigation_handler.snapped_scale() * 100.))
tooltip: "View Mode: Outline".into(), .unit("%")
on_update: WidgetCallback::new(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Outline }.into()), .min(0.000001)
..RadioEntryData::default() .max(1000000.)
}, .mode_increment()
RadioEntryData { .on_update(|number_input: &NumberInput| {
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| {
NavigationMessage::SetCanvasZoom { NavigationMessage::SetCanvasZoom {
zoom_factor: number_input.value.unwrap() / 100., zoom_factor: number_input.value.unwrap() / 100.,
} }
.into() .into()
}), })
increment_behavior: NumberInputIncrementBehavior::Callback, .increment_behavior(NumberInputIncrementBehavior::Callback)
increment_callback_decrease: WidgetCallback::new(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()), .increment_callback_decrease(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into())
increment_callback_increase: WidgetCallback::new(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()), .increment_callback_increase(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into())
..NumberInput::default() .widget_holder(),
})),
]; ];
let rotation_value = self.navigation_handler.snapped_angle() / (std::f64::consts::PI / 180.); let rotation_value = self.navigation_handler.snapped_angle() / (std::f64::consts::PI / 180.);
if rotation_value.abs() > 0.00001 { if rotation_value.abs() > 0.00001 {
widgets.extend([ widgets.extend([
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::related_separator(),
separator_type: SeparatorType::Related, NumberInput::new(Some(rotation_value))
direction: SeparatorDirection::Horizontal, .unit("°")
})), .step(15.)
WidgetHolder::new(Widget::NumberInput(NumberInput { .on_update(|number_input: &NumberInput| {
unit: "°".into(),
value: Some(rotation_value),
step: 15.,
on_update: WidgetCallback::new(|number_input: &NumberInput| {
NavigationMessage::SetCanvasRotation { NavigationMessage::SetCanvasRotation {
angle_radians: number_input.value.unwrap() * (std::f64::consts::PI / 180.), angle_radians: number_input.value.unwrap() * (std::f64::consts::PI / 180.),
} }
.into() .into()
}), })
..NumberInput::default() .widget_holder(),
})),
]); ]);
} }
widgets.extend([ widgets.extend([
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::related_separator(),
separator_type: SeparatorType::Related, PopoverButton::new(
direction: SeparatorDirection::Horizontal, "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.",
WidgetHolder::new(Widget::PopoverButton(PopoverButton { )
header: "Canvas Navigation".into(), .widget_holder(),
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()
})),
]); ]);
let document_bar_layout = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]); let document_bar_layout = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row { let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![ widgets: vec![
WidgetHolder::new(Widget::DropdownInput(DropdownInput { DropdownInput::new(
entries: vec![vec![ vec![vec![
DropdownEntryData { DropdownEntryData::new(DocumentMode::DesignMode.to_string()).icon(DocumentMode::DesignMode.icon_name()),
label: DocumentMode::DesignMode.to_string(), DropdownEntryData::new(DocumentMode::SelectMode.to_string())
icon: DocumentMode::DesignMode.icon_name(), .icon(DocumentMode::SelectMode.icon_name())
..DropdownEntryData::default() .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()),
}, DropdownEntryData::new(DocumentMode::GuideMode.to_string())
DropdownEntryData { .icon(DocumentMode::GuideMode.icon_name())
label: DocumentMode::SelectMode.to_string(), .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(331) }.into()),
icon: DocumentMode::SelectMode.icon_name(), ]])
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()), .selected_index( Some(self.document_mode as u32))
..DropdownEntryData::default() .draw_icon( true)
}, .interactive( false) // TODO: set to true when dialogs are not spawned
DropdownEntryData { .widget_holder(),
label: DocumentMode::GuideMode.to_string(), WidgetHolder::section_separator(),
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,
})),
], ],
}]); }]);
@ -1914,11 +1803,10 @@ impl DocumentMessageHandler {
.map(|modes| { .map(|modes| {
modes modes
.iter() .iter()
.map(|mode| DropdownEntryData { .map(|mode| {
label: mode.to_string(), DropdownEntryData::new(mode.to_string())
value: mode.to_string(), .value(mode.to_string())
on_update: WidgetCallback::new(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into()), .on_update(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into())
..Default::default()
}) })
.collect() .collect()
}) })
@ -1926,57 +1814,41 @@ impl DocumentMessageHandler {
let layer_tree_options = WidgetLayout::new(vec![LayoutGroup::Row { let layer_tree_options = WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![ widgets: vec![
WidgetHolder::new(Widget::DropdownInput(DropdownInput { DropdownInput::new(blend_mode_menu_entries)
entries: blend_mode_menu_entries, .selected_index(blend_mode.map(|blend_mode| blend_mode as u32))
selected_index: blend_mode.map(|blend_mode| blend_mode as u32), .disabled(blend_mode.is_none() && !blend_mode_is_mixed)
disabled: blend_mode.is_none() && !blend_mode_is_mixed, .draw_icon(false)
draw_icon: false, .widget_holder(),
..Default::default() WidgetHolder::related_separator(),
})), NumberInput::new(opacity.map(|opacity| opacity * 100.))
WidgetHolder::new(Widget::Separator(Separator { .label("Opacity")
separator_type: SeparatorType::Related, .unit("%")
direction: SeparatorDirection::Horizontal, .display_decimal_places(2)
})), .disabled(opacity.is_none() && !opacity_is_mixed)
WidgetHolder::new(Widget::NumberInput(NumberInput { .min(0.)
label: "Opacity".into(), .max(100.)
unit: "%".into(), .range_min(Some(0.))
display_decimal_places: 2, .range_max(Some(100.))
disabled: opacity.is_none() && !opacity_is_mixed, .mode(NumberInputMode::Range)
value: opacity.map(|opacity| opacity * 100.), .on_update(|number_input: &NumberInput| {
min: Some(0.),
max: Some(100.),
range_min: Some(0.),
range_max: Some(100.),
mode: NumberInputMode::Range,
on_update: WidgetCallback::new(|number_input: &NumberInput| {
if let Some(value) = number_input.value { if let Some(value) = number_input.value {
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into() DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
} else { } else {
Message::NoOp Message::NoOp
} }
}), })
..NumberInput::default() .widget_holder(),
})), WidgetHolder::section_separator(),
WidgetHolder::new(Widget::Separator(Separator { IconButton::new("Folder", 24)
separator_type: SeparatorType::Section, .tooltip("New Folder")
direction: SeparatorDirection::Horizontal, .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
})), .on_update(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into())
WidgetHolder::new(Widget::IconButton(IconButton { .widget_holder(),
icon: "Folder".into(), IconButton::new("Trash", 24)
tooltip: "New Folder".into(), .tooltip("Delete Selected")
tooltip_shortcut: action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder), .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
size: 24, .on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()), .widget_holder(),
..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()
})),
], ],
}]); }]);

View File

@ -245,12 +245,10 @@ impl NodeGraphMessageHandler {
// Don't show stop previewing button on the original output node // Don't show stop previewing button on the original output node
if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) { if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) {
let output_button = WidgetHolder::new(Widget::TextButton(TextButton { let output_button = TextButton::new(if is_output { "End Preview" } else { "Preview" })
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)")
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())
on_update: WidgetCallback::new(move |_| NodeGraphMessage::TogglePreview { node_id }.into()), .widget_holder();
..Default::default()
}));
widgets.push(output_button); 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::layout_widget::WidgetCallback;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, IconButton, RadioEntryData, RadioInput, TextLabel, WidgetHolder}; use crate::messages::layout::utility_types::widget_prelude::{ColorInput, IconButton, RadioEntryData, RadioInput, TextLabel, WidgetHolder};
use crate::messages::prelude::Message;
use graphene_core::Color; use graphene_core::Color;
@ -12,6 +13,7 @@ pub enum ToolColorType {
Custom, Custom,
} }
/// Color selector widgets seen in [`LayoutTarget::ToolOptions`] bar.
pub struct ToolColorOptions { pub struct ToolColorOptions {
pub custom_color: Option<Color>, pub custom_color: Option<Color>,
pub primary_working_color: Option<Color>, pub primary_working_color: Option<Color>,
@ -62,19 +64,19 @@ impl ToolColorOptions {
&self, &self,
label_text: impl Into<String>, label_text: impl Into<String>,
color_allow_none: bool, color_allow_none: bool,
reset_callback: WidgetCallback<IconButton>, reset_callback: impl Fn(&IconButton) -> Message + 'static + Send + Sync,
radio_callback: fn(ToolColorType) -> WidgetCallback<()>, radio_callback: fn(ToolColorType) -> WidgetCallback<()>,
color_callback: WidgetCallback<ColorInput>, color_callback: impl Fn(&ColorInput) -> Message + 'static + Send + Sync,
) -> Vec<WidgetHolder> { ) -> Vec<WidgetHolder> {
let mut widgets = vec![TextLabel::new(label_text).widget_holder()]; let mut widgets = vec![TextLabel::new(label_text).widget_holder()];
if !color_allow_none { if !color_allow_none {
widgets.push(WidgetHolder::unrelated_separator()); widgets.push(WidgetHolder::unrelated_separator());
} else { } 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) .disabled(self.custom_color.is_none() && self.color_type == ToolColorType::Custom)
.tooltip("Clear Color"); .tooltip("Clear Color")
reset.on_update = reset_callback; .on_update(reset_callback);
widgets.push(WidgetHolder::related_separator()); widgets.push(WidgetHolder::related_separator());
widgets.push(reset.widget_holder()); widgets.push(reset.widget_holder());
@ -97,8 +99,7 @@ impl ToolColorOptions {
widgets.push(radio); widgets.push(radio);
widgets.push(WidgetHolder::related_separator()); widgets.push(WidgetHolder::related_separator());
let mut color_input = ColorInput::new(self.active_color()).allow_none(color_allow_none); let color_input = ColorInput::new(self.active_color()).allow_none(color_allow_none).on_update(color_callback);
color_input.on_update = color_callback;
widgets.push(color_input.widget_holder()); widgets.push(color_input.widget_holder());
widgets 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::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget; use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::*; 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::portfolio::document::node_graph::transform_utils::get_current_transform;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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( widgets.append(&mut self.options.color.create_widgets(
"Color", "Color",
false, 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()), |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()); widgets.push(WidgetHolder::related_separator());
@ -255,10 +254,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
} }
} }
responses.add(LayoutMessage::SendLayout { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; return;
} }

View File

@ -96,9 +96,9 @@ impl PropertyHolder for EllipseTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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()); widgets.push(WidgetHolder::section_separator());
@ -106,9 +106,9 @@ impl PropertyHolder for EllipseTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; return;
} }

View File

@ -1,9 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder}; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::graph_modification_utils;
@ -102,9 +100,9 @@ impl PropertyHolder for FreehandTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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()); widgets.push(WidgetHolder::section_separator());
@ -112,9 +110,9 @@ impl PropertyHolder for FreehandTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; 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::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::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; 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::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::input_widgets::{RadioEntryData, RadioInput};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager; 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::{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::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder}; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::graph_modification_utils;
@ -97,9 +95,9 @@ impl PropertyHolder for LineTool {
let mut widgets = self.options.stroke.create_widgets( let mut widgets = self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; return;
} }

View File

@ -3,7 +3,7 @@ use std::vec;
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, OpposingHandleLengths, ShapeState}; 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::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder}; 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::VectorDataModification; use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::graph_modification_utils;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::snapping::SnapManager; 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::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; 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( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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()); widgets.push(WidgetHolder::section_separator());
@ -132,9 +129,9 @@ impl PropertyHolder for PenTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; return;
} }

View File

@ -1,8 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::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::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::graph_modification_utils;
@ -83,9 +82,9 @@ impl PropertyHolder for RectangleTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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()); widgets.push(WidgetHolder::section_separator());
@ -93,9 +92,9 @@ impl PropertyHolder for RectangleTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; 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::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::assist_widgets::{PivotAssist, PivotPosition}; use crate::messages::layout::utility_types::widget_prelude::*;
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::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*; use crate::messages::prelude::*;
@ -222,11 +219,7 @@ impl PropertyHolder for SelectTool {
.on_update(|_| SelectToolMessage::FlipVertical.into()) .on_update(|_| SelectToolMessage::FlipVertical.into())
.widget_holder(), .widget_holder(),
WidgetHolder::related_separator(), WidgetHolder::related_separator(),
WidgetHolder::new(Widget::PopoverButton(PopoverButton { PopoverButton::new("Flip", "Coming soon").widget_holder(),
header: "Flip".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::section_separator(), WidgetHolder::section_separator(),
IconButton::new("BooleanUnion", 24) IconButton::new("BooleanUnion", 24)
.tooltip("Boolean Union (coming soon)") .tooltip("Boolean Union (coming soon)")

View File

@ -1,8 +1,7 @@
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::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::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::graph_modification_utils;
@ -135,9 +134,9 @@ impl PropertyHolder for ShapeTool {
widgets.append(&mut self.options.fill.create_widgets( widgets.append(&mut self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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()); widgets.push(WidgetHolder::section_separator());
@ -145,9 +144,9 @@ impl PropertyHolder for ShapeTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; return;
} }

View File

@ -1,10 +1,8 @@
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widget_prelude::{ColorInput, WidgetHolder}; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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::graph_modification_utils;
@ -105,9 +103,9 @@ impl PropertyHolder for SplineTool {
let mut widgets = self.options.fill.create_widgets( let mut widgets = self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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()); widgets.push(WidgetHolder::section_separator());
@ -115,9 +113,9 @@ impl PropertyHolder for SplineTool {
widgets.append(&mut self.options.stroke.create_widgets( widgets.append(&mut self.options.stroke.create_widgets(
"Stroke", "Stroke",
true, 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()), |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(WidgetHolder::unrelated_separator());
widgets.push(create_weight_widget(self.options.line_weight)); 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; return;
} }

View File

@ -4,9 +4,8 @@ use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE}; use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon; 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_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::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::portfolio::document::node_graph::new_text_network;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; 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> { fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
let font = FontInput { let font = FontInput::new(&tool.options.font_name, &tool.options.font_style)
is_style_picker: false, .is_style_picker(false)
font_family: tool.options.font_name.clone(), .on_update(|font_input: &FontInput| {
font_style: tool.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(), family: font_input.font_family.clone(),
style: font_input.font_style.clone(), style: font_input.font_style.clone(),
}) })
.into() .into()
}), })
..Default::default() .widget_holder();
} let style = FontInput::new(&tool.options.font_name, &tool.options.font_style)
.widget_holder(); .is_style_picker(true)
let style = FontInput { .on_update(|font_input: &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| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(), family: font_input.font_family.clone(),
style: font_input.font_style.clone(), style: font_input.font_style.clone(),
}) })
.into() .into()
}), })
..Default::default() .widget_holder();
}
.widget_holder();
let size = NumberInput::new(Some(tool.options.font_size as f64)) let size = NumberInput::new(Some(tool.options.font_size as f64))
.unit(" px") .unit(" px")
.label("Size") .label("Size")
@ -146,9 +137,9 @@ impl PropertyHolder for TextTool {
widgets.append(&mut self.options.fill.create_widgets( widgets.append(&mut self.options.fill.create_widgets(
"Fill", "Fill",
true, 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()), |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 }])) 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 { self.register_properties(responses, LayoutTarget::ToolOptions);
layout: self.properties(),
layout_target: LayoutTarget::ToolOptions,
});
return; 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::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::input_mapper::utility_types::misc::ActionKeys; 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::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::IconButton; use crate::messages::layout::utility_types::widget_prelude::*;
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::prelude::*; use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor; use crate::node_graph_executor::NodeGraphExecutor;
@ -144,29 +141,20 @@ impl DocumentToolData {
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) { pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
let layout = WidgetLayout::new(vec![ let layout = WidgetLayout::new(vec![
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput { widgets: vec![SwatchPairInput::new(self.primary_color, self.secondary_color).widget_holder()],
primary: self.primary_color,
secondary: self.secondary_color,
}))],
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: vec![ widgets: vec![
WidgetHolder::new(Widget::IconButton(IconButton { IconButton::new("Swap", 16)
size: 16, .tooltip("Swap")
icon: "Swap".into(), .tooltip_shortcut(action_keys!(ToolMessageDiscriminant::SwapColors))
tooltip: "Swap".into(), .on_update(|_| ToolMessage::SwapColors.into())
tooltip_shortcut: action_keys!(ToolMessageDiscriminant::SwapColors), .widget_holder(),
on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()), IconButton::new("WorkingColors", 16)
..Default::default() .tooltip("Reset")
})), .tooltip_shortcut(action_keys!(ToolMessageDiscriminant::ResetColors))
WidgetHolder::new(Widget::IconButton(IconButton { .on_update(|_| ToolMessage::ResetColors.into())
size: 16, .widget_holder(),
icon: "WorkingColors".into(),
tooltip: "Reset".into(),
tooltip_shortcut: action_keys!(ToolMessageDiscriminant::ResetColors),
on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()),
..Default::default()
})),
], ],
}, },
]); ]);
@ -259,36 +247,29 @@ impl PropertyHolder for ToolData {
.iter() .iter()
.map(|tool_group| tool_group.iter().map(|tool_availability| { .map(|tool_group| tool_group.iter().map(|tool_availability| {
match tool_availability { match tool_availability {
ToolAvailability::Available(tool) => ToolEntry { ToolAvailability::Available(tool) => ToolEntry::new( tool.tool_type(), tool.icon_name())
tooltip: tool.tooltip(), .tooltip( tool.tooltip())
tooltip_shortcut: action_keys!(tool_type_to_activate_tool_message(tool.tool_type())), .tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(tool.tool_type())))
icon_name: tool.icon_name(),
tool_type: tool.tool_type(), ,
},
ToolAvailability::ComingSoon(tool) => tool.clone(), ToolAvailability::ComingSoon(tool) => tool.clone(),
} }
}).collect::<Vec<_>>()) }).collect::<Vec<_>>())
.flat_map(|group| { .flat_map(|group| {
let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator { let separator = std::iter::once(Separator::new(SeparatorDirection::Vertical, SeparatorType::Section).widget_holder());
direction: SeparatorDirection::Vertical,
separator_type: SeparatorType::Section,
})));
let buttons = group.into_iter().map(|ToolEntry { tooltip, tooltip_shortcut, tool_type, icon_name }| { let buttons = group.into_iter().map(|ToolEntry { tooltip, tooltip_shortcut, tool_type, icon_name }| {
WidgetHolder::new(Widget::IconButton(IconButton { IconButton::new(icon_name, 32)
icon: icon_name, .disabled( false)
size: 32, .active( self.active_tool_type == tool_type)
disabled: false, .tooltip( tooltip.clone())
active: self.active_tool_type == tool_type, .tooltip_shortcut(tooltip_shortcut)
tooltip: tooltip.clone(), .on_update(move |_| {
tooltip_shortcut,
on_update: WidgetCallback::new(move |_| {
if !tooltip.contains("Coming Soon") { if !tooltip.contains("Coming Soon") {
ToolMessage::ActivateTool { tool_type }.into() ToolMessage::ActivateTool { tool_type }.into()
} else { } else {
DialogMessage::RequestComingSoonDialog { issue: None }.into() DialogMessage::RequestComingSoonDialog { issue: None }.into()
} }
}), }).widget_holder()
}))
}); });
separator.chain(buttons) 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 { pub struct ToolEntry {
#[widget_builder(constructor)]
pub tool_type: ToolType,
#[widget_builder(constructor)]
pub icon_name: String,
pub tooltip: String, pub tooltip: String,
pub tooltip_shortcut: Option<ActionKeys>, pub tooltip_shortcut: Option<ActionKeys>,
pub icon_name: String,
pub tool_type: ToolType,
} }
#[derive(Debug)] #[derive(Debug)]
@ -346,9 +330,10 @@ impl ToolFsmState {
} }
#[repr(usize)] #[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 { pub enum ToolType {
// General tool group // General tool group
#[default]
Select, Select,
Artboard, Artboard,
Navigate, Navigate,
@ -412,36 +397,11 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
ToolAvailability::Available(Box::<frame_tool::FrameTool>::default()), ToolAvailability::Available(Box::<frame_tool::FrameTool>::default()),
ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()), ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
ToolAvailability::Available(Box::<brush_tool::BrushTool>::default()), ToolAvailability::Available(Box::<brush_tool::BrushTool>::default()),
ToolAvailability::ComingSoon(ToolEntry { ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Heal, "RasterHealTool").tooltip("Coming Soon: Heal Tool (J)")),
tool_type: ToolType::Heal, ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Clone, "RasterCloneTool").tooltip("Coming Soon: Clone Tool (C)")),
icon_name: "RasterHealTool".into(), ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool").tooltip("Coming Soon: Patch Tool")),
tooltip: "Coming Soon: Heal Tool (J)".into(), ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Detail, "RasterDetailTool").tooltip("Coming Soon: Detail Tool (D)")),
tooltip_shortcut: None, ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Relight, "RasterRelightTool").tooltip("Coming Soon: Relight Tool (O)")),
}),
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,
}),
], ],
] ]
} }