From 2910e50b2f09df7de486c8f34fdffa10d6449afa Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 10 Mar 2026 03:41:35 -0700 Subject: [PATCH] Improve Shape tool arrow mode interactive drawing with angle modifier keys and endpoint gizmos (#3874) * Make the order of Shape tool shape types consistent * Add Arrow shape modifier keys and snapping support * Add endpoint dragging to arrows * Show the default cursor when hovering line/arrow endpoints * Reduce duplicated function * Fix incorrect coordinate spaces * Improve endpoint dragging clarity --- .../shapes/arrow_shape.rs | 63 ++++++++-- .../common_functionality/shapes/line_shape.rs | 51 ++------- .../shapes/shape_utility.rs | 54 +++++++-- .../messages/tool/tool_messages/shape_tool.rs | 108 ++++++++++++------ 4 files changed, 177 insertions(+), 99 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs index d62472c3..df04b578 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs @@ -1,5 +1,7 @@ +use super::line_shape::{LineEnd, generate_line}; use super::shape_utility::ShapeToolModifierKey; use super::*; +use crate::consts::BOUNDS_SELECT_THRESHOLD; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type}; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -7,6 +9,8 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils; +pub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; +use crate::messages::tool::common_functionality::snapping::SnapData; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; @@ -31,20 +35,26 @@ impl Arrow { pub fn update_shape( document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, - _viewport: &ViewportMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, tool_data: &mut ShapeToolData, - _modifier: ShapeToolModifierKey, + modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { - // Track current mouse position in viewport space + let [center, snap_angle, lock_angle] = modifier; + tool_data.line_data.drag_current = input.mouse.position; - // Compute arrow_to in document space - let document_to_viewport = document.metadata().document_to_viewport; - let start_document = tool_data.data.drag_start; - let end_document = document_to_viewport.inverse().transform_point2(input.mouse.position); - let arrow_to = end_document - start_document; + let keyboard = &input.keyboard; + let ignore = [layer]; + let snap_data = SnapData::ignore(document, input, viewport, &ignore); + let mut document_points = generate_line(tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); + + if tool_data.line_data.dragging_endpoint == Some(LineEnd::Start) { + document_points.swap(0, 1); + } + + let arrow_to = document_points[1] - document_points[0]; if arrow_to.length() < 1e-6 { return; @@ -54,7 +64,8 @@ impl Arrow { return; }; - // Update Arrow node arrow_to in document space + let document_to_viewport = document.metadata().document_to_viewport; + responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(node_id, 1), input: NodeInput::value(TaggedValue::DVec2(arrow_to), false), @@ -63,7 +74,7 @@ impl Arrow { let scope = downstream.inverse() * document_to_viewport; responses.add(GraphOperationMessage::TransformSet { layer, - transform: DAffine2::from_translation(start_document), + transform: DAffine2::from_translation(document_points[0]), transform_in: TransformIn::Scope { scope }, skip_rerender: false, }); @@ -71,5 +82,35 @@ impl Arrow { responses.add(NodeGraphMessage::RunDocumentGraph); } - pub fn overlays(_document: &DocumentMessageHandler, _tool_data: &ShapeToolData, _overlay_context: &mut OverlayContext) {} + pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, mouse_position: DVec2, overlay_context: &mut OverlayContext) { + let arrow_layers: HashMap = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter_map(|layer| { + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER))?; + let Some(&TaggedValue::DVec2(arrow_to)) = node_inputs[1].as_value() else { return None }; + + let transform = document.metadata().transform_to_viewport(layer); + let viewport_start = transform.transform_point2(DVec2::ZERO); + let viewport_end = transform.transform_point2(arrow_to); + + if !arrow_to.abs_diff_eq(DVec2::ZERO, f64::EPSILON * 1000.) { + let is_editing = shape_tool_data.line_data.editing_layer == Some(layer); + for (i, pos) in [viewport_start, viewport_end].into_iter().enumerate() { + let is_dragged = is_editing && matches!((i, &shape_tool_data.line_data.dragging_endpoint), (0, Some(LineEnd::Start)) | (1, Some(LineEnd::End))); + if is_dragged || (pos - mouse_position).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2) { + overlay_context.hover_manipulator_anchor(pos, is_dragged); + } else { + overlay_context.square(pos, Some(6.), None, None); + } + } + } + + Some((layer, [DVec2::ZERO, arrow_to])) + }) + .collect(); + + shape_tool_data.line_data.selected_layers_with_position.extend(arrow_layers); + } } diff --git a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs index 839c0466..397dc02a 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -87,7 +87,7 @@ impl Line { }); } - pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, overlay_context: &mut OverlayContext) { + pub fn overlays(document: &DocumentMessageHandler, shape_tool_data: &mut ShapeToolData, mouse_position: DVec2, overlay_context: &mut OverlayContext) { shape_tool_data.line_data.selected_layers_with_position = document .network_interface .selected_nodes() @@ -106,8 +106,15 @@ impl Line { let viewport_end = transform.transform_point2(line_to); if !line_to.abs_diff_eq(DVec2::ZERO, f64::EPSILON * 1000.) { overlay_context.line(viewport_start, viewport_end, None, None); - overlay_context.square(viewport_start, Some(6.), None, None); - overlay_context.square(viewport_end, Some(6.), None, None); + let is_editing = shape_tool_data.line_data.editing_layer == Some(layer); + for (i, pos) in [viewport_start, viewport_end].into_iter().enumerate() { + let is_dragged = is_editing && matches!((i, &shape_tool_data.line_data.dragging_endpoint), (0, Some(LineEnd::Start)) | (1, Some(LineEnd::End))); + if is_dragged || (pos - mouse_position).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2) { + overlay_context.hover_manipulator_anchor(pos, is_dragged); + } else { + overlay_context.square(pos, Some(6.), None, None); + } + } } // Store local-space positions for endpoint editing @@ -117,7 +124,7 @@ impl Line { } } -fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { +pub fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> [DVec2; 2] { let document_to_viewport = snap_data.document.metadata().document_to_viewport; let mut document_points = [tool_data.data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.line_data.drag_current)]; @@ -180,42 +187,6 @@ fn generate_line(tool_data: &mut ShapeToolData, snap_data: SnapData, lock_angle: document_points } -pub fn clicked_on_line_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, shape_tool_data: &mut ShapeToolData) -> bool { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER)) else { - return false; - }; - - let Some(&TaggedValue::DVec2(line_to)) = node_inputs[1].as_value() else { - return false; - }; - - // Line goes from local origin (0,0) to line_to, positioned by the Transform node - let local_start = DVec2::ZERO; - let local_end = line_to; - - let transform = document.metadata().transform_to_viewport(layer); - let viewport_x = transform.transform_vector2(DVec2::X).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let viewport_y = transform.transform_vector2(DVec2::Y).normalize_or_zero() * BOUNDS_SELECT_THRESHOLD; - let threshold_x = transform.inverse().transform_vector2(viewport_x).length(); - let threshold_y = transform.inverse().transform_vector2(viewport_y).length(); - - let drag_start = input.mouse.position; - let [start, end] = [local_start, local_end].map(|point| transform.transform_point2(point)); - - let start_click = (drag_start.y - start.y).abs() < threshold_y && (drag_start.x - start.x).abs() < threshold_x; - let end_click = (drag_start.y - end.y).abs() < threshold_y && (drag_start.x - end.x).abs() < threshold_x; - - if start_click || end_click { - shape_tool_data.line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); - // Convert the anchor endpoint (the one NOT being dragged) to document space for drag_start - let anchor_local = if end_click { local_start } else { local_end }; - shape_tool_data.data.drag_start = document.metadata().transform_to_document(layer).transform_point2(anchor_local); - shape_tool_data.line_data.editing_layer = Some(layer); - return true; - } - false -} - #[cfg(test)] mod test_line_tool { use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 6e1d3f22..96dbdd51 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -1,5 +1,6 @@ use super::ShapeToolData; -use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT}; +use super::line_shape::LineEnd; +use crate::consts::{ARC_SWEEP_GIZMO_RADIUS, ARC_SWEEP_GIZMO_TEXT_HEIGHT, BOUNDS_SELECT_THRESHOLD}; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier; @@ -33,10 +34,10 @@ pub enum ShapeType { Arc, Spiral, Grid, - Rectangle, - Ellipse, Arrow, - Line, + Line, // KEEP THIS AT THE END + Rectangle, // KEEP THIS AT THE END + Ellipse, // KEEP THIS AT THE END } impl ShapeType { @@ -46,12 +47,12 @@ impl ShapeType { Self::Star => "Star", Self::Circle => "Circle", Self::Arc => "Arc", - Self::Grid => "Grid", Self::Spiral => "Spiral", - Self::Rectangle => "Rectangle", - Self::Ellipse => "Ellipse", + Self::Grid => "Grid", Self::Arrow => "Arrow", Self::Line => "Line", + Self::Rectangle => "Rectangle", + Self::Ellipse => "Ellipse", }) .into() } @@ -61,7 +62,6 @@ impl ShapeType { Self::Line => "Line Tool", Self::Rectangle => "Rectangle Tool", Self::Ellipse => "Ellipse Tool", - Self::Arrow => "Arrow Tool", _ => "", }) .into() @@ -80,7 +80,6 @@ impl ShapeType { Self::Line => "VectorLineTool", Self::Rectangle => "VectorRectangleTool", Self::Ellipse => "VectorEllipseTool", - Self::Arrow => "VectorArrowTool", _ => "", }) .into() @@ -91,7 +90,6 @@ impl ShapeType { Self::Line => ToolType::Line, Self::Rectangle => ToolType::Rectangle, Self::Ellipse => ToolType::Ellipse, - Self::Arrow => ToolType::Shape, _ => ToolType::Shape, } } @@ -154,6 +152,42 @@ pub trait ShapeGizmoHandler { fn mouse_cursor_icon(&self) -> Option; } +/// Check if the mouse clicked on either endpoint of a line-like shape (Line or Arrow). +pub fn clicked_on_shape_endpoints(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, shape_tool_data: &mut ShapeToolData) -> bool { + let line_like_shape_nodes = [ + DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::line::IDENTIFIER), + DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::arrow::IDENTIFIER), + ]; + + let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); + let endpoint = line_like_shape_nodes.iter().find_map(|id| { + let node_inputs = node_graph_layer.find_node_inputs(id)?; + let &TaggedValue::DVec2(endpoint) = node_inputs[1].as_value()? else { return None }; + Some(endpoint) + }); + let Some(endpoint) = endpoint else { return false }; + + let local_start = DVec2::ZERO; + let local_end = endpoint; + + let transform = document.metadata().transform_to_viewport(layer); + let mouse_pos = input.mouse.position; + let [start, end] = [local_start, local_end].map(|point| transform.transform_point2(point)); + + let start_click = (mouse_pos - start).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2); + let end_click = (mouse_pos - end).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2); + let endpoint_click = start_click || end_click; + + if endpoint_click { + shape_tool_data.line_data.dragging_endpoint = Some(if end_click { LineEnd::End } else { LineEnd::Start }); + let anchor_local = if end_click { local_start } else { local_end }; + shape_tool_data.data.drag_start = document.metadata().transform_to_document(layer).transform_point2(anchor_local); + shape_tool_data.line_data.editing_layer = Some(layer); + } + + endpoint_click +} + /// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { let sign_num = if end[1] > start[1] { 1. } else { -1. }; diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 0253b228..fcad13c5 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1,5 +1,5 @@ use super::tool_prelude::*; -use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; +use crate::consts::{BOUNDS_SELECT_THRESHOLD, DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -12,9 +12,9 @@ use crate::messages::tool::common_functionality::shapes::arc_shape::Arc; use crate::messages::tool::common_functionality::shapes::arrow_shape::Arrow; use crate::messages::tool::common_functionality::shapes::circle_shape::Circle; use crate::messages::tool::common_functionality::shapes::grid_shape::Grid; -use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; +use crate::messages::tool::common_functionality::shapes::line_shape::LineToolData; use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; -use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, clicked_on_shape_endpoints, transform_cage_overlays}; use crate::messages::tool::common_functionality::shapes::spiral_shape::Spiral; use crate::messages::tool::common_functionality::shapes::star_shape::Star; use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle}; @@ -645,11 +645,11 @@ impl Fsm for ShapeToolFsmState { tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - let all_selected_layers_line = document + let all_selected_layers_line_or_arrow = document .network_interface .selected_nodes() .selected_visible_and_unlocked_layers(&document.network_interface) - .all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some()); + .all(|layer| graph_modification_utils::get_line_id(layer, &document.network_interface).is_some() || graph_modification_utils::get_arrow_id(layer, &document.network_interface).is_some()); let ToolMessage::Shape(event) = event else { return self }; @@ -677,7 +677,15 @@ impl Fsm for ShapeToolFsmState { let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. }); let hovering_over_gizmo = tool_data.gizmo_manager.hovering_over_gizmo(); - if !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo { + // Check if hovering over a line/arrow endpoint (using data from previous overlay pass) + let hovering_over_endpoint = tool_data.line_data.selected_layers_with_position.iter().any(|(layer, endpoints)| { + let transform = document.metadata().transform_to_viewport(*layer); + endpoints + .iter() + .any(|&local_pos| (transform.transform_point2(local_pos) - input.mouse.position).length_squared() < BOUNDS_SELECT_THRESHOLD.powi(2)) + }); + + if !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo && !hovering_over_endpoint { tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); } @@ -690,9 +698,13 @@ impl Fsm for ShapeToolFsmState { 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); + Line::overlays(document, tool_data, input.mouse.position, &mut overlay_context); + Arrow::overlays(document, tool_data, input.mouse.position, &mut overlay_context); - if all_selected_layers_line { + if all_selected_layers_line_or_arrow { + let cursor = if hovering_over_endpoint { MouseCursorIcon::Default } else { MouseCursorIcon::Crosshair }; + tool_data.cursor = cursor; + responses.add(FrontendMessage::UpdateMouseCursor { cursor }); return self; } @@ -721,14 +733,20 @@ impl Fsm for ShapeToolFsmState { } } - let cursor = tool_data.gizmo_manager.mouse_cursor_icon().unwrap_or_else(|| tool_data.transform_cage_mouse_icon(input)); + let cursor = tool_data + .gizmo_manager + .mouse_cursor_icon() + .or_else(|| hovering_over_endpoint.then_some(MouseCursorIcon::Default)) + .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) { - Line::overlays(document, tool_data, &mut overlay_context); + Line::overlays(document, tool_data, input.mouse.position, &mut overlay_context); + Arrow::overlays(document, tool_data, input.mouse.position, &mut overlay_context); + if tool_options.shape_type == ShapeType::Circle { tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context); } @@ -836,14 +854,14 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::ModifyingGizmo; } - // If clicked on endpoints of a selected line, drag its endpoints + // If clicked on endpoints of a selected line or arrow, drag its endpoints if let Some((layer, _, _)) = closest_point( document, mouse_pos, SNAP_POINT_TOLERANCE, document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface), |_| false, - ) && clicked_on_line_endpoints(layer, document, input, tool_data) + ) && clicked_on_shape_endpoints(layer, document, input, tool_data) && !input.keyboard.key(Key::Control) { responses.add(DocumentMessage::StartTransaction); @@ -909,10 +927,10 @@ impl Fsm for ShapeToolFsmState { ShapeType::Arc => Arc::create_node(tool_options.arc_type), ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns), ShapeType::Grid => Grid::create_node(tool_options.grid_type), - ShapeType::Rectangle => Rectangle::create_node(), - ShapeType::Ellipse => Ellipse::create_node(), ShapeType::Arrow => Arrow::create_node(tool_options.arrow_shaft_width, tool_options.arrow_head_width, tool_options.arrow_head_length), ShapeType::Line => Line::create_node(), + ShapeType::Rectangle => Rectangle::create_node(), + ShapeType::Ellipse => Ellipse::create_node(), }; let nodes = vec![(NodeId(0), node)]; @@ -980,12 +998,12 @@ impl Fsm for ShapeToolFsmState { ShapeType::Star => Star::update_shape(document, input, viewport, layer, tool_data, modifier, responses), ShapeType::Circle => Circle::update_shape(document, input, viewport, layer, tool_data, modifier, responses), ShapeType::Arc => Arc::update_shape(document, input, viewport, layer, tool_data, modifier, responses), - ShapeType::Arrow => Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses), ShapeType::Spiral => Spiral::update_shape(document, input, viewport, layer, tool_data, responses), ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses), + ShapeType::Arrow => Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Line => Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses), ShapeType::Rectangle => Rectangle::update_shape(document, input, viewport, layer, tool_data, modifier, responses), ShapeType::Ellipse => Ellipse::update_shape(document, input, viewport, layer, tool_data, modifier, responses), - ShapeType::Line => Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses), } // Auto-panning @@ -999,7 +1017,12 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::Ready(tool_data.current_shape); }; - Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses); + if graph_modification_utils::get_arrow_id(layer, &document.network_interface).is_some() { + Arrow::update_shape(document, input, viewport, layer, tool_data, modifier, responses); + } else { + Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses); + } + // Auto-panning let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()]; tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); @@ -1206,15 +1229,30 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Circle"), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ])], + ShapeType::Arc => vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arc"), + HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + ])], ShapeType::Spiral => vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Spiral")]), HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Turns")]), ], - ShapeType::Ellipse => vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), - HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), + ShapeType::Grid => vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Grid"), + HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])], + ShapeType::Arrow => vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arrow"), + HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), + ])], ShapeType::Line => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(), @@ -1226,21 +1264,11 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Circle"), + ShapeType::Ellipse => vec![HintGroup(vec![ + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), + HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])], - ShapeType::Arc => vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arc"), - HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ])], - ShapeType::Grid => vec![HintGroup(vec![ - HintInfo::mouse(MouseMotion::LmbDrag, "Draw Grid"), - HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(), - HintInfo::keys([Key::Alt], "From Center").prepend_plus(), - ])], - ShapeType::Arrow => vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arrow")])], }; HintData(hint_groups) } @@ -1248,17 +1276,21 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), - ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), - ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Spiral => HintGroup(vec![]), ShapeType::Grid => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Arrow => HintGroup(vec![ + HintInfo::keys([Key::Shift], "15° Increments"), + HintInfo::keys([Key::Alt], "From Center"), + HintInfo::keys([Key::Control], "Lock Angle"), + ]), ShapeType::Line => HintGroup(vec![ HintInfo::keys([Key::Shift], "15° Increments"), HintInfo::keys([Key::Alt], "From Center"), HintInfo::keys([Key::Control], "Lock Angle"), ]), - ShapeType::Arrow => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Angle")]), - ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]), - ShapeType::Spiral => HintGroup(vec![]), + ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]), + ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]), }; if !tool_hint_group.0.is_empty() {