From 18bce9f0a04ba867a6435f5276a8c4865aec3ac0 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Mon, 27 Dec 2021 04:37:21 -0500 Subject: [PATCH] Simple blocks --- src/app.rs | 2 +- src/tuine/component/base/block.rs | 138 ++++++++++++++++-- src/tuine/component/base/mod.rs | 2 +- src/tuine/component/base/text_table/mod.rs | 3 +- .../component/base/text_table/sort_type.rs | 13 ++ src/tuine/component/mod.rs | 3 + src/tuine/component/stateful.rs | 3 +- src/tuine/component/widget/temp_table.rs | 10 +- src/tuine/element.rs | 2 +- 9 files changed, 157 insertions(+), 19 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0d8dcddf..ed3cca8f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -246,7 +246,7 @@ impl Application for AppState { Flex::column() .with_flex_child( Flex::row_with_children(vec![ - FlexElement::new(TempTable::new(ctx)), + FlexElement::new(TempTable::build(ctx)), FlexElement::new(TextTable::build( ctx, TextTableProps::new(vec!["D", "E", "F"]), diff --git a/src/tuine/component/base/block.rs b/src/tuine/component/base/block.rs index fd94cec1..ff3f25a3 100644 --- a/src/tuine/component/base/block.rs +++ b/src/tuine/component/base/block.rs @@ -1,23 +1,143 @@ -use tui::{backend::Backend, Frame}; +use std::{borrow::Cow, marker::PhantomData}; -use crate::tuine::{DrawContext, Event, StateContext, Status, TmpComponent}; +use tui::{backend::Backend, layout::Rect, style::Style, widgets::Borders, Frame}; -pub struct Block {} +use crate::tuine::{ + Bounds, DrawContext, Event, LayoutNode, Size, StateContext, Status, TmpComponent, +}; -impl TmpComponent for Block { +/// A set of styles for a [`Block`]. +#[derive(Clone, Debug, Default)] +pub struct StyleSheet { + text: Style, + border: Style, +} + +/// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional +/// titles. +pub struct Block +where + Child: TmpComponent, +{ + _pd: PhantomData, + child: Option, + borders: Borders, + style_sheet: StyleSheet, + left_text: Option>, + right_text: Option>, +} + +impl Block +where + Child: TmpComponent, +{ + pub fn with_child(child: Child) -> Self { + Self { + _pd: Default::default(), + child: Some(child), + borders: Borders::all(), + style_sheet: Default::default(), + left_text: None, + right_text: None, + } + } + + pub fn child(mut self, child: Option) -> Self { + self.child = child; + self + } + + fn inner_rect(&self, original: Rect) -> Rect { + let mut inner = original; + + if self.borders.intersects(Borders::LEFT) { + inner.x = inner.x.saturating_add(1).min(inner.right()); + inner.width = inner.width.saturating_sub(1); + } + if self.borders.intersects(Borders::TOP) + || self.left_text.is_some() + || self.right_text.is_some() + { + inner.y = inner.y.saturating_add(1).min(inner.bottom()); + inner.height = inner.height.saturating_sub(1); + } + if self.borders.intersects(Borders::RIGHT) { + inner.width = inner.width.saturating_sub(1); + } + if self.borders.intersects(Borders::BOTTOM) { + inner.height = inner.height.saturating_sub(1); + } + inner + } +} + +impl TmpComponent for Block +where + Child: TmpComponent, +{ fn draw( - &mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: &DrawContext<'_>, - _frame: &mut Frame<'_, B>, + &mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, + frame: &mut Frame<'_, B>, ) where B: Backend, { - todo!() + let rect = draw_ctx.global_rect(); + + frame.render_widget( + tui::widgets::Block::default() + .borders(self.borders) + .border_style(self.style_sheet.border), + rect, + ); + + if let Some(child) = &mut self.child { + if let Some(child_draw_ctx) = draw_ctx.children().next() { + child.draw(state_ctx, &child_draw_ctx, frame) + } + } } fn on_event( - &mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: &DrawContext<'_>, _event: Event, - _messages: &mut Vec, + &mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, event: Event, + messages: &mut Vec, ) -> Status { + if let Some(child_draw_ctx) = draw_ctx.children().next() { + if let Some(child) = &mut self.child { + return child.on_event(state_ctx, &child_draw_ctx, event, messages); + } + } + Status::Ignored } + + fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> crate::tuine::Size { + if let Some(child) = &self.child { + // Reduce bounds based on borders + let inner_rect = self.inner_rect(Rect::new(0, 0, bounds.max_width, bounds.max_height)); + let child_bounds = Bounds { + min_width: bounds.min_width, + min_height: bounds.min_height, + max_width: inner_rect.width, + max_height: inner_rect.height, + }; + + let mut child_node = LayoutNode::default(); + let child_size = child.layout(child_bounds, &mut child_node); + + child_node.rect = Rect::new( + inner_rect.x, + inner_rect.y, + child_size.width, + child_size.height, + ); + node.children = vec![child_node]; + + child_size + } else { + Size { + width: 0, + height: 0, + } + } + } } diff --git a/src/tuine/component/base/mod.rs b/src/tuine/component/base/mod.rs index 505395dc..2306514d 100644 --- a/src/tuine/component/base/mod.rs +++ b/src/tuine/component/base/mod.rs @@ -1,5 +1,5 @@ pub mod text_table; -pub use text_table::*; +pub use text_table::{DataCell, DataRow, SortType, TextColumn, TextTable, TextTableProps}; pub mod shortcut; pub use shortcut::Shortcut; diff --git a/src/tuine/component/base/text_table/mod.rs b/src/tuine/component/base/text_table/mod.rs index 3542334f..8b470864 100644 --- a/src/tuine/component/base/text_table/mod.rs +++ b/src/tuine/component/base/text_table/mod.rs @@ -32,6 +32,7 @@ use crate::{ tuine::{DrawContext, Event, Key, StateContext, StatefulComponent, Status, TmpComponent}, }; +/// A set of styles for a [`TextTable`]. #[derive(Clone, Debug, Default)] pub struct StyleSheet { text: Style, @@ -114,7 +115,6 @@ impl StatefulComponent for TextTable { type ComponentState = TextTableState; - #[track_caller] fn build(ctx: &mut crate::tuine::ViewContext<'_>, mut props: Self::Properties) -> Self { let sort = props.sort; let (key, state) = ctx.register_and_mut_state_with_default::<_, Self::ComponentState, _>( @@ -126,6 +126,7 @@ impl StatefulComponent for TextTable { ); state.scroll.set_num_items(props.rows.len()); + state.sort.prune_length(props.columns.len()); props.try_sort_data(state.sort); TextTable { diff --git a/src/tuine/component/base/text_table/sort_type.rs b/src/tuine/component/base/text_table/sort_type.rs index ed5fc355..abc9045b 100644 --- a/src/tuine/component/base/text_table/sort_type.rs +++ b/src/tuine/component/base/text_table/sort_type.rs @@ -5,6 +5,19 @@ pub enum SortType { Descending(usize), } +impl SortType { + pub(crate) fn prune_length(&mut self, num_columns: usize) { + match self { + SortType::Unsortable => {} + SortType::Ascending(column) | SortType::Descending(column) => { + if *column >= num_columns { + *column = num_columns - 1; + } + } + } + } +} + impl Default for SortType { fn default() -> Self { Self::Unsortable diff --git a/src/tuine/component/mod.rs b/src/tuine/component/mod.rs index 8d637635..23dbdac7 100644 --- a/src/tuine/component/mod.rs +++ b/src/tuine/component/mod.rs @@ -7,6 +7,9 @@ pub use widget::*; pub mod stateful; pub use stateful::*; +// pub mod stateless; +// pub use stateless::*; + use enum_dispatch::enum_dispatch; use tui::Frame; diff --git a/src/tuine/component/stateful.rs b/src/tuine/component/stateful.rs index 47efe627..650753f7 100644 --- a/src/tuine/component/stateful.rs +++ b/src/tuine/component/stateful.rs @@ -5,10 +5,11 @@ use super::TmpComponent; /// A [`StatefulComponent`] is a builder-style pattern for building a stateful /// [`Component`]. /// -/// Inspired by Flutter's StatefulWidget interface. +/// Inspired by Flutter's [StatefulWidget class](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html). pub trait StatefulComponent: TmpComponent { type Properties; type ComponentState: State; + #[track_caller] fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self; } diff --git a/src/tuine/component/widget/temp_table.rs b/src/tuine/component/widget/temp_table.rs index 61a2bc3c..fb702a66 100644 --- a/src/tuine/component/widget/temp_table.rs +++ b/src/tuine/component/widget/temp_table.rs @@ -1,18 +1,18 @@ use crate::tuine::{ text_table::{DataRow, SortType, TextTableProps}, - Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext, + Block, Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext, }; /// A [`TempTable`] is a text table that is meant to display temperature data. pub struct TempTable { - inner: Shortcut>, + inner: Block>>, } impl TempTable { #[track_caller] - pub fn new(ctx: &mut ViewContext<'_>) -> Self { + pub fn build(ctx: &mut ViewContext<'_>) -> Self { Self { - inner: Shortcut::with_child(TextTable::build( + inner: Block::with_child(Shortcut::with_child(TextTable::build( ctx, TextTableProps::new(vec!["Sensor", "Temp"]) .rows(vec![ @@ -21,7 +21,7 @@ impl TempTable { DataRow::default().cell("C").cell(1), ]) .default_sort(SortType::Ascending(1)), - )), + ))), } } } diff --git a/src/tuine/element.rs b/src/tuine/element.rs index fa8a9075..2e2b5a9f 100644 --- a/src/tuine/element.rs +++ b/src/tuine/element.rs @@ -12,7 +12,7 @@ pub enum Element where C: TmpComponent, { - Block, + Block(Block), Carousel, Container(Container), Flex(Flex),