diff options
author | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-11 17:58:18 -0500 |
---|---|---|
committer | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-11 17:58:32 -0500 |
commit | bf81a389b8ab54e3d9c5b0151e8960396b6a2a75 (patch) | |
tree | 6afce215e33ca9c9300ed5ec8187a30237630106 | |
parent | f1ec2fd70f7fc9a88726e2851341720172ebf0ab (diff) |
hmm
-rw-r--r-- | src/options/layout_options.rs | 7 | ||||
-rw-r--r-- | src/tuice/component.rs | 28 | ||||
-rw-r--r-- | src/tuice/component/base/block.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/carousel.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/column.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/container.rs | 68 | ||||
-rw-r--r-- | src/tuice/component/base/mod.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/row.rs | 8 | ||||
-rw-r--r-- | src/tuice/component/base/shortcut.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/sized_box.rs | 59 | ||||
-rw-r--r-- | src/tuice/component/base/text_table.rs | 105 | ||||
-rw-r--r-- | src/tuice/component/widget/mod.rs | 0 | ||||
-rw-r--r-- | src/tuice/context.rs | 6 | ||||
-rw-r--r-- | src/tuice/layout/bounds.rs | 17 | ||||
-rw-r--r-- | src/tuice/layout/layout_node.rs | 12 | ||||
-rw-r--r-- | src/tuice/layout/length.rs | 5 | ||||
-rw-r--r-- | src/tuice/layout/mod.rs | 13 | ||||
-rw-r--r-- | src/tuice/layout/size.rs | 11 | ||||
-rw-r--r-- | src/tuice/mod.rs | 3 |
19 files changed, 294 insertions, 78 deletions
diff --git a/src/options/layout_options.rs b/src/options/layout_options.rs index 42a4f8de..23815819 100644 --- a/src/options/layout_options.rs +++ b/src/options/layout_options.rs @@ -43,8 +43,15 @@ pub struct FinalWidget { #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[serde(untagged)] pub enum LayoutRule { + /// Let the child decide how big to make the current node. Child, + + /// Expand to whatever space is left; the `ratio` determines how + /// much space to take if there are more than one + /// [`LayoutRule::Expand`] component. Expand { ratio: u32 }, + + /// Take up exactly `length` space if possible. Length { length: u16 }, } diff --git a/src/tuice/component.rs b/src/tuice/component.rs index 4667b047..c359cfac 100644 --- a/src/tuice/component.rs +++ b/src/tuice/component.rs @@ -1,9 +1,12 @@ pub mod base; pub use base::*; +pub mod widget; +pub use widget::*; + use tui::{layout::Rect, Frame}; -use super::{Event, Status}; +use super::{Bounds, Context, Event, LayoutNode, Size, Status}; /// A component displays information and can be interacted with. #[allow(unused_variables)] @@ -11,14 +14,23 @@ pub trait Component<Message, Backend> where Backend: tui::backend::Backend, { - /// Handles an [`Event`]. Defaults to just ignoring the event. - fn on_event(&mut self, bounds: Rect, event: Event, messages: &mut Vec<Message>) -> Status { + /// Draws the component. + fn draw(&mut self, area: Rect, context: &Context, frame: &mut Frame<'_, Backend>); + + /// How a component should react to an [`Event`]. + /// + /// Defaults to just ignoring the event. + fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status { Status::Ignored } - /// Returns the desired layout of the component. Defaults to returning - fn layout(&self) {} - - /// Draws the component. - fn draw(&mut self, bounds: Rect, frame: &mut Frame<'_, Backend>); + /// How a component should size itself and its children, given some [`Bounds`]. + /// + /// Defaults to returning a [`Size`] that fills up the bounds given. + fn layout(&self, bounds: Bounds) -> Size { + Size { + width: bounds.max_width, + height: bounds.max_height, + } + } } diff --git a/src/tuice/component/base/block.rs b/src/tuice/component/base/block.rs index 015d6342..35d3a351 100644 --- a/src/tuice/component/base/block.rs +++ b/src/tuice/component/base/block.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, Event, Status}; +use crate::tuice::{Component, Context, Event, Status}; pub struct Block {} @@ -8,11 +8,11 @@ impl<Message, B> Component<Message, B> for Block where B: Backend, { - fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) { + fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) { todo!() } - fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { + fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { Status::Ignored } } diff --git a/src/tuice/component/base/carousel.rs b/src/tuice/component/base/carousel.rs index ea3ac6b2..bfdfe6ff 100644 --- a/src/tuice/component/base/carousel.rs +++ b/src/tuice/component/base/carousel.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, Event, Status}; +use crate::tuice::{Component, Context, Event, Status}; pub struct Carousel {} @@ -8,11 +8,11 @@ impl<Message, B> Component<Message, B> for Carousel where B: Backend, { - fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) { + fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) { todo!() } - fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { + fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { Status::Ignored } } diff --git a/src/tuice/component/base/column.rs b/src/tuice/component/base/column.rs index 8c8952dd..02b084f4 100644 --- a/src/tuice/component/base/column.rs +++ b/src/tuice/component/base/column.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, Event, Status}; +use crate::tuice::{Component, Context, Event, Status}; pub struct Column {} @@ -8,11 +8,11 @@ impl<Message, B> Component<Message, B> for Column where B: Backend, { - fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) { + fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) { todo!() } - fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { + fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { Status::Ignored } } diff --git a/src/tuice/component/base/container.rs b/src/tuice/component/base/container.rs new file mode 100644 index 00000000..0f1f68cd --- /dev/null +++ b/src/tuice/component/base/container.rs @@ -0,0 +1,68 @@ +use tui::{backend::Backend, layout::Rect, Frame}; + +use crate::tuice::{Bounds, Component, Context, Event, LayoutNode, Length, Size, Status}; + +pub struct Container<'a, Message, B> +where + B: Backend, +{ + width: Length, + height: Length, + child: Box<dyn Component<Message, B> + 'a>, +} + +impl<'a, Message, B> Container<'a, Message, B> +where + B: Backend, +{ + pub fn new(child: Box<dyn Component<Message, B> + 'a>) -> Self { + Self { + width: Length::Flex, + height: Length::Flex, + child, + } + } +} + +impl<'a, Message, B> Component<Message, B> for Container<'a, Message, B> +where + B: Backend, +{ + fn draw(&mut self, area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) { + todo!() + } + + fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { + todo!() + } + + fn layout(&self, bounds: Bounds) -> 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 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!() + } + }; + + Size { height, width } + } +} diff --git a/src/tuice/component/base/mod.rs b/src/tuice/component/base/mod.rs index 95fbcc7b..d2152703 100644 --- a/src/tuice/component/base/mod.rs +++ b/src/tuice/component/base/mod.rs @@ -15,3 +15,9 @@ 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; diff --git a/src/tuice/component/base/row.rs b/src/tuice/component/base/row.rs index 51ab91ea..ebdee1c7 100644 --- a/src/tuice/component/base/row.rs +++ b/src/tuice/component/base/row.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, Event, Status}; +use crate::tuice::{Bounds, Component, Context, Event, Size, Status}; #[derive(Default)] pub struct Row<'a, Message, B> @@ -29,14 +29,14 @@ impl<'a, Message, B> Component<Message, B> for Row<'a, Message, B> where B: Backend, { - fn draw(&mut self, bounds: Rect, frame: &mut Frame<'_, B>) { + fn draw(&mut self, area: Rect, context: &Context, frame: &mut Frame<'_, B>) { self.children.iter_mut().for_each(|child| { // TODO: This is just temp! We need layout! - child.draw(bounds, frame); + child.draw(area, context, frame); }) } - fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { + fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { Status::Ignored } } diff --git a/src/tuice/component/base/shortcut.rs b/src/tuice/component/base/shortcut.rs index 1f7100e6..37a24e94 100644 --- a/src/tuice/component/base/shortcut.rs +++ b/src/tuice/component/base/shortcut.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Component, Event, Status}; +use crate::tuice::{Component, Context, Event, Status}; /// A [`Component`] to handle keyboard shortcuts and assign actions to them. /// @@ -11,11 +11,11 @@ impl<Message, B> Component<Message, B> for Shortcut where B: Backend, { - fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) { + fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) { todo!() } - fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { + fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { Status::Ignored } } diff --git a/src/tuice/component/base/sized_box.rs b/src/tuice/component/base/sized_box.rs new file mode 100644 index 00000000..4f2286b6 --- /dev/null +++ b/src/tuice/component/base/sized_box.rs @@ -0,0 +1,59 @@ +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 d2f84249..597d5573 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, Event, Status}, + tuice::{Component, Context, Event, Status}, }; pub use self::table_column::{TextColumn, TextColumnConstraint}; @@ -179,7 +179,55 @@ impl<'a, Message, B> Component<Message, B> for TextTable<'a, Message> where B: Backend, { - fn on_event(&mut self, bounds: Rect, event: Event, messages: &mut Vec<Message>) -> Status { + fn draw(&mut self, area: Rect, context: &Context, frame: &mut Frame<'_, B>) { + self.table_gap = if !self.show_gap + || (self.rows.len() + 2 > area.height.into() && area.height < TABLE_GAP_HEIGHT_LIMIT) + { + 0 + } else { + 1 + }; + + let table_extras = 1 + self.table_gap; + let scrollable_height = area.height.saturating_sub(table_extras); + self.update_column_widths(area); + + // Calculate widths first, since we need them later. + let widths = self + .column_widths + .iter() + .map(|column| Constraint::Length(*column)) + .collect::<Vec<_>>(); + + // Then calculate rows. We truncate the amount of data read based on height, + // as well as truncating some entries based on available width. + let data_slice = { + // Note: `get_list_start` already ensures `start` is within the bounds of the number of items, so no need to check! + let start = self + .state + .display_start_index(area, scrollable_height as usize); + let end = min(self.state.num_items(), start + scrollable_height as usize); + + self.rows[start..end].to_vec() + }; + + // Now build up our headers... + let header = Row::new(self.columns.iter().map(|column| column.name.clone())) + .style(self.style_sheet.table_header) + .bottom_margin(self.table_gap); + + let mut table = Table::new(data_slice) + .header(header) + .style(self.style_sheet.text); + + if self.show_selected_entry { + table = table.highlight_style(self.style_sheet.selected_text); + } + + frame.render_stateful_widget(table.widths(&widths), area, self.state.tui_state()); + } + + fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status { use crate::tuice::MouseBoundIntersect; use crossterm::event::{MouseButton, MouseEventKind}; @@ -194,10 +242,10 @@ where } } Event::Mouse(mouse_event) => { - if mouse_event.does_mouse_intersect_bounds(bounds) { + if mouse_event.does_mouse_intersect_bounds(area) { match mouse_event.kind { MouseEventKind::Down(MouseButton::Left) => { - let y = mouse_event.row - bounds.top(); + let y = mouse_event.row - area.top(); if self.sortable && y == 0 { todo!() @@ -230,55 +278,6 @@ where } } } - - fn draw(&mut self, bounds: Rect, frame: &mut Frame<'_, B>) { - self.table_gap = if !self.show_gap - || (self.rows.len() + 2 > bounds.height.into() - && bounds.height < TABLE_GAP_HEIGHT_LIMIT) - { - 0 - } else { - 1 - }; - - let table_extras = 1 + self.table_gap; - let scrollable_height = bounds.height.saturating_sub(table_extras); - self.update_column_widths(bounds); - - // Calculate widths first, since we need them later. - let widths = self - .column_widths - .iter() - .map(|column| Constraint::Length(*column)) - .collect::<Vec<_>>(); - - // Then calculate rows. We truncate the amount of data read based on height, - // as well as truncating some entries based on available width. - let data_slice = { - // Note: `get_list_start` already ensures `start` is within the bounds of the number of items, so no need to check! - let start = self - .state - .display_start_index(bounds, scrollable_height as usize); - let end = min(self.state.num_items(), start + scrollable_height as usize); - - self.rows[start..end].to_vec() - }; - - // Now build up our headers... - let header = Row::new(self.columns.iter().map(|column| column.name.clone())) - .style(self.style_sheet.table_header) - .bottom_margin(self.table_gap); - - let mut table = Table::new(data_slice) - .header(header) - .style(self.style_sheet.text); - - if self.show_selected_entry { - table = table.highlight_style(self.style_sheet.selected_text); - } - - frame.render_stateful_widget(table.widths(&widths), bounds, self.state.tui_state()); - } } #[cfg(test)] diff --git a/src/tuice/component/widget/mod.rs b/src/tuice/component/widget/mod.rs new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/tuice/component/widget/mod.rs diff --git a/src/tuice/context.rs b/src/tuice/context.rs new file mode 100644 index 00000000..766e0c17 --- /dev/null +++ b/src/tuice/context.rs @@ -0,0 +1,6 @@ +use crate::app::layout_manager::LayoutNode; + +/// Internal management for drawing and the like. +pub struct Context { + layout_root: LayoutNode, +} diff --git a/src/tuice/layout/bounds.rs b/src/tuice/layout/bounds.rs new file mode 100644 index 00000000..59fe3ff2 --- /dev/null +++ b/src/tuice/layout/bounds.rs @@ -0,0 +1,17 @@ +/// [`Bounds`] represent minimal and maximal widths/height constraints while laying things out. +/// +/// 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. +pub struct Bounds { + /// The minimal width available. + pub min_width: u16, + + /// The minimal height available. + pub min_height: u16, + + /// The maximal width available. + pub max_width: u16, + + /// The maximal height available. + pub max_height: u16, +} diff --git a/src/tuice/layout/layout_node.rs b/src/tuice/layout/layout_node.rs new file mode 100644 index 00000000..aebaaa49 --- /dev/null +++ b/src/tuice/layout/layout_node.rs @@ -0,0 +1,12 @@ +use tui::layout::Rect; + +/// A node for the layout tree. +pub enum LayoutNode { + Leaf { + area: Rect, + }, + Branch { + area: Rect, + children: Vec<LayoutNode>, + }, +} diff --git a/src/tuice/layout/length.rs b/src/tuice/layout/length.rs index e3fea02e..eb63a895 100644 --- a/src/tuice/layout/length.rs +++ b/src/tuice/layout/length.rs @@ -1,4 +1,4 @@ -/// Which strategy to use while laying out widgets. +/// Which strategy to use while laying out things. pub enum Length { /// Fill in remaining space. Equivalent to `Length::FlexRatio(1)`. Flex, @@ -8,4 +8,7 @@ pub enum Length { /// Fill in a fixed amount of space. Fixed(u16), + + /// Let the child determine how large to make the component. + Child, } diff --git a/src/tuice/layout/mod.rs b/src/tuice/layout/mod.rs index 694d91b5..903c4039 100644 --- a/src/tuice/layout/mod.rs +++ b/src/tuice/layout/mod.rs @@ -1,2 +1,15 @@ pub mod length; pub use length::Length; + +pub mod bounds; +pub use bounds::Bounds; + +pub mod size; +pub use size::Size; + +pub mod layout_node; +pub use layout_node::LayoutNode; + +pub fn build_layout() -> LayoutNode { + todo!() +} diff --git a/src/tuice/layout/size.rs b/src/tuice/layout/size.rs new file mode 100644 index 00000000..209cd8f7 --- /dev/null +++ b/src/tuice/layout/size.rs @@ -0,0 +1,11 @@ +/// A [`Size`] represents calculated widths and heights for a component. +/// +/// A [`Size`] is sent from a child component back up to its parents after +/// first being given a [`Bounds`](super::Bounds) from the parent. +pub struct Size { + /// The given width. + pub width: u16, + + /// The given height. + pub height: u16, +} diff --git a/src/tuice/mod.rs b/src/tuice/mod.rs index e4d142c0..62ce33db 100644 --- a/src/tuice/mod.rs +++ b/src/tuice/mod.rs @@ -14,3 +14,6 @@ pub use runtime::RuntimeEvent; pub mod layout; pub use layout::*; + +pub mod context; +pub use context::*; |