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