use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
use anymap2::AnyMap;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use crate::any_state::AnyState;
use crate::root_state::BounceRootState;
use crate::utils::{notify_listeners, Listener, ListenerVec};
pub use bounce_macros::Slice;
#[doc(hidden)]
pub trait Slice: PartialEq + Default {
type Action;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self>;
#[allow(unused_variables)]
fn apply(self: Rc<Self>, notion: Rc<dyn Any>) -> Rc<Self> {
self
}
fn notion_ids(&self) -> Vec<TypeId>;
fn changed(self: Rc<Self>) {}
fn create(init_states: &mut AnyMap) -> Self
where
Self: 'static + Sized,
{
init_states.remove().unwrap_or_default()
}
}
pub trait CloneSlice: Slice + Clone {
#[inline]
fn clone_slice(&self) -> Self {
self.clone()
}
}
impl<T> CloneSlice for T where T: Slice + Clone {}
#[derive(Debug, Default)]
pub(crate) struct SliceState<T>
where
T: Slice,
{
value: Rc<RefCell<Rc<T>>>,
listeners: Rc<RefCell<ListenerVec<T>>>,
}
impl<T> Clone for SliceState<T>
where
T: Slice,
{
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
listeners: self.listeners.clone(),
}
}
}
impl<T> SliceState<T>
where
T: Slice + 'static,
{
pub fn dispatch(&self, action: T::Action) {
let maybe_next_val = {
let mut value = self.value.borrow_mut();
let prev_val: Rc<T> = value.clone();
let next_val = prev_val.clone().reduce(action);
let should_notify = prev_val != next_val;
*value = next_val.clone();
should_notify.then_some(next_val)
};
if let Some(next_val) = maybe_next_val {
self.notify_listeners(next_val);
}
}
pub fn notify_listeners(&self, val: Rc<T>) {
val.clone().changed();
notify_listeners(self.listeners.clone(), val);
}
pub fn listen(&self, callback: Rc<Callback<Rc<T>>>) -> Listener {
let mut callbacks_ref = self.listeners.borrow_mut();
callbacks_ref.push(Rc::downgrade(&callback));
Listener::new(callback)
}
pub fn get(&self) -> Rc<T> {
let value = self.value.borrow();
value.clone()
}
}
impl<T> AnyState for SliceState<T>
where
T: Slice + 'static,
{
fn apply(&self, notion: Rc<dyn Any>) {
let maybe_next_val = {
let mut value = self.value.borrow_mut();
let prev_val: Rc<T> = value.clone();
let next_val = prev_val.clone().apply(notion);
let should_notify = prev_val != next_val;
*value = next_val.clone();
should_notify.then_some(next_val)
};
if let Some(next_val) = maybe_next_val {
self.notify_listeners(next_val);
}
}
fn notion_ids(&self) -> Vec<TypeId> {
self.value.borrow().notion_ids()
}
fn create(init_states: &mut AnyMap) -> Self
where
Self: Sized,
{
Self {
value: Rc::new(RefCell::new(T::create(init_states).into())),
listeners: Rc::default(),
}
}
}
pub struct UseSliceHandle<T>
where
T: Slice,
{
inner: Rc<T>,
root: BounceRootState,
}
impl<T> UseSliceHandle<T>
where
T: Slice + 'static,
{
pub fn dispatch(&self, action: T::Action) {
self.root.get_state::<SliceState<T>>().dispatch(action);
}
}
impl<T> Deref for UseSliceHandle<T>
where
T: Slice,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> Clone for UseSliceHandle<T>
where
T: Slice,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
root: self.root.clone(),
}
}
}
impl<T> fmt::Debug for UseSliceHandle<T>
where
T: Slice + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UseSliceHandle")
.field("inner", &self.inner)
.finish()
}
}
#[hook]
pub fn use_slice<T>() -> UseSliceHandle<T>
where
T: Slice + 'static,
{
let root = use_context::<BounceRootState>().expect_throw("No bounce root found.");
let val = {
let root = root.clone();
use_state_eq(move || root.get_state::<SliceState<T>>().get())
};
{
let val = val.clone();
let root = root.clone();
use_memo(root, move |root| {
let state = root.get_state::<SliceState<T>>();
val.set(state.get());
state.listen(Rc::new(Callback::from(move |m| {
val.set(m);
})))
});
}
let val = (*val).clone();
UseSliceHandle { inner: val, root }
}
#[hook]
pub fn use_slice_dispatch<T>() -> Rc<dyn Fn(T::Action)>
where
T: Slice + 'static,
{
let root = use_context::<BounceRootState>().expect_throw("No bounce root found.");
Rc::new(move |action: T::Action| {
root.get_state::<SliceState<T>>().dispatch(action);
})
}
#[hook]
pub fn use_slice_value<T>() -> Rc<T>
where
T: Slice + 'static,
{
use_slice::<T>().inner
}