mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 23:58:12 +08:00
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:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
4b7d8b7ab0
commit
e3e506ecfb
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user