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::{
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) {

View File

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

View File

@ -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`].
///

View File

@ -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
impl<Message> TmpComponent<Message> for Block {
fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend,
{
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
{
todo!()
}

View File

@ -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
impl<Message> TmpComponent<Message> for Carousel {
fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend,
{
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
{
todo!()
}

View File

@ -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
impl<Message> TmpComponent<Message> for Column {
fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend,
{
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
{
todo!()
}

View File

@ -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
impl<'a, Message> TmpComponent<Message> for Container<'a, Message> {
fn draw<B>(&mut self, area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend,
{
fn draw(&mut self, area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
{
todo!()
}
@ -37,30 +50,39 @@ where
}
fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
let width = match self.width {
Length::Flex => {
todo!()
let (width, height) = if let Some(child) = &self.child {
let mut child_node = LayoutNode::default();
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 => {
todo!()
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 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!()
}
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 }

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 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::*;

View File

@ -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
impl<'a, Message> TmpComponent<Message> for Row<'a, Message> {
fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>)
where
B: Backend,
{
fn draw(&mut self, area: Rect, context: &DrawContext, frame: &mut Frame<'_, B>) {
{
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!()
}
}

View File

@ -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
impl<Message> TmpComponent<Message> for Shortcut {
fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>)
where
B: Backend,
{
fn draw(&mut self, _area: Rect, _context: &DrawContext, _frame: &mut Frame<'_, B>) {
{
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::{
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,
impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>)
where
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.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)
/// 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,
}
}
}

View File

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

View File

@ -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::*;

View File

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

View File

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