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: Option<(taffy::Size<Option<f32>>, taffy::Size<taffy::AvailableSpace>)>,
19    pub(crate) cached_size: Option<taffy::Size<f32>>,
20    pub(crate) text_cache: FxHashMap<NodeId, TextLayout>,
21    pub(crate) valid: bool,
22}
23
24impl ScopeLayoutTree {
25    pub(crate) fn new(key: String) -> Self {
26        Self {
27            key,
28            taffy: TaffyTree::new(),
29            taffy_map: FxHashMap::default(),
30            reverse_map: FxHashMap::default(),
31            root_taffy_id: None,
32            last_constraints: None,
33            cached_size: None,
34            text_cache: FxHashMap::default(),
35            valid: false,
36        }
37    }
38}
39
40pub struct LayoutEngine {
41    /// Persistent view tree.
42    pub(crate) tree: ViewTree,
43
44    /// Root Taffy layout tree (inter-scope layout + non-scope nodes).
45    pub(crate) taffy: TaffyTree<NodeContext>,
46
47    /// Map from ViewTree NodeId to root Taffy NodeId.
48    pub(crate) taffy_map: FxHashMap<NodeId, taffy::NodeId>,
49
50    /// Reverse map: root Taffy NodeId to ViewTree NodeId.
51    pub(crate) reverse_map: FxHashMap<taffy::NodeId, NodeId>,
52
53    /// Per-scope TaffyTrees for scope! macro isolation.
54    pub(crate) scope_trees: HashMap<String, ScopeLayoutTree>,
55
56    /// ViewTree NodeId → scope key for scope boundary root nodes.
57    pub(crate) scope_root_map: FxHashMap<NodeId, String>,
58
59    /// ViewTree NodeId → scope key for ALL nodes belonging to a scope.
60    pub(crate) node_to_scope: FxHashMap<NodeId, String>,
61
62    /// Cached text layouts for non-scope nodes (persists across frames).
63    pub(crate) text_cache: FxHashMap<NodeId, TextLayout>,
64
65    /// Last window size used for layout.
66    pub(crate) last_size_px: Option<(u32, u32)>,
67
68    /// Whether root Taffy has a valid computed layout for `last_size_px`.
69    pub(crate) layout_valid: bool,
70
71    /// Repaint-boundary cache (SceneNodes + hits + semantics).
72    pub(crate) paint_cache: FxHashMap<NodeId, PaintCacheEntry>,
73
74    /// Statistics from the last frame.
75    pub stats: LayoutStats,
76
77    /// Tracks the previously focused view ID to detect focus changes.
78    pub(crate) prev_focused: Option<u64>,
79
80    /// Callbacks registered via `on_focus_changed` modifier, keyed by view ID.
81    pub(crate) focus_callbacks: FxHashMap<u64, Rc<dyn Fn(bool)>>,
82
83    /// Last "locals" stamp used for layout decisions (density/text scale/dir).
84    pub(crate) last_locals_stamp: Option<u64>,
85
86    /// Stable, unique ViewId per ViewTree NodeId.
87    pub(crate) view_ids: FxHashMap<NodeId, u64>,
88    pub(crate) next_view_id: u64,
89
90    /// Monotonic counter for graphics layer ids, assigned during paint.
91    pub(crate) layer_id_counter: u32,
92
93    /// Previous absolute rects for `on_globally_positioned` / `on_size_changed` callbacks.
94    pub(crate) prev_observed_rects: FxHashMap<u64, repose_core::Rect>,
95
96    /// Stack of active focus group IDs. When non-empty, newly created hit regions
97    /// get `focus_group_id` set to the top of this stack. A focus group is entered
98    /// when a node with `modifier.focus_group == true` is traversed.
99    pub(crate) focus_group_stack: Vec<u64>,
100}
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}