1#![allow(non_snake_case)]
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::hash::{Hash, Hasher};
6use std::rc::Rc;
7
8use repose_core::*;
9use repose_tree::{LayoutConstraints, NodeId, ViewTree};
10use rustc_hash::{FxHashMap, FxHasher};
11use taffy::TaffyTree;
12use taffy::prelude::*;
13
14use crate::Interactions;
15use crate::textfield::TextFieldState;
16
17use super::*;
18impl Default for LayoutEngine {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl LayoutEngine {
25 pub fn layout_frame(
26 &mut self,
27 root: &View,
28 size_px: (u32, u32),
29 textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
30 interactions: &Interactions,
31 focused: Option<u64>,
32 ) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
33 let start = web_time::Instant::now();
34 repose_text::begin_frame();
35 self.stats = LayoutStats::default();
36
37 self.focus_group_stack.clear();
39
40 let locals_stamp = Self::locals_stamp();
42 let locals_changed = self.last_locals_stamp != Some(locals_stamp);
43 if locals_changed {
44 self.layout_valid = false;
45 self.paint_cache.clear();
46 self.text_cache.clear();
47 }
48
49 let density_scale = locals::effective_density_scale();
50 let mut max_w_dp = size_px.0 as f32 / density_scale;
51 let mut max_h_dp = size_px.1 as f32 / density_scale;
52 if !max_w_dp.is_finite() || max_w_dp < 10.0 {
53 max_w_dp = 1280.0;
54 }
55 if !max_h_dp.is_finite() || max_h_dp < 10.0 {
56 max_h_dp = 800.0;
57 }
58 self.tree
59 .set_subcompose_scope(repose_core::SubcomposeScope::new(
60 Dp::ZERO,
61 Dp(max_w_dp),
62 Dp::ZERO,
63 Dp(max_h_dp),
64 ));
65 let root_node_id = self.tree.update(root);
66 self.stats.tree = self.tree.stats.clone();
67 let has_paint_mutation = !self.tree.paint_dirty_nodes().is_empty();
68 let size_changed = self.last_size_px != Some(size_px);
69 if has_paint_mutation || size_changed {
70 self.paint_generation = self.paint_generation.wrapping_add(1);
71 }
72 let has_tree_mutation =
73 !self.tree.dirty_nodes().is_empty() || !self.tree.removed_ids.is_empty();
74 if !self.scope_maps_valid || has_tree_mutation {
75 self.build_scope_maps();
76 }
77 if has_tree_mutation {
78 let mut scroll_node_ids = Vec::new();
79 let mut stack = vec![root_node_id];
80 while let Some(id) = stack.pop() {
81 let Some(node) = self.tree.get(id) else {
82 continue;
83 };
84 if node.modifier.scroll.is_some() {
85 scroll_node_ids.push(id);
86 }
87 stack.extend(node.children.iter().copied());
88 }
89 self.scroll_node_ids = scroll_node_ids;
90 }
91 if has_tree_mutation || has_paint_mutation {
92 self.deferred_z_cache.clear();
93 self.transform_layer_cache.clear();
94 }
95
96 let class = locals::calculate_window_size_class(
99 (max_w_dp * density_scale) as u32,
100 (max_h_dp * density_scale) as u32,
101 density_scale,
102 );
103 if class != locals::window_size_class() {
104 locals::set_window_size_class_default(class);
105 }
106 locals::set_window_container_size(max_w_dp, max_h_dp);
107 let mut need_layout =
108 size_changed || !self.layout_valid || has_tree_mutation || locals_changed;
109
110 if locals_changed {
112 let all_ids: Vec<NodeId> = self.tree.iter_with_ids().map(|(id, _)| id).collect();
113 for id in all_ids {
114 self.tree.mark_dirty(id);
115 }
116 for st in self.scope_trees.values_mut() {
117 st.valid = false;
118 }
119 need_layout = true;
120 }
121
122 if size_changed {
124 for &node_id in self.text_cache.keys() {
126 if let Some(&taffy_id) = self.taffy_map.get(&node_id) {
127 let _ = self.taffy.mark_dirty(taffy_id);
128 }
129 }
130 self.text_cache.clear();
131 for st in self.scope_trees.values_mut() {
133 for &node_id in st.text_cache.keys() {
134 if let Some(&tid) = st.taffy_map.get(&node_id) {
135 let _ = st.taffy.mark_dirty(tid);
136 }
137 }
138 st.text_cache.clear();
139 }
140 }
141 if locals_changed {
142 for st in self.scope_trees.values_mut() {
143 st.text_cache.clear();
144 }
145 }
146
147 if has_tree_mutation {
148 let dirty: Vec<NodeId> = self.tree.dirty_nodes().iter().copied().collect();
149 for nid in dirty {
150 if self.text_cache.contains_key(&nid) {
151 if let Some(&tid) = self.taffy_map.get(&nid) {
152 let _ = self.taffy.mark_dirty(tid);
153 }
154 self.text_cache.remove(&nid);
155 }
156 for st in self.scope_trees.values_mut() {
157 if st.text_cache.contains_key(&nid) {
158 if let Some(&tid) = st.taffy_map.get(&nid) {
159 let _ = st.taffy.mark_dirty(tid);
160 }
161 st.text_cache.remove(&nid);
162 }
163 }
164 if let Some(&tid) = self.taffy_map.get(&nid) {
165 let _ = self.taffy.mark_dirty(tid);
166 }
167 for st in self.scope_trees.values_mut() {
168 if let Some(&tid) = st.taffy_map.get(&nid) {
169 let _ = st.taffy.mark_dirty(tid);
170 }
171 }
172 self.paint_cache.remove(&nid);
173 }
174 }
175
176 let px = |v: Dp| v.to_px().0;
179 let font_px = |s: Sp| s.to_px().0;
180
181 self.sync_scope_trees(&font_px);
184 self.sync_taffy_tree(root_node_id, &font_px);
186
187 let taffy_root = self.taffy_map.get(&root_node_id).copied();
189 if let Some(taffy_root) = taffy_root {
190 if need_layout {
191 if let Ok(mut style) = self.taffy.style(taffy_root).cloned() {
192 style.size.width = length(size_px.0 as f32);
193 style.size.height = length(size_px.1 as f32);
194 if let Err(e) = self.taffy.set_style(taffy_root, style) {
195 log::error!("taffy set_style failed for root: {e:?}");
196 }
197 } else {
198 log::error!("taffy root style missing for {:?}", taffy_root);
199 }
200
201 let available = taffy::geometry::Size {
202 width: AvailableSpace::Definite(size_px.0 as f32),
203 height: AvailableSpace::Definite(size_px.1 as f32),
204 };
205
206 Self::run_measure_pass(
207 &mut self.taffy,
208 taffy_root,
209 available,
210 &self.tree,
211 &mut self.text_cache,
212 &self.reverse_map,
213 &self.scope_root_map,
214 &self.node_to_scope,
215 &mut self.scope_trees,
216 &font_px,
217 &px,
218 &mut self.baseline_map,
219 &self.taffy_map,
220 &mut self.baseline_shifts,
221 );
222
223 for (&node_id, &taffy_id) in &self.taffy_map {
225 if let Ok(layout) = self.taffy.layout(taffy_id) {
226 let dp_w = layout.size.width / density_scale;
227 let dp_h = layout.size.height / density_scale;
228 let rect = repose_core::Rect {
229 x: 0.0,
230 y: 0.0,
231 w: dp_w,
232 h: dp_h,
233 };
234 self.tree
235 .set_layout(node_id, rect, rect, LayoutConstraints::default());
236 }
237 }
238
239 self.last_locals_stamp = Some(locals_stamp);
240
241 self.layout_valid = true;
242 self.last_size_px = Some(size_px);
243 self.stats.layout_misses += 1;
244 } else {
245 self.stats.layout_hits += 1;
246 }
247 }
248 self.stats.layout_time_ms = (web_time::Instant::now() - start).as_secs_f32() * 1000.0;
249
250 self.walk_tick(root_node_id);
252
253 let t_paint = web_time::Instant::now();
255 self.focus_interaction_sources.clear();
256 let (scene, hits, sems) = self.paint(
257 root_node_id,
258 textfield_states,
259 interactions,
260 focused,
261 &font_px,
262 );
263 self.stats.paint_time_ms = (web_time::Instant::now() - t_paint).as_secs_f32() * 1000.0;
264
265 if self.prev_focused != focused {
267 if let Some(old_id) = self.prev_focused {
268 if let Some(cb) = self.focus_callbacks.get(&old_id) {
269 (cb)(false);
270 }
271 if let Some(src) = self.focus_interaction_sources.get(&old_id) {
272 src.to_mutable().emit(Interaction::Unfocus);
273 }
274 }
275 if let Some(new_id) = focused {
276 if let Some(cb) = self.focus_callbacks.get(&new_id) {
277 (cb)(true);
278 }
279 if let Some(src) = self.focus_interaction_sources.get(&new_id) {
280 src.to_mutable().emit(Interaction::Focus);
281 }
282 }
283 self.prev_focused = focused;
284 }
285
286 for &node_id in &self.tree.removed_ids {
288 if let Some(&vid) = self.view_ids.get(&node_id) {
289 self.focus_callbacks.remove(&vid);
290 self.focus_interaction_sources.remove(&vid);
291 }
292 }
293
294 self.tree.clear_dirty();
295 (scene, hits, sems)
296 }
297
298 pub fn intrinsic_size(&mut self, view: &View, mode: IntrinsicSizeMode) -> (f32, f32) {
299 let px_closure = |v: Dp| v.to_px().0;
300 let font_px_closure = |s: Sp| s.to_px().0;
301
302 let mut temp_taffy = taffy::TaffyTree::new();
303 let root_tid = self.build_taffy_subtree(view, &mut temp_taffy, &font_px_closure);
304
305 let avail = match mode {
306 IntrinsicSizeMode::MinContent => taffy::geometry::Size {
307 width: taffy::style::AvailableSpace::MinContent,
308 height: taffy::style::AvailableSpace::MinContent,
309 },
310 IntrinsicSizeMode::MaxContent => taffy::geometry::Size {
311 width: taffy::style::AvailableSpace::MaxContent,
312 height: taffy::style::AvailableSpace::MaxContent,
313 },
314 };
315
316 let mut text_cache: FxHashMap<NodeId, TextLayout> = FxHashMap::default();
317 let reverse_map: FxHashMap<taffy::NodeId, NodeId> = FxHashMap::default();
318 let mut baseline_map: FxHashMap<NodeId, TextBaselines> = FxHashMap::default();
319 let empty_map: FxHashMap<NodeId, taffy::NodeId> = FxHashMap::default();
322 let mut dropped_shifts: FxHashMap<NodeId, f32> = FxHashMap::default();
323
324 Self::run_measure_pass(
325 &mut temp_taffy,
326 root_tid,
327 avail,
328 &self.tree,
329 &mut text_cache,
330 &reverse_map,
331 &self.scope_root_map,
332 &self.node_to_scope,
333 &mut self.scope_trees,
334 &font_px_closure,
335 &px_closure,
336 &mut baseline_map,
337 &empty_map,
338 &mut dropped_shifts,
339 );
340
341 let layout = temp_taffy.layout(root_tid).ok();
342 match layout {
343 Some(l) => (l.size.width, l.size.height),
344 None => (0.0, 0.0),
345 }
346 }
347
348 pub fn new() -> Self {
349 Self {
350 tree: ViewTree::new(),
351 taffy: TaffyTree::new(),
352 taffy_map: FxHashMap::default(),
353 reverse_map: FxHashMap::default(),
354 scope_trees: HashMap::new(),
355 scope_root_map: FxHashMap::default(),
356 node_to_scope: FxHashMap::default(),
357 scope_root_ids: FxHashMap::default(),
358 scope_maps_valid: false,
359 scroll_node_ids: Vec::new(),
360 deferred_z_cache: FxHashMap::default(),
361 transform_layer_cache: FxHashMap::default(),
362 text_cache: FxHashMap::default(),
363 baseline_map: FxHashMap::default(),
364 baseline_shifts: FxHashMap::default(),
365 last_size_px: None,
366 layout_valid: false,
367 paint_cache: FxHashMap::default(),
368 stats: LayoutStats::default(),
369 last_locals_stamp: None,
370 view_ids: FxHashMap::default(),
371 next_view_id: 1,
372 layer_id_counter: 0,
373 paint_generation: 0,
374 prev_focused: None,
375 focus_callbacks: FxHashMap::default(),
376 focus_interaction_sources: FxHashMap::default(),
377 prev_observed_rects: FxHashMap::default(),
378 focus_group_stack: Vec::new(),
379 }
380 }
381
382 pub(crate) fn layout_for_node(&self, node_id: NodeId) -> taffy::prelude::Layout {
383 let mut l = self.raw_layout_for_node(node_id);
384 if let Some(s) = self.baseline_shift_for(node_id) {
386 l.location.y += s;
387 }
388 l
389 }
390
391 fn baseline_shift_for(&self, node_id: NodeId) -> Option<f32> {
394 if self.scope_root_map.contains_key(&node_id)
395 && let Some(&s) = self.baseline_shifts.get(&node_id)
396 {
397 return Some(s);
398 }
399 if let Some(parent_id) = self.tree.get(node_id).and_then(|n| n.parent)
400 && let Some(outer_key) = self.node_to_scope.get(&parent_id)
401 && let Some(st) = self.scope_trees.get(outer_key)
402 && let Some(&s) = st.baseline_shifts.get(&node_id)
403 {
404 return Some(s);
405 }
406 if let Some(key) = self.node_to_scope.get(&node_id)
407 && let Some(st) = self.scope_trees.get(key)
408 && let Some(&s) = st.baseline_shifts.get(&node_id)
409 {
410 return Some(s);
411 }
412 self.baseline_shifts.get(&node_id).copied()
413 }
414
415 fn raw_layout_for_node(&self, node_id: NodeId) -> taffy::prelude::Layout {
416 fn fallback() -> taffy::prelude::Layout {
417 log::error!("layout_for_node: missing taffy layout, returning zero rect");
418 taffy::prelude::Layout {
419 size: taffy::geometry::Size {
420 width: 0.0,
421 height: 0.0,
422 },
423 location: taffy::geometry::Point { x: 0.0, y: 0.0 },
424 ..Default::default()
425 }
426 }
427 if self.scope_root_map.contains_key(&node_id) {
428 if let Some(&tid) = self.taffy_map.get(&node_id) {
429 if let Ok(l) = self.taffy.layout(tid) {
430 return *l;
431 }
432 log::error!(
433 "layout_for_node: taffy layout missing for scope root {:?}",
434 node_id
435 );
436 return fallback();
437 }
438 if let Some(parent_id) = self.tree.get(node_id).and_then(|n| n.parent)
439 && let Some(outer_key) = self.node_to_scope.get(&parent_id)
440 && let Some(st) = self.scope_trees.get(outer_key)
441 && let Some(&tid) = st.taffy_map.get(&node_id)
442 && let Ok(l) = st.taffy.layout(tid)
443 {
444 return *l;
445 }
446 if let Some(key) = self.node_to_scope.get(&node_id)
447 && let Some(st) = self.scope_trees.get(key)
448 && let Some(&tid) = st.taffy_map.get(&node_id)
449 && let Ok(l) = st.taffy.layout(tid)
450 {
451 return *l;
452 }
453 return fallback();
454 }
455 if let Some(key) = self.node_to_scope.get(&node_id)
456 && let Some(st) = self.scope_trees.get(key)
457 {
458 if let Some(&tid) = st.taffy_map.get(&node_id) {
459 if let Ok(l) = st.taffy.layout(tid) {
460 return *l;
461 }
462 log::error!(
463 "layout_for_node: scope taffy layout missing for {:?}",
464 node_id
465 );
466 return fallback();
467 }
468 log::error!("layout_for_node: scope taffy_map missing for {:?}", node_id);
469 return fallback();
470 }
471 if let Some(&tid) = self.taffy_map.get(&node_id) {
472 if let Ok(l) = self.taffy.layout(tid) {
473 return *l;
474 }
475 log::error!("layout_for_node: taffy layout missing for {:?}", node_id);
476 } else {
477 log::error!("layout_for_node: taffy_map missing for {:?}", node_id);
478 }
479 fallback()
480 }
481
482 pub(crate) fn ensure_view_id(&mut self, node_id: NodeId) -> u64 {
483 if let Some(&id) = self.view_ids.get(&node_id) {
484 return id;
485 }
486 let id = self.next_view_id;
487 self.next_view_id += 1;
488 self.view_ids.insert(node_id, id);
489 id
490 }
491
492 pub fn debug_taffy_subtree(&self, node_id: NodeId) -> String {
496 let trees: Vec<(&taffy::TaffyTree<NodeContext>, taffy::NodeId)> = if let Some(key) =
497 self.node_to_scope.get(&node_id)
498 && let Some(st) = self.scope_trees.get(key)
499 && let Some(&tid) = st.taffy_map.get(&node_id)
500 {
501 vec![(&st.taffy, tid)]
502 } else if let Some(&tid) = self.taffy_map.get(&node_id) {
503 vec![(&self.taffy, tid)]
504 } else {
505 Vec::new()
506 };
507 let mut out = String::new();
508 for (tree, tid) in trees {
509 let mut buf = Vec::new();
510 if taffy::util::write_tree(&mut buf, tree, tid).is_ok() {
511 out.push_str(&String::from_utf8_lossy(&buf));
512 }
513 }
514 out
515 }
516
517 pub fn debug_grid_summary(&self, node_id: NodeId) -> Option<String> {
522 let tid = if let Some(key) = self.node_to_scope.get(&node_id)
523 && let Some(st) = self.scope_trees.get(key)
524 && let Some(&tid) = st.taffy_map.get(&node_id)
525 {
526 return Self::grid_summary_for(&st.taffy, tid);
527 } else {
528 self.taffy_map.get(&node_id).copied()?
529 };
530 Self::grid_summary_for(&self.taffy, tid)
531 }
532
533 fn grid_summary_for(
534 tree: &taffy::TaffyTree<NodeContext>,
535 tid: taffy::NodeId,
536 ) -> Option<String> {
537 let taffy::tree::DetailedLayoutInfo::Grid(grid) = tree.detailed_layout_info(tid) else {
538 return None;
539 };
540 Some(format!(
541 "grid rows=({} explicit of {} tracks) cols=({} explicit of {} tracks) rows=[{}] cols=[{}] items={}",
542 grid.rows.explicit_tracks,
543 grid.rows.explicit_tracks
544 + grid.rows.negative_implicit_tracks
545 + grid.rows.positive_implicit_tracks,
546 grid.columns.explicit_tracks,
547 grid.columns.explicit_tracks
548 + grid.columns.negative_implicit_tracks
549 + grid.columns.positive_implicit_tracks,
550 grid.grid_template_rows(),
551 grid.grid_template_columns(),
552 grid.items.len(),
553 ))
554 }
555
556 pub(crate) fn locals_stamp() -> u64 {
557 let mut h = FxHasher::default();
558
559 locals::density().scale.to_bits().hash(&mut h);
561 locals::ui_scale().0.to_bits().hash(&mut h);
562 locals::text_scale().0.to_bits().hash(&mut h);
563
564 let dir_u8 = match locals::text_direction() {
565 locals::TextDirection::Ltr => 0u8,
566 locals::TextDirection::Rtl => 1u8,
567 };
568 dir_u8.hash(&mut h);
569 repose_text::font_generation().hash(&mut h);
571
572 h.finish()
573 }
574}