Rename handle mirroring to colinear

This commit is contained in:
Keavon Chambers
2024-03-14 07:44:55 -07:00
parent ea4f3d8bba
commit 5bca931813
20 changed files with 326 additions and 305 deletions
@@ -205,7 +205,7 @@ impl Fsm for EllipseToolFsmState {
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
let manipulator_groups = subpath.manipulator_groups().to_vec();
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses);
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, layer, true, responses);
graph_modification_utils::set_manipulator_colinear_handles_state(&manipulator_groups, layer, true, responses);
shape_data.layer = Some(layer);
let fill_color = tool_options.fill.active_color();
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::overlays::utility_functions::path_over
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_mirror_handles, get_subpaths};
use crate::messages::tool::common_functionality::graph_modification_utils::{get_colinear_manipulators, get_manipulator_from_id, get_subpaths};
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
@@ -34,19 +34,19 @@ pub enum PathToolMessage {
Delete,
DeleteAndBreakPath,
DragStop {
shift_mirror_distance: Key,
equidistant: Key,
},
Enter {
add_to_selection: Key,
},
Escape,
FlipSharp,
FlipSmoothSharp,
GRS {
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
key: Key,
},
ManipulatorAngleMakeSharp,
ManipulatorAngleMakeSmooth,
ManipulatorMakeHandlesFree,
ManipulatorMakeHandlesColinear,
MouseDown {
ctrl: Key,
shift: Key,
@@ -126,23 +126,37 @@ impl LayoutHolder for PathTool {
let related_seperator = Separator::new(SeparatorType::Related).widget_holder();
let unrelated_seperator = Separator::new(SeparatorType::Unrelated).widget_holder();
let manipulator_angle_options = vec![
RadioEntryData::new("smooth").label("Smooth").on_update(|_| PathToolMessage::ManipulatorAngleMakeSmooth.into()),
RadioEntryData::new("sharp").label("Sharp").on_update(|_| PathToolMessage::ManipulatorAngleMakeSharp.into()),
];
let manipulator_angle_index = manipulator_angle.and_then(|angle| match angle {
ManipulatorAngle::Smooth => Some(0),
ManipulatorAngle::Sharp => Some(1),
let colinear_handles_tooltip = "Ensures both handles remain 180° apart";
let colinear_handles_state = manipulator_angle.and_then(|angle| match angle {
ManipulatorAngle::Colinear => Some(true),
ManipulatorAngle::Free => Some(false),
ManipulatorAngle::Mixed => None,
});
let manipulator_angle_radio = RadioInput::new(manipulator_angle_options)
})
// TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state
.unwrap_or_default();
let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state)
.disabled(self.tool_data.selection_status.is_none())
.selected_index(manipulator_angle_index)
.on_update(|&CheckboxInput { checked, .. }| {
if checked {
PathToolMessage::ManipulatorMakeHandlesColinear.into()
} else {
PathToolMessage::ManipulatorMakeHandlesFree.into()
}
})
.tooltip(colinear_handles_tooltip)
.widget_holder();
let colinear_handles_label = TextLabel::new("Colinear Handles").tooltip(colinear_handles_tooltip).widget_holder();
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![x_location, related_seperator, y_location, unrelated_seperator, manipulator_angle_radio],
widgets: vec![
x_location,
related_seperator.clone(),
y_location,
unrelated_seperator,
colinear_handle_checkbox,
related_seperator,
colinear_handles_label,
],
}]))
}
}
@@ -164,7 +178,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
match self.fsm_state {
Ready => actions!(PathToolMessageDiscriminant;
FlipSharp,
FlipSmoothSharp,
MouseDown,
Delete,
NudgeSelectedPoints,
@@ -177,7 +191,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
Dragging => actions!(PathToolMessageDiscriminant;
Escape,
RightClick,
FlipSharp,
FlipSmoothSharp,
DragStop,
PointerMove,
Delete,
@@ -185,7 +199,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
DeleteAndBreakPath,
),
DrawingBox => actions!(PathToolMessageDiscriminant;
FlipSharp,
FlipSmoothSharp,
DragStop,
PointerMove,
Delete,
@@ -378,7 +392,7 @@ impl PathToolData {
// Check if the alt key has just been pressed
if alt && !self.alt_debounce {
self.opposing_handle_lengths = None;
shape_editor.toggle_handle_mirroring_on_selected(responses);
shape_editor.toggle_colinear_handles_state_on_selected(responses);
}
self.alt_debounce = alt;
@@ -545,21 +559,21 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready
}
// Mouse up
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { equidistant }) => {
let equidistant = input.keyboard.get(equidistant as usize);
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !equidistant);
}
responses.add(OverlaysMessage::Draw);
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Ready
}
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
(_, PathToolMessage::DragStop { equidistant }) => {
let equidistant = input.keyboard.get(equidistant as usize);
let nearest_point = shape_editor
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
@@ -567,7 +581,7 @@ impl Fsm for PathToolFsmState {
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {
shape_editor.deselect_all_points();
@@ -598,9 +612,9 @@ impl Fsm for PathToolFsmState {
shape_editor.delete_point_and_break_path(&document.network, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::FlipSharp) => {
(_, PathToolMessage::FlipSmoothSharp) => {
if !tool_data.double_click_handled {
shape_editor.flip_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
shape_editor.flip_smooth_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
responses.add(PathToolMessage::SelectedPointUpdated);
}
self
@@ -641,16 +655,16 @@ impl Fsm for PathToolFsmState {
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
self
}
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.set_handle_mirroring_on_selected(true, responses);
shape_editor.smooth_selected_groups(responses, &document.network);
shape_editor.set_colinear_handles_state_on_selected(true, responses);
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, &document.network);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.set_handle_mirroring_on_selected(false, responses);
shape_editor.set_colinear_handles_state_on_selected(false, responses);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
@@ -663,6 +677,8 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
// TODO: Only show if at least one anchor is selected, and dynamically show either "Smooth" or "Sharp" based on the current state
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, "Make Anchor Smooth/Sharp")]),
// TODO: Only show the following hints if at least one point is selected
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]),
@@ -677,15 +693,13 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Dragging => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![
// TODO: Make hint dynamically say "Make Handle Smooth" or "Make Handle Sharp" based on its current state
// TODO: Switch this to the "S" key
// TODO: Only show this if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown
HintInfo::keys([Key::Alt], "Toggle Smooth/Sharp Handles"),
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line)
// TODO: Show this only when a handle is being dragged
HintInfo::keys([Key::Shift], "Equidistant Handles (Smooth Only)"),
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged)
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged)
// TODO: Switch this to the "S" key. Also, make the hint dynamically say "Make Colinear" or "Make Not Colinear" based on its current state. And only
// TODO: show this hint if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown.
HintInfo::keys([Key::Alt], "Toggle Colinear Handles"),
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line). And show this only when a handle is being dragged.
HintInfo::keys([Key::Shift], "Equidistant Handles"),
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged).
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged).
]),
]),
PathToolFsmState::DrawingBox => HintData(vec![
@@ -761,28 +775,31 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
return SelectionStatus::None;
};
let Some(subpaths) = get_subpaths(layer, document_network) else {
return SelectionStatus::None;
};
let Some(mirror) = get_mirror_handles(layer, document_network) else {
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
return SelectionStatus::None;
};
let Some(point) = shape_state.selected_points().next() else {
return SelectionStatus::None;
};
let Some(group) = get_manipulator_from_id(subpaths, point.group) else {
let Some(manipulator) = get_manipulator_from_id(subpaths, point.group) else {
return SelectionStatus::None;
};
let Some(local_position) = point.manipulator_type.get_position(group) else {
let Some(local_position) = point.manipulator_type.get_position(manipulator) else {
return SelectionStatus::None;
};
let manipulator_angle = if mirror.contains(&point.group) { ManipulatorAngle::Smooth } else { ManipulatorAngle::Sharp };
let coordinates = document_metadata.transform_to_document(layer).transform_point2(local_position);
let manipulator_angle = if colinear_manipulators.contains(&point.group) {
ManipulatorAngle::Colinear
} else {
ManipulatorAngle::Free
};
return SelectionStatus::One(SingleSelectedPoint {
coordinates: document_metadata.transform_to_document(layer).transform_point2(local_position),
coordinates,
layer,
id: *point,
manipulator_angle,
@@ -200,7 +200,7 @@ struct PenToolData {
layer: Option<LayerNodeIdentifier>,
subpath_index: usize,
snap_manager: SnapManager,
should_mirror: bool,
colinear_handles: bool,
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
from_start: bool,
angle: f64,
@@ -212,21 +212,16 @@ impl PenToolData {
self.from_start = from_start;
self.subpath_index = subpath_index;
// Stop the handles on the first point from mirroring
let Some(subpaths) = get_subpaths(layer, &document.network) else {
return;
};
let Some(subpaths) = get_subpaths(layer, &document.network) else { return };
let manipulator_groups = subpaths[subpath_index].manipulator_groups();
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else {
return;
};
let first_or_last = if from_start { manipulator_groups.first() } else { manipulator_groups.last() };
let Some(last_handle) = first_or_last else { return };
let id = last_handle.id;
// Stop the handles on the first point from being colinear
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: last_handle.id,
mirror_angle: false,
},
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
});
}
@@ -270,53 +265,56 @@ impl PenToolData {
self.subpath_index = 0;
}
// TODO: tooltip / user documentation?
/// If you place the anchor on top of the previous anchor then you break the mirror
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<()> {
// Get subpath
let layer = self.layer?;
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
fn bend_from_previous_point(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) {
(|| -> Option<()> {
// Get subpath
let layer = self.layer?;
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter();
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
// Get the last manipulator group and the one previous to that
let mut manipulator_groups = subpath.manipulator_groups().iter();
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
// Get correct handle types
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
// Get correct handle types
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
// Get manipulator points
let last_anchor = last_manipulator_group.anchor;
let previous_anchor = previous_manipulator_group.anchor;
// Get manipulator points
let last_anchor = last_manipulator_group.anchor;
let previous_anchor = previous_manipulator_group.anchor;
// Break the control
let transform = document.metadata.document_to_viewport * transform;
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if !on_top {
return None;
}
// Remove the point that has just been placed
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
});
// Break the control
let transform = document.metadata.document_to_viewport * transform;
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if !on_top {
return None;
}
// Move the in handle of the previous anchor to on top of the previous position
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
});
// Remove the point that has just been placed
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
});
// Stop the handles on the last point from mirroring
let id = previous_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
});
// Move the in handle of the previous anchor to on top of the previous position
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
});
self.should_mirror = false;
None
// Stop the handles on the last point from being colinear
let id = previous_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
});
self.colinear_handles = false;
None
})();
}
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
@@ -357,11 +355,11 @@ impl PenToolData {
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
});
// Stop the handles on the first point from mirroring
// Stop the handles on the first point from being colinear
let id = first_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
});
// Remove the point that has just been placed
@@ -409,11 +407,11 @@ impl PenToolData {
// Get manipulator points
let last_anchor = last_manipulator_group.anchor;
let should_mirror = !modifiers.break_handle && self.should_mirror;
let colinear = !modifiers.break_handle && self.colinear_handles;
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
let pos = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, should_mirror, mouse, Some(last_anchor), false);
if !pos.is_finite() {
let position = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, colinear, mouse, Some(last_anchor), false);
if !position.is_finite() {
return Some(PenToolFsmState::DraggingHandle);
}
@@ -421,25 +419,25 @@ impl PenToolData {
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
responses.add(GraphOperationMessage::Vector {
layer: self.layer?,
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
// Mirror handle of last segment
if should_mirror {
// Place the previous anchor's in handle at the opposing position
if colinear {
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
let pos = last_anchor - (pos - last_anchor);
let position = last_anchor - (position - last_anchor);
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
responses.add(GraphOperationMessage::Vector {
layer: self.layer?,
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
}
// Update the mirror status of the currently modifying point
// Update the colinear handles status of the currently modifying point
let id = last_manipulator_group.id;
responses.add(GraphOperationMessage::Vector {
layer: self.layer?,
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror },
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear },
});
Some(PenToolFsmState::DraggingHandle)
@@ -487,7 +485,7 @@ impl PenToolData {
}
/// Snap the angle of the line from relative to position if the key is pressed.
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, mirror: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
let document = snap_data.document;
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
let snap = &mut self.snap_manager;
@@ -509,7 +507,7 @@ impl PenToolData {
};
let near_point = SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone());
let far_point = SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors);
if mirror {
if colinear {
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None);
document_pos = if snapped_far.other_snap_better(&snapped) {
@@ -523,7 +521,7 @@ impl PenToolData {
document_pos = snapped.snapped_point_document;
snap.update_indicator(snapped);
}
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| mirror) {
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| colinear) {
let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), None, false);
let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), None, false);
document_pos = if snapped_far.other_snap_better(&snapped) {
@@ -641,8 +639,8 @@ impl Fsm for PenToolFsmState {
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
// Disable this tool's mirroring
tool_data.should_mirror = false;
// Prevent the initial point from having a colinear in handle while dragging the out handle
tool_data.colinear_handles = false;
// Perform extension of an existing path
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
@@ -663,11 +661,11 @@ impl Fsm for PenToolFsmState {
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.check_break(document, transform, responses);
tool_data.bend_from_previous_point(document, transform, responses);
PenToolFsmState::DraggingHandle
}
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
tool_data.should_mirror = true;
tool_data.colinear_handles = true;
tool_data.finish_placing_handle(document, transform, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
}
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
@@ -677,6 +675,7 @@ impl Fsm for PenToolFsmState {
break_handle: input.keyboard.key(break_handle),
};
let snap_data = SnapData::new(document, input);
let state = tool_data
.drag_handle(snap_data, transform, input.mouse.position, modifiers, responses)
.unwrap_or(PenToolFsmState::Ready);
@@ -772,6 +771,10 @@ impl Fsm for PenToolFsmState {
]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Sharp Point"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Smooth Point")]),
HintGroup(vec![
HintInfo::mouse(MouseMotion::Lmb, ""),
HintInfo::mouse(MouseMotion::LmbDrag, "Bend from Prev. Point").prepend_slash(),
]),
]),
PenToolFsmState::DraggingHandle => HintData(vec![
HintGroup(vec![
@@ -780,6 +783,7 @@ impl Fsm for PenToolFsmState {
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
// TODO: Only show this if the handle being dragged is colinear, so don't show this when bending from the previous point (by clicking and dragging from the previously placed anchor)
HintGroup(vec![HintInfo::keys([Key::Alt], "Bend Handle")]),
]),
};
@@ -343,7 +343,7 @@ fn update_spline(tool_data: &SplineToolData, show_preview: bool, responses: &mut
return;
};
graph_modification_utils::set_manipulator_mirror_angle(subpath.manipulator_groups(), layer, true, responses);
graph_modification_utils::set_manipulator_colinear_handles_state(subpath.manipulator_groups(), layer, true, responses);
let subpaths = vec![subpath];
let modification = VectorDataModification::UpdateSubpaths { subpaths };
responses.add_front(GraphOperationMessage::Vector { layer, modification });