mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 21:08:12 +08:00
Split out pen tool options into another file
This commit is contained in:
@@ -5,7 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
|
use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
|
||||||
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
|
use crate::messages::tool::tool_messages::select_tool::options::SelectOptionsUpdate;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
|||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
@@ -15,19 +14,20 @@ use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRos
|
|||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||||
use crate::messages::tool::common_functionality::measure;
|
use crate::messages::tool::common_functionality::measure;
|
||||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};
|
||||||
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
||||||
|
use crate::messages::tool::tool_messages::select_tool::options::NestedSelectionBehavior;
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::DMat2;
|
use glam::DMat2;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::path_bool::BooleanOperation;
|
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
use std::fmt;
|
|
||||||
|
pub mod options;
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct SelectTool {
|
pub struct SelectTool {
|
||||||
@@ -35,36 +35,6 @@ pub struct SelectTool {
|
|||||||
tool_data: SelectToolData,
|
tool_data: SelectToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SelectOptions {
|
|
||||||
nested_selection_behavior: NestedSelectionBehavior,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub enum SelectOptionsUpdate {
|
|
||||||
NestedSelectionBehavior(NestedSelectionBehavior),
|
|
||||||
PivotGizmoType(PivotGizmoType),
|
|
||||||
TogglePivotGizmoType(bool),
|
|
||||||
TogglePivotPinned,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub enum NestedSelectionBehavior {
|
|
||||||
#[default]
|
|
||||||
Shallowest,
|
|
||||||
Deepest,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for NestedSelectionBehavior {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
NestedSelectionBehavior::Deepest => write!(f, "Deep Select"),
|
|
||||||
NestedSelectionBehavior::Shallowest => write!(f, "Shallow Select"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct SelectToolPointerKeys {
|
pub struct SelectToolPointerKeys {
|
||||||
pub axis_align: Key,
|
pub axis_align: Key,
|
||||||
@@ -95,7 +65,7 @@ pub enum SelectToolMessage {
|
|||||||
Enter,
|
Enter,
|
||||||
PointerMove(SelectToolPointerKeys),
|
PointerMove(SelectToolPointerKeys),
|
||||||
PointerOutsideViewport(SelectToolPointerKeys),
|
PointerOutsideViewport(SelectToolPointerKeys),
|
||||||
SelectOptions(SelectOptionsUpdate),
|
SelectOptions(options::SelectOptionsUpdate),
|
||||||
SetPivot {
|
SetPivot {
|
||||||
position: ReferencePoint,
|
position: ReferencePoint,
|
||||||
},
|
},
|
||||||
@@ -121,200 +91,19 @@ impl ToolMetadata for SelectTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectTool {
|
|
||||||
fn deep_selection_widget(&self) -> WidgetHolder {
|
|
||||||
let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]
|
|
||||||
.iter()
|
|
||||||
.map(|mode| {
|
|
||||||
MenuListEntry::new(format!("{mode:?}"))
|
|
||||||
.label(mode.to_string())
|
|
||||||
.on_commit(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
DropdownInput::new(vec![layer_selection_behavior_entries])
|
|
||||||
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
|
|
||||||
.tooltip(
|
|
||||||
"Selection Mode\n\
|
|
||||||
\n\
|
|
||||||
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
|
|
||||||
Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
|
|
||||||
)
|
|
||||||
.widget_holder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alignment_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
|
|
||||||
[AlignAxis::X, AlignAxis::Y]
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|axis| [(axis, AlignAggregate::Min), (axis, AlignAggregate::Center), (axis, AlignAggregate::Max)])
|
|
||||||
.map(move |(axis, aggregate)| {
|
|
||||||
let (icon, tooltip) = match (axis, aggregate) {
|
|
||||||
(AlignAxis::X, AlignAggregate::Min) => ("AlignLeft", "Align Left"),
|
|
||||||
(AlignAxis::X, AlignAggregate::Center) => ("AlignHorizontalCenter", "Align Horizontal Center"),
|
|
||||||
(AlignAxis::X, AlignAggregate::Max) => ("AlignRight", "Align Right"),
|
|
||||||
(AlignAxis::Y, AlignAggregate::Min) => ("AlignTop", "Align Top"),
|
|
||||||
(AlignAxis::Y, AlignAggregate::Center) => ("AlignVerticalCenter", "Align Vertical Center"),
|
|
||||||
(AlignAxis::Y, AlignAggregate::Max) => ("AlignBottom", "Align Bottom"),
|
|
||||||
};
|
|
||||||
IconButton::new(icon, 24)
|
|
||||||
.tooltip(tooltip)
|
|
||||||
.on_update(move |_| DocumentMessage::AlignSelectedLayers { axis, aggregate }.into())
|
|
||||||
.disabled(disabled)
|
|
||||||
.widget_holder()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flip_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
|
|
||||||
[(FlipAxis::X, "Horizontal"), (FlipAxis::Y, "Vertical")].into_iter().map(move |(flip_axis, name)| {
|
|
||||||
IconButton::new("Flip".to_string() + name, 24)
|
|
||||||
.tooltip("Flip ".to_string() + name)
|
|
||||||
.on_update(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into())
|
|
||||||
.disabled(disabled)
|
|
||||||
.widget_holder()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn turn_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
|
|
||||||
[(-90., "TurnNegative90", "Turn -90°"), (90., "TurnPositive90", "Turn 90°")]
|
|
||||||
.into_iter()
|
|
||||||
.map(move |(degrees, icon, name)| {
|
|
||||||
IconButton::new(icon, 24)
|
|
||||||
.tooltip(name)
|
|
||||||
.on_update(move |_| DocumentMessage::RotateSelectedLayers { degrees }.into())
|
|
||||||
.disabled(disabled)
|
|
||||||
.widget_holder()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
|
|
||||||
let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
|
|
||||||
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
|
|
||||||
let mut tooltip = info.label.to_string();
|
|
||||||
if let Some(doc) = info.docstring.as_deref() {
|
|
||||||
tooltip.push_str("\n\n");
|
|
||||||
tooltip.push_str(doc);
|
|
||||||
}
|
|
||||||
IconButton::new(info.icon.as_deref().unwrap(), 24)
|
|
||||||
.tooltip(tooltip)
|
|
||||||
.disabled(selected_count == 0)
|
|
||||||
.on_update(move |_| {
|
|
||||||
let group_folder_type = GroupFolderType::BooleanOperation(*operation);
|
|
||||||
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
|
|
||||||
})
|
|
||||||
.widget_holder()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutHolder for SelectTool {
|
|
||||||
fn layout(&self) -> Layout {
|
|
||||||
let mut widgets = Vec::new();
|
|
||||||
|
|
||||||
// Select mode (Deep/Shallow)
|
|
||||||
widgets.push(self.deep_selection_widget());
|
|
||||||
|
|
||||||
// Pivot gizmo type (checkbox + dropdown for pivot/origin)
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
|
|
||||||
|
|
||||||
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
|
|
||||||
// Nine-position reference point widget
|
|
||||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
|
||||||
widgets.push(pivot_reference_point_widget(
|
|
||||||
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
|
||||||
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
|
||||||
PivotToolSource::Select,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Pivot pin button
|
|
||||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
|
||||||
|
|
||||||
let pin_active = self.tool_data.pivot_gizmo.pin_active();
|
|
||||||
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
|
|
||||||
|
|
||||||
if pin_active || pin_enabled {
|
|
||||||
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align
|
|
||||||
let disabled = self.tool_data.selected_layers_count < 2;
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
widgets.extend(self.alignment_widgets(disabled));
|
|
||||||
// widgets.push(
|
|
||||||
// PopoverButton::new()
|
|
||||||
// .popover_layout(vec![
|
|
||||||
// LayoutGroup::Row {
|
|
||||||
// widgets: vec![TextLabel::new("Align").bold(true).widget_holder()],
|
|
||||||
// },
|
|
||||||
// LayoutGroup::Row {
|
|
||||||
// widgets: vec![TextLabel::new("Coming soon").widget_holder()],
|
|
||||||
// },
|
|
||||||
// ])
|
|
||||||
// .disabled(disabled)
|
|
||||||
// .widget_holder(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Flip
|
|
||||||
let disabled = self.tool_data.selected_layers_count == 0;
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
widgets.extend(self.flip_widgets(disabled));
|
|
||||||
|
|
||||||
// Turn
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
widgets.extend(self.turn_widgets(disabled));
|
|
||||||
|
|
||||||
// Boolean
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));
|
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SelectTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SelectTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||||
let mut redraw_reference_pivot = false;
|
|
||||||
|
|
||||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
|
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
|
||||||
match option_update {
|
self.update_tool_options(option_update, responses);
|
||||||
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
|
|
||||||
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
|
|
||||||
responses.add(ToolMessage::UpdateHints);
|
|
||||||
}
|
|
||||||
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
|
||||||
if !self.tool_data.pivot_gizmo.state.disabled {
|
|
||||||
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
|
|
||||||
responses.add(ToolMessage::UpdateHints);
|
|
||||||
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
|
||||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
redraw_reference_pivot = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
|
|
||||||
self.tool_data.pivot_gizmo.state.disabled = !state;
|
|
||||||
responses.add(ToolMessage::UpdateHints);
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
redraw_reference_pivot = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectOptionsUpdate::TogglePivotPinned => {
|
|
||||||
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
|
||||||
responses.add(ToolMessage::UpdateHints);
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
redraw_reference_pivot = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, false);
|
self.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, false);
|
||||||
|
|
||||||
if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {
|
if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.pivot_changed {
|
||||||
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
|
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
|
||||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||||
self.tool_data.selected_layers_changed = false;
|
self.tool_data.pivot_changed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +116,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Sele
|
|||||||
);
|
);
|
||||||
|
|
||||||
let additional = match self.fsm_state {
|
let additional = match self.fsm_state {
|
||||||
SelectToolFsmState::Ready { .. } => actions!(SelectToolMessageDiscriminant; DragStart),
|
SelectToolFsmState::Ready => actions!(SelectToolMessageDiscriminant; DragStart),
|
||||||
_ => actions!(SelectToolMessageDiscriminant; DragStop),
|
_ => actions!(SelectToolMessageDiscriminant; DragStop),
|
||||||
};
|
};
|
||||||
common.extend(additional);
|
common.extend(additional);
|
||||||
@@ -348,9 +137,7 @@ impl ToolTransition for SelectTool {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
enum SelectToolFsmState {
|
enum SelectToolFsmState {
|
||||||
Ready {
|
Ready,
|
||||||
selection: NestedSelectionBehavior,
|
|
||||||
},
|
|
||||||
Drawing {
|
Drawing {
|
||||||
selection_shape: SelectionShapeType,
|
selection_shape: SelectionShapeType,
|
||||||
has_drawn: bool,
|
has_drawn: bool,
|
||||||
@@ -372,8 +159,7 @@ enum SelectToolFsmState {
|
|||||||
|
|
||||||
impl Default for SelectToolFsmState {
|
impl Default for SelectToolFsmState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let selection = NestedSelectionBehavior::Deepest;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,9 +184,9 @@ struct SelectToolData {
|
|||||||
compass_rose: CompassRose,
|
compass_rose: CompassRose,
|
||||||
line_center: DVec2,
|
line_center: DVec2,
|
||||||
skew_edge: EdgeBool,
|
skew_edge: EdgeBool,
|
||||||
nested_selection_behavior: NestedSelectionBehavior,
|
nested_selection_behavior: options::NestedSelectionBehavior,
|
||||||
selected_layers_count: usize,
|
selected_layers_count: usize,
|
||||||
selected_layers_changed: bool,
|
pivot_changed: bool,
|
||||||
snap_candidates: Vec<SnapCandidatePoint>,
|
snap_candidates: Vec<SnapCandidatePoint>,
|
||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
}
|
}
|
||||||
@@ -593,7 +379,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||||
|
|
||||||
let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count();
|
let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count();
|
||||||
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
tool_data.pivot_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||||
tool_data.selected_layers_count = selected_layers_count;
|
tool_data.selected_layers_count = selected_layers_count;
|
||||||
|
|
||||||
// Outline selected layers, but not artboards
|
// Outline selected layers, but not artboards
|
||||||
@@ -996,7 +782,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
SelectToolFsmState::Ready { .. },
|
SelectToolFsmState::Ready,
|
||||||
SelectToolMessage::DragStart {
|
SelectToolMessage::DragStart {
|
||||||
extend_selection,
|
extend_selection,
|
||||||
remove_from_selection,
|
remove_from_selection,
|
||||||
@@ -1128,8 +914,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::Abort) => {
|
(SelectToolFsmState::DraggingPivot, SelectToolMessage::Abort) => {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
@@ -1287,7 +1072,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
|
|
||||||
SelectToolFsmState::Drawing { selection_shape, has_drawn: true }
|
SelectToolFsmState::Drawing { selection_shape, has_drawn: true }
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => {
|
(SelectToolFsmState::Ready, SelectToolMessage::PointerMove(_)) => {
|
||||||
let dragging_bounds = tool_data
|
let dragging_bounds = tool_data
|
||||||
.bounding_box_manager
|
.bounding_box_manager
|
||||||
.as_mut()
|
.as_mut()
|
||||||
@@ -1312,8 +1097,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
SelectToolFsmState::Dragging {
|
SelectToolFsmState::Dragging {
|
||||||
@@ -1451,8 +1235,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
||||||
@@ -1475,8 +1258,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
|
(SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
|
||||||
let quad = tool_data.selection_quad();
|
let quad = tool_data.selection_quad();
|
||||||
@@ -1549,10 +1331,9 @@ impl Fsm for SelectToolFsmState {
|
|||||||
|
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::Enter) => {
|
(SelectToolFsmState::Ready, SelectToolMessage::Enter) => {
|
||||||
let selected_nodes = document.network_interface.selected_nodes();
|
let selected_nodes = document.network_interface.selected_nodes();
|
||||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||||
|
|
||||||
@@ -1564,8 +1345,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Dragging { .. }, SelectToolMessage::Abort) => {
|
(SelectToolFsmState::Dragging { .. }, SelectToolMessage::Abort) => {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
@@ -1574,8 +1354,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
tool_data.lasso_polygon.clear();
|
tool_data.lasso_polygon.clear();
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(_, SelectToolMessage::Abort) => {
|
(_, SelectToolMessage::Abort) => {
|
||||||
tool_data.layers_dragging.retain(|layer| {
|
tool_data.layers_dragging.retain(|layer| {
|
||||||
@@ -1595,8 +1374,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
tool_data.lasso_polygon.clear();
|
tool_data.lasso_polygon.clear();
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::Ready
|
||||||
SelectToolFsmState::Ready { selection }
|
|
||||||
}
|
}
|
||||||
(_, SelectToolMessage::SetPivot { position }) => {
|
(_, SelectToolMessage::SetPivot { position }) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
@@ -1650,13 +1428,14 @@ impl Fsm for SelectToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hints(&self, _: &Self::ToolData, _: &ToolActionMessageContext, _: &Self::ToolOptions, responses: &mut VecDeque<Message>) {
|
fn update_hints(&self, tool_data: &Self::ToolData, _: &ToolActionMessageContext, _: &Self::ToolOptions, responses: &mut VecDeque<Message>) {
|
||||||
match self {
|
match self {
|
||||||
SelectToolFsmState::Ready { selection } => {
|
SelectToolFsmState::Ready => {
|
||||||
|
let selection = tool_data.nested_selection_behavior;
|
||||||
let hint_data = HintData(vec![
|
let hint_data = HintData(vec![
|
||||||
HintGroup({
|
HintGroup({
|
||||||
let mut hints = vec![HintInfo::mouse(MouseMotion::Lmb, "Select Object"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()];
|
let mut hints = vec![HintInfo::mouse(MouseMotion::Lmb, "Select Object"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()];
|
||||||
if *selection == NestedSelectionBehavior::Shallowest {
|
if selection == NestedSelectionBehavior::Shallowest {
|
||||||
hints.extend([HintInfo::keys([Key::Accel], "Deepest").prepend_plus(), HintInfo::mouse(MouseMotion::LmbDouble, "Deepen")]);
|
hints.extend([HintInfo::keys([Key::Accel], "Deepest").prepend_plus(), HintInfo::mouse(MouseMotion::LmbDouble, "Deepen")]);
|
||||||
}
|
}
|
||||||
hints
|
hints
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
use super::super::tool_prelude::*;
|
||||||
|
use super::SelectTool;
|
||||||
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
||||||
|
use crate::messages::tool::common_functionality::pivot::{PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||||
|
use graphene_std::path_bool::BooleanOperation;
|
||||||
|
use graphene_std::vector::ReferencePoint;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum SelectOptionsUpdate {
|
||||||
|
NestedSelectionBehavior(NestedSelectionBehavior),
|
||||||
|
PivotGizmoType(PivotGizmoType),
|
||||||
|
TogglePivotGizmoType(bool),
|
||||||
|
TogglePivotPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum NestedSelectionBehavior {
|
||||||
|
#[default]
|
||||||
|
Shallowest,
|
||||||
|
Deepest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NestedSelectionBehavior {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
NestedSelectionBehavior::Deepest => write!(f, "Deep Select"),
|
||||||
|
NestedSelectionBehavior::Shallowest => write!(f, "Shallow Select"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectTool {
|
||||||
|
fn deep_selection_widget(&self) -> WidgetHolder {
|
||||||
|
let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]
|
||||||
|
.iter()
|
||||||
|
.map(|mode| {
|
||||||
|
MenuListEntry::new(format!("{mode:?}"))
|
||||||
|
.label(mode.to_string())
|
||||||
|
.on_commit(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
DropdownInput::new(vec![layer_selection_behavior_entries])
|
||||||
|
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
|
||||||
|
.tooltip(
|
||||||
|
"Selection Mode\n\
|
||||||
|
\n\
|
||||||
|
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
|
||||||
|
Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
|
||||||
|
)
|
||||||
|
.widget_holder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alignment_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||||
|
[AlignAxis::X, AlignAxis::Y]
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|axis| [(axis, AlignAggregate::Min), (axis, AlignAggregate::Center), (axis, AlignAggregate::Max)])
|
||||||
|
.map(move |(axis, aggregate)| {
|
||||||
|
let (icon, tooltip) = match (axis, aggregate) {
|
||||||
|
(AlignAxis::X, AlignAggregate::Min) => ("AlignLeft", "Align Left"),
|
||||||
|
(AlignAxis::X, AlignAggregate::Center) => ("AlignHorizontalCenter", "Align Horizontal Center"),
|
||||||
|
(AlignAxis::X, AlignAggregate::Max) => ("AlignRight", "Align Right"),
|
||||||
|
(AlignAxis::Y, AlignAggregate::Min) => ("AlignTop", "Align Top"),
|
||||||
|
(AlignAxis::Y, AlignAggregate::Center) => ("AlignVerticalCenter", "Align Vertical Center"),
|
||||||
|
(AlignAxis::Y, AlignAggregate::Max) => ("AlignBottom", "Align Bottom"),
|
||||||
|
};
|
||||||
|
IconButton::new(icon, 24)
|
||||||
|
.tooltip(tooltip)
|
||||||
|
.on_update(move |_| DocumentMessage::AlignSelectedLayers { axis, aggregate }.into())
|
||||||
|
.disabled(disabled)
|
||||||
|
.widget_holder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flip_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||||
|
[(FlipAxis::X, "Horizontal"), (FlipAxis::Y, "Vertical")].into_iter().map(move |(flip_axis, name)| {
|
||||||
|
IconButton::new("Flip".to_string() + name, 24)
|
||||||
|
.tooltip("Flip ".to_string() + name)
|
||||||
|
.on_update(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into())
|
||||||
|
.disabled(disabled)
|
||||||
|
.widget_holder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn turn_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||||
|
[(-90., "TurnNegative90", "Turn -90°"), (90., "TurnPositive90", "Turn 90°")]
|
||||||
|
.into_iter()
|
||||||
|
.map(move |(degrees, icon, name)| {
|
||||||
|
IconButton::new(icon, 24)
|
||||||
|
.tooltip(name)
|
||||||
|
.on_update(move |_| DocumentMessage::RotateSelectedLayers { degrees }.into())
|
||||||
|
.disabled(disabled)
|
||||||
|
.widget_holder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||||
|
let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
|
||||||
|
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
|
||||||
|
let mut tooltip = info.label.to_string();
|
||||||
|
if let Some(doc) = info.docstring.as_deref() {
|
||||||
|
tooltip.push_str("\n\n");
|
||||||
|
tooltip.push_str(doc);
|
||||||
|
}
|
||||||
|
IconButton::new(info.icon.as_deref().unwrap(), 24)
|
||||||
|
.tooltip(tooltip)
|
||||||
|
.disabled(selected_count == 0)
|
||||||
|
.on_update(move |_| {
|
||||||
|
let group_folder_type = GroupFolderType::BooleanOperation(*operation);
|
||||||
|
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
|
||||||
|
})
|
||||||
|
.widget_holder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_tool_options(&mut self, option_update: &SelectOptionsUpdate, responses: &mut VecDeque<Message>) {
|
||||||
|
match option_update {
|
||||||
|
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
|
||||||
|
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
}
|
||||||
|
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||||
|
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||||
|
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||||
|
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
self.tool_data.pivot_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||||
|
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
self.tool_data.pivot_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectOptionsUpdate::TogglePivotPinned => {
|
||||||
|
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
self.tool_data.pivot_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutHolder for SelectTool {
|
||||||
|
fn layout(&self) -> Layout {
|
||||||
|
let mut widgets = Vec::new();
|
||||||
|
|
||||||
|
// Select mode (Deep/Shallow)
|
||||||
|
widgets.push(self.deep_selection_widget());
|
||||||
|
|
||||||
|
// Pivot gizmo type (checkbox + dropdown for pivot/origin)
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
|
||||||
|
|
||||||
|
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||||
|
// Nine-position reference point widget
|
||||||
|
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||||
|
widgets.push(pivot_reference_point_widget(
|
||||||
|
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||||
|
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||||
|
PivotToolSource::Select,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Pivot pin button
|
||||||
|
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||||
|
|
||||||
|
let pin_active = self.tool_data.pivot_gizmo.pin_active();
|
||||||
|
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
|
||||||
|
|
||||||
|
if pin_active || pin_enabled {
|
||||||
|
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Align
|
||||||
|
let disabled = self.tool_data.selected_layers_count < 2;
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
widgets.extend(self.alignment_widgets(disabled));
|
||||||
|
// widgets.push(
|
||||||
|
// PopoverButton::new()
|
||||||
|
// .popover_layout(vec![
|
||||||
|
// LayoutGroup::Row {
|
||||||
|
// widgets: vec![TextLabel::new("Align").bold(true).widget_holder()],
|
||||||
|
// },
|
||||||
|
// LayoutGroup::Row {
|
||||||
|
// widgets: vec![TextLabel::new("Coming soon").widget_holder()],
|
||||||
|
// },
|
||||||
|
// ])
|
||||||
|
// .disabled(disabled)
|
||||||
|
// .widget_holder(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Flip
|
||||||
|
let disabled = self.tool_data.selected_layers_count == 0;
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
widgets.extend(self.flip_widgets(disabled));
|
||||||
|
|
||||||
|
// Turn
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
widgets.extend(self.turn_widgets(disabled));
|
||||||
|
|
||||||
|
// Boolean
|
||||||
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));
|
||||||
|
|
||||||
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user