Add alignment overlays

This commit is contained in:
0hypercube
2022-01-04 20:07:44 +00:00
parent 7da03d7ddd
commit 9a0b908ab3
8 changed files with 81 additions and 31 deletions

View File

@@ -1,24 +1,71 @@
use glam::DVec2;
use graphene::LayerId;
use std::f64::consts::PI;
use crate::consts::SNAP_TOLERANCE;
use glam::{DAffine2, DVec2};
use graphene::{
layers::style::{self, Stroke},
LayerId, Operation,
};
use crate::{
consts::{COLOR_ACCENT, SNAP_TOLERANCE},
message_prelude::*,
};
use super::DocumentMessageHandler;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct SnapHandler {
snap_targets: Option<(Vec<f64>, Vec<f64>)>,
}
impl Default for SnapHandler {
fn default() -> Self {
Self { snap_targets: None }
}
overlay_paths: Vec<Vec<LayerId>>,
}
impl SnapHandler {
fn add_overlays(&mut self, responses: &mut VecDeque<Message>, viewport_bounds: DVec2) {
fn add_overlay_line(responses: &mut VecDeque<Message>, transform: DAffine2) -> Vec<LayerId> {
let layer_path = vec![generate_uuid()];
let operation = Operation::AddOverlayLine {
path: layer_path.clone(),
transform: transform.to_cols_array(),
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), None),
};
responses.push_back(DocumentMessage::Overlay(operation.into()).into());
layer_path
}
if let Some((x_targets, y_targets)) = &self.snap_targets {
for x_target in x_targets {
self.overlay_paths.push(add_overlay_line(
responses,
DAffine2::from_scale_angle_translation(DVec2::new(viewport_bounds.y, 1.), PI / 2., DVec2::new(x_target.round(), 0.)),
));
}
for y_target in y_targets {
self.overlay_paths.push(add_overlay_line(
responses,
DAffine2::from_scale_angle_translation(DVec2::new(viewport_bounds.x, 1.), 0., DVec2::new(0., y_target.round())),
));
}
}
}
fn remove_overlays(&mut self, responses: &mut VecDeque<Message>) {
while let Some(layer) = self.overlay_paths.pop() {
responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into());
}
}
/// 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.)
/// This should be called at the start of a drag.
pub fn start_snap(&mut self, document_message_handler: &DocumentMessageHandler, target_layers: Vec<Vec<LayerId>>, ignore_layers: &[Vec<LayerId>]) {
pub fn start_snap(
&mut self,
responses: &mut VecDeque<Message>,
viewport_bounds: DVec2,
document_message_handler: &DocumentMessageHandler,
target_layers: Vec<Vec<LayerId>>,
ignore_layers: &[Vec<LayerId>],
) {
if document_message_handler.snapping_enabled {
// Could be made into sorted Vec or a HashSet for more performant lookups.
self.snap_targets = Some(
@@ -30,6 +77,7 @@ impl SnapHandler {
.map(|vec| vec.into())
.unzip(),
);
self.add_overlays(responses, viewport_bounds);
}
}
@@ -104,7 +152,8 @@ impl SnapHandler {
}
}
pub fn cleanup(&mut self) {
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
self.remove_overlays(responses);
self.snap_targets = None;
}
}

View File

@@ -81,7 +81,7 @@ impl Fsm for EllipseToolFsmState {
if let ToolMessage::Ellipse(event) = event {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(document, input.mouse.position);
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(vec![generate_uuid()]);
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -112,12 +112,12 @@ impl Fsm for EllipseToolFsmState {
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
shape_data.cleanup();
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.push_back(DocumentMessage::AbortTransaction.into());
shape_data.cleanup();
shape_data.cleanup(responses);
Ready
}

View File

@@ -86,7 +86,7 @@ impl Fsm for LineToolFsmState {
if let ToolMessage::Line(event) = event {
match (self, event) {
(Ready, DragStart) => {
data.snap_handler.start_snap(document, document.all_layers_sorted(), &[]);
data.snap_handler.start_snap(responses, input.viewport_bounds.size(), document, document.all_layers_sorted(), &[]);
data.drag_start = data.snap_handler.snap_position(document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
@@ -120,7 +120,7 @@ impl Fsm for LineToolFsmState {
}
(Drawing, DragStop) => {
data.drag_current = data.snap_handler.snap_position(document, input.mouse.position);
data.snap_handler.cleanup();
data.snap_handler.cleanup(responses);
// TODO: introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
match data.drag_start == input.mouse.position {
@@ -133,7 +133,7 @@ impl Fsm for LineToolFsmState {
Ready
}
(Drawing, Abort) => {
data.snap_handler.cleanup();
data.snap_handler.cleanup(responses);
responses.push_back(DocumentMessage::AbortTransaction.into());
data.path = None;
Ready

View File

@@ -94,7 +94,7 @@ impl Fsm for PenToolFsmState {
data.path = Some(vec![generate_uuid()]);
data.layer_exists = false;
data.snap_handler.start_snap(document, document.all_layers_sorted(), &[]);
data.snap_handler.start_snap(responses, input.viewport_bounds.size(), document, document.all_layers_sorted(), &[]);
let snapped_position = data.snap_handler.snap_position(document, input.mouse.position);
let pos = transform.inverse() * DAffine2::from_translation(snapped_position);
@@ -148,7 +148,7 @@ impl Fsm for PenToolFsmState {
data.path = None;
data.points.clear();
data.snap_handler.cleanup();
data.snap_handler.cleanup(responses);
Ready
}

View File

@@ -82,7 +82,7 @@ impl Fsm for RectangleToolFsmState {
if let ToolMessage::Rectangle(event) = event {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(document, input.mouse.position);
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(vec![generate_uuid()]);
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -113,13 +113,13 @@ impl Fsm for RectangleToolFsmState {
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
shape_data.cleanup();
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.push_back(DocumentMessage::AbortTransaction.into());
shape_data.cleanup();
shape_data.cleanup(responses);
Ready
}

View File

@@ -14,9 +14,9 @@ pub struct Resize {
}
impl Resize {
/// Starts a resize, assigning the snap targets and snapping the starting position.
pub fn start(&mut self, document: &DocumentMessageHandler, mouse_position: DVec2) {
pub fn start(&mut self, responses: &mut VecDeque<Message>, viewport_bounds: DVec2, document: &DocumentMessageHandler, mouse_position: DVec2) {
let layers = document.all_layers_sorted();
self.snap_handler.start_snap(document, layers, &[]);
self.snap_handler.start_snap(responses, viewport_bounds, document, layers, &[]);
self.drag_start = self.snap_handler.snap_position(document, mouse_position);
}
@@ -47,8 +47,8 @@ impl Resize {
}
}
pub fn cleanup(&mut self) {
self.snap_handler.cleanup();
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
self.snap_handler.cleanup(responses);
self.path = None;
}
}

View File

@@ -200,7 +200,8 @@ impl Fsm for SelectToolFsmState {
} else {
Vec::new()
};
data.snap_handler.start_snap(document, document.non_selected_layers_sorted(), &ignore_layers);
data.snap_handler
.start_snap(responses, input.viewport_bounds.size(), document, document.non_selected_layers_sorted(), &ignore_layers);
state
}
(Dragging, MouseMove { snap_angle }) => {
@@ -255,7 +256,7 @@ impl Fsm for SelectToolFsmState {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
};
data.snap_handler.cleanup();
data.snap_handler.cleanup(responses);
responses.push_front(response.into());
Ready
}

View File

@@ -83,7 +83,7 @@ impl Fsm for ShapeToolFsmState {
if let ToolMessage::Shape(event) = event {
match (self, event) {
(Ready, DragStart) => {
shape_data.start(document, input.mouse.position);
shape_data.start(responses, input.viewport_bounds.size(), document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(vec![generate_uuid()]);
responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -121,12 +121,12 @@ impl Fsm for ShapeToolFsmState {
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
}
shape_data.cleanup();
shape_data.cleanup(responses);
Ready
}
(Drawing, Abort) => {
responses.push_back(DocumentMessage::AbortTransaction.into());
shape_data.cleanup();
shape_data.cleanup(responses);
Ready
}