mirror of
https://github.com/penpot/penpot.git
synced 2025-05-15 22:16:37 +02:00
✨ Add constraints calculation on WASM (#5894)
* ✨ Add constraints calculation on WASM * ✨ Fix after review
This commit is contained in:
parent
f5c913d26e
commit
6cb1aa24cd
15 changed files with 746 additions and 76 deletions
|
@ -106,8 +106,22 @@
|
||||||
[clip-content]
|
[clip-content]
|
||||||
(h/call internal-module "_set_shape_clip_content" clip-content))
|
(h/call internal-module "_set_shape_clip_content" clip-content))
|
||||||
|
|
||||||
|
(defn- translate-shape-type
|
||||||
|
[type]
|
||||||
|
(case type
|
||||||
|
:frame 0
|
||||||
|
:group 1
|
||||||
|
:bool 2
|
||||||
|
:rect 3
|
||||||
|
:path 4
|
||||||
|
:text 5
|
||||||
|
:circle 6
|
||||||
|
:svg-raw 7
|
||||||
|
:image 8))
|
||||||
|
|
||||||
(defn set-shape-type
|
(defn set-shape-type
|
||||||
[type {:keys [masked]}]
|
[type {:keys [masked]}]
|
||||||
|
(h/call internal-module "_set_shape_type" (translate-shape-type type))
|
||||||
(cond
|
(cond
|
||||||
(= type :circle)
|
(= type :circle)
|
||||||
(h/call internal-module "_set_shape_kind_circle")
|
(h/call internal-module "_set_shape_kind_circle")
|
||||||
|
@ -541,6 +555,8 @@
|
||||||
type (dm/get-prop shape :type)
|
type (dm/get-prop shape :type)
|
||||||
masked (dm/get-prop shape :masked-group)
|
masked (dm/get-prop shape :masked-group)
|
||||||
selrect (dm/get-prop shape :selrect)
|
selrect (dm/get-prop shape :selrect)
|
||||||
|
constraint-h (dm/get-prop shape :constraints-h)
|
||||||
|
constraint-v (dm/get-prop shape :constraints-v)
|
||||||
clip-content (if (= type :frame)
|
clip-content (if (= type :frame)
|
||||||
(not (dm/get-prop shape :show-content))
|
(not (dm/get-prop shape :show-content))
|
||||||
false)
|
false)
|
||||||
|
@ -569,6 +585,8 @@
|
||||||
(set-shape-type type {:masked masked})
|
(set-shape-type type {:masked masked})
|
||||||
(set-shape-clip-content clip-content)
|
(set-shape-clip-content clip-content)
|
||||||
(set-shape-selrect selrect)
|
(set-shape-selrect selrect)
|
||||||
|
(set-constraints-h constraint-h)
|
||||||
|
(set-constraints-v constraint-v)
|
||||||
(set-shape-rotation rotation)
|
(set-shape-rotation rotation)
|
||||||
(set-shape-transform transform)
|
(set-shape-transform transform)
|
||||||
(set-shape-blend-mode blend-mode)
|
(set-shape-blend-mode blend-mode)
|
||||||
|
|
|
@ -1,5 +1,51 @@
|
||||||
# Serialization
|
# Serialization
|
||||||
|
|
||||||
|
## Shape Type
|
||||||
|
|
||||||
|
Shape types are serialized as `u8`:
|
||||||
|
|
||||||
|
| Value | Field |
|
||||||
|
| ----- | ---------- |
|
||||||
|
| 0 | Frame |
|
||||||
|
| 1 | Group |
|
||||||
|
| 2 | Bool |
|
||||||
|
| 3 | Rect |
|
||||||
|
| 4 | Path |
|
||||||
|
| 5 | Text |
|
||||||
|
| 6 | Circle |
|
||||||
|
| 7 | SvgRaw |
|
||||||
|
| 8 | Image |
|
||||||
|
| \_ | Rect |
|
||||||
|
|
||||||
|
|
||||||
|
## Horizontal Constraint
|
||||||
|
|
||||||
|
Horizontal constraints are serialized as `u8`:
|
||||||
|
|
||||||
|
| Value | Field |
|
||||||
|
| ----- | --------- |
|
||||||
|
| 0 | Left |
|
||||||
|
| 1 | Right |
|
||||||
|
| 2 | LeftRight |
|
||||||
|
| 3 | Center |
|
||||||
|
| 4 | Scale |
|
||||||
|
| \_ | None |
|
||||||
|
|
||||||
|
|
||||||
|
## Vertical Constraint
|
||||||
|
|
||||||
|
Vertical constraints are serialized as `u8`:
|
||||||
|
|
||||||
|
| Value | Field |
|
||||||
|
| ----- | --------- |
|
||||||
|
| 0 | Top |
|
||||||
|
| 1 | Bottom |
|
||||||
|
| 2 | TopBottom |
|
||||||
|
| 3 | Center |
|
||||||
|
| 4 | Scale |
|
||||||
|
| \_ | None |
|
||||||
|
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
|
|
||||||
Paths are made of segments of **28 bytes** each. The layout (assuming positions in a `Uint8Array`) is the following:
|
Paths are made of segments of **28 bytes** each. The layout (assuming positions in a `Uint8Array`) is the following:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use skia::Rect;
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
|
@ -10,7 +11,7 @@ mod utils;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
use crate::mem::SerializableResult;
|
use crate::mem::SerializableResult;
|
||||||
use crate::shapes::{BoolType, ConstraintH, ConstraintV, Group, Kind, Path, TransformEntry};
|
use crate::shapes::{BoolType, ConstraintH, ConstraintV, Group, Kind, Path, TransformEntry, Type};
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
use crate::utils::uuid_from_u32_quartet;
|
use crate::utils::uuid_from_u32_quartet;
|
||||||
|
@ -142,7 +143,7 @@ pub extern "C" fn set_shape_kind_circle() {
|
||||||
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
||||||
|
|
||||||
if let Some(shape) = state.current_shape() {
|
if let Some(shape) = state.current_shape() {
|
||||||
shape.set_kind(Kind::Circle(math::Rect::new_empty()));
|
shape.set_kind(Kind::Circle(Rect::new_empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ pub extern "C" fn set_shape_kind_rect() {
|
||||||
if let Some(shape) = state.current_shape() {
|
if let Some(shape) = state.current_shape() {
|
||||||
match shape.kind() {
|
match shape.kind() {
|
||||||
Kind::Rect(_, _) => {}
|
Kind::Rect(_, _) => {}
|
||||||
_ => shape.set_kind(Kind::Rect(math::Rect::new_empty(), None)),
|
_ => shape.set_kind(Kind::Rect(Rect::new_empty(), None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,6 +186,15 @@ pub extern "C" fn set_shape_bool_type(raw_bool_type: u8) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn set_shape_type(shape_type: u8) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
||||||
|
|
||||||
|
if let Some(shape) = state.current_shape() {
|
||||||
|
shape.set_shape_type(Type::from(shape_type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
|
||||||
|
|
|
@ -1,4 +1,330 @@
|
||||||
use skia_safe as skia;
|
use skia_safe::{Matrix, Point, Vector};
|
||||||
|
|
||||||
pub type Rect = skia::Rect;
|
pub trait VectorExt {
|
||||||
pub type Point = (f32, f32);
|
fn new_points(a: &Point, b: &Point) -> Vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VectorExt for Vector {
|
||||||
|
// Creates a vector from two points
|
||||||
|
fn new_points(from: &Point, to: &Point) -> Vector {
|
||||||
|
Vector::new(to.x - from.x, to.y - from.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Bounds {
|
||||||
|
pub nw: Point,
|
||||||
|
pub ne: Point,
|
||||||
|
pub se: Point,
|
||||||
|
pub sw: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vec_min_max(arr: &[Option<f32>]) -> Option<(f32, f32)> {
|
||||||
|
let mut minv: Option<f32> = None;
|
||||||
|
let mut maxv: Option<f32> = None;
|
||||||
|
|
||||||
|
for it in arr {
|
||||||
|
if let Some(it) = *it {
|
||||||
|
match minv {
|
||||||
|
None => minv = Some(it),
|
||||||
|
Some(n) => minv = Some(f32::min(it, n)),
|
||||||
|
}
|
||||||
|
match maxv {
|
||||||
|
None => maxv = Some(it),
|
||||||
|
Some(n) => maxv = Some(f32::max(it, n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((minv?, maxv?))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bounds {
|
||||||
|
pub fn new(nw: Point, ne: Point, se: Point, sw: Point) -> Self {
|
||||||
|
Self { nw, ne, se, sw }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn horizontal_vec(&self) -> Vector {
|
||||||
|
Vector::new_points(&self.nw, &self.ne)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertical_vec(&self) -> Vector {
|
||||||
|
Vector::new_points(&self.nw, &self.sw)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hv(&self, scalar: f32) -> Vector {
|
||||||
|
let mut hv = self.horizontal_vec();
|
||||||
|
hv.normalize();
|
||||||
|
hv.scale(scalar);
|
||||||
|
hv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vv(&self, scalar: f32) -> Vector {
|
||||||
|
let mut vv = self.vertical_vec();
|
||||||
|
vv.normalize();
|
||||||
|
vv.scale(scalar);
|
||||||
|
vv
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> f32 {
|
||||||
|
Point::distance(self.nw, self.ne)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> f32 {
|
||||||
|
Point::distance(self.nw, self.sw)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform(&self, mtx: &Matrix) -> Self {
|
||||||
|
Self {
|
||||||
|
nw: mtx.map_point(self.nw),
|
||||||
|
ne: mtx.map_point(self.ne),
|
||||||
|
se: mtx.map_point(self.se),
|
||||||
|
sw: mtx.map_point(self.sw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_mut(&mut self, mtx: &Matrix) {
|
||||||
|
self.nw = mtx.map_point(self.nw);
|
||||||
|
self.ne = mtx.map_point(self.ne);
|
||||||
|
self.se = mtx.map_point(self.se);
|
||||||
|
self.sw = mtx.map_point(self.sw);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn box_bounds(&self, other: &Self) -> Option<Self> {
|
||||||
|
let hv = self.horizontal_vec();
|
||||||
|
let vv = self.vertical_vec();
|
||||||
|
|
||||||
|
let hr = Ray::new(self.nw, hv);
|
||||||
|
let vr = Ray::new(self.nw, vv);
|
||||||
|
|
||||||
|
let (min_ht, max_ht) = vec_min_max(&[
|
||||||
|
intersect_rays_t(&hr, &Ray::new(other.nw, vv)),
|
||||||
|
intersect_rays_t(&hr, &Ray::new(other.ne, vv)),
|
||||||
|
intersect_rays_t(&hr, &Ray::new(other.sw, vv)),
|
||||||
|
intersect_rays_t(&hr, &Ray::new(other.se, vv)),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
let (min_vt, max_vt) = vec_min_max(&[
|
||||||
|
intersect_rays_t(&vr, &Ray::new(other.nw, hv)),
|
||||||
|
intersect_rays_t(&vr, &Ray::new(other.ne, hv)),
|
||||||
|
intersect_rays_t(&vr, &Ray::new(other.sw, hv)),
|
||||||
|
intersect_rays_t(&vr, &Ray::new(other.se, hv)),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
let nw = intersect_rays(&Ray::new(hr.t(min_ht), vv), &Ray::new(vr.t(min_vt), hv))?;
|
||||||
|
let ne = intersect_rays(&Ray::new(hr.t(max_ht), vv), &Ray::new(vr.t(min_vt), hv))?;
|
||||||
|
let sw = intersect_rays(&Ray::new(hr.t(min_ht), vv), &Ray::new(vr.t(max_vt), hv))?;
|
||||||
|
let se = intersect_rays(&Ray::new(hr.t(max_ht), vv), &Ray::new(vr.t(max_vt), hv))?;
|
||||||
|
|
||||||
|
Some(Self { nw, ne, se, sw })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left(&self, p: Point) -> f32 {
|
||||||
|
let hr = Ray::new(p, self.horizontal_vec());
|
||||||
|
let vr = Ray::new(self.nw, self.vertical_vec());
|
||||||
|
if let Some(project_point) = intersect_rays(&hr, &vr) {
|
||||||
|
if vr.is_positive_side(&p) {
|
||||||
|
-Point::distance(project_point, p)
|
||||||
|
} else {
|
||||||
|
Point::distance(project_point, p)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This should not happen. All points should have a proyection so the
|
||||||
|
// intersection ray should always exist
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right(&self, p: Point) -> f32 {
|
||||||
|
let hr = Ray::new(p, self.horizontal_vec());
|
||||||
|
let vr = Ray::new(self.ne, self.vertical_vec());
|
||||||
|
if let Some(project_point) = intersect_rays(&hr, &vr) {
|
||||||
|
if vr.is_positive_side(&p) {
|
||||||
|
Point::distance(project_point, p)
|
||||||
|
} else {
|
||||||
|
-Point::distance(project_point, p)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This should not happen. All points should have a proyection so the
|
||||||
|
// intersection ray should always exist
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top(&self, p: Point) -> f32 {
|
||||||
|
let vr = Ray::new(p, self.vertical_vec());
|
||||||
|
let hr = Ray::new(self.nw, self.horizontal_vec());
|
||||||
|
if let Some(project_point) = intersect_rays(&vr, &hr) {
|
||||||
|
if hr.is_positive_side(&p) {
|
||||||
|
Point::distance(project_point, p)
|
||||||
|
} else {
|
||||||
|
-Point::distance(project_point, p)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This should not happen. All points should have a proyection so the
|
||||||
|
// intersection ray should always exist
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bottom(&self, p: Point) -> f32 {
|
||||||
|
let vr = Ray::new(p, self.vertical_vec());
|
||||||
|
let hr = Ray::new(self.sw, self.horizontal_vec());
|
||||||
|
if let Some(project_point) = intersect_rays(&vr, &hr) {
|
||||||
|
if hr.is_positive_side(&p) {
|
||||||
|
-Point::distance(project_point, p)
|
||||||
|
} else {
|
||||||
|
Point::distance(project_point, p)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This should not happen. All points should have a proyection so the
|
||||||
|
// intersection ray should always exist
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Ray {
|
||||||
|
origin: Point,
|
||||||
|
direction: Vector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ray {
|
||||||
|
pub fn new(origin: Point, direction: Vector) -> Self {
|
||||||
|
Self { origin, direction }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn t(&self, t: f32) -> Point {
|
||||||
|
self.origin + self.direction * t
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_positive_side(&self, p: &Point) -> bool {
|
||||||
|
let a = self.direction.y;
|
||||||
|
let b = -self.direction.x;
|
||||||
|
let c = self.direction.x * self.origin.y - self.direction.y * self.origin.x;
|
||||||
|
let v = p.x * a + p.y * b + c;
|
||||||
|
v < 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersect_rays_t(ray1: &Ray, ray2: &Ray) -> Option<f32> {
|
||||||
|
let p1 = ray1.origin;
|
||||||
|
let d1 = ray1.direction;
|
||||||
|
let p2 = ray2.origin;
|
||||||
|
let d2 = ray2.direction;
|
||||||
|
|
||||||
|
// Calculate the determinant to check if the rays are parallel
|
||||||
|
let determinant = d1.cross(d2);
|
||||||
|
if determinant.abs() < f32::EPSILON {
|
||||||
|
// Parallel rays, no intersection
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve for t1 and t2 parameters
|
||||||
|
let diff = p2 - p1;
|
||||||
|
|
||||||
|
Some(diff.cross(d2) / determinant)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersect_rays(ray1: &Ray, ray2: &Ray) -> Option<Point> {
|
||||||
|
if let Some(t) = intersect_rays_t(ray1, ray2) {
|
||||||
|
Some(ray1.t(t))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ray_parameter() {
|
||||||
|
let r = Ray::new(Point::new(0.0, 0.0), Vector::new(0.5, 0.5));
|
||||||
|
assert_eq!(r.t(1.0), Point::new(0.5, 0.5));
|
||||||
|
assert_eq!(r.t(2.0), Point::new(1.0, 1.0));
|
||||||
|
assert_eq!(r.t(-2.0), Point::new(-1.0, -1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intersect() {
|
||||||
|
// Test Cases for Ray-Ray Intersections
|
||||||
|
// Simple Intersection at (2, 2)
|
||||||
|
let r1 = Ray::new(Point::new(0.0, 0.0), Vector::new(1.0, 1.0));
|
||||||
|
let r2 = Ray::new(Point::new(0.0, 4.0), Vector::new(1.0, -1.0));
|
||||||
|
assert_eq!(intersect_rays(&r1, &r2), Some(Point::new(2.0, 2.0)));
|
||||||
|
|
||||||
|
// Parallel Rays (No Intersection)
|
||||||
|
let r1 = Ray::new(Point::new(0.0, 0.0), Vector::new(1.0, 1.0));
|
||||||
|
let r2 = Ray::new(Point::new(0.0, 2.0), Vector::new(1.0, 1.0));
|
||||||
|
assert_eq!(intersect_rays(&r1, &r2), None);
|
||||||
|
|
||||||
|
// Coincident Rays (Infinite Intersections)
|
||||||
|
let r1 = Ray::new(Point::new(0.0, 0.0), Vector::new(1.0, 1.0));
|
||||||
|
let r2 = Ray::new(Point::new(1.0, 1.0), Vector::new(1.0, 1.0));
|
||||||
|
assert_eq!(intersect_rays(&r1, &r2), None);
|
||||||
|
|
||||||
|
let r1 = Ray::new(Point::new(1.0, 0.0), Vector::new(2.0, 1.0));
|
||||||
|
let r2 = Ray::new(Point::new(4.0, 4.0), Vector::new(-1.0, -1.0));
|
||||||
|
assert_eq!(intersect_rays(&r1, &r2), Some(Point::new(-1.0, -1.0)));
|
||||||
|
|
||||||
|
let r1 = Ray::new(Point::new(1.0, 1.0), Vector::new(3.0, 2.0));
|
||||||
|
let r2 = Ray::new(Point::new(4.0, 0.0), Vector::new(-2.0, 3.0));
|
||||||
|
assert_eq!(
|
||||||
|
intersect_rays(&r1, &r2),
|
||||||
|
Some(Point::new(2.6153846, 2.0769231))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vec_min_max() {
|
||||||
|
assert_eq!(None, vec_min_max(&[]));
|
||||||
|
assert_eq!(None, vec_min_max(&[None, None]));
|
||||||
|
assert_eq!(Some((1.0, 1.0)), vec_min_max(&[None, Some(1.0)]));
|
||||||
|
assert_eq!(
|
||||||
|
Some((0.0, 1.0)),
|
||||||
|
vec_min_max(&[Some(0.3), None, Some(0.0), Some(0.7), Some(1.0), Some(0.1)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_box_bounds() {
|
||||||
|
let b1 = Bounds::new(
|
||||||
|
Point::new(1.0, 5.0),
|
||||||
|
Point::new(5.0, 5.0),
|
||||||
|
Point::new(5.0, 1.0),
|
||||||
|
Point::new(1.0, 1.0),
|
||||||
|
);
|
||||||
|
let b2 = Bounds::new(
|
||||||
|
Point::new(3.0, 4.0),
|
||||||
|
Point::new(4.0, 3.0),
|
||||||
|
Point::new(3.0, 2.0),
|
||||||
|
Point::new(2.0, 3.0),
|
||||||
|
);
|
||||||
|
let result = b1.box_bounds(&b2);
|
||||||
|
assert_eq!(
|
||||||
|
Some(Bounds::new(
|
||||||
|
Point::new(2.0, 4.0),
|
||||||
|
Point::new(4.0, 4.0),
|
||||||
|
Point::new(4.0, 2.0),
|
||||||
|
Point::new(2.0, 2.0),
|
||||||
|
)),
|
||||||
|
result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bounds_distances() {
|
||||||
|
let b1 = Bounds::new(
|
||||||
|
Point::new(1.0, 10.0),
|
||||||
|
Point::new(8.0, 10.0),
|
||||||
|
Point::new(8.0, 1.0),
|
||||||
|
Point::new(1.0, 1.0),
|
||||||
|
);
|
||||||
|
assert_eq!(b1.left(Point::new(4.0, 8.0)), -3.0);
|
||||||
|
assert_eq!(b1.top(Point::new(4.0, 8.0)), -2.0);
|
||||||
|
assert_eq!(b1.right(Point::new(7.0, 6.0),), -1.0);
|
||||||
|
assert_eq!(b1.bottom(Point::new(7.0, 6.0),), -5.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use skia_safe as skia;
|
use skia_safe::{self as skia, Contains, Matrix, Rect};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::math;
|
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
use skia::{Contains, Matrix};
|
|
||||||
|
|
||||||
mod blend;
|
mod blend;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
@ -45,7 +43,7 @@ pub struct NodeRenderState {
|
||||||
// We use this bool to keep that we've traversed all the children inside this node.
|
// We use this bool to keep that we've traversed all the children inside this node.
|
||||||
pub visited_children: bool,
|
pub visited_children: bool,
|
||||||
// This is used to clip the content of frames.
|
// This is used to clip the content of frames.
|
||||||
pub clip_bounds: Option<math::Rect>,
|
pub clip_bounds: Option<(Rect, Matrix)>,
|
||||||
// This is a flag to indicate that we've already drawn the mask of a masked group.
|
// This is a flag to indicate that we've already drawn the mask of a masked group.
|
||||||
pub visited_mask: bool,
|
pub visited_mask: bool,
|
||||||
// This bool indicates that we're drawing the mask shape.
|
// This bool indicates that we're drawing the mask shape.
|
||||||
|
@ -273,27 +271,39 @@ impl RenderState {
|
||||||
&mut self,
|
&mut self,
|
||||||
shape: &mut Shape,
|
shape: &mut Shape,
|
||||||
modifiers: Option<&Matrix>,
|
modifiers: Option<&Matrix>,
|
||||||
clip_bounds: Option<skia::Rect>,
|
clip_bounds: Option<(Rect, Matrix)>,
|
||||||
) {
|
) {
|
||||||
if let Some(modifiers) = modifiers {
|
if let Some((bounds, transform)) = clip_bounds {
|
||||||
self.drawing_surface.canvas().concat(&modifiers);
|
self.drawing_surface.canvas().concat(&transform);
|
||||||
}
|
self.drawing_surface
|
||||||
|
.canvas()
|
||||||
|
.clip_rect(bounds, skia::ClipOp::Intersect, true);
|
||||||
|
|
||||||
let center = shape.bounds().center();
|
if self.options.is_debug_visible() {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
paint.set_color(skia::Color::from_argb(255, 255, 0, 0));
|
||||||
|
paint.set_stroke_width(4.);
|
||||||
|
self.drawing_surface.canvas().draw_rect(bounds, &paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.drawing_surface
|
||||||
|
.canvas()
|
||||||
|
.concat(&transform.invert().unwrap());
|
||||||
|
}
|
||||||
|
let center = shape.center();
|
||||||
|
|
||||||
// Transform the shape in the center
|
// Transform the shape in the center
|
||||||
let mut matrix = shape.transform.clone();
|
let mut matrix = shape.transform.clone();
|
||||||
matrix.post_translate(center);
|
matrix.post_translate(center);
|
||||||
matrix.pre_translate(-center);
|
matrix.pre_translate(-center);
|
||||||
|
|
||||||
self.drawing_surface.canvas().concat(&matrix);
|
if let Some(modifiers) = modifiers {
|
||||||
|
matrix.post_concat(&modifiers);
|
||||||
if let Some(bounds) = clip_bounds {
|
|
||||||
self.drawing_surface
|
|
||||||
.canvas()
|
|
||||||
.clip_rect(bounds, skia::ClipOp::Intersect, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.drawing_surface.canvas().concat(&matrix);
|
||||||
|
|
||||||
match &shape.kind {
|
match &shape.kind {
|
||||||
Kind::SVGRaw(sr) => {
|
Kind::SVGRaw(sr) => {
|
||||||
if let Some(svg) = shape.svg.as_ref() {
|
if let Some(svg) = shape.svg.as_ref() {
|
||||||
|
@ -503,7 +513,7 @@ impl RenderState {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let render_complete = self.viewbox.area.contains(element.bounds());
|
let render_complete = self.viewbox.area.contains(element.selrect());
|
||||||
if visited_children {
|
if visited_children {
|
||||||
if !visited_mask {
|
if !visited_mask {
|
||||||
match element.kind {
|
match element.kind {
|
||||||
|
@ -553,7 +563,7 @@ impl RenderState {
|
||||||
|
|
||||||
// If we didn't visited_children this shape, then we need to do
|
// If we didn't visited_children this shape, then we need to do
|
||||||
if !node_render_state.id.is_nil() {
|
if !node_render_state.id.is_nil() {
|
||||||
if !element.bounds().intersects(self.viewbox.area) || element.hidden() {
|
if !element.selrect().intersects(self.viewbox.area) || element.hidden() {
|
||||||
debug::render_debug_shape(self, element, false);
|
debug::render_debug_shape(self, element, false);
|
||||||
self.render_complete = render_complete;
|
self.render_complete = render_complete;
|
||||||
continue;
|
continue;
|
||||||
|
@ -622,7 +632,16 @@ impl RenderState {
|
||||||
|
|
||||||
if element.is_recursive() {
|
if element.is_recursive() {
|
||||||
let children_clip_bounds =
|
let children_clip_bounds =
|
||||||
(!node_render_state.id.is_nil() & element.clip()).then(|| element.bounds());
|
(!node_render_state.id.is_nil() & element.clip()).then(|| {
|
||||||
|
let bounds = element.selrect();
|
||||||
|
let mut transform = element.transform;
|
||||||
|
transform.post_translate(bounds.center());
|
||||||
|
transform.pre_translate(-bounds.center());
|
||||||
|
if let Some(modifiers) = modifiers.get(&element.id) {
|
||||||
|
transform.post_concat(&modifiers);
|
||||||
|
}
|
||||||
|
(bounds, transform)
|
||||||
|
});
|
||||||
|
|
||||||
for child_id in element.children_ids().iter().rev() {
|
for child_id in element.children_ids().iter().rev() {
|
||||||
self.pending_nodes.push(NodeRenderState {
|
self.pending_nodes.push(NodeRenderState {
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub fn render_debug_shape(render_state: &mut RenderState, element: &Shape, inter
|
||||||
});
|
});
|
||||||
paint.set_stroke_width(1.);
|
paint.set_stroke_width(1.);
|
||||||
|
|
||||||
let mut scaled_rect = element.bounds();
|
let mut scaled_rect = element.selrect();
|
||||||
let x = 100. + scaled_rect.x() * 0.2;
|
let x = 100. + scaled_rect.x() * 0.2;
|
||||||
let y = 100. + scaled_rect.y() * 0.2;
|
let y = 100. + scaled_rect.y() * 0.2;
|
||||||
let width = scaled_rect.width() * 0.2;
|
let width = scaled_rect.width() * 0.2;
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use crate::{
|
use crate::shapes::{Fill, ImageFill, Kind, Shape};
|
||||||
math,
|
use skia_safe::{self as skia, RRect, Rect};
|
||||||
shapes::{Fill, ImageFill, Kind, Shape},
|
|
||||||
};
|
|
||||||
use skia_safe::{self as skia, RRect};
|
|
||||||
|
|
||||||
use super::RenderState;
|
use super::RenderState;
|
||||||
|
|
||||||
|
@ -46,7 +43,7 @@ fn draw_image_fill_in_container(
|
||||||
let scaled_width = width * scale;
|
let scaled_width = width * scale;
|
||||||
let scaled_height = height * scale;
|
let scaled_height = height * scale;
|
||||||
|
|
||||||
let dest_rect = math::Rect::from_xywh(
|
let dest_rect = Rect::from_xywh(
|
||||||
container.left - (scaled_width - container_width) / 2.0,
|
container.left - (scaled_width - container_width) / 2.0,
|
||||||
container.top - (scaled_height - container_height) / 2.0,
|
container.top - (scaled_height - container_height) / 2.0,
|
||||||
scaled_width,
|
scaled_width,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::math::{self, Rect};
|
|
||||||
use crate::shapes::{Corners, Fill, ImageFill, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
|
use crate::shapes::{Corners, Fill, ImageFill, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
|
||||||
|
use skia::Rect;
|
||||||
use skia_safe::{self as skia, RRect};
|
use skia_safe::{self as skia, RRect};
|
||||||
|
|
||||||
use super::RenderState;
|
use super::RenderState;
|
||||||
|
@ -297,7 +297,7 @@ fn draw_triangle_cap(
|
||||||
canvas.draw_path(&path, paint);
|
canvas.draw_path(&path, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_scaled_rect(size: (i32, i32), container: &math::Rect, delta: f32) -> math::Rect {
|
fn calculate_scaled_rect(size: (i32, i32), container: &Rect, delta: f32) -> Rect {
|
||||||
let (width, height) = (size.0 as f32, size.1 as f32);
|
let (width, height) = (size.0 as f32, size.1 as f32);
|
||||||
let image_aspect_ratio = width / height;
|
let image_aspect_ratio = width / height;
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ fn calculate_scaled_rect(size: (i32, i32), container: &math::Rect, delta: f32) -
|
||||||
let scaled_width = width * scale;
|
let scaled_width = width * scale;
|
||||||
let scaled_height = height * scale;
|
let scaled_height = height * scale;
|
||||||
|
|
||||||
math::Rect::from_xywh(
|
Rect::from_xywh(
|
||||||
container.left - delta - (scaled_width - container_width) / 2.0,
|
container.left - delta - (scaled_width - container_width) / 2.0,
|
||||||
container.top - delta - (scaled_height - container_height) / 2.0,
|
container.top - delta - (scaled_height - container_height) / 2.0,
|
||||||
scaled_width + (2. * delta) + (scaled_width - container_width),
|
scaled_width + (2. * delta) + (scaled_width - container_width),
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::math;
|
use skia_safe::{self as skia, Matrix, Point, Rect};
|
||||||
use skia_safe as skia;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::render::BlendMode;
|
use crate::render::BlendMode;
|
||||||
use skia::Matrix;
|
|
||||||
|
|
||||||
mod blurs;
|
mod blurs;
|
||||||
mod bools;
|
mod bools;
|
||||||
|
@ -28,13 +27,45 @@ pub use strokes::*;
|
||||||
pub use svgraw::*;
|
pub use svgraw::*;
|
||||||
pub use transform::*;
|
pub use transform::*;
|
||||||
|
|
||||||
pub type CornerRadius = skia::Point;
|
use crate::math::Bounds;
|
||||||
|
|
||||||
|
pub type CornerRadius = Point;
|
||||||
pub type Corners = [CornerRadius; 4];
|
pub type Corners = [CornerRadius; 4];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Type {
|
||||||
|
Frame,
|
||||||
|
Group,
|
||||||
|
Bool,
|
||||||
|
Rect,
|
||||||
|
Path,
|
||||||
|
Text,
|
||||||
|
Circle,
|
||||||
|
SvgRaw,
|
||||||
|
Image,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
0 => Type::Frame,
|
||||||
|
1 => Type::Group,
|
||||||
|
2 => Type::Bool,
|
||||||
|
3 => Type::Rect,
|
||||||
|
4 => Type::Path,
|
||||||
|
5 => Type::Text,
|
||||||
|
6 => Type::Circle,
|
||||||
|
7 => Type::SvgRaw,
|
||||||
|
8 => Type::Image,
|
||||||
|
_ => Type::Rect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Rect(math::Rect, Option<Corners>),
|
Rect(Rect, Option<Corners>),
|
||||||
Circle(math::Rect),
|
Circle(Rect),
|
||||||
Path(Path),
|
Path(Path),
|
||||||
Bool(BoolType, Path),
|
Bool(BoolType, Path),
|
||||||
SVGRaw(SVGRaw),
|
SVGRaw(SVGRaw),
|
||||||
|
@ -91,9 +122,10 @@ pub type Color = skia::Color;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Shape {
|
pub struct Shape {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
|
pub shape_type: Type,
|
||||||
pub children: Vec<Uuid>,
|
pub children: Vec<Uuid>,
|
||||||
pub kind: Kind,
|
pub kind: Kind,
|
||||||
pub selrect: math::Rect,
|
pub selrect: Rect,
|
||||||
pub transform: Matrix,
|
pub transform: Matrix,
|
||||||
pub rotation: f32,
|
pub rotation: f32,
|
||||||
pub constraint_h: Option<ConstraintH>,
|
pub constraint_h: Option<ConstraintH>,
|
||||||
|
@ -114,9 +146,10 @@ impl Shape {
|
||||||
pub fn new(id: Uuid) -> Self {
|
pub fn new(id: Uuid) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
shape_type: Type::Rect,
|
||||||
children: Vec::<Uuid>::new(),
|
children: Vec::<Uuid>::new(),
|
||||||
kind: Kind::Rect(math::Rect::new_empty(), None),
|
kind: Kind::Rect(Rect::new_empty(), None),
|
||||||
selrect: math::Rect::new_empty(),
|
selrect: Rect::new_empty(),
|
||||||
transform: Matrix::default(),
|
transform: Matrix::default(),
|
||||||
rotation: 0.,
|
rotation: 0.,
|
||||||
constraint_h: None,
|
constraint_h: None,
|
||||||
|
@ -134,8 +167,12 @@ impl Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> Kind {
|
pub fn set_shape_type(&mut self, shape_type: Type) {
|
||||||
self.kind.clone()
|
self.shape_type = shape_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_frame(&self) -> bool {
|
||||||
|
self.shape_type == Type::Frame
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
|
@ -155,6 +192,10 @@ impl Shape {
|
||||||
self.kind = kind;
|
self.kind = kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> Kind {
|
||||||
|
self.kind.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_clip(&mut self, value: bool) {
|
pub fn set_clip(&mut self, value: bool) {
|
||||||
self.clip_content = value;
|
self.clip_content = value;
|
||||||
}
|
}
|
||||||
|
@ -175,10 +216,18 @@ impl Shape {
|
||||||
self.constraint_h = constraint;
|
self.constraint_h = constraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constraint_h(&self, default: ConstraintH) -> ConstraintH {
|
||||||
|
self.constraint_h.clone().unwrap_or(default)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_constraint_v(&mut self, constraint: Option<ConstraintV>) {
|
pub fn set_constraint_v(&mut self, constraint: Option<ConstraintV>) {
|
||||||
self.constraint_v = constraint;
|
self.constraint_v = constraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constraint_v(&self, default: ConstraintV) -> ConstraintV {
|
||||||
|
self.constraint_v.clone().unwrap_or(default)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_hidden(&mut self, value: bool) {
|
pub fn set_hidden(&mut self, value: bool) {
|
||||||
self.hidden = value;
|
self.hidden = value;
|
||||||
}
|
}
|
||||||
|
@ -339,10 +388,36 @@ impl Shape {
|
||||||
self.hidden
|
self.hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bounds(&self) -> math::Rect {
|
// TODO: Maybe store this inside the shape
|
||||||
|
pub fn bounds(&self) -> Bounds {
|
||||||
|
let mut bounds = Bounds::new(
|
||||||
|
Point::new(self.selrect.x(), self.selrect.y()),
|
||||||
|
Point::new(self.selrect.x() + self.selrect.width(), self.selrect.y()),
|
||||||
|
Point::new(
|
||||||
|
self.selrect.x() + self.selrect.width(),
|
||||||
|
self.selrect.y() + self.selrect.height(),
|
||||||
|
),
|
||||||
|
Point::new(self.selrect.x(), self.selrect.y() + self.selrect.height()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let center = self.center();
|
||||||
|
let mut matrix = self.transform.clone();
|
||||||
|
matrix.post_translate(center);
|
||||||
|
matrix.pre_translate(-center);
|
||||||
|
|
||||||
|
bounds.transform_mut(&matrix);
|
||||||
|
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selrect(&self) -> Rect {
|
||||||
self.selrect
|
self.selrect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn center(&self) -> Point {
|
||||||
|
self.selrect.center()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clip(&self) -> bool {
|
pub fn clip(&self) -> bool {
|
||||||
self.clip_content
|
self.clip_content
|
||||||
}
|
}
|
||||||
|
@ -405,11 +480,11 @@ impl Shape {
|
||||||
.filter(|shadow| shadow.style() == ShadowStyle::Inner)
|
.filter(|shadow| shadow.style() == ShadowStyle::Inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_path_transform(&self) -> Option<skia::Matrix> {
|
pub fn to_path_transform(&self) -> Option<Matrix> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Path(_) | Kind::Bool(_, _) => {
|
Kind::Path(_) | Kind::Bool(_, _) => {
|
||||||
let center = self.bounds().center();
|
let center = self.center();
|
||||||
let mut matrix = skia::Matrix::new_identity();
|
let mut matrix = Matrix::new_identity();
|
||||||
matrix.pre_translate(center);
|
matrix.pre_translate(center);
|
||||||
matrix.pre_concat(&self.transform.invert()?);
|
matrix.pre_concat(&self.transform.invert()?);
|
||||||
matrix.pre_translate(-center);
|
matrix.pre_translate(-center);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use skia_safe as skia;
|
use skia_safe::{self as skia, Rect};
|
||||||
|
|
||||||
use super::Color;
|
use super::Color;
|
||||||
use crate::math;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -44,7 +43,7 @@ impl Gradient {
|
||||||
self.offsets.push(offset);
|
self.offsets.push(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_linear_shader(&self, rect: &math::Rect) -> Option<skia::Shader> {
|
fn to_linear_shader(&self, rect: &Rect) -> Option<skia::Shader> {
|
||||||
let start = (
|
let start = (
|
||||||
rect.left + self.start.0 * rect.width(),
|
rect.left + self.start.0 * rect.width(),
|
||||||
rect.top + self.start.1 * rect.height(),
|
rect.top + self.start.1 * rect.height(),
|
||||||
|
@ -63,7 +62,7 @@ impl Gradient {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_radial_shader(&self, rect: &math::Rect) -> Option<skia::Shader> {
|
fn to_radial_shader(&self, rect: &Rect) -> Option<skia::Shader> {
|
||||||
let center = skia::Point::new(
|
let center = skia::Point::new(
|
||||||
rect.left + self.start.0 * rect.width(),
|
rect.left + self.start.0 * rect.width(),
|
||||||
rect.top + self.start.1 * rect.height(),
|
rect.top + self.start.1 * rect.height(),
|
||||||
|
@ -159,7 +158,7 @@ impl Fill {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint {
|
pub fn to_paint(&self, rect: &Rect) -> skia::Paint {
|
||||||
match self {
|
match self {
|
||||||
Self::Solid(color) => {
|
Self::Solid(color) => {
|
||||||
let mut p = skia::Paint::default();
|
let mut p = skia::Paint::default();
|
||||||
|
|
|
@ -1,22 +1,162 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use skia::Matrix;
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::shapes::{Shape, TransformEntry};
|
use crate::math::Bounds;
|
||||||
|
use crate::shapes::{ConstraintH, ConstraintV, Shape, TransformEntry};
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
|
|
||||||
fn propagate_shape(_state: &State, shape: &Shape, transform: skia::Matrix) -> Vec<TransformEntry> {
|
fn calculate_new_bounds(
|
||||||
let children: Vec<TransformEntry> = shape
|
constraint_h: ConstraintH,
|
||||||
.children
|
constraint_v: ConstraintV,
|
||||||
.iter()
|
parent_before: &Bounds,
|
||||||
.map(|id| TransformEntry {
|
parent_after: &Bounds,
|
||||||
id: id.clone(),
|
child_bounds: &Bounds,
|
||||||
transform,
|
) -> (f32, f32, f32, f32) {
|
||||||
})
|
let (delta_left, scale_width) = match constraint_h {
|
||||||
.collect();
|
ConstraintH::Scale => {
|
||||||
|
let width_scale = parent_after.width() / parent_before.width();
|
||||||
|
let target_left = parent_before.left(child_bounds.nw) * width_scale;
|
||||||
|
let current_left = parent_after.left(child_bounds.nw);
|
||||||
|
(target_left - current_left, width_scale)
|
||||||
|
}
|
||||||
|
ConstraintH::Left => {
|
||||||
|
let target_left = parent_before.left(child_bounds.nw);
|
||||||
|
let current_left = parent_after.left(child_bounds.nw);
|
||||||
|
(target_left - current_left, 1.0)
|
||||||
|
}
|
||||||
|
ConstraintH::Right => {
|
||||||
|
let target_right = parent_before.right(child_bounds.ne);
|
||||||
|
let current_right = parent_after.right(child_bounds.ne);
|
||||||
|
(current_right - target_right, 1.0)
|
||||||
|
}
|
||||||
|
ConstraintH::LeftRight => {
|
||||||
|
let target_left = parent_before.left(child_bounds.nw);
|
||||||
|
let target_right = parent_before.right(child_bounds.ne);
|
||||||
|
let current_left = parent_after.left(child_bounds.nw);
|
||||||
|
let new_width = parent_after.width() - target_left - target_right;
|
||||||
|
let width_scale = new_width / child_bounds.width();
|
||||||
|
(target_left - current_left, width_scale)
|
||||||
|
}
|
||||||
|
ConstraintH::Center => {
|
||||||
|
let delta_width = parent_after.width() - parent_before.width();
|
||||||
|
let delta_left = delta_width / 2.0;
|
||||||
|
(delta_left, 1.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
children
|
let (delta_top, scale_height) = match constraint_v {
|
||||||
|
ConstraintV::Scale => {
|
||||||
|
let height_scale = parent_after.height() / parent_before.height();
|
||||||
|
let target_top = parent_before.top(child_bounds.nw) * height_scale;
|
||||||
|
let current_top = parent_after.top(child_bounds.nw);
|
||||||
|
(target_top - current_top, height_scale)
|
||||||
|
}
|
||||||
|
ConstraintV::Top => {
|
||||||
|
let height_scale = 1.0;
|
||||||
|
let target_top = parent_before.top(child_bounds.nw);
|
||||||
|
let current_top = parent_after.top(child_bounds.nw);
|
||||||
|
(target_top - current_top, height_scale)
|
||||||
|
}
|
||||||
|
ConstraintV::Bottom => {
|
||||||
|
let target_bottom = parent_before.bottom(child_bounds.sw);
|
||||||
|
let current_bottom = parent_after.bottom(child_bounds.sw);
|
||||||
|
(current_bottom - target_bottom, 1.0)
|
||||||
|
}
|
||||||
|
ConstraintV::TopBottom => {
|
||||||
|
let target_top = parent_before.top(child_bounds.nw);
|
||||||
|
let target_bottom = parent_before.bottom(child_bounds.sw);
|
||||||
|
let current_top = parent_after.top(child_bounds.nw);
|
||||||
|
let new_height = parent_after.height() - target_top - target_bottom;
|
||||||
|
let height_scale = new_height / child_bounds.height();
|
||||||
|
(target_top - current_top, height_scale)
|
||||||
|
}
|
||||||
|
ConstraintV::Center => {
|
||||||
|
let delta_height = parent_after.height() - parent_before.height();
|
||||||
|
let delta_top = delta_height / 2.0;
|
||||||
|
(delta_top, 1.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(delta_left, delta_top, scale_width, scale_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propagate_shape(
|
||||||
|
shapes: &HashMap<Uuid, Shape>,
|
||||||
|
shape: &Shape,
|
||||||
|
transform: Matrix,
|
||||||
|
) -> Vec<TransformEntry> {
|
||||||
|
if shape.children.len() == 0 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_bounds_before = shape.bounds();
|
||||||
|
let parent_bounds_after = parent_bounds_before.transform(&transform);
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
for child_id in shape.children.iter() {
|
||||||
|
if let Some(child) = shapes.get(child_id) {
|
||||||
|
let constraint_h = child.constraint_h(if shape.is_frame() {
|
||||||
|
ConstraintH::Left
|
||||||
|
} else {
|
||||||
|
ConstraintH::Scale
|
||||||
|
});
|
||||||
|
|
||||||
|
let constraint_v = child.constraint_v(if shape.is_frame() {
|
||||||
|
ConstraintV::Top
|
||||||
|
} else {
|
||||||
|
ConstraintV::Scale
|
||||||
|
});
|
||||||
|
// if the constrains are scale & scale or the transform has only moves we
|
||||||
|
// can propagate as is
|
||||||
|
if (constraint_h == ConstraintH::Scale && constraint_v == ConstraintV::Scale)
|
||||||
|
|| transform.is_translate()
|
||||||
|
{
|
||||||
|
result.push(TransformEntry::new(child_id.clone(), transform));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(child_bounds_before) = parent_bounds_before.box_bounds(&child.bounds()) {
|
||||||
|
let (delta_left, delta_top, scale_width, scale_height) = calculate_new_bounds(
|
||||||
|
constraint_h,
|
||||||
|
constraint_v,
|
||||||
|
&parent_bounds_before,
|
||||||
|
&parent_bounds_after,
|
||||||
|
&child_bounds_before,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Translate position
|
||||||
|
let th = parent_bounds_after.hv(delta_left);
|
||||||
|
let tv = parent_bounds_after.vv(delta_top);
|
||||||
|
let mut transform = Matrix::translate(th + tv);
|
||||||
|
let child_bounds = child_bounds_before.transform(&transform);
|
||||||
|
|
||||||
|
// Scale shape
|
||||||
|
let center = child.center();
|
||||||
|
let mut parent_transform = shape.transform;
|
||||||
|
parent_transform.post_translate(center);
|
||||||
|
parent_transform.pre_translate(-center);
|
||||||
|
|
||||||
|
let parent_transform_inv = &parent_transform.invert().unwrap();
|
||||||
|
let origin = parent_transform_inv.map_point(child_bounds.nw);
|
||||||
|
|
||||||
|
let mut scale = Matrix::scale((scale_width, scale_height));
|
||||||
|
scale.post_translate(origin);
|
||||||
|
scale.post_concat(&parent_transform);
|
||||||
|
scale.pre_translate(-origin);
|
||||||
|
scale.pre_concat(&parent_transform_inv);
|
||||||
|
|
||||||
|
transform.post_concat(&scale);
|
||||||
|
result.push(TransformEntry::new(child_id.clone(), transform));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec<TransformEntry> {
|
pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec<TransformEntry> {
|
||||||
|
@ -28,7 +168,7 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec
|
||||||
while let Some(entry) = entries.pop() {
|
while let Some(entry) = entries.pop() {
|
||||||
if !processed.contains(&entry.id) {
|
if !processed.contains(&entry.id) {
|
||||||
if let Some(shape) = state.shapes.get(&entry.id) {
|
if let Some(shape) = state.shapes.get(&entry.id) {
|
||||||
let mut children = propagate_shape(state, shape, entry.transform);
|
let mut children = propagate_shape(&state.shapes, shape, entry.transform);
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
processed.insert(entry.id);
|
processed.insert(entry.id);
|
||||||
result.push(entry.clone());
|
result.push(entry.clone());
|
||||||
|
@ -38,3 +178,38 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::shapes::Type;
|
||||||
|
use skia::Point;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_propagate_shape() {
|
||||||
|
let mut shapes = HashMap::<Uuid, Shape>::new();
|
||||||
|
|
||||||
|
let child_id = Uuid::new_v4();
|
||||||
|
let mut child = Shape::new(child_id);
|
||||||
|
child.set_selrect(3.0, 3.0, 2.0, 2.0);
|
||||||
|
shapes.insert(child_id, child);
|
||||||
|
|
||||||
|
let parent_id = Uuid::new_v4();
|
||||||
|
let mut parent = Shape::new(parent_id);
|
||||||
|
parent.set_shape_type(Type::Group);
|
||||||
|
parent.add_child(child_id);
|
||||||
|
parent.set_selrect(1.0, 1.0, 5.0, 5.0);
|
||||||
|
shapes.insert(parent_id, parent.clone());
|
||||||
|
|
||||||
|
let mut transform = Matrix::scale((2.0, 1.5));
|
||||||
|
let x = parent.selrect.x();
|
||||||
|
let y = parent.selrect.y();
|
||||||
|
transform.post_translate(Point::new(x, y));
|
||||||
|
transform.pre_translate(Point::new(-x, -y));
|
||||||
|
|
||||||
|
let result = propagate_shape(&shapes, &parent, transform);
|
||||||
|
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use std::array::TryFromSliceError;
|
use std::array::TryFromSliceError;
|
||||||
|
|
||||||
use crate::math::Point;
|
type Point = (f32, f32);
|
||||||
|
|
||||||
fn stringify_slice_err(_: TryFromSliceError) -> String {
|
fn stringify_slice_err(_: TryFromSliceError) -> String {
|
||||||
format!("Error deserializing path")
|
format!("Error deserializing path")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::math;
|
|
||||||
use crate::shapes::fills::Fill;
|
use crate::shapes::fills::Fill;
|
||||||
use skia_safe as skia;
|
use skia_safe::{self as skia, Rect};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::Corners;
|
use super::Corners;
|
||||||
|
@ -122,18 +121,18 @@ impl Stroke {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outer_rect(&self, rect: &math::Rect) -> math::Rect {
|
pub fn outer_rect(&self, rect: &Rect) -> Rect {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
StrokeKind::InnerStroke => math::Rect::from_xywh(
|
StrokeKind::InnerStroke => Rect::from_xywh(
|
||||||
rect.left + (self.width / 2.),
|
rect.left + (self.width / 2.),
|
||||||
rect.top + (self.width / 2.),
|
rect.top + (self.width / 2.),
|
||||||
rect.width() - self.width,
|
rect.width() - self.width,
|
||||||
rect.height() - self.width,
|
rect.height() - self.width,
|
||||||
),
|
),
|
||||||
StrokeKind::CenterStroke => {
|
StrokeKind::CenterStroke => {
|
||||||
math::Rect::from_xywh(rect.left, rect.top, rect.width(), rect.height())
|
Rect::from_xywh(rect.left, rect.top, rect.width(), rect.height())
|
||||||
}
|
}
|
||||||
StrokeKind::OuterStroke => math::Rect::from_xywh(
|
StrokeKind::OuterStroke => Rect::from_xywh(
|
||||||
rect.left - (self.width / 2.),
|
rect.left - (self.width / 2.),
|
||||||
rect.top - (self.width / 2.),
|
rect.top - (self.width / 2.),
|
||||||
rect.width() + self.width,
|
rect.width() + self.width,
|
||||||
|
@ -158,7 +157,7 @@ impl Stroke {
|
||||||
|
|
||||||
pub fn to_paint(
|
pub fn to_paint(
|
||||||
&self,
|
&self,
|
||||||
rect: &math::Rect,
|
rect: &Rect,
|
||||||
svg_attrs: &HashMap<String, String>,
|
svg_attrs: &HashMap<String, String>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) -> skia::Paint {
|
) -> skia::Paint {
|
||||||
|
@ -223,7 +222,7 @@ impl Stroke {
|
||||||
pub fn to_stroked_paint(
|
pub fn to_stroked_paint(
|
||||||
&self,
|
&self,
|
||||||
is_open: bool,
|
is_open: bool,
|
||||||
rect: &math::Rect,
|
rect: &Rect,
|
||||||
svg_attrs: &HashMap<String, String>,
|
svg_attrs: &HashMap<String, String>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) -> skia::Paint {
|
) -> skia::Paint {
|
||||||
|
|
|
@ -12,6 +12,12 @@ pub struct TransformEntry {
|
||||||
pub transform: Matrix,
|
pub transform: Matrix,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TransformEntry {
|
||||||
|
pub fn new(id: Uuid, transform: Matrix) -> Self {
|
||||||
|
TransformEntry { id, transform }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SerializableResult for TransformEntry {
|
impl SerializableResult for TransformEntry {
|
||||||
type BytesType = [u8; 40];
|
type BytesType = [u8; 40];
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::math::Rect;
|
use skia_safe::Rect;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub(crate) struct Viewbox {
|
pub(crate) struct Viewbox {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue