Improve snapping with better snap target names, tooltips, cleaner overlay labels, code cleanup

This commit is contained in:
Keavon Chambers
2025-01-09 19:03:14 -08:00
parent ae2637e08e
commit 07601a5c6c
12 changed files with 422 additions and 206 deletions
@@ -3,7 +3,7 @@ use super::node_graph::utility_types::Transform;
use super::overlays::utility_types::Pivot;
use super::utility_types::clipboards::Clipboard;
use super::utility_types::error::EditorError;
use super::utility_types::misc::{SnappingOptions, SnappingState, GET_SNAP_BOX_FUNCTIONS, GET_SNAP_GEOMETRY_FUNCTIONS};
use super::utility_types::misc::{SnappingOptions, SnappingState, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS};
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
@@ -1779,28 +1779,27 @@ impl DocumentMessageHandler {
},
]
.into_iter()
.chain(GET_SNAP_BOX_FUNCTIONS.into_iter().map(|(name, closure)| LayoutGroup::Row {
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(*closure(&mut snapping_state))
.on_update(move |input: &CheckboxInput| DocumentMessage::SetSnapping { closure: Some(closure), snapping_state: input.checked }.into())
.tooltip(tooltip)
.widget_holder(),
TextLabel::new(name).widget_holder(),
TextLabel::new(name).tooltip(tooltip).widget_holder(),
],
}))
.chain(
[LayoutGroup::Row {
widgets: vec![TextLabel::new(SnappingOptions::Geometry.to_string()).widget_holder()],
}]
.into_iter()
.chain(GET_SNAP_GEOMETRY_FUNCTIONS.into_iter().map(|(name, closure)| LayoutGroup::Row {
widgets: vec![
.chain([LayoutGroup::Row {
widgets: vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_holder()],
}])
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
widgets: vec![
CheckboxInput::new(*closure(&mut snapping_state2))
.on_update(move |input: &CheckboxInput| DocumentMessage::SetSnapping { closure: Some(closure), snapping_state: input.checked }.into())
.tooltip(tooltip)
.widget_holder(),
TextLabel::new(name).widget_holder(),
TextLabel::new(name).tooltip(tooltip).widget_holder(),
],
})),
)
}))
.collect(),
)
.widget_holder(),
@@ -62,11 +62,11 @@ impl DocumentMode {
pub struct SnappingState {
pub snapping_enabled: bool,
pub grid_snapping: bool,
pub bounds: BoundsSnapping,
pub nodes: PointSnapping,
pub grid: GridSnapping,
pub tolerance: f64,
pub artboards: bool,
pub tolerance: f64,
pub bounding_box: BoundingBoxSnapping,
pub path: PathSnapping,
pub grid: GridSnapping,
}
impl Default for SnappingState {
@@ -74,11 +74,11 @@ impl Default for SnappingState {
Self {
snapping_enabled: true,
grid_snapping: false,
bounds: Default::default(),
nodes: Default::default(),
grid: Default::default(),
tolerance: 8.,
artboards: true,
tolerance: 8.,
bounding_box: BoundingBoxSnapping::default(),
path: PathSnapping::default(),
grid: GridSnapping::default(),
}
}
}
@@ -89,26 +89,25 @@ impl SnappingState {
return false;
}
match target {
SnapTarget::BoundingBox(bounding_box) => match bounding_box {
BoundingBoxSnapTarget::Corner => self.bounds.corners,
BoundingBoxSnapTarget::Edge => self.bounds.edges,
BoundingBoxSnapTarget::EdgeMidpoint => self.bounds.edge_midpoints,
BoundingBoxSnapTarget::Center => self.bounds.centers,
SnapTarget::BoundingBox(target) => match target {
BoundingBoxSnapTarget::CornerPoint => self.bounding_box.corner_point,
BoundingBoxSnapTarget::AlongEdge => self.bounding_box.along_edge,
BoundingBoxSnapTarget::EdgeMidpoint => self.bounding_box.edge_midpoint,
BoundingBoxSnapTarget::CenterPoint => self.bounding_box.center_point,
},
SnapTarget::Geometry(nodes) => match nodes {
GeometrySnapTarget::AnchorWithColinearHandles => self.nodes.anchors,
GeometrySnapTarget::AnchorWithFreeHandles => self.nodes.anchors,
GeometrySnapTarget::LineMidpoint => self.nodes.line_midpoints,
GeometrySnapTarget::Path => self.nodes.paths,
GeometrySnapTarget::Normal => self.nodes.normals,
GeometrySnapTarget::Tangent => self.nodes.tangents,
GeometrySnapTarget::Intersection => self.nodes.path_intersections,
SnapTarget::Path(target) => match target {
PathSnapTarget::AnchorPointWithColinearHandles | PathSnapTarget::AnchorPointWithFreeHandles => self.path.anchor_point,
PathSnapTarget::LineMidpoint => self.path.line_midpoint,
PathSnapTarget::AlongPath => self.path.along_path,
PathSnapTarget::NormalToPath => self.path.normal_to_path,
PathSnapTarget::TangentToPath => self.path.tangent_to_path,
PathSnapTarget::IntersectionPoint => self.path.path_intersection_point,
},
SnapTarget::Artboard(_) => self.artboards,
SnapTarget::Grid(_) => self.grid_snapping,
SnapTarget::Alignment(AlignmentSnapTarget::Handle) => self.nodes.align,
SnapTarget::Alignment(_) => self.bounds.align,
SnapTarget::Distribution(_) => self.bounds.distribute,
SnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint) => self.path.align_with_anchor_point,
SnapTarget::Alignment(_) => self.bounding_box.align_with_corner_point,
SnapTarget::DistributeEvenly(_) => self.bounding_box.distribute_evenly,
_ => false,
}
}
@@ -116,50 +115,50 @@ impl SnappingState {
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct BoundsSnapping {
pub edges: bool,
pub corners: bool,
pub edge_midpoints: bool,
pub centers: bool,
pub align: bool,
pub distribute: bool,
pub struct BoundingBoxSnapping {
pub center_point: bool,
pub corner_point: bool,
pub edge_midpoint: bool,
pub along_edge: bool,
pub align_with_corner_point: bool,
pub distribute_evenly: bool,
}
impl Default for BoundsSnapping {
impl Default for BoundingBoxSnapping {
fn default() -> Self {
Self {
edges: true,
corners: true,
edge_midpoints: false,
centers: true,
align: true,
distribute: true,
center_point: true,
corner_point: true,
edge_midpoint: true,
along_edge: true,
align_with_corner_point: true,
distribute_evenly: true,
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct PointSnapping {
pub paths: bool,
pub path_intersections: bool,
pub anchors: bool,
pub line_midpoints: bool,
pub normals: bool,
pub tangents: bool,
pub align: bool,
pub struct PathSnapping {
pub anchor_point: bool,
pub line_midpoint: bool,
pub along_path: bool,
pub normal_to_path: bool,
pub tangent_to_path: bool,
pub path_intersection_point: bool,
pub align_with_anchor_point: bool, // TODO: Rename
}
impl Default for PointSnapping {
impl Default for PathSnapping {
fn default() -> Self {
Self {
paths: true,
path_intersections: true,
anchors: true,
line_midpoints: true,
normals: true,
tangents: true,
align: false,
anchor_point: true,
line_midpoint: true,
along_path: true,
normal_to_path: true,
tangent_to_path: true,
path_intersection_point: true,
align_with_anchor_point: true,
}
}
}
@@ -265,34 +264,75 @@ impl GridSnapping {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundingBoxSnapSource {
Center,
Corner,
CornerPoint,
CenterPoint,
EdgeMidpoint,
}
impl fmt::Display for BoundingBoxSnapSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BoundingBoxSnapSource::CornerPoint => write!(f, "Bounding Box: Corner Point"),
BoundingBoxSnapSource::CenterPoint => write!(f, "Bounding Box: Center Point"),
BoundingBoxSnapSource::EdgeMidpoint => write!(f, "Bounding Box: Edge Midpoint"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArtboardSnapSource {
Center,
Corner,
CornerPoint,
CenterPoint,
}
impl fmt::Display for ArtboardSnapSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArtboardSnapSource::CornerPoint => write!(f, "Artboard: Corner Point"),
ArtboardSnapSource::CenterPoint => write!(f, "Artboard: Center Point"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeometrySnapSource {
AnchorWithColinearHandles,
AnchorWithFreeHandles,
Handle,
pub enum PathSnapSource {
AnchorPointWithColinearHandles,
AnchorPointWithFreeHandles,
HandlePoint,
LineMidpoint,
Intersection,
IntersectionPoint,
}
impl fmt::Display for PathSnapSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathSnapSource::AnchorPointWithColinearHandles | PathSnapSource::AnchorPointWithFreeHandles => write!(f, "Path: Anchor Point"),
PathSnapSource::HandlePoint => write!(f, "Path: Handle Point"),
PathSnapSource::LineMidpoint => write!(f, "Path: Line Midpoint"),
PathSnapSource::IntersectionPoint => write!(f, "Path: Intersection Point"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlignmentSnapSource {
BoundsCorner,
BoundsCenter,
BoundsEdgeMidpoint,
ArtboardCorner,
ArtboardCenter,
Handle,
BoundingBoxCornerPoint,
BoundingBoxCenterPoint,
BoundingBoxEdgeMidpoint,
ArtboardCornerPoint,
ArtboardCenterPoint,
}
impl fmt::Display for AlignmentSnapSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AlignmentSnapSource::BoundingBoxCornerPoint => write!(f, "{}", BoundingBoxSnapSource::CornerPoint),
AlignmentSnapSource::BoundingBoxCenterPoint => write!(f, "{}", BoundingBoxSnapSource::CenterPoint),
AlignmentSnapSource::BoundingBoxEdgeMidpoint => write!(f, "{}", BoundingBoxSnapSource::EdgeMidpoint),
AlignmentSnapSource::ArtboardCornerPoint => write!(f, "{}", ArtboardSnapSource::CornerPoint),
AlignmentSnapSource::ArtboardCenterPoint => write!(f, "{}", ArtboardSnapSource::CenterPoint),
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -301,7 +341,7 @@ pub enum SnapSource {
None,
BoundingBox(BoundingBoxSnapSource),
Artboard(ArtboardSnapSource),
Geometry(GeometrySnapSource),
Path(PathSnapSource),
Alignment(AlignmentSnapSource),
}
@@ -318,54 +358,164 @@ impl SnapSource {
pub fn center(&self) -> bool {
matches!(
self,
Self::Alignment(AlignmentSnapSource::ArtboardCenter | AlignmentSnapSource::BoundsCenter) | Self::Artboard(ArtboardSnapSource::Center) | Self::BoundingBox(BoundingBoxSnapSource::Center)
Self::Alignment(AlignmentSnapSource::ArtboardCenterPoint | AlignmentSnapSource::BoundingBoxCenterPoint)
| Self::Artboard(ArtboardSnapSource::CenterPoint)
| Self::BoundingBox(BoundingBoxSnapSource::CenterPoint)
)
}
}
impl fmt::Display for SnapSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SnapSource::None => write!(f, "None"),
SnapSource::BoundingBox(bounding_box_snap_source) => write!(f, "{bounding_box_snap_source}"),
SnapSource::Artboard(artboard_snap_source) => write!(f, "{artboard_snap_source}"),
SnapSource::Path(path_snap_source) => write!(f, "{path_snap_source}"),
SnapSource::Alignment(alignment_snap_source) => write!(f, "{alignment_snap_source}"),
}
}
}
type GetSnapState = for<'a> fn(&'a mut SnappingState) -> &'a mut bool;
pub const GET_SNAP_BOX_FUNCTIONS: [(&str, GetSnapState); 6] = [
("Box Center", (|snapping_state| &mut snapping_state.bounds.centers) as GetSnapState),
("Box Corner", (|snapping_state| &mut snapping_state.bounds.corners) as GetSnapState),
("Along Edge", (|snapping_state| &mut snapping_state.bounds.edges) as GetSnapState),
("Midpoint of Edge", (|snapping_state| &mut snapping_state.bounds.edge_midpoints) as GetSnapState),
("Align to Box", (|snapping_state| &mut snapping_state.bounds.align) as GetSnapState),
("Evenly Distribute Boxes", (|snapping_state| &mut snapping_state.bounds.distribute) as GetSnapState),
pub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 6] = [
(
// TODO: Rename to "Beyond Edges" and update behavior to snap to an infinite extension of the bounding box edges
// TODO: (even when the layer is locally rotated) instead of horizontally/vertically aligning with the corner points
"Align with Corner Points",
(|snapping_state| &mut snapping_state.bounding_box.align_with_corner_point) as GetSnapState,
"Snaps to horizontal/vertical alignment with the corner points of any layer's bounding box",
),
(
"Corner Points",
(|snapping_state| &mut snapping_state.bounding_box.corner_point) as GetSnapState,
"Snaps to the four corners of any layer's bounding box",
),
(
"Center Points",
(|snapping_state| &mut snapping_state.bounding_box.center_point) as GetSnapState,
"Snaps to the center point of any layer's bounding box",
),
(
"Edge Midpoints",
(|snapping_state| &mut snapping_state.bounding_box.edge_midpoint) as GetSnapState,
"Snaps to any of the four points at the middle of the edges of any layer's bounding box",
),
(
"Along Edges",
(|snapping_state| &mut snapping_state.bounding_box.along_edge) as GetSnapState,
"Snaps anywhere along the four edges of any layer's bounding box",
),
(
"Distribute Evenly",
(|snapping_state| &mut snapping_state.bounding_box.distribute_evenly) as GetSnapState,
// TODO: Fix the bug/limitation that requires 'Center Points' and 'Corner Points' to be enabled
"Snaps to a consistent distance offset established by the bounding boxes of nearby layers\n(due to a bug, 'Center Points' and 'Corner Points' must be enabled)",
),
];
pub const GET_SNAP_GEOMETRY_FUNCTIONS: [(&str, GetSnapState); 7] = [
("Anchor", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.anchors) as GetSnapState),
("Line Midpoint", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.line_midpoints) as GetSnapState),
("Path", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.paths) as GetSnapState),
("Normal to Path", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.normals) as GetSnapState),
("Tangent to Path", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.tangents) as GetSnapState),
("Intersection", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.path_intersections) as GetSnapState),
("Align to Selected Path", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.align) as GetSnapState),
pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [
(
"Align with Anchor Points",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.align_with_anchor_point) as GetSnapState,
"Snaps to horizontal/vertical alignment with the anchor points of any vector path",
),
(
"Anchor Points",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.anchor_point) as GetSnapState,
"Snaps to the anchor point of any vector path",
),
(
// TODO: Extend to the midpoints of curved segments and rename to "Segment Midpoint"
"Line Midpoints",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.line_midpoint) as GetSnapState,
"Snaps to the point at the middle of any straight line segment of a vector path",
),
(
"Path Intersection Points",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.path_intersection_point) as GetSnapState,
"Snaps to any points where vector paths intersect",
),
(
"Along Paths",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.along_path) as GetSnapState,
"Snaps along the length of any vector path",
),
(
// TODO: This works correctly for line segments, but not curved segments.
// TODO: Therefore, we should make this use the normal in relation to the incoming curve, not the straight line between the incoming curve's start point and the path.
"Normal to Paths",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.normal_to_path) as GetSnapState,
// TODO: Fix the bug/limitation that requires 'Intersections of Paths' to be enabled
"Snaps a line to a point perpendicular to a vector path\n(due to a bug, 'Intersections of Paths' must be enabled)",
),
(
// TODO: This works correctly for line segments, but not curved segments.
// TODO: Therefore, we should make this use the tangent in relation to the incoming curve, not the straight line between the incoming curve's start point and the path.
"Tangent to Paths",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.tangent_to_path) as GetSnapState,
// TODO: Fix the bug/limitation that requires 'Intersections of Paths' to be enabled
"Snaps a line to a point tangent to a vector path\n(due to a bug, 'Intersections of Paths' must be enabled)",
),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BoundingBoxSnapTarget {
Center,
Corner,
Edge,
CornerPoint,
CenterPoint,
EdgeMidpoint,
AlongEdge,
}
impl fmt::Display for BoundingBoxSnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BoundingBoxSnapTarget::CornerPoint => write!(f, "Bounding Box: Corner Point"),
BoundingBoxSnapTarget::CenterPoint => write!(f, "Bounding Box: Center Point"),
BoundingBoxSnapTarget::EdgeMidpoint => write!(f, "Bounding Box: Edge Midpoint"),
BoundingBoxSnapTarget::AlongEdge => write!(f, "Bounding Box: Along Edge"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum GeometrySnapTarget {
AnchorWithColinearHandles,
AnchorWithFreeHandles,
pub enum PathSnapTarget {
AnchorPointWithColinearHandles,
AnchorPointWithFreeHandles,
LineMidpoint,
Path,
Normal,
Tangent,
Intersection,
AlongPath,
NormalToPath,
TangentToPath,
IntersectionPoint,
}
impl fmt::Display for PathSnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathSnapTarget::AnchorPointWithColinearHandles | PathSnapTarget::AnchorPointWithFreeHandles => write!(f, "Path: Anchor Point"),
PathSnapTarget::LineMidpoint => write!(f, "Path: Line Midpoint"),
PathSnapTarget::AlongPath => write!(f, "Path: Along Path"),
PathSnapTarget::NormalToPath => write!(f, "Path: Normal to Path"),
PathSnapTarget::TangentToPath => write!(f, "Path: Tangent to Path"),
PathSnapTarget::IntersectionPoint => write!(f, "Path: Intersection Point"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArtboardSnapTarget {
Edge,
Corner,
Center,
CornerPoint,
CenterPoint,
AlongEdge,
}
impl fmt::Display for ArtboardSnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArtboardSnapTarget::CornerPoint => write!(f, "Artboard: Corner Point"),
ArtboardSnapTarget::CenterPoint => write!(f, "Artboard: Center Point"),
ArtboardSnapTarget::AlongEdge => write!(f, "Artboard: Along Edge"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -375,14 +525,37 @@ pub enum GridSnapTarget {
Intersection,
}
impl fmt::Display for GridSnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GridSnapTarget::Line => write!(f, "Grid: Along Line"),
GridSnapTarget::LineNormal => write!(f, "Grid: Normal to Line"),
GridSnapTarget::Intersection => write!(f, "Grid: Intersection Point"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlignmentSnapTarget {
BoundsCorner,
BoundsCenter,
ArtboardCorner,
ArtboardCenter,
Handle,
Intersection,
BoundingBoxCornerPoint,
BoundingBoxCenterPoint,
ArtboardCornerPoint,
ArtboardCenterPoint,
AlignWithAnchorPoint,
IntersectionPoint,
}
impl fmt::Display for AlignmentSnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AlignmentSnapTarget::BoundingBoxCornerPoint => write!(f, "{}", BoundingBoxSnapTarget::CornerPoint),
AlignmentSnapTarget::BoundingBoxCenterPoint => write!(f, "{}", BoundingBoxSnapTarget::CenterPoint),
AlignmentSnapTarget::ArtboardCornerPoint => write!(f, "{}", ArtboardSnapTarget::CornerPoint),
AlignmentSnapTarget::ArtboardCenterPoint => write!(f, "{}", ArtboardSnapTarget::CenterPoint),
AlignmentSnapTarget::AlignWithAnchorPoint => write!(f, "{}", PathSnapTarget::AnchorPointWithColinearHandles),
AlignmentSnapTarget::IntersectionPoint => write!(f, "{}", PathSnapTarget::IntersectionPoint),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -393,7 +566,21 @@ pub enum DistributionSnapTarget {
Left,
Up,
Down,
Xy,
XY,
}
impl fmt::Display for DistributionSnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DistributionSnapTarget::X => write!(f, "Distribute: X"),
DistributionSnapTarget::Y => write!(f, "Distribute: Y"),
DistributionSnapTarget::Right => write!(f, "Distribute: Right"),
DistributionSnapTarget::Left => write!(f, "Distribute: Left"),
DistributionSnapTarget::Up => write!(f, "Distribute: Up"),
DistributionSnapTarget::Down => write!(f, "Distribute: Down"),
DistributionSnapTarget::XY => write!(f, "Distribute: XY"),
}
}
}
impl DistributionSnapTarget {
@@ -410,11 +597,11 @@ pub enum SnapTarget {
#[default]
None,
BoundingBox(BoundingBoxSnapTarget),
Geometry(GeometrySnapTarget),
Path(PathSnapTarget),
Artboard(ArtboardSnapTarget),
Grid(GridSnapTarget),
Alignment(AlignmentSnapTarget),
Distribution(DistributionSnapTarget),
DistributeEvenly(DistributionSnapTarget),
}
impl SnapTarget {
@@ -426,17 +613,31 @@ impl SnapTarget {
}
}
impl fmt::Display for SnapTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SnapTarget::None => write!(f, "None"),
SnapTarget::BoundingBox(bounding_box_snap_target) => write!(f, "{bounding_box_snap_target}"),
SnapTarget::Path(path_snap_target) => write!(f, "{path_snap_target}"),
SnapTarget::Artboard(artboard_snap_target) => write!(f, "{artboard_snap_target}"),
SnapTarget::Grid(grid_snap_target) => write!(f, "{grid_snap_target}"),
SnapTarget::Alignment(alignment_snap_target) => write!(f, "{alignment_snap_target}"),
SnapTarget::DistributeEvenly(distribution_snap_target) => write!(f, "{distribution_snap_target}"),
}
}
}
// TODO: implement icons for SnappingOptions eventually
pub enum SnappingOptions {
BoundingBoxes,
Geometry,
Paths,
}
impl fmt::Display for SnappingOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SnappingOptions::BoundingBoxes => write!(f, "Bounding Boxes"),
SnappingOptions::Geometry => write!(f, "Geometry"),
SnappingOptions::Paths => write!(f, "Paths"),
}
}
}
@@ -1,7 +1,7 @@
use super::graph_modification_utils;
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
@@ -193,9 +193,10 @@ impl ShapeState {
for &selected in &state.selected_points {
let source = match selected {
ManipulatorPointId::Anchor(_) if vector_data.colinear(selected) => SnapSource::Geometry(GeometrySnapSource::AnchorWithColinearHandles),
ManipulatorPointId::Anchor(_) => SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles),
_ => SnapSource::Geometry(GeometrySnapSource::Handle),
ManipulatorPointId::Anchor(_) if vector_data.colinear(selected) => SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
ManipulatorPointId::Anchor(_) => SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),
// TODO: This doesn't actually work for handles, instead handles enter the arm above for free handles
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => SnapSource::Path(PathSnapSource::HandlePoint),
};
let Some(position) = selected.get_position(&vector_data) else { continue };
@@ -8,7 +8,7 @@ pub use {alignment_snapper::*, distribution_snapper::*, grid_snapper::*, layer_s
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_SNAP_BACKGROUND, COLOR_OVERLAY_WHITE};
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, GridSnapTarget, SnapTarget};
use crate::messages::portfolio::document::utility_types::misc::{BoundingBoxSnapTarget, GridSnapTarget, PathSnapTarget, SnapTarget};
use crate::messages::prelude::*;
use bezier_rs::{Subpath, TValue};
@@ -23,7 +23,7 @@ use std::cmp::Ordering;
/// Configuration for the relevant snap type
#[derive(Debug, Clone, Copy, Default)]
pub struct SnapTypeConfiguration {
pub only_geometry: bool,
pub only_path: bool,
pub use_existing_candidates: bool,
pub accept_distribution: bool,
pub bbox: Option<Rect>,
@@ -111,7 +111,7 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
(None, None) => None,
(Some(result), None) | (None, Some(result)) => Some(result),
(Some(mut result), Some(align)) => {
let SnapTarget::Distribution(distribution) = result.target else { return Some(result) };
let SnapTarget::DistributeEvenly(distribution) = result.target else { return Some(result) };
if distribution.is_x() && align.alignment_target_x.is_some() {
result.snapped_point_document.y = align.snapped_point_document.y;
result.alignment_target_x = align.alignment_target_x;
@@ -126,7 +126,7 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
}
}
fn get_closest_curve(curves: &[SnappedCurve], exclude_paths: bool) -> Option<&SnappedPoint> {
let keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Geometry(GeometrySnapTarget::Path);
let keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Path(PathSnapTarget::AlongPath);
curves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)
}
fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
@@ -135,12 +135,12 @@ fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {
let mut best = None;
for curve_i in curves {
if curve_i.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::Edge) {
if curve_i.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::AlongEdge) {
continue;
}
for curve_j in curves {
if curve_j.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::Edge) {
if curve_j.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::AlongEdge) {
continue;
}
if curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {
@@ -156,7 +156,7 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
best = Some(SnappedPoint {
snapped_point_document,
distance,
target: SnapTarget::Geometry(GeometrySnapTarget::Intersection),
target: SnapTarget::Path(PathSnapTarget::IntersectionPoint),
tolerance: close.point.tolerance,
curves: [Some(close.document_curve), Some(far.document_curve)],
source: close.point.source,
@@ -262,7 +262,7 @@ impl SnapManager {
if let Some(closest_point) = get_closest_point(snap_results.points) {
snapped_points.push(closest_point);
}
let exclude_paths = !document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Path));
let exclude_paths = !document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath));
if let Some(closest_curve) = get_closest_curve(&snap_results.curves, exclude_paths) {
snapped_points.push(closest_curve.clone());
}
@@ -274,7 +274,7 @@ impl SnapManager {
}
if !constrained {
if document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Intersection)) {
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) {
if let Some(closest_curves_intersection) = get_closest_intersection(point.document_point, &snap_results.curves) {
snapped_points.push(closest_curves_intersection);
}
@@ -287,7 +287,7 @@ impl SnapManager {
}
if to_path {
snapped_points.retain(|i| matches!(i.target, SnapTarget::Geometry(_)));
snapped_points.retain(|i| matches!(i.target, SnapTarget::Path(_)));
}
let mut best_point = None;
@@ -382,7 +382,7 @@ impl SnapManager {
self.alignment_snapper.free_snap(&mut snap_data, point, &mut snap_results, config);
self.distribution_snapper.free_snap(&mut snap_data, point, &mut snap_results, config);
Self::find_best_snap(&mut snap_data, point, snap_results, false, false, config.only_geometry)
Self::find_best_snap(&mut snap_data, point, snap_results, false, false, config.only_path)
}
pub fn constrained_snap(&mut self, snap_data: &SnapData, point: &SnapCandidatePoint, constraint: SnapConstraint, config: SnapTypeConfiguration) -> SnappedPoint {
@@ -408,7 +408,7 @@ impl SnapManager {
self.alignment_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config);
self.distribution_snapper.constrained_snap(&mut snap_data, point, &mut snap_results, constraint, config);
Self::find_best_snap(&mut snap_data, point, snap_results, true, false, config.only_geometry)
Self::find_best_snap(&mut snap_data, point, snap_results, true, false, config.only_path)
}
fn alignment_x_overlay(boxes: &VecDeque<Rect>, transform: DAffine2, overlay_context: &mut OverlayContext) {
@@ -475,9 +475,9 @@ impl SnapManager {
}
if !any_align && ind.distribution_equal_distance_x.is_none() && ind.distribution_equal_distance_y.is_none() {
let text = format!("{:?} to {:?}", ind.source, ind.target);
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 5.));
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_SNAP_BACKGROUND), transform, 5., [Pivot::Start, Pivot::End]);
let text = format!("[{}] from [{}]", ind.target, ind.source);
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.));
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_SNAP_BACKGROUND), transform, 4., [Pivot::Start, Pivot::End]);
overlay_context.square(viewport, Some(4.), Some(COLOR_OVERLAY_BLUE), Some(COLOR_OVERLAY_BLUE));
}
}
@@ -19,7 +19,7 @@ impl AlignmentSnapper {
let document = snap_data.document;
self.bounding_box_points.clear();
if !document.snapping_state.bounds.align {
if !document.snapping_state.bounding_box.align_with_corner_point {
return;
}
@@ -28,7 +28,7 @@ impl AlignmentSnapper {
continue;
}
if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::Corner)) {
if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint)) {
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) else {
continue;
};
@@ -52,7 +52,7 @@ impl AlignmentSnapper {
pub fn snap_bbox_points(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {
self.collect_bounding_box_points(snap_data, !config.use_existing_candidates);
let unselected_geometry = if snap_data.document.snapping_state.target_enabled(SnapTarget::Alignment(AlignmentSnapTarget::Handle)) {
let unselected_geometry = if snap_data.document.snapping_state.target_enabled(SnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint)) {
snap_data.node_snap_cache.map(|cache| cache.unselected.as_slice()).unwrap_or(&[])
} else {
&[]
@@ -77,9 +77,9 @@ impl AlignmentSnapper {
[DVec2::new(point.document_point.x, target_position.y), DVec2::new(target_position.x, point.document_point.y)].map(Some)
};
let target_geometry = matches!(target_point.target, SnapTarget::Geometry(_));
let updated_target = if target_geometry {
SnapTarget::Alignment(AlignmentSnapTarget::Handle)
let target_path = matches!(target_point.target, SnapTarget::Path(_));
let updated_target = if target_path {
SnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint)
} else {
target_point.target
};
@@ -90,7 +90,7 @@ impl AlignmentSnapper {
if distance_to_snapped < tolerance && snap_x.as_ref().map_or(true, |point| distance_to_align_target < point.distance_to_align_target) {
snap_x = Some(SnappedPoint {
snapped_point_document: point_on_x,
source: point.source, //ToDo map source
source: point.source, // TODO(0Hypercube): map source
target: updated_target,
target_bounds: target_point.quad,
distance: distance_to_snapped,
@@ -109,7 +109,7 @@ impl AlignmentSnapper {
if distance_to_snapped < tolerance && snap_y.as_ref().map_or(true, |point| distance_to_align_target < point.distance_to_align_target) {
snap_y = Some(SnappedPoint {
snapped_point_document: point_on_y,
source: point.source, //ToDo map source
source: point.source, // TODO(0Hypercube): map source
target: updated_target,
target_bounds: target_point.quad,
distance: distance_to_snapped,
@@ -137,7 +137,7 @@ impl AlignmentSnapper {
snap_results.points.push(SnappedPoint {
snapped_point_document: intersection,
source: point.source, // TODO: map source
target: SnapTarget::Alignment(AlignmentSnapTarget::Intersection),
target: SnapTarget::Alignment(AlignmentSnapTarget::IntersectionPoint),
target_bounds: snap_x.target_bounds,
distance,
tolerance,
@@ -154,22 +154,23 @@ impl AlignmentSnapper {
_ => {}
}
}
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
let is_bbox = matches!(point.source, SnapSource::BoundingBox(_));
let is_geometry = matches!(point.source, SnapSource::Geometry(_));
let geometry_selected = snap_data.has_manipulators();
let is_path = matches!(point.source, SnapSource::Path(_));
let path_selected = snap_data.has_manipulators();
if is_bbox || (is_geometry && geometry_selected) || (is_geometry && point.alignment) {
if is_bbox || (is_path && path_selected) || (is_path && point.alignment) {
self.snap_bbox_points(snap_data, point, snap_results, SnapConstraint::None, config);
}
}
pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {
let is_bbox = matches!(point.source, SnapSource::BoundingBox(_));
let is_geometry = matches!(point.source, SnapSource::Geometry(_));
let geometry_selected = snap_data.has_manipulators();
let is_path = matches!(point.source, SnapSource::Path(_));
let path_selected = snap_data.has_manipulators();
if is_bbox || (is_geometry && geometry_selected) || (is_geometry && point.alignment) {
if is_bbox || (is_path && path_selected) || (is_path && point.alignment) {
self.snap_bbox_points(snap_data, point, snap_results, constraint, config);
}
}
@@ -210,7 +210,7 @@ impl DistributionSnapper {
let mut final_point = x;
final_point.snapped_point_document += y.snapped_point_document - point.document_point;
final_point.source_bounds = Some(final_bounds.into());
final_point.target = SnapTarget::Distribution(DistributionSnapTarget::Xy);
final_point.target = SnapTarget::DistributeEvenly(DistributionSnapTarget::XY);
final_point.distribution_boxes_y = y.distribution_boxes_y;
final_point.distribution_equal_distance_y = y.distribution_equal_distance_y;
final_point.distance = (final_point.distance * final_point.distance + y.distance * y.distance).sqrt();
@@ -325,7 +325,7 @@ impl DistributionSnapper {
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
let Some(bounds) = config.bbox else { return };
if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::Center) || !snap_data.document.snapping_state.bounds.distribute {
if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint) || !snap_data.document.snapping_state.bounding_box.distribute_evenly {
return;
}
@@ -335,7 +335,7 @@ impl DistributionSnapper {
pub fn constrained_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, constraint: SnapConstraint, config: SnapTypeConfiguration) {
let Some(bounds) = config.bbox else { return };
if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::Center) || !snap_data.document.snapping_state.bounds.distribute {
if point.source != SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint) || !snap_data.document.snapping_state.bounding_box.distribute_evenly {
return;
}
@@ -15,7 +15,6 @@ pub struct LayerSnapper {
points_to_snap: Vec<SnapCandidatePoint>,
paths_to_snap: Vec<SnapCandidatePath>,
}
impl LayerSnapper {
pub fn add_layer_bounds(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, target: SnapTarget) {
if !document.snapping_state.target_enabled(target) {
@@ -49,6 +48,7 @@ impl LayerSnapper {
});
}
}
pub fn collect_paths(&mut self, snap_data: &mut SnapData, first_point: bool) {
if !first_point {
return;
@@ -60,7 +60,7 @@ impl LayerSnapper {
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
continue;
}
self.add_layer_bounds(document, layer, SnapTarget::Artboard(ArtboardSnapTarget::Edge));
self.add_layer_bounds(document, layer, SnapTarget::Artboard(ArtboardSnapTarget::AlongEdge));
}
for &layer in snap_data.get_candidates() {
let transform = document.metadata().transform_to_document(layer);
@@ -68,8 +68,7 @@ impl LayerSnapper {
continue;
}
if document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Intersection)) || document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Path))
{
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) || document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath)) {
for subpath in document.metadata().layer_outline(layer) {
for (start_index, curve) in subpath.iter().enumerate() {
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
@@ -81,23 +80,24 @@ impl LayerSnapper {
document_curve,
layer,
start,
target: SnapTarget::Geometry(GeometrySnapTarget::Path),
target: SnapTarget::Path(PathSnapTarget::AlongPath),
bounds: None,
});
}
}
}
if !snap_data.ignore_bounds(layer) {
self.add_layer_bounds(document, layer, SnapTarget::BoundingBox(BoundingBoxSnapTarget::Edge));
self.add_layer_bounds(document, layer, SnapTarget::BoundingBox(BoundingBoxSnapTarget::AlongEdge));
}
}
}
pub fn free_snap_paths(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
self.collect_paths(snap_data, !config.use_existing_candidates);
let document = snap_data.document;
let normals = document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Normal));
let tangents = document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Tangent));
let normals = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::NormalToPath));
let tangents = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::TangentToPath));
let tolerance = snap_tolerance(document);
for path in &self.paths_to_snap {
@@ -187,7 +187,7 @@ impl LayerSnapper {
return;
}
if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::Corner)) {
if document.snapping_state.target_enabled(SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint)) {
let Some(bounds) = document
.network_interface
.document_metadata()
@@ -217,6 +217,7 @@ impl LayerSnapper {
get_bbox_points(quad, &mut self.points_to_snap, values, document);
}
}
pub fn snap_anchors(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, c: SnapConstraint, constrained_point: DVec2) {
let mut best = None;
for candidate in &self.points_to_snap {
@@ -251,6 +252,7 @@ impl LayerSnapper {
snap_results.points.push(result);
}
}
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
self.collect_anchors(snap_data, !config.use_existing_candidates);
self.snap_anchors(snap_data, point, snap_results, SnapConstraint::None, point.document_point);
@@ -275,7 +277,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
}
snap_results.points.push(SnappedPoint {
snapped_point_document: normal_point,
target: SnapTarget::Geometry(GeometrySnapTarget::Normal),
target: SnapTarget::Path(PathSnapTarget::NormalToPath),
distance,
tolerance,
curves: [Some(path.document_curve), None],
@@ -296,7 +298,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
}
snap_results.points.push(SnappedPoint {
snapped_point_document: tangent_point,
target: SnapTarget::Geometry(GeometrySnapTarget::Tangent),
target: SnapTarget::Path(PathSnapTarget::TangentToPath),
distance,
tolerance,
curves: [Some(path.document_curve), None],
@@ -317,6 +319,7 @@ struct SnapCandidatePath {
target: SnapTarget,
bounds: Option<Quad>,
}
#[derive(Clone, Debug, Default)]
pub struct SnapCandidatePoint {
pub document_point: DVec2,
@@ -330,6 +333,7 @@ impl SnapCandidatePoint {
pub fn new(document_point: DVec2, source: SnapSource, target: SnapTarget) -> Self {
Self::new_quad(document_point, source, target, None, true)
}
pub fn new_quad(document_point: DVec2, source: SnapSource, target: SnapTarget, quad: Option<Quad>, alignment: bool) -> Self {
Self {
document_point,
@@ -340,18 +344,22 @@ impl SnapCandidatePoint {
..Default::default()
}
}
pub fn new_source(document_point: DVec2, source: SnapSource) -> Self {
Self::new(document_point, source, SnapTarget::None)
}
pub fn handle(document_point: DVec2) -> Self {
Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles))
Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles))
}
pub fn handle_neighbors(document_point: DVec2, neighbors: impl Into<Vec<DVec2>>) -> Self {
let mut point = Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles));
let mut point = Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles));
point.neighbors = neighbors.into();
point
}
}
#[derive(Default)]
pub struct BBoxSnapValues {
corner_source: SnapSource,
@@ -363,41 +371,42 @@ pub struct BBoxSnapValues {
}
impl BBoxSnapValues {
pub const BOUNDING_BOX: Self = Self {
corner_source: SnapSource::BoundingBox(BoundingBoxSnapSource::Corner),
corner_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::Corner),
corner_source: SnapSource::BoundingBox(BoundingBoxSnapSource::CornerPoint),
corner_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::CornerPoint),
edge_source: SnapSource::BoundingBox(BoundingBoxSnapSource::EdgeMidpoint),
edge_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::EdgeMidpoint),
center_source: SnapSource::BoundingBox(BoundingBoxSnapSource::Center),
center_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::Center),
center_source: SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint),
center_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::CenterPoint),
};
pub const ARTBOARD: Self = Self {
corner_source: SnapSource::Artboard(ArtboardSnapSource::Corner),
corner_target: SnapTarget::Artboard(ArtboardSnapTarget::Corner),
corner_source: SnapSource::Artboard(ArtboardSnapSource::CornerPoint),
corner_target: SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint),
edge_source: SnapSource::None,
edge_target: SnapTarget::None,
center_source: SnapSource::Artboard(ArtboardSnapSource::Center),
center_target: SnapTarget::Artboard(ArtboardSnapTarget::Center),
center_source: SnapSource::Artboard(ArtboardSnapSource::CenterPoint),
center_target: SnapTarget::Artboard(ArtboardSnapTarget::CenterPoint),
};
pub const ALIGN_BOUNDING_BOX: Self = Self {
corner_source: SnapSource::Alignment(AlignmentSnapSource::BoundsCorner),
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundsCorner),
corner_source: SnapSource::Alignment(AlignmentSnapSource::BoundingBoxCornerPoint),
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundingBoxCornerPoint),
edge_source: SnapSource::None,
edge_target: SnapTarget::None,
center_source: SnapSource::Alignment(AlignmentSnapSource::BoundsCenter),
center_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundsCenter),
center_source: SnapSource::Alignment(AlignmentSnapSource::BoundingBoxCenterPoint),
center_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundingBoxCenterPoint),
};
pub const ALIGN_ARTBOARD: Self = Self {
corner_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCorner),
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCorner),
corner_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCornerPoint),
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCornerPoint),
edge_source: SnapSource::None,
edge_target: SnapTarget::None,
center_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCenter),
center_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCenter),
center_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCenterPoint),
center_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCenterPoint),
};
}
pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values: BBoxSnapValues, document: &DocumentMessageHandler) {
for index in 0..4 {
let start = quad.0[index];
@@ -409,6 +418,7 @@ pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values:
points.push(SnapCandidatePoint::new_quad((start + end) / 2., values.edge_source, values.edge_target, Some(quad), false));
}
}
if document.snapping_state.target_enabled(values.center_target) {
points.push(SnapCandidatePoint::new_quad(quad.center(), values.center_source, values.center_target, Some(quad), false));
}
@@ -417,10 +427,12 @@ pub fn get_bbox_points(quad: Quad, points: &mut Vec<SnapCandidatePoint>, values:
fn handle_not_under(to_document: DAffine2) -> impl Fn(&DVec2) -> bool {
move |&offset: &DVec2| to_document.transform_vector2(offset).length_squared() >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE
}
fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<PointId>, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>, to_document: DAffine2) {
let document = snap_data.document;
// Midpoints of linear segments
if document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::LineMidpoint)) {
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::LineMidpoint)) {
for (index, curve) in subpath.iter().enumerate() {
if snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[index].id) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(index + 1) % subpath.len()].id) {
continue;
@@ -434,12 +446,13 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
if in_handle.is_none() && out_handle.is_none() {
points.push(SnapCandidatePoint::new(
to_document.transform_point2(curve.start() * 0.5 + curve.end * 0.5),
SnapSource::Geometry(GeometrySnapSource::LineMidpoint),
SnapTarget::Geometry(GeometrySnapTarget::LineMidpoint),
SnapSource::Path(PathSnapSource::LineMidpoint),
SnapTarget::Path(PathSnapTarget::LineMidpoint),
));
}
}
}
// Anchors
for (index, group) in subpath.manipulator_groups().iter().enumerate() {
if snap_data.ignore_manipulator(layer, group.id) {
@@ -452,19 +465,20 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
let colinear = are_manipulator_handles_colinear(group, to_document, subpath, index);
if colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::AnchorWithColinearHandles)) {
// Colinear handles
// Colinear handles
if colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles)) {
points.push(SnapCandidatePoint::new(
to_document.transform_point2(group.anchor),
SnapSource::Geometry(GeometrySnapSource::AnchorWithColinearHandles),
SnapTarget::Geometry(GeometrySnapTarget::AnchorWithColinearHandles),
SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
SnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles),
));
} else if !colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::AnchorWithFreeHandles)) {
// Free handles
}
// Free handles
else if !colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles)) {
points.push(SnapCandidatePoint::new(
to_document.transform_point2(group.anchor),
SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles),
SnapTarget::Geometry(GeometrySnapTarget::AnchorWithFreeHandles),
SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),
SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles),
));
}
}
@@ -63,7 +63,7 @@ impl SnappedPoint {
Self {
snapped_point_document: point.document_point + translation,
source: point.source,
target: SnapTarget::Distribution(target),
target: SnapTarget::DistributeEvenly(target),
distribution_boxes_x,
distribution_equal_distance_x: is_x.then_some(distances.equal),
distribution_boxes_y,