Enum dispatch time

This commit is contained in:
ClementTsang 2021-12-12 15:30:42 -05:00
parent 65a1ee5202
commit b6b0493333
21 changed files with 245 additions and 190 deletions

View File

@ -29,7 +29,7 @@ use frozen_state::FrozenState;
use crate::{ use crate::{
canvas::Painter, canvas::Painter,
constants, constants,
tuice::{Application, Row}, tuice::{Application, Element, Row},
units::data_units::DataUnit, units::data_units::DataUnit,
Pid, Pid,
}; };
@ -234,12 +234,11 @@ impl Application for AppState {
self.terminator.load(SeqCst) self.terminator.load(SeqCst)
} }
fn view( fn view(&mut self) -> Element<'static, Self::Message> {
&mut self, use crate::tuice::TextTable;
) -> Box<dyn crate::tuice::Component<Self::Message, crate::tuice::CrosstermBackend>> { Element::from(Row::with_children(vec![Element::from(TextTable::new(
Box::new(Row::with_children(vec![crate::tuice::TextTable::new(
vec!["A", "B", "C"], vec!["A", "B", "C"],
)])) ))]))
} }
fn destroy(&mut self) { fn destroy(&mut self) {

View File

@ -2,7 +2,7 @@ use std::{fmt::Debug, sync::mpsc::Receiver};
use super::{ use super::{
runtime::{self, RuntimeEvent}, runtime::{self, RuntimeEvent},
Component, Event, Element, Event,
}; };
/// An alias to the [`tui::backend::CrosstermBackend`] writing to [`std::io::Stdout`]. /// An alias to the [`tui::backend::CrosstermBackend`] writing to [`std::io::Stdout`].
@ -19,7 +19,7 @@ pub trait Application: Sized {
/// always returning false. /// always returning false.
fn is_terminated(&self) -> bool; 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. /// To run upon stopping the application.
fn destroy(&mut self) {} fn destroy(&mut self) {}

View File

@ -4,18 +4,19 @@ pub use base::*;
pub mod widget; pub mod widget;
pub use widget::*; pub use widget::*;
use enum_dispatch::enum_dispatch;
use tui::{layout::Rect, Frame}; 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. /// A component displays information and can be interacted with.
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait Component<Message, Backend> #[enum_dispatch]
where pub trait TmpComponent<Message> {
Backend: tui::backend::Backend,
{
/// Draws the component. /// 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`]. /// How a component should react to an [`Event`].
/// ///

View File

@ -1,14 +1,14 @@
use tui::{backend::Backend, layout::Rect, Frame}; use tui::{backend::Backend, layout::Rect, Frame};
use crate::tuice::{Component, DrawContext, Event, Status}; use crate::tuice::{Event, Status, TmpComponent};
pub struct Block {} pub struct Block {}
impl<Message, B> Component<Message, B> for Block impl<Message> TmpComponent<Message> for Block {
where fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend, B: Backend,
{ {
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
todo!() todo!()
} }

View File

@ -1,14 +1,14 @@
use tui::{backend::Backend, layout::Rect, Frame}; use tui::{backend::Backend, layout::Rect, Frame};
use crate::tuice::{Component, DrawContext, Event, Status}; use crate::tuice::{Event, Status, TmpComponent};
pub struct Carousel {} pub struct Carousel {}
impl<Message, B> Component<Message, B> for Carousel impl<Message> TmpComponent<Message> for Carousel {
where fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend, B: Backend,
{ {
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
todo!() todo!()
} }

View File

@ -1,14 +1,14 @@
use tui::{backend::Backend, layout::Rect, Frame}; use tui::{backend::Backend, layout::Rect, Frame};
use crate::tuice::{Component, DrawContext, Event, Status}; use crate::tuice::{Event, Status, TmpComponent};
pub struct Column {} pub struct Column {}
impl<Message, B> Component<Message, B> for Column impl<Message> TmpComponent<Message> for Column {
where fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend, B: Backend,
{ {
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
todo!() todo!()
} }

View File

@ -1,34 +1,47 @@
use tui::{backend::Backend, layout::Rect, Frame}; 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> /// A [`Container`] just contains a child, as well as being able to be sized.
where ///
B: Backend, /// Inspired by Flutter's [Container class](https://api.flutter.dev/flutter/widgets/Container-class.html).
{ #[derive(Default)]
width: Length, pub struct Container<'a, Message> {
height: Length, width: Option<u16>,
child: Box<dyn Component<Message, B> + 'a>, height: Option<u16>,
child: Option<Box<Element<'a, Message>>>,
} }
impl<'a, Message, B> Container<'a, Message, B> impl<'a, Message> Container<'a, Message> {
where pub fn with_child(child: Element<'a, Message>) -> Self {
B: Backend,
{
pub fn new(child: Box<dyn Component<Message, B> + 'a>) -> Self {
Self { Self {
width: Length::Flex, width: None,
height: Length::Flex, height: None,
child, 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> impl<'a, Message> TmpComponent<Message> for Container<'a, Message> {
where fn draw<B>(&mut self, area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend, B: Backend,
{ {
fn draw(&mut self, area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
todo!() todo!()
} }
@ -37,30 +50,39 @@ where
} }
fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
let width = match self.width { let (width, height) = if let Some(child) = &self.child {
Length::Flex => { let mut child_node = LayoutNode::default();
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)
} }
Length::FlexRatio(ratio) => {
todo!()
} }
Length::Fixed(length) => length.clamp(bounds.min_width, bounds.max_width),
Length::Child => { let child_bounds = {
todo!() 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 height = match self.height { let child_size = child.layout(child_bounds, &mut child_node);
Length::Flex => {
todo!() // Note that this is implicitly bounded by our above calculations,
} // no need to recheck if it's valid!
Length::FlexRatio(ratio) => { (child_size.width, child_size.height)
todo!() } else {
} (bounds.min_width, bounds.min_height)
Length::Fixed(length) => length.clamp(bounds.min_height, bounds.max_height),
Length::Child => {
todo!()
}
}; };
Size { height, width } Size { height, width }

View File

@ -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 }
}
}

View File

@ -16,8 +16,8 @@ pub use block::Block;
pub mod carousel; pub mod carousel;
pub use carousel::Carousel; pub use carousel::Carousel;
pub mod sized_box;
pub use sized_box::SizedBox;
pub mod container; pub mod container;
pub use container::Container; pub use container::Container;
pub mod flex;
pub use flex::*;

View File

@ -1,42 +1,59 @@
use tui::{backend::Backend, layout::Rect, Frame}; 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)] #[derive(Default)]
pub struct Row<'a, Message, B> pub struct Row<'a, Message> {
where children: Vec<FlexElement<'a, Message>>,
B: Backend,
{
children: Vec<Box<dyn Component<Message, B> + 'a>>, // FIXME: For performance purposes, let's cheat and use enum-dispatch
} }
impl<'a, Message, B> Row<'a, Message, B> impl<'a, Message> Row<'a, Message> {
where
B: Backend,
{
/// Creates a new [`Row`] with the given children. /// Creates a new [`Row`] with the given children.
pub fn with_children<C>(children: Vec<C>) -> Self pub fn with_children<C>(children: Vec<C>) -> Self
where where
C: Into<Box<dyn Component<Message, B> + 'a>>, C: Into<FlexElement<'a, Message>>,
{ {
Self { Self {
children: children.into_iter().map(Into::into).collect(), 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> impl<'a, Message> TmpComponent<Message> for Row<'a, Message> {
where fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>)
where
B: Backend, B: Backend,
{ {
fn draw(&mut self, area: Rect, context: &DrawContext, frame: &mut Frame<'_, B>) {
self.children.iter_mut().for_each(|child| { self.children.iter_mut().for_each(|child| {
// TODO: This is just temp! We need layout! child.draw(area, frame);
child.draw(area, context, frame);
}) })
} }
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
Status::Ignored 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!()
}
} }

View File

@ -1,17 +1,17 @@
use tui::{backend::Backend, layout::Rect, Frame}; 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. /// 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). /// Inspired by [Flutter's approach](https://docs.flutter.dev/development/ui/advanced/actions_and_shortcuts).
pub struct Shortcut {} pub struct Shortcut {}
impl<Message, B> Component<Message, B> for Shortcut impl<Message> TmpComponent<Message> for Shortcut {
where fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend, B: Backend,
{ {
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
todo!() todo!()
} }

View File

@ -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
}
}

View File

@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
constants::TABLE_GAP_HEIGHT_LIMIT, constants::TABLE_GAP_HEIGHT_LIMIT,
tuice::{Component, DrawContext, Event, Status}, tuice::{Event, Status, TmpComponent},
}; };
pub use self::table_column::{TextColumn, TextColumnConstraint}; 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> impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
where fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>)
Message: 'a, where
B: Backend, 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>) {
self.table_gap = if !self.show_gap self.table_gap = if !self.show_gap
|| (self.rows.len() + 2 > area.height.into() && area.height < TABLE_GAP_HEIGHT_LIMIT) || (self.rows.len() + 2 > area.height.into() && area.height < TABLE_GAP_HEIGHT_LIMIT)
{ {

View File

@ -0,0 +1 @@

View File

@ -1 +0,0 @@
pub struct DrawContext {}

19
src/tuice/element.rs Normal file
View File

@ -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>),
}

View File

@ -2,6 +2,7 @@
/// ///
/// These are sent from a parent component to a child to determine the [`Size`](super::Size) /// 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. /// of a child, which is passed back up to the parent.
#[derive(Clone, Copy, Default)]
pub struct Bounds { pub struct Bounds {
/// The minimal width available. /// The minimal width available.
pub min_width: u16, pub min_width: u16,
@ -15,3 +16,14 @@ pub struct Bounds {
/// The maximal height available. /// The maximal height available.
pub max_height: u16, 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,
}
}
}

View File

@ -1,13 +1,8 @@
use tui::layout::Rect; use tui::layout::Rect;
use crate::tuice::{Bounds, Component, LayoutNode}; use crate::tuice::{Bounds, Element, LayoutNode, TmpComponent};
pub fn build_layout_tree<Message, Backend>( pub fn build_layout_tree<Message>(area: Rect, root: &Element<'_, Message>) -> LayoutNode {
area: Rect, root: &Box<dyn Component<Message, Backend>>,
) -> LayoutNode
where
Backend: tui::backend::Backend,
{
let mut root_layout_node = LayoutNode::from_area(area); let mut root_layout_node = LayoutNode::from_area(area);
let bounds = Bounds { let bounds = Bounds {
min_width: 0, min_width: 0,
@ -16,7 +11,7 @@ where
max_height: area.height, max_height: area.height,
}; };
root.layout(bounds, &mut root_layout_node); let _ = root.layout(bounds, &mut root_layout_node);
root_layout_node root_layout_node
} }

View File

@ -15,5 +15,5 @@ pub use runtime::RuntimeEvent;
pub mod layout; pub mod layout;
pub use layout::*; pub use layout::*;
pub mod draw_context; pub mod element;
pub use draw_context::*; pub use element::*;

View File

@ -4,7 +4,7 @@ use tui::layout::Rect;
use crate::tuice::Status; use crate::tuice::Status;
use super::{Application, Event}; use super::{Application, Event, TmpComponent};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum RuntimeEvent<Message> { pub enum RuntimeEvent<Message> {

View File

@ -3,4 +3,3 @@ pub enum DataUnit {
Byte, Byte,
Bit, Bit,
} }