Skip to main content

agg_gui/
draw_cell.rs

1//! [`DrawCell`] and [`DrawRefCell`] — interior-mutable UI state that
2//! automatically requests a repaint when it changes.
3//!
4//! # Why this exists
5//!
6//! The most common bug in this framework is mutating paint-affecting state
7//! without calling [`crate::animation::request_draw`], so part of a widget
8//! silently fails to repaint. The event dispatcher already makes a
9//! `Consumed` event schedule a draw (see [`crate::event::EventResult`]), but
10//! state shared between a widget and its owner via `Rc<Cell<T>>` /
11//! `Rc<RefCell<T>>` is often written from a callback (an `on_change` /
12//! `on_click` closure) that runs *outside* the widget's own `on_event`, so
13//! the auto-invalidation doesn't cover it.
14//!
15//! [`DrawCell`] is the recommended replacement for `Cell<T>` in that role:
16//! its [`set`](DrawCell::set) requests a draw **only when the value actually
17//! changes**, so redundant writes stay free and callers no longer have to
18//! remember a manual `request_draw()`.
19//!
20//! # Example
21//!
22//! ```
23//! use std::rc::Rc;
24//! use agg_gui::DrawCell;
25//!
26//! // Shared between a slider's `on_change` and the paint code.
27//! let stroke_width = Rc::new(DrawCell::new(1.0_f64));
28//!
29//! let for_cb = Rc::clone(&stroke_width);
30//! let on_change = move |v: f64| for_cb.set(v); // auto-requests a draw on change
31//!
32//! on_change(2.0);
33//! assert_eq!(stroke_width.get(), 2.0);
34//! ```
35
36use crate::animation::request_draw;
37use std::cell::{Cell, Ref, RefCell, RefMut};
38use std::fmt;
39use std::ops::{Deref, DerefMut};
40
41/// A [`Cell`]-like container for `Copy + PartialEq` UI state that requests a
42/// repaint whenever its value changes.
43///
44/// Drop-in for the `new` / `get` / `set` / `replace` surface of [`Cell`].
45/// Writing the same value again is a no-op that does **not** schedule a draw,
46/// so callers can `set` unconditionally without causing redundant frames.
47pub struct DrawCell<T: Copy + PartialEq> {
48    inner: Cell<T>,
49}
50
51impl<T: Copy + PartialEq> DrawCell<T> {
52    /// Create a cell holding `value`.
53    pub const fn new(value: T) -> Self {
54        Self {
55            inner: Cell::new(value),
56        }
57    }
58
59    /// Read the current value (a copy).
60    pub fn get(&self) -> T {
61        self.inner.get()
62    }
63
64    /// Store `value`. Requests a repaint via
65    /// [`crate::animation::request_draw`] only when `value` differs from the
66    /// current contents, so no-op writes don't schedule a frame.
67    pub fn set(&self, value: T) {
68        if self.inner.get() != value {
69            self.inner.set(value);
70            request_draw();
71        }
72    }
73
74    /// Store `value`, returning the previous contents. Requests a repaint
75    /// only when the value actually changed.
76    pub fn replace(&self, value: T) -> T {
77        let old = self.inner.get();
78        if old != value {
79            self.inner.set(value);
80            request_draw();
81        }
82        old
83    }
84}
85
86impl<T: Copy + PartialEq + Default> Default for DrawCell<T> {
87    fn default() -> Self {
88        Self::new(T::default())
89    }
90}
91
92impl<T: Copy + PartialEq + fmt::Debug> fmt::Debug for DrawCell<T> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_tuple("DrawCell").field(&self.inner.get()).finish()
95    }
96}
97
98impl<T: Copy + PartialEq> Clone for DrawCell<T> {
99    fn clone(&self) -> Self {
100        Self::new(self.inner.get())
101    }
102}
103
104/// A [`RefCell`]-flavoured variant for state that is not `Copy` (strings,
105/// vectors, structs). Reading is unchanged; a mutable borrow requests a
106/// repaint when its guard drops.
107///
108/// # Caveat
109///
110/// Unlike [`DrawCell`], this cannot do change detection — it doesn't know
111/// whether a `&mut` borrow actually modified anything — so **every**
112/// [`borrow_mut`](DrawRefCell::borrow_mut) requests a draw on drop, even if
113/// the caller changed nothing. Prefer [`DrawCell`] for `Copy` values where
114/// redundant frames matter; reach for `DrawRefCell` when the payload can't be
115/// `Copy` and you accept a repaint per mutable borrow.
116pub struct DrawRefCell<T> {
117    inner: RefCell<T>,
118}
119
120impl<T> DrawRefCell<T> {
121    /// Create a cell holding `value`.
122    pub const fn new(value: T) -> Self {
123        Self {
124            inner: RefCell::new(value),
125        }
126    }
127
128    /// Immutably borrow the contents. Does not request a draw.
129    pub fn borrow(&self) -> Ref<'_, T> {
130        self.inner.borrow()
131    }
132
133    /// Mutably borrow the contents. The returned guard requests a repaint
134    /// when it drops (see the type-level caveat about no change detection).
135    pub fn borrow_mut(&self) -> DrawRefMut<'_, T> {
136        DrawRefMut {
137            inner: self.inner.borrow_mut(),
138        }
139    }
140}
141
142impl<T: Default> Default for DrawRefCell<T> {
143    fn default() -> Self {
144        Self::new(T::default())
145    }
146}
147
148impl<T: fmt::Debug> fmt::Debug for DrawRefCell<T> {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        f.debug_tuple("DrawRefCell").field(&self.inner).finish()
151    }
152}
153
154/// Mutable-borrow guard from [`DrawRefCell::borrow_mut`] that requests a
155/// repaint on drop.
156pub struct DrawRefMut<'a, T> {
157    inner: RefMut<'a, T>,
158}
159
160impl<T> Deref for DrawRefMut<'_, T> {
161    type Target = T;
162    fn deref(&self) -> &T {
163        &self.inner
164    }
165}
166
167impl<T> DerefMut for DrawRefMut<'_, T> {
168    fn deref_mut(&mut self) -> &mut T {
169        &mut self.inner
170    }
171}
172
173impl<T> Drop for DrawRefMut<'_, T> {
174    fn drop(&mut self) {
175        request_draw();
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn set_to_new_value_requests_draw() {
185        let cell = DrawCell::new(1_i32);
186        crate::animation::clear_draw_request();
187        cell.set(2);
188        assert_eq!(cell.get(), 2);
189        assert!(
190            crate::animation::wants_draw(),
191            "changing a DrawCell must request a draw"
192        );
193    }
194
195    #[test]
196    fn set_to_same_value_does_not_request_draw() {
197        let cell = DrawCell::new(5_i32);
198        crate::animation::clear_draw_request();
199        cell.set(5);
200        assert_eq!(cell.get(), 5);
201        assert!(
202            !crate::animation::wants_draw(),
203            "a no-op write must NOT request a draw"
204        );
205    }
206
207    #[test]
208    fn replace_returns_old_and_requests_draw_only_on_change() {
209        let cell = DrawCell::new(10_i32);
210
211        crate::animation::clear_draw_request();
212        let old = cell.replace(11);
213        assert_eq!(old, 10);
214        assert_eq!(cell.get(), 11);
215        assert!(crate::animation::wants_draw());
216
217        crate::animation::clear_draw_request();
218        let old = cell.replace(11);
219        assert_eq!(old, 11);
220        assert!(!crate::animation::wants_draw(), "no-op replace stays quiet");
221    }
222
223    #[test]
224    fn draw_ref_cell_requests_draw_on_mut_borrow_drop() {
225        let cell = DrawRefCell::new(String::from("a"));
226
227        // Read borrow: no draw.
228        crate::animation::clear_draw_request();
229        assert_eq!(&*cell.borrow(), "a");
230        assert!(!crate::animation::wants_draw());
231
232        // Mutable borrow: draw requested when the guard drops.
233        crate::animation::clear_draw_request();
234        {
235            let mut g = cell.borrow_mut();
236            g.push('b');
237        }
238        assert_eq!(&*cell.borrow(), "ab");
239        assert!(crate::animation::wants_draw());
240    }
241}