Add basic artboard snapping (#1734)

* Initial vector modify node

* Initial extraction of data from monitor nodes

* Migrate to point id

* Start converting to modify node

* Non destructive spline tool (tout le reste est cassé)

* Fix unconnected modify node

* Fix freehand tool

* Pen tool

* Migrate demo art

* Select points

* Fix the demo artwork

* Fix the X and Y inputs for path tool

* G1 continous toggle

* Delete points

* Fix test

* Insert point

* Improve robustness of handles

* Fix GRS shortcuts on path

* Dragging points

* Fix build

* Preserve opposing handle lengths

* Update demo art and snapping

* Fix polygon tool

* Double click end anchor

* Improve dragging

* Fix text shifting

* Select only connected verts

* Colinear alt

* Cleanup

* Fix imports

* first commit

* changes in SnapData::ignore()

* Bug fixes and cleanup

* Code review

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Sahil gill
2024-07-05 16:14:47 -07:00
committed by GitHub
co-authored by 0hypercube Keavon Chambers
parent 176ce314c7
commit e66620dc5c
4 changed files with 199 additions and 109 deletions
@@ -1,15 +1,15 @@
use super::*; use super::*;
use crate::consts::HIDE_HANDLE_DISTANCE; use crate::consts::HIDE_HANDLE_DISTANCE;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{ use crate::messages::portfolio::document::utility_types::misc::*;
BoardSnapSource, BoardSnapTarget, BoundingBoxSnapSource, BoundingBoxSnapTarget, GeometrySnapSource, GeometrySnapTarget, SnapSource, SnapTarget,
};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::{Bezier, Identifier, Subpath, TValue}; use bezier_rs::{Bezier, Identifier, Subpath, TValue};
use glam::{DAffine2, DVec2};
use graphene_core::renderer::Quad; use graphene_core::renderer::Quad;
use graphene_core::vector::PointId; use graphene_core::vector::PointId;
use glam::{DAffine2, DVec2};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct LayerSnapper { pub struct LayerSnapper {
points_to_snap: Vec<SnapCandidatePoint>, points_to_snap: Vec<SnapCandidatePoint>,
@@ -21,13 +21,21 @@ impl LayerSnapper {
if !document.snapping_state.target_enabled(target) { if !document.snapping_state.target_enabled(target) {
return; return;
} }
let Some(bounds) = document.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
return; let bounds = if document.metadata.is_artboard(layer) {
document.metadata.bounding_box_with_transform(layer, document.metadata.transform_to_document(layer)).map(Quad::from_box)
} else {
document
.metadata
.bounding_box_with_transform(layer, DAffine2::IDENTITY)
.map(|bounds| document.metadata.transform_to_document(layer) * Quad::from_box(bounds))
}; };
let bounds = document.metadata.transform_to_document(layer) * Quad::from_box(bounds); let Some(bounds) = bounds else { return };
if bounds.0.iter().any(|point| !point.is_finite()) { if bounds.0.iter().any(|point| !point.is_finite()) {
return; return;
} }
for document_curve in bounds.bezier_lines() { for document_curve in bounds.bezier_lines() {
self.paths_to_snap.push(SnapCandidatePath { self.paths_to_snap.push(SnapCandidatePath {
document_curve, document_curve,
@@ -46,7 +54,7 @@ impl LayerSnapper {
self.paths_to_snap.clear(); self.paths_to_snap.clear();
for layer in document.metadata.all_layers() { for layer in document.metadata.all_layers() {
if !document.metadata.is_artboard(layer) { if !document.metadata.is_artboard(layer) || snap_data.ignore.contains(&layer) {
continue; continue;
} }
self.add_layer_bounds(document, layer, SnapTarget::Board(BoardSnapTarget::Edge)); self.add_layer_bounds(document, layer, SnapTarget::Board(BoardSnapTarget::Edge));
@@ -168,22 +176,16 @@ impl LayerSnapper {
self.points_to_snap.clear(); self.points_to_snap.clear();
for layer in document.metadata.all_layers() { for layer in document.metadata.all_layers() {
if !document.metadata.is_artboard(layer) { if !document.metadata.is_artboard(layer) || snap_data.ignore.contains(&layer) {
continue; continue;
} }
if document.snapping_state.target_enabled(SnapTarget::Board(BoardSnapTarget::Corner)) { if document.snapping_state.target_enabled(SnapTarget::Board(BoardSnapTarget::Corner)) {
let Some(bounds) = document.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY) else { let Some(bounds) = document.metadata.bounding_box_with_transform(layer, document.metadata.transform_to_document(layer)) else {
continue; continue;
}; };
let quad = document.metadata.transform_to_document(layer) * Quad::from_box(bounds);
let values = BBoxSnapValues { get_bbox_points(Quad::from_box(bounds), &mut self.points_to_snap, BBoxSnapValues::ARTBOARD, document);
corner_source: SnapSource::Board(BoardSnapSource::Corner),
corner_target: SnapTarget::Board(BoardSnapTarget::Corner),
center_source: SnapSource::Board(BoardSnapSource::Center),
center_target: SnapTarget::Board(BoardSnapTarget::Center),
..Default::default()
};
get_bbox_points(quad, &mut self.points_to_snap, values, document);
} }
} }
for &layer in snap_data.get_candidates() { for &layer in snap_data.get_candidates() {
@@ -351,6 +353,15 @@ impl BBoxSnapValues {
center_source: SnapSource::BoundingBox(BoundingBoxSnapSource::Center), center_source: SnapSource::BoundingBox(BoundingBoxSnapSource::Center),
center_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::Center), center_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::Center),
}; };
pub const ARTBOARD: Self = Self {
corner_source: SnapSource::Board(BoardSnapSource::Corner),
corner_target: SnapTarget::Board(BoardSnapTarget::Corner),
edge_source: SnapSource::None,
edge_target: SnapTarget::None,
center_source: SnapSource::Board(BoardSnapSource::Center),
center_target: SnapTarget::Board(BoardSnapTarget::Center),
};
} }
pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values: BBoxSnapValues, document: &DocumentMessageHandler) { pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values: BBoxSnapValues, document: &DocumentMessageHandler) {
for index in 0..4 { for index in 0..4 {
@@ -216,6 +216,43 @@ pub fn axis_align_drag(axis_align: bool, position: DVec2, start: DVec2) -> DVec2
} }
} }
/// Snaps a dragging event from the artboard or select tool
pub fn snap_drag(start: DVec2, current: DVec2, axis_align: bool, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &Vec<SnapCandidatePoint>) -> DVec2 {
let mouse_position = axis_align_drag(axis_align, snap_data.input.mouse.position, start);
let document = snap_data.document;
let total_mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - start);
let mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - current);
let mut offset = mouse_delta_document;
let mut best_snap = SnappedPoint::infinite_snap(document.metadata.document_to_viewport.inverse().transform_point2(mouse_position));
for point in candidates {
let origin = point.document_point + total_mouse_delta_document;
let snapped = if axis_align {
snap_manager.constrained_snap(
&snap_data,
point,
SnapConstraint::Line {
origin,
direction: total_mouse_delta_document.try_normalize().unwrap_or(DVec2::X),
},
None,
)
} else {
snap_manager.free_snap(&snap_data, point, None, false)
};
if best_snap.other_snap_better(&snapped) {
offset = snapped.snapped_point_document - origin + mouse_delta_document;
best_snap = snapped;
}
}
snap_manager.update_indicator(best_snap);
document.metadata.document_to_viewport.transform_vector2(offset)
}
/// Contains info on the overlays for the bounding box and transform handles /// Contains info on the overlays for the bounding box and transform handles
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct BoundingBoxManager { pub struct BoundingBoxManager {
@@ -3,11 +3,16 @@ use crate::application::generate_uuid;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::snapping;
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
use crate::messages::tool::common_functionality::snapping::SnapData;
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
use glam::{IVec2, Vec2Swizzles};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_core::renderer::Quad;
use glam::{IVec2, Vec2Swizzles};
#[derive(Default)] #[derive(Default)]
pub struct ArtboardTool { pub struct ArtboardTool {
@@ -57,7 +62,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Artboar
); );
let additional = match self.fsm_state { let additional = match self.fsm_state {
ArtboardToolFsmState::Ready => actions!(ArtboardToolMessageDiscriminant; PointerDown), ArtboardToolFsmState::Ready { .. } => actions!(ArtboardToolMessageDiscriminant; PointerDown),
_ => actions!(ArtboardToolMessageDiscriminant; PointerUp, Abort), _ => actions!(ArtboardToolMessageDiscriminant; PointerUp, Abort),
}; };
common.extend(additional); common.extend(additional);
@@ -82,15 +87,20 @@ impl ToolTransition for ArtboardTool {
} }
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ArtboardToolFsmState { enum ArtboardToolFsmState {
#[default] Ready { hovered: bool },
Ready,
Drawing, Drawing,
ResizingBounds, ResizingBounds,
Dragging, Dragging,
} }
impl Default for ArtboardToolFsmState {
fn default() -> Self {
Self::Ready { hovered: false }
}
}
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct ArtboardToolData { struct ArtboardToolData {
bounding_box_manager: Option<BoundingBoxManager>, bounding_box_manager: Option<BoundingBoxManager>,
@@ -100,9 +110,20 @@ struct ArtboardToolData {
drag_start: DVec2, drag_start: DVec2,
drag_current: DVec2, drag_current: DVec2,
auto_panning: AutoPanning, auto_panning: AutoPanning,
snap_candidates: Vec<SnapCandidatePoint>,
} }
impl ArtboardToolData { impl ArtboardToolData {
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) {
self.snap_candidates.clear();
let Some(layer) = self.selected_artboard else { return };
if let Some(bounds) = document.metadata.bounding_box_with_transform(layer, document.metadata.transform_to_document(layer)) {
snapping::get_bbox_points(Quad::from_box(bounds), &mut self.snap_candidates, snapping::BBoxSnapValues::ARTBOARD, document);
}
}
fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> { fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> {
let bounding_box = self.bounding_box_manager.as_mut()?; let bounding_box = self.bounding_box_manager.as_mut()?;
let edges = bounding_box.check_selected_edges(cursor)?; let edges = bounding_box.check_selected_edges(cursor)?;
@@ -120,14 +141,17 @@ impl ArtboardToolData {
} }
} }
fn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
document
.click_xray(input.mouse.position)
.filter(|&layer| document.network.nodes.get(&layer.to_node()).map_or(false, |document_node| document_node.is_artboard()))
.next()
}
fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool { fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let mut intersections = document if let Some(intersection) = Self::hovered_artboard(document, input) {
.click_xray(input.mouse.position)
.filter(|&layer| document.network.nodes.get(&layer.to_node()).map_or(false, |document_node| document_node.is_artboard()));
if let Some(intersection) = intersections.next() {
self.selected_artboard = Some(intersection); self.selected_artboard = Some(intersection);
if let Some(bounds) = document.metadata().bounding_box_document(intersection) { if let Some(bounds) = document.metadata().bounding_box_document(intersection) {
@@ -148,7 +172,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, input: &InputPreprocessorMessageHandler, from_center: bool, constrain_square: bool) {
let Some(bounds) = &self.bounding_box_manager else { let Some(bounds) = &self.bounding_box_manager else {
return; return;
}; };
@@ -161,7 +185,13 @@ impl ArtboardToolData {
} }
let center = from_center.then_some(bounds.center_of_transformation); let center = from_center.then_some(bounds.center_of_transformation);
let (min, size) = movement.new_size(mouse_position, bounds.transform, center, constrain_square, None); let ignore = self.selected_artboard.map_or(Vec::new(), |layer| vec![layer]);
let snap = Some(SizeSnapData {
manager: &mut self.snap_manager,
points: &mut self.snap_candidates,
snap_data: SnapData::ignore(document, input, &ignore),
});
let (min, size) = movement.new_size(input.mouse.position, bounds.transform, center, constrain_square, snap);
let max = min + size; let max = min + size;
let position = min.min(max); let position = min.min(max);
let size = (max - min).abs(); let size = (max - min).abs();
@@ -185,41 +215,58 @@ impl Fsm for ArtboardToolFsmState {
return self; return self;
}; };
match (self, event) { let hovered = ArtboardToolData::hovered_artboard(document, input).is_some();
(state, ArtboardToolMessage::Overlays(mut overlay_context)) if state != ArtboardToolFsmState::Drawing => {
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
bounding_box_manager.bounds = bounds;
bounding_box_manager.transform = document.metadata().document_to_viewport;
bounding_box_manager.render_overlays(&mut overlay_context); match (self, event) {
} else { (state, ArtboardToolMessage::Overlays(mut overlay_context)) => {
tool_data.bounding_box_manager.take(); if state != ArtboardToolFsmState::Drawing {
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
bounding_box_manager.bounds = bounds;
bounding_box_manager.transform = document.metadata().document_to_viewport;
bounding_box_manager.render_overlays(&mut overlay_context);
} else {
tool_data.bounding_box_manager.take();
}
} }
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
self self
} }
(ArtboardToolFsmState::Ready { .. }, ArtboardToolMessage::PointerDown) => {
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => { let to_viewport = document.metadata().document_to_viewport;
tool_data.drag_start = input.mouse.position; let to_document = to_viewport.inverse();
tool_data.drag_current = input.mouse.position; tool_data.drag_start = to_document.transform_point2(input.mouse.position);
tool_data.drag_current = to_document.transform_point2(input.mouse.position);
if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) { if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
tool_data.start_resizing(selected_edges, document, input);
tool_data.start_resizing(selected_edges, document, input);
tool_data.get_snap_candidates(document, input);
ArtboardToolFsmState::ResizingBounds ArtboardToolFsmState::ResizingBounds
} else if tool_data.select_artboard(document, input, responses) { } else if tool_data.select_artboard(document, input, responses) {
tool_data.get_snap_candidates(document, input);
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} else { } else {
tool_data.get_snap_candidates(document, input);
let point = SnapCandidatePoint::handle(to_document.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;
tool_data.drag_current = snapped.snapped_point_document;
ArtboardToolFsmState::Drawing ArtboardToolFsmState::Drawing
} }
} }
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let from_center = input.keyboard.get(center as usize); let from_center = input.keyboard.get(center as usize);
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = input.mouse.position; tool_data.resize_artboard(responses, document, input, from_center, constrain_square);
tool_data.resize_artboard(responses, document, mouse_position, from_center, constrain_square);
// AutoPanning // AutoPanning
let messages = [ let messages = [
@@ -233,13 +280,19 @@ impl Fsm for ArtboardToolFsmState {
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager { if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager {
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize); 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 ignore = tool_data.selected_artboard.map_or(Vec::new(), |layer| vec![layer]);
let snap_data = SnapData::ignore(document, input, &ignore);
let document_to_viewport = document.metadata().document_to_viewport;
let [start, current] = [tool_data.drag_start, tool_data.drag_current].map(|point| document_to_viewport.transform_point2(point));
let mouse_delta = snap_drag(start, current, axis_align, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates);
let size = bounds.bounds[1] - bounds.bounds[0]; let size = bounds.bounds[1] - bounds.bounds[0];
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current); let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_delta);
if tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT { if tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Selected artboard cannot be ROOT_PARENT"); log::error!("Selected artboard cannot be ROOT_PARENT");
return ArtboardToolFsmState::Ready; return ArtboardToolFsmState::Ready { hovered };
} }
responses.add(GraphOperationMessage::ResizeArtboard { responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap().to_node(), id: tool_data.selected_artboard.unwrap().to_node(),
@@ -248,7 +301,7 @@ impl Fsm for ArtboardToolFsmState {
}); });
// The second term is added to prevent the slow change in position due to rounding errors. // The second term is added to prevent the slow change in position due to rounding errors.
tool_data.drag_current = mouse_position + bounds.transform.transform_vector2(position.round() - position); tool_data.drag_current += (document_to_viewport.inverse() * bounds.transform).transform_vector2(position.round() - bounds.bounds[0]);
// Update bounds if another `PointerMove` message comes before `ResizeArtboard` is finished. // Update bounds if another `PointerMove` message comes before `ResizeArtboard` is finished.
bounds.bounds[0] = position.round(); bounds.bounds[0] = position.round();
@@ -264,28 +317,34 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let mouse_position = input.mouse.position; let to_viewport = document.metadata().document_to_viewport;
let snapped_mouse_position = mouse_position; let ignore = if let Some(layer) = tool_data.selected_artboard { vec![layer] } else { vec![] };
let snap_data = SnapData::ignore(document, input, &ignore);
let root_transform = document.metadata().document_to_viewport.inverse(); let document_mouse = to_viewport.inverse().transform_point2(input.mouse.position);
let mut start = tool_data.drag_start; let snapped = tool_data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), None, false);
let snapped_mouse_position = to_viewport.transform_point2(snapped.snapped_point_document);
tool_data.snap_manager.update_indicator(snapped);
let mut start = to_viewport.transform_point2(tool_data.drag_start);
let mut size = snapped_mouse_position - start; let mut size = snapped_mouse_position - start;
// Constrain axis // Constrain axis
if input.keyboard.get(constrain_axis_or_aspect as usize) { if input.keyboard.get(constrain_axis_or_aspect as usize) {
size = size.abs().max(size.abs().yx()) * size.signum(); size = size.abs().max(size.abs().yx()) * size.signum();
} }
// From center // From center
if input.keyboard.get(center as usize) { if input.keyboard.get(center as usize) {
start -= size; start -= size;
size *= 2.; size *= 2.;
} }
let start = root_transform.transform_point2(start); let start = to_viewport.inverse().transform_point2(start);
let size = root_transform.transform_vector2(size); let size = to_viewport.inverse().transform_vector2(size);
let end = start + size; let end = start + size;
let size = (start - end).abs();
let start = start.min(end);
if let Some(artboard) = tool_data.selected_artboard { if let Some(artboard) = tool_data.selected_artboard {
if artboard == LayerNodeIdentifier::ROOT_PARENT { if artboard == LayerNodeIdentifier::ROOT_PARENT {
@@ -293,8 +352,8 @@ impl Fsm for ArtboardToolFsmState {
} else { } else {
responses.add(GraphOperationMessage::ResizeArtboard { responses.add(GraphOperationMessage::ResizeArtboard {
id: artboard.to_node(), id: artboard.to_node(),
location: start.round().as_ivec2(), location: start.min(end).round().as_ivec2(),
dimensions: size.round().as_ivec2(), dimensions: (start.round() - end.round()).abs().as_ivec2(),
}); });
} }
} else { } else {
@@ -324,15 +383,24 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Drawing ArtboardToolFsmState::Drawing
} }
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
let cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false)); (ArtboardToolFsmState::Ready { .. }, ArtboardToolMessage::PointerMove { .. }) => {
let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
if cursor == MouseCursorIcon::Default && !hovered {
tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
responses.add(OverlaysMessage::Draw);
cursor = MouseCursorIcon::Crosshair;
} else {
tool_data.snap_manager.cleanup(responses);
}
if tool_data.cursor != cursor { if tool_data.cursor != cursor {
tool_data.cursor = cursor; tool_data.cursor = cursor;
responses.add(FrontendMessage::UpdateMouseCursor { cursor }); responses.add(FrontendMessage::UpdateMouseCursor { cursor });
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready { hovered }
} }
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => { (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // AutoPanning
@@ -342,18 +410,13 @@ impl Fsm for ArtboardToolFsmState {
} }
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => { (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // AutoPanning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { tool_data.auto_panning.shift_viewport(input, responses);
tool_data.drag_current += shift;
tool_data.drag_start += shift;
}
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // AutoPanning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { tool_data.auto_panning.shift_viewport(input, responses);
tool_data.drag_start += shift;
}
ArtboardToolFsmState::Drawing ArtboardToolFsmState::Drawing
} }
@@ -374,7 +437,7 @@ impl Fsm for ArtboardToolFsmState {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready { hovered }
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
@@ -385,7 +448,7 @@ impl Fsm for ArtboardToolFsmState {
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready { hovered }
} }
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => { (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
@@ -395,7 +458,7 @@ impl Fsm for ArtboardToolFsmState {
} }
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready { hovered }
} }
(_, ArtboardToolMessage::UpdateSelectedArtboard) => { (_, ArtboardToolMessage::UpdateSelectedArtboard) => {
tool_data.selected_artboard = document.selected_nodes.selected_layers(document.metadata()).find(|layer| document.metadata().is_artboard(*layer)); tool_data.selected_artboard = document.selected_nodes.selected_layers(document.metadata()).find(|layer| document.metadata().is_artboard(*layer));
@@ -404,7 +467,8 @@ impl Fsm for ArtboardToolFsmState {
(_, ArtboardToolMessage::DeleteSelected) => { (_, ArtboardToolMessage::DeleteSelected) => {
tool_data.selected_artboard.take(); tool_data.selected_artboard.take();
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true }); responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
ArtboardToolFsmState::Ready
ArtboardToolFsmState::Ready { hovered }
} }
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => { (_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
@@ -419,17 +483,15 @@ impl Fsm for ArtboardToolFsmState {
} }
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready { hovered }
} }
(ArtboardToolFsmState::Dragging | ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::Abort) => { (ArtboardToolFsmState::Dragging | ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction); responses.add(DocumentMessage::AbortTransaction);
// ArtboardTool currently doesn't implement snapping tool_data.snap_manager.cleanup(responses);
// tool_data.snap_manager.cleanup(responses);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready { hovered }
} }
_ => self, _ => self,
} }
@@ -437,7 +499,7 @@ impl Fsm for ArtboardToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) { fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self { let hint_data = match self {
ArtboardToolFsmState::Ready => HintData(vec![ ArtboardToolFsmState::Ready { .. } => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Artboard")]), HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Artboard")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Move Artboard")]), HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Move Artboard")]),
HintGroup(vec![HintInfo::keys([Key::Backspace], "Delete Artboard")]), HintGroup(vec![HintInfo::keys([Key::Backspace], "Delete Artboard")]),
@@ -460,6 +522,10 @@ impl Fsm for ArtboardToolFsmState {
} }
fn update_cursor(&self, responses: &mut VecDeque<Message>) { fn update_cursor(&self, responses: &mut VecDeque<Message>) {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); if let Self::Ready { hovered: false } = self {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
} else {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
}
} }
} }
@@ -12,7 +12,7 @@ use crate::messages::portfolio::document::utility_types::transformation::Selecte
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; 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::pivot::Pivot;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork}; use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
@@ -681,38 +681,14 @@ impl Fsm for SelectToolFsmState {
} }
let axis_align = input.keyboard.key(modifier_keys.axis_align); let axis_align = input.keyboard.key(modifier_keys.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);
// Ignore the non duplicated layers if the current layers have not spawned yet. // Ignore the non duplicated layers if the current layers have not spawned yet.
let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_target(layer).is_some()); let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_target(layer).is_some());
let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging);
let snap_data = SnapData::ignore(document, input, ignore); let snap_data = SnapData::ignore(document, input, ignore);
let mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - tool_data.drag_current); let (start, current) = (tool_data.drag_start, tool_data.drag_current);
let mut offset = mouse_delta_document; let mouse_delta = snap_drag(start, current, axis_align, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates);
let mut best_snap = SnappedPoint::infinite_snap(document.metadata.document_to_viewport.inverse().transform_point2(mouse_position));
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.try_normalize().unwrap_or(DVec2::X),
};
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);
// 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 // 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()) { for layer_ancestors in document.metadata().shallowest_unique_layers(tool_data.layers_dragging.iter().copied()) {