diff options
author | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-12 15:30:42 -0500 |
---|---|---|
committer | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-12 20:29:30 -0500 |
commit | b6b0493333bc8a2154aedab9ec52c59e9a9393cc (patch) | |
tree | 46d33059bd26c84777835504124cb7c963eb9d6d | |
parent | 65a1ee5202193c123c8a11c7826489e5fab4efc6 (diff) |
Enum dispatch time
-rw-r--r-- | src/app.rs | 11 | ||||
-rw-r--r-- | src/tuice/application.rs | 4 | ||||
-rw-r--r-- | src/tuice/component.rs | 13 | ||||
-rw-r--r-- | src/tuice/component/base/block.rs | 12 | ||||
-rw-r--r-- | src/tuice/component/base/carousel.rs | 12 | ||||
-rw-r--r-- | src/tuice/component/base/column.rs | 12 | ||||
-rw-r--r-- | src/tuice/component/base/container.rs | 108 | ||||
-rw-r--r-- | src/tuice/component/base/flex.rs | 60 | ||||
-rw-r--r-- | src/tuice/component/base/mod.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/row.rs | 53 | ||||
-rw-r--r-- | src/tuice/component/base/shortcut.rs | 12 | ||||
-rw-r--r-- | src/tuice/component/base/sized_box.rs | 59 | ||||
-rw-r--r-- | src/tuice/component/base/text_table.rs | 22 | ||||
-rw-r--r-- | src/tuice/component/widget/mod.rs | 1 | ||||
-rw-r--r-- | src/tuice/draw_context.rs | 1 | ||||
-rw-r--r-- | src/tuice/element.rs | 19 | ||||
-rw-r--r-- | src/tuice/layout/bounds.rs | 12 | ||||
-rw-r--r-- | src/tuice/layout/build_layout.rs | 11 | ||||
-rw-r--r-- | src/tuice/mod.rs | 4 | ||||
-rw-r--r-- | src/tuice/runtime.rs | 2 | ||||
-rw-r--r-- | src/units/data_units.rs | 1 |
21 files changed, 245 insertions, 190 deletions
@@ -29,7 +29,7 @@ use frozen_state::FrozenState; use crate::{ canvas::Painter, constants, - tuice::{Application, Row}, + tuice::{Application, Element, Row}, units::data_units::DataUnit, Pid, }; @@ -234,12 +234,11 @@ impl Application for AppState { self.terminator.load(SeqCst) } - fn view( - &mut self, - ) -> Box<dyn crate::tuice::Component<Self::Message, crate::tuice::CrosstermBackend>> { - Box::new(Row::with_children(vec![crate::tuice::TextTable::new( + fn view(&mut self) -> Element<'static, Self::Message> { + use crate::tuice::TextTable; + Element::from(Row::with_children(vec![Element::from(TextTable::new( vec!["A", "B", "C"], - )])) + ))])) } fn destroy(&mut self) { diff --git a/src/tuice/application.rs b/src/tuice/application.rs index b32ff3f6..6fd2e34b 100644 --- a/src/tuice/application.rs +++ b/src/tuice/application.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, sync::mpsc::Receiver}; use super::{ runtime::{self, RuntimeEvent}, - Component, Event, + Element, Event, }; /// An alias to the [`tui::backend::CrosstermBackend`] writing to [`std::io::Stdout`]. @@ -19,7 +19,7 @@ pub trait Application: Sized { /// always returning false. fn is_terminated(&self) -> bool; - fn view(&mut self) -> Box<dyn Component<Self::Message, CrosstermBackend>>; + fn view(&mut self) -> Element<'static, Self::Message>; /// To run upon stopping the application. fn destroy(&mut self) {} diff --git a/src/tuice/component.rs b/src/tuice/component.rs index f4646ac5..131f576c 100644 --- a/src/tuice/component.rs +++ b/src/tuice/component.rs @@ -4,18 +4,19 @@ pub use base::*; pub mod widget; pub use widget::*; +use enum_dispatch::enum_dispatch; use tui::{layout::Rect, Frame}; -use super::{Bounds, DrawContext, Event, LayoutNode, Size, Status}; +use super::{Bounds, Event, LayoutNode, Size, Status}; /// A component displays information and can be interacted with. #[allow(unused_variables)] -pub trait Component<Message, Backend> -where - Backend: tui::backend::Backend, -{ +#[enum_dispatch] +pub trait TmpComponent<Message> { /// Draws the component. - fn draw(&mut self, area: Rect, context: &DrawContext, frame: &mut Frame<'_, Backend>); + fn draw<Backend>(&mut self, area: Rect, frame: &mut Frame<'_, Backend>) + where + Backend: tui::backend::Backend; /// How a component should react to an [`Event`]. /// diff --git a/src/tuice/component/base/block.rs b/src/tuice/component/base/block.rs index e2697f1e..2232f9d7 100644 --- a/src/tuice/component/base/block.rs +++ b/src/tuice/component/base/block.rs @@ -1,14 +1,14 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, DrawContext, Event, Status}; +use crate::tuice::{Event, Status, TmpComponent}; pub struct Block {} -impl<Message, B> Component<Message, B> for Block -where - B: Backend, -{ - fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) { +impl<Message> TmpComponent<Message> for Block { + fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + where + B: Backend, + { todo!() } diff --git a/src/tuice/component/base/carousel.rs b/src/tuice/component/base/carousel.rs index 2169f9d0..62407c9c 100644 --- a/src/tuice/component/base/carousel.rs +++ b/src/tuice/component/base/carousel.rs @@ -1,14 +1,14 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, DrawContext, Event, Status}; +use crate::tuice::{Event, Status, TmpComponent}; pub struct Carousel {} -impl<Message, B> Component<Message, B> for Carousel -where - B: Backend, -{ - fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) { +impl<Message> TmpComponent<Message> for Carousel { + fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + where + B: Backend, + { todo!() } diff --git a/src/tuice/component/base/column.rs b/src/tuice/component/base/column.rs index 3c15f893..5fdae3dc 100644 --- a/src/tuice/component/base/column.rs +++ b/src/tuice/component/base/column.rs @@ -1,14 +1,14 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, DrawContext, Event, Status}; +use crate::tuice::{Event, Status, TmpComponent}; pub struct Column {} -impl<Message, B> Component<Message, B> for Column -where - B: Backend, -{ - fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) { +impl<Message> TmpComponent<Message> for Column { + fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + where + B: Backend, + { todo!() } diff --git a/src/tuice/component/base/container.rs b/src/tuice/component/base/container.rs index a2c28750..f085349e 100644 --- a/src/tuice/component/base/container.rs +++ b/src/tuice/component/base/container.rs @@ -1,34 +1,47 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Bounds, Component, DrawContext, Event, Length, Size, Status, LayoutNode}; +use crate::tuice::{Bounds, Element, Event, LayoutNode, Size, Status, TmpComponent}; -pub struct Container<'a, Message, B> -where - B: Backend, -{ - width: Length, - height: Length, - child: Box<dyn Component<Message, B> + 'a>, +/// A [`Container`] just contains a child, as well as being able to be sized. +/// +/// Inspired by Flutter's [Container class](https://api.flutter.dev/flutter/widgets/Container-class.html). +#[derive(Default)] +pub struct Container<'a, Message> { + width: Option<u16>, + height: Option<u16>, + child: Option<Box<Element<'a, Message>>>, } -impl<'a, Message, B> Container<'a, Message, B> -where - B: Backend, -{ - pub fn new(child: Box<dyn Component<Message, B> + 'a>) -> Self { +impl<'a, Message> Container<'a, Message> { + pub fn with_child(child: Element<'a, Message>) -> Self { Self { - width: Length::Flex, - height: Length::Flex, - child, + width: None, + height: None, + child: Some(child.into()), } } + + pub fn child(mut self, child: Option<Element<'a, Message>>) -> Self { + self.child = child.map(|c| c.into()); + self + } + + pub fn width(mut self, width: Option<u16>) -> Self { + self.width = width; + self + } + + pub fn height(mut self, height: Option<u16>) -> Self { + self.height = height; + self + } } -impl<'a, Message, B> Component<Message, B> for Container<'a, Message, B> -where - B: Backend, -{ - fn draw(&mut self, area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) { +impl<'a, Message> TmpComponent<Message> for Container<'a, Message> { + fn draw<B>(&mut self, area: Rect, _frame: &mut Frame<'_, B>) + where + B: Backend, + { todo!() } @@ -37,30 +50,39 @@ where } fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { - let width = match self.width { - Length::Flex => { - todo!() - } - Length::FlexRatio(ratio) => { - todo!() - } - Length::Fixed(length) => length.clamp(bounds.min_width, bounds.max_width), - Length::Child => { - todo!() - } - }; + let (width, height) = if let Some(child) = &self.child { + let mut child_node = LayoutNode::default(); - let height = match self.height { - Length::Flex => { - todo!() - } - Length::FlexRatio(ratio) => { - todo!() - } - Length::Fixed(length) => length.clamp(bounds.min_height, bounds.max_height), - Length::Child => { - todo!() + fn bounds_if_exist(val: Option<u16>, min_bound: u16, max_bound: u16) -> (u16, u16) { + if let Some(val) = val { + let val = val.clamp(min_bound, max_bound); + (val, val) + } else { + (min_bound, max_bound) + } } + + let child_bounds = { + let (min_width, max_width) = + bounds_if_exist(self.width, bounds.min_width, bounds.max_width); + let (min_height, max_height) = + bounds_if_exist(self.height, bounds.min_height, bounds.max_height); + + Bounds { + min_width, + min_height, + max_width, + max_height, + } + }; + + let child_size = child.layout(child_bounds, &mut child_node); + + // Note that this is implicitly bounded by our above calculations, + // no need to recheck if it's valid! + (child_size.width, child_size.height) + } else { + (bounds.min_width, bounds.min_height) }; Size { height, width } diff --git a/src/tuice/component/base/flex.rs b/src/tuice/component/base/flex.rs new file mode 100644 index 00000000..29d4709b --- /dev/null +++ b/src/tuice/component/base/flex.rs @@ -0,0 +1,60 @@ +use tui::{backend::Backend, layout::Rect, Frame}; + +use crate::tuice::{Bounds, Element, Event, LayoutNode, Size, Status, TmpComponent}; + +pub struct FlexElement<'a, Message> { + /// Represents a ratio with other [`FlexElement`]s on how far to expand. + pub flex: u16, + element: Element<'a, Message>, +} + +impl<'a, Message> FlexElement<'a, Message> { + pub fn new<I: Into<Element<'a, Message>>>(element: I) -> Self { + Self { + flex: 1, + element: element.into(), + } + } + + pub fn with_flex<I: Into<Element<'a, Message>>>(element: I, flex: u16) -> Self { + Self { + flex, + element: element.into(), + } + } + + pub fn with_no_flex<I: Into<Element<'a, Message>>>(element: I) -> Self { + Self { + flex: 0, + element: element.into(), + } + } + + pub fn flex(mut self, flex: u16) -> Self { + self.flex = flex; + self + } + + pub(crate) fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>) + where + B: Backend, + { + self.element.draw(area, frame) + } + + pub(crate) fn on_event( + &mut self, area: Rect, event: Event, messages: &mut Vec<Message>, + ) -> Status { + self.element.on_event(area, event, messages) + } + + pub(crate) fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { + todo!() + } +} + +impl<'a, Message> From<Element<'a, Message>> for FlexElement<'a, Message> { + fn from(element: Element<'a, Message>) -> Self { + Self { flex: 0, element } + } +} diff --git a/src/tuice/component/base/mod.rs b/src/tuice/component/base/mod.rs index d2152703..237621a5 100644 --- a/src/tuice/component/base/mod.rs +++ b/src/tuice/component/base/mod.rs @@ -16,8 +16,8 @@ pub use block::Block; pub mod carousel; pub use carousel::Carousel; -pub mod sized_box; -pub use sized_box::SizedBox; - pub mod container; pub use container::Container; + +pub mod flex; +pub use flex::*; diff --git a/src/tuice/component/base/row.rs b/src/tuice/component/base/row.rs index dcdbf5d2..93c5b317 100644 --- a/src/tuice/component/base/row.rs +++ b/src/tuice/component/base/row.rs @@ -1,42 +1,59 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Bounds, Component, DrawContext, Event, Size, Status}; +use crate::tuice::{Bounds, Event, FlexElement, LayoutNode, Size, Status, TmpComponent}; #[derive(Default)] -pub struct Row<'a, Message, B> -where - B: Backend, -{ - children: Vec<Box<dyn Component<Message, B> + 'a>>, // FIXME: For performance purposes, let's cheat and use enum-dispatch +pub struct Row<'a, Message> { + children: Vec<FlexElement<'a, Message>>, } -impl<'a, Message, B> Row<'a, Message, B> -where - B: Backend, -{ +impl<'a, Message> Row<'a, Message> { /// Creates a new [`Row`] with the given children. pub fn with_children<C>(children: Vec<C>) -> Self where - C: Into<Box<dyn Component<Message, B> + 'a>>, + C: Into<FlexElement<'a, Message>>, { Self { children: children.into_iter().map(Into::into).collect(), } } + + pub fn with_child(mut self) -> Self { + self + } + + pub fn with_flex_child(mut self) -> Self { + self + } } -impl<'a, Message, B> Component<Message, B> for Row<'a, Message, B> -where - B: Backend, -{ - fn draw(&mut self, area: Rect, context: &DrawContext, frame: &mut Frame<'_, B>) { +impl<'a, Message> TmpComponent<Message> for Row<'a, Message> { + fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>) + where + B: Backend, + { self.children.iter_mut().for_each(|child| { - // TODO: This is just temp! We need layout! - child.draw(area, context, frame); + child.draw(area, frame); }) } fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { Status::Ignored } + + fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { + let mut remaining_bounds = bounds; + + let child_nodes: Vec<LayoutNode> = self + .children + .iter() + .map(|child| { + let mut child_node = LayoutNode::default(); + let size = child.layout(remaining_bounds, &mut child_node); + child_node + }) + .collect(); + + todo!() + } } diff --git a/src/tuice/component/base/shortcut.rs b/src/tuice/component/base/shortcut.rs index 9c70d10c..b17ac0dd 100644 --- a/src/tuice/component/base/shortcut.rs +++ b/src/tuice/component/base/shortcut.rs @@ -1,17 +1,17 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, DrawContext, Event, Status}; +use crate::tuice::{Event, Status, TmpComponent}; /// A [`Component`] to handle keyboard shortcuts and assign actions to them. /// /// Inspired by [Flutter's approach](https://docs.flutter.dev/development/ui/advanced/actions_and_shortcuts). pub struct Shortcut {} -impl<Message, B> Component<Message, B> for Shortcut -where - B: Backend, -{ - fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) { +impl<Message> TmpComponent<Message> for Shortcut { + fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + where + B: Backend, + { todo!() } diff --git a/src/tuice/component/base/sized_box.rs b/src/tuice/component/base/sized_box.rs deleted file mode 100644 index 4f2286b6..00000000 --- a/src/tuice/component/base/sized_box.rs +++ /dev/null @@ -1,59 +0,0 @@ -use tui::backend::Backend; - -use crate::tuice::{Component, Length}; - -pub struct SizedBox<'a, Message, B> -where - B: Backend, -{ - width: Length, - height: Length, - child: Box<dyn Component<Message, B> + 'a>, -} - -impl<'a, Message, B> SizedBox<'a, Message, B> -where - B: Backend, -{ - /// Creates a new [`SizedBox`] for a child component - /// with a [`Length::Flex`] width and height. - pub fn new(child: Box<dyn Component<Message, B> + 'a>) -> Self { - Self { - width: Length::Flex, - height: Length::Flex, - child, - } - } - - /// Creates a new [`SizedBox`] for a child component - /// with a [`Length::Flex`] height. - pub fn with_width(child: Box<dyn Component<Message, B> + 'a>, width: Length) -> Self { - Self { - width, - height: Length::Flex, - child, - } - } - - /// Creates a new [`SizedBox`] for a child component - /// with a [`Length::Flex`] width. - pub fn with_height(child: Box<dyn Component<Message, B> + 'a>, height: Length) -> Self { - Self { - width: Length::Flex, - height, - child, - } - } - - /// Sets the width of the [`SizedBox`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`SizedBox`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } -} diff --git a/src/tuice/component/base/text_table.rs b/src/tuice/component/base/text_table.rs index 042e524c..03c45e49 100644 --- a/src/tuice/component/base/text_table.rs +++ b/src/tuice/component/base/text_table.rs @@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::{ constants::TABLE_GAP_HEIGHT_LIMIT, - tuice::{Component, DrawContext, Event, Status}, + tuice::{Event, Status, TmpComponent}, }; pub use self::table_column::{TextColumn, TextColumnConstraint}; @@ -165,21 +165,11 @@ impl<'a, Message> TextTable<'a, Message> { } } -impl<'a, Message, B> From<TextTable<'a, Message>> for Box<dyn Component<Message, B> + 'a> -where - Message: 'a, - B: Backend, -{ - fn from(table: TextTable<'a, Message>) -> Self { - Box::new(table) - } -} - -impl<'a, Message, B> Component<Message, B> for TextTable<'a, Message> -where - B: Backend, -{ - fn draw(&mut self, area: Rect, context: &DrawContext, frame: &mut Frame<'_, B>) { +impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> { + fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>) + where + B: Backend, + { self.table_gap = if !self.show_gap || (self.rows.len() + 2 > area.height.into() && area.height < TABLE_GAP_HEIGHT_LIMIT) { diff --git a/src/tuice/component/widget/mod.rs b/src/tuice/component/widget/mod.rs index e69de29b..8b137891 100644 --- a/src/tuice/component/widget/mod.rs +++ b/src/tuice/component/widget/mod.rs @@ -0,0 +1 @@ + diff --git a/src/tuice/draw_context.rs b/src/tuice/draw_context.rs deleted file mode 100644 index 2815d9a7..00000000 --- a/src/tuice/draw_context.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct DrawContext {} diff --git a/src/tuice/element.rs b/src/tuice/element.rs new file mode 100644 index 00000000..1148df98 --- /dev/null +++ b/src/tuice/element.rs @@ -0,0 +1,19 @@ +use enum_dispatch::enum_dispatch; +use tui::{layout::Rect, Frame}; + +use super::{ + Block, Bounds, Carousel, Column, Container, Event, LayoutNode, Row, Shortcut, Size, Status, + TextTable, TmpComponent, +}; + +/// An [`Element`] is an instantiated [`Component`]. +#[enum_dispatch(TmpComponent<Message>)] +pub enum Element<'a, Message> { + Block, + Carousel, + Column, + Container(Container<'a, Message>), + Row(Row<'a, Message>), + Shortcut, + TextTable(TextTable<'a, Message>), +} diff --git a/src/tuice/layout/bounds.rs b/src/tuice/layout/bounds.rs index 59fe3ff2..15cf7dcc 100644 --- a/src/tuice/layout/bounds.rs +++ b/src/tuice/layout/bounds.rs @@ -2,6 +2,7 @@ /// /// These are sent from a parent component to a child to determine the [`Size`](super::Size) /// of a child, which is passed back up to the parent. +#[derive(Clone, Copy, Default)] pub struct Bounds { /// The minimal width available. pub min_width: u16, @@ -15,3 +16,14 @@ pub struct Bounds { /// The maximal height available. pub max_height: u16, } + +impl Bounds { + pub fn with_two_bounds(width: u16, height: u16) -> Self { + Self { + min_width: width, + min_height: height, + max_width: width, + max_height: height, + } + } +} diff --git a/src/tuice/layout/build_layout.rs b/src/tuice/layout/build_layout.rs index 6d8c59fb..709eed49 100644 --- a/src/tuice/layout/build_layout.rs +++ b/src/tuice/layout/build_layout.rs @@ -1,13 +1,8 @@ use tui::layout::Rect; -use crate::tuice::{Bounds, Component, LayoutNode}; +use crate::tuice::{Bounds, Element, LayoutNode, TmpComponent}; -pub fn build_layout_tree<Message, Backend>( - area: Rect, root: &Box<dyn Component<Message, Backend>>, -) -> LayoutNode -where - Backend: tui::backend::Backend, -{ +pub fn build_layout_tree<Message>(area: Rect, root: &Element<'_, Message>) -> LayoutNode { let mut root_layout_node = LayoutNode::from_area(area); let bounds = Bounds { min_width: 0, @@ -16,7 +11,7 @@ where max_height: area.height, }; - root.layout(bounds, &mut root_layout_node); + let _ = root.layout(bounds, &mut root_layout_node); root_layout_node } diff --git a/src/tuice/mod.rs b/src/tuice/mod.rs index 3a942e2f..3a33cc60 100644 --- a/src/tuice/mod.rs +++ b/src/tuice/mod.rs @@ -15,5 +15,5 @@ pub use runtime::RuntimeEvent; pub mod layout; pub use layout::*; -pub mod draw_context; -pub use draw_context::*; +pub mod element; +pub use element::*; diff --git a/src/tuice/runtime.rs b/src/tuice/runtime.rs index dddb1722..5c22697e 100644 --- a/src/tuice/runtime.rs +++ b/src/tuice/runtime.rs @@ -4,7 +4,7 @@ use tui::layout::Rect; use crate::tuice::Status; -use super::{Application, Event}; +use super::{Application, Event, TmpComponent}; #[derive(Clone, Copy, Debug)] pub enum RuntimeEvent<Message> { diff --git a/src/units/data_units.rs b/src/units/data_units.rs index ce8f4867..d0b7c1e1 100644 --- a/src/units/data_units.rs +++ b/src/units/data_units.rs @@ -3,4 +3,3 @@ pub enum DataUnit { Byte, Bit, } - |