Reduce tool code duplication and boilerplate (#993)

* Reduce tool code duplication

* Add doc comments

* Fix Mac key variant, which can't be auto-converted without false positives

* Rename "key" to "keys"

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-01-28 08:38:52 +00:00
committed by Keavon Chambers
parent 0531df18d5
commit 0a03aec343
21 changed files with 480 additions and 1473 deletions

View File

@@ -423,6 +423,10 @@ impl<const LENGTH: usize> BitVector<LENGTH> {
(self.0[offset] & bit) != 0
}
pub fn key(&self, key: Key) -> bool {
self.get(key as usize)
}
pub fn is_empty(&self) -> bool {
let mut result = 0;

View File

@@ -1,6 +1,10 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::prelude::*;
use bitflags::bitflags;
use glam::DVec2;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
// Origin is top left
pub type ViewportPosition = DVec2;
@@ -85,6 +89,13 @@ impl MouseState {
scroll_delta: ScrollDelta::default(),
}
}
pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque<Message>) {
match drag_start.distance(self.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]

View File

@@ -1,12 +1,14 @@
use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
use graphite_proc_macros::WidgetBuilder;
use derivative::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder)]
#[derivative(Debug, PartialEq)]
pub struct PivotAssist {
#[widget_builder(constructor)]
pub position: PivotPosition,
pub disabled: bool,

View File

@@ -1,7 +1,7 @@
use crate::application::generate_uuid;
use crate::consts::SELECTION_TOLERANCE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::prelude::*;
@@ -59,23 +59,8 @@ impl ToolMetadata for ArtboardTool {
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into());
return;
}
let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
}
fn process_message(&mut self, message: ToolMessage, transition_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
self.fsm_state.process_event(message, &mut self.data, transition_data, &(), responses, false);
}
advertise_actions!(ArtboardToolMessageDiscriminant;
@@ -100,20 +85,15 @@ impl ToolTransition for ArtboardTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum ArtboardToolFsmState {
#[default]
Ready,
Drawing,
ResizingBounds,
Dragging,
}
impl Default for ArtboardToolFsmState {
fn default() -> Self {
ArtboardToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct ArtboardToolData {
bounding_box_overlays: Option<BoundingBoxOverlays>,
@@ -138,14 +118,9 @@ impl Fsm for ArtboardToolFsmState {
) -> Self {
if let ToolMessage::Artboard(event) = event {
match (self, event) {
(ArtboardToolFsmState::Ready | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::DocumentIsDirty) => {
match (
tool_data
.selected_artboard
.map(|path| document.artboard_bounding_box_and_transform(&[path], font_cache))
.unwrap_or(None),
tool_data.bounding_box_overlays.take(),
) {
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
let current_artboard = tool_data.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], font_cache));
match (current_artboard, tool_data.bounding_box_overlays.take()) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
(Some((bounds, transform)), paths) => {
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
@@ -443,51 +418,14 @@ impl Fsm for ArtboardToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
ArtboardToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Artboard"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Move Artboard"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Backspace]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Delete Artboard"),
plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Artboard")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Move Artboard")]),
HintGroup(vec![HintInfo::keys([Key::Backspace], "Delete Artboard")]),
]),
ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain to Axis"),
plus: false,
}])]),
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
])]),
ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")])]),
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => {
HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])])
}
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,6 +1,5 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize;
@@ -52,23 +51,7 @@ impl PropertyHolder for EllipseTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EllipseTool {
fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.data, data, &(), responses, true);
}
fn actions(&self) -> ActionList {
@@ -97,18 +80,13 @@ impl ToolTransition for EllipseTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum EllipseToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for EllipseToolFsmState {
fn default() -> Self {
EllipseToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct EllipseToolData {
data: Resize,
@@ -159,12 +137,9 @@ impl Fsm for EllipseToolFsmState {
state
}
(Drawing, DragStop) => {
match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
@@ -183,44 +158,11 @@ impl Fsm for EllipseToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Ellipse"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Circular"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
])]),
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Circular"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"),
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,5 +1,5 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@@ -45,23 +45,7 @@ impl PropertyHolder for EyedropperTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.data, data, &(), responses, true);
}
advertise_actions!(EyedropperToolMessageDiscriminant;
@@ -84,19 +68,14 @@ impl ToolTransition for EyedropperTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum EyedropperToolFsmState {
#[default]
Ready,
SamplingPrimary,
SamplingSecondary,
}
impl Default for EyedropperToolFsmState {
fn default() -> Self {
EyedropperToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct EyedropperToolData {}
@@ -162,28 +141,10 @@ impl Fsm for EyedropperToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
EyedropperToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Sample to Primary"),
plus: false,
},
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Rmb),
label: String::from("Sample to Secondary"),
plus: false,
},
HintInfo::mouse(MouseMotion::Lmb, "Sample to Primary"),
HintInfo::mouse(MouseMotion::Rmb, "Sample to Secondary"),
])]),
EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Escape]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Cancel"),
plus: false,
}])]),
EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Escape], "Cancel")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -48,23 +48,7 @@ impl PropertyHolder for FillTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.data, data, &(), responses, true);
}
advertise_actions!(FillToolMessageDiscriminant;
@@ -83,17 +67,12 @@ impl ToolTransition for FillTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum FillToolFsmState {
#[default]
Ready,
}
impl Default for FillToolFsmState {
fn default() -> Self {
FillToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct FillToolData {}
@@ -144,20 +123,8 @@ impl Fsm for FillToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
FillToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Fill with Primary"),
plus: false,
},
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Rmb),
label: String::from("Fill with Secondary"),
plus: false,
},
HintInfo::mouse(MouseMotion::Lmb, "Fill with Primary"),
HintInfo::mouse(MouseMotion::Rmb, "Fill with Secondary"),
])]),
};

View File

@@ -1,6 +1,6 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@@ -51,8 +51,9 @@ pub enum FreehandToolMessageOptionsUpdate {
LineWeight(f64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum FreehandToolFsmState {
#[default]
Ready,
Drawing,
}
@@ -71,32 +72,18 @@ impl ToolMetadata for FreehandTool {
impl PropertyHolder for FreehandTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: Some(self.options.line_weight as f64),
is_integer: false,
min: Some(1.),
on_update: WidgetCallback::new(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}]))
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(1.)
.on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message {
match action {
FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -104,13 +91,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool
return;
}
let new_state = self.fsm_state.transition(message, &mut self.data, data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.data, data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@@ -141,11 +122,6 @@ impl ToolTransition for FreehandTool {
}
}
impl Default for FreehandToolFsmState {
fn default() -> Self {
FreehandToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct FreehandToolData {
points: Vec<DVec2>,
@@ -222,13 +198,7 @@ impl Fsm for FreehandToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Polyline"),
plus: false,
}])]),
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline")])]),
FreehandToolFsmState::Drawing => HintData(vec![]),
};

View File

@@ -1,8 +1,8 @@
use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, DRAG_THRESHOLD, 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::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::{RadioEntryData, RadioInput};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager;
@@ -78,15 +78,6 @@ impl ToolMetadata for GradientTool {
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message {
match action {
GradientOptionsUpdate::Type(gradient_type) => {
@@ -100,12 +91,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool
return;
}
let new_state = self.fsm_state.transition(message, &mut self.data, data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
}
self.fsm_state.process_event(message, &mut self.data, data, &self.options, responses, false);
}
advertise_actions!(GradientToolMessageDiscriminant;
@@ -120,47 +106,29 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool
impl PropertyHolder for GradientTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::RadioInput(RadioInput {
selected_index: if self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial {
1
} else {
0
},
entries: vec![
RadioEntryData {
value: "linear".into(),
label: "Linear".into(),
tooltip: "Linear Gradient".into(),
on_update: WidgetCallback::new(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
..RadioEntryData::default()
},
RadioEntryData {
value: "radial".into(),
label: "Radial".into(),
tooltip: "Radial Gradient".into(),
on_update: WidgetCallback::new(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
..RadioEntryData::default()
},
],
..Default::default()
}))],
}]))
let gradient_type = RadioInput::new(vec![
RadioEntryData::new("Linear")
.value("linear")
.tooltip("Linear Gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
RadioEntryData::new("Radial")
.value("radial")
.tooltip("Radial Gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
])
.selected_index((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32)
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![gradient_type] }]))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum GradientToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for GradientToolFsmState {
fn default() -> Self {
GradientToolFsmState::Ready
}
}
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, font_cache: &FontCache) -> DAffine2 {
let bounds = layer.aabb_for_transform(DAffine2::IDENTITY, font_cache).unwrap();
@@ -638,14 +606,7 @@ impl Fsm for GradientToolFsmState {
}
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
match tool_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD {
true => {
responses.push_back(DocumentMessage::AbortTransaction.into());
responses.push_back(GradientToolMessage::DocumentIsDirty.into());
}
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.snap_manager.cleanup(responses);
GradientToolFsmState::Ready
@@ -669,28 +630,10 @@ impl Fsm for GradientToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
GradientToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Gradient"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: true,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Gradient"),
HintInfo::keys([Key::Shift], "Snap 15°").prepend_plus(),
])]),
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
}])]),
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,8 +1,7 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::portfolio::document::node_graph::{NodeImplementation, IMAGINATE_NODE};
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@@ -40,23 +39,7 @@ impl PropertyHolder for ImaginateTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ImaginateTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
fn actions(&self) -> ActionList {
@@ -97,17 +80,13 @@ impl ToolTransition for ImaginateTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum ImaginateToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for ImaginateToolFsmState {
fn default() -> Self {
ImaginateToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct ImaginateToolData {
data: Resize,
@@ -178,11 +157,7 @@ impl Fsm for ImaginateToolFsmState {
state
}
(Drawing, DragStop) => {
match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
responses.push_back(NodeGraphMessage::SetDrawing { new_drawing: false }.into());
shape_data.cleanup(responses);
@@ -207,44 +182,11 @@ impl Fsm for ImaginateToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
ImaginateToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Repaint Frame"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
])]),
ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"),
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,8 +1,8 @@
use crate::consts::{DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE};
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, 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::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager;
@@ -72,32 +72,18 @@ impl ToolMetadata for LineTool {
impl PropertyHolder for LineTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: Some(self.options.line_weight as f64),
is_integer: false,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}]))
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
fn process_message(&mut self, message: ToolMessage, transition_data: ToolActionHandlerData<'a>, messages: &mut VecDeque<Message>) {
if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message {
match action {
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -105,27 +91,13 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, transition_data, &self.options, messages, true);
}
fn actions(&self) -> ActionList {
use LineToolFsmState::*;
match self.fsm_state {
Ready => actions!(LineToolMessageDiscriminant;
DragStart,
),
Drawing => actions!(LineToolMessageDiscriminant;
DragStop,
Redraw,
Abort,
),
LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart),
LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, Redraw, Abort),
}
}
}
@@ -140,18 +112,13 @@ impl ToolTransition for LineTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
enum LineToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for LineToolFsmState {
fn default() -> Self {
LineToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct LineToolData {
drag_start: ViewportPosition,
@@ -205,20 +172,14 @@ impl Fsm for LineToolFsmState {
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect();
responses.push_back(generate_transform(tool_data, values[0], values[1], values[2]));
let keyboard = &input.keyboard;
responses.push_back(generate_transform(tool_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
Drawing
}
(Drawing, DragStop) => {
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
tool_data.snap_manager.cleanup(responses);
match tool_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.path = None;
Ready
@@ -239,57 +200,15 @@ impl Fsm for LineToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
LineToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Line"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Lock Angle"),
plus: true,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"),
HintInfo::keys([Key::Shift], "Snap 15°").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(),
])]),
LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Lock Angle"),
plus: false,
},
HintInfo::keys([Key::Shift], "Snap 15°"),
HintInfo::keys([Key::Alt], "From Center"),
HintInfo::keys([Key::Control], "Lock Angle"),
])]),
};
@@ -301,40 +220,38 @@ impl Fsm for LineToolFsmState {
}
}
fn generate_transform(tool_data: &mut LineToolData, lock: bool, snap: bool, center: bool) -> Message {
fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle: bool, center: bool) -> Message {
let mut start = tool_data.drag_start;
let stop = tool_data.drag_current;
let line_vector = tool_data.drag_current - start;
let dir = stop - start;
let mut angle = -line_vector.angle_between(DVec2::X);
let mut angle = -dir.angle_between(DVec2::X);
if lock_angle {
angle = tool_data.angle;
}
if lock {
angle = tool_data.angle
};
if snap {
if snap_angle {
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
angle = (angle / snap_resolution).round() * snap_resolution;
}
tool_data.angle = angle;
let mut scale = dir.length();
let mut line_length = line_vector.length();
if lock {
if lock_angle {
let angle_vec = DVec2::new(angle.cos(), angle.sin());
scale = dir.dot(angle_vec);
line_length = line_vector.dot(angle_vec);
}
if center {
start -= scale * DVec2::new(angle.cos(), angle.sin());
scale *= 2.;
start -= line_length * DVec2::new(angle.cos(), angle.sin());
line_length *= 2.;
}
Operation::SetLayerTransformInViewport {
path: tool_data.path.clone().unwrap(),
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(scale, 1.), angle, start).to_cols_array(),
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start).to_cols_array(),
}
.into()
}

View File

@@ -1,5 +1,5 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@@ -52,23 +52,7 @@ impl PropertyHolder for NavigateTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
fn actions(&self) -> ActionList {
@@ -99,20 +83,15 @@ impl ToolTransition for NavigateTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum NavigateToolFsmState {
#[default]
Ready,
Panning,
Tilting,
Zooming,
}
impl Default for NavigateToolFsmState {
fn default() -> Self {
NavigateToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct NavigateToolData {
drag_start: DVec2,
@@ -192,76 +171,13 @@ impl Fsm for NavigateToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
NavigateToolFsmState::Ready => HintData(vec![
HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Zoom In"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Zoom Out"),
plus: true,
},
]),
HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Zoom"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap Increments"),
plus: true,
},
]),
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::MmbDrag),
label: String::from("Pan"),
plus: false,
}]),
HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::RmbDrag),
label: String::from("Tilt"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: true,
},
]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Zoom In"), HintInfo::keys([Key::Shift], "Zoom Out").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Zoom"), HintInfo::keys([Key::Control], "Snap Increments").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::MmbDrag, "Pan")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::RmbDrag, "Tilt"), HintInfo::keys([Key::Control], "Snap 15°").prepend_plus()]),
]),
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
}])]),
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap Increments"),
plus: false,
}])]),
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]),
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap Increments")])]),
_ => HintData(Vec::new()),
};

View File

@@ -1,6 +1,5 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize;
@@ -39,23 +38,7 @@ impl PropertyHolder for NodeGraphFrameTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NodeGraphFrameTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
fn actions(&self) -> ActionList {
@@ -96,17 +79,13 @@ impl ToolTransition for NodeGraphFrameTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum NodeGraphToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for NodeGraphToolFsmState {
fn default() -> Self {
NodeGraphToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct NodeGraphToolData {
data: Resize,
@@ -160,11 +139,7 @@ impl Fsm for NodeGraphToolFsmState {
state
}
(Drawing, DragStop) => {
match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
responses.push_back(NodeGraphMessage::SetDrawing { new_drawing: false }.into());
shape_data.cleanup(responses);
@@ -188,44 +163,11 @@ impl Fsm for NodeGraphToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
NodeGraphToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Repaint Frame"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
])]),
NodeGraphToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"),
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
NodeGraphToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,6 +1,6 @@
use crate::consts::{SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
@@ -62,23 +62,7 @@ impl PropertyHolder for PathTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
// Different actions depending on state may be wanted:
@@ -111,18 +95,13 @@ impl ToolTransition for PathTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum PathToolFsmState {
#[default]
Ready,
Dragging,
}
impl Default for PathToolFsmState {
fn default() -> Self {
PathToolFsmState::Ready
}
}
#[derive(Default)]
struct PathToolData {
shape_editor: ShapeEditor,
@@ -309,88 +288,23 @@ impl Fsm for PathToolFsmState {
let hint_data = match self {
PathToolFsmState::Ready => HintData(vec![
HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Select Point"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grow/Shrink Selection"),
plus: true,
},
HintInfo::mouse(MouseMotion::Lmb, "Select Point"),
HintInfo::keys([Key::Shift], "Grow/Shrink Selection").prepend_plus(),
]),
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Drag Selected"),
plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
HintGroup(vec![
HintInfo {
key_groups: vec![
KeysGroup(vec![Key::ArrowUp]).into(),
KeysGroup(vec![Key::ArrowRight]).into(),
KeysGroup(vec![Key::ArrowDown]).into(),
KeysGroup(vec![Key::ArrowLeft]).into(),
],
key_groups_mac: None,
mouse: None,
label: String::from("Nudge Selected (coming soon)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Big Increment Nudge"),
plus: true,
},
HintInfo::arrow_keys("Nudge Selected (coming soon)"),
HintInfo::keys([Key::Shift], "Big Increment Nudge").prepend_plus(),
]),
HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyG]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grab Selected (coming soon)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyR]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Rotate Selected (coming soon)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyS]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Scale Selected (coming soon)"),
plus: false,
},
HintInfo::keys([Key::KeyG], "Grab Selected (coming soon)"),
HintInfo::keys([Key::KeyR], "Rotate Selected (coming soon)"),
HintInfo::keys([Key::KeyS], "Scale Selected (coming soon)"),
]),
]),
PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Split/Align Handles (Toggle)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Share Lengths of Aligned Handles"),
plus: false,
},
HintInfo::keys([Key::Alt], "Split/Align Handles (Toggle)"),
HintInfo::keys([Key::Shift], "Share Lengths of Aligned Handles"),
])]),
};

View File

@@ -1,7 +1,7 @@
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
@@ -59,8 +59,9 @@ pub enum PenToolMessage {
UpdateOptions(PenOptionsUpdate),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum PenToolFsmState {
#[default]
Ready,
DraggingHandle,
PlacingAnchor,
@@ -86,32 +87,18 @@ impl ToolMetadata for PenTool {
impl PropertyHolder for PenTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: Some(self.options.line_weight),
is_integer: false,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}]))
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message {
match action {
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -119,13 +106,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@@ -158,11 +139,6 @@ impl ToolTransition for PenTool {
}
}
impl Default for PenToolFsmState {
fn default() -> Self {
PenToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct PenToolData {
weight: f64,
@@ -614,49 +590,13 @@ impl Fsm for PenToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Draw Path"),
plus: false,
}])]),
PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Path")])]),
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Add Handle"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Add Anchor"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Break Handle"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Enter]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("End Path"),
plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Add Handle")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Anchor")]),
HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Break Handle")]),
HintGroup(vec![HintInfo::keys([Key::Enter], "End Path")]),
]),
};

View File

@@ -1,6 +1,5 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize;
@@ -40,23 +39,7 @@ impl PropertyHolder for RectangleTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
}
fn actions(&self) -> ActionList {
@@ -97,17 +80,13 @@ impl ToolTransition for RectangleTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum RectangleToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for RectangleToolFsmState {
fn default() -> Self {
RectangleToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct RectangleToolData {
data: Resize,
@@ -158,11 +137,7 @@ impl Fsm for RectangleToolFsmState {
state
}
(Drawing, DragStop) => {
match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
@@ -184,44 +159,11 @@ impl Fsm for RectangleToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Rectangle"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
])]),
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain Square"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"),
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,9 +1,9 @@
use crate::application::generate_uuid;
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, 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::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::assist_widgets::{PivotAssist, PivotPosition};
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
@@ -85,182 +85,112 @@ impl PropertyHolder for SelectTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "AlignLeft".into(),
tooltip: "Align Left".into(),
size: 24,
on_update: WidgetCallback::new(|_| {
IconButton::new("AlignLeft", 24)
.tooltip("Align Left")
.on_update(|_| {
DocumentMessage::AlignSelectedLayers {
axis: AlignAxis::X,
aggregate: AlignAggregate::Min,
}
.into()
}),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "AlignHorizontalCenter".into(),
tooltip: "Align Horizontal Center".into(),
size: 24,
on_update: WidgetCallback::new(|_| {
})
.widget_holder(),
IconButton::new("AlignHorizontalCenter", 24)
.tooltip("Align Horizontal Center")
.on_update(|_| {
DocumentMessage::AlignSelectedLayers {
axis: AlignAxis::X,
aggregate: AlignAggregate::Center,
}
.into()
}),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "AlignRight".into(),
tooltip: "Align Right".into(),
size: 24,
on_update: WidgetCallback::new(|_| {
})
.widget_holder(),
IconButton::new("AlignRight", 24)
.tooltip("Align Right")
.on_update(|_| {
DocumentMessage::AlignSelectedLayers {
axis: AlignAxis::X,
aggregate: AlignAggregate::Max,
}
.into()
}),
..IconButton::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Unrelated,
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "AlignTop".into(),
tooltip: "Align Top".into(),
size: 24,
on_update: WidgetCallback::new(|_| {
})
.widget_holder(),
WidgetHolder::unrelated_separator(),
IconButton::new("AlignTop", 24)
.tooltip("Align Top")
.on_update(|_| {
DocumentMessage::AlignSelectedLayers {
axis: AlignAxis::Y,
aggregate: AlignAggregate::Min,
}
.into()
}),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "AlignVerticalCenter".into(),
tooltip: "Align Vertical Center".into(),
size: 24,
on_update: WidgetCallback::new(|_| {
})
.widget_holder(),
IconButton::new("AlignVerticalCenter", 24)
.tooltip("Align Vertical Center")
.on_update(|_| {
DocumentMessage::AlignSelectedLayers {
axis: AlignAxis::Y,
aggregate: AlignAggregate::Center,
}
.into()
}),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "AlignBottom".into(),
tooltip: "Align Bottom".into(),
size: 24,
on_update: WidgetCallback::new(|_| {
})
.widget_holder(),
IconButton::new("AlignBottom", 24)
.tooltip("Align Bottom")
.on_update(|_| {
DocumentMessage::AlignSelectedLayers {
axis: AlignAxis::Y,
aggregate: AlignAggregate::Max,
}
.into()
}),
..IconButton::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Related,
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Align".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Section,
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "FlipHorizontal".into(),
tooltip: "Flip Horizontal".into(),
size: 24,
on_update: WidgetCallback::new(|_| SelectToolMessage::FlipHorizontal.into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "FlipVertical".into(),
tooltip: "Flip Vertical".into(),
size: 24,
on_update: WidgetCallback::new(|_| SelectToolMessage::FlipVertical.into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Related,
})),
})
.widget_holder(),
WidgetHolder::related_separator(),
PopoverButton::new("Align", "Coming soon").widget_holder(),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
IconButton::new("FlipHorizontal", 24)
.tooltip("Flip Horizontal")
.on_update(|_| SelectToolMessage::FlipHorizontal.into())
.widget_holder(),
IconButton::new("FlipVertical", 24)
.tooltip("Flip Vertical")
.on_update(|_| SelectToolMessage::FlipVertical.into())
.widget_holder(),
WidgetHolder::related_separator(),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Flip".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Section,
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "BooleanUnion".into(),
tooltip: "Boolean Union".into(),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "BooleanSubtractFront".into(),
tooltip: "Boolean Subtract Front".into(),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "BooleanSubtractBack".into(),
tooltip: "Boolean Subtract Back".into(),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "BooleanIntersect".into(),
tooltip: "Boolean Intersect".into(),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "BooleanDifference".into(),
tooltip: "Boolean Difference".into(),
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into()),
..IconButton::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Related,
})),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Boolean".into(),
text: "Coming soon".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Section,
})),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
IconButton::new("BooleanUnion", 24)
.tooltip("Boolean Union")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into())
.widget_holder(),
IconButton::new("BooleanSubtractFront", 24)
.tooltip("Boolean Subtract Front")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into())
.widget_holder(),
IconButton::new("BooleanSubtractBack", 24)
.tooltip("Boolean Subtract Back")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into())
.widget_holder(),
IconButton::new("BooleanIntersect", 24)
.tooltip("Boolean Intersect")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into())
.widget_holder(),
IconButton::new("BooleanDifference", 24)
.tooltip("Boolean Difference")
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into())
.widget_holder(),
WidgetHolder::related_separator(),
PopoverButton::new("Boolean", "Coming soon").widget_holder(),
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
// We'd like this widget to hide and show itself whenever the transformation cage is active or inactive (i.e. when no layers are selected)
WidgetHolder::new(Widget::PivotAssist(PivotAssist {
position: self.tool_data.pivot.to_pivot_position(),
on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()),
..Default::default()
})),
PivotAssist::new(self.tool_data.pivot.to_pivot_position())
.on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into())
.widget_holder(),
],
}]))
}
@@ -268,27 +198,12 @@ impl PropertyHolder for SelectTool {
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into());
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
if self.tool_data.pivot.should_refresh_pivot_position() {
// Notify the frontend about the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
self.register_properties(responses, LayoutTarget::ToolOptions);
}
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
}
}
fn actions(&self) -> ActionList {
@@ -321,8 +236,9 @@ impl ToolTransition for SelectTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
enum SelectToolFsmState {
#[default]
Ready,
Dragging,
DrawingBox,
@@ -331,12 +247,6 @@ enum SelectToolFsmState {
DraggingPivot,
}
impl Default for SelectToolFsmState {
fn default() -> Self {
SelectToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct SelectToolData {
drag_start: ViewportPosition,
@@ -812,138 +722,34 @@ impl Fsm for SelectToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
SelectToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Drag Selected"),
plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyG]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grab Selected"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyR]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Rotate Selected"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyS]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Scale Selected"),
plus: false,
},
HintInfo::keys([Key::KeyG], "Grab Selected"),
HintInfo::keys([Key::KeyR], "Rotate Selected"),
HintInfo::keys([Key::KeyS], "Scale Selected"),
]),
HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Select Object"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command]).into()]),
mouse: None,
label: String::from("Innermost"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grow/Shrink Selection"),
plus: true,
},
HintInfo::mouse(MouseMotion::Lmb, "Select Object"),
HintInfo::keys([Key::Control], "Innermost").add_mac_keys([Key::Command]).prepend_plus(),
HintInfo::keys([Key::Shift], "Grow/Shrink Selection").prepend_plus(),
]),
HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Select Area"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Grow/Shrink Selection"),
plus: true,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"),
HintInfo::keys([Key::Shift], "Grow/Shrink Selection").prepend_plus(),
]),
HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "Big Increment Nudge").prepend_plus()]),
HintGroup(vec![
HintInfo {
key_groups: vec![
KeysGroup(vec![Key::ArrowUp]).into(),
KeysGroup(vec![Key::ArrowRight]).into(),
KeysGroup(vec![Key::ArrowDown]).into(),
KeysGroup(vec![Key::ArrowLeft]).into(),
],
key_groups_mac: None,
mouse: None,
label: String::from("Nudge Selected"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Big Increment Nudge"),
plus: true,
},
]),
HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Move Duplicate"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control, Key::KeyD]).into()],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::KeyD]).into()]),
mouse: None,
label: String::from("Duplicate"),
plus: false,
},
HintInfo::keys([Key::Alt], "Move Duplicate"),
HintInfo::keys([Key::Control, Key::KeyD], "Duplicate").add_mac_keys([Key::Command, Key::KeyD]),
]),
]),
SelectToolFsmState::Dragging => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain to Axis"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap to Points (coming soon)"),
plus: false,
},
HintInfo::keys([Key::Shift], "Constrain to Axis"),
HintInfo::keys([Key::Control], "Snap to Points (coming soon)"),
])]),
SelectToolFsmState::DrawingBox => HintData(vec![]),
SelectToolFsmState::ResizingBounds => HintData(vec![]),
SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Snap 15°"),
plus: false,
}])]),
SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]),
SelectToolFsmState::DraggingPivot => HintData(vec![]),
};

View File

@@ -1,7 +1,6 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::resize::Resize;
@@ -69,32 +68,20 @@ impl ToolMetadata for ShapeTool {
impl PropertyHolder for ShapeTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
label: "Sides".into(),
value: Some(self.options.vertices as f64),
is_integer: true,
min: Some(3.),
max: Some(1000.),
on_update: WidgetCallback::new(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()),
..NumberInput::default()
}))],
}]))
let sides = NumberInput::new(Some(self.options.vertices as f64))
.label("Sides")
.int()
.min(3.)
.max(1000.)
.mode(crate::messages::layout::utility_types::widget_prelude::NumberInputMode::Increment)
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![sides] }]))
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message {
match action {
ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
@@ -102,13 +89,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@@ -137,17 +118,13 @@ impl ToolTransition for ShapeTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum ShapeToolFsmState {
#[default]
Ready,
Drawing,
}
impl Default for ShapeToolFsmState {
fn default() -> Self {
ShapeToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct ShapeToolData {
sides: u32,
@@ -201,11 +178,7 @@ impl Fsm for ShapeToolFsmState {
state
}
(Drawing, DragStop) => {
match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
shape_data.cleanup(responses);
Ready
@@ -227,44 +200,11 @@ impl Fsm for ShapeToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Draw Shape"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain 1:1 Aspect"),
plus: true,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: true,
},
])]),
ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Shift]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Constrain 1:1 Aspect"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Alt]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("From Center"),
plus: false,
},
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Shape"),
HintInfo::keys([Key::Shift], "Constrain 1:1 Aspect").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain 1:1 Aspect"), HintInfo::keys([Key::Alt], "From Center")])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());

View File

@@ -1,7 +1,7 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapManager;
@@ -49,8 +49,9 @@ pub enum SplineToolMessage {
UpdateOptions(SplineOptionsUpdate),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum SplineToolFsmState {
#[default]
Ready,
Drawing,
}
@@ -75,32 +76,18 @@ impl ToolMetadata for SplineTool {
impl PropertyHolder for SplineTool {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: Some(self.options.line_weight),
is_integer: false,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}]))
let weight = NumberInput::new(Some(self.options.line_weight))
.unit(" px")
.label("Weight")
.min(0.)
.on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }]))
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message {
match action {
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
@@ -108,13 +95,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@@ -148,11 +129,6 @@ impl ToolTransition for SplineTool {
}
}
impl Default for SplineToolFsmState {
fn default() -> Self {
SplineToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct SplineToolData {
points: Vec<DVec2>,
@@ -251,28 +227,10 @@ impl Fsm for SplineToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Draw Spline"),
plus: false,
}])]),
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Spline")])]),
SplineToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Extend Spline"),
plus: false,
}]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Enter]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("End Spline"),
plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]),
HintGroup(vec![HintInfo::keys([Key::Enter], "End Spline")]),
]),
};

View File

@@ -1,11 +1,10 @@
use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::input_widgets::{FontInput, NumberInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@@ -86,68 +85,49 @@ impl ToolMetadata for TextTool {
impl PropertyHolder for TextTool {
fn properties(&self) -> Layout {
let font = FontInput {
is_style_picker: false,
font_family: self.options.font_name.clone(),
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let style = FontInput {
is_style_picker: true,
font_family: self.options.font_name.clone(),
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
}
.widget_holder();
let size = NumberInput::new(Some(self.options.font_size as f64))
.unit(" px")
.label("Size")
.int()
.min(1.)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into())
.widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::FontInput(FontInput {
is_style_picker: false,
font_family: self.options.font_name.clone(),
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Related,
})),
WidgetHolder::new(Widget::FontInput(FontInput {
is_style_picker: true,
font_family: self.options.font_name.clone(),
font_style: self.options.font_style.clone(),
on_update: WidgetCallback::new(|font_input: &FontInput| {
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(),
style: font_input.font_style.clone(),
})
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal,
separator_type: SeparatorType::Related,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Size".into(),
value: Some(self.options.font_size as f64),
is_integer: true,
min: Some(1.),
on_update: WidgetCallback::new(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()),
..NumberInput::default()
})),
],
widgets: vec![font, WidgetHolder::related_separator(), style, WidgetHolder::related_separator(), size],
}]))
}
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if message == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
if message == ToolMessage::UpdateCursor {
self.fsm_state.update_cursor(responses);
return;
}
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message {
match action {
TextOptionsUpdate::Font { family, style } => {
@@ -161,13 +141,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
return;
}
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
self.fsm_state.update_cursor(responses);
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
}
fn actions(&self) -> ActionList {
@@ -196,18 +170,12 @@ impl ToolTransition for TextTool {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum TextToolFsmState {
#[default]
Ready,
Editing,
}
impl Default for TextToolFsmState {
fn default() -> Self {
TextToolFsmState::Ready
}
}
#[derive(Clone, Debug, Default)]
struct TextToolData {
layer_path: Vec<LayerId>,
@@ -421,37 +389,10 @@ impl Fsm for TextToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
TextToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Add Text"),
plus: false,
},
HintInfo {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(MouseMotion::Lmb),
label: String::from("Edit Text"),
plus: false,
},
])]),
TextToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Text"), HintInfo::mouse(MouseMotion::Lmb, "Edit Text")])]),
TextToolFsmState::Editing => HintData(vec![HintGroup(vec![
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control, Key::Enter]).into()],
key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::Enter]).into()]),
mouse: None,
label: String::from("Commit Edit"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::Escape]).into()],
key_groups_mac: None,
mouse: None,
label: String::from("Discard Edit"),
plus: false,
},
HintInfo::keys([Key::Control, Key::Enter], "Commit Edit").add_mac_keys([Key::Command, Key::Enter]),
HintInfo::keys([Key::Escape], "Discard Edit"),
])]),
};

View File

@@ -1,6 +1,5 @@
use super::tool_messages::*;
use crate::messages::input_mapper::utility_types::input_keyboard::LayoutKeysGroup;
use crate::messages::input_mapper::utility_types::input_keyboard::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::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
@@ -23,15 +22,78 @@ impl<T> ToolCommon for T where T: for<'a> MessageHandler<ToolMessage, ToolAction
type Tool = dyn ToolCommon + Send + Sync;
/// The FSM (finite state machine) is a flowchart between different operating states that a specific tool might be in.
/// It is the central "core" logic area of each tool which is in charge of maintaining the state of the tool and responding to events coming from outside (like user input).
/// For example, a tool might be `Ready` or `Drawing` depending on if the user is idle or actively drawing with the mouse held down.
/// The FSM keeps track of what the tool is doing and allows the tool to take action when events are directed at the FSM.
/// Every tool, which implements this trait, must implement the `transition()` function.
/// That is where new events are sent, and where the flowchart transition logic occurs to respond to events and end in a new state.
pub trait Fsm {
/// The implementing tool must set this to a struct designed to store the internal values stored in the tool.
/// For example, it might be used to store the starting location of a point when a drag began so the displacement distance can be calculated.
type ToolData;
/// The implementing tool must set this to a struct (or `()` if none) designed to store the values of the tool options set by the user in the Options Bar
/// (located above the viewport, below the document's tab).
type ToolOptions;
/// Implementing this mandatory trait function lets a specific tool react accordingly (and potentially change its state or internal variables) upon receiving an event to do something.
/// Based on its current state, and what the event is, the FSM (finite state machine) should direct the tool to an appropriate outcome.
/// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages,
/// update some internal tool variables, and end by transitioning to a `Drawing` state.
#[must_use]
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque<Message>) -> Self;
/// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar.
fn update_hints(&self, responses: &mut VecDeque<Message>);
/// Implementing this trait function lets a specific tool set the current mouse cursor icon.
fn update_cursor(&self, responses: &mut VecDeque<Message>);
/// If this message is a standard tool message, process it and return true. Standard tool messages are those which are common across every tool.
fn standard_tool_messages(&self, message: &ToolMessage, messages: &mut VecDeque<Message>) -> bool {
// Check for standard hits or cursor events
match message {
ToolMessage::UpdateHints => {
self.update_hints(messages);
true
}
ToolMessage::UpdateCursor => {
self.update_cursor(messages);
true
}
_ => false,
}
}
/// When an event makes the tool change or do something, it is processed here to perform a step (transition) on the tool's finite state machine (FSM).
/// This function is called by the specific tool's message handler when the dispatcher routes a message to the active tool.
fn process_event(
&mut self,
message: ToolMessage,
tool_data: &mut Self::ToolData,
transition_data: ToolActionHandlerData,
options: &Self::ToolOptions,
messages: &mut VecDeque<Message>,
update_cursor_on_transition: bool,
) where
Self: PartialEq + Sized + Copy,
{
// If this message is one of the standard tool messages, process it and exit early
if self.standard_tool_messages(&message, messages) {
return;
}
// Transition the tool
let new_state = self.transition(message, tool_data, transition_data, options, messages);
// Update state
if *self != new_state {
*self = new_state;
self.update_hints(messages);
if update_cursor_on_transition {
self.update_cursor(messages);
}
}
}
}
#[derive(Debug, Clone)]
@@ -455,6 +517,55 @@ pub struct HintInfo {
pub plus: bool,
}
impl HintInfo {
pub fn keys(keys: impl IntoIterator<Item = Key>, label: impl Into<String>) -> Self {
let keys: Vec<_> = keys.into_iter().collect();
Self {
key_groups: vec![KeysGroup(keys).into()],
key_groups_mac: None,
mouse: None,
label: label.into(),
plus: false,
}
}
pub fn mouse(mouse_motion: MouseMotion, label: impl Into<String>) -> Self {
Self {
key_groups: vec![],
key_groups_mac: None,
mouse: Some(mouse_motion),
label: label.into(),
plus: false,
}
}
pub fn arrow_keys(label: impl Into<String>) -> Self {
HintInfo {
key_groups: vec![
KeysGroup(vec![Key::ArrowUp]).into(),
KeysGroup(vec![Key::ArrowRight]).into(),
KeysGroup(vec![Key::ArrowDown]).into(),
KeysGroup(vec![Key::ArrowLeft]).into(),
],
key_groups_mac: None,
mouse: None,
label: label.into(),
plus: false,
}
}
pub fn prepend_plus(mut self) -> Self {
self.plus = true;
self
}
pub fn add_mac_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {
let mac_keys: Vec<_> = keys.into_iter().collect();
self.key_groups_mac = Some(vec![KeysGroup(mac_keys).into()]);
self
}
}
#[cfg(test)]
mod tool_crash_on_layer_delete_tests {
use crate::application::{set_uuid_seed, Editor};