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
68 self.build_scope_maps();
70
71 let size_changed = self.last_size_px != Some(size_px);
73 let class = locals::calculate_window_size_class(
75 (max_w_dp * density_scale) as u32,
76 (max_h_dp * density_scale) as u32,
77 density_scale,
78 );
79 if class != locals::window_size_class() {
80 locals::set_window_size_class_default(class);
81 }
82 locals::set_window_container_size(max_w_dp, max_h_dp);
83 let has_tree_mutation =
84 !self.tree.dirty_nodes().is_empty() || !self.tree.removed_ids.is_empty();
85 let mut need_layout =
86 size_changed || !self.layout_valid || has_tree_mutation || locals_changed;
87
88 if locals_changed {
90 let all_ids: Vec<NodeId> = self.tree.iter_with_ids().map(|(id, _)| id).collect();
91 for id in all_ids {
92 self.tree.mark_dirty(id);
93 }
94 for st in self.scope_trees.values_mut() {
95 st.valid = false;
96 }
97 need_layout = true;
98 }
99
100 if size_changed {
102 for &node_id in self.text_cache.keys() {
104 if let Some(&taffy_id) = self.taffy_map.get(&node_id) {
105 let _ = self.taffy.mark_dirty(taffy_id);
106 }
107 }
108 self.text_cache.clear();
109 for st in self.scope_trees.values_mut() {
111 for &node_id in st.text_cache.keys() {
112 if let Some(&tid) = st.taffy_map.get(&node_id) {
113 let _ = st.taffy.mark_dirty(tid);
114 }
115 }
116 st.text_cache.clear();
117 }
118 }
119 if locals_changed {
120 for st in self.scope_trees.values_mut() {
121 st.text_cache.clear();
122 }
123 }
124
125 if has_tree_mutation {
126 let dirty: Vec<NodeId> = self.tree.dirty_nodes().iter().copied().collect();
127 for nid in dirty {
128 if self.text_cache.contains_key(&nid) {
129 if let Some(&tid) = self.taffy_map.get(&nid) {
130 let _ = self.taffy.mark_dirty(tid);
131 }
132 self.text_cache.remove(&nid);
133 }
134 for st in self.scope_trees.values_mut() {
135 if st.text_cache.contains_key(&nid) {
136 if let Some(&tid) = st.taffy_map.get(&nid) {
137 let _ = st.taffy.mark_dirty(tid);
138 }
139 st.text_cache.remove(&nid);
140 }
141 }
142 if let Some(&tid) = self.taffy_map.get(&nid) {
143 let _ = self.taffy.mark_dirty(tid);
144 }
145 for st in self.scope_trees.values_mut() {
146 if let Some(&tid) = st.taffy_map.get(&nid) {
147 let _ = st.taffy.mark_dirty(tid);
148 }
149 }
150 self.paint_cache.remove(&nid);
151 }
152 }
153
154 let px = |v: Dp| v.to_px().0;
157 let font_px = |s: Sp| s.to_px().0;
158
159 self.sync_scope_trees(&font_px);
162 self.sync_taffy_tree(root_node_id, &font_px);
164
165 let taffy_root = self.taffy_map.get(&root_node_id).copied();
167 if let Some(taffy_root) = taffy_root {
168 if need_layout {
169 if let Ok(mut style) = self.taffy.style(taffy_root).cloned() {
170 style.size.width = length(size_px.0 as f32);
171 style.size.height = length(size_px.1 as f32);
172 if let Err(e) = self.taffy.set_style(taffy_root, style) {
173 log::error!("taffy set_style failed for root: {e:?}");
174 }
175 } else {
176 log::error!("taffy root style missing for {:?}", taffy_root);
177 }
178
179 let available = taffy::geometry::Size {
180 width: AvailableSpace::Definite(size_px.0 as f32),
181 height: AvailableSpace::Definite(size_px.1 as f32),
182 };
183
184 Self::run_measure_pass(
185 &mut self.taffy,
186 taffy_root,
187 available,
188 &self.tree,
189 &mut self.text_cache,
190 &self.reverse_map,
191 &self.scope_root_map,
192 &self.node_to_scope,
193 &mut self.scope_trees,
194 &font_px,
195 &px,
196 &mut self.baseline_map,
197 &self.taffy_map,
198 &mut self.baseline_shifts,
199 );
200
201 for (&node_id, &taffy_id) in &self.taffy_map {
203 if let Ok(layout) = self.taffy.layout(taffy_id) {
204 let dp_w = layout.size.width / density_scale;
205 let dp_h = layout.size.height / density_scale;
206 let rect = repose_core::Rect {
207 x: 0.0,
208 y: 0.0,
209 w: dp_w,
210 h: dp_h,
211 };
212 self.tree
213 .set_layout(node_id, rect, rect, LayoutConstraints::default());
214 }
215 }
216
217 self.last_locals_stamp = Some(locals_stamp);
218
219 self.layout_valid = true;
220 self.last_size_px = Some(size_px);
221 self.stats.layout_misses += 1;
222 } else {
223 self.stats.layout_hits += 1;
224 }
225 }
226 self.stats.layout_time_ms = (web_time::Instant::now() - start).as_secs_f32() * 1000.0;
227
228 self.walk_tick(root_node_id);
230
231 let t_paint = web_time::Instant::now();
233 self.focus_interaction_sources.clear();
234 let (scene, hits, sems) = self.paint(
235 root_node_id,
236 textfield_states,
237 interactions,
238 focused,
239 &font_px,
240 );
241 self.stats.paint_time_ms = (web_time::Instant::now() - t_paint).as_secs_f32() * 1000.0;
242
243 if self.prev_focused != focused {
245 if let Some(old_id) = self.prev_focused {
246 if let Some(cb) = self.focus_callbacks.get(&old_id) {
247 (cb)(false);
248 }
249 if let Some(src) = self.focus_interaction_sources.get(&old_id) {
250 src.to_mutable().emit(Interaction::Unfocus);
251 }
252 }
253 if let Some(new_id) = focused {
254 if let Some(cb) = self.focus_callbacks.get(&new_id) {
255 (cb)(true);
256 }
257 if let Some(src) = self.focus_interaction_sources.get(&new_id) {
258 src.to_mutable().emit(Interaction::Focus);
259 }
260 }
261 self.prev_focused = focused;
262 }
263
264 for &node_id in &self.tree.removed_ids {
266 if let Some(&vid) = self.view_ids.get(&node_id) {
267 self.focus_callbacks.remove(&vid);
268 self.focus_interaction_sources.remove(&vid);
269 }
270 }
271
272 self.tree.clear_dirty();
273 (scene, hits, sems)
274 }
275
276 pub fn intrinsic_size(&mut self, view: &View, mode: IntrinsicSizeMode) -> (f32, f32) {
277 let px_closure = |v: Dp| v.to_px().0;
278 let font_px_closure = |s: Sp| s.to_px().0;
279
280 let mut temp_taffy = taffy::TaffyTree::new();
281 let root_tid = self.build_taffy_subtree(view, &mut temp_taffy, &font_px_closure);
282
283 let avail = match mode {
284 IntrinsicSizeMode::MinContent => taffy::geometry::Size {
285 width: taffy::style::AvailableSpace::MinContent,
286 height: taffy::style::AvailableSpace::MinContent,
287 },
288 IntrinsicSizeMode::MaxContent => taffy::geometry::Size {
289 width: taffy::style::AvailableSpace::MaxContent,
290 height: taffy::style::AvailableSpace::MaxContent,
291 },
292 };
293
294 let mut text_cache: FxHashMap<NodeId, TextLayout> = FxHashMap::default();
295 let reverse_map: FxHashMap<taffy::NodeId, NodeId> = FxHashMap::default();
296 let mut baseline_map: FxHashMap<NodeId, TextBaselines> = FxHashMap::default();
297 let empty_map: FxHashMap<NodeId, taffy::NodeId> = FxHashMap::default();
300 let mut dropped_shifts: FxHashMap<NodeId, f32> = FxHashMap::default();
301
302 Self::run_measure_pass(
303 &mut temp_taffy,
304 root_tid,
305 avail,
306 &self.tree,
307 &mut text_cache,
308 &reverse_map,
309 &self.scope_root_map,
310 &self.node_to_scope,
311 &mut self.scope_trees,
312 &font_px_closure,
313 &px_closure,
314 &mut baseline_map,
315 &empty_map,
316 &mut dropped_shifts,
317 );
318
319 let layout = temp_taffy.layout(root_tid).ok();
320 match layout {
321 Some(l) => (l.size.width, l.size.height),
322 None => (0.0, 0.0),
323 }
324 }
325
326 pub fn new() -> Self {
327 Self {
328 tree: ViewTree::new(),
329 taffy: TaffyTree::new(),
330 taffy_map: FxHashMap::default(),
331 reverse_map: FxHashMap::default(),
332 scope_trees: HashMap::new(),
333 scope_root_map: FxHashMap::default(),
334 node_to_scope: FxHashMap::default(),
335 text_cache: FxHashMap::default(),
336 baseline_map: FxHashMap::default(),
337 baseline_shifts: FxHashMap::default(),
338 last_size_px: None,
339 layout_valid: false,
340 paint_cache: FxHashMap::default(),
341 stats: LayoutStats::default(),
342 last_locals_stamp: None,
343 view_ids: FxHashMap::default(),
344 next_view_id: 1,
345 layer_id_counter: 0,
346 prev_focused: None,
347 focus_callbacks: FxHashMap::default(),
348 focus_interaction_sources: FxHashMap::default(),
349 prev_observed_rects: FxHashMap::default(),
350 focus_group_stack: Vec::new(),
351 }
352 }
353
354 pub(crate) fn layout_for_node(&self, node_id: NodeId) -> taffy::prelude::Layout {
355 let mut l = self.raw_layout_for_node(node_id);
356 if let Some(s) = self.baseline_shift_for(node_id) {
358 l.location.y += s;
359 }
360 l
361 }
362
363 fn baseline_shift_for(&self, node_id: NodeId) -> Option<f32> {
366 if self.scope_root_map.contains_key(&node_id) {
367 if let Some(&s) = self.baseline_shifts.get(&node_id) {
368 return Some(s);
369 }
370 }
371 if let Some(parent_id) = self.tree.get(node_id).and_then(|n| n.parent)
372 && let Some(outer_key) = self.node_to_scope.get(&parent_id)
373 && let Some(st) = self.scope_trees.get(outer_key)
374 && let Some(&s) = st.baseline_shifts.get(&node_id)
375 {
376 return Some(s);
377 }
378 if let Some(key) = self.node_to_scope.get(&node_id)
379 && let Some(st) = self.scope_trees.get(key)
380 && let Some(&s) = st.baseline_shifts.get(&node_id)
381 {
382 return Some(s);
383 }
384 self.baseline_shifts.get(&node_id).copied()
385 }
386
387 fn raw_layout_for_node(&self, node_id: NodeId) -> taffy::prelude::Layout {
388 fn fallback() -> taffy::prelude::Layout {
389 log::error!("layout_for_node: missing taffy layout, returning zero rect");
390 taffy::prelude::Layout {
391 size: taffy::geometry::Size {
392 width: 0.0,
393 height: 0.0,
394 },
395 location: taffy::geometry::Point { x: 0.0, y: 0.0 },
396 ..Default::default()
397 }
398 }
399 if self.scope_root_map.contains_key(&node_id) {
400 if let Some(&tid) = self.taffy_map.get(&node_id) {
401 if let Ok(l) = self.taffy.layout(tid) {
402 return *l;
403 }
404 log::error!(
405 "layout_for_node: taffy layout missing for scope root {:?}",
406 node_id
407 );
408 return fallback();
409 }
410 if let Some(parent_id) = self.tree.get(node_id).and_then(|n| n.parent)
411 && let Some(outer_key) = self.node_to_scope.get(&parent_id)
412 && let Some(st) = self.scope_trees.get(outer_key)
413 && let Some(&tid) = st.taffy_map.get(&node_id)
414 && let Ok(l) = st.taffy.layout(tid)
415 {
416 return *l;
417 }
418 if let Some(key) = self.node_to_scope.get(&node_id)
419 && let Some(st) = self.scope_trees.get(key)
420 && let Some(&tid) = st.taffy_map.get(&node_id)
421 && let Ok(l) = st.taffy.layout(tid)
422 {
423 return *l;
424 }
425 return fallback();
426 }
427 if let Some(key) = self.node_to_scope.get(&node_id)
428 && let Some(st) = self.scope_trees.get(key)
429 {
430 if let Some(&tid) = st.taffy_map.get(&node_id) {
431 if let Ok(l) = st.taffy.layout(tid) {
432 return *l;
433 }
434 log::error!(
435 "layout_for_node: scope taffy layout missing for {:?}",
436 node_id
437 );
438 return fallback();
439 }
440 log::error!("layout_for_node: scope taffy_map missing for {:?}", node_id);
441 return fallback();
442 }
443 if let Some(&tid) = self.taffy_map.get(&node_id) {
444 if let Ok(l) = self.taffy.layout(tid) {
445 return *l;
446 }
447 log::error!("layout_for_node: taffy layout missing for {:?}", node_id);
448 } else {
449 log::error!("layout_for_node: taffy_map missing for {:?}", node_id);
450 }
451 fallback()
452 }
453
454 pub(crate) fn ensure_view_id(&mut self, node_id: NodeId) -> u64 {
455 if let Some(&id) = self.view_ids.get(&node_id) {
456 return id;
457 }
458 let id = self.next_view_id;
459 self.next_view_id += 1;
460 self.view_ids.insert(node_id, id);
461 id
462 }
463
464 pub fn debug_taffy_subtree(&self, node_id: NodeId) -> String {
468 let trees: Vec<(&taffy::TaffyTree<NodeContext>, taffy::NodeId)> = if let Some(key) =
469 self.node_to_scope.get(&node_id)
470 && let Some(st) = self.scope_trees.get(key)
471 && let Some(&tid) = st.taffy_map.get(&node_id)
472 {
473 vec![(&st.taffy, tid)]
474 } else if let Some(&tid) = self.taffy_map.get(&node_id) {
475 vec![(&self.taffy, tid)]
476 } else {
477 Vec::new()
478 };
479 let mut out = String::new();
480 for (tree, tid) in trees {
481 let mut buf = Vec::new();
482 if taffy::util::write_tree(&mut buf, tree, tid).is_ok() {
483 out.push_str(&String::from_utf8_lossy(&buf));
484 }
485 }
486 out
487 }
488
489 pub fn debug_grid_summary(&self, node_id: NodeId) -> Option<String> {
494 let tid = if let Some(key) = self.node_to_scope.get(&node_id)
495 && let Some(st) = self.scope_trees.get(key)
496 && let Some(&tid) = st.taffy_map.get(&node_id)
497 {
498 return Self::grid_summary_for(&st.taffy, tid);
499 } else {
500 self.taffy_map.get(&node_id).copied()?
501 };
502 Self::grid_summary_for(&self.taffy, tid)
503 }
504
505 fn grid_summary_for(
506 tree: &taffy::TaffyTree<NodeContext>,
507 tid: taffy::NodeId,
508 ) -> Option<String> {
509 let taffy::tree::DetailedLayoutInfo::Grid(grid) = tree.detailed_layout_info(tid) else {
510 return None;
511 };
512 Some(format!(
513 "grid rows=({} explicit of {} tracks) cols=({} explicit of {} tracks) rows=[{}] cols=[{}] items={}",
514 grid.rows.explicit_tracks,
515 grid.rows.explicit_tracks
516 + grid.rows.negative_implicit_tracks
517 + grid.rows.positive_implicit_tracks,
518 grid.columns.explicit_tracks,
519 grid.columns.explicit_tracks
520 + grid.columns.negative_implicit_tracks
521 + grid.columns.positive_implicit_tracks,
522 grid.grid_template_rows(),
523 grid.grid_template_columns(),
524 grid.items.len(),
525 ))
526 }
527
528 pub(crate) fn locals_stamp() -> u64 {
529 let mut h = FxHasher::default();
530
531 locals::density().scale.to_bits().hash(&mut h);
533 locals::ui_scale().0.to_bits().hash(&mut h);
534 locals::text_scale().0.to_bits().hash(&mut h);
535
536 let dir_u8 = match locals::text_direction() {
537 locals::TextDirection::Ltr => 0u8,
538 locals::TextDirection::Rtl => 1u8,
539 };
540 dir_u8.hash(&mut h);
541 repose_text::font_generation().hash(&mut h);
543
544 h.finish()
545 }
546}