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
This commit is contained in:
parent
e7a2800665
commit
2910e50b2f
|
|
@ -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<Message>,
|
||||
) {
|
||||
// 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<LayerNodeIdentifier, [DVec2; 2]> = 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<MouseCursorIcon>;
|
||||
}
|
||||
|
||||
/// 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<Message>) {
|
||||
let sign_num = if end[1] > start[1] { 1. } else { -1. };
|
||||
|
|
|
|||
|
|
@ -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<Message>,
|
||||
) -> 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);
|
||||
};
|
||||
|
||||
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<Mess
|
|||
]),
|
||||
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]),
|
||||
],
|
||||
ShapeType::Circle => 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<Mess
|
|||
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Circle => 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<Mess
|
|||
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
let tool_hint_group = match shape {
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => 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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue