Fix Shape tool arc gizmo snap visualization and pointer cursor icon when hovering or dragging (#2957)

* fixed cursor,arc bugs

* Update cursor icon

* send pointer-move only when required

* make it compile

* Code review

* remove repeated modifier key

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0SlowPoke0 2025-07-31 12:34:08 +05:30 committed by GitHub
parent 3cc9dd79fb
commit 7e0a274bd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 147 additions and 46 deletions

View File

@ -178,7 +178,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control, Shift])),
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control])),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),

View File

@ -423,11 +423,9 @@ impl OverlayContext {
self.render_context.stroke();
}
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, dash_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
let end_point2 = pivot + dash_radius * DVec2::from_angle(offset_angle);
self.line(pivot, end_point1, None, None);
self.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
}
@ -592,9 +590,9 @@ impl OverlayContext {
self.end_dpi_aware_transform();
}
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, dash_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
self.draw_arc_gizmo_angle(pivot, bold_radius, dash_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
}

View File

@ -379,11 +379,9 @@ impl OverlayContext {
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
}
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, dash_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
let end_point2 = pivot + dash_radius * DVec2::from_angle(offset_angle);
self.line(pivot, end_point1, None, None);
self.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
}
@ -542,9 +540,9 @@ impl OverlayContext {
}
#[allow(clippy::too_many_arguments)]
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, dash_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
self.draw_arc_gizmo_angle(pivot, bold_radius, dash_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
}

View File

@ -1,3 +1,4 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@ -123,6 +124,15 @@ impl ShapeGizmoHandlers {
Self::None => {}
}
}
pub fn gizmo_cursor_icon(&self) -> Option<MouseCursorIcon> {
match self {
Self::Star(h) => h.mouse_cursor_icon(),
Self::Polygon(h) => h.mouse_cursor_icon(),
Self::Arc(h) => h.mouse_cursor_icon(),
Self::None => None,
}
}
}
/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas.
@ -256,4 +266,12 @@ impl GizmoManager {
}
}
}
/// Returns the cursor icon to display when hovering or dragging a gizmo.
///
/// If a gizmo is active (hovered or being manipulated), it returns the cursor icon associated with that gizmo;
/// otherwise, returns `None` to indicate the default crosshair cursor should be used.
pub fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
self.active_shape_handler.as_ref().and_then(|h| h.gizmo_cursor_icon())
}
}

View File

@ -1,10 +1,9 @@
use crate::consts::{ARC_SNAP_THRESHOLD, COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage};
use crate::messages::prelude::DocumentMessageHandler;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::shapes::shape_utility::{arc_end_points, calculate_arc_text_transform, extract_arc_parameters, format_rounded};
use crate::messages::tool::tool_messages::tool_prelude::*;
@ -57,7 +56,7 @@ impl SweepAngleGizmo {
self.handle_state == SweepAngleGizmoState::Dragging || self.handle_state == SweepAngleGizmoState::Snapped
}
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2) {
if self.handle_state == SweepAngleGizmoState::Inactive {
let Some((start, end)) = arc_end_points(Some(layer), document) else { return };
let Some((_, start_angle, sweep_angle, _)) = extract_arc_parameters(Some(layer), document) else {
@ -89,8 +88,6 @@ impl SweepAngleGizmo {
self.snap_angles = Self::calculate_snap_angles();
self.update_state(SweepAngleGizmoState::Hover);
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
}
}
}
@ -127,6 +124,11 @@ impl SweepAngleGizmo {
// Depending on which endpoint is being dragged, draw guides relative to the static point
let point = if self.endpoint == EndpointType::End { current_end } else { current_start };
// Draw the dashed line from center to drag start position
overlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));
// Draw the angle, text and the bold line
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
}
SweepAngleGizmoState::Snapped => {
@ -143,6 +145,9 @@ impl SweepAngleGizmo {
// Draw lines from endpoints to the arc center
overlay_context.line(start, center, Some(COLOR_OVERLAY_RED), Some(2.));
overlay_context.line(end, center, Some(COLOR_OVERLAY_RED), Some(2.));
// Draw the line from drag start to arc center
overlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));
}
}
}
@ -155,7 +160,6 @@ impl SweepAngleGizmo {
let final_vector = final_point - center;
let offset_angle = initial_vector.to_angle() + tilt_offset;
let dash_radius = initial_point.distance(center);
let bold_radius = final_point.distance(center);
let angle = initial_vector.angle_to(final_vector).to_degrees();
@ -170,7 +174,7 @@ impl SweepAngleGizmo {
let transform = calculate_arc_text_transform(angle, offset_angle, center, text_texture_width);
overlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, dash_radius, center, &text, transform);
overlay_context.arc_sweep_angle(offset_angle, angle, final_point, bold_radius, center, &text, transform);
}
pub fn update_arc(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
@ -209,12 +213,11 @@ impl SweepAngleGizmo {
let wrapped = new_sweep_angle % 360.;
self.total_angle_delta = -wrapped;
// Remaining drag gets passed to the ending endpoint
let rest_angle = angle_delta + wrapped;
self.endpoint = EndpointType::End;
self.initial_sweep_angle = 360.;
self.initial_start_angle = current_start_angle + rest_angle;
self.initial_start_angle = current_start_angle;
self.update_state(SweepAngleGizmoState::Snapped);
self.apply_arc_update(node_id, self.initial_start_angle, self.initial_sweep_angle - wrapped, input, responses);
}
@ -288,12 +291,13 @@ impl SweepAngleGizmo {
}
// Clamp sweep angle above 360°, switch to start
() if new_sweep_angle > 360. => {
let delta = angle_delta - (360. - current_sweep_angle);
let delta = angle_delta - (360. - new_sweep_angle);
let sign = -delta.signum();
self.total_angle_delta = angle_delta;
self.total_angle_delta = angle_delta - (360. - new_sweep_angle);
self.initial_sweep_angle = 360.;
self.endpoint = EndpointType::Start;
self.update_state(SweepAngleGizmoState::Snapped);
self.apply_arc_update(node_id, self.initial_start_angle + angle_delta, self.initial_sweep_angle + angle_delta.abs() * sign, input, responses);
}
@ -339,7 +343,7 @@ impl SweepAngleGizmo {
pub fn calculate_snap_angles() -> Vec<f64> {
let mut snap_points = Vec::new();
for i in 0..8 {
for i in 0..=8 {
let snap_point = i as f64 * FRAC_PI_4;
snap_points.push(snap_point.to_degrees());
}

View File

@ -26,8 +26,8 @@ impl ArcGizmoHandler {
}
impl ShapeGizmoHandler for ArcGizmoHandler {
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
self.sweep_angle_gizmo.handle_actions(selected_shape_layers, document, mouse_position, responses);
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {
self.sweep_angle_gizmo.handle_actions(selected_shape_layers, document, mouse_position);
}
fn is_any_gizmo_hovered(&self) -> bool {
@ -74,6 +74,14 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
arc_outline(selected_shape_layers.or(self.sweep_angle_gizmo.layer), document, overlay_context);
}
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
if self.sweep_angle_gizmo.hovered() || self.sweep_angle_gizmo.is_dragging_or_snapped() {
return Some(MouseCursorIcon::Default);
}
None
}
fn cleanup(&mut self) {
self.sweep_angle_gizmo.cleanup();
}

View File

@ -28,7 +28,7 @@ impl Ellipse {
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, lock_ratio, _, _] = modifier;
let [center, lock_ratio, _] = modifier;
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else {

View File

@ -53,7 +53,7 @@ impl Line {
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, _, lock_angle, snap_angle] = modifier;
let [center, snap_angle, lock_angle] = modifier;
shape_tool_data.line_data.drag_current = ipp.mouse.position;

View File

@ -89,6 +89,18 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
}
}
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {
return Some(MouseCursorIcon::EWResize);
}
if self.point_radius_handle.is_dragging_or_snapped() || self.point_radius_handle.hovered() {
return Some(MouseCursorIcon::Default);
}
None
}
fn cleanup(&mut self) {
self.number_of_points_dial.cleanup();
self.point_radius_handle.cleanup();
@ -112,7 +124,7 @@ impl Polygon {
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, lock_ratio, _, _] = modifier;
let [center, lock_ratio, _] = modifier;
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
// TODO: We need to determine how to allow the polygon node to make irregular shapes

View File

@ -28,7 +28,7 @@ impl Rectangle {
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, lock_ratio, _, _] = modifier;
let [center, lock_ratio, _] = modifier;
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else {

View File

@ -1,5 +1,6 @@
use super::ShapeToolData;
use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@ -74,7 +75,7 @@ impl ShapeType {
}
}
pub type ShapeToolModifierKey = [Key; 4];
pub type ShapeToolModifierKey = [Key; 3];
/// The `ShapeGizmoHandler` trait defines the interactive behavior and overlay logic for shape-specific tools in the editor.
/// A gizmo is a visual handle or control point used to manipulate a shape's properties (e.g., number of sides, radius, angle).
@ -127,6 +128,8 @@ pub trait ShapeGizmoHandler {
///
/// For example, dragging states or hover flags should be cleared to avoid visual glitches when switching tools or shapes.
fn cleanup(&mut self);
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon>;
}
/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side

View File

@ -90,6 +90,18 @@ impl ShapeGizmoHandler for StarGizmoHandler {
self.number_of_points_dial.cleanup();
self.point_radius_handle.cleanup();
}
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
if self.number_of_points_dial.is_dragging() || self.number_of_points_dial.is_hovering() {
return Some(MouseCursorIcon::EWResize);
}
if self.point_radius_handle.is_dragging_or_snapped() || self.point_radius_handle.hovered() {
return Some(MouseCursorIcon::Default);
}
None
}
}
#[derive(Default)]
@ -114,7 +126,7 @@ impl Star {
modifier: ShapeToolModifierKey,
responses: &mut VecDeque<Message>,
) {
let [center, lock_ratio, _, _] = modifier;
let [center, lock_ratio, _] = modifier;
if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) {
// TODO: We need to determine how to allow the polygon node to make irregular shapes

View File

@ -335,7 +335,7 @@ pub struct ShapeToolData {
current_shape: ShapeType,
// Gizmos
gizmo_manger: GizmoManager,
gizmo_manager: GizmoManager,
}
impl ShapeToolData {
@ -351,6 +351,23 @@ impl ShapeToolData {
}
}
}
fn transform_cage_mouse_icon(&mut self, input: &InputPreprocessorMessageHandler) -> MouseCursorIcon {
let dragging_bounds = self
.bounding_box_manager
.as_mut()
.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))
.is_some();
self.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Crosshair, |bounds| {
let cursor_icon = bounds.get_cursor(input, true, dragging_bounds, Some(self.skew_edge));
if cursor_icon == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor_icon }
})
}
fn shape_tool_modifier_keys() -> [Key; 3] {
[Key::Alt, Key::Shift, Key::Control]
}
}
impl Fsm for ShapeToolFsmState {
@ -388,30 +405,34 @@ impl Fsm for ShapeToolFsmState {
.indicator_pos()
.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))
.unwrap_or(input.mouse.position);
let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds);
if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) {
tool_data.gizmo_manger.handle_actions(mouse_position, document, responses);
tool_data.gizmo_manger.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
tool_data.gizmo_manager.handle_actions(mouse_position, document, responses);
tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
}
if matches!(self, ShapeToolFsmState::ModifyingGizmo) && !input.keyboard.key(Key::Control) {
tool_data.gizmo_manger.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
tool_data.gizmo_manager.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
let cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or(MouseCursorIcon::Crosshair);
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
}
let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. });
let hovering_over_gizmo = tool_data.gizmo_manger.hovering_over_gizmo();
let hovering_over_gizmo = tool_data.gizmo_manager.hovering_over_gizmo();
if !is_resizing_or_rotating && !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo {
if !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo {
tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
}
if modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) {
transform_cage_overlays(document, tool_data, &mut overlay_context);
responses.add(FrontendMessage::UpdateMouseCursor { cursor: tool_data.cursor });
}
if input.keyboard.key(Key::Control) && matches!(self, ShapeToolFsmState::Ready(_)) {
anchor_overlays(document, &mut overlay_context);
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
} else if matches!(self, ShapeToolFsmState::Ready(_)) {
Line::overlays(document, tool_data, &mut overlay_context);
@ -433,7 +454,7 @@ impl Fsm for ShapeToolFsmState {
let edges = bounds.check_selected_edges(input.mouse.position);
let is_skewing = matches!(self, ShapeToolFsmState::SkewingBounds { .. });
let is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));
if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating && !hovering_over_gizmo) {
if is_skewing || (dragging_bounds && is_near_square && !hovering_over_gizmo) {
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
}
if !is_skewing && dragging_bounds && !hovering_over_gizmo {
@ -442,6 +463,11 @@ impl Fsm for ShapeToolFsmState {
}
}
}
let cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or_else(|| tool_data.transform_cage_mouse_icon(input));
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
}
if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) {
@ -562,8 +588,16 @@ impl Fsm for ShapeToolFsmState {
tool_data.line_data.drag_current = mouse_pos;
if tool_data.gizmo_manger.handle_click() {
if tool_data.gizmo_manager.handle_click() && !input.keyboard.key(Key::Accel) {
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
responses.add(DocumentMessage::StartTransaction);
let cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or(MouseCursorIcon::Crosshair);
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
// Send a PointerMove message to refresh the cursor icon
responses.add(ShapeToolMessage::PointerMove(ShapeToolData::shape_tool_modifier_keys()));
return ShapeToolFsmState::ModifyingGizmo;
}
@ -584,17 +618,31 @@ impl Fsm for ShapeToolFsmState {
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
if !input.keyboard.key(Key::Control) {
// Helper function to update cursor and send pointer move message
let update_cursor_and_pointer = |tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>| {
let cursor = tool_data.transform_cage_mouse_icon(input);
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
responses.add(ShapeToolMessage::PointerMove(ShapeToolData::shape_tool_modifier_keys()));
};
match (resize, rotate, skew) {
(true, false, false) => {
tool_data.get_snap_candidates(document, input);
update_cursor_and_pointer(tool_data, responses);
return ShapeToolFsmState::ResizingBounds;
}
(false, true, false) => {
tool_data.data.drag_start = mouse_pos;
update_cursor_and_pointer(tool_data, responses);
return ShapeToolFsmState::RotatingBounds;
}
(false, false, true) => {
tool_data.get_snap_candidates(document, input);
update_cursor_and_pointer(tool_data, responses);
return ShapeToolFsmState::SkewingBounds { skew: Key::Control };
}
_ => {}
@ -686,8 +734,7 @@ impl Fsm for ShapeToolFsmState {
self
}
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.gizmo_manger.handle_update(tool_data.data.drag_start, document, input, responses);
tool_data.gizmo_manager.handle_update(tool_data.data.drag_start, document, input, responses);
responses.add(OverlaysMessage::Draw);
@ -758,7 +805,7 @@ impl Fsm for ShapeToolFsmState {
if cursor == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor }
});
if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && !all_selected_layers_line {
if tool_data.cursor != cursor {
tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
}
@ -797,7 +844,7 @@ impl Fsm for ShapeToolFsmState {
input.mouse.finish_transaction(tool_data.data.drag_start, responses);
tool_data.data.cleanup(responses);
tool_data.gizmo_manger.handle_cleanup();
tool_data.gizmo_manager.handle_cleanup();
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
@ -822,12 +869,13 @@ impl Fsm for ShapeToolFsmState {
tool_data.data.cleanup(responses);
tool_data.line_data.dragging_endpoint = None;
tool_data.gizmo_manger.handle_cleanup();
tool_data.gizmo_manager.handle_cleanup();
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
tool_data.cursor = MouseCursorIcon::Crosshair;
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
ShapeToolFsmState::Ready(tool_data.current_shape)