mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Improve snapping with better snap target names, tooltips, cleaner overlay labels, code cleanup
This commit is contained in:
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::overlays::utility_types::Pivot;
|
||||||
use super::utility_types::clipboards::Clipboard;
|
use super::utility_types::clipboards::Clipboard;
|
||||||
use super::utility_types::error::EditorError;
|
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::network_interface::{self, NodeNetworkInterface, TransactionStatus};
|
||||||
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
||||||
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
|
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
|
||||||
|
@ -1779,28 +1779,27 @@ impl DocumentMessageHandler {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
.into_iter()
|
.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![
|
widgets: vec![
|
||||||
CheckboxInput::new(*closure(&mut snapping_state))
|
CheckboxInput::new(*closure(&mut snapping_state))
|
||||||
.on_update(move |input: &CheckboxInput| DocumentMessage::SetSnapping { closure: Some(closure), snapping_state: input.checked }.into())
|
.on_update(move |input: &CheckboxInput| DocumentMessage::SetSnapping { closure: Some(closure), snapping_state: input.checked }.into())
|
||||||
|
.tooltip(tooltip)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
TextLabel::new(name).widget_holder(),
|
TextLabel::new(name).tooltip(tooltip).widget_holder(),
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
.chain(
|
.chain([LayoutGroup::Row {
|
||||||
[LayoutGroup::Row {
|
widgets: vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_holder()],
|
||||||
widgets: vec![TextLabel::new(SnappingOptions::Geometry.to_string()).widget_holder()],
|
}])
|
||||||
}]
|
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
|
||||||
.into_iter()
|
widgets: vec![
|
||||||
.chain(GET_SNAP_GEOMETRY_FUNCTIONS.into_iter().map(|(name, closure)| LayoutGroup::Row {
|
|
||||||
widgets: vec![
|
|
||||||
CheckboxInput::new(*closure(&mut snapping_state2))
|
CheckboxInput::new(*closure(&mut snapping_state2))
|
||||||
.on_update(move |input: &CheckboxInput| DocumentMessage::SetSnapping { closure: Some(closure), snapping_state: input.checked }.into())
|
.on_update(move |input: &CheckboxInput| DocumentMessage::SetSnapping { closure: Some(closure), snapping_state: input.checked }.into())
|
||||||
|
.tooltip(tooltip)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
TextLabel::new(name).widget_holder(),
|
TextLabel::new(name).tooltip(tooltip).widget_holder(),
|
||||||
],
|
],
|
||||||
})),
|
}))
|
||||||
)
|
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
|
|
|
@ -62,11 +62,11 @@ impl DocumentMode {
|
||||||
pub struct SnappingState {
|
pub struct SnappingState {
|
||||||
pub snapping_enabled: bool,
|
pub snapping_enabled: bool,
|
||||||
pub grid_snapping: bool,
|
pub grid_snapping: bool,
|
||||||
pub bounds: BoundsSnapping,
|
|
||||||
pub nodes: PointSnapping,
|
|
||||||
pub grid: GridSnapping,
|
|
||||||
pub tolerance: f64,
|
|
||||||
pub artboards: bool,
|
pub artboards: bool,
|
||||||
|
pub tolerance: f64,
|
||||||
|
pub bounding_box: BoundingBoxSnapping,
|
||||||
|
pub path: PathSnapping,
|
||||||
|
pub grid: GridSnapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SnappingState {
|
impl Default for SnappingState {
|
||||||
|
@ -74,11 +74,11 @@ impl Default for SnappingState {
|
||||||
Self {
|
Self {
|
||||||
snapping_enabled: true,
|
snapping_enabled: true,
|
||||||
grid_snapping: false,
|
grid_snapping: false,
|
||||||
bounds: Default::default(),
|
|
||||||
nodes: Default::default(),
|
|
||||||
grid: Default::default(),
|
|
||||||
tolerance: 8.,
|
|
||||||
artboards: true,
|
artboards: true,
|
||||||
|
tolerance: 8.,
|
||||||
|
bounding_box: BoundingBoxSnapping::default(),
|
||||||
|
path: PathSnapping::default(),
|
||||||
|
grid: GridSnapping::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,26 +89,25 @@ impl SnappingState {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
match target {
|
match target {
|
||||||
SnapTarget::BoundingBox(bounding_box) => match bounding_box {
|
SnapTarget::BoundingBox(target) => match target {
|
||||||
BoundingBoxSnapTarget::Corner => self.bounds.corners,
|
BoundingBoxSnapTarget::CornerPoint => self.bounding_box.corner_point,
|
||||||
BoundingBoxSnapTarget::Edge => self.bounds.edges,
|
BoundingBoxSnapTarget::AlongEdge => self.bounding_box.along_edge,
|
||||||
BoundingBoxSnapTarget::EdgeMidpoint => self.bounds.edge_midpoints,
|
BoundingBoxSnapTarget::EdgeMidpoint => self.bounding_box.edge_midpoint,
|
||||||
BoundingBoxSnapTarget::Center => self.bounds.centers,
|
BoundingBoxSnapTarget::CenterPoint => self.bounding_box.center_point,
|
||||||
},
|
},
|
||||||
SnapTarget::Geometry(nodes) => match nodes {
|
SnapTarget::Path(target) => match target {
|
||||||
GeometrySnapTarget::AnchorWithColinearHandles => self.nodes.anchors,
|
PathSnapTarget::AnchorPointWithColinearHandles | PathSnapTarget::AnchorPointWithFreeHandles => self.path.anchor_point,
|
||||||
GeometrySnapTarget::AnchorWithFreeHandles => self.nodes.anchors,
|
PathSnapTarget::LineMidpoint => self.path.line_midpoint,
|
||||||
GeometrySnapTarget::LineMidpoint => self.nodes.line_midpoints,
|
PathSnapTarget::AlongPath => self.path.along_path,
|
||||||
GeometrySnapTarget::Path => self.nodes.paths,
|
PathSnapTarget::NormalToPath => self.path.normal_to_path,
|
||||||
GeometrySnapTarget::Normal => self.nodes.normals,
|
PathSnapTarget::TangentToPath => self.path.tangent_to_path,
|
||||||
GeometrySnapTarget::Tangent => self.nodes.tangents,
|
PathSnapTarget::IntersectionPoint => self.path.path_intersection_point,
|
||||||
GeometrySnapTarget::Intersection => self.nodes.path_intersections,
|
|
||||||
},
|
},
|
||||||
SnapTarget::Artboard(_) => self.artboards,
|
SnapTarget::Artboard(_) => self.artboards,
|
||||||
SnapTarget::Grid(_) => self.grid_snapping,
|
SnapTarget::Grid(_) => self.grid_snapping,
|
||||||
SnapTarget::Alignment(AlignmentSnapTarget::Handle) => self.nodes.align,
|
SnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint) => self.path.align_with_anchor_point,
|
||||||
SnapTarget::Alignment(_) => self.bounds.align,
|
SnapTarget::Alignment(_) => self.bounding_box.align_with_corner_point,
|
||||||
SnapTarget::Distribution(_) => self.bounds.distribute,
|
SnapTarget::DistributeEvenly(_) => self.bounding_box.distribute_evenly,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,50 +115,50 @@ impl SnappingState {
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct BoundsSnapping {
|
pub struct BoundingBoxSnapping {
|
||||||
pub edges: bool,
|
pub center_point: bool,
|
||||||
pub corners: bool,
|
pub corner_point: bool,
|
||||||
pub edge_midpoints: bool,
|
pub edge_midpoint: bool,
|
||||||
pub centers: bool,
|
pub along_edge: bool,
|
||||||
pub align: bool,
|
pub align_with_corner_point: bool,
|
||||||
pub distribute: bool,
|
pub distribute_evenly: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BoundsSnapping {
|
impl Default for BoundingBoxSnapping {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
edges: true,
|
center_point: true,
|
||||||
corners: true,
|
corner_point: true,
|
||||||
edge_midpoints: false,
|
edge_midpoint: true,
|
||||||
centers: true,
|
along_edge: true,
|
||||||
align: true,
|
align_with_corner_point: true,
|
||||||
distribute: true,
|
distribute_evenly: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PointSnapping {
|
pub struct PathSnapping {
|
||||||
pub paths: bool,
|
pub anchor_point: bool,
|
||||||
pub path_intersections: bool,
|
pub line_midpoint: bool,
|
||||||
pub anchors: bool,
|
pub along_path: bool,
|
||||||
pub line_midpoints: bool,
|
pub normal_to_path: bool,
|
||||||
pub normals: bool,
|
pub tangent_to_path: bool,
|
||||||
pub tangents: bool,
|
pub path_intersection_point: bool,
|
||||||
pub align: bool,
|
pub align_with_anchor_point: bool, // TODO: Rename
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PointSnapping {
|
impl Default for PathSnapping {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
paths: true,
|
anchor_point: true,
|
||||||
path_intersections: true,
|
line_midpoint: true,
|
||||||
anchors: true,
|
along_path: true,
|
||||||
line_midpoints: true,
|
normal_to_path: true,
|
||||||
normals: true,
|
tangent_to_path: true,
|
||||||
tangents: true,
|
path_intersection_point: true,
|
||||||
align: false,
|
align_with_anchor_point: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -265,34 +264,75 @@ impl GridSnapping {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BoundingBoxSnapSource {
|
pub enum BoundingBoxSnapSource {
|
||||||
Center,
|
CornerPoint,
|
||||||
Corner,
|
CenterPoint,
|
||||||
EdgeMidpoint,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ArtboardSnapSource {
|
pub enum ArtboardSnapSource {
|
||||||
Center,
|
CornerPoint,
|
||||||
Corner,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum GeometrySnapSource {
|
pub enum PathSnapSource {
|
||||||
AnchorWithColinearHandles,
|
AnchorPointWithColinearHandles,
|
||||||
AnchorWithFreeHandles,
|
AnchorPointWithFreeHandles,
|
||||||
Handle,
|
HandlePoint,
|
||||||
LineMidpoint,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AlignmentSnapSource {
|
pub enum AlignmentSnapSource {
|
||||||
BoundsCorner,
|
BoundingBoxCornerPoint,
|
||||||
BoundsCenter,
|
BoundingBoxCenterPoint,
|
||||||
BoundsEdgeMidpoint,
|
BoundingBoxEdgeMidpoint,
|
||||||
ArtboardCorner,
|
ArtboardCornerPoint,
|
||||||
ArtboardCenter,
|
ArtboardCenterPoint,
|
||||||
Handle,
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -301,7 +341,7 @@ pub enum SnapSource {
|
||||||
None,
|
None,
|
||||||
BoundingBox(BoundingBoxSnapSource),
|
BoundingBox(BoundingBoxSnapSource),
|
||||||
Artboard(ArtboardSnapSource),
|
Artboard(ArtboardSnapSource),
|
||||||
Geometry(GeometrySnapSource),
|
Path(PathSnapSource),
|
||||||
Alignment(AlignmentSnapSource),
|
Alignment(AlignmentSnapSource),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,54 +358,164 @@ impl SnapSource {
|
||||||
pub fn center(&self) -> bool {
|
pub fn center(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
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;
|
type GetSnapState = for<'a> fn(&'a mut SnappingState) -> &'a mut bool;
|
||||||
pub const GET_SNAP_BOX_FUNCTIONS: [(&str, GetSnapState); 6] = [
|
pub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 6] = [
|
||||||
("Box Center", (|snapping_state| &mut snapping_state.bounds.centers) as GetSnapState),
|
(
|
||||||
("Box Corner", (|snapping_state| &mut snapping_state.bounds.corners) as GetSnapState),
|
// TODO: Rename to "Beyond Edges" and update behavior to snap to an infinite extension of the bounding box edges
|
||||||
("Along Edge", (|snapping_state| &mut snapping_state.bounds.edges) as GetSnapState),
|
// TODO: (even when the layer is locally rotated) instead of horizontally/vertically aligning with the corner points
|
||||||
("Midpoint of Edge", (|snapping_state| &mut snapping_state.bounds.edge_midpoints) as GetSnapState),
|
"Align with Corner Points",
|
||||||
("Align to Box", (|snapping_state| &mut snapping_state.bounds.align) as GetSnapState),
|
(|snapping_state| &mut snapping_state.bounding_box.align_with_corner_point) as GetSnapState,
|
||||||
("Evenly Distribute Boxes", (|snapping_state| &mut snapping_state.bounds.distribute) 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] = [
|
pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 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),
|
"Align with Anchor Points",
|
||||||
("Path", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.paths) as GetSnapState),
|
(|snapping_state: &mut SnappingState| &mut snapping_state.path.align_with_anchor_point) as GetSnapState,
|
||||||
("Normal to Path", (|snapping_state: &mut SnappingState| &mut snapping_state.nodes.normals) as GetSnapState),
|
"Snaps to horizontal/vertical alignment with the anchor points of any vector path",
|
||||||
("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),
|
"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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum BoundingBoxSnapTarget {
|
pub enum BoundingBoxSnapTarget {
|
||||||
Center,
|
CornerPoint,
|
||||||
Corner,
|
CenterPoint,
|
||||||
Edge,
|
|
||||||
EdgeMidpoint,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum GeometrySnapTarget {
|
pub enum PathSnapTarget {
|
||||||
AnchorWithColinearHandles,
|
AnchorPointWithColinearHandles,
|
||||||
AnchorWithFreeHandles,
|
AnchorPointWithFreeHandles,
|
||||||
LineMidpoint,
|
LineMidpoint,
|
||||||
Path,
|
AlongPath,
|
||||||
Normal,
|
NormalToPath,
|
||||||
Tangent,
|
TangentToPath,
|
||||||
Intersection,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ArtboardSnapTarget {
|
pub enum ArtboardSnapTarget {
|
||||||
Edge,
|
CornerPoint,
|
||||||
Corner,
|
CenterPoint,
|
||||||
Center,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -375,14 +525,37 @@ pub enum GridSnapTarget {
|
||||||
Intersection,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AlignmentSnapTarget {
|
pub enum AlignmentSnapTarget {
|
||||||
BoundsCorner,
|
BoundingBoxCornerPoint,
|
||||||
BoundsCenter,
|
BoundingBoxCenterPoint,
|
||||||
ArtboardCorner,
|
ArtboardCornerPoint,
|
||||||
ArtboardCenter,
|
ArtboardCenterPoint,
|
||||||
Handle,
|
AlignWithAnchorPoint,
|
||||||
Intersection,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -393,7 +566,21 @@ pub enum DistributionSnapTarget {
|
||||||
Left,
|
Left,
|
||||||
Up,
|
Up,
|
||||||
Down,
|
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 {
|
impl DistributionSnapTarget {
|
||||||
|
@ -410,11 +597,11 @@ pub enum SnapTarget {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
BoundingBox(BoundingBoxSnapTarget),
|
BoundingBox(BoundingBoxSnapTarget),
|
||||||
Geometry(GeometrySnapTarget),
|
Path(PathSnapTarget),
|
||||||
Artboard(ArtboardSnapTarget),
|
Artboard(ArtboardSnapTarget),
|
||||||
Grid(GridSnapTarget),
|
Grid(GridSnapTarget),
|
||||||
Alignment(AlignmentSnapTarget),
|
Alignment(AlignmentSnapTarget),
|
||||||
Distribution(DistributionSnapTarget),
|
DistributeEvenly(DistributionSnapTarget),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SnapTarget {
|
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
|
// TODO: implement icons for SnappingOptions eventually
|
||||||
pub enum SnappingOptions {
|
pub enum SnappingOptions {
|
||||||
BoundingBoxes,
|
BoundingBoxes,
|
||||||
Geometry,
|
Paths,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SnappingOptions {
|
impl fmt::Display for SnappingOptions {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
SnappingOptions::BoundingBoxes => write!(f, "Bounding Boxes"),
|
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::graph_modification_utils;
|
||||||
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
|
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::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::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
||||||
|
@ -193,9 +193,10 @@ impl ShapeState {
|
||||||
|
|
||||||
for &selected in &state.selected_points {
|
for &selected in &state.selected_points {
|
||||||
let source = match selected {
|
let source = match selected {
|
||||||
ManipulatorPointId::Anchor(_) if vector_data.colinear(selected) => SnapSource::Geometry(GeometrySnapSource::AnchorWithColinearHandles),
|
ManipulatorPointId::Anchor(_) if vector_data.colinear(selected) => SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
|
||||||
ManipulatorPointId::Anchor(_) => SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles),
|
ManipulatorPointId::Anchor(_) => SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),
|
||||||
_ => SnapSource::Geometry(GeometrySnapSource::Handle),
|
// 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 };
|
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::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::overlays::utility_types::{OverlayContext, Pivot};
|
||||||
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::{BoundingBoxSnapTarget, GeometrySnapTarget, GridSnapTarget, SnapTarget};
|
use crate::messages::portfolio::document::utility_types::misc::{BoundingBoxSnapTarget, GridSnapTarget, PathSnapTarget, SnapTarget};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use bezier_rs::{Subpath, TValue};
|
use bezier_rs::{Subpath, TValue};
|
||||||
|
@ -23,7 +23,7 @@ use std::cmp::Ordering;
|
||||||
/// Configuration for the relevant snap type
|
/// Configuration for the relevant snap type
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct SnapTypeConfiguration {
|
pub struct SnapTypeConfiguration {
|
||||||
pub only_geometry: bool,
|
pub only_path: bool,
|
||||||
pub use_existing_candidates: bool,
|
pub use_existing_candidates: bool,
|
||||||
pub accept_distribution: bool,
|
pub accept_distribution: bool,
|
||||||
pub bbox: Option<Rect>,
|
pub bbox: Option<Rect>,
|
||||||
|
@ -111,7 +111,7 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
(Some(result), None) | (None, Some(result)) => Some(result),
|
(Some(result), None) | (None, Some(result)) => Some(result),
|
||||||
(Some(mut result), Some(align)) => {
|
(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() {
|
if distribution.is_x() && align.alignment_target_x.is_some() {
|
||||||
result.snapped_point_document.y = align.snapped_point_document.y;
|
result.snapped_point_document.y = align.snapped_point_document.y;
|
||||||
result.alignment_target_x = align.alignment_target_x;
|
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> {
|
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)
|
curves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)
|
||||||
}
|
}
|
||||||
fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
|
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> {
|
fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
for curve_i in curves {
|
for curve_i in curves {
|
||||||
if curve_i.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::Edge) {
|
if curve_i.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::AlongEdge) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for curve_j in curves {
|
for curve_j in curves {
|
||||||
if curve_j.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::Edge) {
|
if curve_j.point.target == SnapTarget::BoundingBox(BoundingBoxSnapTarget::AlongEdge) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {
|
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 {
|
best = Some(SnappedPoint {
|
||||||
snapped_point_document,
|
snapped_point_document,
|
||||||
distance,
|
distance,
|
||||||
target: SnapTarget::Geometry(GeometrySnapTarget::Intersection),
|
target: SnapTarget::Path(PathSnapTarget::IntersectionPoint),
|
||||||
tolerance: close.point.tolerance,
|
tolerance: close.point.tolerance,
|
||||||
curves: [Some(close.document_curve), Some(far.document_curve)],
|
curves: [Some(close.document_curve), Some(far.document_curve)],
|
||||||
source: close.point.source,
|
source: close.point.source,
|
||||||
|
@ -262,7 +262,7 @@ impl SnapManager {
|
||||||
if let Some(closest_point) = get_closest_point(snap_results.points) {
|
if let Some(closest_point) = get_closest_point(snap_results.points) {
|
||||||
snapped_points.push(closest_point);
|
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) {
|
if let Some(closest_curve) = get_closest_curve(&snap_results.curves, exclude_paths) {
|
||||||
snapped_points.push(closest_curve.clone());
|
snapped_points.push(closest_curve.clone());
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ impl SnapManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !constrained {
|
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) {
|
if let Some(closest_curves_intersection) = get_closest_intersection(point.document_point, &snap_results.curves) {
|
||||||
snapped_points.push(closest_curves_intersection);
|
snapped_points.push(closest_curves_intersection);
|
||||||
}
|
}
|
||||||
|
@ -287,7 +287,7 @@ impl SnapManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if to_path {
|
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;
|
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.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.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 {
|
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.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.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) {
|
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() {
|
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 text = format!("[{}] from [{}]", ind.target, ind.source);
|
||||||
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 5.));
|
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.));
|
||||||
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_SNAP_BACKGROUND), transform, 5., [Pivot::Start, Pivot::End]);
|
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));
|
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;
|
let document = snap_data.document;
|
||||||
|
|
||||||
self.bounding_box_points.clear();
|
self.bounding_box_points.clear();
|
||||||
if !document.snapping_state.bounds.align {
|
if !document.snapping_state.bounding_box.align_with_corner_point {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ impl AlignmentSnapper {
|
||||||
continue;
|
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 {
|
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) else {
|
||||||
continue;
|
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) {
|
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);
|
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(&[])
|
snap_data.node_snap_cache.map(|cache| cache.unselected.as_slice()).unwrap_or(&[])
|
||||||
} else {
|
} 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)
|
[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 target_path = matches!(target_point.target, SnapTarget::Path(_));
|
||||||
let updated_target = if target_geometry {
|
let updated_target = if target_path {
|
||||||
SnapTarget::Alignment(AlignmentSnapTarget::Handle)
|
SnapTarget::Alignment(AlignmentSnapTarget::AlignWithAnchorPoint)
|
||||||
} else {
|
} else {
|
||||||
target_point.target
|
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) {
|
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 {
|
snap_x = Some(SnappedPoint {
|
||||||
snapped_point_document: point_on_x,
|
snapped_point_document: point_on_x,
|
||||||
source: point.source, //ToDo map source
|
source: point.source, // TODO(0Hypercube): map source
|
||||||
target: updated_target,
|
target: updated_target,
|
||||||
target_bounds: target_point.quad,
|
target_bounds: target_point.quad,
|
||||||
distance: distance_to_snapped,
|
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) {
|
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 {
|
snap_y = Some(SnappedPoint {
|
||||||
snapped_point_document: point_on_y,
|
snapped_point_document: point_on_y,
|
||||||
source: point.source, //ToDo map source
|
source: point.source, // TODO(0Hypercube): map source
|
||||||
target: updated_target,
|
target: updated_target,
|
||||||
target_bounds: target_point.quad,
|
target_bounds: target_point.quad,
|
||||||
distance: distance_to_snapped,
|
distance: distance_to_snapped,
|
||||||
|
@ -137,7 +137,7 @@ impl AlignmentSnapper {
|
||||||
snap_results.points.push(SnappedPoint {
|
snap_results.points.push(SnappedPoint {
|
||||||
snapped_point_document: intersection,
|
snapped_point_document: intersection,
|
||||||
source: point.source, // TODO: map source
|
source: point.source, // TODO: map source
|
||||||
target: SnapTarget::Alignment(AlignmentSnapTarget::Intersection),
|
target: SnapTarget::Alignment(AlignmentSnapTarget::IntersectionPoint),
|
||||||
target_bounds: snap_x.target_bounds,
|
target_bounds: snap_x.target_bounds,
|
||||||
distance,
|
distance,
|
||||||
tolerance,
|
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) {
|
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_bbox = matches!(point.source, SnapSource::BoundingBox(_));
|
||||||
let is_geometry = matches!(point.source, SnapSource::Geometry(_));
|
let is_path = matches!(point.source, SnapSource::Path(_));
|
||||||
let geometry_selected = snap_data.has_manipulators();
|
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);
|
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) {
|
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_bbox = matches!(point.source, SnapSource::BoundingBox(_));
|
||||||
let is_geometry = matches!(point.source, SnapSource::Geometry(_));
|
let is_path = matches!(point.source, SnapSource::Path(_));
|
||||||
let geometry_selected = snap_data.has_manipulators();
|
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);
|
self.snap_bbox_points(snap_data, point, snap_results, constraint, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,7 @@ impl DistributionSnapper {
|
||||||
let mut final_point = x;
|
let mut final_point = x;
|
||||||
final_point.snapped_point_document += y.snapped_point_document - point.document_point;
|
final_point.snapped_point_document += y.snapped_point_document - point.document_point;
|
||||||
final_point.source_bounds = Some(final_bounds.into());
|
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_boxes_y = y.distribution_boxes_y;
|
||||||
final_point.distribution_equal_distance_y = y.distribution_equal_distance_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();
|
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) {
|
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 };
|
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;
|
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) {
|
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 };
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ pub struct LayerSnapper {
|
||||||
points_to_snap: Vec<SnapCandidatePoint>,
|
points_to_snap: Vec<SnapCandidatePoint>,
|
||||||
paths_to_snap: Vec<SnapCandidatePath>,
|
paths_to_snap: Vec<SnapCandidatePath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerSnapper {
|
impl LayerSnapper {
|
||||||
pub fn add_layer_bounds(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, target: SnapTarget) {
|
pub fn add_layer_bounds(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, target: SnapTarget) {
|
||||||
if !document.snapping_state.target_enabled(target) {
|
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) {
|
pub fn collect_paths(&mut self, snap_data: &mut SnapData, first_point: bool) {
|
||||||
if !first_point {
|
if !first_point {
|
||||||
return;
|
return;
|
||||||
|
@ -60,7 +60,7 @@ impl LayerSnapper {
|
||||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
|
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
|
||||||
continue;
|
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() {
|
for &layer in snap_data.get_candidates() {
|
||||||
let transform = document.metadata().transform_to_document(layer);
|
let transform = document.metadata().transform_to_document(layer);
|
||||||
|
@ -68,8 +68,7 @@ impl LayerSnapper {
|
||||||
continue;
|
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 subpath in document.metadata().layer_outline(layer) {
|
||||||
for (start_index, curve) in subpath.iter().enumerate() {
|
for (start_index, curve) in subpath.iter().enumerate() {
|
||||||
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
|
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
|
||||||
|
@ -81,23 +80,24 @@ impl LayerSnapper {
|
||||||
document_curve,
|
document_curve,
|
||||||
layer,
|
layer,
|
||||||
start,
|
start,
|
||||||
target: SnapTarget::Geometry(GeometrySnapTarget::Path),
|
target: SnapTarget::Path(PathSnapTarget::AlongPath),
|
||||||
bounds: None,
|
bounds: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !snap_data.ignore_bounds(layer) {
|
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) {
|
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);
|
self.collect_paths(snap_data, !config.use_existing_candidates);
|
||||||
|
|
||||||
let document = snap_data.document;
|
let document = snap_data.document;
|
||||||
let normals = document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Normal));
|
let normals = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::NormalToPath));
|
||||||
let tangents = document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Tangent));
|
let tangents = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::TangentToPath));
|
||||||
let tolerance = snap_tolerance(document);
|
let tolerance = snap_tolerance(document);
|
||||||
|
|
||||||
for path in &self.paths_to_snap {
|
for path in &self.paths_to_snap {
|
||||||
|
@ -187,7 +187,7 @@ impl LayerSnapper {
|
||||||
return;
|
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
|
let Some(bounds) = document
|
||||||
.network_interface
|
.network_interface
|
||||||
.document_metadata()
|
.document_metadata()
|
||||||
|
@ -217,6 +217,7 @@ impl LayerSnapper {
|
||||||
get_bbox_points(quad, &mut self.points_to_snap, values, document);
|
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) {
|
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;
|
let mut best = None;
|
||||||
for candidate in &self.points_to_snap {
|
for candidate in &self.points_to_snap {
|
||||||
|
@ -251,6 +252,7 @@ impl LayerSnapper {
|
||||||
snap_results.points.push(result);
|
snap_results.points.push(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_snap(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
|
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.collect_anchors(snap_data, !config.use_existing_candidates);
|
||||||
self.snap_anchors(snap_data, point, snap_results, SnapConstraint::None, point.document_point);
|
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 {
|
snap_results.points.push(SnappedPoint {
|
||||||
snapped_point_document: normal_point,
|
snapped_point_document: normal_point,
|
||||||
target: SnapTarget::Geometry(GeometrySnapTarget::Normal),
|
target: SnapTarget::Path(PathSnapTarget::NormalToPath),
|
||||||
distance,
|
distance,
|
||||||
tolerance,
|
tolerance,
|
||||||
curves: [Some(path.document_curve), None],
|
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 {
|
snap_results.points.push(SnappedPoint {
|
||||||
snapped_point_document: tangent_point,
|
snapped_point_document: tangent_point,
|
||||||
target: SnapTarget::Geometry(GeometrySnapTarget::Tangent),
|
target: SnapTarget::Path(PathSnapTarget::TangentToPath),
|
||||||
distance,
|
distance,
|
||||||
tolerance,
|
tolerance,
|
||||||
curves: [Some(path.document_curve), None],
|
curves: [Some(path.document_curve), None],
|
||||||
|
@ -317,6 +319,7 @@ struct SnapCandidatePath {
|
||||||
target: SnapTarget,
|
target: SnapTarget,
|
||||||
bounds: Option<Quad>,
|
bounds: Option<Quad>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct SnapCandidatePoint {
|
pub struct SnapCandidatePoint {
|
||||||
pub document_point: DVec2,
|
pub document_point: DVec2,
|
||||||
|
@ -330,6 +333,7 @@ impl SnapCandidatePoint {
|
||||||
pub fn new(document_point: DVec2, source: SnapSource, target: SnapTarget) -> Self {
|
pub fn new(document_point: DVec2, source: SnapSource, target: SnapTarget) -> Self {
|
||||||
Self::new_quad(document_point, source, target, None, true)
|
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 {
|
pub fn new_quad(document_point: DVec2, source: SnapSource, target: SnapTarget, quad: Option<Quad>, alignment: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
document_point,
|
document_point,
|
||||||
|
@ -340,18 +344,22 @@ impl SnapCandidatePoint {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_source(document_point: DVec2, source: SnapSource) -> Self {
|
pub fn new_source(document_point: DVec2, source: SnapSource) -> Self {
|
||||||
Self::new(document_point, source, SnapTarget::None)
|
Self::new(document_point, source, SnapTarget::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(document_point: DVec2) -> Self {
|
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 {
|
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.neighbors = neighbors.into();
|
||||||
point
|
point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BBoxSnapValues {
|
pub struct BBoxSnapValues {
|
||||||
corner_source: SnapSource,
|
corner_source: SnapSource,
|
||||||
|
@ -363,41 +371,42 @@ pub struct BBoxSnapValues {
|
||||||
}
|
}
|
||||||
impl BBoxSnapValues {
|
impl BBoxSnapValues {
|
||||||
pub const BOUNDING_BOX: Self = Self {
|
pub const BOUNDING_BOX: Self = Self {
|
||||||
corner_source: SnapSource::BoundingBox(BoundingBoxSnapSource::Corner),
|
corner_source: SnapSource::BoundingBox(BoundingBoxSnapSource::CornerPoint),
|
||||||
corner_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::Corner),
|
corner_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::CornerPoint),
|
||||||
edge_source: SnapSource::BoundingBox(BoundingBoxSnapSource::EdgeMidpoint),
|
edge_source: SnapSource::BoundingBox(BoundingBoxSnapSource::EdgeMidpoint),
|
||||||
edge_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::EdgeMidpoint),
|
edge_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::EdgeMidpoint),
|
||||||
center_source: SnapSource::BoundingBox(BoundingBoxSnapSource::Center),
|
center_source: SnapSource::BoundingBox(BoundingBoxSnapSource::CenterPoint),
|
||||||
center_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::Center),
|
center_target: SnapTarget::BoundingBox(BoundingBoxSnapTarget::CenterPoint),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ARTBOARD: Self = Self {
|
pub const ARTBOARD: Self = Self {
|
||||||
corner_source: SnapSource::Artboard(ArtboardSnapSource::Corner),
|
corner_source: SnapSource::Artboard(ArtboardSnapSource::CornerPoint),
|
||||||
corner_target: SnapTarget::Artboard(ArtboardSnapTarget::Corner),
|
corner_target: SnapTarget::Artboard(ArtboardSnapTarget::CornerPoint),
|
||||||
edge_source: SnapSource::None,
|
edge_source: SnapSource::None,
|
||||||
edge_target: SnapTarget::None,
|
edge_target: SnapTarget::None,
|
||||||
center_source: SnapSource::Artboard(ArtboardSnapSource::Center),
|
center_source: SnapSource::Artboard(ArtboardSnapSource::CenterPoint),
|
||||||
center_target: SnapTarget::Artboard(ArtboardSnapTarget::Center),
|
center_target: SnapTarget::Artboard(ArtboardSnapTarget::CenterPoint),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ALIGN_BOUNDING_BOX: Self = Self {
|
pub const ALIGN_BOUNDING_BOX: Self = Self {
|
||||||
corner_source: SnapSource::Alignment(AlignmentSnapSource::BoundsCorner),
|
corner_source: SnapSource::Alignment(AlignmentSnapSource::BoundingBoxCornerPoint),
|
||||||
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundsCorner),
|
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundingBoxCornerPoint),
|
||||||
edge_source: SnapSource::None,
|
edge_source: SnapSource::None,
|
||||||
edge_target: SnapTarget::None,
|
edge_target: SnapTarget::None,
|
||||||
center_source: SnapSource::Alignment(AlignmentSnapSource::BoundsCenter),
|
center_source: SnapSource::Alignment(AlignmentSnapSource::BoundingBoxCenterPoint),
|
||||||
center_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundsCenter),
|
center_target: SnapTarget::Alignment(AlignmentSnapTarget::BoundingBoxCenterPoint),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ALIGN_ARTBOARD: Self = Self {
|
pub const ALIGN_ARTBOARD: Self = Self {
|
||||||
corner_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCorner),
|
corner_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCornerPoint),
|
||||||
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCorner),
|
corner_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCornerPoint),
|
||||||
edge_source: SnapSource::None,
|
edge_source: SnapSource::None,
|
||||||
edge_target: SnapTarget::None,
|
edge_target: SnapTarget::None,
|
||||||
center_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCenter),
|
center_source: SnapSource::Alignment(AlignmentSnapSource::ArtboardCenterPoint),
|
||||||
center_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCenter),
|
center_target: SnapTarget::Alignment(AlignmentSnapTarget::ArtboardCenterPoint),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
let start = quad.0[index];
|
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));
|
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) {
|
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));
|
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 {
|
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
|
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) {
|
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;
|
let document = snap_data.document;
|
||||||
|
|
||||||
// Midpoints of linear segments
|
// 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() {
|
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) {
|
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;
|
continue;
|
||||||
|
@ -434,12 +446,13 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||||
if in_handle.is_none() && out_handle.is_none() {
|
if in_handle.is_none() && out_handle.is_none() {
|
||||||
points.push(SnapCandidatePoint::new(
|
points.push(SnapCandidatePoint::new(
|
||||||
to_document.transform_point2(curve.start() * 0.5 + curve.end * 0.5),
|
to_document.transform_point2(curve.start() * 0.5 + curve.end * 0.5),
|
||||||
SnapSource::Geometry(GeometrySnapSource::LineMidpoint),
|
SnapSource::Path(PathSnapSource::LineMidpoint),
|
||||||
SnapTarget::Geometry(GeometrySnapTarget::LineMidpoint),
|
SnapTarget::Path(PathSnapTarget::LineMidpoint),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anchors
|
// Anchors
|
||||||
for (index, group) in subpath.manipulator_groups().iter().enumerate() {
|
for (index, group) in subpath.manipulator_groups().iter().enumerate() {
|
||||||
if snap_data.ignore_manipulator(layer, group.id) {
|
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);
|
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(
|
points.push(SnapCandidatePoint::new(
|
||||||
to_document.transform_point2(group.anchor),
|
to_document.transform_point2(group.anchor),
|
||||||
SnapSource::Geometry(GeometrySnapSource::AnchorWithColinearHandles),
|
SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
|
||||||
SnapTarget::Geometry(GeometrySnapTarget::AnchorWithColinearHandles),
|
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(
|
points.push(SnapCandidatePoint::new(
|
||||||
to_document.transform_point2(group.anchor),
|
to_document.transform_point2(group.anchor),
|
||||||
SnapSource::Geometry(GeometrySnapSource::AnchorWithFreeHandles),
|
SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),
|
||||||
SnapTarget::Geometry(GeometrySnapTarget::AnchorWithFreeHandles),
|
SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl SnappedPoint {
|
||||||
Self {
|
Self {
|
||||||
snapped_point_document: point.document_point + translation,
|
snapped_point_document: point.document_point + translation,
|
||||||
source: point.source,
|
source: point.source,
|
||||||
target: SnapTarget::Distribution(target),
|
target: SnapTarget::DistributeEvenly(target),
|
||||||
distribution_boxes_x,
|
distribution_boxes_x,
|
||||||
distribution_equal_distance_x: is_x.then_some(distances.equal),
|
distribution_equal_distance_x: is_x.then_some(distances.equal),
|
||||||
distribution_boxes_y,
|
distribution_boxes_y,
|
||||||
|
|
|
@ -1340,7 +1340,7 @@ fn get_selected_faces<'a>(predicate: &'a impl Fn(u8) -> bool, flags: &'a HashMap
|
||||||
|
|
||||||
fn walk_faces<'a>(faces: &'a [DualVertexKey], edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, vertices: &SlotMap<DualVertexKey, DualGraphVertex>) -> impl Iterator<Item = PathSegment> + 'a {
|
fn walk_faces<'a>(faces: &'a [DualVertexKey], edges: &SlotMap<DualEdgeKey, DualGraphHalfEdge>, vertices: &SlotMap<DualVertexKey, DualGraphVertex>) -> impl Iterator<Item = PathSegment> + 'a {
|
||||||
let face_set: HashSet<_> = faces.iter().copied().collect();
|
let face_set: HashSet<_> = faces.iter().copied().collect();
|
||||||
// TODO: Try using a binary search to avioid the hashset construction
|
// TODO: Try using a binary search to avoid the hashset construction
|
||||||
let is_removed_edge = |edge: &DualGraphHalfEdge| face_set.contains(&edge.incident_vertex) == face_set.contains(&edges[edge.twin.unwrap()].incident_vertex);
|
let is_removed_edge = |edge: &DualGraphHalfEdge| face_set.contains(&edge.incident_vertex) == face_set.contains(&edges[edge.twin.unwrap()].incident_vertex);
|
||||||
|
|
||||||
let mut edge_to_next = HashMap::new();
|
let mut edge_to_next = HashMap::new();
|
||||||
|
@ -1481,7 +1481,7 @@ impl Display for BooleanError {
|
||||||
match self {
|
match self {
|
||||||
Self::MultipleOuterFaces => f.write_str("Found multiple candidates for the outer face in a connected component of the dual graph."),
|
Self::MultipleOuterFaces => f.write_str("Found multiple candidates for the outer face in a connected component of the dual graph."),
|
||||||
Self::NoEarInPolygon => f.write_str("Failed to compute winding order for one of the faces, this usually happens when the polygon is malformed."),
|
Self::NoEarInPolygon => f.write_str("Failed to compute winding order for one of the faces, this usually happens when the polygon is malformed."),
|
||||||
Self::InvalidPathCommand(cmd) => f.write_fmt(format_args!("Encountered a '{cmd}' while parsing the svg data which was not recogniezed")),
|
Self::InvalidPathCommand(cmd) => f.write_fmt(format_args!("Encountered a '{cmd}' while parsing the svg data which was not recognized")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,7 +282,7 @@ impl Default for VectorData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curviture).
|
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum ManipulatorPointId {
|
pub enum ManipulatorPointId {
|
||||||
|
|
|
@ -55,7 +55,7 @@ The right side of the control bar has controls related to the active document an
|
||||||
| | |
|
| | |
|
||||||
|-|-|
|
|-|-|
|
||||||
| Overlays | <p>When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the temporary contextual visualizations (like bounding boxes and vector manipulators) that are usually blue and appear atop the viewport when using tools.</p> |
|
| Overlays | <p>When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the temporary contextual visualizations (like bounding boxes and vector manipulators) that are usually blue and appear atop the viewport when using tools.</p> |
|
||||||
| Snapping | <p>When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/snapping-popover__3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>Snapping options relating to **Bounding Boxes**:</p><p><ul><li>**Box Center** enables snapping to the center point of any layer's bounding box.</li><li>**Box Corner** enables snapping to the four corners of any layer's bounding box.</li><li>**Along Edge** enables snapping anywhere along the four edges of any layer's bounding box.</li><li>**Midpoint of Edge** enables snapping to any of the four points at the middle of the edges of any layer's bounding box.</li><li>**Align to Box** enables snapping to anywhere outside any layer's bounding box along the four lines extending outward from its edges.</li><li>**Evenly Distribute Boxes** enables snapping to a consistent distance offset established by the bounding boxes of nearby layers (due to a bug, **Box Center** and **Box Corner** must be enabled).</li></ul></p><p>Snapping options relating to **Geometry**:</p><p><ul><li>**Anchor** enables snapping to the anchor point of any vector path.</li><li>**Line Midpoint** enables snapping to the point at the middle of any straight line segment of a vector path.</li><li>**Path** enables snapping along the length of any vector path.</li><li>**Normal to Path** enables snapping a line to a point perpendicular to a vector path (due to a bug, **Intersection** must be enabled).</li><li>**Tangent to Path** enables snapping a line to a point tangent to a vector path (due to a bug, **Intersection** must be enabled).</li><li>**Intersection** enables snapping to any points where vector paths intersect.</li><li>**Align to Selected Path** enables snapping to the handle control points of a selected vector layer.</li></ul></p> |
|
| Snapping | <p>When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/snapping-popover__4.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>Snapping options relating to **Bounding Boxes**:</p><p><ul><li>**Align with Corner Points**: Snaps to horizontal/vertical alignment with the corner points of any layer's bounding box.</li><li>**Corner Points**: Snaps to the four corners of any layer's bounding box.</li><li>**Center Points**: Snaps to the center point of any layer's bounding box.</li><li>**Edge Midpoints**: Snaps to any of the four points at the middle of the edges of any layer's bounding box.</li><li>**Along Edges**: Snaps anywhere along the four edges of any layer's bounding box.</li><li>**Distribute Evenly**: Snaps to a consistent distance offset established by the bounding boxes of nearby layers (due to a bug, **Center Points** and **Corner Points** must be enabled).</li></ul></p><p>Snapping options relating to **Paths**:</p><p><ul><li>**Align with Anchor Points**: Snaps to horizontal/vertical alignment with the anchor points of any vector path.</li><li>**Anchor Points**: Snaps to the anchor point of any vector path.</li><li>**Line Midpoints**: Snaps to the point at the middle of any straight line segment of a vector path.</li><li>**Path Intersection Points**: Snaps to any points where vector paths intersect.</li><li>**Along Paths**: Snaps along the length of any vector path.</li><li>**Normal to Paths**: Snaps a line to a point perpendicular to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li><li>**Tangent to Paths**: Snaps a line to a point tangent to a vector path (due to a bug, **Intersections of Paths** must be enabled).</li></ul></p> |
|
||||||
| Grid | <p>When checked (off by default), grid lines are shown and snapping to them becomes active. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.</p><ul><li><p>**Type** sets whether the grid pattern is made of squares or triangles.</p><p>**Rectangular** is a pattern of horizontal and vertical lines:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-rectangular-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has one option unique to this mode:</p><ul><li>**Spacing** is the width and height of the rectangle grid cells.</li></ul><p>**Isometric** is a pattern of triangles:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-isometric-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has two options unique to this mode:</p><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li><li>**Display** gives control over the appearance of the grid. The **Display as dotted grid** checkbox (off by default) replaces the solid lines with dots at their intersection points.</li><li>**Origin** is the position in the canvas where the repeating grid pattern begins from. If you need an offset for the grid where an intersection occurs at a specific location, set those coordinates.</li></ul> |
|
| Grid | <p>When checked (off by default), grid lines are shown and snapping to them becomes active. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.</p><ul><li><p>**Type** sets whether the grid pattern is made of squares or triangles.</p><p>**Rectangular** is a pattern of horizontal and vertical lines:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-rectangular-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has one option unique to this mode:</p><ul><li>**Spacing** is the width and height of the rectangle grid cells.</li></ul><p>**Isometric** is a pattern of triangles:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-isometric-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has two options unique to this mode:</p><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li><li>**Display** gives control over the appearance of the grid. The **Display as dotted grid** checkbox (off by default) replaces the solid lines with dots at their intersection points.</li><li>**Origin** is the position in the canvas where the repeating grid pattern begins from. If you need an offset for the grid where an intersection occurs at a specific location, set those coordinates.</li></ul> |
|
||||||
| View Mode | <p>**Normal** (default): The artwork is rendered normally.</p><p>**Outline**: The artwork is rendered as a wireframe.</p><p>**Pixels**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level.</p> |
|
| View Mode | <p>**Normal** (default): The artwork is rendered normally.</p><p>**Outline**: The artwork is rendered as a wireframe.</p><p>**Pixels**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level.</p> |
|
||||||
| Zoom In | <p>Zooms the viewport in to the next whole increment.</p> |
|
| Zoom In | <p>Zooms the viewport in to the next whole increment.</p> |
|
||||||
|
|
|
@ -50,7 +50,7 @@ body {
|
||||||
|
|
||||||
@media screen and (max-width: 780px) {
|
@media screen and (max-width: 780px) {
|
||||||
:root {
|
:root {
|
||||||
--font-size-link: calc(1rem * 24 / 18);
|
--font-size-link: calc(1rem * 4 / 3);
|
||||||
--page-edge-padding: 28px;
|
--page-edge-padding: 28px;
|
||||||
--border-thickness: 1px;
|
--border-thickness: 1px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue