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