use std::any::{Any, TypeId};
use std::marker::PhantomData;
use std::time::{Duration, Instant};
use ratatui_core::layout::Rect;
use crate::component::{EventResult, Tracked};
use crate::context::ContextMap;
use crate::node::{
AnyCursorHook, AnyDesiredHeightHook, AnyEventHook, CallSite, Effect, EffectKind, Layout,
TypedCursorHook, TypedDesiredHeightHook, TypedEffectHandler, TypedEventHook, WidthConstraint,
};
pub(crate) type ConsumerFn<S> = Box<dyn FnOnce(&ContextMap, &dyn Any, &mut Tracked<S>) + Send>;
pub(crate) struct HooksOutput<S: 'static> {
pub effects: Vec<Effect>,
pub autofocus: bool,
pub focus_scope: bool,
pub provided: Vec<(TypeId, Box<dyn Any + Send + Sync>)>,
pub consumers: Vec<ConsumerFn<S>>,
pub focusable: Option<bool>,
pub cursor_hook: Option<Box<dyn AnyCursorHook>>,
pub event_hook: Option<Box<dyn AnyEventHook>>,
pub capture_hook: Option<Box<dyn AnyEventHook>>,
pub layout: Option<Layout>,
pub width_constraint: Option<WidthConstraint>,
pub height_hint: Option<u16>,
pub desired_height_hook: Option<Box<dyn AnyDesiredHeightHook>>,
}
pub struct Hooks<P: 'static, S: 'static> {
effects: Vec<Effect>,
autofocus: bool,
focus_scope: bool,
provided: Vec<(TypeId, Box<dyn Any + Send + Sync>)>,
consumers: Vec<ConsumerFn<S>>,
focusable: Option<bool>,
cursor_hook: Option<Box<dyn AnyCursorHook>>,
event_hook: Option<Box<dyn AnyEventHook>>,
capture_hook: Option<Box<dyn AnyEventHook>>,
layout: Option<Layout>,
width_constraint: Option<WidthConstraint>,
height_hint: Option<u16>,
desired_height_hook: Option<Box<dyn AnyDesiredHeightHook>>,
_marker: PhantomData<fn() -> P>,
}
const _: () = {
assert!(std::mem::size_of::<Hooks<u8, ()>>() == std::mem::size_of::<Hooks<u64, ()>>());
assert!(std::mem::align_of::<Hooks<u8, ()>>() == std::mem::align_of::<Hooks<u64, ()>>());
};
impl<P: Send + Sync + 'static, S: Send + Sync + 'static> Default for Hooks<P, S> {
fn default() -> Self {
Self::new()
}
}
impl<P: Send + Sync + 'static, S: Send + Sync + 'static> Hooks<P, S> {
pub fn new() -> Self {
Self {
effects: Vec::new(),
autofocus: false,
focus_scope: false,
provided: Vec::new(),
consumers: Vec::new(),
focusable: None,
cursor_hook: None,
event_hook: None,
capture_hook: None,
layout: None,
width_constraint: None,
height_hint: None,
desired_height_hook: None,
_marker: PhantomData,
}
}
#[track_caller]
pub fn use_interval(
&mut self,
interval: Duration,
handler: impl Fn(&P, &mut Tracked<S>) + Send + Sync + 'static,
) {
self.effects.push(Effect {
handler: Box::new(TypedEffectHandler {
handler: Box::new(handler),
}),
kind: EffectKind::Interval {
interval,
last_tick: Instant::now(),
},
call_site: CallSite::from_location(std::panic::Location::caller()),
});
}
#[track_caller]
pub fn use_mount(&mut self, handler: impl Fn(&P, &mut Tracked<S>) + Send + Sync + 'static) {
self.effects.push(Effect {
handler: Box::new(TypedEffectHandler {
handler: Box::new(handler),
}),
kind: EffectKind::OnMount,
call_site: CallSite::from_location(std::panic::Location::caller()),
});
}
#[track_caller]
pub fn use_unmount(&mut self, handler: impl Fn(&P, &mut Tracked<S>) + Send + Sync + 'static) {
self.effects.push(Effect {
handler: Box::new(TypedEffectHandler {
handler: Box::new(handler),
}),
kind: EffectKind::OnUnmount,
call_site: CallSite::from_location(std::panic::Location::caller()),
});
}
pub fn use_autofocus(&mut self) {
self.autofocus = true;
}
pub fn use_focus_scope(&mut self) {
self.focus_scope = true;
}
pub fn provide_context<T: Any + Send + Sync>(&mut self, value: T) {
self.provided.push((TypeId::of::<T>(), Box::new(value)));
}
pub fn use_context<T: Any + Send + Sync + 'static>(
&mut self,
handler: impl FnOnce(Option<&T>, &P, &mut Tracked<S>) + Send + 'static,
) {
let type_id = TypeId::of::<T>();
self.consumers.push(Box::new(
move |context: &ContextMap, component: &dyn Any, tracked: &mut Tracked<S>| {
let props = component
.downcast_ref::<P>()
.expect("props type mismatch in use_context");
let value = context
.get_by_type_id(type_id)
.and_then(|v| v.downcast_ref::<T>());
handler(value, props, tracked);
},
));
}
pub fn use_focusable(&mut self, focusable: bool) {
self.focusable = Some(focusable);
}
pub fn use_cursor(
&mut self,
handler: impl Fn(Rect, &P, &S) -> Option<(u16, u16)> + Send + Sync + 'static,
) {
self.cursor_hook = Some(Box::new(TypedCursorHook {
handler: Box::new(handler),
}));
}
pub fn use_event(
&mut self,
handler: impl Fn(&crossterm::event::Event, &P, &mut Tracked<S>) -> EventResult
+ Send
+ Sync
+ 'static,
) {
self.event_hook = Some(Box::new(TypedEventHook {
handler: Box::new(handler),
}));
}
pub fn use_event_capture(
&mut self,
handler: impl Fn(&crossterm::event::Event, &P, &mut Tracked<S>) -> EventResult
+ Send
+ Sync
+ 'static,
) {
self.capture_hook = Some(Box::new(TypedEventHook {
handler: Box::new(handler),
}));
}
pub fn use_layout(&mut self, layout: Layout) {
self.layout = Some(layout);
}
pub fn use_width_constraint(&mut self, constraint: WidthConstraint) {
self.width_constraint = Some(constraint);
}
pub fn use_height_hint(&mut self, height: u16) {
self.height_hint = Some(height);
}
pub fn use_desired_height(
&mut self,
handler: impl Fn(u16, &P, &S) -> Option<u16> + Send + Sync + 'static,
) {
self.desired_height_hook = Some(Box::new(TypedDesiredHeightHook {
handler: Box::new(handler),
}));
}
pub(crate) fn decompose(self) -> HooksOutput<S> {
HooksOutput {
effects: self.effects,
autofocus: self.autofocus,
focus_scope: self.focus_scope,
provided: self.provided,
consumers: self.consumers,
focusable: self.focusable,
cursor_hook: self.cursor_hook,
event_hook: self.event_hook,
capture_hook: self.capture_hook,
layout: self.layout,
width_constraint: self.width_constraint,
height_hint: self.height_hint,
desired_height_hook: self.desired_height_hook,
}
}
}