Skip to main content

fret_ui/tree/paint/
entry.rs

1use super::super::*;
2
3impl<H: UiHost> UiTree<H> {
4    #[stacksafe::stacksafe]
5    pub fn paint_all(
6        &mut self,
7        app: &mut H,
8        services: &mut dyn UiServices,
9        bounds: Rect,
10        scene: &mut Scene,
11        scale_factor: f32,
12    ) {
13        let trace_paint = tracing::enabled!(tracing::Level::TRACE);
14        let window = self.window;
15        let frame_id = app.frame_id();
16
17        // `paint_node` can run multiple times within the same runner `FrameId` in tests and
18        // diagnostics scenarios. Use a tree-local paint pass token to avoid conflating distinct
19        // invocations when tracking "bounds were already updated this pass" markers.
20        let paint_pass = self.paint_pass.saturating_add(1);
21        self.paint_pass = paint_pass;
22
23        let ((), paint_elapsed) = fret_perf::measure_span(
24            self.debug_enabled,
25            trace_paint,
26            || {
27                tracing::trace_span!(
28                    "fret.ui.paint_all",
29                    window = ?window,
30                    frame_id = frame_id.0,
31                    paint_pass,
32                    scale_factor,
33                )
34            },
35            || {
36                if let Some(window) = self.window {
37                    let frame_id = app.frame_id();
38                    app.with_global_mut_untracked(
39                        fret_core::WindowFrameClockService::default,
40                        |svc, _host| svc.record_frame(window, frame_id),
41                    );
42                }
43                if self.debug_enabled {
44                    self.begin_debug_frame_if_needed(app.frame_id());
45                    self.debug_stats.frame_id = app.frame_id();
46                    self.debug_stats.paint_nodes = 0;
47                    self.debug_stats.paint_nodes_performed = 0;
48                    self.debug_stats.paint_cache_hits = 0;
49                    self.debug_stats.paint_cache_misses = 0;
50                    self.debug_stats.paint_cache_replayed_ops = 0;
51                    self.debug_stats.paint_cache_hit_test_only_replay_allowed = 0;
52                    self.debug_stats
53                        .paint_cache_hit_test_only_replay_rejected_key_mismatch = 0;
54                    self.debug_stats.paint_cache_replay_time = Duration::default();
55                    self.debug_stats.paint_cache_bounds_translate_time = Duration::default();
56                    self.debug_stats.paint_cache_bounds_translated_nodes = 0;
57                    self.debug_stats.paint_record_visual_bounds_time = Duration::default();
58                    self.debug_stats.paint_record_visual_bounds_calls = 0;
59                    self.debug_stats.paint_cache_key_time = Duration::default();
60                    self.debug_stats.paint_cache_hit_check_time = Duration::default();
61                    self.debug_stats.paint_widget_time = Duration::default();
62                    self.debug_stats.paint_observation_record_time = Duration::default();
63                    self.debug_stats.paint_host_widget_observed_models_time = Duration::default();
64                    self.debug_stats.paint_host_widget_observed_models_items = 0;
65                    self.debug_stats.paint_host_widget_observed_globals_time = Duration::default();
66                    self.debug_stats.paint_host_widget_observed_globals_items = 0;
67                    self.debug_stats.paint_host_widget_instance_lookup_time = Duration::default();
68                    self.debug_stats.paint_host_widget_instance_lookup_calls = 0;
69                    self.debug_stats.paint_text_prepare_time = Duration::default();
70                    self.debug_stats.paint_text_prepare_calls = 0;
71                    self.debug_stats.paint_text_prepare_reason_blob_missing = 0;
72                    self.debug_stats.paint_text_prepare_reason_scale_changed = 0;
73                    self.debug_stats.paint_text_prepare_reason_text_changed = 0;
74                    self.debug_stats.paint_text_prepare_reason_rich_changed = 0;
75                    self.debug_stats.paint_text_prepare_reason_style_changed = 0;
76                    self.debug_stats.paint_text_prepare_reason_wrap_changed = 0;
77                    self.debug_stats.paint_text_prepare_reason_overflow_changed = 0;
78                    self.debug_stats.paint_text_prepare_reason_width_changed = 0;
79                    self.debug_stats
80                        .paint_text_prepare_reason_font_stack_changed = 0;
81                    self.debug_paint_widget_exclusive_started = None;
82                    self.debug_stats.paint_input_context_time = Duration::default();
83                    self.debug_stats.paint_scroll_handle_invalidation_time = Duration::default();
84                    self.debug_stats.paint_collect_roots_time = Duration::default();
85                    self.debug_stats.paint_publish_text_input_snapshot_time = Duration::default();
86                    self.debug_stats.paint_collapse_observations_time = Duration::default();
87                    self.debug_stats.view_cache_active = self.view_cache_active();
88                    self.debug_stats.focus = self.focus;
89                    self.debug_stats.captured = self.captured_for(fret_core::PointerId(0));
90                }
91
92                // Keep IME enabled state in sync even if focus is set programmatically and no input event
93                // has been dispatched yet (ADR 0012).
94                let focus_is_text_input = self.focus_is_text_input(app);
95                self.set_ime_allowed(app, focus_is_text_input);
96                let input_ctx_started = self.debug_enabled.then(Instant::now);
97                let (active_layers, barrier_root) = self.active_input_layers();
98                let _ = active_layers;
99                let input_ctx = self.current_window_input_context(
100                    app,
101                    barrier_root.is_some(),
102                    focus_is_text_input,
103                );
104                self.publish_window_input_context_snapshot_untracked(app, &input_ctx, true);
105                if let Some(input_ctx_started) = input_ctx_started {
106                    self.debug_stats.paint_input_context_time = self
107                        .debug_stats
108                        .paint_input_context_time
109                        .saturating_add(input_ctx_started.elapsed());
110                }
111
112                // Scroll offsets can change without triggering layout invalidations (e.g. wheel deltas that
113                // only affect hit-testing/paint, or programmatic scroll handle updates in frames that skip
114                // layout). Ensure we consume scroll-handle change invalidations before paint-cache replay
115                // so cached ancestors cannot replay stale ops.
116                let (_, scroll_inv_elapsed) = fret_perf::measure(self.debug_enabled, || {
117                    self.invalidate_scroll_handle_bindings_for_changed_handles(
118                        app,
119                        crate::layout_pass::LayoutPassKind::Final,
120                        false,
121                        true,
122                    );
123                });
124                if let Some(scroll_inv_elapsed) = scroll_inv_elapsed {
125                    self.debug_stats.paint_scroll_handle_invalidation_time = self
126                        .debug_stats
127                        .paint_scroll_handle_invalidation_time
128                        .saturating_add(scroll_inv_elapsed);
129                }
130
131                let cache_enabled = self.paint_cache_enabled();
132                if cache_enabled {
133                    self.paint_cache.begin_frame();
134                } else {
135                    self.paint_cache.invalidate_recording();
136                }
137
138                self.scratch_visual_bounds_records.clear();
139
140                let (roots, roots_elapsed) = fret_perf::measure(self.debug_enabled, || {
141                    self.visible_layers_in_paint_order()
142                        .map(|layer| self.layers[layer].root)
143                        .collect::<Vec<NodeId>>()
144                });
145                if let Some(roots_elapsed) = roots_elapsed {
146                    self.debug_stats.paint_collect_roots_time = self
147                        .debug_stats
148                        .paint_collect_roots_time
149                        .saturating_add(roots_elapsed);
150                }
151                for root in roots {
152                    self.paint(app, services, root, bounds, scene, scale_factor);
153                }
154
155                if let Some(window) = self.window
156                    && !self.scratch_visual_bounds_records.is_empty()
157                {
158                    let mut records = std::mem::take(&mut self.scratch_visual_bounds_records);
159                    let (_, flush_elapsed) = fret_perf::measure(self.debug_enabled, || {
160                        crate::elements::with_window_state(app, window, |st| {
161                            for (element, visual) in records.drain(..) {
162                                if st
163                                    .current_bounds(element)
164                                    .is_some_and(|bounds| bounds == visual)
165                                {
166                                    continue;
167                                }
168                                st.record_visual_bounds(element, visual);
169                            }
170                        });
171                    });
172                    self.scratch_visual_bounds_records = records;
173                    if let Some(flush_elapsed) = flush_elapsed {
174                        self.debug_stats.paint_record_visual_bounds_time = self
175                            .debug_stats
176                            .paint_record_visual_bounds_time
177                            .saturating_add(flush_elapsed);
178                    }
179                }
180
181                // Publish a platform-facing text-input snapshot after paint so text widgets can update
182                // their IME cursor area in the same frame (ADR 0012).
183                if let Some(window) = self.window {
184                    let (_, text_snapshot_elapsed) = fret_perf::measure(self.debug_enabled, || {
185                        let mut next = if focus_is_text_input {
186                            if let Some(focus) = self.focus
187                                && let Some(snapshot) =
188                                    crate::declarative::frame::with_element_record_for_node(
189                                        app,
190                                        window,
191                                        focus,
192                                        |r| match &r.instance {
193                                            crate::declarative::ElementInstance::TextInputRegion(
194                                                props,
195                                            ) => Some(
196                                                super::super::ui_tree_text_input::text_input_region_platform_text_input_snapshot(props),
197                                            ),
198                                            _ => None,
199                                        },
200                                    )
201                                    .flatten()
202                            {
203                                snapshot
204                            } else {
205                                self.focus
206                                    .and_then(|focus| self.nodes.get(focus))
207                                    .and_then(|n| n.widget.as_ref())
208                                    .and_then(|w| w.platform_text_input_snapshot())
209                                    .unwrap_or_else(|| fret_runtime::WindowTextInputSnapshot {
210                                        focus_is_text_input: true,
211                                        ..Default::default()
212                                    })
213                            }
214                        } else {
215                            fret_runtime::WindowTextInputSnapshot::default()
216                        };
217                        next.focus_is_text_input = focus_is_text_input;
218
219                        let needs_update = app
220                            .global::<fret_runtime::WindowTextInputSnapshotService>()
221                            .and_then(|svc| svc.snapshot(window))
222                            .is_none_or(|prev| prev != &next);
223                        if needs_update {
224                            app.with_global_mut(
225                                fret_runtime::WindowTextInputSnapshotService::default,
226                                |svc, _app| {
227                                    svc.set_snapshot(window, next);
228                                },
229                            );
230                        }
231                    });
232                    if let Some(text_snapshot_elapsed) = text_snapshot_elapsed {
233                        self.debug_stats.paint_publish_text_input_snapshot_time = self
234                            .debug_stats
235                            .paint_publish_text_input_snapshot_time
236                            .saturating_add(text_snapshot_elapsed);
237                    }
238                }
239
240                if cache_enabled {
241                    self.paint_cache.finish_frame();
242                    if self.debug_enabled {
243                        self.debug_stats.paint_cache_hits = self.paint_cache.hits;
244                        self.debug_stats.paint_cache_misses = self.paint_cache.misses;
245                        self.debug_stats.paint_cache_replayed_ops = self.paint_cache.replayed_ops;
246                    }
247                }
248
249                let (_, collapse_elapsed) = fret_perf::measure(self.debug_enabled, || {
250                    self.collapse_paint_observations_to_view_cache_roots_if_needed();
251                });
252                if let Some(collapse_elapsed) = collapse_elapsed {
253                    self.debug_stats.paint_collapse_observations_time = self
254                        .debug_stats
255                        .paint_collapse_observations_time
256                        .saturating_add(collapse_elapsed);
257                }
258            },
259        );
260        if let Some(paint_elapsed) = paint_elapsed {
261            self.debug_stats.paint_time = paint_elapsed;
262        }
263    }
264
265    pub fn paint(
266        &mut self,
267        app: &mut H,
268        services: &mut dyn UiServices,
269        root: NodeId,
270        bounds: Rect,
271        scene: &mut Scene,
272        scale_factor: f32,
273    ) {
274        self.paint_node(
275            app,
276            services,
277            root,
278            bounds,
279            scene,
280            scale_factor,
281            crate::tree::paint_style::PaintStyleState::default(),
282            Transform2D::IDENTITY,
283        );
284    }
285
286    #[cfg(test)]
287    pub(crate) fn test_set_paint_cache_allow_hit_test_only_override(value: Option<bool>) {
288        super::set_paint_cache_allow_hit_test_only_for_test(value);
289    }
290}