0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-05 19:41:27 -05:00

Flex layout modifiers wasm implementation

*  Flex layout modifiers wasm implementation

*  Flex auto modifiers propagation
This commit is contained in:
Alonso Torres 2025-03-17 10:46:32 +01:00 committed by GitHub
parent fa9d8a9b15
commit fa0da3a695
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1400 additions and 219 deletions

View file

@ -108,6 +108,15 @@
(aget buffer 2)
(aget buffer 3))))
(defn set-parent-id
[id]
(let [buffer (uuid/get-u32 id)]
(h/call internal-module "_set_parent"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
(defn set-shape-clip-content
[clip-content]
(h/call internal-module "_set_shape_clip_content" clip-content))
@ -614,6 +623,15 @@
:fix 1
:auto 2))
(defn translate-align-self
[value]
(when value
(case value
:start 0
:end 1
:center 2
:stretch 3)))
(defn set-layout-child
[shape]
(let [margins (dm/get-prop shape :layout-item-margin)
@ -624,6 +642,7 @@
h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :auto) translate-layout-sizing)
v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :auto) translate-layout-sizing)
align-self (-> (dm/get-prop shape :layout-item-align-self) translate-align-self)
max-h (dm/get-prop shape :layout-item-max-h)
has-max-h (some? max-h)
@ -651,6 +670,8 @@
(or max-w 0)
has-min-w
(or min-w 0)
(some? align-self)
(or align-self 0)
is-absolute
z-index)))
@ -794,6 +815,7 @@
(if (< index total-shapes)
(let [shape (nth shapes index)
id (dm/get-prop shape :id)
parent-id (dm/get-prop shape :parent-id)
type (dm/get-prop shape :type)
masked (dm/get-prop shape :masked-group)
selrect (dm/get-prop shape :selrect)
@ -824,6 +846,7 @@
shadows (dm/get-prop shape :shadow)]
(use-shape id)
(set-parent-id parent-id)
(set-shape-type type)
(set-shape-clip-content clip-content)
(set-shape-selrect selrect)
@ -850,7 +873,8 @@
(when (and (= type :text) (some? content))
(set-shape-text-content content))
(when (ctl/any-layout-immediate-child? objects shape)
(when (or (ctl/any-layout? shape)
(ctl/any-layout-immediate-child? objects shape))
(set-layout-child shape))
(when (ctl/flex-layout? shape)

View file

@ -111,6 +111,7 @@
(when ^boolean shape/*wasm-sync*
(api/use-shape (:id self))
(case k
:parent-id (api/set-parent-id v)
:type (api/set-shape-type v)
:bool-type (api/set-shape-bool-type v)
:bool-content (api/set-shape-bool-content v)

View file

@ -162,6 +162,16 @@ Shadow styles are serialized as `u8`:
| 3 | Stretch |
| \_ | error |
### Align self
| Value | Field |
| ----- | ------- |
| 0 | Start |
| 1 | End |
| 2 | Center |
| 3 | Stretch |
| \_ | error |
### Align Content
| Value | Field |

View file

@ -167,6 +167,15 @@ pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
});
}
#[no_mangle]
pub unsafe extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
let id = uuid_from_u32_quartet(a, b, c, d);
if let Some(shape) = state.current_shape() {
shape.set_parent(id);
}
}
#[no_mangle]
pub extern "C" fn set_shape_masked_group(masked: bool) {
with_current_shape!(state, |shape: &mut Shape| {
@ -697,6 +706,8 @@ pub extern "C" fn set_layout_child_data(
max_w: f32,
has_min_w: bool,
min_w: f32,
has_align_self: bool,
align_self: u8,
is_absolute: bool,
z_index: i32,
) {
@ -706,6 +717,11 @@ pub extern "C" fn set_layout_child_data(
let min_h = if has_min_h { Some(min_h) } else { None };
let max_w = if has_max_w { Some(max_w) } else { None };
let min_w = if has_min_w { Some(min_w) } else { None };
let align_self = if has_align_self {
shapes::AlignSelf::from_u8(align_self)
} else {
None
};
with_current_shape!(state, |shape: &mut Shape| {
shape.set_flex_layout_child_data(
@ -719,6 +735,7 @@ pub extern "C" fn set_layout_child_data(
min_h,
max_w,
min_w,
align_self,
is_absolute,
z_index,
);

View file

@ -24,7 +24,7 @@ pub struct Bounds {
pub sw: Point,
}
fn vec_min_max(arr: &[Option<f32>]) -> Option<(f32, f32)> {
fn vec_min_max(arr: &Vec<Option<f32>>) -> Option<(f32, f32)> {
let mut minv: Option<f32> = None;
let mut maxv: Option<f32> = None;
@ -95,26 +95,30 @@ impl Bounds {
self.sw = mtx.map_point(self.sw);
}
pub fn box_bounds(&self, other: &Self) -> Option<Self> {
// pub fn box_bounds(&self, other: &Self) -> Option<Self> {
// self.from_points(other.points())
// }
pub fn from_points(&self, points: Vec<Point>) -> 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_ht, max_ht) = vec_min_max(
&points
.iter()
.map(|p| intersect_rays_t(&hr, &Ray::new(*p, vv)))
.collect(),
)?;
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 (min_vt, max_vt) = vec_min_max(
&points
.iter()
.map(|p| intersect_rays_t(&vr, &Ray::new(*p, hv)))
.collect(),
)?;
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))?;
@ -124,6 +128,10 @@ impl Bounds {
Some(Self { nw, ne, se, sw })
}
pub fn points(&self) -> Vec<Point> {
vec![self.nw, self.ne, self.se, self.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());
@ -224,6 +232,11 @@ impl Bounds {
result
}
pub fn center(&self) -> Point {
// Calculates the centroid of the four points
Point::new(self.nw.x + self.se.x / 2.0, self.nw.y + self.se.y / 2.0)
}
pub fn transform_matrix(&self) -> Option<Matrix> {
let w2 = self.width() / 2.0;
let h2 = self.height() / 2.0;
@ -320,6 +333,40 @@ pub fn intersect_rays(ray1: &Ray, ray2: &Ray) -> Option<Point> {
}
}
/*
* Creates a resizing matrix with width/height relative to the parent
* box and keepin the same transform as the parent.
*/
pub fn resize_matrix(
parent_bounds: &Bounds,
child_bounds: &Bounds,
new_width: f32,
new_height: f32,
) -> Matrix {
let mut result = Matrix::default();
let scale_width = new_width / child_bounds.width();
let scale_height = new_height / child_bounds.height();
let center = child_bounds.center();
let mut parent_transform = parent_bounds
.transform_matrix()
.unwrap_or(Matrix::default());
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);
result.post_concat(&scale);
result
}
#[cfg(test)]
mod tests {
use super::*;
@ -364,12 +411,19 @@ mod tests {
#[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!(None, vec_min_max(&vec![]));
assert_eq!(None, vec_min_max(&vec![None, None]));
assert_eq!(Some((1.0, 1.0)), vec_min_max(&vec![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)])
vec_min_max(&vec![
Some(0.3),
None,
Some(0.0),
Some(0.7),
Some(1.0),
Some(0.1)
])
);
}

View file

@ -153,9 +153,9 @@ impl ConstraintV {
pub type Color = skia::Color;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Shape {
pub id: Uuid,
pub parent_id: Option<Uuid>,
pub shape_type: Type,
pub children: Vec<Uuid>,
pub selrect: math::Rect,
@ -180,6 +180,7 @@ impl Shape {
pub fn new(id: Uuid) -> Self {
Self {
id,
parent_id: None,
shape_type: Type::Rect(Rect::default()),
children: Vec::<Uuid>::new(),
selrect: math::Rect::new_empty(),
@ -201,14 +202,32 @@ impl Shape {
}
}
pub fn set_parent(&mut self, id: Uuid) {
self.parent_id = Some(id);
}
pub fn set_shape_type(&mut self, shape_type: Type) {
self.shape_type = shape_type;
}
#[allow(dead_code)]
pub fn is_frame(&self) -> bool {
matches!(self.shape_type, Type::Frame(_))
}
pub fn is_group_like(&self) -> bool {
matches!(self.shape_type, Type::Group(_)) || matches!(self.shape_type, Type::Bool(_))
}
pub fn has_layout(&self) -> bool {
match self.shape_type {
Type::Frame(Frame {
layout: Some(_), ..
}) => true,
_ => false,
}
}
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
self.selrect.set_ltrb(left, top, right, bottom);
match self.shape_type {
@ -276,6 +295,7 @@ impl Shape {
min_h: Option<f32>,
max_w: Option<f32>,
min_w: Option<f32>,
align_self: Option<AlignSelf>,
is_absolute: bool,
z_index: i32,
) {
@ -292,6 +312,7 @@ impl Shape {
min_w,
is_absolute,
z_index,
align_self,
});
}
@ -654,6 +675,34 @@ impl Shape {
_ => {}
}
}
pub fn is_layout_vertical_auto(&self) -> bool {
match &self.layout_item {
Some(LayoutItem { v_sizing, .. }) => v_sizing == &Sizing::Auto,
_ => false,
}
}
pub fn is_layout_vertical_fill(&self) -> bool {
match &self.layout_item {
Some(LayoutItem { v_sizing, .. }) => v_sizing == &Sizing::Fill,
_ => false,
}
}
pub fn is_layout_horizontal_auto(&self) -> bool {
match &self.layout_item {
Some(LayoutItem { h_sizing, .. }) => h_sizing == &Sizing::Auto,
_ => false,
}
}
pub fn is_layout_horizontal_fill(&self) -> bool {
match &self.layout_item {
Some(LayoutItem { h_sizing, .. }) => h_sizing == &Sizing::Fill,
_ => false,
}
}
}
#[cfg(test)]

View file

@ -1,6 +1,5 @@
#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum Layout {
FlexLayout(LayoutData, FlexData),
GridLayout(LayoutData, GridData),
@ -137,7 +136,7 @@ impl WrapType {
#[derive(Debug, Clone, PartialEq)]
pub struct GridTrack {}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum Sizing {
Fill,
Fix,
@ -168,6 +167,49 @@ pub struct LayoutData {
pub padding_left: f32,
}
impl LayoutData {
pub fn is_reverse(&self) -> bool {
match &self.direction {
Direction::RowReverse | Direction::ColumnReverse => true,
_ => false,
}
}
pub fn is_row(&self) -> bool {
match &self.direction {
Direction::RowReverse | Direction::Row => true,
_ => false,
}
}
#[allow(dead_code)]
pub fn is_column(&self) -> bool {
match &self.direction {
Direction::ColumnReverse | Direction::Column => true,
_ => false,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum AlignSelf {
Start,
End,
Center,
Stretch,
}
impl AlignSelf {
pub fn from_u8(value: u8) -> Option<AlignSelf> {
match value {
0 => Some(Self::Start),
1 => Some(Self::End),
2 => Some(Self::Center),
3 => Some(Self::Stretch),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FlexData {
pub row_gap: f32,
@ -175,6 +217,15 @@ pub struct FlexData {
pub wrap_type: WrapType,
}
impl FlexData {
pub fn is_wrap(&self) -> bool {
match self.wrap_type {
WrapType::Wrap => true,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GridData {
pub rows: Vec<GridTrack>,
@ -182,7 +233,7 @@ pub struct GridData {
// layout-grid-cells ;; map of id->grid-cell
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct LayoutItem {
pub margin_top: f32,
pub margin_right: f32,
@ -196,4 +247,5 @@ pub struct LayoutItem {
pub min_w: Option<f32>,
pub is_absolute: bool,
pub z_index: i32,
pub align_self: Option<AlignSelf>,
}

View file

@ -1,220 +1,228 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet, VecDeque};
use skia::Matrix;
use skia_safe as skia;
mod common;
mod constraints;
mod flex_layout;
mod grid_layout;
use std::collections::HashSet;
use uuid::Uuid;
use crate::math::Bounds;
use crate::shapes::{ConstraintH, ConstraintV, Shape, TransformEntry};
use common::GetBounds;
use crate::math::{Bounds, Matrix, Point};
use crate::shapes::{
ConstraintH, ConstraintV, Frame, Group, Layout, Modifier, Shape, TransformEntry, Type,
};
use crate::state::State;
fn calculate_resize(
constraint_h: ConstraintH,
constraint_v: ConstraintV,
parent_before: &Bounds,
parent_after: &Bounds,
child_before: &Bounds,
child_after: &Bounds,
) -> Option<(f32, f32)> {
let scale_width = match constraint_h {
ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => {
parent_before.width() / parent_after.width()
}
ConstraintH::LeftRight => {
let left = parent_before.left(child_before.nw);
let right = parent_before.right(child_before.ne);
let target_width = parent_after.width() - left - right;
target_width / child_after.width()
}
_ => 1.0,
};
let scale_height = match constraint_v {
ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => {
parent_before.height() / parent_after.height()
}
ConstraintV::TopBottom => {
let top = parent_before.top(child_before.nw);
let bottom = parent_before.bottom(child_before.sw);
let target_height = parent_after.height() - top - bottom;
target_height / child_after.height()
}
_ => 1.0,
};
if (scale_width - 1.0).abs() < f32::EPSILON && (scale_height - 1.0).abs() < f32::EPSILON {
None
} else {
Some((scale_width, scale_height))
}
}
fn calculate_displacement(
constraint_h: ConstraintH,
constraint_v: ConstraintV,
parent_before: &Bounds,
parent_after: &Bounds,
child_before: &Bounds,
child_after: &Bounds,
) -> Option<(f32, f32)> {
let delta_x = match constraint_h {
ConstraintH::Left | ConstraintH::LeftRight => {
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left
}
ConstraintH::Right => {
let target_right = parent_before.right(child_before.ne);
let current_right = parent_after.right(child_after.ne);
current_right - target_right
}
ConstraintH::Center => {
let delta_width = parent_after.width() - parent_before.width();
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left + delta_width / 2.0
}
_ => 0.0,
};
let delta_y = match constraint_v {
ConstraintV::Top | ConstraintV::TopBottom => {
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top
}
ConstraintV::Bottom => {
let target_bottom = parent_before.bottom(child_before.ne);
let current_bottom = parent_after.bottom(child_after.ne);
current_bottom - target_bottom
}
ConstraintV::Center => {
let delta_height = parent_after.height() - parent_before.height();
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top + delta_height / 2.0
}
_ => 0.0,
};
if delta_x.abs() < f32::EPSILON && delta_y.abs() < f32::EPSILON {
None
} else {
Some((delta_x, delta_y))
}
}
fn propagate_shape(
shapes: &HashMap<Uuid, Shape>,
fn propagate_children(
shape: &Shape,
shapes: &HashMap<Uuid, Shape>,
parent_bounds_before: &Bounds,
parent_bounds_after: &Bounds,
transform: Matrix,
) -> Vec<TransformEntry> {
bounds: &HashMap<Uuid, Bounds>,
) -> VecDeque<Modifier> {
if shape.children.len() == 0 {
return vec![];
return VecDeque::new();
}
let parent_bounds_before = shape.bounds();
let parent_bounds_after = parent_bounds_before.transform(&transform);
let mut result = Vec::new();
let mut result = VecDeque::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 Some(child) = shapes.get(child_id) else {
continue;
};
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;
}
let child_bounds = bounds.find(child);
if let Some(child_bounds_before) = parent_bounds_before.box_bounds(&child.bounds()) {
let mut transform = transform;
let mut child_bounds_after = child_bounds_before.transform(&transform);
let constraint_h = match &shape.shape_type {
Type::Frame(Frame {
layout: Some(_), ..
}) => ConstraintH::Left,
Type::Frame(_) => child.constraint_h(ConstraintH::Left),
_ => child.constraint_h(ConstraintH::Scale),
};
// Scale shape
if let Some((scale_width, scale_height)) = calculate_resize(
constraint_h,
constraint_v,
&parent_bounds_before,
&parent_bounds_after,
&child_bounds_before,
&child_bounds_after,
) {
let center = child.center();
let constraint_v = match &shape.shape_type {
Type::Frame(Frame {
layout: Some(_), ..
}) => ConstraintV::Top,
Type::Frame(_) => child.constraint_v(ConstraintV::Top),
_ => child.constraint_v(ConstraintV::Scale),
};
let mut parent_transform = parent_bounds_after
.transform_matrix()
.unwrap_or(Matrix::default());
parent_transform.post_translate(center);
parent_transform.pre_translate(-center);
let transform = constraints::propagate_shape_constraints(
&parent_bounds_before,
&parent_bounds_after,
&child_bounds,
constraint_h,
constraint_v,
transform,
);
let parent_transform_inv = &parent_transform.invert().unwrap();
let origin = parent_transform_inv.map_point(child_bounds_after.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);
child_bounds_after.transform_mut(&scale);
transform.post_concat(&scale);
}
// Translate position
if let Some((delta_x, delta_y)) = calculate_displacement(
constraint_h,
constraint_v,
&parent_bounds_before,
&parent_bounds_after,
&child_bounds_before,
&child_bounds_after,
) {
let th = parent_bounds_after.hv(delta_x);
let tv = parent_bounds_after.vv(delta_y);
transform.post_concat(&Matrix::translate(th + tv));
}
result.push(TransformEntry::new(child_id.clone(), transform));
}
}
result.push_back(Modifier::transform(*child_id, transform));
}
result
}
fn calculate_group_bounds(
shape: &Shape,
shapes: &HashMap<Uuid, Shape>,
bounds: &HashMap<Uuid, Bounds>,
) -> Option<Bounds> {
let shape_bounds = bounds.find(&shape);
let mut result = Vec::<Point>::new();
for child_id in shape.children.iter() {
let Some(child) = shapes.get(child_id) else {
continue;
};
let child_bounds = bounds.find(child);
result.append(&mut child_bounds.points());
}
shape_bounds.from_points(result)
}
pub fn propagate_modifiers(state: &State, modifiers: Vec<TransformEntry>) -> Vec<TransformEntry> {
let mut entries = modifiers.clone();
let mut processed = HashSet::<Uuid>::new();
let mut result = Vec::<TransformEntry>::new();
let shapes = &state.shapes;
// Propagate the transform to children
while let Some(entry) = entries.pop() {
if !processed.contains(&entry.id) {
if let Some(shape) = state.shapes.get(&entry.id) {
let mut children = propagate_shape(&state.shapes, shape, entry.transform);
entries.append(&mut children);
processed.insert(entry.id);
result.push(entry.clone());
let mut entries: VecDeque<_> = modifiers
.iter()
.map(|entry| Modifier::Transform(entry.clone()))
.collect();
let mut modifiers = HashMap::<Uuid, Matrix>::new();
let mut bounds = HashMap::<Uuid, Bounds>::new();
let mut reflown = HashSet::<Uuid>::new();
let mut layout_reflows = Vec::<Uuid>::new();
// We first propagate the transforms to the children and then after
// recalculate the layouts. The layout can create further transforms that
// we need to re-propagate.
// In order for loop to eventualy finish, we limit the flex reflow to just
// one (the reflown set).
while !entries.is_empty() {
while let Some(modifier) = entries.pop_front() {
match modifier {
Modifier::Transform(entry) => {
// println!("Transform {}", entry.id);
let Some(shape) = state.shapes.get(&entry.id) else {
continue;
};
let shape_bounds_before = bounds.find(&shape);
let shape_bounds_after = shape_bounds_before.transform(&entry.transform);
if entry.propagate {
let mut children = propagate_children(
shape,
shapes,
&shape_bounds_before,
&shape_bounds_after,
entry.transform,
&bounds,
);
entries.append(&mut children);
}
bounds.insert(shape.id, shape_bounds_after);
let default_matrix = Matrix::default();
let mut shape_modif =
modifiers.get(&shape.id).unwrap_or(&default_matrix).clone();
shape_modif.post_concat(&entry.transform);
modifiers.insert(shape.id, shape_modif);
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
if parent.has_layout() || parent.is_group_like() {
entries.push_back(Modifier::reflow(parent.id));
}
}
}
Modifier::Reflow(id) => {
let Some(shape) = state.shapes.get(&id) else {
continue;
};
match &shape.shape_type {
Type::Frame(Frame {
layout: Some(_), ..
}) => {
if !reflown.contains(&id) {
layout_reflows.push(id);
reflown.insert(id);
}
}
Type::Group(Group { masked: true }) => {
if let Some(child) = shapes.get(&shape.children[0]) {
let child_bounds = bounds.find(&child);
bounds.insert(shape.id, child_bounds);
}
}
Type::Group(_) => {
if let Some(shape_bounds) =
calculate_group_bounds(shape, shapes, &bounds)
{
bounds.insert(shape.id, shape_bounds);
}
}
Type::Bool(_) => {
// TODO: How to calculate from rust the new box? we need to calculate the
// new path... impossible right now. I'm going to use for the moment the group
// calculation
if let Some(shape_bounds) =
calculate_group_bounds(shape, shapes, &bounds)
{
bounds.insert(shape.id, shape_bounds);
}
}
_ => {
// Other shapes don't have to be reflown
}
}
if let Some(parent) = shape.parent_id.and_then(|id| shapes.get(&id)) {
if parent.has_layout() || parent.is_group_like() {
entries.push_back(Modifier::reflow(parent.id));
}
}
}
}
}
for id in layout_reflows.iter() {
let Some(shape) = state.shapes.get(&id) else {
continue;
};
let Type::Frame(frame_data) = &shape.shape_type else {
continue;
};
if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
let mut children =
flex_layout::reflow_flex_layout(shape, layout_data, flex_data, shapes, &bounds);
entries.append(&mut children);
}
if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
let mut children =
grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, &bounds);
entries.append(&mut children);
}
}
layout_reflows = Vec::new();
}
result
modifiers
.iter()
.map(|(key, val)| TransformEntry::new(*key, *val))
.collect()
}
#[cfg(test)]
@ -246,8 +254,47 @@ mod tests {
transform.post_translate(Point::new(x, y));
transform.pre_translate(Point::new(-x, -y));
let result = propagate_shape(&shapes, &parent, transform);
let bounds_before = parent.bounds();
let bounds_after = bounds_before.transform(&transform);
let result = propagate_children(
&parent,
&shapes,
&bounds_before,
&bounds_after,
transform,
&HashMap::<Uuid, Bounds>::new(),
);
assert_eq!(result.len(), 1);
}
#[test]
fn test_group_bounds() {
let mut shapes = HashMap::<Uuid, Shape>::new();
let child1_id = Uuid::new_v4();
let mut child1 = Shape::new(child1_id);
child1.set_selrect(3.0, 3.0, 2.0, 2.0);
shapes.insert(child1_id, child1);
let child2_id = Uuid::new_v4();
let mut child2 = Shape::new(child2_id);
child2.set_selrect(0.0, 0.0, 1.0, 1.0);
shapes.insert(child2_id, child2);
let parent_id = Uuid::new_v4();
let mut parent = Shape::new(parent_id);
parent.set_shape_type(Type::Group(Group::default()));
parent.add_child(child1_id);
parent.add_child(child2_id);
parent.set_selrect(0.0, 0.0, 3.0, 3.0);
shapes.insert(parent_id, parent.clone());
let bounds =
calculate_group_bounds(&parent, &shapes, HashMap::<Uuid, Bounds>::new()).unwrap();
assert_eq!(bounds.width(), 3.0);
assert_eq!(bounds.height(), 3.0);
}
}

View file

@ -0,0 +1,17 @@
use std::collections::HashMap;
use uuid::Uuid;
use crate::math::Bounds;
use crate::shapes::Shape;
pub trait GetBounds {
fn find(&self, shape: &Shape) -> Bounds;
}
impl GetBounds for HashMap<Uuid, Bounds> {
fn find(&self, shape: &Shape) -> Bounds {
self.get(&shape.id)
.map(|b| b.clone())
.unwrap_or(shape.bounds())
}
}

View file

@ -0,0 +1,164 @@
use crate::math::{Bounds, Matrix};
use crate::shapes::{ConstraintH, ConstraintV};
pub fn calculate_resize(
constraint_h: ConstraintH,
constraint_v: ConstraintV,
parent_before: &Bounds,
parent_after: &Bounds,
child_before: &Bounds,
child_after: &Bounds,
) -> Option<(f32, f32)> {
let scale_width = match constraint_h {
ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => {
parent_before.width() / parent_after.width()
}
ConstraintH::LeftRight => {
let left = parent_before.left(child_before.nw);
let right = parent_before.right(child_before.ne);
let target_width = parent_after.width() - left - right;
target_width / child_after.width()
}
_ => 1.0,
};
let scale_height = match constraint_v {
ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => {
parent_before.height() / parent_after.height()
}
ConstraintV::TopBottom => {
let top = parent_before.top(child_before.nw);
let bottom = parent_before.bottom(child_before.sw);
let target_height = parent_after.height() - top - bottom;
target_height / child_after.height()
}
_ => 1.0,
};
if (scale_width - 1.0).abs() < f32::EPSILON && (scale_height - 1.0).abs() < f32::EPSILON {
None
} else {
Some((scale_width, scale_height))
}
}
pub fn calculate_displacement(
constraint_h: ConstraintH,
constraint_v: ConstraintV,
parent_before: &Bounds,
parent_after: &Bounds,
child_before: &Bounds,
child_after: &Bounds,
) -> Option<(f32, f32)> {
let delta_x = match constraint_h {
ConstraintH::Left | ConstraintH::LeftRight => {
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left
}
ConstraintH::Right => {
let target_right = parent_before.right(child_before.ne);
let current_right = parent_after.right(child_after.ne);
current_right - target_right
}
ConstraintH::Center => {
let delta_width = parent_after.width() - parent_before.width();
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left + delta_width / 2.0
}
_ => 0.0,
};
let delta_y = match constraint_v {
ConstraintV::Top | ConstraintV::TopBottom => {
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top
}
ConstraintV::Bottom => {
let target_bottom = parent_before.bottom(child_before.ne);
let current_bottom = parent_after.bottom(child_after.ne);
current_bottom - target_bottom
}
ConstraintV::Center => {
let delta_height = parent_after.height() - parent_before.height();
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top + delta_height / 2.0
}
_ => 0.0,
};
if delta_x.abs() < f32::EPSILON && delta_y.abs() < f32::EPSILON {
None
} else {
Some((delta_x, delta_y))
}
}
pub fn propagate_shape_constraints(
parent_bounds_before: &Bounds,
parent_bounds_after: &Bounds,
child_bounds_before: &Bounds,
constraint_h: ConstraintH,
constraint_v: ConstraintV,
transform: Matrix,
) -> Matrix {
// 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()
{
return transform;
}
let mut transform = transform;
let mut child_bounds_after = child_bounds_before.transform(&transform);
// Scale shape
if let Some((scale_width, scale_height)) = calculate_resize(
constraint_h,
constraint_v,
&parent_bounds_before,
&parent_bounds_after,
&child_bounds_before,
&child_bounds_after,
) {
let center = child_bounds_before.center();
let mut parent_transform = parent_bounds_after
.transform_matrix()
.unwrap_or(Matrix::default());
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_after.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);
child_bounds_after.transform_mut(&scale);
transform.post_concat(&scale);
}
// Translate position
if let Some((delta_x, delta_y)) = calculate_displacement(
constraint_h,
constraint_v,
&parent_bounds_before,
&parent_bounds_after,
&child_bounds_before,
&child_bounds_after,
) {
let th = parent_bounds_after.hv(delta_x);
let tv = parent_bounds_after.vv(delta_y);
transform.post_concat(&Matrix::translate(th + tv));
}
transform
}

View file

@ -0,0 +1,701 @@
#![allow(dead_code)]
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
use crate::shapes::{
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
Modifier, Shape,
};
use std::collections::{HashMap, VecDeque};
use uuid::Uuid;
use super::common::GetBounds;
const MIN_SIZE: f32 = 0.01;
const MAX_SIZE: f32 = f32::INFINITY;
#[derive(Debug)]
struct TrackData {
main_size: f32,
across_size: f32,
max_across_size: f32,
is_fill_across: bool,
shapes: Vec<ChildAxis>,
anchor: Point,
}
impl TrackData {
fn default() -> Self {
Self {
main_size: MIN_SIZE,
across_size: MIN_SIZE,
max_across_size: MAX_SIZE,
is_fill_across: false,
shapes: Vec::new(),
anchor: Point::default(),
}
}
}
#[derive(Debug)]
struct LayoutAxis {
main_size: f32,
across_size: f32,
main_v: Vector,
across_v: Vector,
padding_main_start: f32,
padding_main_end: f32,
padding_across_start: f32,
padding_across_end: f32,
gap_main: f32,
gap_across: f32,
is_auto_main: bool,
is_auto_across: bool,
}
impl LayoutAxis {
fn main_space(&self) -> f32 {
self.main_size - self.padding_main_start - self.padding_main_end
}
fn across_space(&self) -> f32 {
self.across_size - self.padding_across_start - self.padding_across_end
}
}
impl LayoutAxis {
fn new(
shape: &Shape,
layout_bounds: &Bounds,
layout_data: &LayoutData,
flex_data: &FlexData,
) -> Self {
if layout_data.is_row() {
Self {
main_size: layout_bounds.width(),
across_size: layout_bounds.height(),
main_v: layout_bounds.hv(1.0),
across_v: layout_bounds.vv(1.0),
padding_main_start: layout_data.padding_left,
padding_main_end: layout_data.padding_right,
padding_across_start: layout_data.padding_top,
padding_across_end: layout_data.padding_bottom,
gap_main: flex_data.column_gap,
gap_across: flex_data.row_gap,
is_auto_main: shape.is_layout_horizontal_auto(),
is_auto_across: shape.is_layout_vertical_auto(),
}
} else {
Self {
main_size: layout_bounds.height(),
across_size: layout_bounds.width(),
main_v: layout_bounds.vv(1.0),
across_v: layout_bounds.hv(1.0),
padding_main_start: layout_data.padding_top,
padding_main_end: layout_data.padding_bottom,
padding_across_start: layout_data.padding_left,
padding_across_end: layout_data.padding_right,
gap_main: flex_data.row_gap,
gap_across: flex_data.column_gap,
is_auto_main: shape.is_layout_vertical_auto(),
is_auto_across: shape.is_layout_horizontal_auto(),
}
}
}
}
#[derive(Debug, Copy, Clone)]
struct ChildAxis {
id: Uuid,
main_size: f32,
across_size: f32,
margin_main_start: f32,
margin_main_end: f32,
margin_across_start: f32,
margin_across_end: f32,
min_main_size: f32,
max_main_size: f32,
min_across_size: f32,
max_across_size: f32,
is_fill_main: bool,
is_fill_across: bool,
is_absolute: bool,
z_index: i32,
}
impl ChildAxis {
fn new(child: &Shape, child_bounds: &Bounds, layout_data: &LayoutData) -> Self {
let id = child.id;
let layout_item = child.layout_item;
let mut result = if layout_data.is_row() {
Self {
id,
main_size: child_bounds.width(),
across_size: child_bounds.height(),
margin_main_start: layout_item.map(|i| i.margin_left).unwrap_or(0.0),
margin_main_end: layout_item.map(|i| i.margin_right).unwrap_or(0.0),
margin_across_start: layout_item.map(|i| i.margin_top).unwrap_or(0.0),
margin_across_end: layout_item.map(|i| i.margin_bottom).unwrap_or(0.0),
min_main_size: layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE),
max_main_size: layout_item.and_then(|i| i.max_w).unwrap_or(MAX_SIZE),
min_across_size: layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE),
max_across_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
is_fill_main: child.is_layout_horizontal_fill(),
is_fill_across: child.is_layout_vertical_fill(),
is_absolute: layout_item.map(|i| i.is_absolute).unwrap_or(false),
z_index: layout_item.map(|i| i.z_index).unwrap_or(0),
}
} else {
Self {
id,
across_size: child_bounds.width(),
main_size: child_bounds.height(),
margin_across_start: layout_item.map(|i| i.margin_left).unwrap_or(0.0),
margin_across_end: layout_item.map(|i| i.margin_right).unwrap_or(0.0),
margin_main_start: layout_item.map(|i| i.margin_top).unwrap_or(0.0),
margin_main_end: layout_item.map(|i| i.margin_bottom).unwrap_or(0.0),
min_across_size: layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE),
max_across_size: layout_item.and_then(|i| i.max_w).unwrap_or(MAX_SIZE),
min_main_size: layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE),
max_main_size: layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE),
is_fill_main: child.is_layout_vertical_fill(),
is_fill_across: child.is_layout_horizontal_fill(),
is_absolute: layout_item.map(|i| i.is_absolute).unwrap_or(false),
z_index: layout_item.map(|i| i.z_index).unwrap_or(0),
}
};
if result.is_fill_main {
result.main_size = result.min_main_size;
}
if result.is_fill_across {
result.across_size = result.min_across_size;
}
result
}
}
fn initialize_tracks(
shape: &Shape,
layout_axis: &LayoutAxis,
layout_data: &LayoutData,
flex_data: &FlexData,
shapes: &HashMap<Uuid, Shape>,
bounds: &HashMap<Uuid, Bounds>,
) -> Vec<TrackData> {
let mut tracks = Vec::<TrackData>::new();
let mut current_track = TrackData::default();
let mut children = shape.children.clone();
let mut first = true;
if !layout_data.is_reverse() {
children.reverse();
}
for child_id in children.iter() {
let Some(child) = shapes.get(child_id) else {
continue;
};
let child_bounds = bounds.find(child);
let child_axis = ChildAxis::new(child, &child_bounds, layout_data);
let child_main_size = if child_axis.is_fill_main {
child_axis.min_main_size
} else {
child_axis.main_size
};
let child_across_size = if child_axis.is_fill_across {
child_axis.min_across_size
} else {
child_axis.across_size
};
let child_max_across_size = if child_axis.is_fill_across {
child_axis.max_across_size
} else {
child_axis.across_size
};
let gap_main = if first { 0.0 } else { layout_axis.gap_main };
let next_main_size = current_track.main_size + child_main_size + gap_main;
if !layout_axis.is_auto_main
&& flex_data.is_wrap()
&& (next_main_size > layout_axis.main_space())
{
tracks.push(current_track);
current_track = TrackData {
main_size: child_main_size,
across_size: child_across_size,
shapes: Vec::from([child_axis]),
is_fill_across: child_axis.is_fill_across,
anchor: Point::default(),
max_across_size: child_max_across_size,
};
} else {
// Update current track
current_track.main_size = next_main_size;
current_track.across_size = f32::max(child_across_size, current_track.across_size);
current_track.shapes.push(child_axis);
current_track.is_fill_across =
current_track.is_fill_across || child_axis.is_fill_across;
current_track.max_across_size =
f32::max(current_track.max_across_size, child_max_across_size);
}
first = false;
}
// Finalize current track
tracks.push(current_track);
tracks
}
// Resize main axis fill
fn distribute_fill_main_space(layout_axis: &LayoutAxis, tracks: &mut Vec<TrackData>) {
for track in tracks.iter_mut() {
let mut left_space = layout_axis.main_space() - track.main_size;
let mut to_resize_children: Vec<&mut ChildAxis> = Vec::new();
for child in track.shapes.iter_mut() {
if child.is_fill_main && child.main_size < child.max_main_size {
to_resize_children.push(child);
}
}
while left_space > MIN_SIZE && !to_resize_children.is_empty() {
let current = left_space / to_resize_children.len() as f32;
for i in (0..to_resize_children.len()).rev() {
let child = &mut to_resize_children[i];
let delta =
f32::min(child.max_main_size, child.main_size + current) - child.main_size;
child.main_size = child.main_size + delta;
left_space = left_space - delta;
if (child.main_size - child.max_main_size).abs() < MIN_SIZE {
to_resize_children.remove(i);
}
}
}
}
}
fn distribute_fill_across_space(layout_axis: &LayoutAxis, tracks: &mut Vec<TrackData>) {
let total_across_size = tracks.iter().map(|t| t.across_size).sum::<f32>()
+ (tracks.len() - 1) as f32 * layout_axis.gap_across;
let mut left_space = layout_axis.across_space() - total_across_size;
let mut to_resize_tracks: Vec<&mut TrackData> = Vec::new();
for track in tracks.iter_mut() {
if track.is_fill_across && track.across_size < track.max_across_size {
to_resize_tracks.push(track);
}
}
while left_space > MIN_SIZE && !to_resize_tracks.is_empty() {
let current = left_space / to_resize_tracks.len() as f32;
for i in (0..to_resize_tracks.len()).rev() {
let track = &mut to_resize_tracks[i];
let delta =
f32::min(track.max_across_size, track.across_size + current) - track.across_size;
track.across_size = track.across_size + delta;
left_space = left_space - delta;
if (track.across_size - track.max_across_size).abs() < MIN_SIZE {
to_resize_tracks.remove(i);
}
}
}
// After assigning the across size to the tracks we can assing the size to the shapes
for track in tracks.iter_mut() {
if !track.is_fill_across {
continue;
}
for child in track.shapes.iter_mut() {
if child.is_fill_across {
child.across_size = track
.across_size
.clamp(child.min_across_size, child.max_across_size);
}
}
}
}
fn stretch_tracks_sizes(
layout_axis: &LayoutAxis,
tracks: &mut Vec<TrackData>,
total_across_size: f32,
) {
let total_across_size = total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across;
let left_space = layout_axis.across_space() - total_across_size;
let delta = left_space / tracks.len() as f32;
for track in tracks.iter_mut() {
track.across_size = track.across_size + delta;
}
}
fn calculate_track_positions(
layout_data: &LayoutData,
layout_axis: &LayoutAxis,
layout_bounds: &Bounds,
tracks: &mut Vec<TrackData>,
total_across_size: f32,
) {
let mut align_content = &layout_data.align_content;
if layout_axis.is_auto_across {
align_content = &AlignContent::Start;
}
match align_content {
AlignContent::End => {
let total_across_size_gap: f32 =
total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across;
let delta =
layout_axis.across_size - total_across_size_gap - layout_axis.padding_across_end;
let mut next_anchor = layout_bounds.nw + layout_axis.across_v * delta;
for track in tracks.iter_mut() {
track.anchor = next_anchor;
next_anchor = next_anchor
+ layout_axis.across_v * (track.across_size + layout_axis.gap_across);
}
}
AlignContent::Center => {
let total_across_size_gap: f32 =
total_across_size + (tracks.len() - 1) as f32 * layout_axis.gap_across;
let center_margin = (layout_axis.across_size - total_across_size_gap) / 2.0;
let mut next_anchor = layout_bounds.nw + layout_axis.across_v * center_margin;
for track in tracks.iter_mut() {
track.anchor = next_anchor;
next_anchor = next_anchor
+ layout_axis.across_v * (track.across_size + layout_axis.gap_across);
}
}
AlignContent::SpaceBetween => {
let mut next_anchor =
layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start;
let effective_gap = f32::max(
layout_axis.gap_across,
(layout_axis.across_space() - total_across_size) / (tracks.len() - 1) as f32,
);
for track in tracks.iter_mut() {
track.anchor = next_anchor;
next_anchor =
next_anchor + layout_axis.across_v * (track.across_size + effective_gap);
}
}
AlignContent::SpaceAround => {
let effective_gap =
(layout_axis.across_space() - total_across_size) / tracks.len() as f32;
let mut next_anchor = layout_bounds.nw
+ layout_axis.across_v * (layout_axis.padding_across_start + effective_gap / 2.0);
for track in tracks.iter_mut() {
track.anchor = next_anchor;
next_anchor =
next_anchor + layout_axis.across_v * (track.across_size + effective_gap);
}
}
AlignContent::SpaceEvenly => {
let effective_gap =
(layout_axis.across_space() - total_across_size) / (tracks.len() + 1) as f32;
let mut next_anchor = layout_bounds.nw
+ layout_axis.across_v * (layout_axis.padding_across_start + effective_gap);
for track in tracks.iter_mut() {
track.anchor = next_anchor;
next_anchor =
next_anchor + layout_axis.across_v * (track.across_size + effective_gap);
}
}
_ => {
let mut next_anchor =
layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start;
for track in tracks.iter_mut() {
track.anchor = next_anchor;
next_anchor = next_anchor
+ layout_axis.across_v * (track.across_size + layout_axis.gap_across);
}
}
}
}
fn calculate_track_data(
shape: &Shape,
layout_data: &LayoutData,
flex_data: &FlexData,
layout_bounds: &Bounds,
shapes: &HashMap<Uuid, Shape>,
bounds: &HashMap<Uuid, Bounds>,
) -> Vec<TrackData> {
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
let mut tracks = initialize_tracks(shape, &layout_axis, layout_data, flex_data, shapes, bounds);
if !layout_axis.is_auto_main {
distribute_fill_main_space(&layout_axis, &mut tracks);
}
if !layout_axis.is_auto_across {
distribute_fill_across_space(&layout_axis, &mut tracks);
}
let total_across_size = tracks.iter().map(|t| t.across_size).sum::<f32>();
if !layout_axis.is_auto_across && layout_data.align_content == AlignContent::Stretch {
stretch_tracks_sizes(&layout_axis, &mut tracks, total_across_size);
}
calculate_track_positions(
&layout_data,
&layout_axis,
layout_bounds,
&mut tracks,
total_across_size,
);
tracks
}
fn first_anchor(
layout_data: &LayoutData,
layout_axis: &LayoutAxis,
track: &TrackData,
total_shapes_size: f32,
) -> Point {
if layout_axis.is_auto_main {
return track.anchor + layout_axis.main_v * layout_axis.padding_main_start;
}
let delta = match layout_data.justify_content {
JustifyContent::Center => (layout_axis.main_size - track.main_size) / 2.0,
JustifyContent::End => {
layout_axis.main_size - layout_axis.padding_main_end - track.main_size
}
JustifyContent::SpaceAround => {
let effective_gap =
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len()) as f32;
layout_axis.padding_main_end + f32::max(layout_axis.gap_main, effective_gap / 2.0)
}
JustifyContent::SpaceEvenly => {
let effective_gap =
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len() + 1) as f32;
layout_axis.padding_main_end + f32::max(layout_axis.gap_main, effective_gap)
}
_ => layout_axis.padding_main_start,
};
track.anchor + layout_axis.main_v * delta
}
fn next_anchor(
layout_data: &LayoutData,
layout_axis: &LayoutAxis,
child_axis: &ChildAxis,
track: &TrackData,
prev_anchor: Point,
total_shapes_size: f32,
) -> Point {
let delta = match layout_data.justify_content {
JustifyContent::SpaceBetween => {
let effective_gap =
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len() - 1) as f32;
child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
}
JustifyContent::SpaceAround => {
let effective_gap =
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len()) as f32;
child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
}
JustifyContent::SpaceEvenly => {
let effective_gap =
(layout_axis.main_space() - total_shapes_size) / (track.shapes.len() + 1) as f32;
child_axis.main_size + f32::max(layout_axis.gap_main, effective_gap)
}
_ => child_axis.main_size + layout_axis.gap_main,
};
prev_anchor + layout_axis.main_v * delta
}
fn child_position(
child: &Shape,
shape_anchor: Point,
layout_data: &LayoutData,
layout_axis: &LayoutAxis,
child_axis: &ChildAxis,
track: &TrackData,
) -> Point {
let delta = match child.layout_item {
Some(LayoutItem {
align_self: Some(align_self),
..
}) => match align_self {
AlignSelf::Center => (track.across_size - child_axis.across_size) / 2.0,
AlignSelf::End => track.across_size - child_axis.across_size,
_ => 0.0,
},
_ => match layout_data.align_items {
AlignItems::Center => (track.across_size - child_axis.across_size) / 2.0,
AlignItems::End => track.across_size - child_axis.across_size,
_ => 0.0,
},
};
shape_anchor + layout_axis.across_v * delta
}
pub fn reflow_flex_layout(
shape: &Shape,
layout_data: &LayoutData,
flex_data: &FlexData,
shapes: &HashMap<Uuid, Shape>,
bounds: &HashMap<Uuid, Bounds>,
) -> VecDeque<Modifier> {
let mut result = VecDeque::new();
let layout_bounds = &bounds.find(&shape);
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
let tracks = calculate_track_data(shape, layout_data, flex_data, layout_bounds, shapes, bounds);
for track in tracks.iter() {
let total_shapes_size = track.shapes.iter().map(|s| s.main_size).sum::<f32>();
let mut shape_anchor = first_anchor(&layout_data, &layout_axis, track, total_shapes_size);
for child_axis in track.shapes.iter() {
let child_id = child_axis.id;
let Some(child) = shapes.get(&child_id) else {
continue;
};
let position = child_position(
child,
shape_anchor,
layout_data,
&layout_axis,
child_axis,
track,
);
let child_bounds = bounds.find(child);
let delta_v = Vector::new_points(&child_bounds.nw, &position);
let (new_width, new_height) = if layout_data.is_row() {
(child_axis.main_size, child_axis.across_size)
} else {
(child_axis.across_size, child_axis.main_size)
};
let mut transform = Matrix::default();
if (new_width - child_bounds.width()).abs() > MIN_SIZE
|| (new_height - child_bounds.height()).abs() > MIN_SIZE
{
transform.post_concat(&math::resize_matrix(
layout_bounds,
&child_bounds,
new_width,
new_height,
));
}
if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE {
transform.post_concat(&Matrix::translate(delta_v));
}
result.push_back(Modifier::transform(child.id, transform));
shape_anchor = next_anchor(
&layout_data,
&layout_axis,
&child_axis,
&track,
shape_anchor,
total_shapes_size,
);
}
}
if layout_axis.is_auto_across || layout_axis.is_auto_main {
let width = layout_bounds.width();
let height = layout_bounds.height();
let auto_across_size = if layout_axis.is_auto_across {
tracks.iter().map(|track| track.across_size).sum::<f32>()
+ (tracks.len() - 1) as f32 * layout_axis.gap_across
+ layout_axis.padding_main_start
+ layout_axis.padding_main_end
} else {
0.0
};
let auto_main_size = if layout_axis.is_auto_main {
tracks
.iter()
.map(|track| {
track.shapes.iter().map(|s| s.main_size).sum::<f32>()
+ (track.shapes.len() - 1) as f32 * layout_axis.gap_main
})
.reduce(f32::max)
.unwrap_or(0.01)
+ layout_axis.padding_across_start
+ layout_axis.padding_across_end
} else {
0.0
};
let (scale_width, scale_height) = if layout_data.is_row() {
(
if layout_axis.is_auto_main {
auto_main_size / width
} else {
1.0
},
if layout_axis.is_auto_across {
auto_across_size / height
} else {
1.0
},
)
} else {
(
if layout_axis.is_auto_across {
auto_across_size / width
} else {
1.0
},
if layout_axis.is_auto_main {
auto_main_size / height
} else {
1.0
},
)
};
let parent_transform = layout_bounds
.transform_matrix()
.unwrap_or(Matrix::default());
let parent_transform_inv = &parent_transform.invert().unwrap();
let origin = parent_transform_inv.map_point(layout_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);
result.push_back(Modifier::parent(shape.id, scale));
}
result
}

View file

@ -0,0 +1,15 @@
use crate::math::Bounds;
use crate::shapes::{GridData, LayoutData, Modifier, Shape};
use std::collections::{HashMap, VecDeque};
use uuid::Uuid;
pub fn reflow_grid_layout(
_shape: &Shape,
_layout_data: &LayoutData,
_grid_data: &GridData,
_shapes: &HashMap<Uuid, Shape>,
_bounds: &HashMap<Uuid, Bounds>,
) -> VecDeque<Modifier> {
// TODO
VecDeque::new()
}

View file

@ -5,16 +5,46 @@ use crate::mem::SerializableResult;
use crate::utils::{uuid_from_u32_quartet, uuid_to_u32_quartet};
use skia::Matrix;
#[derive(PartialEq, Debug, Clone)]
pub enum Modifier {
Transform(TransformEntry),
Reflow(Uuid),
}
impl Modifier {
pub fn transform(id: Uuid, transform: Matrix) -> Self {
Modifier::Transform(TransformEntry::new(id, transform))
}
pub fn parent(id: Uuid, transform: Matrix) -> Self {
Modifier::Transform(TransformEntry::parent(id, transform))
}
pub fn reflow(id: Uuid) -> Self {
Modifier::Reflow(id)
}
}
#[derive(PartialEq, Debug, Clone)]
#[repr(C)]
pub struct TransformEntry {
pub id: Uuid,
pub transform: Matrix,
pub propagate: bool,
}
impl TransformEntry {
pub fn new(id: Uuid, transform: Matrix) -> Self {
TransformEntry { id, transform }
TransformEntry {
id,
transform,
propagate: true,
}
}
pub fn parent(id: Uuid, transform: Matrix) -> Self {
TransformEntry {
id,
transform,
propagate: false,
}
}
}
@ -40,7 +70,7 @@ impl SerializableResult for TransformEntry {
0.0,
1.0,
);
TransformEntry { id, transform }
TransformEntry::new(id, transform)
}
fn as_bytes(&self) -> Self::BytesType {
@ -75,10 +105,10 @@ mod tests {
#[test]
fn test_serialization() {
let entry = TransformEntry {
id: uuid!("550e8400-e29b-41d4-a716-446655440000"),
transform: Matrix::new_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 1.0),
};
let entry = TransformEntry::new(
uuid!("550e8400-e29b-41d4-a716-446655440000"),
Matrix::new_all(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.0, 0.0, 1.0),
);
let bytes = entry.as_bytes();