use crate::{
component::{Component, ComponentHelper, ComponentHelperExt},
props::AnyProps,
render, terminal_render_loop, Canvas, Terminal,
};
use any_key::AnyHash;
use crossterm::{terminal, tty::IsTty};
use std::{
fmt::Debug,
future::Future,
hash::Hash,
io::{self, stderr, stdout, Write},
os::fd::AsRawFd,
rc::Rc,
};
#[doc(hidden)]
pub trait ExtendWithElements<T>: Sized {
fn extend<E: Extend<T>>(self, dest: &mut E);
}
impl<'a, T, U> ExtendWithElements<T> for Element<'a, U>
where
U: ElementType + 'a,
T: From<Element<'a, U>>,
{
fn extend<E: Extend<T>>(self, dest: &mut E) {
dest.extend([self.into()]);
}
}
impl<'a> ExtendWithElements<AnyElement<'a>> for AnyElement<'a> {
fn extend<E: Extend<AnyElement<'a>>>(self, dest: &mut E) {
dest.extend([self]);
}
}
impl<T, U, I> ExtendWithElements<T> for I
where
I: IntoIterator<Item = U>,
U: Into<T>,
{
fn extend<E: Extend<T>>(self, dest: &mut E) {
dest.extend(self.into_iter().map(|e| e.into()));
}
}
#[doc(hidden)]
pub fn extend_with_elements<T, U, E>(dest: &mut T, elements: U)
where
T: Extend<E>,
U: ExtendWithElements<E>,
{
elements.extend(dest);
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub struct ElementKey(Rc<Box<dyn AnyHash>>);
impl ElementKey {
pub fn new<K: Debug + Hash + Eq + 'static>(key: K) -> Self {
Self(Rc::new(Box::new(key)))
}
}
#[derive(Clone)]
pub struct Element<'a, T: ElementType + 'a> {
pub key: ElementKey,
pub props: T::Props<'a>,
}
pub trait ElementType {
type Props<'a>
where
Self: 'a;
}
pub struct AnyElement<'a> {
key: ElementKey,
props: AnyProps<'a>,
helper: Box<dyn ComponentHelperExt>,
}
impl<'a, T> Element<'a, T>
where
T: Component + 'a,
{
pub fn into_any(self) -> AnyElement<'a> {
self.into()
}
}
impl<'a, T> From<Element<'a, T>> for AnyElement<'a>
where
T: Component + 'a,
{
fn from(e: Element<'a, T>) -> Self {
Self {
key: e.key,
props: AnyProps::owned(e.props),
helper: ComponentHelper::<T>::boxed(),
}
}
}
impl<'a, 'b: 'a, T> From<&'a mut Element<'b, T>> for AnyElement<'a>
where
T: Component,
{
fn from(e: &'a mut Element<'b, T>) -> Self {
Self {
key: e.key.clone(),
props: AnyProps::borrowed(&mut e.props),
helper: ComponentHelper::<T>::boxed(),
}
}
}
mod private {
use super::*;
pub trait Sealed {}
impl<'a> Sealed for AnyElement<'a> {}
impl<'a> Sealed for &mut AnyElement<'a> {}
impl<'a, T> Sealed for Element<'a, T> where T: Component {}
impl<'a, T> Sealed for &mut Element<'a, T> where T: Component {}
}
pub trait ElementExt: private::Sealed + Sized {
fn key(&self) -> &ElementKey;
#[doc(hidden)]
fn props_mut(&mut self) -> AnyProps;
#[doc(hidden)]
fn helper(&self) -> Box<dyn ComponentHelperExt>;
fn render(&mut self, max_width: Option<usize>) -> Canvas;
fn to_string(&mut self) -> String {
self.render(None).to_string()
}
fn print(&mut self) {
self.write_to_raw_fd(stdout()).unwrap();
}
fn eprint(&mut self) {
self.write_to_raw_fd(stderr()).unwrap();
}
fn write<W: Write>(&mut self, w: W) -> io::Result<()> {
let canvas = self.render(None);
canvas.write(w)
}
fn write_to_raw_fd<F: Write + AsRawFd>(&mut self, fd: F) -> io::Result<()> {
if fd.is_tty() {
let (width, _) = terminal::size().expect("we should be able to get the terminal size");
let canvas = self.render(Some(width as _));
canvas.write_ansi(fd)
} else {
self.write(fd)
}
}
fn render_loop(&mut self) -> impl Future<Output = io::Result<()>>;
fn fullscreen(&mut self) -> impl Future<Output = io::Result<()>>;
}
impl<'a> ElementExt for AnyElement<'a> {
fn key(&self) -> &ElementKey {
&self.key
}
fn props_mut(&mut self) -> AnyProps {
self.props.borrow()
}
#[doc(hidden)]
fn helper(&self) -> Box<dyn ComponentHelperExt> {
self.helper.copy()
}
fn render(&mut self, max_width: Option<usize>) -> Canvas {
render(self, max_width)
}
async fn render_loop(&mut self) -> io::Result<()> {
terminal_render_loop(self, Terminal::new()?).await
}
async fn fullscreen(&mut self) -> io::Result<()> {
terminal_render_loop(self, Terminal::fullscreen()?).await
}
}
impl<'a> ElementExt for &mut AnyElement<'a> {
fn key(&self) -> &ElementKey {
&self.key
}
fn props_mut(&mut self) -> AnyProps {
self.props.borrow()
}
#[doc(hidden)]
fn helper(&self) -> Box<dyn ComponentHelperExt> {
self.helper.copy()
}
fn render(&mut self, max_width: Option<usize>) -> Canvas {
render(&mut **self, max_width)
}
async fn render_loop(&mut self) -> io::Result<()> {
terminal_render_loop(&mut **self, Terminal::new()?).await
}
async fn fullscreen(&mut self) -> io::Result<()> {
terminal_render_loop(&mut **self, Terminal::fullscreen()?).await
}
}
impl<'a, T> ElementExt for Element<'a, T>
where
T: Component + 'static,
{
fn key(&self) -> &ElementKey {
&self.key
}
fn props_mut(&mut self) -> AnyProps {
AnyProps::borrowed(&mut self.props)
}
#[doc(hidden)]
fn helper(&self) -> Box<dyn ComponentHelperExt> {
ComponentHelper::<T>::boxed()
}
fn render(&mut self, max_width: Option<usize>) -> Canvas {
render(self, max_width)
}
async fn render_loop(&mut self) -> io::Result<()> {
terminal_render_loop(self, Terminal::new()?).await
}
async fn fullscreen(&mut self) -> io::Result<()> {
terminal_render_loop(self, Terminal::fullscreen()?).await
}
}
impl<'a, T> ElementExt for &mut Element<'a, T>
where
T: Component + 'static,
{
fn key(&self) -> &ElementKey {
&self.key
}
fn props_mut(&mut self) -> AnyProps {
AnyProps::borrowed(&mut self.props)
}
#[doc(hidden)]
fn helper(&self) -> Box<dyn ComponentHelperExt> {
ComponentHelper::<T>::boxed()
}
fn render(&mut self, max_width: Option<usize>) -> Canvas {
render(&mut **self, max_width)
}
async fn render_loop(&mut self) -> io::Result<()> {
terminal_render_loop(&mut **self, Terminal::new()?).await
}
async fn fullscreen(&mut self) -> io::Result<()> {
terminal_render_loop(&mut **self, Terminal::fullscreen()?).await
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
#[test]
fn test_element() {
let mut box_element = element!(Box);
box_element.key();
box_element.print();
box_element.eprint();
(&mut box_element).key();
(&mut box_element).print();
(&mut box_element).eprint();
let mut any_element: AnyElement<'static> = box_element.into_any();
any_element.key();
any_element.print();
any_element.eprint();
(&mut any_element).key();
(&mut any_element).print();
(&mut any_element).eprint();
let mut box_element = element!(Box);
let mut any_element_ref: AnyElement = (&mut box_element).into();
any_element_ref.print();
any_element_ref.eprint();
}
}