use std::future::Future;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen_futures::spawn_local;
use crate::event::{EventCast, Listener};
use crate::internal::{In, Out};
use crate::stateful::{Inner, ShouldRender};
use crate::View;
#[repr(transparent)]
pub struct Hook<S> {
inner: Inner<S>,
}
#[repr(transparent)]
pub struct Signal<S> {
pub(super) weak: Weak<Inner<S>>,
}
impl<S> Signal<S> {
pub fn update<F, O>(&self, mutator: F)
where
F: FnOnce(&mut S) -> O,
O: ShouldRender,
{
if let Some(inner) = self.weak.upgrade() {
if inner.state.with(mutator).should_render() {
inner.update()
}
}
}
pub fn update_silent<F>(&self, mutator: F)
where
F: FnOnce(&mut S),
{
if let Some(inner) = self.weak.upgrade() {
inner.state.with(mutator);
}
}
pub fn set(&self, val: S) {
self.update(move |s| *s = val);
}
}
impl<S> Clone for Signal<S> {
fn clone(&self) -> Self {
Signal {
weak: self.weak.clone(),
}
}
}
impl<S> Hook<S> {
pub(super) fn new(inner: &Inner<S>) -> &Self {
unsafe { &*(inner as *const _ as *const Hook<S>) }
}
pub fn bind<E, F, O>(&self, callback: F) -> Bound<S, F>
where
S: 'static,
E: EventCast,
F: Fn(&mut S, E) -> O + 'static,
O: ShouldRender,
{
let inner = &self.inner;
Bound { inner, callback }
}
pub fn bind_async<E, F, T>(&self, callback: F) -> impl Listener<E>
where
S: 'static,
E: EventCast,
F: Fn(Signal<S>, E) -> T + 'static,
T: Future<Output = ()> + 'static,
{
let inner = &self.inner as *const Inner<S>;
move |e| {
let rc = ManuallyDrop::new(unsafe { Rc::from_raw(inner) });
let signal = Signal {
weak: Rc::downgrade(&*rc),
};
spawn_local(callback(signal, e));
}
}
pub fn get(&self) -> S
where
S: Copy,
{
**self
}
}
pub struct Bound<'b, S, F> {
inner: &'b Inner<S>,
callback: F,
}
impl<S, F> Bound<'_, S, F> {
pub fn into_listener<E, O>(self) -> impl Listener<E>
where
S: 'static,
E: EventCast,
F: Fn(&mut S, E) -> O + 'static,
O: ShouldRender,
{
let Bound { inner, callback } = self;
let inner = inner as *const Inner<S>;
let bound = move |e| {
let inner = unsafe { &*inner };
let state = unsafe { inner.state.mut_unchecked() };
if callback(state, e).should_render() {
inner.update();
}
};
BoundListener {
bound,
_unbound: PhantomData::<F>,
}
}
}
impl<S, F> Clone for Bound<'_, S, F>
where
F: Clone,
{
fn clone(&self) -> Self {
Bound {
inner: self.inner,
callback: self.callback.clone(),
}
}
}
impl<S, F> Copy for Bound<'_, S, F> where F: Copy {}
struct BoundListener<B, U> {
bound: B,
_unbound: PhantomData<U>,
}
impl<B, U, E> Listener<E> for BoundListener<B, U>
where
B: Listener<E>,
E: EventCast,
Self: 'static,
{
type Product = B::Product;
fn build(self, p: In<Self::Product>) -> Out<Self::Product> {
self.bound.build(p)
}
fn update(self, p: &mut Self::Product) {
if std::mem::size_of::<U>() != 0 {
self.bound.update(p);
}
}
}
impl<S> Deref for Hook<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
unsafe { self.inner.state.ref_unchecked() }
}
}
impl<'a, V> View for &'a Hook<V>
where
&'a V: View + 'a,
{
type Product = <&'a V as View>::Product;
fn build(self, p: In<Self::Product>) -> Out<Self::Product> {
(**self).build(p)
}
fn update(self, p: &mut Self::Product) {
(**self).update(p)
}
}
#[cfg(test)]
mod test {
use std::cell::UnsafeCell;
use wasm_bindgen::JsCast;
use crate::stateful::cell::WithCell;
use crate::stateful::product::ProductHandler;
use crate::value::TextProduct;
use super::*;
#[test]
fn bound_callback_is_copy() {
let inner = Inner {
state: WithCell::new(0_i32),
prod: UnsafeCell::new(ProductHandler::mock(
|_, _| {},
TextProduct {
memo: 0,
node: wasm_bindgen::JsValue::UNDEFINED.unchecked_into(),
},
)),
};
let mock = Bound {
inner: &inner,
callback: |state: &mut i32, _: web_sys::Event| {
*state += 1;
},
};
drop([mock, mock]);
}
}