1use crate::{
2 Theme, UiHost, declarative,
3 elements::GlobalElementId,
4 widget::{
5 CommandCx, EventCx, Invalidation, LayoutCx, PaintCx, PlatformTextInputCx, SemanticsCx,
6 Widget,
7 },
8};
9use fret_core::time::{Duration, Instant};
10use fret_core::{
11 AppWindowId, Corners, Event, KeyCode, NodeId, Point, PointerEvent, PointerId, Px, Rect, Scene,
12 SceneOp, SemanticsNode, SemanticsRole, SemanticsRoot, SemanticsSnapshot, Size, TextConstraints,
13 Transform2D, UiServices, ViewId,
14};
15use fret_runtime::{
16 CommandId, Effect, FrameId, InputContext, InputDispatchPhase, KeyChord, KeymapService, ModelId,
17 Platform, PlatformCapabilities, TickId,
18};
19use slotmap::{Key, SecondaryMap, SlotMap};
20use std::any::TypeId;
21use std::collections::{HashMap, HashSet};
22use std::panic::{AssertUnwindSafe, Location, catch_unwind, resume_unwind};
23use std::sync::Arc;
24use std::sync::{Mutex, OnceLock};
25
26mod bounds_tree;
27mod commands;
28mod debug;
29mod dispatch;
30mod dispatch_snapshot;
31mod frame_arena;
32mod hit_test;
33mod invalidation_dedup;
34mod layers;
35mod layout;
36mod measure;
37mod node_storage;
38mod observation;
39mod paint;
40mod paint_cache;
41pub(crate) mod paint_style;
42mod prepaint;
43mod profiling;
44mod propagation_depth;
45mod semantics;
46mod shortcuts;
47mod small_list;
48mod ui_tree_accessors;
49mod ui_tree_debug;
50mod ui_tree_default;
51mod ui_tree_focus;
52mod ui_tree_input_snapshot;
53mod ui_tree_invalidation;
54mod ui_tree_invalidation_walk;
55mod ui_tree_mutation;
56mod ui_tree_outside_press;
57mod ui_tree_scratch;
58mod ui_tree_semantics;
59mod ui_tree_subtree_layout_dirty;
60mod ui_tree_text_input;
61mod ui_tree_view_cache;
62mod ui_tree_widget;
63mod util;
64use debug::{
65 DebugLayoutStackFrame, DebugPaintStackFrame, DebugViewCacheRootRecord,
66 DebugWidgetMeasureStackFrame, UiDebugHoverDeclarativeInvalidationCounts,
67};
68pub use debug::{
69 PointerOcclusion, UiDebugCacheRootReuseReason, UiDebugCacheRootStats, UiDebugDirtyView,
70 UiDebugFrameStats, UiDebugGlobalChangeHotspot, UiDebugGlobalChangeUnobserved, UiDebugHitTest,
71 UiDebugHoverDeclarativeInvalidationHotspot, UiDebugInvalidationDetail,
72 UiDebugInvalidationSource, UiDebugInvalidationWalk, UiDebugLayerInfo,
73 UiDebugLayoutEngineMeasureChildHotspot, UiDebugLayoutEngineMeasureHotspot,
74 UiDebugLayoutEngineSolve, UiDebugLayoutHotspot, UiDebugModelChangeHotspot,
75 UiDebugModelChangeUnobserved, UiDebugNotifyRequest, UiDebugPaintTextPrepareHotspot,
76 UiDebugPaintWidgetHotspot, UiDebugPrepaintAction, UiDebugPrepaintActionKind,
77 UiDebugRetainedVirtualListReconcile, UiDebugRetainedVirtualListReconcileKind,
78 UiDebugScrollAxis, UiDebugScrollHandleChange, UiDebugScrollHandleChangeKind,
79 UiDebugScrollNodeTelemetry, UiDebugScrollOverflowObservationTelemetry,
80 UiDebugScrollbarTelemetry, UiDebugTextConstraintsSnapshot, UiDebugVirtualListWindow,
81 UiDebugVirtualListWindowShiftApplyMode, UiDebugVirtualListWindowShiftKind,
82 UiDebugVirtualListWindowShiftReason, UiDebugVirtualListWindowShiftSample,
83 UiDebugVirtualListWindowSource, UiDebugWidgetMeasureHotspot, UiInputArbitrationSnapshot,
84};
85use frame_arena::FrameArenaScratch;
86use invalidation_dedup::{InvalidationDedupTable, InvalidationVisited};
87use measure::{DebugMeasureChildRecord, MeasureReentrancyDiagnostics, MeasureStackKey};
88use observation::{GlobalObservationIndex, ObservationIndex, ObservationMask};
89use profiling::{
90 LayoutNodeProfileConfig, LayoutNodeProfileState, MeasureNodeProfileConfig,
91 MeasureNodeProfileState,
92};
93use propagation_depth::PropagationDepthCacheEntry;
94#[cfg(test)]
95use util::event_allows_hit_test_path_cache_reuse;
96use util::{
97 TouchPointerDownOutsideCandidate, event_position, interactive_resize_stable_frames_required,
98 pointer_type_supports_hover, rect_aabb_transformed, text_wrap_width_bucket_px,
99 text_wrap_width_small_step_bucket_px, text_wrap_width_small_step_max_dw_px,
100};
101
102#[cfg(feature = "diagnostics")]
103pub use debug::{
104 UiDebugDispatchSnapshot, UiDebugDispatchSnapshotNode, UiDebugDispatchSnapshotParityReport,
105 UiDebugOverlayPolicyDecisionWrite, UiDebugParentSeverWrite, UiDebugRemoveSubtreeFrameContext,
106 UiDebugRemoveSubtreeOutcome, UiDebugRemoveSubtreeRecord, UiDebugSetChildrenWrite,
107 UiDebugSetLayerVisibleWrite,
108};
109
110use layers::UiLayer;
111pub use layers::{OverlayRootOptions, UiLayerId};
112use node_storage::{
113 ChildrenWritePolicy, HitTestPathCache, Node, NodeMeasureCache, NodeMeasureCacheKey,
114 PrepaintHitTestCache, ViewCacheFlags,
115};
116pub use paint_cache::PaintCachePolicy;
117use paint_cache::{PaintCacheEntry, PaintCacheKey, PaintCacheState};
118use shortcuts::{
119 KeydownShortcutParams, PendingShortcut, PointerDownOutsideOutcome, PointerDownOutsideParams,
120};
121use small_list::{SmallCopyList, SmallNodeList};
122
123pub(crate) use dispatch_snapshot::UiDispatchSnapshot;
124
125fn type_id_sort_key(id: TypeId) -> u64 {
126 use std::hash::{Hash, Hasher};
127
128 let mut hasher = std::collections::hash_map::DefaultHasher::new();
129 id.hash(&mut hasher);
130 hasher.finish()
131}
132
133fn record_layout_invalidation_transition(count: &mut u32, before: bool, after: bool) {
134 if before == after {
135 return;
136 }
137 if after {
138 *count = count.saturating_add(1);
139 } else {
140 debug_assert!(*count > 0);
141 *count = count.saturating_sub(1);
142 }
143}
144
145#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
146pub struct InvalidationFlags {
147 pub layout: bool,
148 pub paint: bool,
149 pub hit_test: bool,
150}
151
152impl InvalidationFlags {
153 pub fn mark(&mut self, inv: Invalidation) {
154 match inv {
155 Invalidation::Paint => self.paint = true,
156 Invalidation::Layout => {
157 self.layout = true;
158 self.paint = true;
159 }
160 Invalidation::HitTest => {
161 self.hit_test = true;
162 self.layout = true;
163 self.paint = true;
164 }
165 Invalidation::HitTestOnly => {
166 self.hit_test = true;
167 self.paint = true;
168 }
169 }
170 }
171
172 pub fn clear(&mut self) {
173 self.layout = false;
174 self.paint = false;
175 self.hit_test = false;
176 }
177}
178
179pub struct UiTree<H: UiHost> {
191 nodes: SlotMap<NodeId, Node<H>>,
192 layers: SlotMap<UiLayerId, UiLayer>,
193 layer_order: Vec<UiLayerId>,
194 root_to_layer: HashMap<NodeId, UiLayerId>,
195 base_layer: Option<UiLayerId>,
196 focus: Option<NodeId>,
197 pending_focus_target: Option<GlobalElementId>,
198 captured: HashMap<PointerId, NodeId>,
199 active_touch_drag_target: HashMap<PointerId, GlobalElementId>,
200 last_pointer_move_hit: HashMap<PointerId, Option<NodeId>>,
201 touch_pointer_down_outside_candidates: HashMap<PointerId, TouchPointerDownOutsideCandidate>,
202 hit_test_path_cache: Option<HitTestPathCache>,
203 hit_test_bounds_trees: bounds_tree::HitTestBoundsTrees,
204 last_internal_drag_target: Option<NodeId>,
205 window: Option<AppWindowId>,
206 ime_allowed: bool,
207 ime_composing: bool,
208 suppress_text_input_until_key_up: Option<KeyCode>,
209 pending_shortcut: PendingShortcut,
210 replaying_pending_shortcut: bool,
211 alt_menu_bar_arm_key: Option<KeyCode>,
212 alt_menu_bar_canceled: bool,
213 observed_in_layout: ObservationIndex,
214 observed_in_paint: ObservationIndex,
215 observed_globals_in_layout: GlobalObservationIndex,
216 observed_globals_in_paint: GlobalObservationIndex,
217 measure_stack: Vec<MeasureStackKey>,
218 measure_cache_this_frame: HashMap<MeasureStackKey, Size>,
219 frame_arena: FrameArenaScratch,
220 paint_pass: u64,
221 scratch_pending_invalidations: HashMap<NodeId, u8>,
222 scratch_node_stack: Vec<NodeId>,
223 scratch_element_nodes: Vec<(GlobalElementId, NodeId)>,
224 scratch_bounds_records: Vec<(GlobalElementId, Rect)>,
225 scratch_visual_bounds_records: Vec<(GlobalElementId, Rect)>,
226 measure_reentrancy_diagnostics: MeasureReentrancyDiagnostics,
227 layout_engine: crate::layout_engine::TaffyLayoutEngine,
228 layout_call_depth: u32,
229 layout_invalidations_count: u32,
230 last_layout_frame_id: Option<FrameId>,
231 last_layout_bounds: Option<Rect>,
232 last_layout_scale_factor: Option<f32>,
233 interactive_resize_active: bool,
234 interactive_resize_needs_full_rebuild: bool,
235 interactive_resize_stable_frames: u8,
236 interactive_resize_last_updated_frame: Option<FrameId>,
237 interactive_resize_last_bounds_delta: Option<(fret_core::Px, fret_core::Px)>,
238 viewport_roots: Vec<(NodeId, Rect)>,
239 pending_barrier_relayouts: Vec<NodeId>,
240 pending_declarative_window_snapshot_roots: HashSet<NodeId>,
241 pending_post_layout_window_runtime_snapshot_refine: bool,
242
243 #[cfg(debug_assertions)]
244 debug_last_declarative_render_root_frame_id: Option<FrameId>,
245
246 debug_enabled: bool,
247 debug_stats: UiDebugFrameStats,
248 debug_view_cache_roots: Vec<DebugViewCacheRootRecord>,
249 debug_view_cache_contained_relayout_roots: Vec<NodeId>,
250 debug_paint_cache_replays: HashMap<NodeId, u32>,
251 debug_paint_widget_exclusive_started: Option<Instant>,
252 debug_layout_engine_solves: Vec<UiDebugLayoutEngineSolve>,
253 debug_layout_hotspots: Vec<UiDebugLayoutHotspot>,
254 debug_layout_inclusive_hotspots: Vec<UiDebugLayoutHotspot>,
255 debug_layout_stack: Vec<DebugLayoutStackFrame>,
256 debug_widget_measure_hotspots: Vec<UiDebugWidgetMeasureHotspot>,
257 debug_widget_measure_stack: Vec<DebugWidgetMeasureStackFrame>,
258 debug_paint_widget_hotspots: Vec<UiDebugPaintWidgetHotspot>,
259 debug_paint_text_prepare_hotspots: Vec<UiDebugPaintTextPrepareHotspot>,
260 debug_paint_stack: Vec<DebugPaintStackFrame>,
261 debug_measure_children: HashMap<NodeId, HashMap<NodeId, DebugMeasureChildRecord>>,
262 debug_invalidation_walks: Vec<UiDebugInvalidationWalk>,
263 debug_model_change_hotspots: Vec<UiDebugModelChangeHotspot>,
264 debug_model_change_unobserved: Vec<UiDebugModelChangeUnobserved>,
265 debug_global_change_hotspots: Vec<UiDebugGlobalChangeHotspot>,
266 debug_global_change_unobserved: Vec<UiDebugGlobalChangeUnobserved>,
267 debug_hover_edge_this_frame: bool,
268 debug_hover_declarative_invalidations:
269 HashMap<NodeId, UiDebugHoverDeclarativeInvalidationCounts>,
270 debug_dirty_views: Vec<UiDebugDirtyView>,
271 #[cfg(feature = "diagnostics")]
272 debug_notify_requests: Vec<UiDebugNotifyRequest>,
273 debug_virtual_list_windows: Vec<UiDebugVirtualListWindow>,
274 debug_virtual_list_window_shift_samples: Vec<UiDebugVirtualListWindowShiftSample>,
275 debug_retained_virtual_list_reconciles: Vec<UiDebugRetainedVirtualListReconcile>,
276 debug_scroll_handle_changes: Vec<UiDebugScrollHandleChange>,
277 debug_scroll_nodes: Vec<UiDebugScrollNodeTelemetry>,
278 debug_scrollbars: Vec<UiDebugScrollbarTelemetry>,
279 debug_prepaint_actions: Vec<UiDebugPrepaintAction>,
280 #[cfg(feature = "diagnostics")]
281 debug_set_children_writes: HashMap<NodeId, UiDebugSetChildrenWrite>,
282 #[cfg(feature = "diagnostics")]
283 debug_parent_sever_writes: HashMap<NodeId, UiDebugParentSeverWrite>,
284 #[cfg(feature = "diagnostics")]
285 debug_layer_visible_writes: Vec<UiDebugSetLayerVisibleWrite>,
286 #[cfg(feature = "diagnostics")]
287 debug_overlay_policy_decisions: Vec<UiDebugOverlayPolicyDecisionWrite>,
288 #[cfg(feature = "diagnostics")]
289 debug_remove_subtree_frame_context: HashMap<NodeId, UiDebugRemoveSubtreeFrameContext>,
290 #[cfg(feature = "diagnostics")]
291 debug_removed_subtrees: Vec<UiDebugRemoveSubtreeRecord>,
292 #[cfg(feature = "diagnostics")]
293 debug_reachable_from_layer_roots: Option<(FrameId, HashSet<NodeId>)>,
294 #[cfg(feature = "diagnostics")]
295 debug_dispatch_snapshot: Option<UiDispatchSnapshot>,
296 #[cfg(feature = "diagnostics")]
297 debug_text_constraints_measured: HashMap<NodeId, TextConstraints>,
298 #[cfg(feature = "diagnostics")]
299 debug_text_constraints_prepared: HashMap<NodeId, TextConstraints>,
300
301 view_cache_enabled: bool,
302 paint_cache_policy: PaintCachePolicy,
303 inspection_active: bool,
304 paint_cache: PaintCacheState,
305 interaction_cache: prepaint::InteractionCacheState,
306
307 dirty_cache_roots: HashSet<NodeId>,
308 dirty_cache_root_reasons:
309 HashMap<NodeId, (UiDebugInvalidationSource, UiDebugInvalidationDetail)>,
310 last_redraw_request_tick: Option<TickId>,
311
312 propagation_depth_generation: u32,
313 propagation_depth_cache: SecondaryMap<NodeId, PropagationDepthCacheEntry>,
314 propagation_chain: Vec<NodeId>,
315 propagation_entries: Vec<(u8, u32, u64, NodeId, Invalidation)>,
316 invalidation_dedup: InvalidationDedupTable,
317 invalidated_layout_nodes: u32,
318 invalidated_paint_nodes: u32,
319 invalidated_hit_test_nodes: u32,
320
321 semantics: Option<Arc<SemanticsSnapshot>>,
322 semantics_requested: bool,
323 layout_node_profile: Option<LayoutNodeProfileState>,
324 measure_node_profile: Option<MeasureNodeProfileState>,
325 deferred_cleanup: Vec<Box<dyn Widget<H>>>,
326}
327
328#[cfg(test)]
329mod tests;