use crate::view::interactivity::{ClickEvent, EventContext};
use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
pub struct State<T> {
inner: Rc<RefCell<T>>,
key: &'static str,
}
impl<T> Clone for State<T> {
fn clone(&self) -> Self {
Self { inner: Rc::clone(&self.inner), key: self.key }
}
}
impl<T> State<T> {
pub fn key(&self) -> &'static str {
self.key
}
pub fn read<R>(&self, read: impl FnOnce(&T) -> R) -> R {
read(&self.inner.borrow())
}
pub fn write(&self, write: impl FnOnce(&mut T)) {
write(&mut self.inner.borrow_mut());
}
pub fn with_mut<R>(&self, body: impl FnOnce(&mut T) -> R) -> R {
body(&mut self.inner.borrow_mut())
}
pub fn update(&self, change: impl Fn(&mut T) + 'static) -> impl Fn(&ClickEvent, &mut EventContext) + 'static
where
T: 'static,
{
let state = self.clone();
move |_, cx| {
state.write(|value| change(value));
cx.notify();
}
}
}
impl<T: Clone> State<T> {
pub fn get(&self) -> T {
self.inner.borrow().clone()
}
pub fn set(&self, value: T) -> impl Fn(&ClickEvent, &mut EventContext) + 'static
where
T: 'static,
{
let state = self.clone();
move |_, cx| {
state.write(|current| *current = value.clone());
cx.notify();
}
}
}
impl State<bool> {
pub fn toggle(&self) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
let state = self.clone();
move |_, cx| {
state.write(|value| *value = !*value);
cx.notify();
}
}
}
#[derive(Default)]
pub(crate) struct StateStore {
values: HashMap<&'static str, Rc<dyn Any>>,
}
impl StateStore {
pub fn get<T: 'static>(&self, key: &'static str) -> Option<State<T>> {
let existing = self.values.get(key)?;
let Some(cell) = existing.clone().downcast::<RefCell<T>>().ok() else {
panic!("el estado `{key}` se pidió como `{}` pero ya existía como otro tipo", std::any::type_name::<T>());
};
Some(State { inner: cell, key })
}
pub fn insert<T: 'static>(&mut self, key: &'static str, value: T) -> State<T> {
let cell = Rc::new(RefCell::new(value));
self.values.insert(key, cell.clone());
State { inner: cell, key }
}
pub fn get_or_insert<T: 'static>(&mut self, key: &'static str, default: T) -> State<T> {
self.get_or_insert_with(key, || default)
}
pub fn get_or_insert_with<T: 'static>(&mut self, key: &'static str, default: impl FnOnce() -> T) -> State<T> {
if let Some(existing) = self.values.get(key) {
let Some(cell) = existing.clone().downcast::<RefCell<T>>().ok() else {
panic!(
"el estado `{key}` se pidió como `{}` pero ya existía como otro tipo",
std::any::type_name::<T>()
);
};
return State { inner: cell, key };
}
let cell = Rc::new(RefCell::new(default()));
self.values.insert(key, cell.clone());
State { inner: cell, key }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn el_estado_se_conserva_y_se_lee() {
let mut store = StateStore::default();
let count = store.get_or_insert("clics", 0u32);
count.write(|value| *value += 1);
assert_eq!(count.get(), 1);
let same = store.get_or_insert("clics", 99u32);
assert_eq!(same.get(), 1, "la clave devuelve el mismo valor, no el default");
}
#[test]
#[should_panic(expected = "ya existía como otro tipo")]
fn una_clave_con_otro_tipo_avisa_claro() {
let mut store = StateStore::default();
let _ = store.get_or_insert("dato", 1u32);
let _ = store.get_or_insert("dato", String::new());
}
}