Skip to main content

cranpose_render_common/
graph_scene.rs

1use std::{
2    cell::{Cell, Ref, RefCell},
3    cmp::Reverse,
4    rc::Rc,
5};
6
7use cranpose_core::{
8    MemoryApplier, NodeId,
9    collections::map::{HashMap, HashSet},
10};
11use cranpose_foundation::{MINIMUM_TOUCH_TARGET_SIZE, PointerEvent, PointerEventKind};
12use cranpose_ui::{LayoutNode, ModifierNodeSlices, SubcomposeLayoutNode};
13use cranpose_ui_graphics::{Point, PointerIcon, Rect, RoundedCornerShape, space::Ray};
14
15use crate::{
16    HitTestTarget, RenderScene, SceneUpdates,
17    graph::{ProjectiveTransform, RenderGraph},
18    scene_pick::ScenePick,
19};
20
21/// Counters and once-only warnings that a scene collects while it dispatches pointer events.
22pub struct RenderDiagnostics {
23    reported_warnings: RefCell<HashSet<&'static str>>,
24    live_modifier_slice_lookup_miss_count: Cell<usize>,
25}
26
27impl RenderDiagnostics {
28    /// Creates diagnostics with no warnings reported and no lookup misses counted.
29    pub fn new() -> Self {
30        Self {
31            reported_warnings: RefCell::new(HashSet::default()),
32            live_modifier_slice_lookup_miss_count: Cell::new(0),
33        }
34    }
35
36    /// Returns true the first time it is called with `key`, so that a warning is reported once.
37    pub fn claim_warning_once(&self, key: &'static str) -> bool {
38        self.reported_warnings.borrow_mut().insert(key)
39    }
40
41    /// Counts one pointer dispatch that fell back to the captured modifier slices because the live
42    /// node could not be found.
43    pub fn record_live_modifier_slice_lookup_miss(&self) {
44        self.live_modifier_slice_lookup_miss_count.set(
45            self.live_modifier_slice_lookup_miss_count
46                .get()
47                .saturating_add(1),
48        );
49    }
50
51    /// Number of pointer dispatches that did not find the live node and used the modifier slices
52    /// captured at build time instead.
53    pub fn live_modifier_slice_lookup_miss_count(&self) -> usize {
54        self.live_modifier_slice_lookup_miss_count.get()
55    }
56}
57
58impl Default for RenderDiagnostics {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64/// A clip that cuts a hit target, kept as a transformed rectangle.
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub struct HitClip {
67    /// The clip rectangle after the layer transforms, as four corners in window space.
68    pub quad: [[f32; 2]; 4],
69    /// Axis-aligned bounds of `quad` in window coordinates.
70    pub bounds: Rect,
71}
72
73/// A mesh surface of a scene, as the hit targets on its content see it:
74/// the scene's pick, which surface it is, and the clips above it.
75pub struct SurfaceTarget {
76    /// Finds the surface and content point a window point shows.
77    pub pick: Rc<ScenePick>,
78    /// The surface's index in `pick`.
79    pub index: usize,
80    /// Axis-aligned bounds of the clips above the surface, in window space.
81    pub outer_clip_bounds: Option<Rect>,
82    /// The clips above the surface, as quads in window space.
83    pub outer_clips: Vec<HitClip>,
84}
85
86/// How many parts a target's rect splits into along each side for its
87/// bounds in the window: on a curved surface the corners alone miss the
88/// bulge between them.
89const WINDOW_BOUNDS_STEPS: u16 = 2;
90
91impl SurfaceTarget {
92    /// Where `local`, a rect of a target's own space into which
93    /// `content_to_local` maps the surface's content, shows in the window:
94    /// the bounds of a grid of its points in front of the eye; `None` when
95    /// none is.
96    pub fn window_bounds(
97        &self,
98        content_to_local: &ProjectiveTransform,
99        local: Rect,
100    ) -> Option<Rect> {
101        let local_to_content = content_to_local.inverse()?;
102        let steps = f32::from(WINDOW_BOUNDS_STEPS);
103        let grid = (0..=WINDOW_BOUNDS_STEPS)
104            .flat_map(|row| (0..=WINDOW_BOUNDS_STEPS).map(move |column| (row, column)));
105        grid.filter_map(|(row, column)| {
106            let point = Point::new(
107                local.x + local.width * f32::from(column) / steps,
108                local.y + local.height * f32::from(row) / steps,
109            );
110            let content = local_to_content.map_point(point);
111            self.pick.window_point(self.index, content)
112        })
113        .map(|point| Rect {
114            x: point.x,
115            y: point.y,
116            width: 0.0,
117            height: 0.0,
118        })
119        .reduce(|bounds, point| bounds.union(point))
120    }
121}
122
123/// Where a hit target on a mesh surface lies on the surface's content.
124#[derive(Clone, Copy)]
125pub struct SurfaceGeometry<'a> {
126    /// The surface the target is on.
127    pub target: &'a Rc<SurfaceTarget>,
128    /// Maps the surface's content into the node's local space.
129    pub content_to_local: ProjectiveTransform,
130    /// Axis-aligned bounds of the clips within the surface, in content
131    /// space, or `None` when none clips.
132    pub clip_bounds: Option<Rect>,
133    /// The clips within the surface, as quads in content space.
134    pub clips: &'a [HitClip],
135}
136
137impl SurfaceGeometry<'_> {
138    /// Where `local`, a rect of the target's own space, shows in the window.
139    pub fn window_bounds(&self, local: Rect) -> Option<Rect> {
140        self.target.window_bounds(&self.content_to_local, local)
141    }
142}
143
144/// Geometry for a hit target, borrowing the clip chain until the sink records it.
145#[derive(Clone, Copy)]
146pub struct HitGeometry<'a> {
147    /// Axis-aligned bounds of `quad` in window coordinates.
148    pub rect: Rect,
149    /// The node's rectangle after the layer transforms, as four corners in window space.
150    pub quad: [[f32; 2]; 4],
151    /// The node's own rectangle in its local space.
152    pub local_bounds: Rect,
153    /// Maps window coordinates into the node's local space.
154    pub world_to_local: ProjectiveTransform,
155    /// Axis-aligned bounds of the ancestors' clips in window space, or `None` when none clips.
156    pub hit_clip_bounds: Option<Rect>,
157    /// Clips of the ancestors that cut the target, as transformed quads in window space.
158    pub hit_clips: &'a [HitClip],
159    /// For a target on a mesh surface, where it lies on the surface's
160    /// content. The window-space fields above then hold the surface's flat
161    /// stand-in, exact only on a flat mesh; the surface decides the hit.
162    pub surface: Option<SurfaceGeometry<'a>>,
163}
164
165/// What a hit target answers with: the shape that narrows its bounds, and the
166/// node's modifier slices, which hold the pointer handlers it dispatches to
167/// and the pointer icon it asks for while hovered.
168pub struct HitTargetSpec<'a> {
169    /// Narrows the target's rectangle to a rounded shape, so a point in a
170    /// corner cutout misses it.
171    pub shape: Option<RoundedCornerShape>,
172    /// The node's slices, shared by the target rather than copied.
173    pub handlers: &'a Rc<ModifierNodeSlices>,
174}
175
176/// A hit target in window space: the node's geometry, clips and pointer handlers.
177#[derive(Clone)]
178pub struct HitRegion {
179    /// The layout node that owns the target.
180    pub node_id: NodeId,
181    /// Nodes that keep receiving the move, up and cancel events of a gesture that starts here: this
182    /// node first, then its pointer-input ancestors from the nearest outward.
183    pub capture_path: Vec<NodeId>,
184    /// Axis-aligned bounds of `quad` in window coordinates.
185    pub rect: Rect,
186    /// The node's rectangle after the layer transforms, as four corners in window space.
187    pub quad: [[f32; 2]; 4],
188    /// The node's own rectangle in its local space.
189    pub local_bounds: Rect,
190    /// Maps window coordinates into the node's local space.
191    pub world_to_local: ProjectiveTransform,
192    /// Rounded shape that narrows the target's rectangle, or `None` for a plain rectangle.
193    pub shape: Option<RoundedCornerShape>,
194    /// The node's modifier slices, shared with the render graph: its pointer
195    /// handlers and pointer icon.
196    pub handlers: Rc<ModifierNodeSlices>,
197    /// Draw order of the target. A larger value is drawn later and is hit first.
198    pub z_index: usize,
199    /// Axis-aligned bounds of the ancestors' clips in window space, or `None` when none clips.
200    pub hit_clip_bounds: Option<Rect>,
201    /// Clips of the ancestors that cut this target, as transformed quads in window space.
202    pub hit_clips: Vec<HitClip>,
203    /// For a target on a mesh surface, where it lies on the surface; shared,
204    /// as few targets are and clones of a target hold the same.
205    surface: Option<Rc<SurfaceRegion>>,
206    diagnostics: Rc<RenderDiagnostics>,
207}
208
209/// A hit target's place on a mesh surface's content.
210struct SurfaceRegion {
211    target: Rc<SurfaceTarget>,
212    content_to_local: ProjectiveTransform,
213    clip_bounds: Option<Rect>,
214    clips: Vec<HitClip>,
215}
216
217impl SurfaceRegion {
218    /// The local point the window point shows on the target, when the
219    /// target's surface is the nearest there and no clip cuts the point.
220    fn local_point(&self, window: Point) -> Option<Point> {
221        let target = &self.target;
222        if target
223            .outer_clip_bounds
224            .is_some_and(|clip| !clip.contains(window.x, window.y))
225            || !target
226                .outer_clips
227                .iter()
228                .all(|clip| point_in_quad(window, clip.quad))
229        {
230            return None;
231        }
232        self.local_in(target.pick.content_point(window, target.index)?)
233    }
234
235    /// The local point `ray`, in the space the scene's eye stands in, meets
236    /// on the target, when the target's surface is the nearest along it and
237    /// no clip within the surface cuts the point.
238    fn local_point_along(&self, ray: Ray) -> Option<Point> {
239        let (index, hit) = self.target.pick.nearest_along(ray)?;
240        (index == self.target.index)
241            .then(|| self.local_in(hit.point))
242            .flatten()
243    }
244
245    /// The local point of content point `content`, unless a clip within
246    /// the surface cuts it.
247    fn local_in(&self, content: Point) -> Option<Point> {
248        if self
249            .clip_bounds
250            .is_some_and(|clip| !clip.contains(content.x, content.y))
251            || !self
252                .clips
253                .iter()
254                .all(|clip| point_in_quad(content, clip.quad))
255        {
256            return None;
257        }
258        self.content_to_local.map_point_in_front(content)
259    }
260
261    /// The local point under the window point, or along the event's ray,
262    /// for a gesture the target holds, which may leave the surface.
263    fn drag_point(&self, event: &PointerEvent) -> Option<Point> {
264        let target = &self.target;
265        let content = match event.ray {
266            Some(ray) => target.pick.drag_point_along(ray, target.index)?,
267            None => target
268                .pick
269                .drag_point(event.global_position, target.index)?,
270        };
271        Some(self.content_to_local.map_point(content))
272    }
273}
274
275struct HitRegionInit<'a> {
276    node_id: NodeId,
277    capture_path: Vec<NodeId>,
278    geometry: HitGeometry<'a>,
279    clip_buffer: Vec<HitClip>,
280    shape: Option<RoundedCornerShape>,
281    handlers: Rc<ModifierNodeSlices>,
282    z_index: usize,
283    diagnostics: Rc<RenderDiagnostics>,
284}
285
286impl Default for HitRegionInit<'_> {
287    fn default() -> Self {
288        Self {
289            node_id: NodeId(0),
290            capture_path: Vec::new(),
291            geometry: HitGeometry {
292                rect: Rect {
293                    x: 0.0,
294                    y: 0.0,
295                    width: 0.0,
296                    height: 0.0,
297                },
298                quad: [[0.0, 0.0]; 4],
299                local_bounds: Rect {
300                    x: 0.0,
301                    y: 0.0,
302                    width: 0.0,
303                    height: 0.0,
304                },
305                world_to_local: ProjectiveTransform::identity(),
306                hit_clip_bounds: None,
307                hit_clips: &[],
308                surface: None,
309            },
310            clip_buffer: Vec::new(),
311            shape: None,
312            handlers: Rc::default(),
313            z_index: 0,
314            diagnostics: Rc::new(RenderDiagnostics::new()),
315        }
316    }
317}
318
319impl HitRegion {
320    fn with_diagnostics(init: HitRegionInit<'_>) -> Self {
321        let HitRegionInit {
322            node_id,
323            capture_path,
324            geometry,
325            clip_buffer: mut hit_clips,
326            shape,
327            handlers,
328            z_index,
329            diagnostics,
330        } = init;
331        let HitGeometry {
332            rect,
333            quad,
334            local_bounds,
335            world_to_local,
336            hit_clip_bounds,
337            hit_clips: clips,
338            surface,
339        } = geometry;
340        hit_clips.extend_from_slice(clips);
341        Self {
342            node_id,
343            capture_path,
344            rect,
345            quad,
346            local_bounds,
347            world_to_local,
348            shape,
349            handlers,
350            z_index,
351            hit_clip_bounds,
352            hit_clips,
353            surface: surface.map(|surface| {
354                Rc::new(SurfaceRegion {
355                    target: Rc::clone(surface.target),
356                    content_to_local: surface.content_to_local,
357                    clip_bounds: surface.clip_bounds,
358                    clips: surface.clips.to_vec(),
359                })
360            }),
361            diagnostics,
362        }
363    }
364
365    fn contains(&self, x: f32, y: f32) -> bool {
366        if let Some(surface) = &self.surface {
367            return surface
368                .local_point(Point { x, y })
369                .is_some_and(|local| self.contains_local(local));
370        }
371        if !self.rect.contains(x, y) {
372            return false;
373        }
374
375        if let Some(clip_bounds) = self.hit_clip_bounds
376            && !clip_bounds.contains(x, y)
377        {
378            return false;
379        }
380
381        let point = Point { x, y };
382        if !point_in_quad(point, self.quad) {
383            return false;
384        }
385
386        for clip in &self.hit_clips {
387            if !point_in_quad(point, clip.quad) {
388                return false;
389            }
390        }
391
392        let Some(local_point) = self.world_to_local.map_point_in_front(point) else {
393            return false;
394        };
395        self.contains_local(local_point)
396    }
397
398    fn contains_local(&self, local_point: Point) -> bool {
399        if let Some(shape) = self.shape {
400            point_in_rounded_rect(local_point, self.local_bounds, shape)
401        } else {
402            self.local_bounds.contains(local_point.x, local_point.y)
403        }
404    }
405
406    /// The squared distance from the point to this target when the point lies
407    /// inside the target grown to the minimum touch size and the target takes
408    /// input. None when the target is large enough on its own, when the point
409    /// is outside its reach, or when a clip cuts the point off.
410    fn reach_distance(&self, x: f32, y: f32) -> Option<f32> {
411        if self.handlers.pointer_inputs().is_empty() || self.surface.is_some() {
412            return None;
413        }
414        if let Some(clip_bounds) = self.hit_clip_bounds
415            && !clip_bounds.contains(x, y)
416        {
417            return None;
418        }
419        let grow_x = ((MINIMUM_TOUCH_TARGET_SIZE - self.rect.width) / 2.0).max(0.0);
420        let grow_y = ((MINIMUM_TOUCH_TARGET_SIZE - self.rect.height) / 2.0).max(0.0);
421        if grow_x <= 0.0 && grow_y <= 0.0 {
422            return None;
423        }
424        let right = self.rect.x + self.rect.width;
425        let bottom = self.rect.y + self.rect.height;
426        let in_reach = x >= self.rect.x - grow_x
427            && x <= right + grow_x
428            && y >= self.rect.y - grow_y
429            && y <= bottom + grow_y;
430        if !in_reach {
431            return None;
432        }
433        let dx = (self.rect.x - x).max(x - right).max(0.0);
434        let dy = (self.rect.y - y).max(y - bottom).max(0.0);
435        Some(dx * dx + dy * dy)
436    }
437
438    fn localize_event(&self, event: &PointerEvent) -> PointerEvent {
439        let local = match &self.surface {
440            Some(surface) => surface
441                .drag_point(event)
442                .unwrap_or_else(|| self.world_to_local.map_point(event.global_position)),
443            None => self.world_to_local.map_point(event.global_position),
444        };
445        event.copy_with_local_position(Point {
446            x: local.x - self.local_bounds.x,
447            y: local.y - self.local_bounds.y,
448        })
449    }
450
451    fn dispatch_modifier_slices(&self, modifier_slices: &ModifierNodeSlices, event: PointerEvent) {
452        if should_skip_consumed_event(&event) {
453            return;
454        }
455
456        modifier_slices.dispatch_pointer_event(self.localize_event(&event));
457    }
458
459    fn live_modifier_slices(&self, applier: &mut MemoryApplier) -> Option<Rc<ModifierNodeSlices>> {
460        if let Ok(modifier_slices) =
461            applier.with_node::<LayoutNode, _>(self.node_id, |node| node.modifier_slices_snapshot())
462        {
463            return Some(modifier_slices);
464        }
465
466        applier
467            .with_node::<SubcomposeLayoutNode, _>(self.node_id, |node| {
468                node.modifier_slices_snapshot()
469            })
470            .ok()
471    }
472}
473
474fn is_terminal_pointer_event(kind: PointerEventKind) -> bool {
475    matches!(kind, PointerEventKind::Up | PointerEventKind::Cancel)
476}
477
478fn should_skip_consumed_event(event: &PointerEvent) -> bool {
479    event.is_consumed() && !is_terminal_pointer_event(event.kind)
480}
481
482impl HitTestTarget for HitRegion {
483    fn node_id(&self) -> NodeId {
484        self.node_id
485    }
486
487    fn pointer_icon(&self) -> Option<PointerIcon> {
488        self.handlers.pointer_icon().cloned()
489    }
490
491    fn capture_path(&self) -> Vec<NodeId> {
492        self.capture_path.clone()
493    }
494
495    fn dispatch(&self, event: PointerEvent) {
496        self.dispatch_modifier_slices(&self.handlers, event);
497    }
498
499    fn dispatch_with_applier(&self, applier: &mut MemoryApplier, event: PointerEvent) {
500        if let Some(modifier_slices) = self.live_modifier_slices(applier) {
501            self.dispatch_modifier_slices(modifier_slices.as_ref(), event);
502            return;
503        }
504
505        self.diagnostics.record_live_modifier_slice_lookup_miss();
506        self.dispatch_modifier_slices(&self.handlers, event);
507    }
508}
509
510#[derive(Default)]
511struct HitBuffers {
512    hit_clips: Vec<HitClip>,
513    capture_path: Vec<NodeId>,
514}
515
516// The hit targets of a scene's graph in draw order, the last drawn at the
517// end, and the storage the next collection of them reuses.
518#[derive(Default)]
519struct HitList {
520    hits: Vec<HitRegion>,
521    buffers: Vec<HitBuffers>,
522    // The draw order index the next pushed target receives.
523    next_z: usize,
524    // Where in `hits` the target of each node lies.
525    node_index: HashMap<NodeId, usize>,
526    // The graph changed a target since the list was collected: the next
527    // query collects it again.
528    stale: bool,
529}
530
531impl HitList {
532    // Removes the targets, releases their handlers and keeps their storage.
533    fn clear(&mut self) {
534        self.buffers.clear();
535        for hit in self.hits.drain(..) {
536            let mut buffers = HitBuffers {
537                hit_clips: hit.hit_clips,
538                capture_path: hit.capture_path,
539            };
540            buffers.hit_clips.clear();
541            buffers.capture_path.clear();
542            self.buffers.push(buffers);
543        }
544        self.node_index.clear();
545        self.next_z = 0;
546    }
547}
548
549/// The hit targets of a [`Scene`], borrowed apart from its graph, so a walk
550/// of the graph pushes targets while it reads the graph in place.
551pub(crate) struct HitTargets<'a> {
552    list: &'a mut HitList,
553    diagnostics: &'a Rc<RenderDiagnostics>,
554}
555
556impl HitTargets<'_> {
557    /// Adds an interactive target in draw order (see [`Scene::push_hit`]).
558    pub(crate) fn push(
559        &mut self,
560        node_id: NodeId,
561        capture_path: &[NodeId],
562        geometry: HitGeometry<'_>,
563        target: HitTargetSpec<'_>,
564    ) {
565        let HitTargetSpec { shape, handlers } = target;
566        if handlers.pointer_inputs().is_empty() && handlers.pointer_icon().is_none() {
567            return;
568        }
569        let list = &mut *self.list;
570        let mut buffers = list.buffers.pop().unwrap_or_default();
571        buffers.capture_path.extend_from_slice(capture_path);
572
573        let z_index = list.next_z;
574        list.next_z += 1;
575        let hit_index = list.hits.len();
576        list.hits.push(HitRegion::with_diagnostics(HitRegionInit {
577            node_id,
578            capture_path: buffers.capture_path,
579            geometry,
580            clip_buffer: buffers.hit_clips,
581            shape,
582            handlers: Rc::clone(handlers),
583            z_index,
584            diagnostics: Rc::clone(self.diagnostics),
585        }));
586        list.node_index.insert(node_id, hit_index);
587    }
588}
589
590/// The retained render graph of a window together with the hit targets built from it.
591pub struct Scene {
592    /// The retained render graph, or `None` before the first build.
593    pub graph: Option<RenderGraph>,
594    // Collected from the graph when a query first needs them after it
595    // changed: a frame that moves a target and takes no pointer input does
596    // not walk the graph for targets.
597    hits: RefCell<HitList>,
598    diagnostics: Rc<RenderDiagnostics>,
599    // The updates that moved a layer with no layout pass, and the rebuilds,
600    // which may have.
601    layer_moves: u64,
602}
603
604/// How many hit targets a [`Scene`] holds and has room for, and the same for
605/// its index of them by node, for debugging memory use.
606#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
607pub struct HitStorage {
608    /// Hit targets held.
609    pub hits_len: usize,
610    /// Room reserved for hit targets.
611    pub hits_capacity: usize,
612    /// Nodes indexed.
613    pub node_index_len: usize,
614    /// Room reserved in the node index.
615    pub node_index_capacity: usize,
616}
617
618impl Scene {
619    /// Creates an empty scene with no graph and no hit targets.
620    pub fn new() -> Self {
621        Self {
622            graph: None,
623            hits: RefCell::default(),
624            diagnostics: Rc::new(RenderDiagnostics::new()),
625            layer_moves: 0,
626        }
627    }
628
629    /// The diagnostics shared by the scene and its hit targets.
630    pub fn diagnostics(&self) -> &RenderDiagnostics {
631        self.diagnostics.as_ref()
632    }
633
634    /// Rebuilds the retained graph from the applier, reusing storage from the
635    /// previous graph. The hit targets follow the new graph.
636    pub fn rebuild_from_applier(&mut self, applier: &MemoryApplier, root: NodeId) {
637        let previous = self.graph.take();
638        self.hits.get_mut().stale = true;
639        self.layer_moves += 1;
640        let Some(graph) =
641            crate::scene_builder::rebuild_graph_from_applier(applier, root, 1.0, previous)
642        else {
643            return;
644        };
645        crate::scene_builder::publish_space(applier, &graph);
646        self.graph = Some(graph);
647    }
648
649    // The hit targets, collected from the graph first when it changed since
650    // they were.
651    fn fresh_hits(&self) -> Ref<'_, HitList> {
652        if self.hits.borrow().stale {
653            let mut list = self.hits.borrow_mut();
654            list.clear();
655            list.stale = false;
656            if let Some(graph) = &self.graph {
657                crate::hit_graph::collect_hits_from_graph(
658                    &graph.root,
659                    ProjectiveTransform::identity(),
660                    &mut HitTargets {
661                        list: &mut list,
662                        diagnostics: &self.diagnostics,
663                    },
664                    None,
665                );
666            }
667        }
668        self.hits.borrow()
669    }
670
671    /// The hit targets in draw order, the last drawn at the end.
672    pub fn hit_targets(&self) -> Vec<HitRegion> {
673        self.fresh_hits().hits.clone()
674    }
675
676    /// How much storage the hit targets take.
677    pub fn hit_storage(&self) -> HitStorage {
678        let list = self.hits.borrow();
679        HitStorage {
680            hits_len: list.hits.len(),
681            hits_capacity: list.hits.capacity(),
682            node_index_len: list.node_index.len(),
683            node_index_capacity: list.node_index.capacity(),
684        }
685    }
686
687    /// Applies content and layer-property changes to the retained graph.
688    /// Empty updates or an unavailable scoped update rebuild the scene.
689    /// The hit targets follow the graph when the update changed a hit target
690    /// or the geometry above one: each updated layer compares its hit state,
691    /// and the layers beneath one built again, before and after.
692    pub fn update_from_applier(
693        &mut self,
694        applier: &MemoryApplier,
695        root: NodeId,
696        updates: SceneUpdates<'_>,
697    ) {
698        let report = if updates.is_empty() {
699            None
700        } else {
701            self.graph.as_mut().map(|graph| {
702                crate::scene_builder::update_graph_from_applier_report(applier, graph, updates, 1.0)
703            })
704        };
705        let Some(report) = report.filter(|report| report.applied()) else {
706            self.rebuild_from_applier(applier, root);
707            return;
708        };
709        if let Some(graph) = &self.graph {
710            crate::scene_builder::publish_space(applier, graph);
711        }
712        if report.hit_graph_dirty {
713            self.hits.get_mut().stale = true;
714        }
715        self.layer_moves += u64::from(report.layers_moved);
716    }
717
718    // Adds an interactive target in draw order, ignoring targets that neither
719    // handle a pointer nor name a pointer icon.
720    pub(crate) fn push_hit(
721        &mut self,
722        node_id: NodeId,
723        capture_path: &[NodeId],
724        geometry: HitGeometry<'_>,
725        target: HitTargetSpec<'_>,
726    ) {
727        HitTargets {
728            list: self.hits.get_mut(),
729            diagnostics: &self.diagnostics,
730        }
731        .push(node_id, capture_path, geometry, target);
732    }
733
734    // Removes all hit targets and releases their handlers while retaining
735    // their storage.
736    fn clear_hits(&mut self) {
737        let list = self.hits.get_mut();
738        list.clear();
739        list.stale = false;
740    }
741
742    /// Replaces the retained render graph and leaves the hit targets as they are.
743    pub fn replace_graph(&mut self, graph: RenderGraph) {
744        self.graph = Some(graph);
745    }
746}
747
748impl Default for Scene {
749    fn default() -> Self {
750        Self::new()
751    }
752}
753
754impl RenderScene for Scene {
755    type HitTarget = HitRegion;
756
757    fn clear(&mut self) {
758        self.graph = None;
759        self.clear_hits();
760    }
761
762    fn hit_test(&self, x: f32, y: f32) -> Vec<Self::HitTarget> {
763        let list = self.fresh_hits();
764        let mut hit_indices: Vec<usize> = list
765            .hits
766            .iter()
767            .enumerate()
768            .filter_map(|(index, hit)| hit.contains(x, y).then_some(index))
769            .collect();
770
771        hit_indices.sort_by_key(|&index| Reverse(list.hits[index].z_index));
772        hit_indices
773            .into_iter()
774            .map(|index| list.hits[index].clone())
775            .collect()
776    }
777
778    fn hit_test_near(&self, x: f32, y: f32) -> Option<Self::HitTarget> {
779        self.fresh_hits()
780            .hits
781            .iter()
782            .filter_map(|hit| hit.reach_distance(x, y).map(|distance| (distance, hit)))
783            .min_by(|(near, hit), (other_near, other)| {
784                near.total_cmp(other_near)
785                    .then_with(|| other.z_index.cmp(&hit.z_index))
786            })
787            .map(|(_, hit)| hit.clone())
788    }
789
790    fn find_target(&self, node_id: NodeId) -> Option<Self::HitTarget> {
791        let list = self.fresh_hits();
792        list.node_index
793            .get(&node_id)
794            .and_then(|&index| list.hits.get(index))
795            .cloned()
796    }
797
798    fn hit_test_ray(&self, scene: NodeId, ray: Ray) -> Vec<Self::HitTarget> {
799        let list = self.fresh_hits();
800        let mut hit: Vec<&HitRegion> = list
801            .hits
802            .iter()
803            .filter(|hit| {
804                hit.surface.as_ref().is_some_and(|surface| {
805                    surface.target.pick.scene() == Some(scene)
806                        && surface
807                            .local_point_along(ray)
808                            .is_some_and(|local| hit.contains_local(local))
809                })
810            })
811            .collect();
812        hit.sort_by_key(|hit| Reverse(hit.z_index));
813        hit.into_iter().cloned().collect()
814    }
815
816    fn surface_windows(&self, visit: &mut dyn FnMut(NodeId, ProjectiveTransform)) {
817        let Some(graph) = &self.graph else {
818            return;
819        };
820        let root = ProjectiveTransform::identity();
821        crate::space::for_each_window_eye(&graph.root, root, &mut |_, view, to_window| {
822            for seen in &view.surfaces {
823                if let (Some(node_id), Some(content)) =
824                    (seen.layer.node_id, view.content_to_window(seen, to_window))
825                {
826                    visit(node_id, content);
827                }
828            }
829        });
830    }
831
832    fn surface_window_bounds(&self, node_id: NodeId) -> Option<Rect> {
833        let list = self.fresh_hits();
834        let hit = list.hits.get(*list.node_index.get(&node_id)?)?;
835        let surface = hit.surface.as_ref()?;
836        surface
837            .target
838            .window_bounds(&surface.content_to_local, hit.local_bounds)
839    }
840
841    fn node_window_bounds(&self, visit: &mut dyn FnMut(NodeId, Rect)) {
842        let Some(graph) = &self.graph else {
843            return;
844        };
845        let mut surfaces = HashMap::default();
846        self.surface_windows(&mut |node_id, content| {
847            surfaces.insert(node_id, content);
848        });
849        crate::node_window_bounds::for_each_node_window_bounds(
850            &graph.root,
851            ProjectiveTransform::identity(),
852            &surfaces,
853            visit,
854        );
855    }
856
857    fn layer_moves(&self) -> u64 {
858        self.layer_moves
859    }
860
861    fn collect_retained_visual_observation_nodes(&self, nodes: &mut HashSet<NodeId>) -> bool {
862        if let Some(graph) = &self.graph {
863            graph.collect_retained_visual_observation_nodes(nodes);
864        } else {
865            nodes.clear();
866        }
867        true
868    }
869}
870
871fn point_in_rounded_rect(point: Point, rect: Rect, shape: RoundedCornerShape) -> bool {
872    if !rect.contains(point.x, point.y) {
873        return false;
874    }
875
876    let local_x = point.x - rect.x;
877    let local_y = point.y - rect.y;
878    let radii = shape.resolve(rect.width, rect.height);
879    let tl = radii.top_left;
880    let tr = radii.top_right;
881    let bl = radii.bottom_left;
882    let br = radii.bottom_right;
883
884    if local_x < tl && local_y < tl {
885        let dx = tl - local_x;
886        let dy = tl - local_y;
887        return dx * dx + dy * dy <= tl * tl;
888    }
889
890    if local_x > rect.width - tr && local_y < tr {
891        let dx = local_x - (rect.width - tr);
892        let dy = tr - local_y;
893        return dx * dx + dy * dy <= tr * tr;
894    }
895
896    if local_x < bl && local_y > rect.height - bl {
897        let dx = bl - local_x;
898        let dy = local_y - (rect.height - bl);
899        return dx * dx + dy * dy <= bl * bl;
900    }
901
902    if local_x > rect.width - br && local_y > rect.height - br {
903        let dx = local_x - (rect.width - br);
904        let dy = local_y - (rect.height - br);
905        return dx * dx + dy * dy <= br * br;
906    }
907
908    true
909}
910
911fn point_in_quad(point: Point, quad: [[f32; 2]; 4]) -> bool {
912    point_in_triangle(point, quad[0], quad[1], quad[3])
913        || point_in_triangle(point, quad[0], quad[3], quad[2])
914}
915
916fn point_in_triangle(point: Point, a: [f32; 2], b: [f32; 2], c: [f32; 2]) -> bool {
917    let d1 = triangle_sign(point, a, b);
918    let d2 = triangle_sign(point, b, c);
919    let d3 = triangle_sign(point, c, a);
920    let has_negative = d1 < -f32::EPSILON || d2 < -f32::EPSILON || d3 < -f32::EPSILON;
921    let has_positive = d1 > f32::EPSILON || d2 > f32::EPSILON || d3 > f32::EPSILON;
922    !(has_negative && has_positive)
923}
924
925fn triangle_sign(point: Point, a: [f32; 2], b: [f32; 2]) -> f32 {
926    (point.x - b[0]) * (a[1] - b[1]) - (a[0] - b[0]) * (point.y - b[1])
927}
928
929#[cfg(test)]
930#[path = "tests/graph_scene_tests.rs"]
931mod tests;