From 54ed495491b0f1170a77824f562bc1f8d8fd954a Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 20 Jul 2022 22:05:07 +0200 Subject: [PATCH] Add HJoin and VJoin widgets --- src/ui/widgets.rs | 1 + src/ui/widgets/join.rs | 134 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/ui/widgets/join.rs diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs index 3e41eb2..c9c1288 100644 --- a/src/ui/widgets.rs +++ b/src/ui/widgets.rs @@ -1,5 +1,6 @@ pub mod background; pub mod empty; +pub mod join; pub mod list; pub mod rules; pub mod text; diff --git a/src/ui/widgets/join.rs b/src/ui/widgets/join.rs new file mode 100644 index 0000000..4b13260 --- /dev/null +++ b/src/ui/widgets/join.rs @@ -0,0 +1,134 @@ +use async_trait::async_trait; +use toss::frame::{Frame, Pos, Size}; + +use super::Widget; + +pub struct Segment { + widget: Box, + expanding: bool, +} + +impl Segment { + pub fn new(widget: W) -> Self { + Self { + widget: Box::new(widget), + expanding: false, + } + } + + pub fn expanding(mut self, active: bool) -> Self { + self.expanding = active; + self + } +} + +fn expand(amounts: &mut [(u16, bool)], total: u16) { + // Weirdly, rustc needs this type annotation while rust-analyzer manages to + // derive the correct type in an inlay hint. + let actual: u16 = amounts.iter().map(|(a, _)| *a).sum(); + if actual < total { + let mut remaining = total - actual; + while remaining > 0 { + for (amount, expanding) in amounts.iter_mut() { + if *expanding { + if remaining > 0 { + *amount += 1; + remaining -= 1; + } else { + break; + } + } + } + } + } +} + +/// Place multiple widgets next to each other horizontally. +pub struct HJoin { + segments: Vec, +} + +impl HJoin { + pub fn new(segments: Vec) -> Self { + Self { segments } + } +} + +#[async_trait] +impl Widget for HJoin { + fn size(&self, frame: &mut Frame, _max_width: Option, max_height: Option) -> Size { + let mut size = Size::ZERO; + for segment in &self.segments { + let widget_size = segment.widget.size(frame, None, max_height); + size.width += widget_size.width; + size.height = size.height.max(widget_size.height); + } + size + } + + async fn render(self: Box, frame: &mut Frame) { + let size = frame.size(); + let mut widths = self + .segments + .iter() + .map(|s| { + let width = s.widget.size(frame, None, Some(size.height)).width; + (width, s.expanding) + }) + .collect::>(); + expand(&mut widths, size.width); + + let mut x = 0; + for (segment, (width, _)) in self.segments.into_iter().zip(widths.into_iter()) { + frame.push(Pos::new(x, 0), Size::new(width, size.height)); + segment.widget.render(frame).await; + frame.pop(); + x += width as i32; + } + } +} + +/// Place multiple widgets next to each other vertically. +pub struct VJoin { + segments: Vec, +} + +impl VJoin { + pub fn new(segments: Vec) -> Self { + Self { segments } + } +} + +#[async_trait] +impl Widget for VJoin { + fn size(&self, frame: &mut Frame, max_width: Option, _max_height: Option) -> Size { + let mut size = Size::ZERO; + for segment in &self.segments { + let widget_size = segment.widget.size(frame, max_width, None); + size.width = size.width.max(widget_size.width); + size.height += widget_size.height; + } + size + } + + async fn render(self: Box, frame: &mut Frame) { + let size = frame.size(); + let mut heights = self + .segments + .iter() + .map(|s| { + let height = s.widget.size(frame, Some(size.width), None).height; + (height, s.expanding) + }) + .collect::>(); + expand(&mut heights, size.height); + + let mut y = 0; + for (segment, (height, _)) in self.segments.into_iter().zip(heights.into_iter()) { + frame.push(Pos::new(0, y), Size::new(size.width, height)); + segment.widget.render(frame).await; + frame.pop(); + y += height as i32; + } + } +}