diff --git a/common/src/app/common/geom/shapes/flex_layout/params.cljc b/common/src/app/common/geom/shapes/flex_layout/params.cljc index 57a1fab63d..0d7d67ba1b 100644 --- a/common/src/app/common/geom/shapes/flex_layout/params.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/params.cljc @@ -88,8 +88,8 @@ parent-selrect (:selrect parent) padding (when (and (not (nil? parent)) (> (count shapes) 0)) - {:p1 (- min-y (:y1 parent-selrect)) - :p2 (- min-x (:x1 parent-selrect))})] + {:p1 (max 0 (- min-y (:y1 parent-selrect))) + :p2 (max 0 (- min-x (:x1 parent-selrect)))})] (cond-> {:layout-flex-dir direction :layout-gap layout-gap} (not (nil? padding)) diff --git a/common/src/app/common/geom/shapes/grid_layout/params.cljc b/common/src/app/common/geom/shapes/grid_layout/params.cljc index ee3a779942..45db3d7b07 100644 --- a/common/src/app/common/geom/shapes/grid_layout/params.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/params.cljc @@ -154,8 +154,10 @@ layout-grid-columns (mapv (constantly ctl/default-track-value) cols) parent-childs-vector (gpt/to-vec (gpo/origin (:points parent)) (gpt/point all-shapes-rect)) - p-left (:x parent-childs-vector) - p-top (:y parent-childs-vector)] + ;; Children poking out of the frame (or off by float noise) can't + ;; give a negative padding. + p-left (max 0 (:x parent-childs-vector)) + p-top (max 0 (:y parent-childs-vector))] (-> {:layout-grid-columns layout-grid-columns :layout-grid-rows layout-grid-rows diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 4ddaf0eded..fd8a64e185 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -1265,6 +1265,18 @@ geom-keys) base))) +(defn- commit-finalized-text + "Commits the text finalization, taking the final geometry from the state at + emit time so it keeps what the parent layout set; the rest comes from `state`." + [it state id opts] + (ptk/reify ::commit-finalized-text + ptk/WatchEvent + (watch [_ current-state _] + (let [resize-geom (some-> (dsh/lookup-shape current-state id) + (select-keys [:selrect :points :width :height])) + opts (assoc opts :resize-geom resize-geom)] + (rx/of (dch/commit-changes (build-finalize-commit-changes it state id opts))))))) + (defn v2-update-text-shape-content [id content & {:keys [update-name? name finalize? save-undo? original-content] :or {update-name? false name nil finalize? false save-undo? true original-content nil}}] @@ -1303,13 +1315,8 @@ ;; modifier machinery, made auto-width typing very laggy. new-size (when (and finalize? (not= :fixed (:grow-type shape))) (dwwt/get-wasm-text-new-size shape content)) - ;; Also compute the resized geometry for the finalize commit; the - ;; async `apply-wasm-modifiers` below never updates this `state`. resize-modifiers (when (some? new-size) (dwwt/resize-wasm-text-modifiers shape content)) - resize-geom (when resize-modifiers - (-> (gsh/transform-shape shape (get-in resize-modifiers [id :modifiers])) - (select-keys [:selrect :points :width :height]))) ;; New shapes: single undo on finalize only (no per-keystroke undo) effective-save-undo? (if new-shape? finalize? save-undo?) effective-stack-undo? (and new-shape? finalize?) @@ -1358,7 +1365,8 @@ :stack-undo? effective-stack-undo? :undo-group (when new-shape? id)}) - ;; Push the auto-grow geometry to WASM/app state; the commit persists it via `resize-geom`. + ;; Push the auto-grow geometry to WASM/app state, reflowing any parent + ;; layout; `commit-finalized-text` reads the result back to persist it. ;; Skipped for a no-op finalize: applying it would record an undo transaction. (when (and (some? resize-modifiers) (not finalize-no-op?)) (dwm/apply-wasm-modifiers resize-modifiers {:undo-group (when new-shape? id)}))) @@ -1380,22 +1388,20 @@ (rx/concat (if (and content-has-text? (not finalize-no-op?)) (rx/of - (dch/commit-changes - (build-finalize-commit-changes it state id - {:new-shape? new-shape? - :content-has-text? content-has-text? - :content content - ;; Undo baseline for the finalize commit: restore the - ;; content as it was right before this commit. For existing - ;; shapes that's `prev-content`; using the (unset, nil) - ;; `original-content` here wiped `:content` to nil on undo, - ;; which emptied the shape and crashed the WASM editor's - ;; select-all on 0 paragraphs. New shapes keep the previous - ;; behavior (their create is bundled in the undo group). - :original-content (if new-shape? original-content prev-content) - :update-name? update-name? - :name name - :resize-geom resize-geom}))) + (commit-finalized-text it state id + {:new-shape? new-shape? + :content-has-text? content-has-text? + :content content + ;; Undo baseline for the finalize commit: restore the + ;; content as it was right before this commit. For existing + ;; shapes that's `prev-content`; using the (unset, nil) + ;; `original-content` here wiped `:content` to nil on undo, + ;; which emptied the shape and crashed the WASM editor's + ;; select-all on 0 paragraphs. New shapes keep the previous + ;; behavior (their create is bundled in the undo group). + :original-content (if new-shape? original-content prev-content) + :update-name? update-name? + :name name})) (rx/empty)) (rx/of (dwt/finish-transform) (fn [state] diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index 1bb6b9d3cb..c8437dae23 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -451,6 +451,7 @@ fn reflow_shape( reflown: &mut HashSet, entries: &mut VecDeque, bounds: &mut HashMap, + grow_from_right: bool, ) -> Result<()> { let Some(shape) = state.shapes.get(id) else { return Ok(()); @@ -463,18 +464,61 @@ fn reflow_shape( }; 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)?; + let mut children = flex_layout::reflow_flex_layout( + shape, + layout_data, + flex_data, + shapes, + bounds, + grow_from_right, + )?; entries.append(&mut children); } else 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)?; + let mut children = grid_layout::reflow_grid_layout( + shape, + layout_data, + grid_data, + shapes, + bounds, + grow_from_right, + )?; entries.append(&mut children); } reflown.insert(*id); Ok(()) } +/// Layouts that auto-size from their right edge: the ancestors of the resized +/// texts, when every text resized inside them is rtl auto-width. +fn rtl_grow_layouts(state: &State, modifiers: &[TransformEntry]) -> HashSet { + let mut rtl = HashSet::new(); + let mut ltr = HashSet::new(); + + for entry in modifiers { + let Some(shape) = state.shapes.get(&entry.id) else { + continue; + }; + let Type::Text(text_content) = &shape.shape_type else { + continue; + }; + let Some(parent_id) = shape.parent_id else { + continue; + }; + let target = if text_content.grow_type() == GrowType::AutoWidth && text_content.is_rtl() { + &mut rtl + } else { + &mut ltr + }; + target.extend(shapes::all_with_ancestors( + &[parent_id], + &state.shapes, + true, + )); + } + + rtl.difference(<r).copied().collect() +} + /// Propagates a set of transforms through the shape tree, returning one /// transform per affected shape. /// @@ -498,6 +542,7 @@ pub fn propagate_modifiers( .collect(); let shapes = &state.shapes; + let rtl_layouts = rtl_grow_layouts(state, modifiers); let mut modifiers = HashMap::::new(); let mut bounds = HashMap::::new(); let mut reflown = HashSet::::new(); @@ -569,7 +614,14 @@ pub fn propagate_modifiers( if reflown.contains(id) { continue; } - reflow_shape(id, state, &mut reflown, &mut entries, &mut bounds_temp)?; + reflow_shape( + id, + state, + &mut reflown, + &mut entries, + &mut bounds_temp, + rtl_layouts.contains(id), + )?; } } @@ -934,4 +986,156 @@ mod tests { assert_eq!(bounds.width(), 4.0); assert_eq!(bounds.height(), 4.0); } + + fn directed_text(direction: skia_safe::textlayout::TextDirection, width: f32) -> Type { + let span = TextSpan::new( + "hello".to_string(), + FontFamily::new(Uuid::nil(), 400, FontStyle::Normal), + 14.0, + 1.2, + 0.0, + None, + None, + direction, + 400, + Uuid::nil(), + vec![], + ); + let mut content = TextContent::new( + math::Rect::from_xywh(100.0, 50.0, 60.0, 20.0), + GrowType::AutoWidth, + ); + content.add_paragraph(Paragraph::new( + TextAlign::Right, + direction, + None, + None, + 1.2, + 0.0, + vec![span], + )); + // The renderer has already measured the grown text. + content.size.width = width; + content.size.height = 20.0; + Type::Text(content) + } + + /// An auto-width flex board wrapping one auto-width text, both at + /// x = 100..160, after the text grows to 120: returns (board, text) bounds. + fn grow_text_in_auto_board( + direction: skia_safe::textlayout::TextDirection, + ) -> (Bounds, Bounds) { + let board_id = Uuid::new_v4(); + let text_id = Uuid::new_v4(); + let mut state = State::new(); + state.shapes.initialize(10); + { + let text = state.shapes.add_shape(text_id); + text.set_selrect(100.0, 50.0, 160.0, 70.0); + text.set_shape_type(directed_text(direction, 120.0)); + text.set_parent(board_id); + } + { + let board = state.shapes.add_shape(board_id); + board.set_shape_type(Type::Frame(Frame::default())); + board.set_selrect(100.0, 50.0, 160.0, 70.0); + board.add_child(text_id); + board.set_flex_layout_data( + FlexDirection::Row, + 0.0, + 0.0, + AlignItems::Start, + AlignContent::Start, + JustifyItems::Start, + JustifyContent::Start, + WrapType::NoWrap, + 0.0, + 0.0, + 0.0, + 0.0, + ); + board.set_flex_layout_child_data( + 0.0, + 0.0, + 0.0, + 0.0, + Sizing::Auto, + Sizing::Auto, + None, + None, + None, + None, + None, + false, + None, + ); + } + + // The resize the frontend sends: rtl grows from the top-right corner. + let text_bounds = state.shapes.get(&text_id).unwrap().bounds(); + let anchor = if direction == skia_safe::textlayout::TextDirection::RTL { + text_bounds.ne + } else { + text_bounds.nw + }; + let resize = math::resize_matrix_from(&text_bounds, &text_bounds, 120.0, 20.0, anchor); + let entry = TransformEntry::from_input(text_id, resize); + let result = propagate_modifiers(&state, &[entry], PixelPrecision::Disabled).unwrap(); + + let bounds_of = |id: Uuid| { + let transform = result + .iter() + .find(|entry| entry.id == id) + .map(|entry| entry.transform) + .unwrap_or_default(); + state + .shapes + .get(&id) + .unwrap() + .bounds() + .transform(&transform) + }; + (bounds_of(board_id), bounds_of(text_id)) + } + + #[test] + fn test_auto_board_grows_left_with_rtl_text() { + let (board, text) = grow_text_in_auto_board(skia_safe::textlayout::TextDirection::RTL); + assert!( + is_close_to(board.ne.x, 160.0), + "board right edge {}", + board.ne.x + ); + assert!( + is_close_to(board.nw.x, 40.0), + "board left edge {}", + board.nw.x + ); + assert!(is_close_to(text.nw.x, 40.0), "text left edge {}", text.nw.x); + assert!( + is_close_to(text.ne.x, 160.0), + "text right edge {}", + text.ne.x + ); + } + + #[test] + fn test_auto_board_grows_right_with_ltr_text() { + let (board, text) = grow_text_in_auto_board(skia_safe::textlayout::TextDirection::LTR); + assert!( + is_close_to(board.nw.x, 100.0), + "board left edge {}", + board.nw.x + ); + assert!( + is_close_to(board.ne.x, 220.0), + "board right edge {}", + board.ne.x + ); + assert!( + is_close_to(text.nw.x, 100.0), + "text left edge {}", + text.nw.x + ); + } } diff --git a/render-wasm/src/shapes/modifiers/common.rs b/render-wasm/src/shapes/modifiers/common.rs index 475e8812f7..53d9573c01 100644 --- a/render-wasm/src/shapes/modifiers/common.rs +++ b/render-wasm/src/shapes/modifiers/common.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use crate::math::Bounds; +use crate::error::{Error, Result}; +use crate::math::{Bounds, Matrix}; use crate::shapes::Shape; use crate::uuid::Uuid; @@ -13,3 +14,31 @@ impl GetBounds for HashMap { self.get(&shape.id).copied().unwrap_or(shape.bounds()) } } + +/// Scale that auto-sizes a layout along its own axes, keeping its top-right +/// corner when `from_right` (rtl text growth) and its top-left one otherwise. +pub fn auto_size_matrix( + layout_bounds: &Bounds, + scale_width: f32, + scale_height: f32, + from_right: bool, +) -> Result { + let parent_transform = layout_bounds.transform_matrix().unwrap_or_default(); + + let parent_transform_inv = &parent_transform.invert().ok_or(Error::CriticalError( + "Failed to invert parent transform".to_string(), + ))?; + let anchor = if from_right { + layout_bounds.ne + } else { + layout_bounds.nw + }; + let origin = parent_transform_inv.map_point(anchor); + + 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); + Ok(scale) +} diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index 5bdd64e488..47c47afdc2 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, Result}; +use crate::error::Result; use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt}; use crate::shapes::{ AlignContent, AlignItems, AlignSelf, ConstraintH, ConstraintV, FlexData, JustifyContent, @@ -9,7 +9,7 @@ use crate::uuid::Uuid; use std::collections::{HashMap, VecDeque}; -use super::common::GetBounds; +use super::common::{self, GetBounds}; const MIN_SIZE: f32 = 0.01; const MAX_SIZE: f32 = f32::INFINITY; @@ -610,83 +610,16 @@ pub fn reflow_flex_layout( flex_data: &FlexData, shapes: ShapesPoolRef, bounds: &mut HashMap, + grow_from_right: bool, ) -> Result> { 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 + s.margin_main_start + s.margin_main_end) - .sum::(); - 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 = &child_axis.bounds; - let delta_v = Vector::new_points(&child_bounds.nw, &position); - - let (new_width, new_height) = if flex_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(); - - let mut force_reflow = false; - if (new_width - child_bounds.width()).abs() > MIN_SIZE - || (new_height - child_bounds.height()).abs() > MIN_SIZE - { - // When the child is fill we need to force a reflow - force_reflow = true; - 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)); - } - - // Skip identity: propagating it fans out through the whole subtree. - if !math::identitish(&transform) { - result.push_back(Modifier::transform_propagate(child.id, transform)); - if child.has_layout() { - result.push_back(Modifier::reflow(child.id, force_reflow)); - } - } - - 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 { + // Sized before placing children: a layout growing from the right moves its + // left edge, and the children have to follow it. + let auto_size = if layout_axis.is_auto_across || layout_axis.is_auto_main { let width = layout_bounds.width(); let height = layout_bounds.height(); @@ -747,21 +680,90 @@ pub fn reflow_flex_layout( ) }; - let parent_transform = layout_bounds.transform_matrix().unwrap_or_default(); + let scale = + common::auto_size_matrix(layout_bounds, scale_width, scale_height, grow_from_right)?; + Some((scale, layout_bounds.transform(&scale))) + } else { + None + }; + let shift = auto_size + .as_ref() + .filter(|_| grow_from_right) + .map(|(_, after)| Vector::new_points(&layout_bounds.nw, &after.nw)) + .unwrap_or_else(|| Vector::new(0.0, 0.0)); - let parent_transform_inv = &parent_transform.invert().ok_or(Error::CriticalError( - "Failed to invert parent transform".to_string(), - ))?; - let origin = parent_transform_inv.map_point(layout_bounds.nw); + for track in tracks.iter() { + let total_shapes_size = track + .shapes + .iter() + .map(|s| s.main_size + s.margin_main_start + s.margin_main_end) + .sum::(); + let mut shape_anchor = first_anchor(layout_data, &layout_axis, track, total_shapes_size); - 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); + for child_axis in track.shapes.iter() { + let child_id = child_axis.id; + let Some(child) = shapes.get(&child_id) else { + continue; + }; - let layout_bounds_after = layout_bounds.transform(&scale); + let position = child_position( + child, + shape_anchor, + layout_data, + &layout_axis, + child_axis, + track, + ); + let child_bounds = &child_axis.bounds; + let delta_v = Vector::new_points(&child_bounds.nw, &position); + let (new_width, new_height) = if flex_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(); + + let mut force_reflow = false; + if (new_width - child_bounds.width()).abs() > MIN_SIZE + || (new_height - child_bounds.height()).abs() > MIN_SIZE + { + // When the child is fill we need to force a reflow + force_reflow = true; + transform.post_concat(&math::resize_matrix( + layout_bounds, + child_bounds, + new_width, + new_height, + )); + } + + let delta_v = delta_v + shift; + if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE { + transform.post_concat(&Matrix::translate(delta_v)); + } + + // Skip identity: propagating it fans out through the whole subtree. + if !math::identitish(&transform) { + result.push_back(Modifier::transform_propagate(child.id, transform)); + if child.has_layout() { + result.push_back(Modifier::reflow(child.id, force_reflow)); + } + } + + shape_anchor = next_anchor( + layout_data, + &layout_axis, + child_axis, + track, + shape_anchor, + total_shapes_size, + ); + } + } + + if let Some((scale, layout_bounds_after)) = auto_size { // Propagate the parent auto-resize to absolute children using their constraints. for child_id in shape.children_ids_iter(true) { let Some(child) = shapes.get(child_id) else { diff --git a/render-wasm/src/shapes/modifiers/grid_layout.rs b/render-wasm/src/shapes/modifiers/grid_layout.rs index a9749b9d8f..afa1140c1a 100644 --- a/render-wasm/src/shapes/modifiers/grid_layout.rs +++ b/render-wasm/src/shapes/modifiers/grid_layout.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, Result}; +use crate::error::Result; use crate::math::{self as math, intersect_rays, Bounds, Matrix, Point, Ray, Vector, VectorExt}; use crate::shapes::{ AlignContent, AlignItems, AlignSelf, Frame, GridCell, GridData, GridTrack, GridTrackType, @@ -10,7 +10,7 @@ use crate::uuid::Uuid; use std::collections::{HashMap, HashSet, VecDeque}; -use super::common::GetBounds; +use super::common::{self, GetBounds}; const MIN_SIZE: f32 = 0.01; const MAX_SIZE: f32 = f32::INFINITY; @@ -742,6 +742,7 @@ pub fn reflow_grid_layout( grid_data: &GridData, shapes: ShapesPoolRef, bounds: &mut HashMap, + grow_from_right: bool, ) -> Result> { let mut result = VecDeque::new(); let layout_bounds = bounds.find(shape); @@ -769,6 +770,43 @@ pub fn reflow_grid_layout( bounds, ); + // Sized before placing children: a layout growing from the right moves its + // left edge, and the children have to follow it. + let auto_size = if shape.is_layout_horizontal_auto() || shape.is_layout_vertical_auto() { + let width = layout_bounds.width(); + let height = layout_bounds.height(); + + let mut scale_width = 1.0; + let mut scale_height = 1.0; + + if shape.is_layout_horizontal_auto() { + let auto_width = column_tracks.iter().map(|t| t.size).sum::() + + layout_data.padding_left + + layout_data.padding_right + + column_tracks.len().saturating_sub(1) as f32 * layout_data.column_gap; + scale_width = auto_width / width; + } + + if shape.is_layout_vertical_auto() { + let auto_height = row_tracks.iter().map(|t| t.size).sum::() + + layout_data.padding_top + + layout_data.padding_bottom + + row_tracks.len().saturating_sub(1) as f32 * layout_data.row_gap; + scale_height = auto_height / height; + } + + let scale = + common::auto_size_matrix(&layout_bounds, scale_width, scale_height, grow_from_right)?; + Some((scale, layout_bounds.transform(&scale))) + } else { + None + }; + let shift = auto_size + .as_ref() + .filter(|_| grow_from_right) + .map(|(_, after)| Vector::new_points(&layout_bounds.nw, &after.nw)) + .unwrap_or_else(|| Vector::new(0.0, 0.0)); + let cells = create_cell_data( &layout_bounds, &children, @@ -878,7 +916,7 @@ pub fn reflow_grid_layout( h_anchor * hv.x + v_anchor * vv.x, h_anchor * hv.y + v_anchor * vv.y, ); - let delta_v = Vector::new_points(&child_ref, &position); + let delta_v = Vector::new_points(&child_ref, &position) + shift; if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE { transform.post_concat(&Matrix::translate(delta_v)); @@ -893,43 +931,7 @@ pub fn reflow_grid_layout( } } - if shape.is_layout_horizontal_auto() || shape.is_layout_vertical_auto() { - let width = layout_bounds.width(); - let height = layout_bounds.height(); - - let mut scale_width = 1.0; - let mut scale_height = 1.0; - - if shape.is_layout_horizontal_auto() { - let auto_width = column_tracks.iter().map(|t| t.size).sum::() - + layout_data.padding_left - + layout_data.padding_right - + column_tracks.len().saturating_sub(1) as f32 * layout_data.column_gap; - scale_width = auto_width / width; - } - - if shape.is_layout_vertical_auto() { - let auto_height = row_tracks.iter().map(|t| t.size).sum::() - + layout_data.padding_top - + layout_data.padding_bottom - + row_tracks.len().saturating_sub(1) as f32 * layout_data.row_gap; - scale_height = auto_height / height; - } - - let parent_transform = layout_bounds.transform_matrix().unwrap_or_default(); - - let parent_transform_inv = &parent_transform.invert().ok_or(Error::CriticalError( - "Failed to invert parent transform".to_string(), - ))?; - 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); - - let layout_bounds_after = layout_bounds.transform(&scale); + if let Some((scale, layout_bounds_after)) = auto_size { result.push_back(Modifier::parent(shape.id, scale)); bounds.insert(shape.id, layout_bounds_after); }