diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index 8b7b9ca..a1d3865 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -1,3 +1,4 @@ mod blocks; mod cursor; mod renderer; +mod widgets; diff --git a/src/ui/chat2/widgets.rs b/src/ui/chat2/widgets.rs new file mode 100644 index 0000000..0241232 --- /dev/null +++ b/src/ui/chat2/widgets.rs @@ -0,0 +1,129 @@ +use std::convert::Infallible; + +use async_trait::async_trait; +use crossterm::style::Stylize; +use time::format_description::FormatItem; +use time::macros::format_description; +use time::OffsetDateTime; +use toss::widgets::{BoxedAsync, Empty, Text}; +use toss::{AsyncWidget, Frame, Pos, Size, Style, WidgetExt, WidthDb}; + +use crate::util::InfallibleExt; + +pub const INDENT_STR: &str = "│ "; +pub const INDENT_WIDTH: usize = 2; + +pub struct Indent { + level: usize, + style: Style, +} + +impl Indent { + pub fn new(level: usize, style: Style) -> Self { + Self { level, style } + } +} + +#[async_trait] +impl AsyncWidget for Indent { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Result { + let width = (INDENT_WIDTH * self.level).try_into().unwrap_or(u16::MAX); + Ok(Size::new(width, 0)) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + let size = frame.size(); + let indent_string = INDENT_STR.repeat(self.level); + + for y in 0..size.height { + frame.write(Pos::new(0, y.into()), (&indent_string, self.style)) + } + + Ok(()) + } +} + +const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); +const TIME_WIDTH: u16 = 16; + +pub struct Time(BoxedAsync<'static, Infallible>); + +impl Time { + pub fn new(time: Option, style: Style) -> Self { + let widget = if let Some(time) = time { + let text = time.format(TIME_FORMAT).expect("could not format time"); + Text::new((text, style)) + .background() + .with_style(style) + .boxed_async() + } else { + Empty::new() + .with_width(TIME_WIDTH) + .background() + .with_style(style) + .boxed_async() + }; + Self(widget) + } +} + +#[async_trait] +impl AsyncWidget for Time { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Result { + Ok(self + .0 + .size(widthdb, max_width, max_height) + .await + .infallible()) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).await.infallible(); + Ok(()) + } +} + +pub struct Seen(BoxedAsync<'static, Infallible>); + +impl Seen { + pub fn new(seen: bool) -> Self { + let widget = if seen { + Empty::new().with_width(1).boxed_async() + } else { + let style = Style::new().black().on_green(); + Text::new("*").background().with_style(style).boxed_async() + }; + Self(widget) + } +} + +#[async_trait] +impl AsyncWidget for Seen { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Result { + Ok(self + .0 + .size(widthdb, max_width, max_height) + .await + .infallible()) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).await.infallible(); + Ok(()) + } +}