Skip to main content

devpage_design/render/
layout.rs

1//! Layout: the scene as a taffy tree — flexbox restricted to Figma's auto
2//! layout, no-layout frames hugging their children — and every node's box.
3
4use crate::scene::{self, Align, ImageFiles, Layout, Node, Scene, Size as DSize};
5use crate::tree::{Order, OrderKey};
6
7use super::*;
8
9mod run;
10pub(super) use run::*;
11impl Renderer {
12    /// Lays the scene out: every node's box, in tree order — as a drawing
13    /// lays it out, runs and all.
14    pub fn layout(&mut self, scene: &Scene) -> Vec<Frame> {
15        self.register(&scene.fonts);
16        let mut l = Laid::build(self, scene);
17        l.settle_top(self);
18        l.frames(self, &scene.root).into_values().collect()
19    }
20
21    pub(super) fn compute(
22        &mut self,
23        runs: &mut Runs,
24        tree: &mut TaffyTree<Option<Leaf>>,
25        root: NodeId,
26        available: Size<AvailableSpace>,
27    ) {
28        tree.compute_layout_with_measure(root, available, |input, _id, ctx, style| {
29            let known = input.known_dimensions;
30            let fixed = |d: Dimension| (!d.is_auto()).then(|| d.value());
31            let known = Size {
32                width: known.width.or(fixed(style.size.width)),
33                height: known.height.or(fixed(style.size.height)),
34            };
35            let size = match ctx {
36                Some(Some(Leaf::Intrinsic(w, h))) => {
37                    // A vector or an image hugging keeps its own aspect.
38                    let (w, h) = (*w, *h);
39                    match (known.width, known.height) {
40                        (Some(kw), None) => Size {
41                            width: kw,
42                            height: kw * h / w,
43                        },
44                        (None, Some(kh)) => Size {
45                            width: kh * w / h,
46                            height: kh,
47                        },
48                        (kw, kh) => Size {
49                            width: kw.unwrap_or(w),
50                            height: kh.unwrap_or(h),
51                        },
52                    }
53                }
54                Some(Some(Leaf::Run(id))) => {
55                    // A size the run's own style gives is never less than
56                    // its padding, as taffy floors a box's own size; a size
57                    // its parent gives — a stretch — is taken as it is.
58                    let [t, r, b, l] = runs.get(*id).padding;
59                    let own = Size {
60                        width: fixed(style.size.width).map(|v| v.max(l + r)),
61                        height: fixed(style.size.height).map(|v| v.max(t + b)),
62                    };
63                    let known = Size {
64                        width: input.known_dimensions.width.or(own.width),
65                        height: input.known_dimensions.height.or(own.height),
66                    };
67                    return taffy::LayoutOutput::from_outer_size(self.measure_run(
68                        runs,
69                        *id,
70                        known,
71                        input.available_space,
72                    ));
73                }
74                Some(Some(Leaf::Text(text, wraps))) => {
75                    let width = known.width.or(match input.available_space.width {
76                        AvailableSpace::Definite(w) if *wraps => Some(w),
77                        _ => None,
78                    });
79                    let layout = self.text_layout(text, width);
80                    Size {
81                        width: known.width.unwrap_or(layout.width().ceil()),
82                        height: known.height.unwrap_or(layout.height()),
83                    }
84                }
85                _ => Size {
86                    width: known.width.unwrap_or(0.0),
87                    height: known.height.unwrap_or(0.0),
88                },
89            };
90            taffy::LayoutOutput::from_outer_size(size)
91        })
92        .expect("layout");
93    }
94}
95
96impl Renderer {
97    /// Lays a tree out from `root` in `available`, hugging no-layout frames
98    /// to their children.
99    pub(super) fn settle_in(
100        &mut self,
101        runs: &mut Runs,
102        tree: &mut TaffyTree<Option<Leaf>>,
103        root: NodeId,
104        available: Size<AvailableSpace>,
105        hugs: &[(NodeId, bool, bool)],
106    ) {
107        // To the fixed point: each pass settles at least one more level of
108        // hugging frames, and a run sized against the room a hugging frame
109        // gives it can take another; bounded, so it always ends.
110        for _ in 0..256 {
111            self.compute(runs, tree, root, available);
112            let mut changed = false;
113            for &(id, w, h) in hugs {
114                let (mut ew, mut eh) = (0.0f32, 0.0f32);
115                for child in tree.children(id).expect("children") {
116                    let l = tree.unrounded_layout(child);
117                    ew = ew.max(l.location.x + l.size.width);
118                    eh = eh.max(l.location.y + l.size.height);
119                }
120                let mut style = tree.style(id).expect("style").clone();
121                let mut ch = false;
122                let mut set = |d: &mut Dimension, v: f32| {
123                    if *d != Dimension::length(v) {
124                        *d = Dimension::length(v);
125                        ch = true;
126                    }
127                };
128                if w {
129                    set(&mut style.size.width, ew);
130                }
131                if h {
132                    set(&mut style.size.height, eh);
133                }
134                if ch {
135                    tree.set_style(id, style).expect("style");
136                    changed = true;
137                }
138            }
139            if !changed {
140                break;
141            }
142        }
143    }
144}
145
146pub(super) fn find_node<'a>(root: &'a Node, order: &[OrderKey]) -> &'a Node {
147    let mut at = root;
148    for k in order {
149        at = at.children.get(k).expect("a node there");
150    }
151    at
152}
153
154/// Removes `id` and everything under it from the layout tree.
155pub(super) fn remove_subtree(tree: &mut TaffyTree<Option<Leaf>>, id: NodeId) {
156    for child in tree.children(id).unwrap_or_default() {
157        remove_subtree(tree, child);
158    }
159    let _ = tree.remove(id);
160}
161
162/// What a leaf of the layout tree measures by: a text, or a vector's or an
163/// image's own size.
164pub(super) enum Leaf {
165    /// A text, and whether it wraps at its width.
166    Text(Box<scene::TextStyle>, bool),
167    Intrinsic(f32, f32),
168    /// A run, laid out by its own tree: [`Renderer::measure_run`].
169    Run(u32),
170    /// A run's `"fill"` child as the run's flex tree sees it: measured by
171    /// laying the child out in its own container.
172    Proxy(OrderKey),
173}
174
175/// A scene's layout kept in pieces: the drawing's own layout tree, a tree
176/// for each run's children, and where each node is in them.
177pub(super) struct Laid {
178    pub(super) top: TaffyTree<Option<Leaf>>,
179    pub(super) root: NodeId,
180    pub(super) runs: Runs,
181    /// Each node's tree and layout node, by its order.
182    pub(super) nodes: BTreeMap<Order, (Owner, NodeId)>,
183    /// Each run's layout, by its node's order.
184    pub(super) run_of: BTreeMap<Order, u32>,
185    /// Whether each node's size can depend on the room it is given, and how
186    /// many of its children's can: counted, so an edit settles it along its
187    /// path alone.
188    pub(super) sens: BTreeMap<Order, Sens>,
189}
190
191/// Whether a node's size can depend on the room it is laid out in, along
192/// each axis, and how many of its children's sizes can along each.
193#[derive(Clone, Copy, Debug, Default)]
194pub(super) struct Sens {
195    pub(super) me: bool,
196    pub(super) w: bool,
197    pub(super) h: bool,
198    pub(super) kids_w: usize,
199    pub(super) kids_h: usize,
200}
201
202/// Whether `node`'s width and height depend on its room, given how many of
203/// its children's do: a size it fixes doesn't, whatever is in it; `"fill"`
204/// does; a size that hugs does only through a child's that does. A text
205/// wraps at a width it is given — fixed or `"fill"` — and hugs its words
206/// otherwise, so a text's own room reaches it only through `"fill"`.
207fn room(node: &Node, kids_w: usize, kids_h: usize) -> (bool, bool) {
208    let along = |s: DSize, kids: usize| match s {
209        DSize::Px(_) => false,
210        DSize::Fill => true,
211        DSize::Hug => kids > 0,
212    };
213    (along(node.width, kids_w), along(node.height, kids_h))
214}
215
216impl Laid {
217    /// The room-dependence of `node`, at `order`, and everything under it:
218    /// whether its width and its height depend on its room.
219    pub(super) fn sens_build(&mut self, node: &Node, order: Order) -> (bool, bool) {
220        let (mut kids_w, mut kids_h) = (0, 0);
221        for (k, c) in node.children.entries() {
222            let mut o = order.clone();
223            o.push(k.clone());
224            let (w, h) = self.sens_build(c, o);
225            kids_w += w as usize;
226            kids_h += h as usize;
227        }
228        let (w, h) = room(node, kids_w, kids_h);
229        self.sens.insert(
230            order,
231            Sens {
232                me: w || h,
233                w,
234                h,
235                kids_w,
236                kids_h,
237            },
238        );
239        (w, h)
240    }
241
242    /// Whether the node at `order` can depend on its room.
243    pub(super) fn sensitive(&self, order: &Order) -> bool {
244        self.sens.get(order).is_some_and(|s| s.me)
245    }
246
247    /// The node at `order` changed, or a child of it came or went, changing
248    /// how many of its children depend on their room along each axis by
249    /// `kids`: its room-dependence settled again, and its parents' while it
250    /// changes — a run told its child is laid out another way.
251    pub(super) fn sens_settle(&mut self, root: &Node, order: &Order, kids: (isize, isize)) {
252        let mut at = order.clone();
253        let mut delta = kids;
254        loop {
255            let node = find_node(root, &at);
256            let s = self.sens.entry(at.clone()).or_default();
257            s.kids_w = (s.kids_w as isize + delta.0).max(0) as usize;
258            s.kids_h = (s.kids_h as isize + delta.1).max(0) as usize;
259            let (was_w, was_h, was) = (s.w, s.h, s.me);
260            let (w, h) = room(node, s.kids_w, s.kids_h);
261            (s.w, s.h, s.me) = (w, h, w || h);
262            if at.is_empty() {
263                return;
264            }
265            let parent = at[..at.len() - 1].to_vec();
266            if was != s.me
267                && let Some(&rid) = self.run_of.get(&parent)
268            {
269                let now = s.me;
270                let k = at.last().expect("a key").clone();
271                let run = self.runs.get_mut(rid);
272                let stretch = run.children.get(&k).is_some_and(|c| c.stretch);
273                run.classify(&k, stretch, now);
274            }
275            if (was_w, was_h) == (w, h) {
276                return;
277            }
278            delta = (w as isize - was_w as isize, h as isize - was_h as isize);
279            at = parent;
280        }
281    }
282
283    /// The subtree at `order` gone: its records dropped, and its parent's
284    /// counts with it.
285    pub(super) fn sens_drop(&mut self, root: &Node, order: &Order) {
286        let (w, h) = self.sens.get(order).map_or((false, false), |s| (s.w, s.h));
287        let under: Vec<Order> = self
288            .sens
289            .range(order.clone()..)
290            .take_while(|(o, _)| o.starts_with(order))
291            .map(|(o, _)| o.clone())
292            .collect();
293        for o in under {
294            self.sens.remove(&o);
295        }
296        if (w || h) && !order.is_empty() {
297            let parent = order[..order.len() - 1].to_vec();
298            if self.sens.contains_key(&parent) {
299                self.sens_settle(root, &parent, (-(w as isize), -(h as isize)));
300            }
301        }
302    }
303}
304
305use std::collections::BTreeMap;
306
307impl Laid {
308    /// The layout trees of a scene, not yet laid out.
309    pub(super) fn build(_r: &mut Renderer, scene: &Scene) -> Laid {
310        let mut top = TaffyTree::new();
311        // Only the boxes read are rounded, by `placed`, as taffy would round
312        // them all.
313        top.disable_rounding();
314        let mut l = Laid {
315            top: TaffyTree::new(),
316            root: NodeId::from(0u64),
317            runs: Runs::default(),
318            nodes: BTreeMap::new(),
319            run_of: BTreeMap::new(),
320            sens: BTreeMap::new(),
321        };
322        l.sens_build(&scene.root, Vec::new());
323        let root = l.build_node(
324            &mut top,
325            Owner::Top,
326            None,
327            &scene.root,
328            None,
329            &scene.images,
330            Vec::new(),
331        );
332        l.top = top;
333        l.root = root;
334        l
335    }
336
337    pub(super) fn tree(&self, owner: Owner) -> &TaffyTree<Option<Leaf>> {
338        match owner {
339            Owner::Top => &self.top,
340            Owner::Run(id) => &self.runs.get(id).tree,
341        }
342    }
343
344    pub(super) fn tree_mut(&mut self, owner: Owner) -> &mut TaffyTree<Option<Leaf>> {
345        match owner {
346            Owner::Top => &mut self.top,
347            Owner::Run(id) => &mut self.runs.get_mut(id).tree,
348        }
349    }
350
351    /// The layout tree of `node` — at `order`, in `tree`, which is `owner`'s
352    /// and, in a run's tree, inside that run's child `inside` — and
353    /// everything under it.
354    #[allow(clippy::too_many_arguments)]
355    pub(super) fn build_node(
356        &mut self,
357        tree: &mut TaffyTree<Option<Leaf>>,
358        owner: Owner,
359        inside: Option<OrderKey>,
360        node: &Node,
361        parent: Option<&Node>,
362        images: &ImageFiles,
363        order: Order,
364    ) -> NodeId {
365        let style = style_of(node, parent);
366        if let Some(leaf) = leaf_of(node, images) {
367            let id = tree.new_leaf_with_context(style, Some(leaf)).expect("leaf");
368            self.nodes.insert(order, (owner, id));
369            return id;
370        }
371        if is_run(node) {
372            let rid = self.runs.reserve();
373            // Its padding stays in its style: what a box is given is never
374            // less than its padding, and taffy floors it there — but a frame
375            // with no layout sizes itself, as taffy sizes such a frame.
376            let leaf = tree
377                .new_leaf_with_context(style, Some(Leaf::Run(rid)))
378                .expect("leaf");
379            let mut run = RunLayout::new(node, order.clone(), owner, leaf, inside);
380            run.hug = hugs_by_extent(node, parent).unwrap_or((false, false));
381            for (k, c) in node.children.entries() {
382                self.add_child(&mut run, rid, k, c, node, images, &order);
383            }
384            self.runs.put(rid, run);
385            self.run_of.insert(order.clone(), rid);
386            self.nodes.insert(order, (owner, leaf));
387            return leaf;
388        }
389        // A frame with no children: a box of its own size.
390        let id = tree.new_leaf(style).expect("leaf");
391        self.nodes.insert(order, (owner, id));
392        id
393    }
394
395    /// Builds `child`, at key `k` under the run `node` — `run`, in slot
396    /// `rid`, at `order` — into the run's tree as a child of it.
397    #[allow(clippy::too_many_arguments)]
398    pub(super) fn add_child(
399        &mut self,
400        run: &mut RunLayout,
401        rid: u32,
402        k: &OrderKey,
403        child: &Node,
404        node: &Node,
405        images: &ImageFiles,
406        order: &Order,
407    ) {
408        let mut o = order.clone();
409        o.push(k.clone());
410        let mut tree = std::mem::replace(&mut run.tree, TaffyTree::new());
411        let id = self.build_node(
412            &mut tree,
413            Owner::Run(rid),
414            Some(k.clone()),
415            child,
416            Some(node),
417            images,
418            o,
419        );
420        run.tree = tree;
421        let (along, across) = if run.row {
422            (child.width, child.height)
423        } else {
424            (child.height, child.width)
425        };
426        let mut o = order.clone();
427        o.push(k.clone());
428        let sens = self.sensitive(&o);
429        run.add(k.clone(), id, across == DSize::Fill, sens);
430        if along == DSize::Fill {
431            run.add_fill(k.clone(), style_of(child, Some(node)));
432        }
433    }
434
435    /// Lays the drawing's own tree out, and every run in it that changed.
436    pub(super) fn settle_top(&mut self, r: &mut Renderer) {
437        let root = self.root;
438        r.settle_in(&mut self.runs, &mut self.top, root, Size::MAX_CONTENT, &[]);
439    }
440
441    /// Where the run at `order` places each child: the run's laid-out sizes
442    /// for its final breadth, where the first child starts along it, and
443    /// how far apart its children are — its gap, and the free room
444    /// `"space-between"` spreads between them.
445    pub(super) fn run_places(
446        &self,
447        rid: u32,
448        size: Size<f32>,
449    ) -> Option<(&RunLayout, &Asked, f32, f64)> {
450        let run = self.runs.get(rid);
451        let (pa0, pa1) = run.padding_along();
452        let (pc0, pc1) = run.padding_across();
453        let (along, across) = if run.row {
454            (size.width, size.height)
455        } else {
456            (size.height, size.width)
457        };
458        let line = (across - pc0 - pc1).max(0.0);
459        let asked = run.placed(line)?;
460        let free = along - pa0 - pa1 - run.length(asked) as f32;
461        let n = run.children.len();
462        let spread = if run.justify == Align::SpaceBetween && free > 1e-3 && n > 1 {
463            free as f64 / (n - 1) as f64
464        } else {
465            0.0
466        };
467        Some((run, asked, pa0 + run.lead(free), run.gap as f64 + spread))
468    }
469
470    /// Every node's box, by order, in the design's coordinates.
471    pub(super) fn frames(&mut self, r: &mut Renderer, root: &Node) -> BTreeMap<Order, Frame> {
472        let mut out = BTreeMap::new();
473        let top = self.root;
474        self.frames_of(
475            r,
476            root,
477            Vec::new(),
478            Owner::Top,
479            top,
480            Place::default(),
481            true,
482            &mut out,
483        );
484        out
485    }
486
487    #[allow(clippy::too_many_arguments)]
488    fn frames_of(
489        &mut self,
490        r: &mut Renderer,
491        node: &Node,
492        order: Order,
493        owner: Owner,
494        id: NodeId,
495        parent: Place,
496        root: bool,
497        out: &mut BTreeMap<Order, Frame>,
498    ) {
499        let tree = self.tree(owner);
500        let (frame, place) = placed(tree, id, parent, root);
501        out.insert(order.clone(), frame);
502        if let Some(&rid) = self.run_of.get(&order)
503            && self.runs.get(rid).free
504        {
505            // Each child at its own place in the frame, measured from it.
506            for (k, c) in node.children.entries() {
507                r.relay_free(&mut self.runs, rid, k);
508                let node_id = self.runs.get(rid).children[k].node;
509                let mut o = order.clone();
510                o.push(k.clone());
511                self.frames_of(r, c, o, Owner::Run(rid), node_id, place, false, out);
512            }
513            return;
514        }
515        if let Some(&rid) = self.run_of.get(&order) {
516            let size = tree.unrounded_layout(id).size;
517            let mut before = 0.0f64;
518            for (k, c) in node.children.entries() {
519                let Some((run, asked, lead, step)) = self.run_places(rid, size) else {
520                    return;
521                };
522                let line = asked.line;
523                let Some(s) = run.size_of(asked, k) else {
524                    continue;
525                };
526                let (pc0, _) = run.padding_across();
527                let stretch = run.children[k].stretch;
528                let across = pc0 + run.across(line, &s, stretch);
529                let item = child_place(place, run.row, lead as f64 + before, across as f64);
530                let node_id = run.children[k].node;
531                r.relay(&mut self.runs, rid, k, line);
532                let mut o = order.clone();
533                o.push(k.clone());
534                self.frames_of(r, c, o, Owner::Run(rid), node_id, item, false, out);
535                before += s.along as f64 + step;
536            }
537            return;
538        }
539        let kids = tree.children(id).unwrap_or_default();
540        for ((k, c), &child) in node.children.entries().zip(&kids) {
541            let mut o = order.clone();
542            o.push(k.clone());
543            self.frames_of(r, c, o, owner, child, place, false, out);
544        }
545    }
546}
547
548/// The place a run's child is laid out from: `along` and `across` from the
549/// run's node's unrounded top-left, rounded as taffy rounds a box in the
550/// flow — measured from the run's node — so that the child, at the origin of
551/// its own one-item tree, gets exactly the box taffy would give it.
552pub(super) fn child_place(run: Place, row: bool, along: f64, across: f64) -> Place {
553    // In single precision, as taffy places a box and its rounding pass
554    // rounds it: the child's place within the run, then the run's.
555    let (along, across) = (along as f32, across as f32);
556    let (dx, dy) = if row {
557        (along, across)
558    } else {
559        (across, along)
560    };
561    let raw = (run.raw.0 + dx, run.raw.1 + dy);
562    let at = (
563        run.at.0 + snap(raw.0) - snap(run.raw.0),
564        run.at.1 + snap(raw.1) - snap(run.raw.1),
565    );
566    Place { at, raw }
567}
568
569/// The layout tree of `node` and everything under it as one taffy tree,
570/// no run laid out by its own: what taffy alone makes of a scene, for the
571/// tests to hold the drawing's layout to.
572#[cfg(test)]
573pub(super) fn build_plain(
574    tree: &mut TaffyTree<Option<Leaf>>,
575    node: &Node,
576    parent: Option<&Node>,
577    hugs: &mut Vec<(NodeId, bool, bool)>,
578    images: &ImageFiles,
579) -> NodeId {
580    let style = style_of(node, parent);
581    if let Some(leaf) = leaf_of(node, images) {
582        return tree.new_leaf_with_context(style, Some(leaf)).expect("leaf");
583    }
584    let children: Vec<NodeId> = node
585        .children
586        .iter()
587        .map(|c| build_plain(tree, c, Some(node), hugs, images))
588        .collect();
589    let id = tree.new_with_children(style, &children).expect("node");
590    if let Some((w, h)) = hugs_by_extent(node, parent) {
591        hugs.push((id, w, h));
592    }
593    id
594}
595
596/// Whether `node`, laid out in a plain taffy tree, hugs its children's
597/// extent — a frame with no layout — and along which axes.
598pub(super) fn hugs_by_extent(node: &Node, parent: Option<&Node>) -> Option<(bool, bool)> {
599    if leafy(node) {
600        return None;
601    }
602    // A root has no parent space to fill: on its own, `"fill"` hugs.
603    let hugs_by = |s: DSize| s == DSize::Hug || (parent.is_none() && s == DSize::Fill);
604    let (w, h) = (hugs_by(node.width), hugs_by(node.height));
605    (node.layout == Layout::None && !node.children.is_empty() && (w || h)).then_some((w, h))
606}
607
608/// Whether `node` is a leaf of the layout tree: a text, a vector or an image.
609pub(super) fn leafy(node: &Node) -> bool {
610    node.text.is_some() || node.vector.is_some() || node.image.is_some()
611}
612
613/// What a leaf of the layout tree measures by, for a node that is one.
614pub(super) fn leaf_of(node: &Node, images: &ImageFiles) -> Option<Leaf> {
615    if let Some(text) = &node.text {
616        let wraps = !matches!(node.width, DSize::Hug);
617        return Some(Leaf::Text(Box::new(text.clone()), wraps));
618    }
619    let intrinsic = match (&node.vector, &node.image) {
620        (Some(v), _) => Some((v.view_box[2], v.view_box[3])),
621        (_, Some(i)) => images.get(&i.id).map(|f| (f.width as f32, f.height as f32)),
622        _ => None,
623    };
624    intrinsic.map(|(w, h)| Leaf::Intrinsic(w, h))
625}
626
627/// A node's flexbox style, which its parent's layout decides part of.
628pub(super) fn style_of(node: &Node, parent: Option<&Node>) -> Style {
629    let mut style = Style::default();
630    let dir = parent.map(|p| p.layout);
631    let dim = |s: DSize| match s {
632        DSize::Px(v) => Dimension::length(v),
633        _ => Dimension::auto(),
634    };
635    style.size = Size {
636        width: dim(node.width),
637        height: dim(node.height),
638    };
639    let (main, cross) = match dir {
640        Some(Layout::Row) => (node.width, node.height),
641        Some(Layout::Column) => (node.height, node.width),
642        _ => (DSize::Hug, DSize::Hug),
643    };
644    if main == DSize::Fill {
645        style.flex_grow = 1.0;
646        style.flex_basis = Dimension::length(0.0);
647    }
648    if cross == DSize::Fill {
649        style.align_self = Some(AlignSelf::STRETCH);
650    }
651    // Every frame is the containing block of the boxes placed absolutely in
652    // it, as in Figma: a child at `x` and `y` is placed, sized and rounded
653    // against its own parent, never a farther ancestor.
654    style.position = Position::Relative;
655    if dir == Some(Layout::None) {
656        style.position = Position::Absolute;
657        style.inset = taffy::Rect {
658            left: LengthPercentageAuto::length(node.x),
659            top: LengthPercentageAuto::length(node.y),
660            right: LengthPercentageAuto::auto(),
661            bottom: LengthPercentageAuto::auto(),
662        };
663    }
664    style.flex_shrink = 0.0;
665    // A frame with no layout is never smaller than its padding, along
666    // either axis — as every other box is, and as taffy floors a leaf.
667    if node.layout == Layout::None && !leafy(node) {
668        let [t, r, b, l] = node.padding;
669        style.min_size = Size {
670            width: LengthPercentageAuto::length(l + r),
671            height: LengthPercentageAuto::length(t + b),
672        };
673    }
674    match node.layout {
675        Layout::Row => style.flex_direction = FlexDirection::Row,
676        Layout::Column => style.flex_direction = FlexDirection::Column,
677        Layout::None => {}
678    }
679    style.gap = Size {
680        width: LengthPercentage::length(node.gap),
681        height: LengthPercentage::length(node.gap),
682    };
683    let [t, r, b, l] = node.padding;
684    style.padding = taffy::Rect {
685        top: LengthPercentage::length(t),
686        right: LengthPercentage::length(r),
687        bottom: LengthPercentage::length(b),
688        left: LengthPercentage::length(l),
689    };
690    style.align_items = match node.align {
691        Align::Center => AlignItems::CENTER,
692        Align::End => AlignItems::FLEX_END,
693        _ => AlignItems::FLEX_START,
694    };
695    style.justify_content = match node.justify {
696        Align::Center => JustifyContent::CENTER,
697        Align::End => JustifyContent::FLEX_END,
698        Align::SpaceBetween => JustifyContent::SPACE_BETWEEN,
699        Align::Start => JustifyContent::FLEX_START,
700    };
701    style
702}
703
704#[cfg(test)]
705mod tests;
706
707/// Rounds to the nearest whole pixel, a half up, as taffy rounds.
708pub(super) fn snap(v: f32) -> f32 {
709    (v + 0.5).floor()
710}
711
712/// Where a node's parent is, for placing the node: the parent's box's
713/// top-left in whole pixels, and its unrounded one, which taffy's rounding
714/// pass measures the node from — every frame is the containing block of the
715/// boxes placed absolutely in it, so a box is always measured from its
716/// parent.
717#[derive(Clone, Copy, Debug, Default)]
718pub(super) struct Place {
719    pub(super) at: (f32, f32),
720    pub(super) raw: (f32, f32),
721}
722
723/// A node's box in whole pixels, in the design's coordinates, and the place
724/// its children are measured from — given its parent's. The tree is laid
725/// out unrounded, and only the boxes read are rounded, exactly as taffy's
726/// own pass rounds every box: each edge at its unrounded place measured from
727/// its parent — so the boxes are the very ones a tree rounded whole has.
728pub(super) fn placed(
729    tree: &TaffyTree<Option<Leaf>>,
730    id: NodeId,
731    parent: Place,
732    _root: bool,
733) -> (Frame, Place) {
734    let l = tree.unrounded_layout(id);
735    let base = parent.raw;
736    let (x, y) = (base.0 + l.location.x, base.1 + l.location.y);
737    let frame = Frame {
738        x: parent.at.0 + snap(x) - snap(base.0),
739        y: parent.at.1 + snap(y) - snap(base.1),
740        width: snap(x + l.size.width) - snap(x),
741        height: snap(y + l.size.height) - snap(y),
742    };
743    let place = Place {
744        at: (frame.x, frame.y),
745        raw: (x, y),
746    };
747    (frame, place)
748}