mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 20:08:12 +08:00
Add alignment overlays
This commit is contained in:
+60
-11
@@ -1,24 +1,71 @@
|
|||||||
use glam::DVec2;
|
use std::f64::consts::PI;
|
||||||
use graphene::LayerId;
|
|
||||||
|
|
||||||
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;
|
use super::DocumentMessageHandler;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct SnapHandler {
|
pub struct SnapHandler {
|
||||||
snap_targets: Option<(Vec<f64>, Vec<f64>)>,
|
snap_targets: Option<(Vec<f64>, Vec<f64>)>,
|
||||||
}
|
overlay_paths: Vec<Vec<LayerId>>,
|
||||||
impl Default for SnapHandler {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { snap_targets: None }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SnapHandler {
|
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.)
|
/// 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.
|
/// 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 {
|
if document_message_handler.snapping_enabled {
|
||||||
// Could be made into sorted Vec or a HashSet for more performant lookups.
|
// Could be made into sorted Vec or a HashSet for more performant lookups.
|
||||||
self.snap_targets = Some(
|
self.snap_targets = Some(
|
||||||
@@ -30,6 +77,7 @@ impl SnapHandler {
|
|||||||
.map(|vec| vec.into())
|
.map(|vec| vec.into())
|
||||||
.unzip(),
|
.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;
|
self.snap_targets = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl Fsm for EllipseToolFsmState {
|
|||||||
if let ToolMessage::Ellipse(event) = event {
|
if let ToolMessage::Ellipse(event) = event {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(Ready, DragStart) => {
|
(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());
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
shape_data.path = Some(vec![generate_uuid()]);
|
shape_data.path = Some(vec![generate_uuid()]);
|
||||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||||
@@ -112,12 +112,12 @@ impl Fsm for EllipseToolFsmState {
|
|||||||
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_data.cleanup();
|
shape_data.cleanup(responses);
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
(Drawing, Abort) => {
|
(Drawing, Abort) => {
|
||||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||||
shape_data.cleanup();
|
shape_data.cleanup(responses);
|
||||||
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ impl Fsm for LineToolFsmState {
|
|||||||
if let ToolMessage::Line(event) = event {
|
if let ToolMessage::Line(event) = event {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(Ready, DragStart) => {
|
(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);
|
data.drag_start = data.snap_handler.snap_position(document, input.mouse.position);
|
||||||
|
|
||||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
@@ -120,7 +120,7 @@ impl Fsm for LineToolFsmState {
|
|||||||
}
|
}
|
||||||
(Drawing, DragStop) => {
|
(Drawing, DragStop) => {
|
||||||
data.drag_current = data.snap_handler.snap_position(document, input.mouse.position);
|
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)
|
// TODO: introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||||
match data.drag_start == input.mouse.position {
|
match data.drag_start == input.mouse.position {
|
||||||
@@ -133,7 +133,7 @@ impl Fsm for LineToolFsmState {
|
|||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
(Drawing, Abort) => {
|
(Drawing, Abort) => {
|
||||||
data.snap_handler.cleanup();
|
data.snap_handler.cleanup(responses);
|
||||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||||
data.path = None;
|
data.path = None;
|
||||||
Ready
|
Ready
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl Fsm for PenToolFsmState {
|
|||||||
data.path = Some(vec![generate_uuid()]);
|
data.path = Some(vec![generate_uuid()]);
|
||||||
data.layer_exists = false;
|
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 snapped_position = data.snap_handler.snap_position(document, input.mouse.position);
|
||||||
|
|
||||||
let pos = transform.inverse() * DAffine2::from_translation(snapped_position);
|
let pos = transform.inverse() * DAffine2::from_translation(snapped_position);
|
||||||
@@ -148,7 +148,7 @@ impl Fsm for PenToolFsmState {
|
|||||||
|
|
||||||
data.path = None;
|
data.path = None;
|
||||||
data.points.clear();
|
data.points.clear();
|
||||||
data.snap_handler.cleanup();
|
data.snap_handler.cleanup(responses);
|
||||||
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ impl Fsm for RectangleToolFsmState {
|
|||||||
if let ToolMessage::Rectangle(event) = event {
|
if let ToolMessage::Rectangle(event) = event {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(Ready, DragStart) => {
|
(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());
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
shape_data.path = Some(vec![generate_uuid()]);
|
shape_data.path = Some(vec![generate_uuid()]);
|
||||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||||
@@ -113,13 +113,13 @@ impl Fsm for RectangleToolFsmState {
|
|||||||
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_data.cleanup();
|
shape_data.cleanup(responses);
|
||||||
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
(Drawing, Abort) => {
|
(Drawing, Abort) => {
|
||||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||||
shape_data.cleanup();
|
shape_data.cleanup(responses);
|
||||||
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ pub struct Resize {
|
|||||||
}
|
}
|
||||||
impl Resize {
|
impl Resize {
|
||||||
/// Starts a resize, assigning the snap targets and snapping the starting position.
|
/// 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();
|
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);
|
self.drag_start = self.snap_handler.snap_position(document, mouse_position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ impl Resize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cleanup(&mut self) {
|
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
||||||
self.snap_handler.cleanup();
|
self.snap_handler.cleanup(responses);
|
||||||
self.path = None;
|
self.path = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ impl Fsm for SelectToolFsmState {
|
|||||||
} else {
|
} else {
|
||||||
Vec::new()
|
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
|
state
|
||||||
}
|
}
|
||||||
(Dragging, MouseMove { snap_angle }) => {
|
(Dragging, MouseMove { snap_angle }) => {
|
||||||
@@ -255,7 +256,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
true => DocumentMessage::Undo,
|
true => DocumentMessage::Undo,
|
||||||
false => DocumentMessage::CommitTransaction,
|
false => DocumentMessage::CommitTransaction,
|
||||||
};
|
};
|
||||||
data.snap_handler.cleanup();
|
data.snap_handler.cleanup(responses);
|
||||||
responses.push_front(response.into());
|
responses.push_front(response.into());
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ impl Fsm for ShapeToolFsmState {
|
|||||||
if let ToolMessage::Shape(event) = event {
|
if let ToolMessage::Shape(event) = event {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(Ready, DragStart) => {
|
(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());
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
shape_data.path = Some(vec![generate_uuid()]);
|
shape_data.path = Some(vec![generate_uuid()]);
|
||||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||||
@@ -121,12 +121,12 @@ impl Fsm for ShapeToolFsmState {
|
|||||||
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
shape_data.cleanup();
|
shape_data.cleanup(responses);
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
(Drawing, Abort) => {
|
(Drawing, Abort) => {
|
||||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||||
shape_data.cleanup();
|
shape_data.cleanup(responses);
|
||||||
|
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user