From 575faf9bbf61f2c95206081ed614794049eb11b5 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 16 Feb 2023 15:12:27 +0100 Subject: [PATCH] Add Float widget --- examples/hello_world_widgets.rs | 25 +++---- src/widget.rs | 10 ++- src/widgets.rs | 2 + src/widgets/float.rs | 112 ++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 src/widgets/float.rs diff --git a/examples/hello_world_widgets.rs b/examples/hello_world_widgets.rs index 99393ec..3bf1d24 100644 --- a/examples/hello_world_widgets.rs +++ b/examples/hello_world_widgets.rs @@ -6,17 +6,20 @@ use toss::widgets::{BorderLook, Text}; use toss::{Styled, Terminal, Widget, WidgetExt}; fn widget() -> impl Widget { - Text::new( - Styled::new("Hello world!", ContentStyle::default().green()) - .then_plain("\n") - .then( - "Press any key to exit", - ContentStyle::default().on_dark_blue(), - ), - ) - .border() - .look(BorderLook::LINE_DOUBLE) - .style(ContentStyle::default().dark_red()) + let styled = Styled::new("Hello world!", ContentStyle::default().green()) + .then_plain("\n") + .then( + "Press any key to exit", + ContentStyle::default().on_dark_blue(), + ); + Text::new(styled) + .padding() + .horizontal(1) + .border() + .look(BorderLook::LINE_DOUBLE) + .style(ContentStyle::default().dark_red()) + .float() + .all(0.5) } fn render_frame(term: &mut Terminal) { diff --git a/src/widget.rs b/src/widget.rs index 4ac3710..b4df21b 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; -use crate::widgets::{Border, Padding}; +use crate::widgets::{Border, Float, Padding}; use crate::{Frame, Size}; // TODO Feature-gate these traits @@ -33,6 +33,10 @@ pub trait WidgetExt: Sized { Border::new(self) } + fn float(self) -> Float { + Float::new(self) + } + fn padding(self) -> Padding { Padding::new(self) } @@ -45,6 +49,10 @@ pub trait AsyncWidgetExt: Sized { Border::new(self) } + fn float(self) -> Float { + Float::new(self) + } + fn padding(self) -> Padding { Padding::new(self) } diff --git a/src/widgets.rs b/src/widgets.rs index 7a938af..c1021ec 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,7 +1,9 @@ mod border; +mod float; mod padding; mod text; pub use border::*; +pub use float::*; pub use padding::*; pub use text::*; diff --git a/src/widgets/float.rs b/src/widgets/float.rs new file mode 100644 index 0000000..c19aa27 --- /dev/null +++ b/src/widgets/float.rs @@ -0,0 +1,112 @@ +use async_trait::async_trait; + +use crate::{AsyncWidget, Frame, Pos, Size, Widget}; + +pub struct Float { + inner: I, + horizontal: Option, + vertical: Option, +} + +impl Float { + pub fn new(inner: I) -> Self { + Self { + inner, + horizontal: None, + vertical: None, + } + } + + pub fn horizontal(mut self, position: f32) -> Self { + self.horizontal = Some(position); + self + } + + pub fn vertical(mut self, position: f32) -> Self { + self.vertical = Some(position); + self + } + + pub fn all(self, position: f32) -> Self { + self.horizontal(position).vertical(position) + } + + fn push_inner(&self, frame: &mut Frame, size: Size, mut inner_size: Size) { + inner_size.width = inner_size.width.min(size.width); + inner_size.height = inner_size.height.min(size.height); + + let mut inner_pos = Pos::ZERO; + + if let Some(horizontal) = self.horizontal { + let available = (size.width - inner_size.width) as f32; + // Biased towards the left if horizontal lands exactly on the + // boundary between two cells + inner_pos.x = (horizontal * available).floor().min(available) as i32; + } + + if let Some(vertical) = self.vertical { + let available = (size.height - inner_size.height) as f32; + // Biased towards the top if vertical lands exactly on the boundary + // between two cells + inner_pos.y = (vertical * available).floor().min(available) as i32; + } + + frame.push(inner_pos, inner_size); + } +} + +impl Widget for Float +where + I: Widget, +{ + fn size( + &self, + frame: &mut Frame, + max_width: Option, + max_height: Option, + ) -> Result { + self.inner.size(frame, max_width, max_height) + } + + fn draw(self, frame: &mut Frame) -> Result<(), E> { + let size = frame.size(); + let inner_size = self + .inner + .size(frame, Some(size.width), Some(size.height))?; + + self.push_inner(frame, size, inner_size); + self.inner.draw(frame)?; + frame.pop(); + + Ok(()) + } +} + +#[async_trait] +impl AsyncWidget for Float +where + I: AsyncWidget + Send + Sync, +{ + async fn size( + &self, + frame: &mut Frame, + max_width: Option, + max_height: Option, + ) -> Result { + self.inner.size(frame, max_width, max_height).await + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + let size = frame.size(); + let inner_size = self + .inner + .size(frame, Some(size.width), Some(size.height)) + .await?; + + self.push_inner(frame, size, inner_size); + self.inner.draw(frame).await?; + frame.pop(); + + Ok(()) + } +}