mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
🐛 Fix grow direction of boards containing RTL shapes
This commit is contained in:
parent
be63107ed6
commit
6508f72914
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -451,6 +451,7 @@ fn reflow_shape(
|
||||
reflown: &mut HashSet<Uuid>,
|
||||
entries: &mut VecDeque<Modifier>,
|
||||
bounds: &mut HashMap<Uuid, Bounds>,
|
||||
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<Uuid> {
|
||||
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::<Uuid, Matrix>::new();
|
||||
let mut bounds = HashMap::<Uuid, Bounds>::new();
|
||||
let mut reflown = HashSet::<Uuid>::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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Uuid, Bounds> {
|
||||
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<Matrix> {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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<Uuid, Bounds>,
|
||||
grow_from_right: bool,
|
||||
) -> Result<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 + s.margin_main_start + s.margin_main_end)
|
||||
.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 = &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::<f32>();
|
||||
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 {
|
||||
|
||||
@ -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<Uuid, Bounds>,
|
||||
grow_from_right: bool,
|
||||
) -> Result<VecDeque<Modifier>> {
|
||||
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::<f32>()
|
||||
+ 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::<f32>()
|
||||
+ 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::<f32>()
|
||||
+ 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::<f32>()
|
||||
+ 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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user