mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00:00
Add Path tool support for dragging along an axis when Shift is held (#2449)
* Initial logic for snapping * Solved switching of axes * Solved conflict * Fixed autopanning issue * Autopanning issue * cleared comments * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
7e7e88f6fa
commit
4275eaf5bf
1 changed files with 113 additions and 14 deletions
|
@ -7,6 +7,7 @@ use crate::messages::portfolio::document::overlays::utility_functions::{path_ove
|
|||
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Axis;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{
|
||||
|
@ -373,6 +374,7 @@ struct PathToolData {
|
|||
current_selected_handle_id: Option<ManipulatorPointId>,
|
||||
angle: f64,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
snapping_axis: Option<Axis>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
|
@ -736,6 +738,50 @@ impl PathToolData {
|
|||
document.metadata().document_to_viewport.transform_vector2(snap_result.snapped_point_document - handle_position)
|
||||
}
|
||||
|
||||
fn start_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Find the negative delta to take the point to the drag start position
|
||||
let current_mouse = input.mouse.position;
|
||||
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);
|
||||
|
||||
// Calculate the projected delta and shift the points along that delta
|
||||
let delta = current_mouse - drag_start;
|
||||
let axis = if delta.x.abs() >= delta.y.abs() { Axis::X } else { Axis::Y };
|
||||
self.snapping_axis = Some(axis);
|
||||
let projected_delta = match axis {
|
||||
Axis::X => DVec2::new(delta.x, 0.),
|
||||
Axis::Y => DVec2::new(0., delta.y),
|
||||
_ => DVec2::new(delta.x, 0.),
|
||||
};
|
||||
|
||||
shape_editor.move_selected_points(None, document, projected_delta, false, true, None, responses);
|
||||
}
|
||||
|
||||
fn stop_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Calculate the negative delta of the selection and move it back to the drag start
|
||||
let current_mouse = input.mouse.position;
|
||||
let drag_start = self.drag_start_pos;
|
||||
|
||||
let opposite_delta = drag_start - current_mouse;
|
||||
let Some(axis) = self.snapping_axis else { return };
|
||||
let opposite_projected_delta = match axis {
|
||||
Axis::X => DVec2::new(opposite_delta.x, 0.),
|
||||
Axis::Y => DVec2::new(0., opposite_delta.y),
|
||||
_ => DVec2::new(opposite_delta.x, 0.),
|
||||
};
|
||||
|
||||
shape_editor.move_selected_points(None, document, opposite_projected_delta, false, true, 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);
|
||||
|
||||
self.snapping_axis = None;
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn drag(
|
||||
&mut self,
|
||||
|
@ -747,6 +793,19 @@ impl PathToolData {
|
|||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
// First check if selection is not just a single handle point
|
||||
let selected_points = shape_editor.selected_points();
|
||||
let single_handle_selected = selected_points.count() == 1
|
||||
&& shape_editor
|
||||
.selected_points()
|
||||
.any(|point| matches!(point, ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_)));
|
||||
|
||||
if snap_angle && self.snapping_axis.is_none() && !single_handle_selected {
|
||||
self.start_snap_along_axis(shape_editor, document, input, responses);
|
||||
} else if !snap_angle && self.snapping_axis.is_some() {
|
||||
self.stop_snap_along_axis(shape_editor, document, input, responses);
|
||||
}
|
||||
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position);
|
||||
let current_mouse = input.mouse.position;
|
||||
|
@ -769,8 +828,31 @@ 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 };
|
||||
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, opposite, responses);
|
||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
|
||||
let unsnapped_delta = current_mouse - previous_mouse;
|
||||
|
||||
if self.snapping_axis.is_none() {
|
||||
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, opposite, responses);
|
||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
|
||||
} else {
|
||||
let Some(axis) = self.snapping_axis else { return };
|
||||
let projected_delta = match axis {
|
||||
Axis::X => DVec2::new(unsnapped_delta.x, 0.),
|
||||
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);
|
||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(unsnapped_delta);
|
||||
}
|
||||
|
||||
if snap_angle && self.snapping_axis.is_some() {
|
||||
let Some(current_axis) = self.snapping_axis else { return };
|
||||
let total_delta = self.drag_start_pos - input.mouse.position;
|
||||
|
||||
if (total_delta.x.abs() > total_delta.y.abs() && current_axis == Axis::Y) || (total_delta.y.abs() > total_delta.x.abs() && current_axis == Axis::X) {
|
||||
self.stop_snap_along_axis(shape_editor, document, input, responses);
|
||||
self.start_snap_along_axis(shape_editor, document, input, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -872,6 +954,28 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
Self::Dragging(_) => {
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
|
||||
// Draw the snapping axis lines
|
||||
if tool_data.snapping_axis.is_some() {
|
||||
let Some(axis) = tool_data.snapping_axis else { return self };
|
||||
let origin = tool_data.drag_start_pos;
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
let mut faded_blue = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).rgba_hex();
|
||||
faded_blue.insert(0, '#');
|
||||
let other = faded_blue.as_str();
|
||||
|
||||
match axis {
|
||||
Axis::Y => {
|
||||
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_BLUE));
|
||||
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(other));
|
||||
}
|
||||
Axis::X | Axis::Both => {
|
||||
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(COLOR_OVERLAY_BLUE));
|
||||
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(other));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::InsertPoint => {
|
||||
let state = tool_data.update_insertion(shape_editor, document, responses, input);
|
||||
|
@ -1065,19 +1169,10 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
PathToolFsmState::Drawing { selection_shape: selection_type }
|
||||
}
|
||||
(
|
||||
PathToolFsmState::Dragging(dragging_state),
|
||||
PathToolMessage::PointerOutsideViewport {
|
||||
equidistant, snap_angle, lock_angle, ..
|
||||
},
|
||||
) => {
|
||||
(PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
if tool_data.auto_panning.shift_viewport(input, responses).is_some() {
|
||||
let equidistant = input.keyboard.get(equidistant as usize);
|
||||
let snap_angle = input.keyboard.get(snap_angle as usize);
|
||||
let lock_angle = input.keyboard.get(lock_angle as usize);
|
||||
|
||||
tool_data.drag(equidistant, lock_angle, snap_angle, shape_editor, document, input, responses);
|
||||
if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
tool_data.drag_start_pos += offset;
|
||||
}
|
||||
|
||||
PathToolFsmState::Dragging(dragging_state)
|
||||
|
@ -1223,6 +1318,10 @@ impl Fsm for PathToolFsmState {
|
|||
shape_editor.deselect_all_points();
|
||||
}
|
||||
|
||||
if tool_data.snapping_axis.is_some() {
|
||||
tool_data.snapping_axis = None;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue