Skip to content
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