From 964f3bf011141cd7c331ab1a91517cc3b4907315 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 16 Feb 2023 10:57:41 +0100 Subject: [PATCH] Add Border widget --- examples/hello_world_widgets.rs | 8 +- src/widgets.rs | 2 + src/widgets/border.rs | 201 ++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/widgets/border.rs diff --git a/examples/hello_world_widgets.rs b/examples/hello_world_widgets.rs index d57bf7b..507feea 100644 --- a/examples/hello_world_widgets.rs +++ b/examples/hello_world_widgets.rs @@ -2,18 +2,20 @@ use std::convert::Infallible; use crossterm::event::Event; use crossterm::style::{ContentStyle, Stylize}; -use toss::widgets::Text; +use toss::widgets::{Border, BorderLook, Text}; use toss::{Styled, Terminal, Widget}; fn widget() -> impl Widget { - Text::new( + Border::new(Text::new( Styled::new("Hello world!", ContentStyle::default().green()) .then_plain("\n") .then( "Press any key to exit", ContentStyle::default().on_dark_blue(), ), - ) + )) + .look(BorderLook::LINE_DOUBLE) + .style(ContentStyle::default().dark_red()) } fn render_frame(term: &mut Terminal) { diff --git a/src/widgets.rs b/src/widgets.rs index 3021f9a..27280a5 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,3 +1,5 @@ +mod border; mod text; +pub use border::*; pub use text::*; diff --git a/src/widgets/border.rs b/src/widgets/border.rs new file mode 100644 index 0000000..ac087b1 --- /dev/null +++ b/src/widgets/border.rs @@ -0,0 +1,201 @@ +use async_trait::async_trait; +use crossterm::style::ContentStyle; + +use crate::{AsyncWidget, Frame, Pos, Size, Widget}; + +#[derive(Debug, Clone, Copy)] +pub struct BorderLook { + pub top_left: &'static str, + pub top_right: &'static str, + pub bottom_left: &'static str, + pub bottom_right: &'static str, + pub top: &'static str, + pub bottom: &'static str, + pub left: &'static str, + pub right: &'static str, +} + +impl BorderLook { + /// ``` + /// +-------+ + /// | Hello | + /// +-------+ + /// ``` + pub const ASCII: Self = Self { + top_left: "+", + top_right: "+", + bottom_left: "+", + bottom_right: "+", + top: "-", + bottom: "-", + left: "|", + right: "|", + }; + + /// ``` + /// ┌───────┐ + /// │ Hello │ + /// └───────┘ + /// ``` + pub const LINE: Self = Self { + top_left: "┌", + top_right: "┐", + bottom_left: "└", + bottom_right: "┘", + top: "─", + bottom: "─", + left: "│", + right: "│", + }; + + /// ``` + /// ┏━━━━━━━┓ + /// ┃ Hello ┃ + /// ┗━━━━━━━┛ + /// ``` + pub const LINE_HEAVY: Self = Self { + top_left: "┏", + top_right: "┓", + bottom_left: "┗", + bottom_right: "┛", + top: "━", + bottom: "━", + left: "┃", + right: "┃", + }; + + /// ``` + /// ╔═══════╗ + /// ║ Hello ║ + /// ╚═══════╝ + /// ``` + pub const LINE_DOUBLE: Self = Self { + top_left: "╔", + top_right: "╗", + bottom_left: "╚", + bottom_right: "╝", + top: "═", + bottom: "═", + left: "║", + right: "║", + }; +} + +impl Default for BorderLook { + fn default() -> Self { + Self::LINE + } +} + +pub struct Border { + inner: I, + look: BorderLook, + style: ContentStyle, +} + +impl Border { + pub fn new(inner: I) -> Self { + Self { + inner, + look: BorderLook::default(), + style: ContentStyle::default(), + } + } + + pub fn look(mut self, look: BorderLook) -> Self { + self.look = look; + self + } + + pub fn style(mut self, style: ContentStyle) -> Self { + self.style = style; + self + } + + fn draw_border(&self, frame: &mut Frame) { + let size = frame.size(); + let right = size.width.saturating_sub(1).into(); + let bottom = size.height.saturating_sub(1).into(); + + for y in 1..bottom { + frame.write(Pos::new(right, y), (self.look.right, self.style)); + frame.write(Pos::new(0, y), (self.look.left, self.style)); + } + + for x in 1..right { + frame.write(Pos::new(x, bottom), (self.look.bottom, self.style)); + frame.write(Pos::new(x, 0), (self.look.top, self.style)); + } + + frame.write( + Pos::new(right, bottom), + (self.look.bottom_right, self.style), + ); + frame.write(Pos::new(0, bottom), (self.look.bottom_left, self.style)); + frame.write(Pos::new(right, 0), (self.look.top_right, self.style)); + frame.write(Pos::new(0, 0), (self.look.top_left, self.style)); + } + + fn push_inner(&self, frame: &mut Frame) { + let mut size = frame.size(); + size.width = size.width.saturating_sub(2); + size.height = size.height.saturating_sub(2); + + frame.push(Pos::new(1, 1), size); + } +} + +impl Widget for Border +where + I: Widget, +{ + fn size( + &self, + frame: &mut Frame, + max_width: Option, + max_height: Option, + ) -> Result { + let max_width = max_width.map(|w| w.saturating_sub(2)); + let max_height = max_height.map(|h| h.saturating_sub(2)); + let size = self.inner.size(frame, max_width, max_height)?; + Ok(size + Size::new(2, 2)) + } + + fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.draw_border(frame); + + self.push_inner(frame); + self.inner.draw(frame)?; + frame.pop(); + + Ok(()) + } +} + +#[async_trait] +impl AsyncWidget for Border +where + I: AsyncWidget + Send + Sync, +{ + async fn size( + &self, + frame: &mut Frame, + max_width: Option, + max_height: Option, + ) -> Result { + let max_width = max_width.map(|w| w.saturating_sub(2)); + let max_height = max_height.map(|h| h.saturating_sub(2)); + let size = self.inner.size(frame, max_width, max_height).await?; + Ok(size + Size::new(2, 2)) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.draw_border(frame); + + self.push_inner(frame); + self.inner.draw(frame).await?; + frame.pop(); + + Ok(()) + } +}