Skip to main content

repose_ui/layout/
types.rs

1#![allow(non_snake_case)]
2
3use std::collections::HashMap;
4use std::rc::Rc;
5use std::sync::Arc;
6
7use repose_core::*;
8use repose_tree::{NodeId, TreeStats, ViewTree};
9use rustc_hash::FxHashMap;
10use taffy::TaffyTree;
11
12pub(crate) struct ScopeLayoutTree {
13    pub(crate) key: String,
14    pub(crate) taffy: TaffyTree<NodeContext>,
15    pub(crate) taffy_map: FxHashMap<NodeId, taffy::NodeId>,
16    pub(crate) reverse_map: FxHashMap<taffy::NodeId, NodeId>,
17    pub(crate) root_taffy_id: Option<taffy::NodeId>,
18    pub(crate) last_constraints:
19        Option<(taffy::Size<Option<f32>>, taffy::Size<taffy::AvailableSpace>)>,
20    pub(crate) cached_size: Option<taffy::Size<f32>>,
21    pub(crate) text_cache: FxHashMap<NodeId, TextLayout>,
22    pub(crate) valid: bool,
23}
24
25impl ScopeLayoutTree {
26    pub(crate) fn new(key: String) -> Self {
27        Self {
28            key,
29            taffy: TaffyTree::new(),
30            taffy_map: FxHashMap::default(),
31            reverse_map: FxHashMap::default(),
32            root_taffy_id: None,
33            last_constraints: None,
34            cached_size: None,
35            text_cache: FxHashMap::default(),
36            valid: false,
37        }
38    }
39}
40
41pub struct LayoutEngine {
42    /// Persistent view tree.
43    pub(crate) tree: ViewTree,
44
45    /// Root Taffy layout tree (inter-scope layout + non-scope nodes).
46    pub(crate) taffy: TaffyTree<NodeContext>,
47
48    /// Map from ViewTree NodeId to root Taffy NodeId.
49    pub(crate) taffy_map: FxHashMap<NodeId, taffy::NodeId>,
50
51    /// Reverse map: root Taffy NodeId to ViewTree NodeId.
52    pub(crate) reverse_map: FxHashMap<taffy::NodeId, NodeId>,
53
54    /// Per-scope TaffyTrees for scope! macro isolation.
55    pub(crate) scope_trees: HashMap<String, ScopeLayoutTree>,
56
57    /// ViewTree NodeId -> scope key for scope boundary root nodes.
58    pub(crate) scope_root_map: FxHashMap<NodeId, String>,
59
60    /// ViewTree NodeId -> scope key for ALL nodes belonging to a scope.
61    pub(crate) node_to_scope: FxHashMap<NodeId, String>,
62
63    /// Cached text layouts for non-scope nodes (persists across frames).
64    pub(crate) text_cache: FxHashMap<NodeId, TextLayout>,
65
66    /// Last window size used for layout.
67    pub(crate) last_size_px: Option<(u32, u32)>,
68
69    /// Whether root Taffy has a valid computed layout for `last_size_px`.
70    pub(crate) layout_valid: bool,
71
72    /// Repaint-boundary cache (SceneNodes + hits + semantics).
73    pub(crate) paint_cache: FxHashMap<NodeId, PaintCacheEntry>,
74
75    /// Statistics from the last frame.
76    pub stats: LayoutStats,
77
78    /// Tracks the previously focused view ID to detect focus changes.
79    pub(crate) prev_focused: Option<u64>,
80
81    /// Callbacks registered via `on_focus_changed` modifier, keyed by view ID.
82    pub(crate) focus_callbacks: FxHashMap<u64, Rc<dyn Fn(bool)>>,
83
84    /// Last "locals" stamp used for layout decisions (density/text scale/dir).
85    pub(crate) last_locals_stamp: Option<u64>,
86
87    /// Stable, unique ViewId per ViewTree NodeId.
88    pub(crate) view_ids: FxHashMap<NodeId, u64>,
89    pub(crate) next_view_id: u64,
90
91    /// Monotonic counter for graphics layer ids, assigned during paint.
92    pub(crate) layer_id_counter: u32,
93
94    /// Previous absolute rects for `on_globally_positioned` / `on_size_changed` callbacks.
95    pub(crate) prev_observed_rects: FxHashMap<u64, repose_core::Rect>,
96
97    /// Stack of active focus group IDs. When non-empty, newly created hit regions
98    /// get `focus_group_id` set to the top of this stack. A focus group is entered
99    /// when a node with `modifier.focus_group == true` is traversed.
100    pub(crate) focus_group_stack: Vec<u64>,
101}
102
103/// Statistics about layout performance.
104#[derive(Clone, Debug, Default)]
105pub struct LayoutStats {
106    /// Stats from tree reconciliation.
107    pub tree: TreeStats,
108
109    /// Taffy nodes created this frame.
110    pub taffy_created: usize,
111
112    /// Taffy nodes reused this frame.
113    pub taffy_reused: usize,
114
115    /// Layout cache hits.
116    pub layout_hits: usize,
117
118    /// Layout cache misses.
119    pub layout_misses: usize,
120
121    /// Paint cache hits (repaint boundaries).
122    pub paint_cache_hits: usize,
123
124    /// Paint cache misses (repaint boundaries).
125    pub paint_cache_misses: usize,
126
127    /// Nodes skipped due to clip/viewport culling.
128    pub paint_culled: usize,
129
130    /// Total time for layout+paint (ms).
131    pub layout_time_ms: f32,
132
133    /// Total time spent in the paint pass only (ms).
134    pub paint_time_ms: f32,
135}
136
137#[derive(Clone)]
138pub(crate) struct PaintCacheEntry {
139    pub(crate) subtree_hash: u64,
140    pub(crate) stamp: u64,
141    pub(crate) rect: repose_core::Rect,
142    pub(crate) parent_offset_px: (f32, f32),
143    pub(crate) sem_parent: Option<u64>,
144    pub(crate) alpha_q: u8,
145    pub(crate) nodes: Arc<Vec<SceneNode>>,
146    pub(crate) hits: Arc<Vec<HitRegion>>,
147    pub(crate) sems: Arc<Vec<SemNode>>,
148}
149
150/// Selects the intrinsic sizing mode for [`LayoutEngine::intrinsic_size`].
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152pub enum IntrinsicSizeMode {
153    /// Smallest size at which the view's content does not overflow.
154    MinContent,
155    /// Largest size the view's content would take if unconstrained.
156    MaxContent,
157}
158
159/// Context stored with each Taffy node.
160#[derive(Clone)]
161pub(crate) enum NodeContext {
162    Text {
163        text: String,
164        color: Color,
165        font_dp: f32,
166        soft_wrap: bool,
167        max_lines: Option<usize>,
168        overflow: TextOverflow,
169        font_family: Option<&'static str>,
170        annotations: Option<Arc<[TextSpan]>>,
171        text_align: TextAlign,
172        font_weight: FontWeight,
173        font_style: FontStyle,
174        text_decoration: TextDecoration,
175        letter_spacing: f32,
176        line_height: f32,
177        font_variation_settings: Option<Arc<str>>,
178    },
179    Container,
180    ScrollContainer,
181    TextInput {
182        multiline: bool,
183    },
184}
185
186#[derive(Clone)]
187pub(crate) struct TextLayout {
188    pub(crate) lines: Vec<String>,
189    /// Byte ranges into the original text for each line (used for annotation splitting).
190    pub(crate) line_ranges: Vec<(usize, usize)>,
191    pub(crate) size_px: f32,
192    pub(crate) line_h_px: f32,
193    /// Pre-measured width per line.
194    pub(crate) line_widths: Vec<f32>,
195}