Skip to main content

repose_core/
state.rs

1use std::any::Any;
2use std::cell::{Ref, RefCell, RefMut};
3use std::rc::Rc;
4
5use crate::{Signal, on_unmount, reactive, remember_with_key, request_frame, scoped_effect, signal};
6
7pub struct MutableState<T: Clone + 'static> {
8    inner: Signal<T>,
9    saver: Option<Box<dyn StateSaver<T>>>,
10}pub trait StateSaver<T>: 'static {
11    fn save(&self, value: &T) -> Box<dyn Any>;
12    fn restore(&self, saved: &dyn Any) -> Option<T>;
13}
14
15pub fn remember_derived<T: Clone + 'static>(
16    key: impl Into<String>,
17    producer: impl Fn() -> T + 'static + Clone,
18) -> std::rc::Rc<crate::Signal<T>> {
19    let key: String = key.into();
20    produce_state(format!("derived:{key}"), producer)
21}
22
23// State holder pattern
24pub trait StateHolder: 'static {
25    type State: Clone;
26    type Event;
27
28    fn initial_state() -> Self::State;
29    fn reduce(state: &Self::State, event: Self::Event) -> Self::State;
30}
31
32/// Lazily produces a Signal<T> (remembered by key) and keeps it up to date
33/// by re-running `producer` under the reactive graph whenever its dependencies change.
34///
35/// - Runs an initial compute immediately to establish dependencies.
36pub fn produce_state<T: Clone + 'static>(
37    key: impl Into<String>,
38    producer: impl Fn() -> T + 'static + Clone,
39) -> Rc<Signal<T>> {
40    produce_state_inner(key.into(), producer, |out, v| out.set(v))
41}
42
43/// Like [`produce_state`], but only writes the output signal when the computed
44/// value actually changed (`T: PartialEq`), skipping invalidations/frame
45/// requests when the derived value is unchanged.
46pub fn produce_state_eq<T: Clone + PartialEq + 'static>(
47    key: impl Into<String>,
48    producer: impl Fn() -> T + 'static + Clone,
49) -> Rc<Signal<T>> {
50    produce_state_inner(key.into(), producer, |out, v| out.set_neq(v))
51}
52
53fn produce_state_inner<T: Clone + 'static>(
54    key: String,
55    producer: impl Fn() -> T + 'static + Clone,
56    write: impl Fn(Signal<T>, T) + 'static + Copy,
57) -> Rc<Signal<T>> {
58    remember_with_key(format!("produce:{key}"), || {
59        let out: Signal<T> = signal(producer());
60        let out_clone = out.clone();
61
62        let obs_id = reactive::new_observer({
63            let producer = producer.clone();
64            move || {
65                let v = producer();
66                write(out_clone.clone(), v);
67            }
68        });
69
70        // Establish initial deps and value
71        reactive::run_observer_now(obs_id);
72
73        scoped_effect(move || {
74            on_unmount(move || {
75                reactive::remove_observer(obs_id);
76            })
77        });
78
79        out
80    })
81}
82
83/// Local widget state that drives recomposition on every write.
84///
85/// Unlike [`crate::remember_state`] (a bare `Rc<RefCell<T>>` that never requests
86/// a frame), `Mutable` calls [`request_frame`] on `set`/`update` so async /
87/// timer / layout-callback mutations reliably re-render. Prefer [`Signal`] for
88/// shared/derived state; use `Mutable` for widget-local state that should
89/// always recompose.
90pub struct Mutable<T: 'static>(Rc<RefCell<T>>);
91
92// Manual impl: `#[derive(Clone)]` would require `T: Clone`, but `Rc<RefCell<T>>`
93// is unconditionally cloneable and local widget state must not need `T: Clone`.
94impl<T: 'static> Clone for Mutable<T> {
95    fn clone(&self) -> Self {
96        Self(self.0.clone())
97    }
98}
99
100impl<T: 'static> Mutable<T> {
101    pub fn new(v: T) -> Self {
102        Self(Rc::new(RefCell::new(v)))
103    }
104
105    pub fn get(&self) -> Ref<'_, T> {
106        self.0.borrow()
107    }
108
109    /// Read the current value without holding the borrow across the closure.
110    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
111        f(&*self.0.borrow())
112    }
113
114    pub fn set(&self, v: T) {
115        *self.0.borrow_mut() = v;
116        crate::signal_fired();
117        request_frame();
118    }
119
120    /// Like [`set`], but skips the frame request + signal when the value is
121    /// unchanged (`T: PartialEq`).
122    pub fn set_neq(&self, v: T)
123    where
124        T: PartialEq,
125    {
126        {
127            let mut b = self.0.borrow_mut();
128            if *b == v {
129                return;
130            }
131            *b = v;
132        }
133        crate::signal_fired();
134        request_frame();
135    }
136
137    pub fn update(&self, f: impl FnOnce(&mut T)) {
138        f(&mut *self.0.borrow_mut());
139        crate::signal_fired();
140        request_frame();
141    }
142
143    /// Like [`update`], but only requests a frame + fires the signal when the
144    /// value actually changed (`T: PartialEq + Clone`).
145    pub fn update_neq(&self, f: impl FnOnce(&mut T))
146    where
147        T: PartialEq + Clone,
148    {
149        let changed = {
150            let mut b = self.0.borrow_mut();
151            let before = (*b).clone();
152            f(&mut *b);
153            *b != before
154        };
155        if changed {
156            crate::signal_fired();
157            request_frame();
158        }
159    }
160
161    /// Escape hatch when batching many writes; call [`request_frame`] yourself.
162    pub fn borrow_mut_silent(&self) -> RefMut<'_, T> {
163        self.0.borrow_mut()
164    }
165
166    pub fn as_rc(&self) -> Rc<RefCell<T>> {
167        self.0.clone()
168    }
169}
170
171/// Remember a [`Mutable`] in the current composition slot.
172#[track_caller]
173pub fn remember_mutable<T: 'static>(init: impl FnOnce() -> T) -> Mutable<T> {
174    crate::remember(|| Mutable::new(init())).as_ref().clone()
175}
176
177/// Key-based variant of [`remember_mutable`]; stable across conditional branches.
178#[track_caller]
179pub fn remember_mutable_with_key<T: 'static>(
180    key: impl Into<String>,
181    init: impl FnOnce() -> T,
182) -> Mutable<T> {
183    remember_with_key(key, || Mutable::new(init())).as_ref().clone()
184}
185
186/// Remember a reducer-backed local state. Returns a [`Mutable`] snapshot reader
187/// plus a dispatch closure that runs `H::reduce` and writes the result back.
188///
189/// Prefer this for multi-field widget state over many loose `Mutable`s; it keeps
190/// the state shape and all mutations in one place.
191#[track_caller]
192pub fn remember_reducer<H: StateHolder>() -> (Mutable<H::State>, impl Fn(H::Event) + Clone)
193where
194    H::State: 'static,
195    H::Event: 'static,
196{
197    let state = remember_mutable(|| H::initial_state());
198    let dispatch = {
199        let state = state.clone();
200        move |ev: H::Event| {
201            state.update(|s| *s = H::reduce(s, ev));
202        }
203    };
204    (state, dispatch)
205}
206
207/// Key-based variant of [`remember_reducer`]; stable across conditional branches.
208#[track_caller]
209pub fn remember_reducer_with_key<H: StateHolder>(
210    key: impl Into<String>,
211) -> (Mutable<H::State>, impl Fn(H::Event) + Clone)
212where
213    H::State: 'static,
214    H::Event: 'static,
215{
216    let state = remember_mutable_with_key(key, || H::initial_state());
217    let dispatch = {
218        let state = state.clone();
219        move |ev: H::Event| {
220            state.update(|s| *s = H::reduce(s, ev));
221        }
222    };
223    (state, dispatch)
224}