mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
Add alignment overlays
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user