mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 17:08:11 +08:00
Layer and grid snapping systems (#1521)
* Grid overlays * Rectangle tool basic snapping * Fix bezier demos * Fix bézier crate tests * Constrained snapping for circle & shape tool * Line tool snapping * Pen tool snapping * Path tool snapping * Snapping whilst dragging layers (not constrained) * Constrained drag * Resize snapping * Normal and tangent * Cleanup * Grid snapping * Grid snapping * Fix imports * Fix bug in artboard tool * Fix hang on 0 size grid spacing * Fix NaN when scaling * Polishing --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
78a1bb17cd
commit
456ca170a4
@@ -116,12 +116,7 @@ impl ArtboardToolData {
|
||||
Some(edges)
|
||||
}
|
||||
|
||||
fn start_resizing(&mut self, selected_edges: (bool, bool, bool, bool), document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) {
|
||||
let snap_x = selected_edges.2 || selected_edges.3;
|
||||
let snap_y = selected_edges.0 || selected_edges.1;
|
||||
|
||||
self.snap_manager.start_snap(document, input, document.bounding_boxes(), snap_x, snap_y);
|
||||
|
||||
fn start_resizing(&mut self, _selected_edges: (bool, bool, bool, bool), _document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) {
|
||||
if let Some(bounds) = &mut self.bounding_box_manager {
|
||||
bounds.center_of_transformation = (bounds.bounds[0] + bounds.bounds[1]) / 2.;
|
||||
}
|
||||
@@ -138,7 +133,11 @@ impl ArtboardToolData {
|
||||
if let Some(intersection) = intersections.next() {
|
||||
self.selected_artboard = Some(intersection);
|
||||
|
||||
self.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
if let Some(bounds) = document.metadata().bounding_box_document(intersection) {
|
||||
let bounding_box_manager = self.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||
bounding_box_manager.bounds = bounds;
|
||||
bounding_box_manager.transform = document.metadata().document_to_viewport;
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
@@ -150,7 +149,7 @@ impl ArtboardToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_artboard(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) {
|
||||
fn resize_artboard(&mut self, responses: &mut VecDeque<Message>, _document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) {
|
||||
let Some(bounds) = &self.bounding_box_manager else {
|
||||
return;
|
||||
};
|
||||
@@ -158,9 +157,8 @@ impl ArtboardToolData {
|
||||
return;
|
||||
};
|
||||
|
||||
let snapped_mouse_position: DVec2 = self.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
|
||||
let centre = from_center.then_some(bounds.center_of_transformation);
|
||||
let (position, size) = movement.new_size(mouse_position, bounds.transform, centre, constrain_square, None);
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: self.selected_artboard.unwrap().to_node(),
|
||||
location: position.round().as_ivec2(),
|
||||
@@ -223,16 +221,9 @@ impl Fsm for ArtboardToolFsmState {
|
||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
|
||||
if let Some(bounds) = &tool_data.bounding_box_manager {
|
||||
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
|
||||
|
||||
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
|
||||
let mouse_delta = mouse_position - tool_data.drag_current;
|
||||
|
||||
let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
|
||||
let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta);
|
||||
|
||||
let size = bounds.bounds[1] - bounds.bounds[0];
|
||||
|
||||
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
|
||||
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current);
|
||||
|
||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||
id: tool_data.selected_artboard.unwrap().to_node(),
|
||||
@@ -242,13 +233,13 @@ impl Fsm for ArtboardToolFsmState {
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
|
||||
tool_data.drag_current = mouse_position + closest_move;
|
||||
tool_data.drag_current = mouse_position;
|
||||
}
|
||||
ArtboardToolFsmState::Dragging
|
||||
}
|
||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
let snapped_mouse_position = mouse_position; //tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let root_transform = document.metadata().document_to_viewport.inverse();
|
||||
|
||||
@@ -277,7 +268,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||
let id = NodeId(generate_uuid());
|
||||
tool_data.selected_artboard = Some(LayerNodeIdentifier::new_unchecked(id));
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
//tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::uuid::generate_uuid;
|
||||
@@ -48,7 +50,7 @@ pub enum EllipseOptionsUpdate {
|
||||
pub enum EllipseToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
CanvasTransformed,
|
||||
Overlays(OverlayContext),
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
#[remain::unsorted]
|
||||
@@ -57,7 +59,7 @@ pub enum EllipseToolMessage {
|
||||
// Tool-specific messages
|
||||
DragStart,
|
||||
DragStop,
|
||||
Resize {
|
||||
PointerMove {
|
||||
center: Key,
|
||||
lock_ratio: Key,
|
||||
},
|
||||
@@ -147,11 +149,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Ellipse
|
||||
match self.fsm_state {
|
||||
Ready => actions!(EllipseToolMessageDiscriminant;
|
||||
DragStart,
|
||||
PointerMove,
|
||||
),
|
||||
Drawing => actions!(EllipseToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
PointerMove,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -160,7 +163,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Ellipse
|
||||
impl ToolTransition for EllipseTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
canvas_transformed: Some(EllipseToolMessage::CanvasTransformed.into()),
|
||||
overlay_provider: Some(|overlay_context| EllipseToolMessage::Overlays(overlay_context).into()),
|
||||
tool_abort: Some(EllipseToolMessage::Abort.into()),
|
||||
working_color_changed: Some(EllipseToolMessage::WorkingColorChanged.into()),
|
||||
..Default::default()
|
||||
@@ -195,12 +198,12 @@ impl Fsm for EllipseToolFsmState {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input);
|
||||
(_, EllipseToolMessage::Overlays(mut overlay_context)) => {
|
||||
shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
self
|
||||
}
|
||||
(EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => {
|
||||
shape_data.start(responses, document, input);
|
||||
shape_data.start(document, input);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Create a new ellipse vector shape
|
||||
@@ -223,12 +226,17 @@ impl Fsm for EllipseToolFsmState {
|
||||
|
||||
EllipseToolFsmState::Drawing
|
||||
}
|
||||
(state, EllipseToolMessage::Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::PointerMove { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
self
|
||||
}
|
||||
(_, EllipseToolMessage::PointerMove { .. }) => {
|
||||
shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct GradientOptions {
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, ToolMessage, Gradient)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)]
|
||||
pub enum GradientToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
@@ -267,10 +267,6 @@ struct GradientToolData {
|
||||
drag_start: DVec2,
|
||||
}
|
||||
|
||||
pub fn start_snap(snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) {
|
||||
snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
}
|
||||
|
||||
impl Fsm for GradientToolFsmState {
|
||||
type ToolData = GradientToolData;
|
||||
type ToolOptions = GradientOptions;
|
||||
@@ -303,7 +299,7 @@ impl Fsm for GradientToolFsmState {
|
||||
let Gradient { start, end, positions, .. } = gradient;
|
||||
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
||||
|
||||
overlay_context.line(start, end);
|
||||
overlay_context.line(start, end, None);
|
||||
overlay_context.handle(start, dragging == Some(GradientDragTarget::Start));
|
||||
overlay_context.handle(end, dragging == Some(GradientDragTarget::End));
|
||||
|
||||
@@ -430,7 +426,6 @@ impl Fsm for GradientToolFsmState {
|
||||
let pos = transform.transform_point2(pos);
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
start_snap(&mut tool_data.snap_manager, document, input);
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
layer,
|
||||
transform,
|
||||
@@ -474,8 +469,6 @@ impl Fsm for GradientToolFsmState {
|
||||
|
||||
tool_data.selected_gradient = Some(selected_gradient);
|
||||
|
||||
start_snap(&mut tool_data.snap_manager, document, input);
|
||||
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
GradientToolFsmState::Ready
|
||||
@@ -484,7 +477,7 @@ impl Fsm for GradientToolFsmState {
|
||||
}
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let mouse = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
|
||||
}
|
||||
GradientToolFsmState::Drawing
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Fsm for ImaginateToolFsmState {
|
||||
self
|
||||
}
|
||||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||
shape_data.start(responses, document, input);
|
||||
shape_data.start(document, input);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), document.network()));
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
@@ -168,7 +168,7 @@ impl Fsm for ImaginateToolFsmState {
|
||||
ImaginateToolFsmState::Drawing
|
||||
}
|
||||
(state, ImaginateToolMessage::Resize { center, lock_ratio }) => {
|
||||
let message = shape_data.calculate_transform(responses, document, input, center, lock_ratio, true);
|
||||
let message = shape_data.calculate_transform(document, input, center, lock_ratio, true);
|
||||
responses.try_add(message);
|
||||
|
||||
state
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::uuid::generate_uuid;
|
||||
@@ -39,7 +40,7 @@ pub enum LineToolMessage {
|
||||
#[remain::unsorted]
|
||||
DocumentIsDirty,
|
||||
#[remain::unsorted]
|
||||
CanvasTransformed,
|
||||
Overlays(OverlayContext),
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
#[remain::unsorted]
|
||||
@@ -48,7 +49,7 @@ pub enum LineToolMessage {
|
||||
// Tool-specific messages
|
||||
DragStart,
|
||||
DragStop,
|
||||
Redraw {
|
||||
PointerMove {
|
||||
center: Key,
|
||||
lock_angle: Key,
|
||||
snap_angle: Key,
|
||||
@@ -127,8 +128,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineToo
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
match self.fsm_state {
|
||||
LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart),
|
||||
LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, Redraw, Abort),
|
||||
LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart, PointerMove),
|
||||
LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, PointerMove, Abort),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +137,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineToo
|
||||
impl ToolTransition for LineTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
canvas_transformed: Some(LineToolMessage::CanvasTransformed.into()),
|
||||
overlay_provider: Some(|overlay_context| LineToolMessage::Overlays(overlay_context).into()),
|
||||
tool_abort: Some(LineToolMessage::Abort.into()),
|
||||
working_color_changed: Some(LineToolMessage::WorkingColorChanged.into()),
|
||||
..Default::default()
|
||||
@@ -174,11 +175,14 @@ impl Fsm for LineToolFsmState {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, LineToolMessage::Overlays(mut overlay_context)) => {
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
self
|
||||
}
|
||||
(LineToolFsmState::Ready, LineToolMessage::DragStart) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
|
||||
let viewport_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(viewport_start);
|
||||
let point = SnapCandidatePoint::handle(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
|
||||
tool_data.drag_start = snapped.snapped_point_document;
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
||||
|
||||
@@ -195,15 +199,21 @@ impl Fsm for LineToolFsmState {
|
||||
|
||||
LineToolFsmState::Drawing
|
||||
}
|
||||
(LineToolFsmState::Drawing, LineToolMessage::Redraw { center, snap_angle, lock_angle }) => {
|
||||
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
(LineToolFsmState::Drawing, LineToolMessage::PointerMove { center, snap_angle, lock_angle }) => {
|
||||
tool_data.drag_current = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let keyboard = &input.keyboard;
|
||||
let transform = document.metadata().document_to_viewport;
|
||||
responses.add(generate_transform(tool_data, transform, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
|
||||
let ignore = if let Some(layer) = tool_data.layer { vec![layer] } else { vec![] };
|
||||
let snap_data = SnapData::ignore(document, input, &ignore);
|
||||
responses.add(generate_transform(tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
|
||||
|
||||
LineToolFsmState::Drawing
|
||||
}
|
||||
(_, LineToolMessage::PointerMove { .. }) => {
|
||||
tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(LineToolFsmState::Drawing, LineToolMessage::DragStop) => {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
||||
@@ -250,38 +260,71 @@ impl Fsm for LineToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_transform(tool_data: &mut LineToolData, document_to_viewport: DAffine2, lock_angle: bool, snap_angle: bool, center: bool) -> Message {
|
||||
let mut start = document_to_viewport.transform_point2(tool_data.drag_start);
|
||||
let line_vector = tool_data.drag_current - start;
|
||||
|
||||
let mut angle = -line_vector.angle_between(DVec2::X);
|
||||
fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, centre: bool) -> Message {
|
||||
let document_to_viewport = snap_data.document.metadata.document_to_viewport;
|
||||
let mut document_points = [tool_data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.drag_current)];
|
||||
|
||||
let mut angle = -(document_points[1] - document_points[0]).angle_between(DVec2::X);
|
||||
let mut line_length = (document_points[1] - document_points[0]).length();
|
||||
if lock_angle {
|
||||
angle = tool_data.angle;
|
||||
}
|
||||
|
||||
if snap_angle {
|
||||
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||
angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
}
|
||||
|
||||
tool_data.angle = angle;
|
||||
|
||||
let mut line_length = line_vector.length();
|
||||
|
||||
if lock_angle {
|
||||
let angle_vec = DVec2::new(angle.cos(), angle.sin());
|
||||
line_length = line_vector.dot(angle_vec);
|
||||
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
|
||||
}
|
||||
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());
|
||||
|
||||
let constrained = snap_angle || lock_angle;
|
||||
let snap = &mut tool_data.snap_manager;
|
||||
|
||||
let near_point = SnapCandidatePoint::handle_neighbours(document_points[1], [tool_data.drag_start]);
|
||||
let far_point = SnapCandidatePoint::handle_neighbours(2. * document_points[0] - document_points[1], [tool_data.drag_start]);
|
||||
|
||||
if constrained {
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: document_points[0],
|
||||
direction: document_points[1] - document_points[0],
|
||||
};
|
||||
if centre {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
||||
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
document_points[1] = document_points[0] * 2. - best.snapped_point_document;
|
||||
document_points[0] = best.snapped_point_document;
|
||||
snap.update_indicator(best);
|
||||
} else {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
||||
document_points[1] = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
} else if centre {
|
||||
let snapped = snap.free_snap(&snap_data, &near_point, None, false);
|
||||
let snapped_far = snap.free_snap(&snap_data, &far_point, None, false);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
document_points[1] = document_points[0] * 2. - best.snapped_point_document;
|
||||
document_points[0] = best.snapped_point_document;
|
||||
snap.update_indicator(best);
|
||||
} else {
|
||||
let snapped = snap.free_snap(&snap_data, &near_point, None, false);
|
||||
document_points[1] = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
|
||||
if center {
|
||||
start -= line_length * DVec2::new(angle.cos(), angle.sin());
|
||||
line_length *= 2.;
|
||||
}
|
||||
// Used for keeping the same angle next frame
|
||||
tool_data.angle = -(document_points[1] - document_points[0]).angle_between(DVec2::X);
|
||||
|
||||
let viewport_points = [document_to_viewport.transform_point2(document_points[0]), document_to_viewport.transform_point2(document_points[1])];
|
||||
let line_length = (viewport_points[1] - viewport_points[0]).length();
|
||||
let angle = -(viewport_points[1] - viewport_points[0]).angle_between(DVec2::X);
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: tool_data.layer.unwrap(),
|
||||
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start),
|
||||
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, viewport_points[0]),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_mirror_handles, get_subpaths};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
||||
|
||||
use graph_craft::document::NodeNetwork;
|
||||
use graphene_core::renderer::Quad;
|
||||
@@ -297,9 +297,9 @@ impl PathToolData {
|
||||
}
|
||||
|
||||
// Move the selected points with the mouse
|
||||
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
shape_editor.move_selected_points(&document.network, &document.metadata, snapped_position - self.previous_mouse_position, shift, responses);
|
||||
self.previous_mouse_position = snapped_position;
|
||||
let snapped_delta = shape_editor.snap(&mut self.snap_manager, document, input, self.previous_mouse_position);
|
||||
shape_editor.move_selected_points(&document.network, &document.metadata, snapped_delta, shift, responses);
|
||||
self.previous_mouse_position += snapped_delta;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +330,8 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
if self == Self::DrawingBox {
|
||||
overlay_context.quad(Quad::from_box([tool_data.drag_start_pos, tool_data.previous_mouse_position]))
|
||||
} else if self == Self::Dragging {
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::uuid::{generate_uuid, ManipulatorGroupId};
|
||||
@@ -44,8 +44,6 @@ impl Default for PenOptions {
|
||||
pub enum PenToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
CanvasTransformed,
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
#[remain::unsorted]
|
||||
SelectionChanged,
|
||||
@@ -171,6 +169,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
|
||||
DragStop,
|
||||
Confirm,
|
||||
Abort,
|
||||
PointerMove,
|
||||
),
|
||||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant;
|
||||
DragStart,
|
||||
@@ -186,7 +185,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
|
||||
impl ToolTransition for PenTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
canvas_transformed: Some(PenToolMessage::CanvasTransformed.into()),
|
||||
tool_abort: Some(PenToolMessage::Abort.into()),
|
||||
selection_changed: Some(PenToolMessage::SelectionChanged.into()),
|
||||
working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()),
|
||||
@@ -249,9 +247,11 @@ impl PenToolData {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = document.metadata().transform_to_viewport(parent);
|
||||
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let start_position = transform.inverse().transform_point2(snapped_position);
|
||||
let transform = document.metadata().transform_to_document(parent);
|
||||
let point = SnapCandidatePoint::handle(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||
let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
|
||||
let start_position = transform.inverse().transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
self.weight = line_weight;
|
||||
|
||||
// Create the initial shape with a `bez_path` (only contains a moveto initially)
|
||||
@@ -293,6 +293,7 @@ impl PenToolData {
|
||||
let previous_anchor = previous_manipulator_group.anchor;
|
||||
|
||||
// Break the control
|
||||
let transform = document.metadata.document_to_viewport * transform;
|
||||
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if !on_top {
|
||||
return None;
|
||||
@@ -347,6 +348,7 @@ impl PenToolData {
|
||||
let first_anchor = first_manipulator_group.anchor;
|
||||
let last_in = inwards_handle.get_position(last_manipulator_group)?;
|
||||
|
||||
let transform = document.metadata.document_to_viewport * transform;
|
||||
let transformed_distance_between_squared = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(first_anchor));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
let should_close_path = transformed_distance_between_squared < snap_point_tolerance_squared && previous_manipulator_group.is_some();
|
||||
@@ -394,7 +396,8 @@ impl PenToolData {
|
||||
Some(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
|
||||
fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
fn drag_handle(&mut self, mut snap_data: SnapData, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
let document = snap_data.document;
|
||||
// Get subpath
|
||||
let subpath = &get_subpaths(self.layer?, &document.network)?[self.subpath_index];
|
||||
|
||||
@@ -409,10 +412,10 @@ impl PenToolData {
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
|
||||
let mouse = self.snap_manager.snap_position(responses, document, mouse);
|
||||
let pos = transform.inverse().transform_point2(mouse);
|
||||
let should_mirror = !modifiers.break_handle && self.should_mirror;
|
||||
|
||||
let pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, last_anchor);
|
||||
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
||||
let pos = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, should_mirror, mouse, Some(last_anchor), false);
|
||||
if !pos.is_finite() {
|
||||
return Some(PenToolFsmState::DraggingHandle);
|
||||
}
|
||||
@@ -424,7 +427,6 @@ impl PenToolData {
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
|
||||
let should_mirror = !modifiers.break_handle && self.should_mirror;
|
||||
// Mirror handle of last segment
|
||||
if should_mirror {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
@@ -446,7 +448,8 @@ impl PenToolData {
|
||||
Some(PenToolFsmState::DraggingHandle)
|
||||
}
|
||||
|
||||
fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
fn place_anchor(&mut self, mut snap_data: SnapData, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
let document = snap_data.document;
|
||||
// Get subpath
|
||||
let layer = self.layer?;
|
||||
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
|
||||
@@ -463,23 +466,18 @@ impl PenToolData {
|
||||
// Get manipulator points
|
||||
let first_anchor = first_manipulator_group.anchor;
|
||||
|
||||
let mouse = self.snap_manager.snap_position(responses, document, mouse);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
let previous_anchor = previous_manipulator_group.map(|group| group.anchor);
|
||||
|
||||
// Snap to the first point (to show close path)
|
||||
let show_close_path = mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if show_close_path {
|
||||
pos = first_anchor;
|
||||
}
|
||||
|
||||
if let Some(relative_previous_anchor) = previous_manipulator_group.map(|group| group.anchor) {
|
||||
let pos = if let Some(last_anchor) = previous_anchor.filter(|&a| mouse.distance_squared(transform.transform_point2(a)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2)) {
|
||||
// Snap to the previously placed point (to show break control)
|
||||
if mouse.distance_squared(transform.transform_point2(relative_previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = relative_previous_anchor;
|
||||
} else {
|
||||
pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, relative_previous_anchor);
|
||||
}
|
||||
}
|
||||
last_anchor
|
||||
} else if mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
// Snap to the first point (to show close path)
|
||||
first_anchor
|
||||
} else {
|
||||
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
||||
self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, false, mouse, previous_anchor, true)
|
||||
};
|
||||
|
||||
for manipulator_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] {
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, manipulator_type);
|
||||
@@ -491,6 +489,65 @@ impl PenToolData {
|
||||
Some(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
|
||||
/// Snap the angle of the line from relative to position if the key is pressed.
|
||||
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, mirror: bool, mouse: DVec2, relative: Option<DVec2>, neighbour: bool) -> DVec2 {
|
||||
let document = snap_data.document;
|
||||
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
|
||||
let snap = &mut self.snap_manager;
|
||||
|
||||
let neighbours = relative.filter(|_| neighbour).map_or(Vec::new(), |neighbour| vec![neighbour]);
|
||||
|
||||
if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| snap_angle || lock_angle) {
|
||||
let resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||
let angle = if lock_angle {
|
||||
self.angle
|
||||
} else {
|
||||
(-(relative - document_pos).angle_between(DVec2::X) / resolution).round() * resolution
|
||||
};
|
||||
document_pos = relative - (relative - document_pos).project_onto(DVec2::new(angle.cos(), angle.sin()));
|
||||
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: relative,
|
||||
direction: document_pos - relative,
|
||||
};
|
||||
let near_point = SnapCandidatePoint::handle_neighbours(document_pos, neighbours.clone());
|
||||
let far_point = SnapCandidatePoint::handle_neighbours(2. * relative - document_pos, neighbours);
|
||||
if mirror {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
||||
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None);
|
||||
document_pos = if snapped_far.other_snap_better(&snapped) {
|
||||
snapped.snapped_point_document
|
||||
} else {
|
||||
2. * relative - snapped_far.snapped_point_document
|
||||
};
|
||||
snap.update_indicator(if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far });
|
||||
} else {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
||||
document_pos = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| mirror) {
|
||||
let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbours(document_pos, neighbours.clone()), None, false);
|
||||
let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbours(2. * relative - document_pos, neighbours), None, false);
|
||||
document_pos = if snapped_far.other_snap_better(&snapped) {
|
||||
snapped.snapped_point_document
|
||||
} else {
|
||||
2. * relative - snapped_far.snapped_point_document
|
||||
};
|
||||
snap.update_indicator(if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far });
|
||||
} else {
|
||||
let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbours(document_pos, neighbours), None, false);
|
||||
document_pos = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
|
||||
if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)) {
|
||||
self.angle = -(relative - document_pos).angle_between(DVec2::X)
|
||||
}
|
||||
|
||||
transform.inverse().transform_point2(document_pos)
|
||||
}
|
||||
|
||||
fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> {
|
||||
// Get subpath
|
||||
let subpath = &get_subpaths(self.layer?, &document.network)?[self.subpath_index];
|
||||
@@ -548,13 +605,13 @@ impl Fsm for PenToolFsmState {
|
||||
..
|
||||
} = tool_action_data;
|
||||
|
||||
let mut transform = tool_data.layer.map(|layer| document.metadata().transform_to_viewport(layer)).unwrap_or_default();
|
||||
let mut transform = tool_data.layer.map(|layer| document.metadata().transform_to_document(layer)).unwrap_or_default();
|
||||
|
||||
if !transform.inverse().is_finite() {
|
||||
let parent_transform = tool_data
|
||||
.layer
|
||||
.and_then(|layer| layer.parent(document.metadata()))
|
||||
.map(|layer| document.metadata().transform_to_viewport(layer));
|
||||
.map(|layer| document.metadata().transform_to_document(layer));
|
||||
|
||||
transform = parent_transform.unwrap_or(DAffine2::IDENTITY);
|
||||
}
|
||||
@@ -567,16 +624,13 @@ impl Fsm for PenToolFsmState {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(_, PenToolMessage::CanvasTransformed) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::SelectionChanged) => {
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||
path_overlays(document, shape_editor, &mut overlay_context);
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -590,9 +644,6 @@ impl Fsm for PenToolFsmState {
|
||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Initialize snapping
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
|
||||
// Disable this tool's mirroring
|
||||
tool_data.should_mirror = false;
|
||||
|
||||
@@ -627,7 +678,10 @@ impl Fsm for PenToolFsmState {
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
|
||||
let snap_data = SnapData::new(document, input);
|
||||
tool_data
|
||||
.drag_handle(snap_data, transform, input.mouse.position, modifiers, responses)
|
||||
.unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||
let modifiers = ModifierState {
|
||||
@@ -636,9 +690,14 @@ impl Fsm for PenToolFsmState {
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
tool_data
|
||||
.place_anchor(document, transform, input.mouse.position, modifiers, responses)
|
||||
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, modifiers, responses)
|
||||
.unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::PointerMove { .. }) => {
|
||||
tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||
// Abort or commit the transaction to the undo history
|
||||
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
|
||||
@@ -677,31 +736,6 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Snap the angle of the line from relative to position if the key is pressed.
|
||||
fn compute_snapped_angle(cached_angle: &mut f64, lock_angle: bool, snap_angle: bool, position: DVec2, relative: DVec2) -> DVec2 {
|
||||
let delta = relative - position;
|
||||
let mut angle = -delta.angle_between(DVec2::X);
|
||||
|
||||
if lock_angle {
|
||||
angle = *cached_angle;
|
||||
}
|
||||
|
||||
if snap_angle {
|
||||
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||
angle = (angle / snap_resolution).round() * snap_resolution;
|
||||
}
|
||||
|
||||
*cached_angle = angle;
|
||||
|
||||
if snap_angle || lock_angle {
|
||||
let length = delta.length();
|
||||
let rotated = DVec2::new(length * angle.cos(), length * angle.sin());
|
||||
relative - rotated
|
||||
} else {
|
||||
position
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a [ManipulatorGroup] to the current layer via a [GraphOperationMessage].
|
||||
fn add_manipulator_group(layer: Option<LayerNodeIdentifier>, from_start: bool, manipulator_group: bezier_rs::ManipulatorGroup<ManipulatorGroupId>) -> Message {
|
||||
let Some(layer) = layer else {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::uuid::generate_uuid;
|
||||
@@ -41,7 +43,7 @@ impl Default for PolygonOptions {
|
||||
pub enum PolygonToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
CanvasTransformed,
|
||||
Overlays(OverlayContext),
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
#[remain::unsorted]
|
||||
@@ -50,7 +52,7 @@ pub enum PolygonToolMessage {
|
||||
// Tool-specific messages
|
||||
DragStart,
|
||||
DragStop,
|
||||
Resize {
|
||||
PointerMove {
|
||||
center: Key,
|
||||
lock_ratio: Key,
|
||||
},
|
||||
@@ -187,11 +189,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Polygon
|
||||
match self.fsm_state {
|
||||
Ready => actions!(PolygonToolMessageDiscriminant;
|
||||
DragStart,
|
||||
PointerMove,
|
||||
),
|
||||
Drawing => actions!(PolygonToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
PointerMove,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -200,7 +203,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Polygon
|
||||
impl ToolTransition for PolygonTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
canvas_transformed: Some(PolygonToolMessage::CanvasTransformed.into()),
|
||||
overlay_provider: Some(|overlay_context| PolygonToolMessage::Overlays(overlay_context).into()),
|
||||
tool_abort: Some(PolygonToolMessage::Abort.into()),
|
||||
working_color_changed: Some(PolygonToolMessage::WorkingColorChanged.into()),
|
||||
..Default::default()
|
||||
@@ -235,12 +238,12 @@ impl Fsm for PolygonToolFsmState {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input);
|
||||
(_, PolygonToolMessage::Overlays(mut overlay_context)) => {
|
||||
polygon_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
self
|
||||
}
|
||||
(PolygonToolFsmState::Ready, PolygonToolMessage::DragStart) => {
|
||||
polygon_data.start(responses, document, input);
|
||||
polygon_data.start(document, input);
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let subpath = match tool_options.primitive_shape_type {
|
||||
@@ -263,12 +266,17 @@ impl Fsm for PolygonToolFsmState {
|
||||
|
||||
PolygonToolFsmState::Drawing
|
||||
}
|
||||
(state, PolygonToolMessage::Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = polygon_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::PointerMove { center, lock_ratio }) => {
|
||||
if let Some(message) = polygon_data.calculate_transform(document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
self
|
||||
}
|
||||
(_, PolygonToolMessage::PointerMove { .. }) => {
|
||||
polygon_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::DragStop) => {
|
||||
input.mouse.finish_transaction(polygon_data.viewport_drag_start(document), responses);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::uuid::generate_uuid;
|
||||
@@ -48,7 +50,7 @@ pub enum RectangleOptionsUpdate {
|
||||
pub enum RectangleToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
CanvasTransformed,
|
||||
Overlays(OverlayContext),
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
#[remain::unsorted]
|
||||
@@ -57,7 +59,7 @@ pub enum RectangleToolMessage {
|
||||
// Tool-specific messages
|
||||
DragStart,
|
||||
DragStop,
|
||||
Resize {
|
||||
PointerMove {
|
||||
center: Key,
|
||||
lock_ratio: Key,
|
||||
},
|
||||
@@ -136,11 +138,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Rectang
|
||||
match self.fsm_state {
|
||||
Ready => actions!(RectangleToolMessageDiscriminant;
|
||||
DragStart,
|
||||
PointerMove,
|
||||
),
|
||||
Drawing => actions!(RectangleToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
PointerMove,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -161,7 +164,7 @@ impl ToolMetadata for RectangleTool {
|
||||
impl ToolTransition for RectangleTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
canvas_transformed: Some(RectangleToolMessage::CanvasTransformed.into()),
|
||||
overlay_provider: Some(|overlay_context| RectangleToolMessage::Overlays(overlay_context).into()),
|
||||
tool_abort: Some(RectangleToolMessage::Abort.into()),
|
||||
working_color_changed: Some(RectangleToolMessage::WorkingColorChanged.into()),
|
||||
..Default::default()
|
||||
@@ -195,9 +198,6 @@ impl Fsm for RectangleToolFsmState {
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use RectangleToolFsmState::*;
|
||||
use RectangleToolMessage::*;
|
||||
|
||||
let shape_data = &mut tool_data.data;
|
||||
|
||||
let ToolMessage::Rectangle(event) = event else {
|
||||
@@ -205,12 +205,12 @@ impl Fsm for RectangleToolFsmState {
|
||||
};
|
||||
|
||||
match (self, event) {
|
||||
(Drawing, CanvasTransformed) => {
|
||||
tool_data.data.recalculate_snaps(document, input);
|
||||
(_, RectangleToolMessage::Overlays(mut overlay_context)) => {
|
||||
shape_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
self
|
||||
}
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input);
|
||||
(RectangleToolFsmState::Ready, RectangleToolMessage::DragStart) => {
|
||||
shape_data.start(document, input);
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
@@ -230,29 +230,34 @@ impl Fsm for RectangleToolFsmState {
|
||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
||||
});
|
||||
|
||||
Drawing
|
||||
RectangleToolFsmState::Drawing
|
||||
}
|
||||
(state, Resize { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(responses, document, input, center, lock_ratio, false) {
|
||||
(RectangleToolFsmState::Drawing, RectangleToolMessage::PointerMove { center, lock_ratio }) => {
|
||||
if let Some(message) = shape_data.calculate_transform(document, input, center, lock_ratio, false) {
|
||||
responses.add(message);
|
||||
}
|
||||
|
||||
state
|
||||
self
|
||||
}
|
||||
(Drawing, DragStop) => {
|
||||
(_, RectangleToolMessage::PointerMove { .. }) => {
|
||||
shape_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(RectangleToolFsmState::Drawing, RectangleToolMessage::DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
(Drawing, Abort) => {
|
||||
(RectangleToolFsmState::Drawing, RectangleToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
Ready
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
(_, WorkingColorChanged) => {
|
||||
(_, RectangleToolMessage::WorkingColorChanged) => {
|
||||
responses.add(RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
|
||||
use graph_craft::document::NodeNetwork;
|
||||
@@ -53,7 +53,7 @@ impl fmt::Display for NestedSelectionBehavior {
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, ToolMessage, Select)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum SelectToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
@@ -273,9 +273,17 @@ struct SelectToolData {
|
||||
nested_selection_behavior: NestedSelectionBehavior,
|
||||
selected_layers_count: usize,
|
||||
selected_layers_changed: bool,
|
||||
snap_candidates: Vec<SnapCandidatePoint>,
|
||||
}
|
||||
|
||||
impl SelectToolData {
|
||||
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) {
|
||||
self.snap_candidates.clear();
|
||||
for &layer in &self.layers_dragging {
|
||||
snapping::get_layer_snap_points(layer, &SnapData::new(document, input), &mut self.snap_candidates);
|
||||
}
|
||||
}
|
||||
|
||||
fn selection_quad(&self) -> Quad {
|
||||
let bbox = self.selection_box();
|
||||
Quad::from_box(bbox)
|
||||
@@ -381,6 +389,8 @@ impl Fsm for SelectToolFsmState {
|
||||
};
|
||||
match (self, event) {
|
||||
(_, SelectToolMessage::Overlays(mut overlay_context)) => {
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
|
||||
let selected_layers_count = document.selected_nodes.selected_layers(document.metadata()).count();
|
||||
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||
tool_data.selected_layers_count = selected_layers_count;
|
||||
@@ -478,7 +488,8 @@ impl Fsm for SelectToolFsmState {
|
||||
let state = if tool_data.pivot.is_over(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
//tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
SelectToolFsmState::DraggingPivot
|
||||
} else if let Some(_selected_edges) = dragging_bounds {
|
||||
@@ -502,6 +513,7 @@ impl Fsm for SelectToolFsmState {
|
||||
);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
||||
}
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
|
||||
SelectToolFsmState::ResizingBounds
|
||||
} else if rotating_bounds {
|
||||
@@ -534,9 +546,7 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
// tool_data
|
||||
// .snap_manager
|
||||
// .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true);
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
|
||||
SelectToolFsmState::Dragging
|
||||
} else {
|
||||
@@ -557,6 +567,7 @@ impl Fsm for SelectToolFsmState {
|
||||
NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(responses, selected, tool_data, document),
|
||||
NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data),
|
||||
}
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
SelectToolFsmState::Dragging
|
||||
} else {
|
||||
// Deselect all layers if using shallowest selection behavior
|
||||
@@ -577,28 +588,46 @@ impl Fsm for SelectToolFsmState {
|
||||
// TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here.
|
||||
responses.add_front(SelectToolMessage::DocumentIsDirty);
|
||||
|
||||
let mouse_position = axis_align_drag(input.keyboard.key(axis_align), input.mouse.position, tool_data.drag_start);
|
||||
let axis_align = input.keyboard.key(axis_align);
|
||||
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
|
||||
let total_mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - tool_data.drag_start);
|
||||
|
||||
let mouse_delta = mouse_position - tool_data.drag_current;
|
||||
let snap_data = SnapData::ignore(document, input, &tool_data.layers_dragging);
|
||||
let mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - tool_data.drag_current);
|
||||
let mut offset = mouse_delta_document;
|
||||
let mut best_snap = SnappedPoint::infinite_snap(document.metadata.document_to_viewport.inverse().transform_point2(mouse_position));
|
||||
|
||||
let snap = tool_data
|
||||
.layers_dragging
|
||||
.iter()
|
||||
.filter_map(|&layer| document.metadata().bounding_box_viewport(layer))
|
||||
.flat_map(snapping::expand_bounds)
|
||||
.collect();
|
||||
for point in &mut tool_data.snap_candidates {
|
||||
point.document_point += total_mouse_delta_document;
|
||||
let snapped = if axis_align {
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: point.document_point,
|
||||
direction: total_mouse_delta_document.normalize(),
|
||||
};
|
||||
tool_data.snap_manager.constrained_snap(&snap_data, &point, constraint, None)
|
||||
} else {
|
||||
tool_data.snap_manager.free_snap(&snap_data, &point, None, false)
|
||||
};
|
||||
if best_snap.other_snap_better(&snapped) {
|
||||
offset = snapped.snapped_point_document - point.document_point + mouse_delta_document;
|
||||
best_snap = snapped;
|
||||
}
|
||||
point.document_point -= total_mouse_delta_document;
|
||||
}
|
||||
tool_data.snap_manager.update_indicator(best_snap);
|
||||
|
||||
let mouse_delta = document.metadata.document_to_viewport.transform_vector2(offset);
|
||||
|
||||
let closest_move = tool_data.snap_manager.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 layer_ancestors in document.metadata().shallowest_unique_layers(tool_data.layers_dragging.iter().copied()) {
|
||||
responses.add_front(GraphOperationMessage::TransformChange {
|
||||
layer: *layer_ancestors.last().unwrap(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move),
|
||||
transform: DAffine2::from_translation(mouse_delta),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
tool_data.drag_current = mouse_position + closest_move;
|
||||
tool_data.drag_current += mouse_delta;
|
||||
|
||||
// TODO: Reenable this feature after fixing it
|
||||
if false {
|
||||
@@ -614,14 +643,16 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
if let Some(movement) = &mut bounds.selected_edges {
|
||||
let (_center, axis_align) = (input.keyboard.key(center), input.keyboard.key(axis_align));
|
||||
let (_center, constrain) = (input.keyboard.key(center), input.keyboard.key(axis_align));
|
||||
let center = false; // TODO: Reenable this feature after fixing it
|
||||
|
||||
let mouse_position = input.mouse.position;
|
||||
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.original_bound_transform, center, bounds.center_of_transformation, axis_align);
|
||||
let centre = center.then_some(bounds.center_of_transformation);
|
||||
let snap = Some(SizeSnapData {
|
||||
manager: &mut tool_data.snap_manager,
|
||||
points: &mut tool_data.snap_candidates,
|
||||
snap_data: SnapData::ignore(document, input, &tool_data.layers_dragging),
|
||||
});
|
||||
let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, centre, constrain, snap);
|
||||
let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size);
|
||||
|
||||
let pivot_transform = DAffine2::from_translation(pivot);
|
||||
@@ -682,7 +713,7 @@ impl Fsm for SelectToolFsmState {
|
||||
}
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove { .. }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
let snapped_mouse_position = mouse_position; //tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
|
||||
|
||||
SelectToolFsmState::DraggingPivot
|
||||
|
||||
@@ -205,7 +205,7 @@ impl Fsm for SplineToolFsmState {
|
||||
};
|
||||
match (self, event) {
|
||||
(_, SplineToolMessage::CanvasTransformed) => {
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
self
|
||||
}
|
||||
(SplineToolFsmState::Ready, SplineToolMessage::DragStart) => {
|
||||
@@ -215,8 +215,9 @@ impl Fsm for SplineToolFsmState {
|
||||
let parent = document.new_layer_parent();
|
||||
let transform = document.metadata().transform_to_viewport(parent);
|
||||
|
||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
//tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
let snapped_position = input.mouse.position; //tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
|
||||
@@ -244,7 +245,7 @@ impl Fsm for SplineToolFsmState {
|
||||
let Some(layer) = tool_data.layer else {
|
||||
return SplineToolFsmState::Ready;
|
||||
};
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let snapped_position = input.mouse.position; //tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
|
||||
@@ -263,7 +264,7 @@ impl Fsm for SplineToolFsmState {
|
||||
let Some(layer) = tool_data.layer else {
|
||||
return SplineToolFsmState::Ready;
|
||||
};
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let snapped_position = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let pos = transform.inverse().transform_point2(snapped_position);
|
||||
tool_data.next_point = pos;
|
||||
|
||||
Reference in New Issue
Block a user