use std::marker::PhantomData;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use crate::core::{MessageContext, MessageResult, Mut, NoElement, View, ViewMarker};
use crate::{OptionalAction, ViewCtx};
pub struct Interval<Callback, State, Action> {
ms: u32,
callback: Callback,
phantom: PhantomData<fn() -> (State, Action)>,
}
pub fn interval<State, Action, OA, Callback>(
ms: u32,
callback: Callback,
) -> Interval<Callback, State, Action>
where
State: 'static,
Action: 'static,
OA: OptionalAction<Action> + 'static,
Callback: Fn(&mut State) -> OA + 'static,
{
Interval {
ms,
callback,
phantom: PhantomData,
}
}
#[expect(
unnameable_types,
reason = "Implementation detail, public because of trait visibility rules"
)]
pub struct IntervalState {
interval_fn: Closure<dyn FnMut()>,
interval_handle: i32,
}
fn start_interval(callback: &Closure<dyn FnMut()>, ms: u32) -> i32 {
web_sys::window()
.unwrap_throw()
.set_interval_with_callback_and_timeout_and_arguments_0(
callback.as_ref().unchecked_ref(),
ms.try_into().expect_throw(
"`setInterval` requires this to be an `i32`,\
which is why values above `2147483647` are not possible,\
see https://developer.mozilla.org/en-US/docs/Web/API/setInterval#sect2 \
for more details",
),
)
.unwrap_throw()
}
fn clear_interval(handle: i32) {
web_sys::window()
.unwrap_throw()
.clear_interval_with_handle(handle);
}
impl<Callback, State, Action> ViewMarker for Interval<Callback, State, Action> {}
impl<State, Action, Callback, OA> View<State, Action, ViewCtx> for Interval<Callback, State, Action>
where
State: 'static,
Action: 'static,
OA: OptionalAction<Action> + 'static,
Callback: Fn(&mut State) -> OA + 'static,
{
type Element = NoElement;
type ViewState = IntervalState;
fn build(&self, ctx: &mut ViewCtx, _: &mut State) -> (Self::Element, Self::ViewState) {
let thunk = ctx.message_thunk();
let interval_fn = Closure::new(move || thunk.push_message(()));
let state = IntervalState {
interval_handle: start_interval(&interval_fn, self.ms),
interval_fn,
};
(NoElement, state)
}
fn rebuild(
&self,
prev: &Self,
view_state: &mut Self::ViewState,
_: &mut ViewCtx,
(): Mut<'_, Self::Element>,
_: &mut State,
) {
if prev.ms != self.ms {
clear_interval(view_state.interval_handle);
view_state.interval_handle = start_interval(&view_state.interval_fn, self.ms);
}
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
_: &mut ViewCtx,
_: Mut<'_, Self::Element>,
) {
clear_interval(view_state.interval_handle);
}
fn message(
&self,
_: &mut Self::ViewState,
message: &mut MessageContext,
_element: Mut<'_, Self::Element>,
app_state: &mut State,
) -> MessageResult<Action> {
debug_assert!(message.remaining_path().is_empty());
message.take_message::<()>().unwrap_throw();
match (self.callback)(app_state).action() {
Some(action) => MessageResult::Action(action),
None => MessageResult::Nop,
}
}
}