pub mod bindings;
pub mod layout;
pub(crate) mod template;
pub use self::layout::{ComponentExt, Layout};
use std::{
any::{self, TypeId},
fmt,
marker::PhantomData,
rc::Rc,
};
use self::{
bindings::{Bindings, NamedBindingQuery},
template::{ComponentId, DynamicMessage},
};
use crate::{
app::{ComponentMessage, MessageSender},
terminal::{Key, Rect},
};
pub trait Component: Sized + 'static {
type Message: Send + 'static;
type Properties;
fn create(properties: Self::Properties, frame: Rect, link: ComponentLink<Self>) -> Self;
fn view(&self) -> Layout;
fn change(&mut self, _properties: Self::Properties) -> ShouldRender {
ShouldRender::No
}
fn resize(&mut self, _frame: Rect) -> ShouldRender {
ShouldRender::No
}
fn update(&mut self, _message: Self::Message) -> ShouldRender {
ShouldRender::No
}
fn bindings(&self, _bindings: &mut Bindings<Self>) {}
fn notify_binding_queries(&self, _queries: &[Option<NamedBindingQuery>], _keys: &[Key]) {}
fn tick(&self) -> Option<Self::Message> {
None
}
}
pub struct Callback<InputT, OutputT = ()>(pub Rc<dyn Fn(InputT) -> OutputT>);
impl<InputT, OutputT> Clone for Callback<InputT, OutputT> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<InputT, OutputT> fmt::Debug for Callback<InputT, OutputT> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"Callback({} -> {} @ {:?})",
any::type_name::<InputT>(),
any::type_name::<OutputT>(),
Rc::as_ptr(&self.0)
)
}
}
impl<InputT, OutputT> PartialEq for Callback<InputT, OutputT> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(
self.0.as_ref() as *const _ as *const (),
other.0.as_ref() as *const _ as *const (),
)
}
}
impl<InputT, OutputT> Callback<InputT, OutputT> {
pub fn emit(&self, value: InputT) -> OutputT {
(self.0)(value)
}
}
impl<InputT, OutputT, FnT> From<FnT> for Callback<InputT, OutputT>
where
FnT: Fn(InputT) -> OutputT + 'static,
{
fn from(function: FnT) -> Self {
Self(Rc::new(function))
}
}
#[derive(Debug)]
pub struct ComponentLink<ComponentT> {
sender: Box<dyn MessageSender>,
component_id: ComponentId,
_component: PhantomData<fn() -> ComponentT>,
}
impl<ComponentT: Component> ComponentLink<ComponentT> {
pub fn send(&self, message: ComponentT::Message) {
self.sender.send(ComponentMessage(LinkMessage::Component(
self.component_id,
DynamicMessage(Box::new(message)),
)));
}
pub fn callback<InputT>(
&self,
callback: impl Fn(InputT) -> ComponentT::Message + 'static,
) -> Callback<InputT> {
let link = self.clone();
Callback(Rc::new(move |input| link.send(callback(input))))
}
pub fn exit(&self) {
self.sender.send(ComponentMessage(LinkMessage::Exit));
}
pub(crate) fn new(sender: Box<dyn MessageSender>, component_id: ComponentId) -> Self {
assert_eq!(TypeId::of::<ComponentT>(), component_id.type_id());
Self {
sender,
component_id,
_component: PhantomData,
}
}
}
impl<ComponentT> Clone for ComponentLink<ComponentT> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone_box(),
component_id: self.component_id,
_component: PhantomData,
}
}
}
impl<ComponentT> PartialEq for ComponentLink<ComponentT> {
fn eq(&self, other: &Self) -> bool {
self.component_id == other.component_id
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ShouldRender {
Yes,
No,
}
impl From<ShouldRender> for bool {
fn from(should_render: ShouldRender) -> Self {
matches!(should_render, ShouldRender::Yes)
}
}
impl From<bool> for ShouldRender {
fn from(should_render: bool) -> Self {
if should_render {
ShouldRender::Yes
} else {
ShouldRender::No
}
}
}
impl std::ops::BitOr for ShouldRender {
type Output = Self;
fn bitor(self, other: Self) -> Self {
((self == ShouldRender::Yes) || (other == ShouldRender::Yes)).into()
}
}
impl std::ops::BitAnd for ShouldRender {
type Output = Self;
fn bitand(self, other: Self) -> Self {
((self == ShouldRender::Yes) && (other == ShouldRender::Yes)).into()
}
}
pub(crate) enum LinkMessage {
Component(ComponentId, DynamicMessage),
Exit,
}
impl std::fmt::Debug for LinkMessage {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "LinkMessage::")?;
match self {
Self::Component(id, message) => write!(
formatter,
"Component({:?}, DynamicMessage(...) @ {:?})",
id, &*message.0 as *const _
),
Self::Exit => write!(formatter, "Exit"),
}
}
}