Skip to content

Commit 0a2890c

Browse files
0HyperCubeotdaviesKeavon
authored
Add alignment overlays (#462)
* Add alignment overlays * Made snapping / overlays not render extra times, made snapping distance larger * Revert snap tolerance change until user testing can be done * WIP conversion to messages * Revert "WIP conversion to messages" This reverts commit ddcc238. * Overlay document always has (0,0) in top left * Fix AA on overlay lines * Merge branch 'master' into alignment-overlays * Squashed commit of the following: commit dbc1991 Author: asyncth <[email protected]> Date: Sun Jan 16 12:57:03 2022 +0500 Set the mouse cursor in the canvas based on the current tool and its state (#480) * Add FrontendMouseCursor and DisplayMouseCursor * Add update_cursor method to the Fsm trait and implement it for all tools * Rename DisplayMouseCursor to UpdateMouseCursor * Add 'To CSS Cursor Property' transform decorator and change the mouse cursor in the canvas based on the current tool and its state * Implement update_cursor for Navigate tool properly * Keep the cursor when dragging outside of the canvas * Change the mouse cursor to 'zoom-in' when LMB dragging on canvas with Navigate tool * Rename FrontendMouseCursor to MouseCursorIcon * Rename 'event' to 'e' and replace v-on with @ * Change the definition of the MouseCursorIcon type in TS * Replace switch with dictionary look-up * Move the definition of MouseCursorIcon closer to where it's used commit 9b36e6a Author: Keavon Chambers <[email protected]> Date: Sat Jan 15 17:24:58 2022 -0800 Fix all remaining Vue/TS errors flagged in Volar commit 2cc39cd Author: Keavon Chambers <[email protected]> Date: Sat Jan 15 12:55:51 2022 -0800 Tweak whitespace around `use` statements and other lint fixes commit fa390c3 Author: Keavon Chambers <[email protected]> Date: Sat Jan 15 06:35:06 2022 -0800 Change canvas artboard background color to be 1 shade lighter commit ea2d003 Author: Keavon Chambers <[email protected]> Date: Fri Jan 14 20:54:38 2022 -0800 Modify all message enum data to use named struct values, not tuples (#479) * Massively reorganize and clean up the whole Rust codebase * Modify all message enum data to use named struct values, not tuples commit 9b6cbb5 Author: Keavon Chambers <[email protected]> Date: Fri Jan 14 14:58:08 2022 -0800 Massively reorganize and clean up the whole Rust codebase (#478) * Massively reorganize and clean up the whole Rust codebase * Additional changes during code review commit a535f5c Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 16:17:40 2022 -0800 Sort messages and message handlers commit e708588 Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 14:16:13 2022 -0800 Standardize FrontendMessage message names commit 0b4934b Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 12:45:07 2022 -0800 Rename document_file.rs to document_message_handler.rs commit ec7bf4a Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 12:19:14 2022 -0800 Rename DocumentsMessage to PortfolioMessage commit 0991312 Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 11:44:49 2022 -0800 Rename document_message_handler.rs to portfolio_message_handler.rs commit c76c92e Author: 0HyperCube <[email protected]> Date: Wed Jan 12 19:05:55 2022 +0000 Migrate to using MoveSelectedLayersTo (#469) * migrate to using MoveSelectedLayersTo * Fix dragging a selected layer with multiple selected layers * Fix CreatedLayer overriding selection * Fix MoveSelectedLayersTo behaviour * Squashed commit of the following: commit 095d577 Author: Keavon Chambers <[email protected]> Date: Mon Jan 10 18:06:12 2022 -0800 Fix NumberInput clamping regression with undefined bounds commit 9f54a37 Author: mfish33 <[email protected]> Date: Sun Jan 9 15:52:55 2022 -0800 Fix bounds with artboards for zoom-to-fit and scrollbar scaling (#473) * - document load keeps postition - zoom to fit - scrollbars use artboard dimensions * - review comments - svg export uses all artboard bounds Co-authored-by: Keavon Chambers <[email protected]> commit 61432de Author: 0HyperCube <[email protected]> Date: Sat Jan 8 21:06:15 2022 +0000 Fix rotation input (#472) * Fix insert with no nesting at end of panel * Deselect other layers on paste * Resolve logging commit 5740283 Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 06:14:32 2022 -0800 Add `npm start` script alias for `npm run serve` commit 75e8fc6 Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 05:17:56 2022 -0800 Switch VS Code's Vue extension from Vetur to Volar commit 389b445 Author: Keavon Chambers <[email protected]> Date: Wed Jan 12 03:56:28 2022 -0800 Remove Charcoal references from the code for now commit 095d577 Author: Keavon Chambers <[email protected]> Date: Mon Jan 10 18:06:12 2022 -0800 Fix NumberInput clamping regression with undefined bounds commit 9f54a37 Author: mfish33 <[email protected]> Date: Sun Jan 9 15:52:55 2022 -0800 Fix bounds with artboards for zoom-to-fit and scrollbar scaling (#473) * - document load keeps postition - zoom to fit - scrollbars use artboard dimensions * - review comments - svg export uses all artboard bounds Co-authored-by: Keavon Chambers <[email protected]> commit 61432de Author: 0HyperCube <[email protected]> Date: Sat Jan 8 21:06:15 2022 +0000 Fix rotation input (#472) commit 3eeac79 Author: 0HyperCube <[email protected]> Date: Sat Jan 8 16:30:03 2022 +0000 Enhance the Navigate Tool zoom behavior (#461) * Snap zoom * Navigate zoom from centre * Ctrl to snap zoom in navigate * Use ctrl for global snap rotate * Fix the rotation input on snap rotate * Update hint to use ctrl * Fix mouse centre on drag * Click to zoom in * Clean up centre zoom * Update user input hints; tweak some variable names for clarity and standardization Co-authored-by: Keavon Chambers <[email protected]> commit a2c2f7f Author: 0HyperCube <[email protected]> Date: Sat Jan 8 16:02:02 2022 +0000 Add support for resizing workspace panels (#443) * Resize panels * Removing move_selection test pending #444 resolved * Bind event listners and cursor to the document * Fix flex grow on document being reset when drawing * Call onresize when the boundry is dragged * Add min panel size * Add explicit function return types * Dispatch resize event * Lock pointer instead of setting cursor on document Co-authored-by: otdavies <[email protected]> Co-authored-by: Keavon Chambers <[email protected]> commit 54e9121 Author: mfish33 <[email protected]> Date: Sat Jan 8 07:50:08 2022 -0800 Implement artboards and document version enforcement (#466) * - graphite document artboard implementation - autosave document load hitch fix - Autosave will delete saved files when graphite document version changes * formating * - top left 0,0 - fixed hitch on first document - vue calls first render * Revert * Merge branch 'master' into artboards * Small bug fixes and code review tweaks Co-authored-by: Oliver Davies <[email protected]> Co-authored-by: Keavon Chambers <[email protected]> commit 11f15bd Author: Leonard Pauli <[email protected]> Date: Sat Jan 8 14:25:24 2022 +0100 Path Tool: Implement anchor point dragging (#451) * #82 path-tool: WIP selecting control point working * Fix bug where duplication with Ctrl+D doesn't properly duplicate (#423) * bug fix: duplication didn't properly duplicate * cargo fmt * changed the formatting slightly for readability * Small cleanups, changed color of handles upon selection * Fix changes from merge * Remove duplicate anchor points on top of one another * Fix possible issues with thumbnails not being updated from Graphene operations * path-tool: attempt to move control points on click * Add dragging for control points * Editing shape anchors functional. Handles next. * Comment cleanup & slight cleanup of closest_anchor(..) * Removing conflict with master * Tiny code tweaks Co-authored-by: Keavon Chambers <[email protected]> Co-authored-by: caleb <[email protected]> Co-authored-by: otdavies <[email protected]> Co-authored-by: Dennis <[email protected]> commit 05e8a98 Author: Keavon Chambers <[email protected]> Date: Fri Jan 7 23:13:33 2022 -0800 Redesign the Layer Tree UI (#468) commit 8e3d237 Author: Oliver Davies <[email protected]> Date: Fri Jan 7 15:53:12 2022 -0800 Ungroup layers (#465) * WIP handling corner cases, like ungrouping subfolders * Resolved hanging * Fix recursive ungrouping * Functional, corner case free Ungroup. Small Undo issue & warnings * Update layertree upon undo * Also update layerdata upon redo * Add some polish * Resolved TODOs * Oops didn't save all after rename, ha. Co-authored-by: Dennis <[email protected]> commit c1c7192 Author: TrueDoctor <[email protected]> Date: Fri Jan 7 04:15:08 2022 +0100 Tidy up path handling in document_file (#464) * Tidy up path handling in document_file + Improve #455 * Cargo Clippy lints * Rename to_vec to map_to_vec Co-authored-by: Oliver Davies <[email protected]> commit f4707f9 Author: mfish33 <[email protected]> Date: Thu Jan 6 18:45:37 2022 -0800 Better decimal rounding in the NumberInput widget (#457) * better decimal rounding in NumberInput * - created function to reuse - used math instead of string manipulation * updated comment to be correct * updated comment * updated comment commit 0219f06 Author: Keavon Chambers <[email protected]> Date: Wed Jan 5 05:40:32 2022 -0800 Fix build script to use correct branch name * Fix indentation * Overlays fade in * Fix formatting * Add consts for snap visible and opacity * Tweak constants for improved UX, rejigger imports * Fix AA bounding box * Snap only visible layers * Add some comments Co-authored-by: otdavies <[email protected]> Co-authored-by: Keavon Chambers <[email protected]>
1 parent e3a2752 commit 0a2890c

14 files changed

+202
-107
lines changed

editor/src/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub const VIEWPORT_SCROLL_RATE: f64 = 0.6;
1616
pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
1717

1818
pub const SNAP_TOLERANCE: f64 = 3.;
19+
pub const SNAP_OVERLAY_FADE_DISTANCE: f64 = 20.;
20+
pub const SNAP_OVERLAY_UNSNAPPED_OPACITY: f64 = 0.4;
1921

2022
// Transforming layer
2123
pub const ROTATE_SNAP_ANGLE: f64 = 15.;

editor/src/document/document_message_handler.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ impl DocumentMessageHandler {
182182
self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.as_slice()))
183183
}
184184

185+
pub fn non_selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
186+
self.layer_metadata.iter().filter_map(|(path, data)| (!data.selected).then(|| path.as_slice()))
187+
}
188+
185189
pub fn selected_layers_without_children(&self) -> Vec<&[LayerId]> {
186190
let unique_layers = GrapheneDocument::shallowest_unique_layers(self.selected_layers());
187191

@@ -200,6 +204,13 @@ impl DocumentMessageHandler {
200204
})
201205
}
202206

207+
pub fn visible_layers(&self) -> impl Iterator<Item = &[LayerId]> {
208+
self.all_layers().filter(|path| match self.graphene_document.layer(path) {
209+
Ok(layer) => layer.visible,
210+
Err(_) => false,
211+
})
212+
}
213+
203214
fn serialize_structure(&self, folder: &Folder, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) {
204215
let mut space = 0;
205216
for (id, layer) in folder.layer_ids.iter().zip(folder.layers()).rev() {
@@ -267,7 +278,7 @@ impl DocumentMessageHandler {
267278
self.layer_metadata.keys().filter_map(|path| (!path.is_empty()).then(|| path.as_slice()))
268279
}
269280

270-
/// Returns the paths to all layers in order, optionally including only selected or non-selected layers.
281+
/// Returns the paths to all layers in order
271282
fn sort_layers<'a>(&self, paths: impl Iterator<Item = &'a [LayerId]>) -> Vec<&'a [LayerId]> {
272283
// Compute the indices for each layer to be able to sort them
273284
let mut layers_with_indices: Vec<(&[LayerId], Vec<usize>)> = paths
@@ -302,7 +313,7 @@ impl DocumentMessageHandler {
302313
/// Returns the paths to all non_selected layers in order
303314
#[allow(dead_code)] // used for test cases
304315
pub fn non_selected_layers_sorted(&self) -> Vec<&[LayerId]> {
305-
self.sort_layers(self.all_layers().filter(|layer| !self.selected_layers().any(|path| &path == layer)))
316+
self.sort_layers(self.non_selected_layers())
306317
}
307318

308319
pub fn layer_metadata(&self, path: &[LayerId]) -> &LayerMetadata {

editor/src/document/transformation.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl Translation {
6060
}
6161
}
6262

63+
#[must_use]
6364
pub fn increment_amount(self, delta: DVec2) -> Self {
6465
Self {
6566
dragged_distance: self.dragged_distance + delta,
@@ -87,6 +88,7 @@ impl Rotation {
8788
}
8889
}
8990

91+
#[must_use]
9092
pub fn increment_amount(self, delta: f64) -> Self {
9193
Self {
9294
dragged_angle: self.dragged_angle + delta,
@@ -124,6 +126,7 @@ impl Scale {
124126
}
125127
}
126128

129+
#[must_use]
127130
pub fn increment_amount(self, delta: f64) -> Self {
128131
Self {
129132
dragged_factor: self.dragged_factor + delta,

editor/src/input/input_preprocessor_message_handler.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,6 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
3737
}
3838
.into(),
3939
);
40-
responses.push_back(
41-
DocumentMessage::Overlays(
42-
graphene::Operation::TransformLayer {
43-
path: vec![],
44-
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
45-
}
46-
.into(),
47-
)
48-
.into(),
49-
);
5040
responses.push_back(
5141
DocumentMessage::Artboard(
5242
graphene::Operation::TransformLayer {

editor/src/viewport_tools/snapping.rs

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,105 @@
1-
use crate::consts::SNAP_TOLERANCE;
1+
use crate::consts::{COLOR_ACCENT, SNAP_OVERLAY_FADE_DISTANCE, SNAP_OVERLAY_UNSNAPPED_OPACITY, SNAP_TOLERANCE};
22
use crate::document::DocumentMessageHandler;
3+
use crate::message_prelude::*;
34

4-
use graphene::LayerId;
5+
use graphene::layers::style::{self, Stroke};
6+
use graphene::{LayerId, Operation};
57

6-
use glam::DVec2;
8+
use glam::{DAffine2, DVec2};
9+
use std::f64::consts::PI;
710

811
#[derive(Debug, Clone, Default)]
912
pub struct SnapHandler {
1013
snap_targets: Option<(Vec<f64>, Vec<f64>)>,
14+
overlay_paths: Vec<Vec<LayerId>>,
1115
}
1216

1317
impl SnapHandler {
18+
/// Updates the snapping overlays with the specified distances.
19+
/// `positions_and_distances` is a tuple of `position` and `distance` iterators, respectively, each with `(x, y)` values.
20+
fn update_overlays(
21+
overlay_paths: &mut Vec<Vec<LayerId>>,
22+
responses: &mut VecDeque<Message>,
23+
viewport_bounds: DVec2,
24+
(positions_and_distances): (impl Iterator<Item = (f64, f64)>, impl Iterator<Item = (f64, f64)>),
25+
closest_distance: DVec2,
26+
) {
27+
/// Draws an alignment line overlay with the correct transform and fade opacity, reusing lines from the pool if available.
28+
fn add_overlay_line(responses: &mut VecDeque<Message>, transform: [f64; 6], opacity: f64, index: usize, overlay_paths: &mut Vec<Vec<LayerId>>) {
29+
// If there isn't one in the pool to ruse, add a new alignment line to the pool with the intended transform
30+
let layer_path = if index >= overlay_paths.len() {
31+
let layer_path = vec![generate_uuid()];
32+
responses.push_back(
33+
DocumentMessage::Overlays(
34+
Operation::AddOverlayLine {
35+
path: layer_path.clone(),
36+
transform,
37+
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), None),
38+
}
39+
.into(),
40+
)
41+
.into(),
42+
);
43+
overlay_paths.push(layer_path.clone());
44+
layer_path
45+
}
46+
// Otherwise, reuse an overlay line from the pool and update its new transform
47+
else {
48+
let layer_path = overlay_paths[index].clone();
49+
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into()).into());
50+
layer_path
51+
};
52+
53+
// Then set its opacity to the fade amount
54+
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerOpacity { path: layer_path, opacity }.into()).into());
55+
}
56+
57+
let (positions, distances) = positions_and_distances;
58+
let mut index = 0;
59+
60+
// Draw the vertical alignment lines
61+
for (x_target, distance) in positions.filter(|(_pos, dist)| dist.abs() < SNAP_OVERLAY_FADE_DISTANCE) {
62+
let transform = DAffine2::from_scale_angle_translation(DVec2::new(viewport_bounds.y, 1.), PI / 2., DVec2::new((x_target).round() - 0.5, 0.)).to_cols_array();
63+
64+
let opacity = if closest_distance.x == distance {
65+
1.
66+
} else {
67+
SNAP_OVERLAY_UNSNAPPED_OPACITY - distance.abs() / (SNAP_OVERLAY_FADE_DISTANCE / SNAP_OVERLAY_UNSNAPPED_OPACITY)
68+
};
69+
70+
add_overlay_line(responses, transform, opacity, index, overlay_paths);
71+
index += 1;
72+
}
73+
// Draw the horizontal alignment lines
74+
for (y_target, distance) in distances.filter(|(_pos, dist)| dist.abs() < SNAP_OVERLAY_FADE_DISTANCE) {
75+
let transform = DAffine2::from_scale_angle_translation(DVec2::new(viewport_bounds.x, 1.), 0., DVec2::new(0., (y_target).round() - 0.5)).to_cols_array();
76+
77+
let opacity = if closest_distance.y == distance {
78+
1.
79+
} else {
80+
SNAP_OVERLAY_UNSNAPPED_OPACITY - distance.abs() / (SNAP_OVERLAY_FADE_DISTANCE / SNAP_OVERLAY_UNSNAPPED_OPACITY)
81+
};
82+
83+
add_overlay_line(responses, transform, opacity, index, overlay_paths);
84+
index += 1;
85+
}
86+
Self::remove_unused_overlays(overlay_paths, responses, index);
87+
}
88+
89+
/// Remove overlays from the pool beyond a given index. Pool entries up through that index will be kept.
90+
fn remove_unused_overlays(overlay_paths: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Message>, remove_after_index: usize) {
91+
while overlay_paths.len() > remove_after_index {
92+
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: overlay_paths.pop().unwrap() }.into()).into());
93+
}
94+
}
95+
1496
/// Gets a list of snap targets for the X and Y axes in Viewport coords for the target layers (usually all layers or all non-selected layers.)
1597
/// This should be called at the start of a drag.
16-
pub fn start_snap(&mut self, document_message_handler: &DocumentMessageHandler, target_layers: Vec<&[LayerId]>, ignore_layers: &[Vec<LayerId>]) {
98+
pub fn start_snap<'a>(&mut self, document_message_handler: &DocumentMessageHandler, target_layers: impl Iterator<Item = &'a [LayerId]>) {
1799
if document_message_handler.snapping_enabled {
18100
// Could be made into sorted Vec or a HashSet for more performant lookups.
19101
self.snap_targets = Some(
20102
target_layers
21-
.iter()
22-
.filter(|path| !ignore_layers.iter().any(|layer| layer.as_slice() == **path))
23103
.filter_map(|path| document_message_handler.graphene_document.viewport_bounding_box(path).ok()?)
24104
.flat_map(|[bound1, bound2]| [bound1, bound2, ((bound1 + bound2) / 2.)])
25105
.map(|vec| vec.into())
@@ -30,7 +110,14 @@ impl SnapHandler {
30110

31111
/// Finds the closest snap from an array of layers to the specified snap targets in viewport coords.
32112
/// Returns 0 for each axis that there is no snap less than the snap tolerance.
33-
pub fn snap_layers(&self, document_message_handler: &DocumentMessageHandler, selected_layers: &[Vec<LayerId>], mouse_delta: DVec2) -> DVec2 {
113+
pub fn snap_layers(
114+
&mut self,
115+
responses: &mut VecDeque<Message>,
116+
document_message_handler: &DocumentMessageHandler,
117+
selected_layers: &[Vec<LayerId>],
118+
viewport_bounds: DVec2,
119+
mouse_delta: DVec2,
120+
) -> DVec2 {
34121
if document_message_handler.snapping_enabled {
35122
if let Some((targets_x, targets_y)) = &self.snap_targets {
36123
let (snap_x, snap_y): (Vec<f64>, Vec<f64>) = selected_layers
@@ -40,24 +127,23 @@ impl SnapHandler {
40127
.map(|vec| vec.into())
41128
.unzip();
42129

43-
let closest_move = DVec2::new(
44-
targets_x
45-
.iter()
46-
.flat_map(|target| snap_x.iter().map(move |snap| target - mouse_delta.x - snap))
47-
.min_by(|a, b| a.abs().partial_cmp(&b.abs()).expect("Could not compare document bounds."))
48-
.unwrap_or(0.),
49-
targets_y
50-
.iter()
51-
.flat_map(|target| snap_y.iter().map(move |snap| target - mouse_delta.y - snap))
52-
.min_by(|a, b| a.abs().partial_cmp(&b.abs()).expect("Could not compare document bounds."))
53-
.unwrap_or(0.),
130+
let positions = targets_x.iter().flat_map(|&target| snap_x.iter().map(move |&snap| (target, target - mouse_delta.x - snap)));
131+
let distances = targets_y.iter().flat_map(|&target| snap_y.iter().map(move |&snap| (target, target - mouse_delta.y - snap)));
132+
133+
let min_positions = positions.clone().min_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).expect("Could not compare position."));
134+
let min_distances = distances.clone().min_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).expect("Could not compare position."));
135+
136+
let closest_distance = DVec2::new(min_positions.map_or(0., |(_pos, dist)| dist), min_distances.map_or(0., |(_pos, dist)| dist));
137+
138+
// Clamp, do not move, if above snap tolerance
139+
let clamped_closest_distance = DVec2::new(
140+
if closest_distance.x.abs() > SNAP_TOLERANCE { 0. } else { closest_distance.x },
141+
if closest_distance.y.abs() > SNAP_TOLERANCE { 0. } else { closest_distance.y },
54142
);
55143

56-
// Clamp, do not move if over snap tolerance
57-
DVec2::new(
58-
if closest_move.x.abs() > SNAP_TOLERANCE { 0. } else { closest_move.x },
59-
if closest_move.y.abs() > SNAP_TOLERANCE { 0. } else { closest_move.y },
60-
)
144+
Self::update_overlays(&mut self.overlay_paths, responses, viewport_bounds, (positions, distances), clamped_closest_distance);
145+
146+
clamped_closest_distance
61147
} else {
62148
DVec2::ZERO
63149
}
@@ -67,30 +153,26 @@ impl SnapHandler {
67153
}
68154

69155
/// Handles snapping of a viewport position, returning another viewport position.
70-
pub fn snap_position(&self, document_message_handler: &DocumentMessageHandler, position_viewport: DVec2) -> DVec2 {
156+
pub fn snap_position(&mut self, responses: &mut VecDeque<Message>, viewport_bounds: DVec2, document_message_handler: &DocumentMessageHandler, position_viewport: DVec2) -> DVec2 {
71157
if document_message_handler.snapping_enabled {
72158
if let Some((targets_x, targets_y)) = &self.snap_targets {
73-
// For each list of snap targets, find the shortest distance to move the point to that target.
74-
let closest_move = DVec2::new(
75-
targets_x
76-
.iter()
77-
.map(|x| (x - position_viewport.x))
78-
.min_by(|a, b| a.abs().partial_cmp(&b.abs()).expect("Could not compare document bounds."))
79-
.unwrap_or(0.),
80-
targets_y
81-
.iter()
82-
.map(|y| (y - position_viewport.y))
83-
.min_by(|a, b| a.abs().partial_cmp(&b.abs()).expect("Could not compare document bounds."))
84-
.unwrap_or(0.),
85-
);
159+
let positions = targets_x.iter().map(|&x| (x, x - position_viewport.x));
160+
let distances = targets_y.iter().map(|&y| (y, y - position_viewport.y));
161+
162+
let min_positions = positions.clone().min_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).expect("Could not compare position."));
163+
let min_distances = distances.clone().min_by(|a, b| a.1.abs().partial_cmp(&b.1.abs()).expect("Could not compare position."));
164+
165+
let closest_distance = DVec2::new(min_positions.map_or(0., |(_pos, dist)| dist), min_distances.map_or(0., |(_pos, dist)| dist));
86166

87167
// Do not move if over snap tolerance
88-
let clamped_closest_move = DVec2::new(
89-
if closest_move.x.abs() > SNAP_TOLERANCE { 0. } else { closest_move.x },
90-
if closest_move.y.abs() > SNAP_TOLERANCE { 0. } else { closest_move.y },
168+
let clamped_closest_distance = DVec2::new(
169+
if closest_distance.x.abs() > SNAP_TOLERANCE { 0. } else { closest_distance.x },
170+
if closest_distance.y.abs() > SNAP_TOLERANCE { 0. } else { closest_distance.y },
91171
);
92172

93-
position_viewport + clamped_closest_move
173+
Self::update_overlays(&mut self.overlay_paths, responses, viewport_bounds, (positions, distances), clamped_closest_distance);
174+
175+
position_viewport + clamped_closest_distance
94176
} else {
95177
position_viewport
96178
}
@@ -99,8 +181,9 @@ impl SnapHandler {
99181
}
100182
}
101183

102-
/// Removes snap target data. Call this when snapping is done.
103-
pub fn cleanup(&mut self) {
184+
/// Removes snap target data and overlays. Call this when snapping is done.
185+
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
186+
Self::remove_unused_overlays(&mut self.overlay_paths, responses, 0);
104187
self.snap_targets = None;
105188
}
106189
}

editor/src/viewport_tools/tool.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentTo
1616
pub trait Fsm {
1717
type ToolData;
1818

19+
#[must_use]
1920
fn transition(
2021
self,
2122
message: ToolMessage,

editor/src/viewport_tools/tools/ellipse.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl Fsm for EllipseToolFsmState {
104104
if let ToolMessage::Ellipse(event) = event {
105105
match (self, event) {
106106
(Ready, DragStart) => {
107-
shape_data.start(document, input.mouse.position);
107+
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
108108
responses.push_back(DocumentMessage::StartTransaction.into());
109109
shape_data.path = Some(vec![generate_uuid()]);
110110
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -122,7 +122,7 @@ impl Fsm for EllipseToolFsmState {
122122
Drawing
123123
}
124124
(state, Resize { center, lock_ratio }) => {
125-
if let Some(message) = shape_data.calculate_transform(document, center, lock_ratio, input) {
125+
if let Some(message) = shape_data.calculate_transform(responses, input.viewport_bounds.size(), document, center, lock_ratio, input) {
126126
responses.push_back(message);
127127
}
128128

@@ -135,12 +135,12 @@ impl Fsm for EllipseToolFsmState {
135135
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
136136
}
137137

138-
shape_data.cleanup();
138+
shape_data.cleanup(responses);
139139
Ready
140140
}
141141
(Drawing, Abort) => {
142142
responses.push_back(DocumentMessage::AbortTransaction.into());
143-
shape_data.cleanup();
143+
shape_data.cleanup(responses);
144144

145145
Ready
146146
}

editor/src/viewport_tools/tools/line.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ impl Fsm for LineToolFsmState {
111111
if let ToolMessage::Line(event) = event {
112112
match (self, event) {
113113
(Ready, DragStart) => {
114-
data.snap_handler.start_snap(document, document.all_layers_sorted(), &[]);
115-
data.drag_start = data.snap_handler.snap_position(document, input.mouse.position);
114+
data.snap_handler.start_snap(document, document.visible_layers());
115+
data.drag_start = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
116116

117117
responses.push_back(DocumentMessage::StartTransaction.into());
118118
data.path = Some(vec![generate_uuid()]);
@@ -136,16 +136,16 @@ impl Fsm for LineToolFsmState {
136136
Drawing
137137
}
138138
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
139-
data.drag_current = data.snap_handler.snap_position(document, input.mouse.position);
139+
data.drag_current = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
140140

141141
let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect();
142142
responses.push_back(generate_transform(data, values[0], values[1], values[2]));
143143

144144
Drawing
145145
}
146146
(Drawing, DragStop) => {
147-
data.drag_current = data.snap_handler.snap_position(document, input.mouse.position);
148-
data.snap_handler.cleanup();
147+
data.drag_current = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
148+
data.snap_handler.cleanup(responses);
149149

150150
// TODO: introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
151151
match data.drag_start == input.mouse.position {
@@ -158,7 +158,7 @@ impl Fsm for LineToolFsmState {
158158
Ready
159159
}
160160
(Drawing, Abort) => {
161-
data.snap_handler.cleanup();
161+
data.snap_handler.cleanup(responses);
162162
responses.push_back(DocumentMessage::AbortTransaction.into());
163163
data.path = None;
164164
Ready

0 commit comments

Comments
 (0)