Snapping system improvements and refactor (#621)

* Snap to points and refactor

* Improve dot position on bounds

* Add snap matrix

* Cleanup

* Code review

* Half axis fade rather than increase it

* Fix fmt

* Hide snap to point overlay when active

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-05-09 04:13:57 +01:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 4b7d8b7ab0
commit e3e506ecfb
14 changed files with 303 additions and 183 deletions
@@ -163,6 +163,7 @@ impl Fsm for ArtboardToolFsmState {
data.snap_handler
.start_snap(document, document.bounding_boxes(None, Some(data.selected_board.unwrap())), snap_x, snap_y);
data.snap_handler.add_all_document_handles(document, &[], &[]);
ArtboardToolFsmState::ResizingBounds
} else {
@@ -175,6 +176,7 @@ impl Fsm for ArtboardToolFsmState {
data.selected_board = Some(intersection[0]);
data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(intersection[0])), true, true);
data.snap_handler.add_all_document_handles(document, &[], &[]);
responses.push_back(
PropertiesPanelMessage::SetActiveLayers {
@@ -190,6 +192,7 @@ impl Fsm for ArtboardToolFsmState {
data.selected_board = Some(id);
data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(id)), true, true);
data.snap_handler.add_all_document_handles(document, &[], &[]);
responses.push_back(
ArtboardMessage::AddArtboard {
@@ -213,7 +216,7 @@ impl Fsm for ArtboardToolFsmState {
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = input.mouse.position;
let snapped_mouse_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, mouse_position);
let snapped_mouse_position = data.snap_handler.snap_position(responses, document, mouse_position);
let [position, size] = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
let position = movement.center_position(position, size, from_center);
@@ -239,8 +242,8 @@ impl Fsm for ArtboardToolFsmState {
let mouse_position = axis_align_drag(axis_align, input.mouse.position, data.drag_start);
let mouse_delta = mouse_position - data.drag_current;
let snap = bounds.evaluate_transform_handle_positions().iter().map(|v| (v.x, v.y)).unzip();
let closest_move = data.snap_handler.snap_layers(responses, document, snap, input.viewport_bounds.size(), mouse_delta);
let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
let closest_move = data.snap_handler.snap_layers(responses, document, snap, mouse_delta);
let size = bounds.bounds[1] - bounds.bounds[0];
@@ -263,7 +266,7 @@ impl Fsm for ArtboardToolFsmState {
}
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let mouse_position = input.mouse.position;
let snapped_mouse_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, mouse_position);
let snapped_mouse_position = data.snap_handler.snap_position(responses, document, mouse_position);
let root_transform = document.graphene_document.root.transform.inverse();
@@ -110,7 +110,7 @@ impl Fsm for EllipseToolFsmState {
if let ToolMessage::Ellipse(event) = event {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
shape_data.start(responses, document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -128,7 +128,7 @@ impl Fsm for EllipseToolFsmState {
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, input.viewport_bounds.size(), document, center, lock_ratio, input) {
if let Some(message) = shape_data.calculate_transform(responses, document, center, lock_ratio, input) {
responses.push_back(message);
}
@@ -11,7 +11,7 @@ use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
use graphene::color::Color;
use graphene::intersection::Quad;
use graphene::layers::layer_info::{Layer, LayerDataType};
use graphene::layers::layer_info::Layer;
use graphene::layers::style::{Fill, Gradient, PathStyle, Stroke};
use graphene::Operation;
@@ -241,25 +241,9 @@ struct GradientToolData {
snap_handler: SnapHandler,
}
pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHandler, layer: &Layer, path: &[LayerId]) {
pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHandler) {
snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
if let LayerDataType::Shape(s) = &layer.data {
let transform = document.graphene_document.multiply_transforms(path).unwrap();
let snap_points = s
.path
.iter()
.filter_map(|shape| match shape {
kurbo::PathEl::MoveTo(point) => Some(point),
kurbo::PathEl::LineTo(point) => Some(point),
kurbo::PathEl::QuadTo(_, point) => Some(point),
kurbo::PathEl::CurveTo(_, _, point) => Some(point),
kurbo::PathEl::ClosePath => None,
})
.map(|point| DVec2::new(point.x, point.y))
.map(|pos| transform.transform_point2(pos))
.collect();
snap_handler.add_snap_points(document, snap_points);
}
snap_handler.add_all_document_handles(document, &[], &[]);
}
impl Fsm for GradientToolFsmState {
@@ -307,7 +291,7 @@ impl Fsm for GradientToolFsmState {
for overlay in &data.gradient_overlays {
if overlay.evaluate_gradient_start().distance_squared(mouse) < tolerance {
dragging = true;
start_snap(&mut data.snap_handler, document, document.graphene_document.layer(&overlay.path).unwrap(), &overlay.path);
start_snap(&mut data.snap_handler, document);
data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
transform: overlay.transform,
@@ -317,7 +301,7 @@ impl Fsm for GradientToolFsmState {
}
if overlay.evaluate_gradient_end().distance_squared(mouse) < tolerance {
dragging = true;
start_snap(&mut data.snap_handler, document, document.graphene_document.layer(&overlay.path).unwrap(), &overlay.path);
start_snap(&mut data.snap_handler, document);
data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
transform: overlay.transform,
@@ -348,7 +332,7 @@ impl Fsm for GradientToolFsmState {
data.selected_gradient = Some(selected_gradient);
start_snap(&mut data.snap_handler, document, layer, &intersection);
start_snap(&mut data.snap_handler, document);
GradientToolFsmState::Drawing
} else {
@@ -358,7 +342,7 @@ impl Fsm for GradientToolFsmState {
}
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
if let Some(selected_gradient) = &mut data.selected_gradient {
let mouse = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let mouse = data.snap_handler.snap_position(responses, document, input.mouse.position);
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize));
}
GradientToolFsmState::Drawing
+4 -3
View File
@@ -155,7 +155,8 @@ impl Fsm for LineToolFsmState {
match (self, event) {
(Ready, DragStart) => {
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
data.drag_start = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
data.snap_handler.add_all_document_handles(document, &[], &[]);
data.drag_start = data.snap_handler.snap_position(responses, document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
data.path = Some(document.get_path_for_new_layer());
@@ -176,7 +177,7 @@ impl Fsm for LineToolFsmState {
Drawing
}
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
data.drag_current = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
data.drag_current = data.snap_handler.snap_position(responses, document, input.mouse.position);
let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect();
responses.push_back(generate_transform(data, values[0], values[1], values[2]));
@@ -184,7 +185,7 @@ impl Fsm for LineToolFsmState {
Drawing
}
(Drawing, DragStop) => {
data.drag_current = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
data.drag_current = data.snap_handler.snap_position(responses, document, input.mouse.position);
data.snap_handler.cleanup(responses);
match data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD {
+8 -10
View File
@@ -144,15 +144,13 @@ impl Fsm for PathToolFsmState {
// Select the first point within the threshold (in pixels)
if data.shape_editor.select_point(input.mouse.position, SELECTION_THRESHOLD, add_to_selection, responses) {
responses.push_back(DocumentMessage::StartTransaction.into());
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
let snap_points = data
.shape_editor
.shapes_to_modify
.iter()
.flat_map(|shape| shape.anchors.iter().flat_map(|anchor| anchor.points[0].as_ref()))
.map(|point| point.position)
.collect();
data.snap_handler.add_snap_points(document, snap_points);
let ignore_document = data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.clone()).collect::<Vec<_>>();
data.snap_handler.start_snap(document, document.bounding_boxes(Some(&ignore_document), None), true, true);
let include_handles = data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.as_slice()).collect::<Vec<_>>();
data.snap_handler.add_all_document_handles(document, &include_handles, &[]);
data.drag_start_pos = input.mouse.position;
Dragging
}
@@ -209,7 +207,7 @@ impl Fsm for PathToolFsmState {
}
// Move the selected points by the mouse position
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
data.shape_editor.move_selected_points(snapped_position - data.drag_start_pos, true, responses);
Dragging
}
+5 -4
View File
@@ -169,7 +169,8 @@ impl Fsm for PenToolFsmState {
// Create a new layer and prep snap system
data.path = Some(document.get_path_for_new_layer());
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
data.snap_handler.add_all_document_handles(document, &[], &[]);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
// Get the position and set properties
let start_position = transform.inverse().transform_point2(snapped_position);
@@ -217,14 +218,14 @@ impl Fsm for PenToolFsmState {
}
// Move the newly selected points to the cursor
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
data.shape_editor.move_selected_points(snapped_position, false, responses);
Drawing
}
(Drawing, PointerMove) => {
// Move selected points
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
data.shape_editor.move_selected_points(snapped_position, false, responses);
Drawing
@@ -302,7 +303,7 @@ fn add_to_curve(data: &mut PenToolData, input: &InputPreprocessorMessageHandler,
update_path_representation(data);
// Setup our position params
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
let position = transform.inverse().transform_point2(snapped_position);
// Add a curve to the path
@@ -109,7 +109,7 @@ impl Fsm for RectangleToolFsmState {
if let ToolMessage::Rectangle(event) = event {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
shape_data.start(responses, document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -127,7 +127,7 @@ impl Fsm for RectangleToolFsmState {
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, input.viewport_bounds.size(), document, center, lock_ratio, input) {
if let Some(message) = shape_data.calculate_transform(responses, document, center, lock_ratio, input) {
responses.push_back(message);
}
@@ -9,7 +9,7 @@ use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{IconButton, LayoutRow, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::SnapHandler;
use crate::viewport_tools::snapping::{self, SnapHandler};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
use graphene::boolean_ops::BooleanOperation;
use graphene::document::Document;
@@ -404,6 +404,7 @@ impl Fsm for SelectToolFsmState {
let snap_y = selected_edges.0 || selected_edges.1;
data.snap_handler.start_snap(document, document.bounding_boxes(Some(&selected), None), snap_x, snap_y);
data.snap_handler.add_all_document_handles(document, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>());
data.layers_dragging = selected;
@@ -461,11 +462,10 @@ impl Fsm for SelectToolFsmState {
.layers_dragging
.iter()
.filter_map(|path| document.graphene_document.viewport_bounding_box(path).ok()?)
.flat_map(|[bound1, bound2]| [bound1, bound2, (bound1 + bound2) / 2.])
.map(|vec| vec.into())
.unzip();
.flat_map(snapping::expand_bounds)
.collect();
let closest_move = data.snap_handler.snap_layers(responses, document, snap, input.viewport_bounds.size(), mouse_delta);
let closest_move = data.snap_handler.snap_layers(responses, document, snap, mouse_delta);
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for path in Document::shallowest_unique_layers(data.layers_dragging.iter()) {
responses.push_front(
@@ -486,7 +486,7 @@ impl Fsm for SelectToolFsmState {
let mouse_position = input.mouse.position;
let snapped_mouse_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, mouse_position);
let snapped_mouse_position = data.snap_handler.snap_position(responses, document, mouse_position);
let [_position, size] = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align);
let delta = movement.bounds_to_scale_transform(center, size);
@@ -149,7 +149,7 @@ impl Fsm for ShapeToolFsmState {
if let ToolMessage::Shape(event) = event {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
shape_data.start(responses, document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -169,7 +169,7 @@ impl Fsm for ShapeToolFsmState {
Drawing
}
(state, Resize { center, lock_ratio }) => {
if let Some(message) = shape_data.calculate_transform(responses, input.viewport_bounds.size(), document, center, lock_ratio, input) {
if let Some(message) = shape_data.calculate_transform(responses, document, center, lock_ratio, input) {
responses.push_back(message);
}
@@ -18,15 +18,15 @@ pub struct Resize {
impl Resize {
/// Starts a resize, assigning the snap targets and snapping the starting position.
pub fn start(&mut self, responses: &mut VecDeque<Message>, viewport_bounds: DVec2, document: &DocumentMessageHandler, mouse_position: DVec2) {
pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2) {
self.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
self.drag_start = self.snap_handler.snap_position(responses, viewport_bounds, document, mouse_position);
self.snap_handler.add_all_document_handles(document, &[], &[]);
self.drag_start = self.snap_handler.snap_position(responses, document, mouse_position);
}
pub fn calculate_transform(
&mut self,
responses: &mut VecDeque<Message>,
viewport_bounds: DVec2,
document: &DocumentMessageHandler,
center: Key,
lock_ratio: Key,
@@ -35,7 +35,7 @@ impl Resize {
if let Some(path) = &self.path {
let mut start = self.drag_start;
let stop = self.snap_handler.snap_position(responses, viewport_bounds, document, ipp.mouse.position);
let stop = self.snap_handler.snap_position(responses, document, ipp.mouse.position);
let mut size = stop - start;
if ipp.keyboard.get(lock_ratio as usize) {
@@ -156,7 +156,8 @@ impl Fsm for SplineToolFsmState {
data.path = Some(document.get_path_for_new_layer());
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
data.snap_handler.add_all_document_handles(document, &[], &[]);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
@@ -170,7 +171,7 @@ impl Fsm for SplineToolFsmState {
Drawing
}
(Drawing, DragStop) => {
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
if let Some(last_pos) = data.points.last() {
@@ -186,7 +187,7 @@ impl Fsm for SplineToolFsmState {
Drawing
}
(Drawing, PointerMove) => {
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position);
data.next_point = pos;