use crate::components::metadata::Retain;
use crate::{AnyView, Environment, Metadata, View};
use alloc::boxed::Box;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::marker::PhantomData;
use nami::watcher::Context;
use nami::{Signal, watcher::Metadata as WatcherMetadata};
#[derive(Clone)]
pub struct Dynamic(DynamicHandler);
raw_view!(Dynamic);
#[derive(Clone)]
pub struct DynamicHandler(Rc<RefCell<DynamicHandlerState>>);
enum DynamicHandlerState {
Connected {
receiver: Receiver,
pending_view_slot: Option<Rc<RefCell<Option<AnyView>>>>,
},
Unconnected(Option<AnyView>),
}
type Receiver = Box<dyn Fn(Context<AnyView>)>;
#[derive(Clone, Copy, Debug)]
pub struct DynamicInitialContent;
impl_debug!(Dynamic);
impl_debug!(DynamicHandler);
impl DynamicHandler {
pub fn set_with_metadata(&self, view: impl View, metadata: WatcherMetadata) {
let mut state = self.0.borrow_mut();
let view = AnyView::new(view);
match &mut *state {
DynamicHandlerState::Connected { receiver, .. } => {
receiver(Context::new(view, metadata));
}
DynamicHandlerState::Unconnected(temp_view) => {
*temp_view = Some(view);
}
}
}
pub fn set(&self, view: impl View) {
self.set_with_metadata(view, WatcherMetadata::new());
}
}
impl Dynamic {
#[must_use]
pub fn new() -> (DynamicHandler, Self) {
let handler = DynamicHandler(Rc::new(RefCell::new(DynamicHandlerState::Unconnected(
None,
))));
(handler.clone(), Self(handler))
}
pub fn watch<T, S, V: View>(value: S, f: impl 'static + Fn(T) -> V) -> impl View
where
S: Signal<Output = T> + 'static,
T: 'static,
{
WatchedDynamic {
value,
f,
marker: PhantomData,
}
}
pub fn connect(self, receiver: impl Fn(Context<AnyView>) + 'static) {
self.connect_internal(None, receiver);
}
pub fn connect_with_pending_view(
self,
pending_view_slot: Rc<RefCell<Option<AnyView>>>,
receiver: impl Fn(Context<AnyView>) + 'static,
) {
self.connect_internal(Some(pending_view_slot), receiver);
}
fn connect_internal(
self,
pending_view_slot: Option<Rc<RefCell<Option<AnyView>>>>,
receiver: impl Fn(Context<AnyView>) + 'static,
) {
let mut state = self.0.0.borrow_mut();
match &mut *state {
DynamicHandlerState::Unconnected(temp_view) => {
if let Some(view) = temp_view.take() {
receiver(Context::new(view, WatcherMetadata::new()));
}
*state = DynamicHandlerState::Connected {
receiver: Box::new(receiver),
pending_view_slot,
};
}
DynamicHandlerState::Connected { .. } => unreachable!("Dynamic already connected"),
}
}
#[must_use]
pub fn identity(&self) -> usize {
Rc::as_ptr(&self.0.0) as usize
}
pub fn with_unconnected_view<R>(&self, f: impl FnOnce(Option<&AnyView>) -> R) -> Option<R> {
let state = self.0.0.borrow();
match &*state {
DynamicHandlerState::Unconnected(view) => Some(f(view.as_ref())),
DynamicHandlerState::Connected { .. } => None,
}
}
pub fn with_unconnected_view_mut<R>(
&self,
f: impl FnOnce(&mut Option<AnyView>) -> R,
) -> Option<R> {
let mut state = self.0.0.borrow_mut();
match &mut *state {
DynamicHandlerState::Unconnected(view) => Some(f(view)),
DynamicHandlerState::Connected { .. } => None,
}
}
pub fn with_measurement_view_mut<R>(
&self,
f: impl FnOnce(&mut Option<AnyView>) -> R,
) -> Option<R> {
let mut state = self.0.0.borrow_mut();
match &mut *state {
DynamicHandlerState::Unconnected(view) => Some(f(view)),
DynamicHandlerState::Connected {
pending_view_slot: Some(slot),
..
} => Some(f(&mut slot.borrow_mut())),
DynamicHandlerState::Connected {
pending_view_slot: None,
..
} => None,
}
}
pub fn with_connected_pending_view_mut<R>(
&self,
f: impl FnOnce(&mut Option<AnyView>) -> R,
) -> Option<R> {
let mut state = self.0.0.borrow_mut();
match &mut *state {
DynamicHandlerState::Connected {
pending_view_slot: Some(slot),
..
} => Some(f(&mut slot.borrow_mut())),
DynamicHandlerState::Connected {
pending_view_slot: None,
..
}
| DynamicHandlerState::Unconnected(_) => None,
}
}
}
struct WatchedDynamic<T, S, F> {
value: S,
f: F,
marker: PhantomData<fn(T)>,
}
impl<T, S, F, V> View for WatchedDynamic<T, S, F>
where
T: 'static,
S: Signal<Output = T> + 'static,
F: Fn(T) -> V + 'static,
V: View + 'static,
{
fn body(self, _env: &Environment) -> impl View {
let (handle, dynamic) = Dynamic::new();
let f = Rc::new(self.f);
handle.set_with_metadata(
f(self.value.get()),
WatcherMetadata::new().with(DynamicInitialContent),
);
let guard = self.value.watch({
let f = Rc::clone(&f);
move |value| handle.set(f(value.into_value()))
});
Metadata::new(dynamic, Retain::new((guard, self.value)))
}
}
pub fn watch<T: 'static, S, V: View>(value: S, f: impl Fn(T) -> V + 'static) -> impl View
where
S: Signal<Output = T> + 'static,
{
Dynamic::watch(value, f)
}