Skip to main content

cvkg_layout/
taffy_engine.rs

1use cvkg_core::{Alignment, Distribution, LayoutCache, LayoutView, Rect, Size, SizeProposal};
2use std::collections::HashMap;
3use taffy::prelude::*;
4
5/// The central Taffy engine that computes flexbox and grid layouts.
6/// Stored opaquely inside `cvkg_core::LayoutCache::engine`.
7pub struct TaffyLayoutEngine {
8    pub tree: taffy::TaffyTree,
9    pub node_map: HashMap<u64, taffy::NodeId>,
10}
11
12impl Default for TaffyLayoutEngine {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18impl TaffyLayoutEngine {
19    /// Creates a new TaffyLayoutEngine.
20    pub fn new() -> Self {
21        Self {
22            tree: taffy::TaffyTree::new(),
23            node_map: HashMap::new(),
24        }
25    }
26
27    /// Retrieves or initializes the TaffyLayoutEngine in the layout cache.
28    pub fn get_or_insert_engine(cache: &mut LayoutCache) -> &mut Self {
29        if cache.engine.is_none() {
30            cache.engine = Some(Box::new(TaffyLayoutEngine::new()));
31        }
32        cache
33            .engine
34            .as_mut()
35            .unwrap()
36            .downcast_mut::<TaffyLayoutEngine>()
37            .unwrap()
38    }
39}
40
41pub fn taffy_alignment(alignment: cvkg_core::Alignment) -> Option<taffy::AlignItems> {
42    match alignment {
43        cvkg_core::Alignment::Leading => Some(taffy::AlignItems::Start),
44        cvkg_core::Alignment::Center => Some(taffy::AlignItems::Center),
45        cvkg_core::Alignment::Trailing => Some(taffy::AlignItems::End),
46        cvkg_core::Alignment::Top => Some(taffy::AlignItems::Start),
47        cvkg_core::Alignment::Bottom => Some(taffy::AlignItems::End),
48    }
49}
50
51pub fn taffy_distribution(dist: cvkg_core::Distribution) -> Option<taffy::JustifyContent> {
52    match dist {
53        cvkg_core::Distribution::Leading => Some(taffy::JustifyContent::Start),
54        cvkg_core::Distribution::Center => Some(taffy::JustifyContent::Center),
55        cvkg_core::Distribution::Trailing => Some(taffy::JustifyContent::End),
56        cvkg_core::Distribution::SpaceBetween => Some(taffy::JustifyContent::SpaceBetween),
57        cvkg_core::Distribution::Fill => Some(taffy::JustifyContent::Stretch),
58        _ => None,
59    }
60}
61
62/// Taffy flex layout parameters.
63#[derive(Clone, Copy)]
64pub struct FlexParams {
65    pub dir: taffy::FlexDirection,
66    pub spacing: f32,
67    pub alignment: cvkg_core::Alignment,
68    pub distribution: cvkg_core::Distribution,
69    pub bounds: Rect,
70    pub container_hash: u64,
71}
72
73/// Collect intrinsic sizes for all children without running the Taffy solver.
74pub fn collect_child_sizes(
75    subviews: &[&dyn LayoutView],
76    bounds: Rect,
77    cache: &mut LayoutCache,
78) -> (Vec<u64>, Vec<f32>, Vec<Size>) {
79    let mut sizes = Vec::with_capacity(subviews.len());
80    let mut hashes = Vec::with_capacity(subviews.len());
81    let mut flex_weights = Vec::with_capacity(subviews.len());
82
83    for child in subviews {
84        let hash = child.view_hash();
85        hashes.push(hash);
86        flex_weights.push(child.flex_weight());
87
88        let proposal = SizeProposal::new(Some(bounds.width), Some(bounds.height));
89        let cached_size = if hash != 0 {
90            cache.get_size(hash, proposal)
91        } else {
92            None
93        };
94
95        let size = match cached_size {
96            Some(sz) => sz,
97            None => {
98                let sz = crate::with_layout_cycle_guard(hash, Size::ZERO, || {
99                    child.size_that_fits(proposal, &[], cache)
100                });
101                if hash != 0 {
102                    cache.set_size(hash, proposal, sz);
103                }
104                sz
105            }
106        };
107        if hash != 0 {
108            cache.register_parent(hash, 0);
109        }
110        sizes.push(size);
111    }
112
113    (hashes, flex_weights, sizes)
114}
115
116/// Compute the natural (intrinsic) size of a flex container from child sizes.
117pub fn intrinsic_flex_size(dir: taffy::FlexDirection, spacing: f32, sizes: &[Size]) -> Size {
118    if sizes.is_empty() {
119        return Size::ZERO;
120    }
121    let n = sizes.len();
122    match dir {
123        taffy::FlexDirection::Row | taffy::FlexDirection::RowReverse => {
124            let total_width: f32 = sizes.iter().map(|s| s.width).sum();
125            let max_height: f32 = sizes.iter().map(|s| s.height).fold(0.0, f32::max);
126            Size {
127                width: total_width + spacing * (n.saturating_sub(1) as f32),
128                height: max_height,
129            }
130        }
131        taffy::FlexDirection::Column | taffy::FlexDirection::ColumnReverse => {
132            let max_width: f32 = sizes.iter().map(|s| s.width).fold(0.0, f32::max);
133            let total_height: f32 = sizes.iter().map(|s| s.height).sum();
134            Size {
135                width: max_width,
136                height: total_height + spacing * (n.saturating_sub(1) as f32),
137            }
138        }
139    }
140}
141
142pub fn compute_taffy_flex(
143    params: &FlexParams,
144    subviews: &[&dyn LayoutView],
145    cache: &mut LayoutCache,
146) -> Vec<Rect> {
147    if cache.is_over_budget() {
148        let mut rects = Vec::with_capacity(subviews.len());
149        for child in subviews {
150            let hash = child.view_hash();
151            let r = if hash != 0 {
152                cache.previous_rects.get(&hash).copied().unwrap_or(Rect::zero())
153            } else {
154                Rect::zero()
155            };
156            rects.push(r);
157        }
158        return rects;
159    }
160
161    let (hashes, flex_weights, sizes) = collect_child_sizes(subviews, params.bounds, cache);
162
163    for &hash in &hashes {
164        if hash != 0 && params.container_hash != 0 {
165            cache.register_parent(hash, params.container_hash);
166        }
167    }
168
169    let engine = TaffyLayoutEngine::get_or_insert_engine(cache);
170    let mut child_nodes = Vec::with_capacity(subviews.len());
171
172    for ((&hash, &flex_weight), &size) in hashes.iter().zip(&flex_weights).zip(&sizes) {
173        let style = if flex_weight > 0.0 {
174            taffy::Style {
175                size: taffy::Size {
176                    width: if params.dir == taffy::FlexDirection::Row {
177                        taffy::Dimension::Auto
178                    } else {
179                        taffy::Dimension::Length(size.width)
180                    },
181                    height: if params.dir == taffy::FlexDirection::Column {
182                        taffy::Dimension::Auto
183                    } else {
184                        taffy::Dimension::Length(size.height)
185                    },
186                },
187                flex_grow: flex_weight,
188                flex_basis: taffy::Dimension::Percent(0.0),
189                ..Default::default()
190            }
191        } else {
192            taffy::Style {
193                size: taffy::Size {
194                    width: taffy::Dimension::Length(size.width),
195                    height: taffy::Dimension::Length(size.height),
196                },
197                ..Default::default()
198            }
199        };
200
201        let node = if hash != 0 {
202            if let Some(&existing) = engine.node_map.get(&hash) {
203                let _ = engine.tree.set_style(existing, style);
204                existing
205            } else {
206                let new_node = engine.tree.new_leaf(style).unwrap();
207                engine.node_map.insert(hash, new_node);
208                new_node
209            }
210        } else {
211            engine.tree.new_leaf(style).unwrap()
212        };
213        child_nodes.push(node);
214    }
215
216    let gap_val = taffy::LengthPercentage::Length(params.spacing);
217    let container_style = taffy::Style {
218        display: taffy::Display::Flex,
219        flex_direction: params.dir,
220        gap: taffy::Size {
221            width: if params.dir == taffy::FlexDirection::Row {
222                gap_val
223            } else {
224                taffy::LengthPercentage::Length(0.0)
225            },
226            height: if params.dir == taffy::FlexDirection::Column {
227                gap_val
228            } else {
229                taffy::LengthPercentage::Length(0.0)
230            },
231        },
232        align_items: taffy_alignment(params.alignment),
233        justify_content: taffy_distribution(params.distribution),
234        size: taffy::Size {
235            width: taffy::Dimension::Length(params.bounds.width),
236            height: taffy::Dimension::Length(params.bounds.height),
237        },
238        ..Default::default()
239    };
240
241    let root_node = if params.container_hash != 0 {
242        if let Some(&existing) = engine.node_map.get(&params.container_hash) {
243            let _ = engine.tree.set_style(existing, container_style);
244            let _ = engine.tree.set_children(existing, &child_nodes);
245            existing
246        } else {
247            let new_node = engine
248                .tree
249                .new_with_children(container_style, &child_nodes)
250                .unwrap();
251            engine.node_map.insert(params.container_hash, new_node);
252            new_node
253        }
254    } else {
255        engine
256            .tree
257            .new_with_children(container_style, &child_nodes)
258            .unwrap()
259    };
260
261    engine
262        .tree
263        .compute_layout(root_node, taffy::Size::MAX_CONTENT)
264        .unwrap();
265
266    let mut rects = Vec::with_capacity(subviews.len());
267    for &node in &child_nodes {
268        let layout = engine.tree.layout(node).unwrap();
269        rects.push(Rect {
270            x: params.bounds.x + layout.location.x,
271            y: params.bounds.y + layout.location.y,
272            width: layout.size.width,
273            height: layout.size.height,
274        });
275    }
276
277    if params.container_hash == 0 {
278        let _ = engine.tree.remove(root_node);
279    }
280
281    rects
282}
283
284/// HStack - lays out children horizontally
285pub struct HStack {
286    spacing: f32,
287    alignment: Alignment,
288    distribution: Distribution,
289}
290
291impl HStack {
292    /// Create a new HStack with the given spacing, alignment, and distribution.
293    pub fn new(spacing: f32, alignment: Alignment, distribution: Distribution) -> Self {
294        Self {
295            spacing,
296            alignment,
297            distribution,
298        }
299    }
300
301    /// Compute the layout rects for children without placing them.
302    pub fn compute_layout(
303        spacing: f32,
304        alignment: Alignment,
305        distribution: Distribution,
306        bounds: Rect,
307        subviews: &[&dyn LayoutView],
308        cache: &mut LayoutCache,
309    ) -> Vec<Rect> {
310        Self::compute_layout_incremental(
311            spacing,
312            alignment,
313            distribution,
314            bounds,
315            0,
316            subviews,
317            cache,
318        )
319    }
320
321    pub fn compute_layout_incremental(
322        spacing: f32,
323        alignment: Alignment,
324        distribution: Distribution,
325        bounds: Rect,
326        container_hash: u64,
327        subviews: &[&dyn LayoutView],
328        cache: &mut LayoutCache,
329    ) -> Vec<Rect> {
330        compute_taffy_flex(
331            &FlexParams {
332                dir: taffy::FlexDirection::Row,
333                spacing,
334                alignment,
335                distribution,
336                bounds,
337                container_hash,
338            },
339            subviews,
340            cache,
341        )
342    }
343}
344
345impl LayoutView for HStack {
346    fn size_that_fits(
347        &self,
348        proposal: SizeProposal,
349        subviews: &[&dyn LayoutView],
350        cache: &mut LayoutCache,
351    ) -> Size {
352        let bounds = Rect {
353            x: 0.0,
354            y: 0.0,
355            width: proposal.width.unwrap_or(10000.0),
356            height: proposal.height.unwrap_or(10000.0),
357        };
358        let (_, _, sizes) = collect_child_sizes(subviews, bounds, cache);
359        intrinsic_flex_size(taffy::FlexDirection::Row, self.spacing, &sizes)
360    }
361
362    fn place_subviews(
363        &self,
364        bounds: Rect,
365        subviews: &mut [&mut dyn LayoutView],
366        cache: &mut LayoutCache,
367    ) {
368        let views: Vec<&dyn LayoutView> =
369            subviews.iter().map(|v| &**v as &dyn LayoutView).collect();
370        let rects = Self::compute_layout_incremental(
371            self.spacing,
372            self.alignment,
373            self.distribution,
374            bounds,
375            self.view_hash(),
376            &views,
377            cache,
378        );
379        crate::animation::apply_layout_animations(rects, subviews, cache);
380    }
381}
382
383/// VStack - lays out children vertically
384pub struct VStack {
385    spacing: f32,
386    alignment: Alignment,
387    distribution: Distribution,
388}
389
390impl VStack {
391    /// Create a new VStack with the given spacing, alignment, and distribution.
392    pub fn new(spacing: f32, alignment: Alignment, distribution: Distribution) -> Self {
393        Self {
394            spacing,
395            alignment,
396            distribution,
397        }
398    }
399
400    /// Compute the layout rects for children without placing them.
401    pub fn compute_layout(
402        spacing: f32,
403        alignment: Alignment,
404        distribution: Distribution,
405        bounds: Rect,
406        subviews: &[&dyn LayoutView],
407        cache: &mut LayoutCache,
408    ) -> Vec<Rect> {
409        Self::compute_layout_incremental(
410            spacing,
411            alignment,
412            distribution,
413            bounds,
414            0,
415            subviews,
416            cache,
417        )
418    }
419
420    pub fn compute_layout_incremental(
421        spacing: f32,
422        alignment: Alignment,
423        distribution: Distribution,
424        bounds: Rect,
425        container_hash: u64,
426        subviews: &[&dyn LayoutView],
427        cache: &mut LayoutCache,
428    ) -> Vec<Rect> {
429        compute_taffy_flex(
430            &FlexParams {
431                dir: taffy::FlexDirection::Column,
432                spacing,
433                alignment,
434                distribution,
435                bounds,
436                container_hash,
437            },
438            subviews,
439            cache,
440        )
441    }
442}
443
444impl LayoutView for VStack {
445    fn size_that_fits(
446        &self,
447        proposal: SizeProposal,
448        subviews: &[&dyn LayoutView],
449        cache: &mut LayoutCache,
450    ) -> Size {
451        let bounds = Rect {
452            x: 0.0,
453            y: 0.0,
454            width: proposal.width.unwrap_or(10000.0),
455            height: proposal.height.unwrap_or(10000.0),
456        };
457        let (_, _, sizes) = collect_child_sizes(subviews, bounds, cache);
458        intrinsic_flex_size(taffy::FlexDirection::Column, self.spacing, &sizes)
459    }
460
461    fn place_subviews(
462        &self,
463        bounds: Rect,
464        subviews: &mut [&mut dyn LayoutView],
465        cache: &mut LayoutCache,
466    ) {
467        let views: Vec<&dyn LayoutView> =
468            subviews.iter().map(|v| &**v as &dyn LayoutView).collect();
469        let rects = Self::compute_layout_incremental(
470            self.spacing,
471            self.alignment,
472            self.distribution,
473            bounds,
474            self.view_hash(),
475            &views,
476            cache,
477        );
478        crate::animation::apply_layout_animations(rects, subviews, cache);
479    }
480}
481
482/// ZStack - lays out children on top of each other
483pub struct ZStack {}
484
485impl Default for ZStack {
486    fn default() -> Self {
487        Self::new()
488    }
489}
490
491impl ZStack {
492    /// Create a new ZStack.
493    pub fn new() -> Self {
494        Self {}
495    }
496}
497
498impl LayoutView for ZStack {
499    fn size_that_fits(
500        &self,
501        proposal: SizeProposal,
502        subviews: &[&dyn LayoutView],
503        cache: &mut LayoutCache,
504    ) -> Size {
505        let mut width = 0.0f32;
506        let mut height = 0.0f32;
507        let self_hash = self.view_hash();
508
509        for child in subviews.iter() {
510            let child_hash = child.view_hash();
511            if self_hash != 0 && child_hash != 0 {
512                cache.register_parent(child_hash, self_hash);
513            }
514            let child_size = crate::with_layout_cycle_guard(child_hash, Size::ZERO, || {
515                child.size_that_fits(proposal, &[], cache)
516            });
517            width = width.max(child_size.width);
518            height = height.max(child_size.height);
519        }
520
521        Size { width, height }
522    }
523
524    fn place_subviews(
525        &self,
526        bounds: Rect,
527        subviews: &mut [&mut dyn LayoutView],
528        cache: &mut LayoutCache,
529    ) {
530        let self_hash = self.view_hash();
531        for child in subviews.iter_mut() {
532            let child_hash = child.view_hash();
533            if self_hash != 0 && child_hash != 0 {
534                cache.register_parent(child_hash, self_hash);
535            }
536            let is_visible = if let Some(viewport) = cache.viewport {
537                bounds.intersects(&viewport)
538            } else {
539                true
540            };
541            if is_visible {
542                crate::with_layout_cycle_guard_void(child_hash, || {
543                    child.place_subviews(bounds, &mut [], cache);
544                });
545            }
546        }
547    }
548}
549
550/// Spacer - a layout view that expands to fill available space
551pub struct Spacer;
552
553impl LayoutView for Spacer {
554    fn size_that_fits(
555        &self,
556        proposal: SizeProposal,
557        _subviews: &[&dyn LayoutView],
558        _cache: &mut LayoutCache,
559    ) -> Size {
560        Size {
561            width: proposal.width.unwrap_or(0.0),
562            height: proposal.height.unwrap_or(0.0),
563        }
564    }
565
566    fn place_subviews(
567        &self,
568        _bounds: Rect,
569        _subviews: &mut [&mut dyn LayoutView],
570        _cache: &mut LayoutCache,
571    ) {
572    }
573}
574
575/// Flex - a container that distributes space among its children flexibly
576pub struct Flex {
577    pub orientation: cvkg_core::Orientation,
578    pub spacing: f32,
579}
580
581impl Flex {
582    pub fn new(orientation: cvkg_core::Orientation, spacing: f32) -> Self {
583        Self {
584            orientation,
585            spacing,
586        }
587    }
588}
589
590impl LayoutView for Flex {
591    fn size_that_fits(
592        &self,
593        proposal: SizeProposal,
594        _subviews: &[&dyn LayoutView],
595        _cache: &mut LayoutCache,
596    ) -> Size {
597        Size {
598            width: proposal.width.unwrap_or(100.0),
599            height: proposal.height.unwrap_or(100.0),
600        }
601    }
602
603    fn place_subviews(
604        &self,
605        bounds: Rect,
606        subviews: &mut [&mut dyn LayoutView],
607        cache: &mut LayoutCache,
608    ) {
609        if subviews.is_empty() {
610            return;
611        }
612
613        let self_hash = self.view_hash();
614        let n = subviews.len() as f32;
615        match self.orientation {
616            cvkg_core::Orientation::Horizontal => {
617                let total_spacing = self.spacing * (n - 1.0);
618                let item_width = (bounds.width - total_spacing) / n;
619                for (i, child) in subviews.iter_mut().enumerate() {
620                    let child_rect = Rect {
621                        x: bounds.x + i as f32 * (item_width + self.spacing),
622                        y: bounds.y,
623                        width: item_width,
624                        height: bounds.height,
625                    };
626                    let child_hash = child.view_hash();
627                    if self_hash != 0 && child_hash != 0 {
628                        cache.register_parent(child_hash, self_hash);
629                    }
630                    let is_visible = if let Some(viewport) = cache.viewport {
631                        child_rect.intersects(&viewport)
632                    } else {
633                        true
634                    };
635                    if is_visible {
636                        crate::with_layout_cycle_guard_void(child_hash, || {
637                            child.place_subviews(child_rect, &mut [], cache);
638                        });
639                    }
640                }
641            }
642            cvkg_core::Orientation::Vertical => {
643                let total_spacing = self.spacing * (n - 1.0);
644                let item_height = (bounds.height - total_spacing) / n;
645                for (i, child) in subviews.iter_mut().enumerate() {
646                    let child_rect = Rect {
647                        x: bounds.x,
648                        y: bounds.y + i as f32 * (item_height + self.spacing),
649                        width: bounds.width,
650                        height: item_height,
651                    };
652                    let child_hash = child.view_hash();
653                    if self_hash != 0 && child_hash != 0 {
654                        cache.register_parent(child_hash, self_hash);
655                    }
656                    let is_visible = if let Some(viewport) = cache.viewport {
657                        child_rect.intersects(&viewport)
658                    } else {
659                        true
660                    };
661                    if is_visible {
662                        crate::with_layout_cycle_guard_void(child_hash, || {
663                            child.place_subviews(child_rect, &mut [], cache);
664                        });
665                    }
666                }
667            }
668        }
669    }
670}
671
672/// Track sizing strategy for a single grid track (row or column).
673#[derive(Debug, Clone, Copy, PartialEq)]
674pub enum GridTrack {
675    /// Exact pixel size.
676    Fixed(f32),
677    /// Proportional weight compared to other flex tracks.
678    Flex(f32),
679    /// Size based on the intrinsic size of the grid item.
680    Auto,
681    /// Size clamped between minimum and maximum bounds.
682    MinMax(f32, f32),
683}
684
685pub fn taffy_track(track: GridTrack) -> taffy::TrackSizingFunction {
686    match track {
687        GridTrack::Fixed(v) => taffy::prelude::length(v),
688        GridTrack::Flex(v) => taffy::prelude::fr(v),
689        GridTrack::Auto => taffy::prelude::auto(),
690        GridTrack::MinMax(min, max) => {
691            taffy::prelude::minmax(taffy::prelude::length(min), taffy::prelude::length(max))
692        }
693    }
694}
695
696/// A layout engine that computes coordinates for children positioned in a 2D grid.
697pub struct Grid {
698    pub columns: Vec<GridTrack>,
699    pub rows: Vec<GridTrack>,
700    pub column_gap: f32,
701    pub row_gap: f32,
702}
703
704impl Grid {
705    /// Creates a new Grid layout engine.
706    pub fn new(
707        columns: Vec<GridTrack>,
708        rows: Vec<GridTrack>,
709        column_gap: f32,
710        row_gap: f32,
711    ) -> Self {
712        Self {
713            columns,
714            rows,
715            column_gap,
716            row_gap,
717        }
718    }
719
720    /// Computes the rects for children based on track sizing and grid placements.
721    pub fn compute_layout_rects(
722        &self,
723        bounds: Rect,
724        subviews: &[&dyn LayoutView],
725        placements: &[Option<cvkg_core::GridPlacement>],
726        cache: &mut LayoutCache,
727    ) -> Vec<Rect> {
728        self.compute_layout_rects_incremental(bounds, 0, subviews, placements, cache)
729    }
730
731    pub fn compute_layout_rects_incremental(
732        &self,
733        bounds: Rect,
734        container_hash: u64,
735        subviews: &[&dyn LayoutView],
736        placements: &[Option<cvkg_core::GridPlacement>],
737        cache: &mut LayoutCache,
738    ) -> Vec<Rect> {
739        if cache.is_over_budget() {
740            let mut rects = Vec::with_capacity(subviews.len());
741            for child in subviews {
742                let hash = child.view_hash();
743                let r = if hash != 0 {
744                    cache.previous_rects.get(&hash).copied().unwrap_or(Rect::zero())
745                } else {
746                    Rect::zero()
747                };
748                rects.push(r);
749            }
750            return rects;
751        }
752
753        let mut hashes = Vec::with_capacity(subviews.len());
754        for child in subviews {
755            let hash = child.view_hash();
756            hashes.push(hash);
757            if container_hash != 0 && hash != 0 {
758                cache.register_parent(hash, container_hash);
759            }
760        }
761
762        let engine = TaffyLayoutEngine::get_or_insert_engine(cache);
763        let mut child_nodes = Vec::with_capacity(subviews.len());
764
765        for (hash, placement) in hashes.iter().zip(placements.iter()) {
766            let style = if let Some(p) = placement.as_ref() {
767                taffy::Style {
768                    size: taffy::Size {
769                        width: taffy::Dimension::Auto,
770                        height: taffy::Dimension::Auto,
771                    },
772                    grid_column: taffy::Line {
773                        start: taffy::prelude::line((p.column + 1) as i16),
774                        end: taffy::prelude::span(p.column_span as u16),
775                    },
776                    grid_row: taffy::Line {
777                        start: taffy::prelude::line((p.row + 1) as i16),
778                        end: taffy::prelude::span(p.row_span as u16),
779                    },
780                    ..Default::default()
781                }
782            } else {
783                taffy::Style {
784                    size: taffy::Size {
785                        width: taffy::Dimension::Auto,
786                        height: taffy::Dimension::Auto,
787                    },
788                    ..Default::default()
789                }
790            };
791
792            let node = if *hash != 0 {
793                if let Some(&existing) = engine.node_map.get(hash) {
794                    let _ = engine.tree.set_style(existing, style);
795                    existing
796                } else {
797                    let new_node = engine.tree.new_leaf(style).unwrap();
798                    engine.node_map.insert(*hash, new_node);
799                    new_node
800                }
801            } else {
802                engine.tree.new_leaf(style).unwrap()
803            };
804            child_nodes.push(node);
805        }
806
807        let container_style = taffy::Style {
808            display: taffy::Display::Grid,
809            grid_template_columns: self.columns.iter().copied().map(taffy_track).collect(),
810            grid_template_rows: self.rows.iter().copied().map(taffy_track).collect(),
811            gap: taffy::Size {
812                width: taffy::LengthPercentage::Length(self.column_gap),
813                height: taffy::LengthPercentage::Length(self.row_gap),
814            },
815            size: taffy::Size {
816                width: taffy::Dimension::Length(bounds.width),
817                height: taffy::Dimension::Length(bounds.height),
818            },
819            ..Default::default()
820        };
821
822        let root_node = if container_hash != 0 {
823            if let Some(&existing) = engine.node_map.get(&container_hash) {
824                let _ = engine.tree.set_style(existing, container_style);
825                let _ = engine.tree.set_children(existing, &child_nodes);
826                existing
827            } else {
828                let new_node = engine
829                    .tree
830                    .new_with_children(container_style, &child_nodes)
831                    .unwrap();
832                engine.node_map.insert(container_hash, new_node);
833                new_node
834            }
835        } else {
836            engine
837                .tree
838                .new_with_children(container_style, &child_nodes)
839                .unwrap()
840        };
841
842        engine
843            .tree
844            .compute_layout(root_node, taffy::Size::MAX_CONTENT)
845            .unwrap();
846
847        let mut rects = Vec::with_capacity(subviews.len());
848        for &node in &child_nodes {
849            let layout = engine.tree.layout(node).unwrap();
850            rects.push(Rect {
851                x: bounds.x + layout.location.x,
852                y: bounds.y + layout.location.y,
853                width: layout.size.width,
854                height: layout.size.height,
855            });
856        }
857
858        if container_hash == 0 {
859            let _ = engine.tree.remove(root_node);
860        }
861        rects
862    }
863}
864
865impl LayoutView for Grid {
866    fn size_that_fits(
867        &self,
868        proposal: SizeProposal,
869        _subviews: &[&dyn LayoutView],
870        _cache: &mut LayoutCache,
871    ) -> Size {
872        Size {
873            width: proposal.width.unwrap_or(200.0),
874            height: proposal.height.unwrap_or(200.0),
875        }
876    }
877
878    fn place_subviews(
879        &self,
880        bounds: Rect,
881        subviews: &mut [&mut dyn LayoutView],
882        cache: &mut LayoutCache,
883    ) {
884        let views: Vec<&dyn LayoutView> =
885            subviews.iter().map(|v| &**v as &dyn LayoutView).collect();
886        let placements = vec![None; subviews.len()];
887        let rects = self.compute_layout_rects_incremental(
888            bounds,
889            self.view_hash(),
890            &views,
891            &placements,
892            cache,
893        );
894        crate::animation::apply_layout_animations(rects, subviews, cache);
895    }
896}