pub mod container;
pub mod input;
pub mod list;
pub mod text;
pub mod utils;
pub mod window;
use std::any::Any;
use tokio::sync::mpsc::UnboundedSender;
use termion::event::Key;
use ratatui::prelude::*;
pub type BoxedView<S, M> = Box<dyn View<State = S, Message = M>>;
pub type UpdateCallback<S> = fn(&S) -> ViewProps;
pub type EventCallback<M> = fn(Key, Option<&ViewState>, Option<&ViewProps>) -> Option<M>;
pub type RenderCallback<M> = fn(Option<&ViewProps>, &RenderProps) -> Option<M>;
pub struct ViewProps {
inner: Box<dyn Any>,
}
impl ViewProps {
pub fn inner<T>(self) -> Option<T>
where
T: Default + Clone + 'static,
{
self.inner.downcast::<T>().ok().map(|inner| *inner)
}
pub fn inner_ref<T>(&self) -> Option<&T>
where
T: Default + Clone + 'static,
{
self.inner.downcast_ref::<T>()
}
}
impl From<Box<dyn Any>> for ViewProps {
fn from(props: Box<dyn Any>) -> Self {
ViewProps { inner: props }
}
}
impl From<&'static dyn Any> for ViewProps {
fn from(inner: &'static dyn Any) -> Self {
Self {
inner: Box::new(inner),
}
}
}
pub enum ViewState {
USize(usize),
String(String),
}
impl ViewState {
pub fn unwrap_usize(&self) -> Option<usize> {
match self {
ViewState::USize(value) => Some(*value),
_ => None,
}
}
pub fn unwrap_string(&self) -> Option<String> {
match self {
ViewState::String(value) => Some(value.clone()),
_ => None,
}
}
}
#[derive(Clone, Default)]
pub struct RenderProps {
pub area: Rect,
pub layout: Layout,
pub focus: bool,
}
impl RenderProps {
pub fn focus(mut self, focus: bool) -> Self {
self.focus = focus;
self
}
pub fn layout(mut self, layout: Layout) -> Self {
self.layout = layout;
self
}
}
impl From<Rect> for RenderProps {
fn from(area: Rect) -> Self {
Self {
area,
layout: Layout::default(),
focus: false,
}
}
}
pub trait View {
type State;
type Message;
fn view_state(&self) -> Option<ViewState> {
None
}
fn reset(&mut self) {}
fn handle_event(&mut self, _props: Option<&ViewProps>, _key: Key) -> Option<Self::Message> {
None
}
fn update(&mut self, _props: Option<&ViewProps>, _state: &Self::State) {}
fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame);
}
pub struct Widget<S, M> {
view: BoxedView<S, M>,
props: Option<ViewProps>,
sender: UnboundedSender<M>,
on_update: Option<UpdateCallback<S>>,
on_event: Option<EventCallback<M>>,
on_render: Option<RenderCallback<M>>,
}
impl<S: 'static, M: 'static> Widget<S, M> {
pub fn new<V>(view: V, sender: UnboundedSender<M>) -> Self
where
Self: Sized,
V: View<State = S, Message = M> + 'static,
{
Self {
view: Box::new(view),
props: None,
sender: sender.clone(),
on_update: None,
on_event: None,
on_render: None,
}
}
pub fn reset(&mut self) {
self.view.reset()
}
pub fn handle_event(&mut self, key: Key) {
if let Some(message) = self.view.handle_event(self.props.as_ref(), key) {
let _ = self.sender.send(message);
}
if let Some(on_event) = self.on_event {
if let Some(message) =
(on_event)(key, self.view.view_state().as_ref(), self.props.as_ref())
{
let _ = self.sender.send(message);
}
}
}
pub fn update(&mut self, state: &S) {
self.props = self.on_update.map(|on_update| (on_update)(state));
self.view.update(self.props.as_ref(), state);
}
pub fn render(&self, render: RenderProps, frame: &mut Frame) {
self.view.render(self.props.as_ref(), render.clone(), frame);
if let Some(on_render) = self.on_render {
(on_render)(self.props.as_ref(), &render)
.and_then(|message| self.sender.send(message).ok());
}
}
pub fn on_event(mut self, callback: EventCallback<M>) -> Self
where
Self: Sized,
{
self.on_event = Some(callback);
self
}
pub fn on_update(mut self, callback: UpdateCallback<S>) -> Self
where
Self: Sized,
{
self.on_update = Some(callback);
self
}
pub fn on_render(mut self, callback: RenderCallback<M>) -> Self
where
Self: Sized,
{
self.on_render = Some(callback);
self
}
pub fn send(&self, message: M) {
let _ = self.sender.send(message);
}
}
pub trait ToWidget<S, M> {
fn to_widget(self, tx: UnboundedSender<M>) -> Widget<S, M>
where
Self: Sized + 'static;
}
impl<T, S, M> ToWidget<S, M> for T
where
T: View<State = S, Message = M>,
S: 'static,
M: 'static,
{
fn to_widget(self, tx: UnboundedSender<M>) -> Widget<S, M>
where
Self: Sized + 'static,
{
Widget::new(self, tx)
}
}