mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Add Path tool support for the Tab key swapping to dragging the opposite handle (#2058)
* feat: tab alternates between handles * fix: handle hints, remove anchor to handle switch Added specific handle hints, Can no longer switch to handle if just anchor is selected typo fix * fix: no longer deselect on esc/rclick * feat: hides cursor when switching A pointerlock implementation would be ideal in the future to keep the screen from panning, * fix: tidy up dynamic tool hints switch colinear to V * fix: can no longer hide cursor if anchor selected remove debug statement * fix: clippy * Solve some issues and remap V to C to toggle colinear * Cleanup + change equidistant key from Shift to Alt --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
b7ba2c3637
commit
018e9839f8
5 changed files with 280 additions and 101 deletions
|
|
@ -93,7 +93,7 @@ pub fn input_mappings() -> Mapping {
|
|||
//
|
||||
// SelectToolMessage
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Control, center: Alt, duplicate: Alt })),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { add_to_selection: Shift, select_deepest: Accel }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, select_deepest: Accel }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Shift }),
|
||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer),
|
||||
|
|
@ -204,19 +204,20 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||
entry!(KeyDown(Delete); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { ctrl: Control, shift: Shift }),
|
||||
entry!(KeyDown(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { direct_insert_without_sliding: Control, extend_selection: Shift }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
||||
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Space], action_dispatch=PathToolMessage::PointerMove { alt: Alt, shift: Shift, move_anchor_and_handles: Space}),
|
||||
entry!(PointerMove; refresh_keys=[KeyC, Shift, Alt, Space], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space}),
|
||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { equidistant: Shift }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { add_to_selection: Shift }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift }),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::{Doc
|
|||
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::path_tool::PointSelectState;
|
||||
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_core::transform::Transform;
|
||||
|
|
@ -12,8 +13,9 @@ use graphene_core::vector::{ManipulatorPointId, PointId, VectorData, VectorModif
|
|||
use glam::DVec2;
|
||||
use graphene_std::vector::{HandleId, SegmentId};
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
|
||||
pub enum ManipulatorAngle {
|
||||
#[default]
|
||||
Colinear,
|
||||
Free,
|
||||
Mixed,
|
||||
|
|
@ -161,9 +163,9 @@ impl ClosestSegment {
|
|||
midpoint
|
||||
}
|
||||
|
||||
pub fn adjusted_insert_and_select(&self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, add_to_selection: bool) {
|
||||
pub fn adjusted_insert_and_select(&self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, extend_selection: bool) {
|
||||
let id = self.adjusted_insert(responses);
|
||||
shape_editor.select_anchor_point_by_id(self.layer, id, add_to_selection)
|
||||
shape_editor.select_anchor_point_by_id(self.layer, id, extend_selection)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +223,7 @@ impl ShapeState {
|
|||
|
||||
/// Select/deselect the first point within the selection threshold.
|
||||
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
|
||||
pub fn change_point_selection(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool) -> Option<Option<SelectedPointsInfo>> {
|
||||
pub fn change_point_selection(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64, extend_selection: bool) -> Option<Option<SelectedPointsInfo>> {
|
||||
if self.selected_shape_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -234,14 +236,14 @@ impl ShapeState {
|
|||
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
|
||||
|
||||
// Should we select or deselect the point?
|
||||
let new_selected = if already_selected { !add_to_selection } else { true };
|
||||
let new_selected = if already_selected { !extend_selection } else { true };
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
if new_selected {
|
||||
let retain_existing_selection = add_to_selection || already_selected;
|
||||
let retain_existing_selection = extend_selection || already_selected;
|
||||
if !retain_existing_selection {
|
||||
self.deselect_all_points();
|
||||
}
|
||||
|
|
@ -267,8 +269,8 @@ impl ShapeState {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn select_anchor_point_by_id(&mut self, layer: LayerNodeIdentifier, id: PointId, add_to_selection: bool) {
|
||||
if !add_to_selection {
|
||||
pub fn select_anchor_point_by_id(&mut self, layer: LayerNodeIdentifier, id: PointId, extend_selection: bool) {
|
||||
if !extend_selection {
|
||||
self.deselect_all_points();
|
||||
}
|
||||
let point = ManipulatorPointId::Anchor(id);
|
||||
|
|
@ -1060,6 +1062,71 @@ impl ShapeState {
|
|||
_ => self.sorted_selected_layers(network_interface.document_metadata()).find_map(closest_seg),
|
||||
}
|
||||
}
|
||||
pub fn get_dragging_state(&self, network_interface: &NodeNetworkInterface) -> PointSelectState {
|
||||
for &layer in self.selected_shape_state.keys() {
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
for point in self.selected_points() {
|
||||
if point.as_anchor().is_some() {
|
||||
return PointSelectState::Anchor;
|
||||
}
|
||||
if point.get_handle_pair(&vector_data).is_some() {
|
||||
return PointSelectState::HandleWithPair;
|
||||
}
|
||||
}
|
||||
}
|
||||
PointSelectState::HandleNoPair
|
||||
}
|
||||
|
||||
/// Returns true if at least one handle with pair is selected
|
||||
pub fn handle_with_pair_selected(&mut self, network_interface: &NodeNetworkInterface) -> bool {
|
||||
for &layer in self.selected_shape_state.keys() {
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
for point in self.selected_points() {
|
||||
if point.as_anchor().is_some() {
|
||||
return false;
|
||||
}
|
||||
if point.get_handle_pair(&vector_data).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Alternate selected handles to mirrors
|
||||
pub fn alternate_selected_handles(&mut self, network_interface: &NodeNetworkInterface) {
|
||||
let mut handles_to_update = Vec::new();
|
||||
|
||||
for &layer in self.selected_shape_state.keys() {
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
for point in self.selected_points() {
|
||||
if point.as_anchor().is_some() {
|
||||
continue;
|
||||
}
|
||||
if let Some(handles) = point.get_handle_pair(&vector_data) {
|
||||
// handle[0] is selected, handle[1] is opposite / mirror handle
|
||||
handles_to_update.push((layer, handles[0].to_manipulator_point(), handles[1].to_manipulator_point()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (layer, handle_to_deselect, handle_to_select) in handles_to_update {
|
||||
if let Some(state) = self.selected_shape_state.get_mut(&layer) {
|
||||
let points = &state.selected_points;
|
||||
let both_selected = points.contains(&handle_to_deselect) && points.contains(&handle_to_select);
|
||||
if both_selected {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.deselect_point(handle_to_deselect);
|
||||
state.select_point(handle_to_select);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects handles and anchor connected to current handle
|
||||
pub fn select_handles_and_anchor_connected_to_current_handle(&mut self, network_interface: &NodeNetworkInterface) {
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ pub enum PathToolMessage {
|
|||
Delete,
|
||||
DeleteAndBreakPath,
|
||||
DragStop {
|
||||
equidistant: Key,
|
||||
extend_selection: Key,
|
||||
},
|
||||
Enter {
|
||||
add_to_selection: Key,
|
||||
extend_selection: Key,
|
||||
},
|
||||
Escape,
|
||||
FlipSmoothSharp,
|
||||
|
|
@ -48,22 +48,22 @@ pub enum PathToolMessage {
|
|||
ManipulatorMakeHandlesFree,
|
||||
ManipulatorMakeHandlesColinear,
|
||||
MouseDown {
|
||||
ctrl: Key,
|
||||
shift: Key,
|
||||
direct_insert_without_sliding: Key,
|
||||
extend_selection: Key,
|
||||
},
|
||||
NudgeSelectedPoints {
|
||||
delta_x: f64,
|
||||
delta_y: f64,
|
||||
},
|
||||
PointerMove {
|
||||
alt: Key,
|
||||
shift: Key,
|
||||
move_anchor_and_handles: Key,
|
||||
equidistant: Key,
|
||||
toggle_colinear: Key,
|
||||
move_anchor_with_handles: Key,
|
||||
},
|
||||
PointerOutsideViewport {
|
||||
alt: Key,
|
||||
shift: Key,
|
||||
move_anchor_and_handles: Key,
|
||||
equidistant: Key,
|
||||
toggle_colinear: Key,
|
||||
move_anchor_with_handles: Key,
|
||||
},
|
||||
RightClick,
|
||||
SelectAllAnchors,
|
||||
|
|
@ -74,6 +74,7 @@ pub enum PathToolMessage {
|
|||
SelectedPointYChanged {
|
||||
new_y: f64,
|
||||
},
|
||||
SwapSelectedHandles,
|
||||
}
|
||||
|
||||
impl ToolMetadata for PathTool {
|
||||
|
|
@ -170,7 +171,19 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
match message {
|
||||
ToolMessage::Path(PathToolMessage::SwapSelectedHandles) => {
|
||||
if tool_data.shape_editor.handle_with_pair_selected(&tool_data.document.network_interface) {
|
||||
tool_data.shape_editor.alternate_selected_handles(&tool_data.document.network_interface);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None });
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
}
|
||||
|
||||
if updating_point {
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
|
|
@ -191,7 +204,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
BreakPath,
|
||||
DeleteAndBreakPath,
|
||||
),
|
||||
PathToolFsmState::Dragging => actions!(PathToolMessageDiscriminant;
|
||||
PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;
|
||||
Escape,
|
||||
RightClick,
|
||||
FlipSmoothSharp,
|
||||
|
|
@ -200,6 +213,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
Delete,
|
||||
BreakPath,
|
||||
DeleteAndBreakPath,
|
||||
SwapSelectedHandles,
|
||||
),
|
||||
PathToolFsmState::DrawingBox => actions!(PathToolMessageDiscriminant;
|
||||
FlipSmoothSharp,
|
||||
|
|
@ -235,19 +249,32 @@ impl ToolTransition for PathTool {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct DraggingState {
|
||||
point_select_state: PointSelectState,
|
||||
colinear: ManipulatorAngle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||
pub enum PointSelectState {
|
||||
HandleWithPair,
|
||||
#[default]
|
||||
HandleNoPair,
|
||||
Anchor,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
enum PathToolFsmState {
|
||||
#[default]
|
||||
Ready,
|
||||
Dragging,
|
||||
Dragging(DraggingState),
|
||||
DrawingBox,
|
||||
InsertPoint,
|
||||
}
|
||||
|
||||
enum InsertEndKind {
|
||||
Abort,
|
||||
Add { shift: bool },
|
||||
Add { extend_selection: bool },
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -255,7 +282,7 @@ struct PathToolData {
|
|||
snap_manager: SnapManager,
|
||||
drag_start_pos: DVec2,
|
||||
previous_mouse_position: DVec2,
|
||||
alt_debounce: bool,
|
||||
toggle_colinear_debounce: bool,
|
||||
opposing_handle_lengths: Option<OpposingHandleLengths>,
|
||||
/// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle).
|
||||
/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.
|
||||
|
|
@ -266,12 +293,13 @@ struct PathToolData {
|
|||
auto_panning: AutoPanning,
|
||||
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
|
||||
select_anchor_toggled: bool,
|
||||
dragging_state: DraggingState,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
fn save_points_before_anchor_toggle(&mut self, points: Vec<ManipulatorPointId>) -> PathToolFsmState {
|
||||
self.saved_points_before_anchor_select_toggle = points;
|
||||
PathToolFsmState::Dragging
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
|
||||
fn remove_saved_points(&mut self) {
|
||||
|
|
@ -308,8 +336,8 @@ impl PathToolData {
|
|||
warn!("Segment was `None` before `end_insertion`")
|
||||
}
|
||||
Some(closed_segment) => {
|
||||
if let InsertEndKind::Add { shift } = kind {
|
||||
closed_segment.adjusted_insert_and_select(shape_editor, responses, shift);
|
||||
if let InsertEndKind::Add { extend_selection } = kind {
|
||||
closed_segment.adjusted_insert_and_select(shape_editor, responses, extend_selection);
|
||||
commit_transaction = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -331,7 +359,7 @@ impl PathToolData {
|
|||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
add_to_selection: bool,
|
||||
extend_selection: bool,
|
||||
direct_insert_without_sliding: bool,
|
||||
) -> PathToolFsmState {
|
||||
self.double_click_handled = false;
|
||||
|
|
@ -340,7 +368,7 @@ impl PathToolData {
|
|||
self.drag_start_pos = input.mouse.position;
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
|
||||
if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, extend_selection) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
if let Some(selected_points) = selected_points {
|
||||
|
|
@ -348,21 +376,21 @@ impl PathToolData {
|
|||
self.start_dragging_point(selected_points, input, document, shape_editor);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
PathToolFsmState::Dragging
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
// We didn't find a point nearby, so now we'll try to add a point into the closest path segment
|
||||
else if let Some(closed_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
if direct_insert_without_sliding {
|
||||
self.start_insertion(responses, closed_segment);
|
||||
self.end_insertion(shape_editor, responses, InsertEndKind::Add { shift: add_to_selection })
|
||||
self.end_insertion(shape_editor, responses, InsertEndKind::Add { extend_selection })
|
||||
} else {
|
||||
self.start_insertion(responses, closed_segment)
|
||||
}
|
||||
}
|
||||
// We didn't find a segment path, so consider selecting the nearest shape instead
|
||||
else if let Some(layer) = document.click(input) {
|
||||
if add_to_selection {
|
||||
if extend_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
|
|
@ -372,7 +400,8 @@ impl PathToolData {
|
|||
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
PathToolFsmState::Dragging
|
||||
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
// Start drawing a box
|
||||
else {
|
||||
|
|
@ -413,9 +442,9 @@ impl PathToolData {
|
|||
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position - selected_points.offset);
|
||||
}
|
||||
|
||||
fn update_colinear(&mut self, shift: bool, alt: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||
// Check if the alt key has just been pressed
|
||||
if alt && !self.alt_debounce {
|
||||
fn update_colinear(&mut self, equidistant: bool, toggle_colinear: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||
// Check if the toggle_colinear key has just been pressed
|
||||
if toggle_colinear && !self.toggle_colinear_debounce {
|
||||
self.opposing_handle_lengths = None;
|
||||
let colinear = self.selection_status.angle().map_or(false, |angle| match angle {
|
||||
ManipulatorAngle::Colinear => true,
|
||||
|
|
@ -427,12 +456,12 @@ impl PathToolData {
|
|||
} else {
|
||||
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||
}
|
||||
self.alt_debounce = true;
|
||||
self.toggle_colinear_debounce = true;
|
||||
return true;
|
||||
}
|
||||
self.alt_debounce = alt;
|
||||
self.toggle_colinear_debounce = toggle_colinear;
|
||||
|
||||
if shift && self.opposing_handle_lengths.is_none() {
|
||||
if equidistant && self.opposing_handle_lengths.is_none() {
|
||||
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
||||
}
|
||||
false
|
||||
|
|
@ -481,7 +510,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
overlay_context.quad(Quad::from_box([tool_data.drag_start_pos, tool_data.previous_mouse_position]), Some(&("#".to_string() + &fill_color)));
|
||||
}
|
||||
Self::Dragging => {
|
||||
Self::Dragging(_) => {
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
}
|
||||
Self::InsertPoint => {
|
||||
|
|
@ -502,11 +531,10 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
|
||||
// `Self::InsertPoint` case:
|
||||
(Self::InsertPoint, PathToolMessage::MouseDown { .. } | PathToolMessage::Enter { .. }) => {
|
||||
(Self::InsertPoint, PathToolMessage::MouseDown { extend_selection, .. } | PathToolMessage::Enter { extend_selection }) => {
|
||||
tool_data.double_click_handled = true;
|
||||
// TODO: Don't use `Key::Shift` directly, instead take it as a variable from the input mappings list like in all other places
|
||||
let shift = input.keyboard.get(Key::Shift as usize);
|
||||
tool_data.end_insertion(shape_editor, responses, InsertEndKind::Add { shift })
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
tool_data.end_insertion(shape_editor, responses, InsertEndKind::Add { extend_selection })
|
||||
}
|
||||
(Self::InsertPoint, PathToolMessage::PointerMove { .. }) => {
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
|
@ -529,26 +557,56 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.end_insertion(shape_editor, responses, InsertEndKind::Abort)
|
||||
}
|
||||
// Mouse down
|
||||
(_, PathToolMessage::MouseDown { ctrl, shift }) => {
|
||||
let add_to_selection = input.keyboard.get(shift as usize);
|
||||
let direct_insert_without_sliding = input.keyboard.get(ctrl as usize);
|
||||
tool_data.mouse_down(shape_editor, document, input, responses, add_to_selection, direct_insert_without_sliding)
|
||||
(
|
||||
_,
|
||||
PathToolMessage::MouseDown {
|
||||
direct_insert_without_sliding,
|
||||
extend_selection,
|
||||
},
|
||||
) => {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let direct_insert_without_sliding = input.keyboard.get(direct_insert_without_sliding as usize);
|
||||
tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding)
|
||||
}
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }) => {
|
||||
(
|
||||
PathToolFsmState::DrawingBox,
|
||||
PathToolMessage::PointerMove {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
},
|
||||
) => {
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }.into(),
|
||||
PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }.into(),
|
||||
PathToolMessage::PointerOutsideViewport {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
}
|
||||
.into(),
|
||||
PathToolMessage::PointerMove {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
PathToolFsmState::DrawingBox
|
||||
}
|
||||
(PathToolFsmState::Dragging, PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }) => {
|
||||
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_and_handles as usize);
|
||||
(
|
||||
PathToolFsmState::Dragging(_),
|
||||
PathToolMessage::PointerMove {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
},
|
||||
) => {
|
||||
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
|
||||
let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;
|
||||
let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;
|
||||
|
||||
|
|
@ -565,63 +623,89 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.remove_saved_points();
|
||||
}
|
||||
|
||||
let alt_state = input.keyboard.get(alt as usize);
|
||||
let shift_state = input.keyboard.get(shift as usize);
|
||||
if !tool_data.update_colinear(shift_state, alt_state, shape_editor, document, responses) {
|
||||
tool_data.drag(shift_state, shape_editor, document, input, responses);
|
||||
let toggle_colinear_state = input.keyboard.get(toggle_colinear as usize);
|
||||
let equidistant_state = input.keyboard.get(equidistant as usize);
|
||||
if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, shape_editor, document, responses) {
|
||||
tool_data.drag(equidistant_state, shape_editor, document, input, responses);
|
||||
}
|
||||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }.into(),
|
||||
PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }.into(),
|
||||
PathToolMessage::PointerOutsideViewport {
|
||||
toggle_colinear,
|
||||
equidistant,
|
||||
move_anchor_with_handles,
|
||||
}
|
||||
.into(),
|
||||
PathToolMessage::PointerMove {
|
||||
toggle_colinear,
|
||||
equidistant,
|
||||
move_anchor_with_handles,
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
PathToolFsmState::Dragging
|
||||
PathToolFsmState::Dragging(tool_data.dragging_state)
|
||||
}
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
tool_data.drag_start_pos += shift;
|
||||
if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
tool_data.drag_start_pos += offset;
|
||||
}
|
||||
|
||||
PathToolFsmState::DrawingBox
|
||||
}
|
||||
(PathToolFsmState::Dragging, PathToolMessage::PointerOutsideViewport { shift, .. }) => {
|
||||
(PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { equidistant, .. }) => {
|
||||
// Auto-panning
|
||||
if tool_data.auto_panning.shift_viewport(input, responses).is_some() {
|
||||
let shift_state = input.keyboard.get(shift as usize);
|
||||
tool_data.drag(shift_state, shape_editor, document, input, responses);
|
||||
let equidistant = input.keyboard.get(equidistant as usize);
|
||||
tool_data.drag(equidistant, shape_editor, document, input, responses);
|
||||
}
|
||||
|
||||
PathToolFsmState::Dragging
|
||||
PathToolFsmState::Dragging(dragging_state)
|
||||
}
|
||||
(state, PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }) => {
|
||||
(
|
||||
state,
|
||||
PathToolMessage::PointerOutsideViewport {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
},
|
||||
) => {
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }.into(),
|
||||
PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }.into(),
|
||||
PathToolMessage::PointerOutsideViewport {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
}
|
||||
.into(),
|
||||
PathToolMessage::PointerMove {
|
||||
equidistant,
|
||||
toggle_colinear,
|
||||
move_anchor_with_handles,
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
tool_data.auto_panning.stop(&messages, responses);
|
||||
|
||||
state
|
||||
}
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::Enter { add_to_selection }) => {
|
||||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::Enter { extend_selection }) => {
|
||||
let extend_selection = input.keyboard.get(extend_selection 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_interface, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
shape_editor.select_all_in_quad(&document.network_interface, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !extend_selection);
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(PathToolFsmState::Dragging, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||
(PathToolFsmState::Dragging { .. }, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
shape_editor.deselect_all_points();
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
|
@ -630,20 +714,20 @@ impl Fsm for PathToolFsmState {
|
|||
PathToolFsmState::Ready
|
||||
}
|
||||
// Mouse up
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { equidistant }) => {
|
||||
let equidistant = input.keyboard.get(equidistant as usize);
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { extend_selection }) => {
|
||||
let extend_selection = input.keyboard.get(extend_selection 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_interface, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !equidistant);
|
||||
shape_editor.select_all_in_quad(&document.network_interface, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !extend_selection);
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::DragStop { equidistant }) => {
|
||||
(_, PathToolMessage::DragStop { extend_selection }) => {
|
||||
if tool_data.select_anchor_toggled {
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_anchor_select_toggle);
|
||||
|
|
@ -651,12 +735,12 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.select_anchor_toggled = false;
|
||||
}
|
||||
|
||||
let equidistant = input.keyboard.get(equidistant as usize);
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
|
||||
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
|
||||
|
||||
if let Some((layer, nearest_point)) = nearest_point {
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !extend_selection {
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
|
||||
if clicked_selected {
|
||||
shape_editor.deselect_all_points();
|
||||
|
|
@ -729,6 +813,11 @@ impl Fsm for PathToolFsmState {
|
|||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointUpdated) => {
|
||||
let colinear = shape_editor.selected_manipulator_angles(&document.network_interface);
|
||||
tool_data.dragging_state = DraggingState {
|
||||
point_select_state: shape_editor.get_dragging_state(&document.network_interface),
|
||||
colinear,
|
||||
};
|
||||
tool_data.selection_status = get_selection_status(&document.network_interface, shape_editor);
|
||||
self
|
||||
}
|
||||
|
|
@ -767,19 +856,41 @@ impl Fsm for PathToolFsmState {
|
|||
HintInfo::keys([Key::Shift], "Break Anchor").prepend_plus(),
|
||||
]),
|
||||
]),
|
||||
PathToolFsmState::Dragging => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![
|
||||
// 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).
|
||||
HintInfo::keys([Key::Space], "Drag anchor"),
|
||||
]),
|
||||
]),
|
||||
PathToolFsmState::Dragging(dragging_state) => {
|
||||
let colinear = dragging_state.colinear;
|
||||
let mut dragging_hint_data = HintData(Vec::new());
|
||||
dragging_hint_data
|
||||
.0
|
||||
.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 => vec![drag_anchor],
|
||||
PointSelectState::HandleWithPair => {
|
||||
let mut hints = vec![drag_anchor];
|
||||
hints.push(HintInfo::keys([Key::Tab], "Swap Selected Handles"));
|
||||
hints.push(HintInfo::keys(
|
||||
[Key::KeyC],
|
||||
if colinear == ManipulatorAngle::Colinear {
|
||||
"Break Colinear Handles"
|
||||
} else {
|
||||
"Make Handles Colinear"
|
||||
},
|
||||
));
|
||||
if colinear != ManipulatorAngle::Free {
|
||||
hints.push(HintInfo::keys([Key::Alt], "Equidistant Handles"));
|
||||
}
|
||||
hints
|
||||
}
|
||||
PointSelectState::Anchor => Vec::new(),
|
||||
};
|
||||
|
||||
if !point_select_state_hint_group.is_empty() {
|
||||
dragging_hint_data.0.push(HintGroup(point_select_state_hint_group));
|
||||
}
|
||||
|
||||
dragging_hint_data
|
||||
}
|
||||
PathToolFsmState::DrawingBox => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ pub enum SelectToolMessage {
|
|||
Overlays(OverlayContext),
|
||||
|
||||
// Tool-specific messages
|
||||
DragStart { add_to_selection: Key, select_deepest: Key },
|
||||
DragStart { extend_selection: Key, select_deepest: Key },
|
||||
DragStop { remove_from_selection: Key },
|
||||
EditLayer,
|
||||
Enter,
|
||||
|
|
@ -517,7 +517,7 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
self
|
||||
}
|
||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::DragStart { add_to_selection, select_deepest }) => {
|
||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::DragStart { extend_selection, select_deepest }) => {
|
||||
tool_data.drag_start = input.mouse.position;
|
||||
tool_data.drag_current = input.mouse.position;
|
||||
|
||||
|
|
@ -647,7 +647,7 @@ impl Fsm for SelectToolFsmState {
|
|||
else {
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
if !input.keyboard.key(add_to_selection) {
|
||||
if !input.keyboard.key(extend_selection) {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layers_dragging.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ impl ManipulatorPointId {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attempt to get a pair of handles. For an anchor this is the first to handles connected. For a handle it is self and the first opposing handle.
|
||||
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
|
||||
#[must_use]
|
||||
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
|
||||
match self {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue