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

Make Path tool Alt-drag from an anchor drag out a fresh equidistant handle pair #2496

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -212,7 +212,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
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(MouseLeft); action_dispatch=PathToolMessage::MouseDown { direct_insert_without_sliding: Control, extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt}),
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
Expand Down
7 changes: 5 additions & 2 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@ impl ShapeState {
delta: DVec2,
equidistant: bool,
in_viewport_space: bool,
was_alt_dragging: bool,
opposite_handle_position: Option<DVec2>,
responses: &mut VecDeque<Message>,
) {
Expand Down Expand Up @@ -810,9 +811,11 @@ impl ShapeState {
let length = opposing_handle.copied().unwrap_or_else(|| transform.transform_vector2(other_position - anchor_position).length());
direction.map_or(other_position - anchor_position, |direction| transform.inverse().transform_vector2(-direction * length))
};
let modification_type = other.set_relative_position(new_relative);

responses.add(GraphOperationMessage::Vector { layer, modification_type });
if !was_alt_dragging {
let modification_type = other.set_relative_position(new_relative);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
}
Expand Down
145 changes: 137 additions & 8 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::messages::tool::common_functionality::shape_editor::{
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
use graphene_core::renderer::Quad;
use graphene_core::vector::{ManipulatorPointId, PointId};
use graphene_std::vector::{NoHashBuilder, SegmentId};
use graphene_std::vector::{HandleId, NoHashBuilder, SegmentId, VectorData, VectorModificationType};
use std::vec;

#[derive(Default)]
Expand Down Expand Up @@ -65,6 +65,7 @@ pub enum PathToolMessage {
direct_insert_without_sliding: Key,
extend_selection: Key,
lasso_select: Key,
handle_drag_from_anchor: Key,
},
NudgeSelectedPoints {
delta_x: f64,
Expand Down Expand Up @@ -375,6 +376,8 @@ struct PathToolData {
angle: f64,
opposite_handle_position: Option<DVec2>,
snapping_axis: Option<Axis>,
stored_handle_positions: Option<HashMap<HandleId, DVec2>>,
alt_dragging_from_anchor: bool,
}

impl PathToolData {
Expand Down Expand Up @@ -489,6 +492,7 @@ impl PathToolData {
extend_selection: bool,
direct_insert_without_sliding: bool,
lasso_select: bool,
handle_drag_from_anchor: bool,
) -> PathToolFsmState {
self.double_click_handled = false;
self.opposing_handle_lengths = None;
Expand Down Expand Up @@ -516,6 +520,35 @@ impl PathToolData {
self.saved_points_before_handle_drag = old_selection;
}

if handle_drag_from_anchor {
if let Some((layer, point)) = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {
// Check that selected point is an anchor
if let (Some(point_id), Some(vector_data)) = (point.as_anchor(), document.network_interface.compute_modified_vector(layer)) {
let position = ManipulatorPointId::Anchor(point_id).get_position(&vector_data).unwrap_or_default();
let handles = vector_data.all_connected(point_id).collect::<Vec<_>>();
if vector_data.connected_count(point_id) == 2 {
if let (Some(pos1), Some(pos2)) = (
handles[0].to_manipulator_point().get_position(&vector_data),
handles[1].to_manipulator_point().get_position(&vector_data),
) {
let mut map = HashMap::new();
map.insert(handles[0], pos1 - position);
map.insert(handles[1], pos2 - position);
self.stored_handle_positions = Some(map);
}
}
for handle in &handles {
let modification_type = handle.set_relative_position(DVec2::ZERO);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
let manipulator_point_id = handles[0].to_manipulator_point();
shape_editor.deselect_all_points();
shape_editor.select_points_by_manipulator_id(&vec![manipulator_point_id]);
responses.add(PathToolMessage::SelectionChanged);
}
}
}

self.start_dragging_point(selected_points, input, document, shape_editor);
responses.add(OverlaysMessage::Draw);
}
Expand Down Expand Up @@ -744,7 +777,7 @@ impl PathToolData {
let drag_start = self.drag_start_pos;
let opposite_delta = drag_start - current_mouse;

shape_editor.move_selected_points(None, document, opposite_delta, false, true, None, responses);
shape_editor.move_selected_points(None, document, opposite_delta, false, true, false, None, responses);

// Calculate the projected delta and shift the points along that delta
let delta = current_mouse - drag_start;
Expand All @@ -756,7 +789,7 @@ impl PathToolData {
_ => DVec2::new(delta.x, 0.),
};

shape_editor.move_selected_points(None, document, projected_delta, false, true, None, responses);
shape_editor.move_selected_points(None, document, projected_delta, false, true, false, None, responses);
}

fn stop_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
Expand All @@ -772,16 +805,33 @@ impl PathToolData {
_ => DVec2::new(opposite_delta.x, 0.),
};

shape_editor.move_selected_points(None, document, opposite_projected_delta, false, true, None, responses);
shape_editor.move_selected_points(None, document, opposite_projected_delta, false, true, false, None, responses);

// Calculate what actually would have been the original delta for the point, and apply that
let delta = current_mouse - drag_start;

shape_editor.move_selected_points(None, document, delta, false, true, None, responses);
shape_editor.move_selected_points(None, document, delta, false, true, false, None, responses);

self.snapping_axis = None;
}

fn get_normalized_tangent(&mut self, point: PointId, segment: SegmentId, vector_data: &VectorData) -> Option<DVec2> {
let other_point = vector_data.other_point(segment, point)?;
let position = ManipulatorPointId::Anchor(point).get_position(vector_data)?;

let mut handles = vector_data.all_connected(other_point);
let other_handle = handles.find(|handle| handle.segment == segment)?;

let target_position = if other_handle.length(vector_data) == 0. {
ManipulatorPointId::Anchor(other_point).get_position(vector_data)?
} else {
other_handle.to_manipulator_point().get_position(vector_data)?
};

let tangent_vector = target_position - position;
tangent_vector.try_normalize()
}

#[allow(clippy::too_many_arguments)]
fn drag(
&mut self,
Expand Down Expand Up @@ -829,9 +879,71 @@ impl PathToolData {
let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };
let opposite = if lock_angle { None } else { self.opposite_handle_position };
let unsnapped_delta = current_mouse - previous_mouse;
let mut was_alt_dragging = false;

if self.snapping_axis.is_none() {
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, opposite, responses);
if self.stored_handle_positions.is_some() && !self.alt_dragging_from_anchor && self.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
//checking that drag is in which direction
self.alt_dragging_from_anchor = true;
let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else {
return;
};
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
let Some(point_id) = shape_editor.selected_points().next().unwrap().get_anchor(&vector_data) else {
return;
};

if vector_data.connected_count(point_id) == 2 {
let connected_segments: Vec<HandleId> = vector_data.all_connected(point_id).collect();
let segment1 = connected_segments[0];
let Some(tangent1) = self.get_normalized_tangent(point_id, segment1.segment, &vector_data) else {
return;
};
let segment2 = connected_segments[1];
let Some(tangent2) = self.get_normalized_tangent(point_id, segment2.segment, &vector_data) else {
return;
};

let delta = input.mouse.position - self.drag_start_pos;
let handle = if delta.dot(tangent1) >= delta.dot(tangent2) {
segment1.to_manipulator_point()
} else {
segment2.to_manipulator_point()
};

//now change the selection to this handle
shape_editor.deselect_all_points();
shape_editor.select_points_by_manipulator_id(&vec![handle]);
responses.add(PathToolMessage::SelectionChanged);
}
}

if self.alt_dragging_from_anchor && !equidistant && self.stored_handle_positions.is_some() {
// Move other handle to the position where it started
let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else {
return;
};
let Some(selected_handle) = shape_editor.selected_points().next() else { return };
let Some(position_map) = self.stored_handle_positions.clone() else { return };
let Some((opposite, opp_position)) = position_map
.iter()
.find_map(|(&handle, &pos)| if handle.to_manipulator_point() != *selected_handle { Some((handle, pos)) } else { None })
else {
return;
};

let Ok(handles) = position_map.keys().copied().collect::<Vec<_>>().try_into().map(|v: Vec<HandleId>| [v[0], v[1]]);

let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
let modification_type = opposite.set_relative_position(opp_position);
responses.add(GraphOperationMessage::Vector { layer, modification_type });

was_alt_dragging = true;
self.alt_dragging_from_anchor = false;
self.stored_handle_positions = None;
}
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, was_alt_dragging, opposite, responses);
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
} else {
let Some(axis) = self.snapping_axis else { return };
Expand All @@ -840,7 +952,7 @@ impl PathToolData {
Axis::Y => DVec2::new(0., unsnapped_delta.y),
_ => DVec2::new(unsnapped_delta.x, 0.),
};
shape_editor.move_selected_points(handle_lengths, document, projected_delta, equidistant, true, opposite, responses);
shape_editor.move_selected_points(handle_lengths, document, projected_delta, equidistant, true, false, opposite, responses);
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(unsnapped_delta);
}

Expand Down Expand Up @@ -1024,16 +1136,27 @@ impl Fsm for PathToolFsmState {
direct_insert_without_sliding,
extend_selection,
lasso_select,
handle_drag_from_anchor,
},
) => {
let extend_selection = input.keyboard.get(extend_selection as usize);
let lasso_select = input.keyboard.get(lasso_select as usize);
let direct_insert_without_sliding = input.keyboard.get(direct_insert_without_sliding as usize);
let handle_drag_from_anchor = input.keyboard.get(handle_drag_from_anchor as usize);

tool_data.selection_mode = None;
tool_data.lasso_polygon.clear();

tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding, lasso_select)
tool_data.mouse_down(
shape_editor,
document,
input,
responses,
extend_selection,
direct_insert_without_sliding,
lasso_select,
handle_drag_from_anchor,
)
}
(
PathToolFsmState::Drawing { selection_shape },
Expand Down Expand Up @@ -1295,6 +1418,11 @@ impl Fsm for PathToolFsmState {
tool_data.handle_drag_toggle = false;
}

if tool_data.alt_dragging_from_anchor {
tool_data.alt_dragging_from_anchor = false;
tool_data.stored_handle_positions = None;
}

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);
Expand Down Expand Up @@ -1385,6 +1513,7 @@ impl Fsm for PathToolFsmState {
(delta_x, delta_y).into(),
true,
false,
false,
tool_data.opposite_handle_position,
responses,
);
Expand Down
Loading