mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +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::overlays::utility_types::{DrawHandles, OverlayContext};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
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::network_interface::NodeNetworkInterface;
|
||||||
|
use crate::messages::portfolio::document::utility_types::transformation::Axis;
|
||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{
|
use crate::messages::tool::common_functionality::shape_editor::{
|
||||||
|
@ -373,6 +374,7 @@ struct PathToolData {
|
||||||
current_selected_handle_id: Option<ManipulatorPointId>,
|
current_selected_handle_id: Option<ManipulatorPointId>,
|
||||||
angle: f64,
|
angle: f64,
|
||||||
opposite_handle_position: Option<DVec2>,
|
opposite_handle_position: Option<DVec2>,
|
||||||
|
snapping_axis: Option<Axis>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathToolData {
|
impl PathToolData {
|
||||||
|
@ -736,6 +738,50 @@ impl PathToolData {
|
||||||
document.metadata().document_to_viewport.transform_vector2(snap_result.snapped_point_document - handle_position)
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn drag(
|
fn drag(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -747,6 +793,19 @@ impl PathToolData {
|
||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
responses: &mut VecDeque<Message>,
|
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 document_to_viewport = document.metadata().document_to_viewport;
|
||||||
let previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position);
|
let previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position);
|
||||||
let current_mouse = input.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 handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };
|
||||||
let opposite = if lock_angle { None } else { self.opposite_handle_position };
|
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);
|
let unsnapped_delta = current_mouse - previous_mouse;
|
||||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
|
|
||||||
|
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(_) => {
|
Self::Dragging(_) => {
|
||||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
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 => {
|
Self::InsertPoint => {
|
||||||
let state = tool_data.update_insertion(shape_editor, document, responses, input);
|
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::Drawing { selection_shape: selection_type }
|
||||||
}
|
}
|
||||||
(
|
(PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => {
|
||||||
PathToolFsmState::Dragging(dragging_state),
|
|
||||||
PathToolMessage::PointerOutsideViewport {
|
|
||||||
equidistant, snap_angle, lock_angle, ..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
if tool_data.auto_panning.shift_viewport(input, responses).is_some() {
|
if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||||
let equidistant = input.keyboard.get(equidistant as usize);
|
tool_data.drag_start_pos += offset;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PathToolFsmState::Dragging(dragging_state)
|
PathToolFsmState::Dragging(dragging_state)
|
||||||
|
@ -1223,6 +1318,10 @@ impl Fsm for PathToolFsmState {
|
||||||
shape_editor.deselect_all_points();
|
shape_editor.deselect_all_points();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tool_data.snapping_axis.is_some() {
|
||||||
|
tool_data.snapping_axis = None;
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::EndTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue