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}