cvkg_layout/
progressive.rs1use cvkg_core::{Alignment, Distribution, LayoutCache, LayoutView, Rect};
2
3#[derive(Debug, Clone)]
4pub struct ProgressiveChild {
5 pub hash: u64,
6 pub laid_out: bool,
7 pub rect: Rect,
8}
9
10pub struct ProgressiveLayoutContext<'a> {
15 pub children: &'a [&'a dyn LayoutView],
16 pub entries: Vec<ProgressiveChild>,
17 pub spacing: f32,
18 pub alignment: Alignment,
19 pub distribution: Distribution,
20 pub bounds: Rect,
21 pub completed: usize,
22 pub fallback_applied: bool,
23}
24
25impl<'a> ProgressiveLayoutContext<'a> {
26 pub fn new(
28 bounds: Rect,
29 subviews: &'a [&'a dyn LayoutView],
30 spacing: f32,
31 alignment: Alignment,
32 distribution: Distribution,
33 ) -> Self {
34 let entries = subviews
35 .iter()
36 .map(|v| ProgressiveChild {
37 hash: v.view_hash(),
38 laid_out: false,
39 rect: Rect::zero(),
40 })
41 .collect();
42
43 Self {
44 children: subviews,
45 entries,
46 spacing,
47 alignment,
48 distribution,
49 bounds,
50 completed: 0,
51 fallback_applied: false,
52 }
53 }
54
55 pub fn layout_next_batch(&mut self, batch_size: usize) -> bool {
57 self.layout_next_batch_inner(batch_size, None);
58 self.is_complete()
59 }
60
61 pub fn layout_next_batch_with_cache(
63 &mut self,
64 batch_size: usize,
65 cache: &mut LayoutCache,
66 ) -> (bool, Vec<Rect>) {
67 self.layout_next_batch_inner(batch_size, Some(cache));
68 let new_rects: Vec<Rect> = self
69 .entries
70 .iter()
71 .filter(|e| e.laid_out && e.rect != Rect::zero())
72 .map(|e| e.rect)
73 .collect();
74 (self.is_complete(), new_rects)
75 }
76
77 fn layout_next_batch_inner(
78 &mut self,
79 batch_size: usize,
80 mut cache: Option<&mut LayoutCache>,
81 ) {
82 let mut processed = 0;
83 let mut batch_indices = Vec::new();
84 for (i, entry) in self.entries.iter().enumerate() {
85 if entry.laid_out {
86 continue;
87 }
88 if processed >= batch_size {
89 break;
90 }
91 batch_indices.push(i);
92 processed += 1;
93 }
94
95 if batch_indices.is_empty() {
96 return;
97 }
98
99 let batch_subviews: Vec<&dyn LayoutView> = batch_indices
100 .iter()
101 .map(|&i| self.children[i])
102 .collect();
103
104 let rects = match cache {
105 Some(ref mut c) => crate::taffy_engine::HStack::compute_layout_incremental(
106 self.spacing,
107 self.alignment,
108 self.distribution,
109 self.bounds,
110 0,
111 &batch_subviews,
112 *c,
113 ),
114 None => {
115 let mut tmp = LayoutCache::new();
116 crate::taffy_engine::HStack::compute_layout_incremental(
117 self.spacing,
118 self.alignment,
119 self.distribution,
120 self.bounds,
121 0,
122 &batch_subviews,
123 &mut tmp,
124 )
125 }
126 };
127
128 for (local_idx, &global_idx) in batch_indices.iter().enumerate() {
129 if local_idx < rects.len() {
130 self.entries[global_idx].rect = rects[local_idx];
131 self.entries[global_idx].laid_out = true;
132 self.completed += 1;
133 }
134 }
135
136 if let Some(c) = cache.as_mut() {
137 for (local_idx, &global_idx) in batch_indices.iter().enumerate() {
138 if local_idx < rects.len() {
139 let hash = self.entries[global_idx].hash;
140 if hash != 0 {
141 c.previous_rects.insert(hash, rects[local_idx]);
142 }
143 }
144 }
145 }
146 }
147
148 pub fn is_complete(&self) -> bool {
150 self.fallback_applied || self.completed >= self.entries.len()
151 }
152
153 pub fn progress(&self) -> (usize, usize) {
155 (self.completed, self.entries.len())
156 }
157
158 pub fn apply_remaining_fallback(&mut self, cache: &mut LayoutCache) -> Vec<Rect> {
160 let mut fallback_rects = Vec::new();
161 let remaining: Vec<usize> = self
162 .entries
163 .iter()
164 .enumerate()
165 .filter(|(_, e)| !e.laid_out)
166 .map(|(i, _)| i)
167 .collect();
168
169 if remaining.is_empty() {
170 self.fallback_applied = true;
171 return fallback_rects;
172 }
173
174 let cols = (remaining.len() as f32).sqrt().ceil() as usize;
175 let rows = (remaining.len() + cols - 1) / cols;
176 let cell_w = self.bounds.width / cols as f32;
177 let cell_h = self.bounds.height / rows as f32;
178
179 for (offset, &idx) in remaining.iter().enumerate() {
180 let hash = self.entries[idx].hash;
181 let rect = if hash != 0 {
182 cache
183 .previous_rects
184 .get(&hash)
185 .copied()
186 .unwrap_or_else(|| {
187 let col = offset % cols;
188 let row = offset / cols;
189 Rect {
190 x: self.bounds.x + col as f32 * cell_w,
191 y: self.bounds.y + row as f32 * cell_h,
192 width: cell_w,
193 height: cell_h,
194 }
195 })
196 } else {
197 let col = offset % cols;
198 let row = offset / cols;
199 Rect {
200 x: self.bounds.x + col as f32 * cell_w,
201 y: self.bounds.y + row as f32 * cell_h,
202 width: cell_w,
203 height: cell_h,
204 }
205 };
206
207 self.entries[idx].rect = rect;
208 self.entries[idx].laid_out = true;
209 self.completed += 1;
210 if hash != 0 {
211 cache.previous_rects.insert(hash, rect);
212 }
213 fallback_rects.push(rect);
214 }
215
216 self.fallback_applied = true;
217 fallback_rects
218 }
219
220 pub fn take_rects(self) -> Vec<Rect> {
222 self.entries.into_iter().map(|e| e.rect).collect()
223 }
224}