Skip to content

In the Path tool, make Space shift the anchor while dragging handles #2065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 26, 2024
2 changes: 1 addition & 1 deletion editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ pub fn input_mappings() -> Mapping {
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], action_dispatch=PathToolMessage::PointerMove { alt: Alt, shift: Shift }),
entry!(PointerMove; refresh_keys=[Alt, Shift, Space], action_dispatch=PathToolMessage::PointerMove { alt: Alt, shift: Shift, move_anchor_and_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),
Expand Down
47 changes: 47 additions & 0 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::prelude::*;

use bezier_rs::{Bezier, BezierHandles, TValue};
use dyn_any::DynAny;
use graphene_core::transform::Transform;
use graphene_core::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};

Expand Down Expand Up @@ -1061,6 +1062,52 @@ impl ShapeState {
}
}

/// Selects handles and anchor connected to current handle
pub fn select_handles_and_anchor_connected_to_current_handle(&mut self, network_interface: &NodeNetworkInterface) {
let mut points_to_select: Vec<(LayerNodeIdentifier, Option<PointId>, Option<ManipulatorPointId>)> = 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 let Some(_) = point.as_anchor() {
continue;
}

let anchor = point.get_anchor(&vector_data);
if let Some(handles) = point.get_handle_pair(&vector_data) {
points_to_select.push((layer, anchor, Some(handles[1].to_manipulator_point())));
} else {
points_to_select.push((layer, anchor, None));
}
}
}

for (layer, anchor, handle) in points_to_select {
if let Some(state) = self.selected_shape_state.get_mut(&layer) {
if let Some(anchor) = anchor {
state.select_point(ManipulatorPointId::Anchor(anchor));
}
if let Some(handle) = handle {
state.select_point(handle);
}
}
}
}

pub fn select_points_by_manipulator_id(&mut self, points: &Vec<ManipulatorPointId>) {
let layers_to_modify: Vec<_> = self.selected_shape_state.keys().cloned().collect();

for layer in layers_to_modify {
if let Some(state) = self.selected_shape_state.get_mut(&layer) {
for point in points {
state.select_point(*point);
}
}
}
}
/// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)).
/// If both handles aren't zero-length, they are set that. If both are zero-length, they are stretched apart by a reasonable amount.
/// This can can be activated by double clicking on an anchor with the Path tool.
Expand Down
59 changes: 53 additions & 6 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ pub enum PathToolMessage {
PointerMove {
alt: Key,
shift: Key,
move_anchor_and_handles: Key,
},
PointerOutsideViewport {
alt: Key,
shift: Key,
move_anchor_and_handles: Key,
},
RightClick,
SelectAllAnchors,
Expand Down Expand Up @@ -262,9 +264,20 @@ struct PathToolData {
snap_cache: SnapCache,
double_click_handled: bool,
auto_panning: AutoPanning,
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
select_anchor_toggled: bool,
}

impl PathToolData {
fn save_points_before_anchor_toggle(&mut self, points: Vec<ManipulatorPointId>) -> PathToolFsmState {
self.saved_points_before_anchor_select_toggle = points;
PathToolFsmState::Dragging
}

fn remove_saved_points(&mut self) {
self.saved_points_before_anchor_select_toggle.clear();
}

fn start_insertion(&mut self, responses: &mut VecDeque<Message>, segment: ClosestSegment) -> PathToolFsmState {
if self.segment.is_some() {
warn!("Segment was `Some(..)` before `start_insertion`")
Expand Down Expand Up @@ -521,25 +534,48 @@ impl Fsm for PathToolFsmState {
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)
}
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { alt, shift }) => {
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }) => {
tool_data.previous_mouse_position = input.mouse.position;
responses.add(OverlaysMessage::Draw);

// Auto-panning
let messages = [PathToolMessage::PointerOutsideViewport { alt, shift }.into(), PathToolMessage::PointerMove { alt, shift }.into()];
let messages = [
PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }.into(),
PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);

PathToolFsmState::DrawingBox
}
(PathToolFsmState::Dragging, PathToolMessage::PointerMove { alt, shift }) => {
(PathToolFsmState::Dragging, PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }) => {
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_and_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;

if initial_press {
responses.add(PathToolMessage::SelectedPointUpdated);
tool_data.select_anchor_toggled = true;
tool_data.save_points_before_anchor_toggle(shape_editor.selected_points().cloned().collect());
shape_editor.select_handles_and_anchor_connected_to_current_handle(&document.network_interface);
} else if released_from_toggle {
responses.add(PathToolMessage::SelectedPointUpdated);
tool_data.select_anchor_toggled = false;
shape_editor.deselect_all_points();
shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_anchor_select_toggle);
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);
}

// Auto-panning
let messages = [PathToolMessage::PointerOutsideViewport { alt, shift }.into(), PathToolMessage::PointerMove { alt, shift }.into()];
let messages = [
PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }.into(),
PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }.into(),
];
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);

PathToolFsmState::Dragging
Expand All @@ -561,9 +597,12 @@ impl Fsm for PathToolFsmState {

PathToolFsmState::Dragging
}
(state, PathToolMessage::PointerOutsideViewport { alt, shift }) => {
(state, PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }) => {
// Auto-panning
let messages = [PathToolMessage::PointerOutsideViewport { alt, shift }.into(), PathToolMessage::PointerMove { alt, shift }.into()];
let messages = [
PathToolMessage::PointerOutsideViewport { alt, shift, move_anchor_and_handles }.into(),
PathToolMessage::PointerMove { alt, shift, move_anchor_and_handles }.into(),
];
tool_data.auto_panning.stop(&messages, responses);

state
Expand Down Expand Up @@ -605,6 +644,13 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready
}
(_, PathToolMessage::DragStop { equidistant }) => {
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);
tool_data.remove_saved_points();
tool_data.select_anchor_toggled = false;
}

let equidistant = input.keyboard.get(equidistant as usize);

let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
Expand Down Expand Up @@ -731,6 +777,7 @@ impl Fsm for PathToolFsmState {
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::DrawingBox => HintData(vec![
Expand Down
Loading