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
12/// Measured text geometry feeding baseline alignment, by ViewTree NodeId.
13/// `first`/`last` are real baselines (px from the text block top);
14/// `content_h` is the un-stretched content height. Content height matters
15/// because `Row` children default to `stretch`: layout height follows the
16/// row, content height does not, and the grow pass must fit content rather
17/// than stretch slack.
18#[derive(Clone, Copy, Debug, Default)]
19pub(crate) struct TextBaselines {
20    pub(crate) first: f32,
21    pub(crate) last: f32,
22    pub(crate) content_h: f32,
23}
24
25pub(crate) struct ScopeLayoutTree {
26    pub(crate) taffy: TaffyTree<NodeContext>,
27    pub(crate) taffy_map: FxHashMap<NodeId, taffy::NodeId>,
28    pub(crate) reverse_map: FxHashMap<taffy::NodeId, NodeId>,
29    pub(crate) root_taffy_id: Option<taffy::NodeId>,
30    pub(crate) last_constraints:
31        Option<(taffy::Size<Option<f32>>, taffy::Size<taffy::AvailableSpace>)>,
32    pub(crate) cached_size: Option<taffy::Size<f32>>,
33    pub(crate) text_cache: FxHashMap<NodeId, TextLayout>,
34    pub(crate) valid: bool,
35    /// Measured text geometry by ViewTree NodeId, filled during measure.
36    /// Consumed by the baseline-alignment post-pass (and grow pass).
37    pub(crate) baseline_map: FxHashMap<NodeId, TextBaselines>,
38    /// Vertical px shifts from baseline alignment, applied in
39    /// `layout_for_node`. Overwritten every layout of this tree.
40    pub(crate) baseline_shifts: FxHashMap<NodeId, f32>,
41}
42
43impl ScopeLayoutTree {
44    pub(crate) fn new() -> Self {
45        Self {
46            taffy: TaffyTree::new(),
47            taffy_map: FxHashMap::default(),
48            reverse_map: FxHashMap::default(),
49            root_taffy_id: None,
50            last_constraints: None,
51            cached_size: None,
52            text_cache: FxHashMap::default(),
53            valid: false,
54            baseline_map: FxHashMap::default(),
55            baseline_shifts: FxHashMap::default(),
56        }
57    }
58}
59
60pub struct LayoutEngine {
61    pub(crate) tree: ViewTree,
62
63    /// Root Taffy layout tree (inter-scope layout + non-scope nodes).
64    pub(crate) taffy: TaffyTree<NodeContext>,
65
66    pub(crate) taffy_map: FxHashMap<NodeId, taffy::NodeId>,
67
68    /// Reverse map: root Taffy NodeId to ViewTree NodeId.
69    pub(crate) reverse_map: FxHashMap<taffy::NodeId, NodeId>,
70
71    /// Per-scope TaffyTrees for scope! macro isolation.
72    pub(crate) scope_trees: HashMap<String, ScopeLayoutTree>,
73
74    /// ViewTree NodeId -> scope key for scope boundary root nodes.
75    pub(crate) scope_root_map: FxHashMap<NodeId, String>,
76
77    /// ViewTree NodeId -> scope key for ALL nodes belonging to a scope.
78    pub(crate) node_to_scope: FxHashMap<NodeId, String>,
79
80    /// Cached text layouts for non-scope nodes (persists across frames).
81    pub(crate) text_cache: FxHashMap<NodeId, TextLayout>,
82
83    /// Measured text geometry by ViewTree NodeId, filled during measure.
84    /// Consumed by the baseline-alignment post-pass (and grow pass).
85    pub(crate) baseline_map: FxHashMap<NodeId, TextBaselines>,
86    /// Vertical px shifts from baseline alignment, applied in
87    /// `layout_for_node`. Overwritten every root layout.
88    pub(crate) baseline_shifts: FxHashMap<NodeId, f32>,
89
90    /// Last window size used for layout.
91    pub(crate) last_size_px: Option<(u32, u32)>,
92
93    /// Whether root Taffy has a valid computed layout for `last_size_px`.
94    pub(crate) layout_valid: bool,
95
96    /// Repaint-boundary cache (SceneNodes + hits + semantics).
97    pub(crate) paint_cache: FxHashMap<NodeId, PaintCacheEntry>,
98
99    /// Statistics from the last frame.
100    pub stats: LayoutStats,
101
102    /// Tracks the previously focused view ID to detect focus changes.
103    pub(crate) prev_focused: Option<u64>,
104
105    /// Callbacks registered via `on_focus_changed` modifier, keyed by view ID.
106    pub(crate) focus_callbacks: FxHashMap<u64, Rc<dyn Fn(bool)>>,
107
108    /// Last "locals" stamp used for layout decisions (density/text scale/dir).
109    pub(crate) last_locals_stamp: Option<u64>,
110
111    /// Stable, unique ViewId per ViewTree NodeId.
112    pub(crate) view_ids: FxHashMap<NodeId, u64>,
113    pub(crate) next_view_id: u64,
114
115    /// Monotonic counter for graphics layer ids, assigned during paint.
116    pub(crate) layer_id_counter: u32,
117
118    /// Previous absolute rects for `on_globally_positioned` / `on_size_changed` callbacks.
119    pub(crate) prev_observed_rects: FxHashMap<u64, repose_core::Rect>,
120
121    /// Stack of active focus group IDs. When non-empty, newly created hit regions
122    /// get `focus_group_id` set to the top of this stack. A focus group is entered
123    /// when a node with `modifier.focus_group == true` is traversed.
124    pub(crate) focus_group_stack: Vec<u64>,
125
126    /// InteractionSources that should receive Focus/Unfocus for the current paint tree,
127    /// keyed by view ID.
128    pub(crate) focus_interaction_sources: FxHashMap<u64, InteractionSource>,
129}
130
131/// Statistics about layout performance.
132#[derive(Clone, Debug, Default)]
133pub struct LayoutStats {
134    pub tree: TreeStats,
135
136    /// Taffy nodes created this frame.
137    pub taffy_created: usize,
138
139    /// Taffy nodes reused this frame.
140    pub taffy_reused: usize,
141
142    /// Layout cache hits.
143    pub layout_hits: usize,
144
145    /// Layout cache misses.
146    pub layout_misses: usize,
147
148    /// Paint cache hits (repaint boundaries).
149    pub paint_cache_hits: usize,
150
151    /// Paint cache misses (repaint boundaries).
152    pub paint_cache_misses: usize,
153
154    /// Nodes skipped due to clip/viewport culling.
155    pub paint_culled: usize,
156
157    /// Total time for layout+paint (ms).
158    pub layout_time_ms: f32,
159
160    /// Total time spent in the paint pass only (ms).
161    pub paint_time_ms: f32,
162}
163
164#[derive(Clone)]
165pub(crate) struct PaintCacheEntry {
166    pub(crate) subtree_hash: u64,
167    pub(crate) stamp: u64,
168    pub(crate) rect: repose_core::Rect,
169    pub(crate) parent_offset_px: (f32, f32),
170    pub(crate) sem_parent: Option<u64>,
171    pub(crate) alpha_q: u8,
172    pub(crate) nodes: Rc<Vec<SceneNode>>,
173    pub(crate) hits: Rc<Vec<HitRegion>>,
174    pub(crate) sems: Rc<Vec<SemNode>>,
175}
176
177/// Selects the intrinsic sizing mode for [`LayoutEngine::intrinsic_size`].
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub enum IntrinsicSizeMode {
180    /// Smallest size at which the view's content does not overflow.
181    MinContent,
182    /// Largest size the view's content would take if unconstrained.
183    MaxContent,
184}
185
186/// Context stored with each Taffy node.
187/// Text sizes are [`Sp`](repose_core::Sp) (authoring units); converted to px
188/// by the `font_px` closure at the measure boundary.
189#[derive(Clone)]
190pub(crate) enum NodeContext {
191    Text {
192        text: String,
193        font_sp: Sp,
194        soft_wrap: bool,
195        max_lines: Option<usize>,
196        overflow: TextOverflow,
197        font_family: Option<&'static str>,
198        font_weight: FontWeight,
199        font_style: FontStyle,
200        letter_spacing: Sp,
201        line_height: Sp,
202        font_variation_settings: Option<Arc<str>>,
203        annotations: Option<Arc<[TextSpan]>>,
204    },
205    Container,
206    ScrollContainer,
207    TextInput {
208        multiline: bool,
209    },
210}
211
212#[derive(Clone)]
213pub(crate) struct TextLayout {
214    pub(crate) lines: Vec<String>,
215    /// Byte ranges into the original text for each line (used for annotation splitting).
216    pub(crate) line_ranges: Vec<(usize, usize)>,
217    pub(crate) size_px: f32,
218    pub(crate) line_h_px: f32,
219    /// Pre-measured width per line, in the same order as `lines`.
220    pub(crate) line_widths: Vec<f32>,
221    /// Per-line height in px (accounts for larger annotated font sizes, baseline shifts, stroke).
222    /// Uniform fallback is `line_h_px` when empty.
223    pub(crate) line_heights: Vec<f32>,
224}