From 63666fca483fe3d41b093d78a060686648ef1246 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Mar 2025 17:14:15 +0100 Subject: [PATCH] :sparkles: Grid layout modifiers --- .../geom/shapes/grid_layout/layout_data.cljc | 8 +- frontend/src/app/render_wasm/shape.cljs | 136 ++++-- render-wasm/src/shapes/layouts.rs | 28 +- render-wasm/src/shapes/modifiers.rs | 10 +- .../src/shapes/modifiers/flex_layout.rs | 183 +++++--- .../src/shapes/modifiers/grid_layout.rs | 440 +++++++++++++++++- 6 files changed, 662 insertions(+), 143 deletions(-) diff --git a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc index d994cae10..af3f454b4 100644 --- a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc @@ -39,7 +39,7 @@ ;; ;; 5. If any track still has an infinite growth limit set its growth limit to its base size. -;; - Distribute extra space accross spaned tracks +;; - Distribute extra space accross spaned tracks ;; - Maximize tracks ;; ;; - Expand flexible tracks @@ -198,7 +198,7 @@ track-list)) -(defn add-auto-size +(defn stretch-tracks [track-list add-size] (->> track-list (mapv (fn [{:keys [type size max-size] :as track}] @@ -493,11 +493,11 @@ column-tracks (cond-> column-tracks (= :stretch (:layout-justify-content parent)) - (add-auto-size column-add-auto)) + (stretch-tracks column-add-auto)) row-tracks (cond-> row-tracks (= :stretch (:layout-align-content parent)) - (add-auto-size row-add-auto)) + (stretch-tracks row-add-auto)) column-total-size (tracks-total-size column-tracks) row-total-size (tracks-total-size row-tracks) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 401f0b04f..75da5913e 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -8,6 +8,7 @@ (:require [app.common.transit :as t] [app.common.types.shape :as shape] + [app.common.types.shape.layout :as ctl] [app.render-wasm.api :as api] [beicon.v2.core :as rx] [clojure.core :as c] @@ -110,54 +111,99 @@ (defn- set-wasm-attrs [self k v] (when ^boolean shape/*wasm-sync* - (api/use-shape (:id self)) - (let [pending (case k - :parent-id (api/set-parent-id v) - :type (api/set-shape-type v) - :bool-type (api/set-shape-bool-type v) - :selrect (api/set-shape-selrect v) - :show-content (if (= (:type self) :frame) - (api/set-shape-clip-content (not v)) - (api/set-shape-clip-content false)) - :rotation (api/set-shape-rotation v) - :transform (api/set-shape-transform v) - :fills (into [] (api/set-shape-fills v)) - :strokes (into [] (api/set-shape-strokes v)) - :blend-mode (api/set-shape-blend-mode v) - :opacity (api/set-shape-opacity v) - :hidden (api/set-shape-hidden v) - :shapes (api/set-shape-children v) - :blur (api/set-shape-blur v) - :shadow (api/set-shape-shadows v) - :constraints-h (api/set-constraints-h v) - :constraints-v (api/set-constraints-v v) - :svg-attrs (when (= (:type self) :path) - (api/set-shape-path-attrs v)) - :masked-group (when (and (= (:type self) :group) (:masked-group self)) - (api/set-masked (:masked-group self))) - :content (cond - (= (:type self) :path) - (api/set-shape-path-content v) + (binding [shape/*wasm-sync* false] + (let [self (assoc self k v)] + (api/use-shape (:id self)) + (let [pending (case k + :parent-id (api/set-parent-id v) + :type (api/set-shape-type v) + :bool-type (api/set-shape-bool-type v) + :selrect (api/set-shape-selrect v) + :show-content (if (= (:type self) :frame) + (api/set-shape-clip-content (not v)) + (api/set-shape-clip-content false)) + :rotation (api/set-shape-rotation v) + :transform (api/set-shape-transform v) + :fills (into [] (api/set-shape-fills v)) + :strokes (into [] (api/set-shape-strokes v)) + :blend-mode (api/set-shape-blend-mode v) + :opacity (api/set-shape-opacity v) + :hidden (api/set-shape-hidden v) + :shapes (api/set-shape-children v) + :blur (api/set-shape-blur v) + :shadow (api/set-shape-shadows v) + :constraints-h (api/set-constraints-h v) + :constraints-v (api/set-constraints-v v) - (= (:type self) :svg-raw) - (api/set-shape-svg-raw-content (api/get-static-markup self)) + :svg-attrs + (when (= (:type self) :path) + (api/set-shape-path-attrs v)) - (= (:type self) :text) - (api/set-shape-text-content v)) - nil)] + :masked-group + (when (and (= (:type self) :group) (:masked-group self)) + (api/set-masked (:masked-group self))) - ;; TODO: set-wasm-attrs is called twice with every set - (if (and pending (seq pending)) - (->> (rx/from pending) - (rx/mapcat identity) - (rx/reduce conj []) - (rx/subs! (fn [_] - (api/update-shape-tiles) - (api/clear-drawing-cache) - (api/request-render "set-wasm-attrs-pending")))) - (do - (api/update-shape-tiles) - (api/request-render "set-wasm-attrs")))))) + :content + (cond + (= (:type self) :path) + (api/set-shape-path-content v) + + (= (:type self) :svg-raw) + (api/set-shape-svg-raw-content (api/get-static-markup self)) + + (= (:type self) :text) + (into [] (api/set-shape-text-content v))) + + (:layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-absolute + :layout-item-z-index) + (api/set-layout-child self) + + (:layout-grid-rows + :layout-grid-columns + :layout-grid-cells) + (when (ctl/grid-layout? self) + (api/set-grid-layout self)) + + (:layout + :layout-flex-dir + :layout-gap-type + :layout-gap + :layout-align-items + :layout-align-content + :layout-justify-items + :layout-justify-content + :layout-wrap-type + :layout-padding-type + :layout-padding) + (cond + (ctl/grid-layout? self) + (api/set-grid-layout self) + + (ctl/flex-layout? self) + (api/set-flex-layout self)) + + nil)] + + ;; TODO: set-wasm-attrs is called twice with every set + (if (and pending (seq pending)) + (->> (rx/from pending) + (rx/mapcat identity) + (rx/reduce conj []) + (rx/subs! (fn [_] + (api/update-shape-tiles) + (api/clear-drawing-cache) + (api/request-render "set-wasm-attrs-pending")))) + (do + (api/update-shape-tiles) + (api/request-render "set-wasm-attrs")))))))) (defn- impl-assoc [self k v] (set-wasm-attrs self k v) diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs index f1c1af2d7..b6fea8b85 100644 --- a/render-wasm/src/shapes/layouts.rs +++ b/render-wasm/src/shapes/layouts.rs @@ -44,7 +44,7 @@ impl GridDirection { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum AlignItems { Start, End, @@ -64,7 +64,7 @@ impl AlignItems { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum AlignContent { Start, End, @@ -90,7 +90,7 @@ impl AlignContent { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum JustifyItems { Start, End, @@ -110,7 +110,7 @@ impl JustifyItems { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum JustifyContent { Start, End, @@ -151,7 +151,7 @@ impl WrapType { } } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum GridTrackType { Percent, Flex, @@ -173,8 +173,8 @@ impl GridTrackType { #[derive(Debug, Clone, PartialEq)] pub struct GridTrack { - track_type: GridTrackType, - value: f32, + pub track_type: GridTrackType, + pub value: f32, } impl GridTrack { @@ -188,13 +188,13 @@ impl GridTrack { #[derive(Debug, Clone, PartialEq)] pub struct GridCell { - row: i32, - row_span: i32, - column: i32, - column_span: i32, - align_self: Option, - justify_self: Option, - shape: Option, + pub row: i32, + pub row_span: i32, + pub column: i32, + pub column_span: i32, + pub align_self: Option, + pub justify_self: Option, + pub shape: Option, } impl GridCell { diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index c9ef10ee7..0c7c2d658 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -254,11 +254,11 @@ pub fn propagate_modifiers(state: &State, modifiers: Vec) -> Vec 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); - // } + 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); + } reflown.insert(*id); } layout_reflows = Vec::new(); diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index e4edff1ff..97e161638 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -366,91 +366,140 @@ fn calculate_track_positions( 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 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; + let (real_margin, real_gap) = match align_content { + AlignContent::End => ( + layout_axis.across_size - total_across_size_gap - layout_axis.padding_across_end, + layout_axis.gap_across, + ), - 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 => ( + (layout_axis.across_size - total_across_size_gap) / 2.0, + 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( + AlignContent::SpaceBetween => ( + layout_axis.padding_across_start, + 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); - } + (effective_gap / 2.0, 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); - } + ( + layout_axis.padding_across_start + effective_gap, + effective_gap, + ) } - _ => { - let mut next_anchor = - layout_bounds.nw + layout_axis.across_v * layout_axis.padding_across_start; + _ => (layout_axis.padding_across_start, layout_axis.gap_across), + }; - 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); - } - } + let mut next_anchor = layout_bounds.nw + layout_axis.across_v * real_margin; + + for track in tracks.iter_mut() { + track.anchor = next_anchor; + next_anchor = next_anchor + layout_axis.across_v * real_gap; } + + /* + 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( diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index 2339c4d1d..3af603e8b 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -1,6 +1,9 @@ #![allow(dead_code, unused_variables)] -use crate::math::{Bounds, Matrix, Point, Vector, VectorExt}; -use crate::shapes::{GridData, LayoutData, Modifier, Shape}; +use crate::math::{intersect_rays, Bounds, Matrix, Point, Ray, Vector, VectorExt}; +use crate::shapes::{ + AlignContent, AlignItems, AlignSelf, GridCell, GridData, GridTrack, GridTrackType, + JustifyContent, JustifyItems, JustifySelf, LayoutData, LayoutItem, Modifier, Shape, +}; use crate::uuid::Uuid; use std::collections::{HashMap, VecDeque}; @@ -9,10 +12,365 @@ use super::common::GetBounds; const MIN_SIZE: f32 = 0.01; const MAX_SIZE: f32 = f32::INFINITY; +#[derive(Debug)] struct CellData<'a> { shape: &'a Shape, - main_size: f32, - across_size: f32, + anchor: Point, + width: f32, + height: f32, + align_self: Option, + justify_self: Option, +} + +#[derive(Debug)] +struct TrackData { + track_type: GridTrackType, + value: f32, + size: f32, + max_size: f32, + anchor_start: Point, + anchor_end: Point, +} + +fn calculate_tracks( + is_column: bool, + layout_data: &LayoutData, + grid_data: &GridData, + layout_bounds: &Bounds, + cells: &Vec, + shapes: &HashMap, + bounds: &HashMap, +) -> Vec { + let layout_size = if is_column { + layout_bounds.width() - layout_data.padding_left - layout_data.padding_right + } else { + layout_bounds.height() - layout_data.padding_top - layout_data.padding_bottom + }; + + let grid_tracks = if is_column { + &grid_data.columns + } else { + &grid_data.rows + }; + + let mut tracks = init_tracks(grid_tracks, layout_size); + set_auto_base_size(is_column, &mut tracks, cells, shapes, bounds); + + set_auto_multi_span(is_column, layout_data, &layout_bounds, &mut tracks); + set_flex_multi_span(is_column, layout_data, &layout_bounds, &mut tracks); + + set_fr_value(is_column, layout_data, &mut tracks, layout_size, 0.0); + stretch_tracks(is_column, layout_data, &mut tracks, layout_size); + assign_anchors(is_column, layout_data, &layout_bounds, &mut tracks); + + return tracks; +} + +fn init_tracks(track: &Vec, size: f32) -> Vec { + track + .iter() + .map(|t| { + let (size, max_size) = match t.track_type { + GridTrackType::Fixed => (t.value, t.value), + GridTrackType::Percent => (size * t.value / 100.0, size * t.value / 100.0), + _ => (MIN_SIZE, MAX_SIZE), + }; + TrackData { + track_type: t.track_type, + value: t.value, + size, + max_size, + anchor_start: Point::default(), + anchor_end: Point::default(), + } + }) + .collect() +} + +// Go through cells to adjust auto sizes for span=1. Base is the max of its children +fn set_auto_base_size( + column: bool, + tracks: &mut Vec, + cells: &Vec, + shapes: &HashMap, + bounds: &HashMap, +) { + for cell in cells { + let (prop, prop_span) = if column { + (cell.column, cell.column_span) + } else { + (cell.row, cell.row_span) + }; + + if prop_span != 1 { + continue; + } + + let track = &mut tracks[(prop - 1) as usize]; + + if track.track_type != GridTrackType::Auto && track.track_type != GridTrackType::Flex { + continue; + } + + let Some(shape) = cell.shape.and_then(|id| shapes.get(&id)) else { + continue; + }; + + let bounds = bounds.find(shape); + + let shape_size = if column { + bounds.width() + } else { + bounds.height() + }; + + let min_size = if column && shape.is_layout_horizontal_fill() { + shape.layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE) + } else if !column && shape.is_layout_vertical_fill() { + shape.layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE) + } else { + shape_size + }; + + track.size = f32::max(track.size, min_size); + } +} + +// Adjust multi-spaned cells with no flex columns +fn set_auto_multi_span( + _column: bool, + _layout_data: &LayoutData, + _layout_bounds: &Bounds, + _tracks: &mut Vec, +) { + // Sort descendant order of prop-span + // Remove groups with flex (will be set in flex_multi_span) + // Retrieve teh value we need to distribute (fixed cell size minus gaps) + // Distribute the size between the tracks that already have a set value + // Distribute the space between auto tracks + // If we still have more space we distribute equally between all tracks +} + +// Adjust multi-spaned cells with flex columns +fn set_flex_multi_span( + _column: bool, + _layout_data: &LayoutData, + _layout_bounds: &Bounds, + _tracks: &mut Vec, +) { + // Sort descendant order of prop-span + // Remove groups without flex (will be set in flex_auto_span) + // Retrieve the value that we need to distribute (fixed size of cell minus gaps) + // Distribute the size first between the tracks that have the fixed size + // When finished we distribute equally between the the rest of the tracks +} + +// Calculate the `fr` unit and adjust the size +fn set_fr_value( + column: bool, + layout_data: &LayoutData, + tracks: &mut Vec, + layout_size: f32, + min_fr_size: f32, +) { + let tot_gap: f32 = if column { + layout_data.column_gap * (tracks.len() - 1) as f32 + } else { + layout_data.row_gap * (tracks.len() - 1) as f32 + }; + + // Total size already used + let tot_size: f32 = tracks + .iter() + .filter(|t| t.track_type != GridTrackType::Flex) + .map(|t| t.size) + .sum::() + + tot_gap; + + // Get the total of frs to divide the space into + let tot_frs: f32 = tracks + .iter() + .filter(|t| t.track_type == GridTrackType::Flex) + .map(|t| t.value) + .sum(); + + // Divide the space between FRS + let fr = f32::max(min_fr_size, (layout_size - tot_size) / tot_frs); + + // Assign the space to the FRS + tracks + .iter_mut() + .filter(|t| t.track_type == GridTrackType::Flex) + .for_each(|t| t.size = f32::max(t.size, fr * t.value)); +} + +fn stretch_tracks( + column: bool, + layout_data: &LayoutData, + tracks: &mut Vec, + layout_size: f32, +) { + if (column && layout_data.justify_content != JustifyContent::Stretch) + || (!column && layout_data.align_content != AlignContent::Stretch) + { + return; + } + + let tot_gap: f32 = if column { + layout_data.column_gap * (tracks.len() - 1) as f32 + } else { + layout_data.row_gap * (tracks.len() - 1) as f32 + }; + + // Total size already used + let tot_size: f32 = tracks.iter().map(|t| t.size).sum::() + tot_gap; + + let auto_tracks = tracks + .iter_mut() + .filter(|t| t.track_type == GridTrackType::Auto) + .count() as f32; + + let free_space = layout_size - tot_size; + let add_size = free_space / auto_tracks; + + // Assign the space to the FRS + tracks + .iter_mut() + .filter(|t| t.track_type == GridTrackType::Auto) + .for_each(|t| t.size = f32::min(t.max_size, t.size + add_size)); +} + +fn justify_to_align(justify: JustifyContent) -> AlignContent { + match justify { + JustifyContent::Start => AlignContent::Start, + JustifyContent::End => AlignContent::End, + JustifyContent::Center => AlignContent::Center, + JustifyContent::SpaceBetween => AlignContent::SpaceBetween, + JustifyContent::SpaceAround => AlignContent::SpaceAround, + JustifyContent::SpaceEvenly => AlignContent::SpaceEvenly, + JustifyContent::Stretch => AlignContent::Stretch, + } +} + +fn assign_anchors( + column: bool, + layout_data: &LayoutData, + layout_bounds: &Bounds, + tracks: &mut Vec, +) { + let tot_track_length = tracks.iter().map(|t| t.size).sum::(); + + let mut cursor = layout_bounds.nw; + + let (v, gap, size, padding_start, padding_end, align) = if column { + ( + layout_bounds.hv(1.0), + layout_data.column_gap, + layout_bounds.width(), + layout_data.padding_left, + layout_data.padding_right, + justify_to_align(layout_data.justify_content), + ) + } else { + ( + layout_bounds.vv(1.0), + layout_data.row_gap, + layout_bounds.height(), + layout_data.padding_top, + layout_data.padding_bottom, + layout_data.align_content, + ) + }; + + let tot_gap = gap * (tracks.len() - 1) as f32; + let tot_size = tot_track_length + tot_gap; + let padding = padding_start + padding_end; + let pad_size = size - padding; + + let (real_margin, real_gap) = match align { + AlignContent::End => (size - padding_end - tot_size, gap), + AlignContent::Center => ((size - tot_size) / 2.0, gap), + AlignContent::SpaceAround => { + let effective_gap = (pad_size - tot_track_length) / tracks.len() as f32; + (padding_start + effective_gap / 2.0, effective_gap) + } + AlignContent::SpaceBetween => ( + padding_start, + f32::max( + gap, + (pad_size - tot_track_length) / (tracks.len() - 1) as f32, + ), + ), + _ => (padding_start + 0.0, gap), + }; + + cursor = cursor + (v * real_margin); + + for track in tracks { + track.anchor_start = cursor; + track.anchor_end = cursor + (v * track.size); + cursor = track.anchor_end + (v * real_gap); + } +} + +fn cell_bounds( + layout_bounds: &Bounds, + column_start: Point, + column_end: Point, + row_start: Point, + row_end: Point, +) -> Option { + let hv = layout_bounds.hv(1.0); + let vv = layout_bounds.vv(1.0); + let nw = intersect_rays(&Ray::new(column_start, vv), &Ray::new(row_start, hv))?; + let ne = intersect_rays(&Ray::new(column_end, vv), &Ray::new(row_start, hv))?; + let sw = intersect_rays(&Ray::new(column_start, vv), &Ray::new(row_end, hv))?; + let se = intersect_rays(&Ray::new(column_end, vv), &Ray::new(row_end, hv))?; + Some(Bounds::new(nw, ne, se, sw)) +} + +fn create_cell_data<'a>( + layout_bounds: &Bounds, + shapes: &'a HashMap, + cells: &Vec, + column_tracks: &Vec, + row_tracks: &Vec, +) -> Vec> { + let mut result = Vec::>::new(); + + for cell in cells { + let Some(shape_id) = cell.shape else { + continue; + }; + let Some(shape) = shapes.get(&shape_id) else { + continue; + }; + + let column_start = (cell.column - 1) as usize; + let column_end = (cell.column + cell.column_span - 2) as usize; + let row_start = (cell.row - 1) as usize; + let row_end = (cell.row + cell.row_span - 2) as usize; + let Some(cell_bounds) = cell_bounds( + layout_bounds, + column_tracks[column_start].anchor_start, + column_tracks[column_end].anchor_end, + row_tracks[row_start].anchor_start, + row_tracks[row_end].anchor_end, + ) else { + continue; + }; + + result.push(CellData { + shape, + anchor: cell_bounds.nw, + width: cell_bounds.width(), + height: cell_bounds.height(), + align_self: cell.align_self, + justify_self: cell.justify_self, + }); + } + + result } fn calculate_cell_data<'a>( @@ -22,11 +380,70 @@ fn calculate_cell_data<'a>( shapes: &'a HashMap, bounds: &HashMap, ) -> Vec> { - todo!() + let result: Vec> = vec![]; + + let layout_bounds = bounds.find(shape); + + let column_tracks = calculate_tracks( + true, + layout_data, + grid_data, + &layout_bounds, + &grid_data.cells, + shapes, + bounds, + ); + let row_tracks = calculate_tracks( + false, + layout_data, + grid_data, + &layout_bounds, + &grid_data.cells, + shapes, + bounds, + ); + + create_cell_data( + &layout_bounds, + shapes, + &grid_data.cells, + &column_tracks, + &row_tracks, + ) } -fn child_position(child_bounds: &Bounds, cell: &CellData) -> Point { - todo!() +fn child_position( + layout_bounds: &Bounds, + layout_data: &LayoutData, + child_bounds: &Bounds, + layout_item: Option, + cell: &CellData, +) -> Point { + let hv = layout_bounds.hv(1.0); + let vv = layout_bounds.vv(1.0); + + let margin_left = layout_item.map(|i| i.margin_left).unwrap_or(0.0); + let margin_top = layout_item.map(|i| i.margin_top).unwrap_or(0.0); + let margin_right = layout_item.map(|i| i.margin_right).unwrap_or(0.0); + let margin_bottom = layout_item.map(|i| i.margin_bottom).unwrap_or(0.0); + + cell.anchor + + vv * match (cell.align_self, layout_data.align_items) { + (Some(AlignSelf::Start), _) => margin_left, + (Some(AlignSelf::Center), _) => (cell.height - child_bounds.height()) / 2.0, + (Some(AlignSelf::End), _) => margin_right + cell.height - child_bounds.height(), + (_, AlignItems::Center) => (cell.height - child_bounds.height()) / 2.0, + (_, AlignItems::End) => margin_right + cell.height - child_bounds.height(), + _ => margin_left, + } + + hv * match (cell.justify_self, layout_data.justify_items) { + (Some(JustifySelf::Start), _) => margin_top, + (Some(JustifySelf::Center), _) => (cell.width - child_bounds.width()) / 2.0, + (Some(JustifySelf::End), _) => margin_bottom + cell.width - child_bounds.width(), + (_, JustifyItems::Center) => (cell.width - child_bounds.width()) / 2.0, + (_, JustifyItems::End) => margin_bottom + cell.width - child_bounds.width(), + _ => margin_top, + } } pub fn reflow_grid_layout<'a>( @@ -39,11 +456,18 @@ pub fn reflow_grid_layout<'a>( let mut result = VecDeque::new(); let cells = calculate_cell_data(shape, layout_data, grid_data, shapes, bounds); + let layout_bounds = bounds.find(shape); for cell in cells.iter() { let child = cell.shape; let child_bounds = bounds.find(child); - let position = child_position(&child_bounds, cell); + let position = child_position( + &layout_bounds, + &layout_data, + &child_bounds, + child.layout_item, + cell, + ); let mut transform = Matrix::default(); let delta_v = Vector::new_points(&child_bounds.nw, &position);