Implement extending, joining, and creating new subpaths with the Spline tool (#2203)
* visualize spline end points using overlays * implement for spline tool to extend path by draging end points * allow holding Shift to begin drawing a new spline subpath in the same layer * implement spline tool to join two endpoints * fix naming * refactor spline tool * impl spline tool snapping and overlays * fix joining path and refactor * improve join_path comment * fix snapping overlays flickering by ignoring snapping in current layer * fix inserting single point on aborting spline tool * add snapping for endpoint even when regular snapping is disabled * fix extending * fix inserting new point instead of extending and Add hint for Shift to append * fix grammatical errors and code style * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
33ac141fb8
commit
f7b7f6b9f4
|
|
@ -71,6 +71,9 @@ pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||||
// Pen tool
|
// Pen tool
|
||||||
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
|
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
|
||||||
|
|
||||||
|
// Spline tool
|
||||||
|
pub const PATH_JOIN_THRESHOLD: f64 = 5.;
|
||||||
|
|
||||||
// Line tool
|
// Line tool
|
||||||
pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ pub fn input_mappings() -> Mapping {
|
||||||
//
|
//
|
||||||
// SplineToolMessage
|
// SplineToolMessage
|
||||||
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart { append_to_selected: Shift }),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=SplineToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=SplineToolMessage::DragStop),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=SplineToolMessage::Confirm),
|
entry!(KeyDown(MouseRight); action_dispatch=SplineToolMessage::Confirm),
|
||||||
entry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm),
|
entry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm),
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,32 @@ use glam::DVec2;
|
||||||
|
|
||||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||||
pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
|
pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
|
||||||
|
closest_point(document, goal, tolerance, layers, |_| false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the closest point to the goal point under max_distance.
|
||||||
|
/// Additionally exclude checking closeness to the point which given to exclude() returns true.
|
||||||
|
pub fn closest_point<T>(
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
goal: DVec2,
|
||||||
|
max_distance: f64,
|
||||||
|
layers: impl Iterator<Item = LayerNodeIdentifier>,
|
||||||
|
exclude: T,
|
||||||
|
) -> Option<(LayerNodeIdentifier, PointId, DVec2)>
|
||||||
|
where
|
||||||
|
T: Fn(PointId) -> bool,
|
||||||
|
{
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
let mut best_distance_squared = tolerance * tolerance;
|
let mut best_distance_squared = max_distance * max_distance;
|
||||||
for layer in layers {
|
for layer in layers {
|
||||||
let viewspace = document.metadata().transform_to_viewport(layer);
|
let viewspace = document.metadata().transform_to_viewport(layer);
|
||||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for id in vector_data.single_connected_points() {
|
for id in vector_data.single_connected_points() {
|
||||||
|
if exclude(id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
|
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||||
|
|
||||||
let distance_squared = viewspace.transform_point2(point).distance_squared(goal);
|
let distance_squared = viewspace.transform_point2(point).distance_squared(goal);
|
||||||
|
|
|
||||||
|
|
@ -1061,7 +1061,7 @@ impl Fsm for PathToolFsmState {
|
||||||
HintInfo::keys([Key::Delete], "Delete Selected"),
|
HintInfo::keys([Key::Delete], "Delete Selected"),
|
||||||
// TODO: Only show the following hints if at least one anchor is selected
|
// TODO: Only show the following hints if at least one anchor is selected
|
||||||
HintInfo::keys([Key::Accel], "No Dissolve").prepend_plus(),
|
HintInfo::keys([Key::Accel], "No Dissolve").prepend_plus(),
|
||||||
HintInfo::keys([Key::Shift], "Break Anchor").prepend_plus(),
|
HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus(),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
PathToolFsmState::Dragging(dragging_state) => {
|
PathToolFsmState::Dragging(dragging_state) => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD};
|
use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE};
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;
|
||||||
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint};
|
||||||
|
use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend};
|
||||||
|
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
@ -38,13 +41,14 @@ impl Default for SplineOptions {
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub enum SplineToolMessage {
|
pub enum SplineToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
|
Overlays(OverlayContext),
|
||||||
CanvasTransformed,
|
CanvasTransformed,
|
||||||
Abort,
|
Abort,
|
||||||
WorkingColorChanged,
|
WorkingColorChanged,
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
Confirm,
|
Confirm,
|
||||||
DragStart,
|
DragStart { append_to_selected: Key },
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
PointerOutsideViewport,
|
PointerOutsideViewport,
|
||||||
|
|
@ -152,6 +156,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineT
|
||||||
Undo,
|
Undo,
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
|
PointerMove,
|
||||||
Confirm,
|
Confirm,
|
||||||
Abort,
|
Abort,
|
||||||
),
|
),
|
||||||
|
|
@ -168,6 +173,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineT
|
||||||
impl ToolTransition for SplineTool {
|
impl ToolTransition for SplineTool {
|
||||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
EventToMessageMap {
|
EventToMessageMap {
|
||||||
|
overlay_provider: Some(|overlay_context: OverlayContext| SplineToolMessage::Overlays(overlay_context).into()),
|
||||||
canvas_transformed: Some(SplineToolMessage::CanvasTransformed.into()),
|
canvas_transformed: Some(SplineToolMessage::CanvasTransformed.into()),
|
||||||
tool_abort: Some(SplineToolMessage::Abort.into()),
|
tool_abort: Some(SplineToolMessage::Abort.into()),
|
||||||
working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()),
|
working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()),
|
||||||
|
|
@ -178,7 +184,7 @@ impl ToolTransition for SplineTool {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct SplineToolData {
|
struct SplineToolData {
|
||||||
/// Points that are inserted.
|
/// List of points inserted.
|
||||||
points: Vec<(PointId, DVec2)>,
|
points: Vec<(PointId, DVec2)>,
|
||||||
/// Point to be inserted.
|
/// Point to be inserted.
|
||||||
next_point: DVec2,
|
next_point: DVec2,
|
||||||
|
|
@ -186,32 +192,94 @@ struct SplineToolData {
|
||||||
preview_point: Option<PointId>,
|
preview_point: Option<PointId>,
|
||||||
/// Segment that was inserted temporarily to show preview.
|
/// Segment that was inserted temporarily to show preview.
|
||||||
preview_segment: Option<SegmentId>,
|
preview_segment: Option<SegmentId>,
|
||||||
|
extend: bool,
|
||||||
weight: f64,
|
weight: f64,
|
||||||
layer: Option<LayerNodeIdentifier>,
|
layer: Option<LayerNodeIdentifier>,
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SplineToolData {
|
||||||
|
fn cleanup(&mut self) {
|
||||||
|
self.layer = None;
|
||||||
|
self.preview_point = None;
|
||||||
|
self.preview_segment = None;
|
||||||
|
self.extend = false;
|
||||||
|
self.points = Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the snapped point while ignoring current layer
|
||||||
|
fn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> SnappedPoint {
|
||||||
|
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||||
|
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
||||||
|
let snap_data = SnapData::ignore(document, input, &ignore);
|
||||||
|
self.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Fsm for SplineToolFsmState {
|
impl Fsm for SplineToolFsmState {
|
||||||
type ToolData = SplineToolData;
|
type ToolData = SplineToolData;
|
||||||
type ToolOptions = SplineOptions;
|
type ToolOptions = SplineOptions;
|
||||||
|
|
||||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||||
let ToolActionHandlerData {
|
let ToolActionHandlerData {
|
||||||
document, global_tool_data, input, ..
|
document,
|
||||||
|
global_tool_data,
|
||||||
|
input,
|
||||||
|
shape_editor,
|
||||||
|
..
|
||||||
} = tool_action_data;
|
} = tool_action_data;
|
||||||
|
|
||||||
let ToolMessage::Spline(event) = event else { return self };
|
let ToolMessage::Spline(event) = event else { return self };
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, SplineToolMessage::CanvasTransformed) => self,
|
(_, SplineToolMessage::CanvasTransformed) => self,
|
||||||
(SplineToolFsmState::Ready, SplineToolMessage::DragStart) => {
|
(_, SplineToolMessage::Overlays(mut overlay_context)) => {
|
||||||
|
path_endpoint_overlays(document, shape_editor, &mut overlay_context);
|
||||||
|
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
|
tool_data.cleanup();
|
||||||
|
tool_data.weight = tool_options.line_weight;
|
||||||
|
|
||||||
|
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||||
|
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
|
||||||
|
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||||
|
|
||||||
|
// Extend an endpoint of the selected path
|
||||||
|
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||||
|
if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) {
|
||||||
|
tool_data.layer = Some(layer);
|
||||||
|
tool_data.points.push((point, position));
|
||||||
|
tool_data.next_point = position;
|
||||||
|
tool_data.extend = true;
|
||||||
|
|
||||||
|
extend_spline(tool_data, true, responses);
|
||||||
|
|
||||||
|
return SplineToolFsmState::Drawing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new path in the same layer when shift is down
|
||||||
|
if input.keyboard.key(append_to_selected) {
|
||||||
|
let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface);
|
||||||
|
let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none());
|
||||||
|
if let Some(layer) = existing_layer {
|
||||||
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
|
let position = transform.inverse().transform_point2(input.mouse.position);
|
||||||
|
tool_data.next_point = position;
|
||||||
|
|
||||||
|
return SplineToolFsmState::Drawing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
responses.add(DocumentMessage::DeselectAllLayers);
|
||||||
|
|
||||||
let parent = document.new_layer_bounding_artboard(input);
|
let parent = document.new_layer_bounding_artboard(input);
|
||||||
|
|
||||||
tool_data.weight = tool_options.line_weight;
|
|
||||||
|
|
||||||
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist");
|
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist");
|
||||||
let path_node = path_node_type.default_node_template();
|
let path_node = path_node_type.default_node_template();
|
||||||
let spline_node_type = resolve_document_node_type("Splines from Points").expect("Spline from Points node does not exist");
|
let spline_node_type = resolve_document_node_type("Splines from Points").expect("Spline from Points node does not exist");
|
||||||
|
|
@ -228,33 +296,41 @@ impl Fsm for SplineToolFsmState {
|
||||||
SplineToolFsmState::Drawing
|
SplineToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {
|
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {
|
||||||
responses.add(DocumentMessage::EndTransaction);
|
// The first DragStop event will be ignored to prevent insertion of new point.
|
||||||
|
if tool_data.extend {
|
||||||
let Some(layer) = tool_data.layer else {
|
tool_data.extend = false;
|
||||||
|
return SplineToolFsmState::Drawing;
|
||||||
|
}
|
||||||
|
if tool_data.layer.is_none() {
|
||||||
return SplineToolFsmState::Ready;
|
return SplineToolFsmState::Ready;
|
||||||
};
|
};
|
||||||
let snapped_position = input.mouse.position;
|
if join_path(document, input.mouse.position, tool_data, responses) {
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
let pos = transform.inverse().transform_point2(snapped_position);
|
return SplineToolFsmState::Ready;
|
||||||
|
}
|
||||||
if tool_data.points.last().map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) {
|
tool_data.next_point = tool_data.snapped_point(document, input).snapped_point_document;
|
||||||
tool_data.next_point = pos;
|
if tool_data.points.last().map_or(true, |last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) {
|
||||||
|
extend_spline(tool_data, false, responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_spline(tool_data, false, responses);
|
|
||||||
|
|
||||||
SplineToolFsmState::Drawing
|
SplineToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => {
|
(SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => {
|
||||||
let Some(layer) = tool_data.layer else {
|
let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready };
|
||||||
return SplineToolFsmState::Ready;
|
let ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp);
|
||||||
};
|
let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore);
|
||||||
let snapped_position = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
|
||||||
let pos = transform.inverse().transform_point2(snapped_position);
|
|
||||||
tool_data.next_point = pos;
|
|
||||||
|
|
||||||
update_spline(tool_data, true, responses);
|
// Endpoints snapping
|
||||||
|
if let Some((_, _, point)) = join_point {
|
||||||
|
tool_data.next_point = point;
|
||||||
|
tool_data.snap_manager.clear_indicator();
|
||||||
|
} else {
|
||||||
|
let snapped_point = tool_data.snapped_point(document, input);
|
||||||
|
tool_data.next_point = snapped_point.snapped_point_document;
|
||||||
|
tool_data.snap_manager.update_indicator(snapped_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
extend_spline(tool_data, true, responses);
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
let messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()];
|
let messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()];
|
||||||
|
|
@ -262,6 +338,11 @@ impl Fsm for SplineToolFsmState {
|
||||||
|
|
||||||
SplineToolFsmState::Drawing
|
SplineToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
|
(_, SplineToolMessage::PointerMove) => {
|
||||||
|
tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
self
|
||||||
|
}
|
||||||
(SplineToolFsmState::Drawing, SplineToolMessage::PointerOutsideViewport) => {
|
(SplineToolFsmState::Drawing, SplineToolMessage::PointerOutsideViewport) => {
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
let _ = tool_data.auto_panning.shift_viewport(input, responses);
|
let _ = tool_data.auto_panning.shift_viewport(input, responses);
|
||||||
|
|
@ -283,11 +364,8 @@ impl Fsm for SplineToolFsmState {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_data.layer = None;
|
|
||||||
tool_data.preview_point = None;
|
|
||||||
tool_data.preview_segment = None;
|
|
||||||
tool_data.points.clear();
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
tool_data.cleanup();
|
||||||
|
|
||||||
SplineToolFsmState::Ready
|
SplineToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +382,10 @@ impl Fsm for SplineToolFsmState {
|
||||||
|
|
||||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
let hint_data = match self {
|
let hint_data = match self {
|
||||||
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Spline")])]),
|
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::Lmb, "Draw Spline"),
|
||||||
|
HintInfo::keys([Key::Shift], "Append to Selected Layer").prepend_plus(),
|
||||||
|
])]),
|
||||||
SplineToolFsmState::Drawing => HintData(vec![
|
SplineToolFsmState::Drawing => HintData(vec![
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]),
|
||||||
|
|
@ -320,7 +401,32 @@ impl Fsm for SplineToolFsmState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
|
/// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`.
|
||||||
|
fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque<Message>) -> bool {
|
||||||
|
let Some(&(endpoint, _)) = tool_data.points.last() else { return false };
|
||||||
|
|
||||||
|
let preview_point = tool_data.preview_point;
|
||||||
|
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||||
|
let selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||||
|
|
||||||
|
// Get the closest point to mouse position which is not preview_point or end_point.
|
||||||
|
let closest_point = closest_point(document, mouse_pos, PATH_JOIN_THRESHOLD, selected_layers, |cp| {
|
||||||
|
preview_point.is_some_and(|pp| pp == cp) || cp == endpoint
|
||||||
|
});
|
||||||
|
let Some((layer, join_point, _)) = closest_point else { return false };
|
||||||
|
|
||||||
|
// Last end point inserted was the preview point and segment therefore we delete it before joining the end_point & join_point.
|
||||||
|
delete_preview(tool_data, responses);
|
||||||
|
|
||||||
|
let points = [endpoint, join_point];
|
||||||
|
let id = SegmentId::generate();
|
||||||
|
let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] };
|
||||||
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
|
||||||
delete_preview(tool_data, responses);
|
delete_preview(tool_data, responses);
|
||||||
|
|
||||||
let Some(layer) = tool_data.layer else { return };
|
let Some(layer) = tool_data.layer else { return };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue