1use super::*;
2
3mod dev_overlay;
4mod frame_telemetry;
5mod layout_phase;
6pub(crate) use dev_overlay::{DevOverlay, draw_dev_overlay};
7use frame_telemetry::{
8 FrameStages, RenderPhaseDirtyDiagnostics, frame_stage_telemetry_threshold_ms,
9 log_frame_stage_telemetry, log_render_phase_dirty_diagnostics,
10 render_phase_dirty_diagnostics_enabled,
11};
12
13pub(crate) struct Composed {
19 attempted: bool,
20 recomposed: bool,
21}
22
23impl Composed {
24 pub(crate) const SKIPPED: Self = Self {
26 attempted: false,
27 recomposed: false,
28 };
29
30 pub(crate) fn attempted(&self) -> bool {
32 self.attempted
33 }
34
35 pub(crate) fn recomposed(&self) -> bool {
37 self.recomposed
38 }
39}
40
41struct LaidOut {
44 recomposed: bool,
45}
46
47#[derive(Copy, Clone, Debug)]
48enum DispatchInvalidationKind {
49 Pointer,
50 Focus,
51}
52
53#[derive(Clone, Copy)]
54struct FrameDirt {
55 render_dirty: bool,
56 pointer_dirty: bool,
57 attributed: bool,
58 layer_properties_pending: bool,
59 recomposed_this_frame: bool,
60}
61
62struct SurfaceDirt {
63 draw_dirty_nodes: Vec<NodeId>,
64 layer_property_nodes: Vec<NodeId>,
65 layout_dirty_nodes: Vec<NodeId>,
66 moved_nodes: Vec<NodeId>,
67 structural_parents: Vec<NodeId>,
68 scene_updates: Vec<(NodeId, NodeUpdate)>,
71 draw_repass_pending: bool,
72 render_only_dirty: bool,
73 visual_only_partial_dirty: bool,
74 full_scene_dirty: bool,
75 needs_scene_rebuild: bool,
76}
77
78struct ScopedVisualNodes {
79 draw: Vec<NodeId>,
80 layers: Vec<NodeId>,
81}
82
83struct ScopedLayoutNodes {
85 reshaped: Vec<NodeId>,
86 moved: Vec<NodeId>,
87}
88
89fn any_nodes(lists: &[&[NodeId]]) -> bool {
90 lists.iter().any(|nodes| !nodes.is_empty())
91}
92
93impl SurfaceDirt {
94 fn classify(
95 frame: &FrameDirt,
96 scene_dirty: bool,
97 draw_repass_pending: bool,
98 visual_nodes: ScopedVisualNodes,
99 layout: ScopedLayoutNodes,
100 structural_parents: Vec<NodeId>,
101 scene_updates: Vec<(NodeId, NodeUpdate)>,
102 ) -> Self {
103 let ScopedVisualNodes {
104 draw: draw_dirty_nodes,
105 layers: layer_property_nodes,
106 } = visual_nodes;
107 let ScopedLayoutNodes {
108 reshaped: layout_dirty_nodes,
109 moved: moved_nodes,
110 } = layout;
111 let structural_dirty = !structural_parents.is_empty();
112
113 let layout_changed = !layout_dirty_nodes.is_empty() || !moved_nodes.is_empty();
115 let partial_scene_dirty = any_nodes(&[
116 &draw_dirty_nodes,
117 &layout_dirty_nodes,
118 &moved_nodes,
119 &structural_parents,
120 ]);
121 let render_only_dirty = frame.render_dirty
122 && !frame.attributed
123 && !partial_scene_dirty
124 && layer_property_nodes.is_empty()
125 && !draw_repass_pending
126 && !structural_dirty;
127 let visual_only_partial_dirty = (!draw_dirty_nodes.is_empty()
128 || !layer_property_nodes.is_empty())
129 && !layout_changed
130 && !frame.pointer_dirty
131 && !frame.recomposed_this_frame
132 && !structural_dirty;
133 let scoped_scene_dirty = scene_dirty && layout_changed;
134 let full_scene_dirty = scene_dirty && !scoped_scene_dirty && !visual_only_partial_dirty;
135 let needs_scene_rebuild = full_scene_dirty
138 || partial_scene_dirty
139 || !layer_property_nodes.is_empty()
140 || draw_repass_pending
141 || structural_dirty;
142 let mut dirt = Self {
143 draw_dirty_nodes,
144 layer_property_nodes,
145 layout_dirty_nodes,
146 moved_nodes,
147 structural_parents,
148 scene_updates,
149 draw_repass_pending,
150 render_only_dirty,
151 visual_only_partial_dirty,
152 full_scene_dirty,
153 needs_scene_rebuild,
154 };
155 dirt.fill_scene_updates(frame.pointer_dirty);
156 dirt
157 }
158
159 fn fill_scene_updates(&mut self, pointer_dirty: bool) {
169 let draws = if pointer_dirty {
170 NodeUpdate::Content
171 } else {
172 NodeUpdate::Draws
173 };
174 let updates = &mut self.scene_updates;
175 updates.clear();
176 updates.extend(self.moved_nodes.iter().map(|&id| (id, NodeUpdate::Moved)));
177 updates.extend(
178 self.layer_property_nodes
179 .iter()
180 .map(|&id| (id, NodeUpdate::Layer)),
181 );
182 updates.extend(self.draw_dirty_nodes.iter().map(|&id| (id, draws)));
183 let recorded = self
184 .layout_dirty_nodes
185 .iter()
186 .chain(&self.structural_parents);
187 updates.extend(recorded.map(|&id| (id, NodeUpdate::Content)));
188 }
189
190 fn use_partial_update(&self) -> bool {
191 any_nodes(&[
192 &self.draw_dirty_nodes,
193 &self.layer_property_nodes,
194 &self.layout_dirty_nodes,
195 &self.moved_nodes,
196 &self.structural_parents,
197 ]) && !self.render_only_dirty
198 && !self.full_scene_dirty
199 }
200
201 fn use_visual_update(&self) -> bool {
202 self.use_partial_update() && self.visual_only_partial_dirty
203 }
204
205 fn rebuild_path(&self) -> &'static str {
206 if self.use_visual_update() {
207 "visual-update"
208 } else if self.use_partial_update() {
209 "update"
210 } else {
211 "rebuild"
212 }
213 }
214
215 fn log(&self, frame: &FrameDirt, scene_dirty: bool, path: &str) {
216 log_render_phase_dirty_diagnostics(RenderPhaseDirtyDiagnostics {
217 render_dirty: frame.render_dirty,
218 pointer_dirty: frame.pointer_dirty,
219 scene_dirty,
220 draw_repass_pending: self.draw_repass_pending,
221 draw_dirty_nodes: self.draw_dirty_nodes.len(),
222 layer_property_nodes: self.layer_property_nodes.len(),
223 layout_dirty_nodes: self.layout_dirty_nodes.len(),
224 moved_nodes: self.moved_nodes.len(),
225 structural_dirty_nodes: self.structural_parents.len(),
226 scene_updates: self.scene_updates.len(),
227 dirty_node_ids: render_phase_dirty_diagnostics_enabled().then(|| {
228 format!(
229 "draw={:?} layers={:?} layout={:?} moved={:?} structural={:?} updates={:?}",
230 self.draw_dirty_nodes,
231 self.layer_property_nodes,
232 self.layout_dirty_nodes,
233 self.moved_nodes,
234 self.structural_parents,
235 self.scene_updates,
236 )
237 }),
238 render_only_dirty: self.render_only_dirty,
239 recomposed_this_frame: frame.recomposed_this_frame,
240 path,
241 });
242 }
243}
244
245struct SurfaceFrame {
246 result: FrameUpdateResult,
247 rebuilt: bool,
248}
249
250pub(crate) const IDLE_PREFETCH_MAX_PASSES: usize = 4;
251
252impl<R> AppShell<R>
253where
254 R: Renderer,
255 R::Error: Debug,
256{
257 pub fn set_semantics_enabled(&mut self, enabled: bool) {
260 if self.app.semantics_enabled == enabled {
261 return;
262 }
263 self.app.semantics_enabled = enabled;
264 self.app.semantics_snapshot_revision = self.app.semantics_snapshot_revision.wrapping_add(1);
265 if enabled {
266 self.app.request_forced_layout_pass();
267 self.mark_all_dirty();
268 } else {
269 for surface in &mut self.surfaces {
270 surface.semantics_tree = None;
271 }
272 }
273 }
274
275 pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
280 self.surfaces[0].frame_rate_preference = preference;
281 }
282
283 pub fn frame_rate_preference(&self) -> FrameRatePreference {
287 self.surfaces[0].frame_rate_preference
288 }
289
290 pub(crate) fn process_frame(&mut self) -> FrameUpdateResult {
291 let _app_context = self.app.app_context.enter_scope();
292 self.process_frame_in_context(Composed::SKIPPED)
293 }
294
295 #[inline(never)]
299 pub(crate) fn compose_phase(&mut self) -> Composed {
300 let Some(root_key) = self.app.composition.root_key() else {
301 return Composed::SKIPPED;
302 };
303 let recomposed = match self
304 .app
305 .composition
306 .reconcile(root_key, &mut *self.app.content)
307 {
308 Ok(changed) => {
309 log::trace!(target: "cranpose::input", "reconcile changed={changed}");
310 if changed {
311 self.app.fps_monitor.record_recomposition();
312 if self.app.composition_tree_needs_layout() {
313 self.app.request_layout_pass();
314 }
315 request_render_invalidation();
316 }
317 changed
318 }
319 Err(NodeError::Missing { id }) => {
320 log::debug!("Recomposition skipped: node {id} no longer exists");
321 self.app.request_layout_pass();
322 request_render_invalidation();
323 false
324 }
325 Err(err) => {
326 log::error!("recomposition failed: {err}");
327 self.app.request_layout_pass();
328 request_render_invalidation();
329 false
330 }
331 };
332 Composed {
333 attempted: true,
334 recomposed,
335 }
336 }
337
338 pub(crate) fn process_frame_in_context(&mut self, composed: Composed) -> FrameUpdateResult {
339 let mut stages = FrameStages::start(frame_stage_telemetry_threshold_ms().is_some());
340
341 self.app.app_context.start_text_frame();
342 #[cfg(feature = "installed-fonts")]
343 self.apply_installed_fonts();
344 self.sync_window_roots_in_context();
345 let laid_out = self.lay_out(composed, &mut stages);
346
347 self.run_dispatch_queues();
348 self.update_modal_focus();
349 self.reveal_new_focus();
350
351 stages.reached();
352
353 self.app
354 .app_context
355 .clear_transient_scroll_motion_contexts();
356
357 let result = self.run_render_phase_in_context(laid_out);
358 stages.reached();
359 log_frame_stage_telemetry(&stages);
360 result
361 }
362
363 pub fn has_idle_prefetch_work(&self) -> bool {
365 self.app
366 .app_context
367 .enter(cranpose_ui::has_lazy_prefetch_requests)
368 }
369
370 pub fn run_idle_prefetch(
378 &mut self,
379 deadline: Option<Instant>,
380 mut frame_can_start: impl FnMut(&mut R) -> bool,
381 ) -> bool {
382 let _app_context = self.app.app_context.enter_scope();
383 let mut passes = 0;
384 while passes < IDLE_PREFETCH_MAX_PASSES
385 && cranpose_ui::lazy_prefetch_pass_due()
386 && deadline.is_none_or(|deadline| {
387 Instant::now() + cranpose_ui::lazy_prefetch_pass_cost() <= deadline
388 })
389 && !frame_can_start(self.renderer())
390 {
391 cranpose_ui::with_lazy_prefetch_pass(|| {
392 cranpose_ui::drain_lazy_prefetch_requests(cranpose_ui::schedule_measure_repass);
393 self.run_layout_phase_in_context();
394 cranpose_ui::warm_prefetched_slices(&mut self.app.composition.applier_mut());
395 });
396 passes += 1;
397 }
398 passes > 0
399 }
400
401 #[cfg(feature = "installed-fonts")]
404 fn apply_installed_fonts(&mut self) {
405 let generation = cranpose_render_common::installed_fonts::installed_font_generation();
406 if generation == self.app.installed_fonts {
407 return;
408 }
409 self.app.installed_fonts = generation;
410 for surface in &mut self.surfaces {
411 surface
412 .renderer
413 .apply_installed_fonts(&self.app.app_context);
414 }
415 }
416
417 fn run_dispatch_queues(&mut self) {
418 if self.app.app_context.has_pending_pointer_repasses() {
419 self.clear_dispatch_flags(DispatchInvalidationKind::Pointer);
420 }
421 if self.app.app_context.has_pending_focus_invalidations() {
422 self.clear_dispatch_flags(DispatchInvalidationKind::Focus);
423 }
424 self.app.flush_semantics_invalidations();
425 }
426
427 #[inline(never)]
431 fn clear_dispatch_flags(&mut self, kind: DispatchInvalidationKind) {
432 let mut applier = self.app.composition.applier_mut();
433 let clear = |node_id| match clear_dispatch_invalidation(&mut applier, node_id, kind) {
434 Ok(true) => log::trace!("Cleared the {kind:?} flag of node #{node_id}"),
435 Ok(false) => {}
436 Err(err) => {
437 log::debug!("Could not clear the {kind:?} flag of node #{node_id}: {err}");
438 }
439 };
440 match kind {
441 DispatchInvalidationKind::Pointer => process_pointer_repasses(clear),
442 DispatchInvalidationKind::Focus => process_focus_invalidations(clear),
443 }
444 }
445
446 fn take_structural_change_parents(&mut self) {
447 self.pending_dirty_nodes.clear();
448 if cranpose_core::DIAGNOSTICS.disable_structural_dirt.is_set() {
449 return;
450 }
451 if self.app.composition.root().is_none() {
452 return;
453 }
454 self.app
455 .composition
456 .applier_mut()
457 .take_structural_change_parents_into(&mut self.pending_dirty_nodes);
458 let reordered = self.pending_dirty_nodes.len();
461 self.app
462 .app_context
463 .append_z_order_changes(&mut self.pending_dirty_nodes);
464 if self.pending_dirty_nodes.len() > reordered {
465 self.reordered_children_to_parents(reordered);
466 }
467 }
468
469 #[inline(never)]
473 fn reordered_children_to_parents(&mut self, from: usize) {
474 let mut applier = self.app.composition.applier_mut();
475 for node in &mut self.pending_dirty_nodes[from..] {
476 if let Some(parent) = applier.get_mut(*node).ok().and_then(|child| child.parent()) {
477 *node = parent;
478 }
479 }
480 }
481
482 #[cfg(test)]
483 pub(crate) fn run_render_phase(&mut self) -> FrameUpdateResult {
484 let _app_context = self.app.app_context.enter_scope();
485 let laid_out = self.lay_out(Composed::SKIPPED, &mut FrameStages::start(false));
486 self.run_render_phase_in_context(laid_out)
487 }
488
489 #[expect(
491 clippy::needless_pass_by_value,
492 reason = "a phase takes the token of the phase before it, which only that phase makes"
493 )]
494 fn run_render_phase_in_context(&mut self, laid_out: LaidOut) -> FrameUpdateResult {
495 let inspector_revision = if self.app.inspector_projector.is_some()
496 && self
497 .surfaces
498 .iter()
499 .any(|surface| surface.inspector.state.open)
500 {
501 self.semantics_snapshot_revision()
502 } else {
503 0
504 };
505 let context = &self.app.app_context;
506 context.tick_cursor_blink();
507 let render_dirty = context.take_render_invalidation();
508 let pointer_dirty = context.take_pointer_invalidation();
509 context.take_focus_invalidation();
510 for surface in &mut self.surfaces {
511 surface.scoped_draw_nodes.clear();
512 surface.scoped_layer_property_nodes.clear();
513 surface.structural_scene_nodes.clear();
514 }
515 self.app
516 .app_context
517 .take_draw_repass_nodes_into(&mut self.pending_dirty_nodes);
518 route_nodes_by_surface(
519 &mut self.app,
520 &mut self.surfaces,
521 self.pending_dirty_nodes.iter().copied(),
522 SurfaceDirtyLane::Draw,
523 &mut self.routing_scratch,
524 );
525 self.app
526 .app_context
527 .take_layer_property_repass_nodes_into(&mut self.pending_layer_property_nodes);
528 route_nodes_by_surface(
529 &mut self.app,
530 &mut self.surfaces,
531 self.pending_layer_property_nodes.iter().copied(),
532 SurfaceDirtyLane::LayerProperties,
533 &mut self.routing_scratch,
534 );
535 self.take_structural_change_parents();
536 route_nodes_by_surface(
537 &mut self.app,
538 &mut self.surfaces,
539 self.pending_dirty_nodes.iter().copied(),
540 SurfaceDirtyLane::Structural,
541 &mut self.routing_scratch,
542 );
543 self.pending_dirty_nodes.clear();
544 self.pending_layer_property_nodes.clear();
545 let _ = self.app.app_context.has_focused_field();
546 let attributed = self.surfaces.iter().any(|surface| {
547 !surface.scoped_draw_nodes.is_empty()
548 || !surface.scoped_layer_property_nodes.is_empty()
549 || !surface.structural_scene_nodes.is_empty()
550 || !surface.scoped_layout_scene_nodes.is_empty()
551 || !surface.scoped_moved_scene_nodes.is_empty()
552 });
553 let layer_properties_pending = self
554 .surfaces
555 .iter()
556 .any(|surface| !surface.scoped_layer_property_nodes.is_empty());
557 let frame = FrameDirt {
558 render_dirty,
559 pointer_dirty,
560 attributed,
561 layer_properties_pending,
562 recomposed_this_frame: laid_out.recomposed,
563 };
564
565 let mut result = FrameUpdateResult::default();
566 for surface in &mut self.surfaces {
567 let mut frame = render_surface(&mut self.app, surface, &frame);
568 frame.result.visual_changed |=
569 inspector::refresh(&mut self.app, surface, inspector_revision);
570 surface.last_update = frame.result;
571 surface.frame_owed |= frame.result.visual_changed;
572 result.visual_changed |= frame.result.visual_changed;
573 result.structure_changed |= frame.result.structure_changed;
574 result.content_redrawn |= frame.rebuilt;
575 }
576 if result.content_redrawn {
577 self.prune_stale_draw_observations();
578 }
579 result.content_moved = std::mem::take(&mut self.app.content_moved);
580 result
581 }
582
583 fn prune_stale_draw_observations(&mut self) {
589 self.app.rebuilt_frames_since_observation_prune += 1;
590 if self.app.rebuilt_frames_since_observation_prune < OBSERVATION_PRUNE_INTERVAL {
591 return;
592 }
593 self.app.rebuilt_frames_since_observation_prune = 0;
594 let mut retained_visual_nodes = HashSet::default();
595 let mut any_graph = false;
596 for surface in &mut self.surfaces {
597 any_graph |= surface
598 .renderer
599 .scene()
600 .collect_retained_visual_observation_nodes(&mut surface.retained_visual_nodes);
601 retained_visual_nodes.extend(surface.retained_visual_nodes.iter().copied());
602 }
603 if any_graph {
604 cranpose_ui::prune_draw_observations_to_nodes(&retained_visual_nodes);
605 }
606 }
607}
608
609pub(crate) const OBSERVATION_PRUNE_INTERVAL: u32 = 60;
612
613fn render_surface<R>(
614 app: &mut ShellApp,
615 surface: &mut RootSurface<R>,
616 frame: &FrameDirt,
617) -> SurfaceFrame
618where
619 R: Renderer,
620 R::Error: Debug,
621{
622 surface.begin_frame();
623 let draw_dirty_nodes = std::mem::take(&mut surface.scoped_draw_nodes);
624 let layer_property_nodes = std::mem::take(&mut surface.scoped_layer_property_nodes);
625 let structural_parents = std::mem::take(&mut surface.structural_scene_nodes);
626 let draw_repass_pending = !draw_dirty_nodes.is_empty();
627 let mut draw_dirty_nodes = refresh_draw_nodes(app, surface, draw_dirty_nodes);
628 if frame.render_dirty && !frame.layer_properties_pending && !draw_repass_pending {
629 draw_dirty_nodes = refresh_retained_redraw_nodes(app, surface, draw_dirty_nodes);
630 }
631 let layout = ScopedLayoutNodes {
632 reshaped: std::mem::take(&mut surface.scoped_layout_scene_nodes),
633 moved: std::mem::take(&mut surface.scoped_moved_scene_nodes),
634 };
635 let scene_updates = std::mem::take(&mut surface.scene_updates);
636 let scene_dirty = surface.scene_dirty;
637 let mut dirt = SurfaceDirt::classify(
638 frame,
639 scene_dirty,
640 draw_repass_pending,
641 ScopedVisualNodes {
642 draw: draw_dirty_nodes,
643 layers: layer_property_nodes,
644 },
645 layout,
646 structural_parents,
647 scene_updates,
648 );
649
650 if !dirt.needs_scene_rebuild {
651 dirt.log(
652 frame,
653 scene_dirty,
654 if dirt.render_only_dirty {
655 "present-retained"
656 } else {
657 "skip"
658 },
659 );
660 surface.scoped_layout_scene_nodes = dirt.layout_dirty_nodes;
661 surface.scoped_moved_scene_nodes = dirt.moved_nodes;
662 surface.scoped_draw_nodes = dirt.draw_dirty_nodes;
663 surface.scoped_layer_property_nodes = dirt.layer_property_nodes;
664 surface.structural_scene_nodes = dirt.structural_parents;
665 surface.scene_updates = dirt.scene_updates;
666 if dirt.render_only_dirty && app.dev_options.fps_counter {
667 draw_dev_overlay(app, surface);
668 }
669 return SurfaceFrame {
670 result: FrameUpdateResult {
671 visual_changed: dirt.render_only_dirty,
672 ..FrameUpdateResult::default()
673 },
674 rebuilt: false,
675 };
676 }
677
678 surface.scene_dirty = false;
679 let structure_changed = !dirt.render_only_dirty && !dirt.use_visual_update();
680 rebuild_surface_scene(app, surface, frame, scene_dirty, &dirt);
681 if dev_overlay_belongs_on(surface.id, app.dev_options.fps_counter) {
682 draw_dev_overlay(app, surface);
683 }
684 dirt.layout_dirty_nodes.clear();
685 dirt.moved_nodes.clear();
686 surface.scoped_moved_scene_nodes = dirt.moved_nodes;
687 surface.scoped_draw_nodes = dirt.draw_dirty_nodes;
688 surface.scoped_layer_property_nodes = dirt.layer_property_nodes;
689 surface.structural_scene_nodes = dirt.structural_parents;
690 surface.scene_updates = dirt.scene_updates;
691 surface.scoped_layout_scene_nodes = dirt.layout_dirty_nodes;
692 SurfaceFrame {
693 result: FrameUpdateResult {
694 visual_changed: true,
695 structure_changed,
696 ..FrameUpdateResult::default()
697 },
698 rebuilt: true,
699 }
700}
701
702fn rebuild_surface_scene<R>(
703 app: &mut ShellApp,
704 surface: &mut RootSurface<R>,
705 frame: &FrameDirt,
706 scene_dirty: bool,
707 dirt: &SurfaceDirt,
708) where
709 R: Renderer,
710 R::Error: Debug,
711{
712 let viewport_size = surface.viewport_size();
713 let Some(root) = surface.root_node(app) else {
714 surface.renderer.scene_mut().clear();
715 return;
716 };
717 let mut applier = app.composition.applier_mut();
718 dirt.log(frame, scene_dirty, dirt.rebuild_path());
719 let rebuild_result = if dirt.use_visual_update() {
720 surface.renderer.update_visual_scene_from_applier(
723 &mut applier,
724 root,
725 viewport_size,
726 &dirt.scene_updates,
727 )
728 } else if dirt.use_partial_update() {
729 surface.renderer.update_scene_from_applier(
730 &mut applier,
731 root,
732 viewport_size,
733 &dirt.scene_updates,
734 )
735 } else {
736 surface
737 .renderer
738 .rebuild_scene_from_applier(&mut applier, root, viewport_size)
739 };
740 if let Err(err) = rebuild_result {
741 log::error!("renderer rebuild failed: {err:?}");
742 surface.renderer.scene_mut().clear();
743 }
744}
745
746fn dev_overlay_belongs_on(id: RootId, fps_counter: bool) -> bool {
747 fps_counter && matches!(id, RootId::Primary)
748}
749
750fn refresh_draw_nodes<R: Renderer>(
751 app: &mut ShellApp,
752 surface: &mut RootSurface<R>,
753 mut dirty_nodes: Vec<NodeId>,
754) -> Vec<NodeId> {
755 if dirty_nodes.is_empty() {
756 return dirty_nodes;
757 }
758 let Some(layout_tree) = surface.layout_tree.as_mut() else {
759 return dirty_nodes;
760 };
761
762 dirty_nodes.sort_unstable();
763 dirty_nodes.dedup();
764 let dirty_set: HashSet<NodeId> = dirty_nodes.iter().copied().collect();
765 let mut applier = app.composition.applier_mut();
766 let refresh_scope = build_draw_refresh_scope(&mut applier, &dirty_set);
767 refresh_layout_box_data(
768 &mut applier,
769 layout_tree.root_mut(),
770 &refresh_scope,
771 &dirty_set,
772 );
773 dirty_nodes
774}
775
776fn refresh_retained_redraw_nodes<R: Renderer>(
777 app: &mut ShellApp,
778 surface: &mut RootSurface<R>,
779 mut dirty_nodes: Vec<NodeId>,
780) -> Vec<NodeId> {
781 dirty_nodes.clear();
782 let Some(root) = surface.root_node(app) else {
783 return dirty_nodes;
784 };
785 {
786 let mut applier = app.composition.applier_mut();
787 collect_retained_redraw_nodes(
788 &mut applier,
789 &app.app_context,
790 root,
791 &mut dirty_nodes,
792 &mut surface.redraw_walk,
793 );
794 }
795 refresh_draw_nodes(app, surface, dirty_nodes)
796}
797
798fn clear_dispatch_invalidation(
799 applier: &mut MemoryApplier,
800 node_id: NodeId,
801 invalidation: DispatchInvalidationKind,
802) -> Result<bool, NodeError> {
803 match invalidation {
804 DispatchInvalidationKind::Pointer => {
805 match applier.with_node::<LayoutNode, _>(node_id, |node| {
806 let needs_pointer_pass = node.needs_pointer_pass();
807 if needs_pointer_pass {
808 node.clear_needs_pointer_pass();
809 }
810 needs_pointer_pass
811 }) {
812 Ok(cleared) => Ok(cleared),
813 Err(NodeError::TypeMismatch { .. }) => applier
814 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
815 let needs_pointer_pass = node.needs_pointer_pass();
816 if needs_pointer_pass {
817 node.clear_needs_pointer_pass();
818 }
819 needs_pointer_pass
820 }),
821 Err(err) => Err(err),
822 }
823 }
824 DispatchInvalidationKind::Focus => {
825 match applier.with_node::<LayoutNode, _>(node_id, |node| {
826 let needs_focus_sync = node.needs_focus_sync();
827 if needs_focus_sync {
828 node.clear_needs_focus_sync();
829 }
830 needs_focus_sync
831 }) {
832 Ok(cleared) => Ok(cleared),
833 Err(NodeError::TypeMismatch { .. }) => applier
834 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
835 let needs_focus_sync = node.needs_focus_sync();
836 if needs_focus_sync {
837 node.clear_needs_focus_sync();
838 }
839 needs_focus_sync
840 }),
841 Err(err) => Err(err),
842 }
843 }
844 }
845}
846
847pub(crate) fn build_draw_refresh_scope(
848 applier: &mut MemoryApplier,
849 dirty_nodes: &HashSet<NodeId>,
850) -> HashSet<NodeId> {
851 let mut refresh_scope =
852 HashSet::with_capacity_and_hasher(dirty_nodes.len(), Default::default());
853 for &dirty_node in dirty_nodes {
854 let mut current = Some(dirty_node);
855 while let Some(node_id) = current {
856 if !refresh_scope.insert(node_id) {
857 break;
858 }
859 current = applier.get_mut(node_id).ok().and_then(|node| node.parent());
860 }
861 }
862 refresh_scope
863}
864
865fn collect_retained_redraw_nodes(
871 applier: &mut MemoryApplier,
872 context: &AppContext,
873 root: NodeId,
874 dirty_nodes: &mut Vec<NodeId>,
875 pending: &mut Vec<NodeId>,
876) {
877 pending.clear();
878 context.take_retained_redraw_nodes_into(pending);
879 for &id in pending.iter() {
880 match redraw_place(applier, id, root) {
881 RedrawPlace::Here => {
882 if take_redraw_flag(applier, id) {
883 dirty_nodes.push(id);
884 }
885 }
886 RedrawPlace::Elsewhere => context.keep_retained_redraw_node(id),
887 RedrawPlace::Gone => {}
888 }
889 }
890}
891
892enum RedrawPlace {
894 Here,
896 Elsewhere,
898 Gone,
900}
901
902fn redraw_place(applier: &mut MemoryApplier, id: NodeId, root: NodeId) -> RedrawPlace {
903 let mut current = id;
904 loop {
905 if current == root {
906 return RedrawPlace::Here;
907 }
908 let Ok(node) = applier.get_mut(current) else {
909 return if current == id {
910 RedrawPlace::Gone
911 } else {
912 RedrawPlace::Elsewhere
913 };
914 };
915 let parent = node.parent();
916 if node
917 .as_any_mut()
918 .downcast_mut::<LayoutNode>()
919 .is_some_and(|layout| layout.is_window_root())
920 {
921 return RedrawPlace::Elsewhere;
922 }
923 let Some(parent) = parent else {
924 return RedrawPlace::Elsewhere;
925 };
926 current = parent;
927 }
928}
929
930fn take_redraw_flag(applier: &mut MemoryApplier, id: NodeId) -> bool {
933 let Ok(node) = applier.get_mut(id) else {
934 return false;
935 };
936 let any = node.as_any_mut();
937 if let Some(layout) = any.downcast_mut::<LayoutNode>() {
938 let redraw = layout.needs_redraw();
939 layout.clear_needs_redraw();
940 return redraw;
941 }
942 match any.downcast_mut::<SubcomposeLayoutNode>() {
943 Some(subcompose) => {
944 let redraw = subcompose.needs_redraw();
945 subcompose.clear_needs_redraw();
946 redraw
947 }
948 None => false,
949 }
950}
951
952fn refresh_layout_box_data(
953 applier: &mut MemoryApplier,
954 layout: &mut LayoutBox,
955 refresh_scope: &HashSet<NodeId>,
956 dirty_nodes: &HashSet<NodeId>,
957) {
958 if !refresh_scope.contains(&layout.node_id) {
959 return;
960 }
961
962 if dirty_nodes.contains(&layout.node_id) {
963 let refreshed = applier
964 .with_node::<LayoutNode, _>(layout.node_id, |node| {
965 node.clear_needs_redraw();
966 (
967 node.modifier(),
968 node.resolved_modifiers(),
969 node.modifier_slices_snapshot(),
970 node.semantics_configuration(),
971 )
972 })
973 .or_else(|_| {
974 applier.with_node::<SubcomposeLayoutNode, _>(layout.node_id, |node| {
975 node.clear_needs_redraw();
976 (
977 node.modifier(),
978 node.resolved_modifiers(),
979 node.modifier_slices_snapshot(),
980 node.semantics_configuration(),
981 )
982 })
983 });
984 if let Ok((modifier, resolved_modifiers, slices, semantics)) = refreshed {
985 layout.node_data.modifier = modifier;
986 layout.node_data.resolved_modifiers = resolved_modifiers;
987 layout.node_data.modifier_slices = slices;
988 layout.node_data.semantics = semantics.map(Rc::new);
989 }
990 }
991
992 for child in &mut layout.children {
993 refresh_layout_box_data(applier, child, refresh_scope, dirty_nodes);
994 }
995}
996
997#[cfg(test)]
998#[path = "tests/shell_frame_tests.rs"]
999mod tests;