Improve the Pen tool's colinearity and equidistance controls (#2242)

* basic implementation done now refactor

* fixed overlays refactoring need to fix colinear(update it)

* more_refactoring ,only toggle C for grs to be done(if required)

* cleanup

* cleanup

* more formatting checks

* refactoring alt fixed hints fixed

* code-review-changes

* path-tool-tab-fix

* fixed bugs

* some refactor

* fixed ctrl_snap

* added lock-overlays and fixed grs bug

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0SlowPoke0 2025-02-05 08:45:43 +05:30 committed by GitHub
parent 133d872a9f
commit 41ee1cf8bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 354 additions and 78 deletions

View File

@ -212,7 +212,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { direct_insert_without_sliding: Control, extend_selection: Shift, lasso_select: Control }),
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
@ -254,7 +254,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=ToolMessage::Path(PathToolMessage::ClosePath)),
//
// PenToolMessage
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control}),
entry!(PointerMove; refresh_keys=[Control, Alt, Shift, KeyC], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control, colinear: KeyC }),
entry!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart { append_to_selected: Shift }),
entry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop),
entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Confirm),

View File

@ -1185,16 +1185,9 @@ impl Fsm for PathToolFsmState {
.push(HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]));
let drag_anchor = HintInfo::keys([Key::Space], "Drag Anchor");
let point_select_state_hint_group = match dragging_state.point_select_state {
PointSelectState::HandleNoPair => {
let mut hints = vec![drag_anchor];
hints.push(HintInfo::keys([Key::Shift], "Snap 15°"));
hints.push(HintInfo::keys([Key::Control], "Lock Angle"));
hints
}
PointSelectState::HandleWithPair => {
let mut hints = vec![drag_anchor];
hints.push(HintInfo::keys([Key::Tab], "Swap Selected Handles"));
let toggle_group = match dragging_state.point_select_state {
PointSelectState::HandleNoPair | PointSelectState::HandleWithPair => {
let mut hints = vec![HintInfo::keys([Key::Tab], "Swap Selected Handles")];
hints.push(HintInfo::keys(
[Key::KeyC],
if colinear == ManipulatorAngle::Colinear {
@ -1203,18 +1196,40 @@ impl Fsm for PathToolFsmState {
"Make Handles Colinear"
},
));
hints
}
PointSelectState::Anchor => Vec::new(),
};
let hold_group = match dragging_state.point_select_state {
PointSelectState::HandleNoPair => {
let mut hints = vec![];
if colinear != ManipulatorAngle::Free {
hints.push(HintInfo::keys([Key::Alt], "Equidistant Handles"));
}
hints.push(HintInfo::keys([Key::Shift], "Snap 15°"));
hints.push(HintInfo::keys([Key::Control], "Lock Angle"));
hints.push(drag_anchor);
hints
}
PointSelectState::HandleWithPair => {
let mut hints = vec![];
if colinear != ManipulatorAngle::Free {
hints.push(HintInfo::keys([Key::Alt], "Equidistant Handles"));
}
hints.push(HintInfo::keys([Key::Shift], "Snap 15°"));
hints.push(HintInfo::keys([Key::Control], "Lock Angle"));
hints.push(drag_anchor);
hints
}
PointSelectState::Anchor => Vec::new(),
};
if !point_select_state_hint_group.is_empty() {
dragging_hint_data.0.push(HintGroup(point_select_state_hint_group));
if !toggle_group.is_empty() {
dragging_hint_data.0.push(HintGroup(toggle_group));
}
if !hold_group.is_empty() {
dragging_hint_data.0.push(HintGroup(hold_group));
}
dragging_hint_data

View File

@ -15,7 +15,7 @@ use bezier_rs::{Bezier, BezierHandles};
use graph_craft::document::NodeId;
use graphene_core::vector::{PointId, VectorModificationType};
use graphene_core::Color;
use graphene_std::vector::{HandleId, SegmentId};
use graphene_std::vector::{HandleId, ManipulatorPointId, SegmentId, VectorData};
#[derive(Default)]
pub struct PenTool {
@ -56,8 +56,8 @@ pub enum PenToolMessage {
Confirm,
DragStart { append_to_selected: Key },
DragStop,
PointerMove { snap_angle: Key, break_handle: Key, lock_angle: Key },
PointerOutsideViewport { snap_angle: Key, break_handle: Key, lock_angle: Key },
PointerMove { snap_angle: Key, break_handle: Key, lock_angle: Key, colinear: Key },
PointerOutsideViewport { snap_angle: Key, break_handle: Key, lock_angle: Key, colinear: Key },
Redo,
Undo,
UpdateOptions(PenOptionsUpdate),
@ -71,7 +71,7 @@ pub enum PenToolMessage {
enum PenToolFsmState {
#[default]
Ready,
DraggingHandle,
DraggingHandle(HandleMode),
PlacingAnchor,
GRSHandle,
}
@ -174,7 +174,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
PointerMove,
FinalPosition
),
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant;
PenToolFsmState::DraggingHandle(_) | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant;
DragStart,
DragStop,
PointerMove,
@ -203,6 +203,7 @@ struct ModifierState {
snap_angle: bool,
lock_angle: bool,
break_handle: bool,
colinear: bool,
}
#[derive(Clone, Debug)]
struct LastPoint {
@ -212,6 +213,17 @@ struct LastPoint {
handle_start: DVec2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum HandleMode {
/// Pressing 'C' breaks colinearity
Free,
/// Pressing 'Alt': Handle length is locked
#[default]
ColinearLocked,
/// Pressing 'Alt': Handles are equidistant
ColinearEquidistant,
}
#[derive(Clone, Debug, Default)]
struct PenToolData {
snap_manager: SnapManager,
@ -222,6 +234,9 @@ struct PenToolData {
next_handle_start: DVec2,
g1_continuous: bool,
toggle_colinear_debounce: bool,
segment_end_before_bent: Option<SegmentId>,
angle: f64,
auto_panning: AutoPanning,
@ -229,7 +244,11 @@ struct PenToolData {
buffering_merged_vector: bool,
before_grs_pos: DVec2,
previous_handle_start_pos: DVec2,
previous_handle_end_pos: Option<DVec2>,
alt_press: bool,
handle_mode: HandleMode,
}
impl PenToolData {
fn latest_point(&self) -> Option<&LastPoint> {
@ -265,19 +284,24 @@ impl PenToolData {
}
/// 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, snap_data: SnapData, transform: DAffine2) {
fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2, layer: LayerNodeIdentifier) {
self.g1_continuous = true;
let document = snap_data.document;
self.next_handle_start = self.next_point;
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
// Break the control
let Some(last_pos) = self.latest_point().map(|point| point.pos) else { return };
let transform = document.metadata().document_to_viewport * transform;
let on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if on_top {
if let Some(point) = self.latest_point_mut() {
point.in_segment = None;
}
self.segment_end_before_bent = vector_data.segment_domain.ids().last().copied();
self.handle_mode = HandleMode::Free;
self.handle_end = None;
}
}
@ -326,6 +350,7 @@ impl PenToolData {
let points = [start, end];
let id = SegmentId::generate();
self.segment_end_before_bent = Some(id);
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -351,19 +376,107 @@ impl PenToolData {
Some(if close_subpath { PenToolFsmState::Ready } else { PenToolFsmState::PlacingAnchor })
}
fn drag_handle(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
let colinear = !self.modifiers.break_handle && self.handle_end.is_some();
fn drag_handle(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>, layer: Option<LayerNodeIdentifier>) -> Option<PenToolFsmState> {
let colinear = (self.handle_mode == HandleMode::ColinearEquidistant && self.modifiers.break_handle) || (self.handle_mode == HandleMode::ColinearLocked && !self.modifiers.break_handle);
let document = snap_data.document;
self.next_handle_start = self.compute_snapped_angle(snap_data, transform, colinear, mouse, Some(self.next_point), false);
if let Some(handle_end) = self.handle_end.as_mut().filter(|_| colinear) {
*handle_end = self.next_point * 2. - self.next_handle_start;
self.g1_continuous = true;
} else {
self.g1_continuous = false;
let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) };
let vector_data = document.network_interface.compute_modified_vector(layer)?;
match self.handle_mode {
HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {
self.g1_continuous = true;
self.colinear(responses, layer, self.next_handle_start, self.next_point, &vector_data);
self.adjust_handle_length(responses, layer, &vector_data);
}
HandleMode::Free => {
self.g1_continuous = false;
}
}
responses.add(OverlaysMessage::Draw);
Some(PenToolFsmState::DraggingHandle)
Some(PenToolFsmState::DraggingHandle(self.handle_mode))
}
/// Makes the opposite handle equidistant or locks its length.
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) {
let Some(latest) = self.latest_point() else { return };
let anchor_pos = latest.pos;
match self.handle_mode {
HandleMode::ColinearEquidistant => self.adjust_equidistant_handle(anchor_pos, responses, layer, vector_data),
HandleMode::ColinearLocked => self.adjust_locked_length_handle(anchor_pos, responses, layer),
HandleMode::Free => {} // No adjustments needed in free mode
}
}
fn colinear(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, handle_start: DVec2, anchor_point: DVec2, vector_data: &VectorData) {
let Some(direction) = (anchor_point - handle_start).try_normalize() else {
log::trace!("Skipping colinear adjustment: handle_start and anchor_point are too close!");
return;
};
let handle_offset = if let Some(handle_end) = self.handle_end {
(handle_end - anchor_point).length()
} else {
let Some(segment) = self.segment_end_before_bent else { return };
let end_handle = ManipulatorPointId::EndHandle(segment);
let Some(end_handle) = end_handle.get_position(vector_data) else { return };
(end_handle - anchor_point).length()
};
let new_handle_position = anchor_point + handle_offset * direction;
self.update_handle_position(new_handle_position, anchor_point, responses, layer);
}
fn adjust_equidistant_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) {
if self.modifiers.break_handle {
self.store_handle(vector_data);
self.alt_press = true;
let new_position = self.next_point * 2. - self.next_handle_start;
self.update_handle_position(new_position, anchor_pos, responses, layer);
} else {
self.restore_previous_handle(anchor_pos, responses, layer);
}
}
fn adjust_locked_length_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
if !self.modifiers.break_handle {
let new_position = self.next_point * 2. - self.next_handle_start;
self.update_handle_position(new_position, anchor_pos, responses, layer);
}
}
/// Temporarily stores the opposite handle position to revert back when Alt is released in equidistant mode.
fn store_handle(&mut self, vector_data: &VectorData) {
if !self.alt_press {
self.previous_handle_end_pos = self.handle_end.or_else(|| {
let segment = self.segment_end_before_bent?;
ManipulatorPointId::EndHandle(segment).get_position(vector_data)
});
}
}
fn restore_previous_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
if self.alt_press {
self.alt_press = false;
if let Some(previous_handle) = self.previous_handle_end_pos {
self.update_handle_position(previous_handle, anchor_pos, responses, layer);
}
self.previous_handle_end_pos = None; // Reset storage
}
}
fn update_handle_position(&mut self, new_position: DVec2, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
if let Some(handle) = self.handle_end.as_mut() {
*handle = new_position;
} else {
let Some(segment) = self.segment_end_before_bent else { return };
let relative_position = new_position - anchor_pos;
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
@ -455,7 +568,7 @@ impl PenToolData {
}
if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)) {
if (relative - document_pos) != DVec2::ZERO {
if (relative - document_pos) != DVec2::ZERO && (relative - document_pos).length_squared() > f64::EPSILON * 100. {
self.angle = -(relative - document_pos).angle_to(DVec2::X)
}
}
@ -571,36 +684,67 @@ impl Fsm for PenToolFsmState {
let ToolMessage::Pen(event) = event else { return self };
match (self, event) {
(PenToolFsmState::PlacingAnchor | PenToolFsmState::GRSHandle, PenToolMessage::GRS { grab, rotate, scale }) => {
let Some(latest) = tool_data.latest_point_mut() else { return PenToolFsmState::PlacingAnchor };
let Some(layer) = layer else { return PenToolFsmState::PlacingAnchor };
if latest.handle_start != latest.pos {
let viewport = document.metadata().transform_to_viewport(layer);
let last_point = viewport.transform_point2(latest.pos);
let handle = viewport.transform_point2(latest.handle_start);
if input.keyboard.key(grab) {
responses.add(TransformLayerMessage::BeginGrabPen { last_point, handle });
} else if input.keyboard.key(rotate) {
responses.add(TransformLayerMessage::BeginRotatePen { last_point, handle });
} else if input.keyboard.key(scale) {
responses.add(TransformLayerMessage::BeginScalePen { last_point, handle });
}
tool_data.before_grs_pos = latest.handle_start;
let Some(latest) = tool_data.latest_point() else { return PenToolFsmState::PlacingAnchor };
if latest.handle_start == latest.pos {
return PenToolFsmState::PlacingAnchor;
}
let viewport = document.metadata().transform_to_viewport(layer);
let last_point = viewport.transform_point2(latest.pos);
let handle = viewport.transform_point2(latest.handle_start);
if input.keyboard.key(grab) {
responses.add(TransformLayerMessage::BeginGrabPen { last_point, handle });
} else if input.keyboard.key(rotate) {
responses.add(TransformLayerMessage::BeginRotatePen { last_point, handle });
} else if input.keyboard.key(scale) {
responses.add(TransformLayerMessage::BeginScalePen { last_point, handle });
}
tool_data.previous_handle_start_pos = latest.handle_start;
// Store the handle_end position
let segment = tool_data.segment_end_before_bent;
if let Some(segment) = segment {
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
tool_data.previous_handle_end_pos = ManipulatorPointId::EndHandle(segment).get_position(&vector_data);
}
PenToolFsmState::GRSHandle
}
(PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position: final_pos }) => {
(PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => {
let Some(layer) = layer else { return PenToolFsmState::GRSHandle };
let vector_data = document.network_interface.compute_modified_vector(layer);
let Some(vector_data) = vector_data else { return PenToolFsmState::GRSHandle };
if let Some(latest_pt) = tool_data.latest_point_mut() {
let layer_space_to_viewport = document.metadata().transform_to_viewport(layer);
let final_pos = layer_space_to_viewport.inverse().transform_point2(final_pos);
let final_pos = layer_space_to_viewport.inverse().transform_point2(final_position);
latest_pt.handle_start = final_pos;
}
// Making the end handle colinear
match tool_data.handle_mode {
HandleMode::Free => {}
HandleMode::ColinearEquidistant | HandleMode::ColinearLocked => {
if let Some((latest, segment)) = tool_data.latest_point().zip(tool_data.segment_end_before_bent) {
let handle = ManipulatorPointId::EndHandle(segment).get_position(&vector_data);
let Some(handle) = handle else { return PenToolFsmState::GRSHandle };
let Some(direction) = (latest.pos - latest.handle_start).try_normalize() else {
log::trace!("Skipping handle adjustment: latest.pos and latest.handle_start are too close!");
return PenToolFsmState::GRSHandle;
};
let relative_distance = (handle - latest.pos).length();
let relative_position = relative_distance * direction;
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
responses.add(OverlaysMessage::Draw);
PenToolFsmState::GRSHandle
@ -614,6 +758,7 @@ impl Fsm for PenToolFsmState {
snap_angle: Key::Control,
break_handle: Key::Alt,
lock_angle: Key::Shift,
colinear: Key::KeyC,
});
PenToolFsmState::PlacingAnchor
@ -622,7 +767,9 @@ impl Fsm for PenToolFsmState {
tool_data.next_point = input.mouse.position;
tool_data.next_handle_start = input.mouse.position;
let previous = tool_data.before_grs_pos;
let Some(layer) = layer else { return PenToolFsmState::GRSHandle };
let previous = tool_data.previous_handle_start_pos;
if let Some(latest) = tool_data.latest_point_mut() {
latest.handle_start = previous;
}
@ -632,8 +779,16 @@ impl Fsm for PenToolFsmState {
snap_angle: Key::Control,
break_handle: Key::Alt,
lock_angle: Key::Shift,
colinear: Key::KeyC,
});
// Set the handle-end back to original position
if let Some(((latest, segment), handle_end)) = tool_data.latest_point().zip(tool_data.segment_end_before_bent).zip(tool_data.previous_handle_end_pos) {
let relative = handle_end - latest.pos;
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position: relative };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
PenToolFsmState::PlacingAnchor
}
(_, PenToolMessage::SelectionChanged) => {
@ -680,9 +835,14 @@ impl Fsm for PenToolFsmState {
// Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out)
overlay_context.line(next_anchor, handle_end, None);
if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle {
// Draw the line between the currently-being-placed anchor and last-placed point (Lock angle bent overlays)
overlay_context.dashed_line(anchor_start, next_anchor, None, Some(4.), Some(4.), Some(0.5));
}
path_overlays(document, shape_editor, &mut overlay_context);
if self == PenToolFsmState::DraggingHandle && valid(next_anchor, handle_end) {
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) {
// Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out)
overlay_context.manipulator_handle(handle_end, false, None);
}
@ -696,12 +856,12 @@ impl Fsm for PenToolFsmState {
path_overlays(document, shape_editor, &mut overlay_context);
}
if self == PenToolFsmState::DraggingHandle && valid(next_anchor, next_handle_start) {
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) {
// Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor)
overlay_context.manipulator_handle(next_handle_start, false, None);
}
if self == PenToolFsmState::DraggingHandle {
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) {
// Draw the anchor square for the most recently placed anchor
overlay_context.manipulator_anchor(next_anchor, false, None);
}
@ -720,11 +880,11 @@ impl Fsm for PenToolFsmState {
}
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.handle_mode = HandleMode::Free;
tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected), preferences);
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
PenToolFsmState::DraggingHandle
PenToolFsmState::DraggingHandle(tool_data.handle_mode)
}
(_, PenToolMessage::AddPointLayerPosition { layer, viewport }) => {
tool_data.add_point_layer_position(document, responses, layer, viewport);
@ -739,13 +899,15 @@ impl Fsm for PenToolFsmState {
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);
// Early return if the buffer was started and this message is being run again after the buffer (so that place_anchor updates the state with the newly merged vector)
if tool_data.buffering_merged_vector {
tool_data.buffering_merged_vector = false;
tool_data.bend_from_previous_point(SnapData::new(document, input), transform);
tool_data.handle_mode = HandleMode::ColinearLocked;
tool_data.bend_from_previous_point(SnapData::new(document, input), transform, layer.unwrap());
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses);
tool_data.buffering_merged_vector = false;
PenToolFsmState::DraggingHandle
PenToolFsmState::DraggingHandle(tool_data.handle_mode)
} else {
if tool_data.handle_end.is_some() {
responses.add(DocumentMessage::StartTransaction);
@ -774,37 +936,80 @@ impl Fsm for PenToolFsmState {
last_point.handle_start = last_point.pos;
responses.add(OverlaysMessage::Draw);
} else {
log::warn!("No latest point available to modify handle_start.");
log::trace!("No latest point available to modify handle_start.");
}
self
}
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data
(PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => tool_data
.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses)
.unwrap_or(PenToolFsmState::PlacingAnchor),
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
(
PenToolFsmState::DraggingHandle(_),
PenToolMessage::PointerMove {
snap_angle,
break_handle,
lock_angle,
colinear,
},
) => {
tool_data.modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
colinear: input.keyboard.key(colinear),
};
let snap_data = SnapData::new(document, input);
let state = tool_data.drag_handle(snap_data, transform, input.mouse.position, responses).unwrap_or(PenToolFsmState::Ready);
if tool_data.modifiers.colinear && !tool_data.toggle_colinear_debounce {
tool_data.handle_mode = match tool_data.handle_mode {
HandleMode::Free => HandleMode::ColinearEquidistant,
HandleMode::ColinearEquidistant | HandleMode::ColinearLocked => HandleMode::Free,
};
tool_data.toggle_colinear_debounce = true;
}
if !tool_data.modifiers.colinear {
tool_data.toggle_colinear_debounce = false;
}
let state = tool_data.drag_handle(snap_data, transform, input.mouse.position, responses, layer).unwrap_or(PenToolFsmState::Ready);
// Auto-panning
let messages = [
PenToolMessage::PointerOutsideViewport { snap_angle, break_handle, lock_angle }.into(),
PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }.into(),
PenToolMessage::PointerOutsideViewport {
snap_angle,
break_handle,
lock_angle,
colinear,
}
.into(),
PenToolMessage::PointerMove {
snap_angle,
break_handle,
lock_angle,
colinear,
}
.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
state
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
(
PenToolFsmState::PlacingAnchor,
PenToolMessage::PointerMove {
snap_angle,
break_handle,
lock_angle,
colinear,
},
) => {
tool_data.alt_press = false;
tool_data.modifiers = ModifierState {
snap_angle: input.keyboard.key(snap_angle),
lock_angle: input.keyboard.key(lock_angle),
break_handle: input.keyboard.key(break_handle),
colinear: input.keyboard.key(colinear),
};
let state = tool_data
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses)
@ -812,8 +1017,20 @@ impl Fsm for PenToolFsmState {
// Auto-panning
let messages = [
PenToolMessage::PointerOutsideViewport { snap_angle, break_handle, lock_angle }.into(),
PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }.into(),
PenToolMessage::PointerOutsideViewport {
snap_angle,
break_handle,
lock_angle,
colinear,
}
.into(),
PenToolMessage::PointerMove {
snap_angle,
break_handle,
lock_angle,
colinear,
}
.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
@ -824,11 +1041,11 @@ impl Fsm for PenToolFsmState {
responses.add(OverlaysMessage::Draw);
self
}
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerOutsideViewport { .. }) => {
(PenToolFsmState::DraggingHandle(mode), PenToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses);
PenToolFsmState::DraggingHandle
PenToolFsmState::DraggingHandle(mode)
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
@ -836,17 +1053,37 @@ impl Fsm for PenToolFsmState {
PenToolFsmState::PlacingAnchor
}
(state, PenToolMessage::PointerOutsideViewport { snap_angle, break_handle, lock_angle }) => {
(
state,
PenToolMessage::PointerOutsideViewport {
snap_angle,
break_handle,
lock_angle,
colinear,
},
) => {
// Auto-panning
let messages = [
PenToolMessage::PointerOutsideViewport { snap_angle, break_handle, lock_angle }.into(),
PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }.into(),
PenToolMessage::PointerOutsideViewport {
snap_angle,
break_handle,
lock_angle,
colinear,
}
.into(),
PenToolMessage::PointerMove {
snap_angle,
break_handle,
lock_angle,
colinear,
}
.into(),
];
tool_data.auto_panning.stop(&messages, responses);
state
}
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => {
(PenToolFsmState::DraggingHandle(..) | PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => {
responses.add(DocumentMessage::EndTransaction);
tool_data.handle_end = None;
tool_data.latest_points.clear();
@ -866,7 +1103,7 @@ impl Fsm for PenToolFsmState {
PenToolFsmState::Ready
}
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Undo) => {
(PenToolFsmState::DraggingHandle(..) | PenToolFsmState::PlacingAnchor, PenToolMessage::Undo) => {
if tool_data.point_index > 0 {
tool_data.point_index -= 1;
tool_data
@ -906,16 +1143,40 @@ impl Fsm for PenToolFsmState {
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 Prev. Point").prepend_slash()]),
]),
PenToolFsmState::DraggingHandle => HintData(vec![
HintGroup(vec![
PenToolFsmState::DraggingHandle(mode) => {
let mut dragging_hint_data = HintData(Vec::new());
dragging_hint_data.0.push(HintGroup(vec![
HintInfo::mouse(MouseMotion::Rmb, ""),
HintInfo::keys([Key::Escape], "").prepend_slash(),
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")]),
]),
]));
let toggle_group = match mode {
HandleMode::Free => {
vec![HintInfo::keys([Key::KeyC], "Make Handles Colinear")]
}
HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {
vec![HintInfo::keys([Key::KeyC], "Break Colinear Handles")]
}
};
let mut common_hints = vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")];
let hold_group = match mode {
HandleMode::Free => common_hints,
HandleMode::ColinearLocked => {
common_hints.push(HintInfo::keys([Key::Alt], "Non-Equidistant Handles"));
common_hints
}
HandleMode::ColinearEquidistant => {
common_hints.push(HintInfo::keys([Key::Alt], "Equidistant Handles"));
common_hints
}
};
dragging_hint_data.0.push(HintGroup(toggle_group));
dragging_hint_data.0.push(HintGroup(hold_group));
dragging_hint_data
}
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });