Skip to main content

agg_gui/widgets/
rebuilder.rs

1//! Rebuilder — regenerate a child subtree when an external version
2//! changes.
3//!
4//! agg-gui trees are built once; widgets whose *content set* is dynamic
5//! (a picker whose options change, a list whose row count changes) can't
6//! express that with setters alone. `Rebuilder` closes the gap: give it
7//! a cheap `version_fn` and a `builder`; each layout pass it compares the
8//! version and rebuilds the child when it moved. The declarative-UI
9//! equivalent of keying a subtree by its data identity.
10//!
11//! ```ignore
12//! let list = Rebuilder::new(
13//!     move || items.borrow().len() as u64,
14//!     move || build_rows(&items.borrow(), &font),
15//! );
16//! ```
17//!
18//! Keep `version_fn` cheap (it runs every layout) and derive it from the
19//! data the builder reads — hash names, count rows, bump a counter.
20
21use crate::draw_ctx::DrawCtx;
22use crate::event::{Event, EventResult};
23use crate::geometry::{Rect, Size};
24use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
25use crate::widget::Widget;
26
27pub struct Rebuilder {
28    bounds: Rect,
29    children: Vec<Box<dyn Widget>>, // exactly 1 once built
30    base: WidgetBase,
31    version_fn: Box<dyn Fn() -> u64>,
32    builder: Box<dyn Fn() -> Box<dyn Widget>>,
33    version: Option<u64>,
34}
35
36impl Rebuilder {
37    pub fn new(
38        version_fn: impl Fn() -> u64 + 'static,
39        builder: impl Fn() -> Box<dyn Widget> + 'static,
40    ) -> Self {
41        Self {
42            bounds: Rect::default(),
43            children: Vec::new(),
44            base: WidgetBase::new(),
45            version_fn: Box::new(version_fn),
46            builder: Box::new(builder),
47            version: None,
48        }
49    }
50
51    pub fn with_margin(mut self, m: Insets) -> Self {
52        self.base.margin = m;
53        self
54    }
55    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
56        self.base.h_anchor = h;
57        self
58    }
59    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
60        self.base.v_anchor = v;
61        self
62    }
63}
64
65impl Widget for Rebuilder {
66    fn type_name(&self) -> &'static str {
67        "Rebuilder"
68    }
69    fn bounds(&self) -> Rect {
70        self.bounds
71    }
72    fn set_bounds(&mut self, b: Rect) {
73        self.bounds = b;
74    }
75    fn children(&self) -> &[Box<dyn Widget>] {
76        &self.children
77    }
78    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
79        &mut self.children
80    }
81
82    fn margin(&self) -> Insets {
83        self.base.margin
84    }
85    fn widget_base(&self) -> Option<&WidgetBase> {
86        Some(&self.base)
87    }
88    fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
89        Some(&mut self.base)
90    }
91    fn h_anchor(&self) -> HAnchor {
92        self.base.h_anchor
93    }
94    fn v_anchor(&self) -> VAnchor {
95        self.base.v_anchor
96    }
97
98    fn layout(&mut self, available: Size) -> Size {
99        let version = (self.version_fn)();
100        if self.version != Some(version) {
101            self.children.clear();
102            self.children.push((self.builder)());
103            self.version = Some(version);
104        }
105        let Some(child) = self.children.first_mut() else {
106            return Size::new(0.0, 0.0);
107        };
108        let s = child.layout(available);
109        child.set_bounds(Rect::new(0.0, 0.0, s.width, s.height));
110        self.bounds = Rect::new(0.0, 0.0, s.width, s.height);
111        s
112    }
113
114    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
115
116    fn on_event(&mut self, _e: &Event) -> EventResult {
117        EventResult::Ignored
118    }
119}