mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Add snapping targets for b-box edges and multi-layer spacing distribution (#1793)
* Initial work on aligning bounding boxes * Work in progress distribution * Distribution snapping * Distribution overlays * Align points and clean up * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
mod quad;
|
||||
mod rect;
|
||||
pub use quad::Quad;
|
||||
pub use rect::Rect;
|
||||
|
||||
use crate::raster::bbox::Bbox;
|
||||
use crate::raster::{BlendMode, Image, ImageFrame};
|
||||
|
||||
@@ -16,6 +16,11 @@ impl Quad {
|
||||
Self([bbox[0], bbox[0] + size * DVec2::X, bbox[1], bbox[0] + size * DVec2::Y])
|
||||
}
|
||||
|
||||
/// Create a quad from the center and offset (distance from center to middle of an edge)
|
||||
pub fn from_square(center: DVec2, offset: f64) -> Self {
|
||||
Self::from_box([center - offset, center + offset])
|
||||
}
|
||||
|
||||
/// Get all the edges in the quad.
|
||||
pub fn edges(&self) -> [[DVec2; 2]; 4] {
|
||||
[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]
|
||||
|
||||
125
node-graph/gcore/src/graphic_element/renderer/rect.rs
Normal file
125
node-graph/gcore/src/graphic_element/renderer/rect.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
use super::Quad;
|
||||
|
||||
#[derive(Debug, Clone, Default, Copy, PartialEq)]
|
||||
/// An axis aligned rect defined by two vertices.
|
||||
pub struct Rect(pub [DVec2; 2]);
|
||||
|
||||
impl Rect {
|
||||
/// Create a zero sized quad at the point
|
||||
#[must_use]
|
||||
pub fn from_point(point: DVec2) -> Self {
|
||||
Self([point; 2])
|
||||
}
|
||||
|
||||
/// Convert a box defined by two corner points to a quad.
|
||||
#[must_use]
|
||||
pub fn from_box(bbox: [DVec2; 2]) -> Self {
|
||||
Self([bbox[0].min(bbox[1]), bbox[0].max(bbox[1])])
|
||||
}
|
||||
|
||||
/// Create a quad from the center and offset (distance from center to middle of an edge)
|
||||
#[must_use]
|
||||
pub fn from_square(center: DVec2, offset: f64) -> Self {
|
||||
Self::from_box([center - offset, center + offset])
|
||||
}
|
||||
|
||||
/// Create an AABB from an iter of points, returning None if empty.
|
||||
#[must_use]
|
||||
pub fn point_iter(points: impl Iterator<Item = DVec2>) -> Option<Self> {
|
||||
let mut bounds = None;
|
||||
for point in points {
|
||||
let bounds = bounds.get_or_insert(Self::from_point(point));
|
||||
bounds[0] = bounds[0].min(point);
|
||||
bounds[1] = bounds[1].max(point);
|
||||
}
|
||||
bounds
|
||||
}
|
||||
|
||||
/// Get all the edges in the rect.
|
||||
#[must_use]
|
||||
pub fn edges(&self) -> [[DVec2; 2]; 4] {
|
||||
let corners = [self[0], DVec2::new(self[0].x, self[1].y), self[1], DVec2::new(self[1].y, self[0].x)];
|
||||
[[corners[0], corners[1]], [corners[1], corners[2]], [corners[2], corners[3]], [corners[3], corners[0]]]
|
||||
}
|
||||
|
||||
/// Get all the edges in the rect as linear bezier curves
|
||||
pub fn bezier_lines(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||
self.edges().into_iter().map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
|
||||
}
|
||||
|
||||
/// Gets the center of a rect
|
||||
#[must_use]
|
||||
pub fn center(&self) -> DVec2 {
|
||||
self.0.iter().sum::<DVec2>() / 2.
|
||||
}
|
||||
|
||||
/// Take the outside bounds of two axis aligned rectangles, which are defined by two corner points.
|
||||
#[must_use]
|
||||
pub fn combine_bounds(a: Self, b: Self) -> Self {
|
||||
Self::from_box([a[0].min(b[0]), a[1].max(b[1])])
|
||||
}
|
||||
|
||||
/// Expand a rect by a certain amount on top/bottom and on left/right
|
||||
#[must_use]
|
||||
pub fn expand_by(&self, x: f64, y: f64) -> Self {
|
||||
let delta = DVec2::new(x, y);
|
||||
Self::from_box([self[0] - delta, self[1] + delta])
|
||||
}
|
||||
|
||||
/// Expand a rect by a certain amount on top/bottom and on left/right
|
||||
#[must_use]
|
||||
pub fn intersects(&self, other: Self) -> bool {
|
||||
let [mina, maxa] = [self[0].min(self[1]), self[0].max(self[1])];
|
||||
let [minb, maxb] = [other[0].min(other[1]), other[0].max(other[1])];
|
||||
mina.x <= maxb.x && minb.x <= maxa.x && mina.y <= maxb.y && minb.y <= maxa.y
|
||||
}
|
||||
|
||||
/// Does this rect contain a point
|
||||
#[must_use]
|
||||
pub fn contains(&self, p: DVec2) -> bool {
|
||||
(self[0].x < p.x && p.x < self[1].x) && (self[0].y < p.y && p.y < self[1].y)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn min(&self) -> DVec2 {
|
||||
self.0[0].min(self.0[1])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn max(&self) -> DVec2 {
|
||||
self.0[0].max(self.0[1])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn translate(&self, offset: DVec2) -> Self {
|
||||
Self([self.0[0] + offset, self.0[1] + offset])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul<Rect> for DAffine2 {
|
||||
type Output = super::Quad;
|
||||
|
||||
fn mul(self, rhs: Rect) -> Self::Output {
|
||||
self * super::Quad::from_box(rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Index<usize> for Rect {
|
||||
type Output = DVec2;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
impl core::ops::IndexMut<usize> for Rect {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Quad> for Rect {
|
||||
fn into(self) -> Quad {
|
||||
Quad::from_box(self.0)
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,34 @@ macro_rules! create_ids {
|
||||
|
||||
create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }
|
||||
|
||||
/// A no-op hasher that allows writing u64s (the id type).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct NoHash(Option<u64>);
|
||||
|
||||
impl core::hash::Hasher for NoHash {
|
||||
fn finish(&self) -> u64 {
|
||||
self.0.unwrap()
|
||||
}
|
||||
fn write(&mut self, _bytes: &[u8]) {
|
||||
unimplemented!()
|
||||
}
|
||||
fn write_u64(&mut self, i: u64) {
|
||||
debug_assert!(self.0.is_none());
|
||||
self.0 = Some(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// A hash builder that builds the [`NoHash`] hasher.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct NoHashBuilder;
|
||||
|
||||
impl core::hash::BuildHasher for NoHashBuilder {
|
||||
type Hasher = NoHash;
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
NoHash::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
|
||||
|
||||
Reference in New Issue
Block a user