Skip to main content

rosace_widgets/tree/
repaint_boundary.rs

1use std::sync::Mutex;
2
3use rosace_render::Picture;
4use rosace_state::Atom;
5use super::{Widget, Children, PaintCtx};
6
7/// Caches an expensive subtree's Picture and replays it without re-running
8/// the child's `paint()` — for large mostly-static content (chart backdrops,
9/// icon grids). Records once and holds by default; pass `.repaint_when(atom)`
10/// to re-record whenever that atom changes.
11///
12/// Interactive regions declared inside are recorded at real screen
13/// coordinates and persist across replay frames (D091), so clicks still work.
14pub struct RepaintBoundary<W: Widget + Send + Sync + 'static> {
15    pub child: W,
16    repaint_when: Vec<Atom<u64>>,
17    cache: Mutex<Option<(rosace_core::types::Rect, Picture, Vec<u64>)>>,
18}
19
20impl<W: Widget + Send + Sync + 'static> RepaintBoundary<W> {
21    pub fn new(child: W) -> Self {
22        Self { child, repaint_when: Vec::new(), cache: Mutex::new(None) }
23    }
24
25    /// Re-record whenever `atom` changes. Chain multiple.
26    pub fn repaint_when(mut self, atom: Atom<u64>) -> Self {
27        self.repaint_when.push(atom);
28        self
29    }
30}
31
32impl<W: Widget + Send + Sync + 'static> Widget for RepaintBoundary<W> {
33    fn children(&self) -> Children<'_> { Children::One(&self.child) }
34
35    fn paint(&self, ctx: &mut PaintCtx) {
36        let rect = ctx.rect;
37        let keys: Vec<u64> = self.repaint_when.iter().map(|a| a.get()).collect();
38
39        let stale = {
40            let cache = self.cache.lock().unwrap();
41            match &*cache {
42                Some((r, _, k)) => *r != rect || *k != keys,
43                None => true,
44            }
45        };
46
47        if stale {
48            // Record at the real screen rect so hit regions land correctly.
49            let child = &self.child;
50            let pic = ctx.capture(rect, |cctx| child.paint(cctx));
51            *self.cache.lock().unwrap() = Some((rect, pic, keys));
52        } else {
53            // Preserve the captured sub-node (and its hit regions) this frame.
54            ctx.keep_child_slot();
55        }
56
57        let cache = self.cache.lock().unwrap();
58        if let Some((_, pic, _)) = &*cache {
59            ctx.replay_offset(pic, 0.0, 0.0);
60        }
61    }
62    // layout, flex_factor: protocol defaults delegate to the child.
63}