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