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