summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-12-11 17:58:18 -0500
committerClementTsang <cjhtsang@uwaterloo.ca>2021-12-11 17:58:32 -0500
commitbf81a389b8ab54e3d9c5b0151e8960396b6a2a75 (patch)
tree6afce215e33ca9c9300ed5ec8187a30237630106
parentf1ec2fd70f7fc9a88726e2851341720172ebf0ab (diff)
hmm
-rw-r--r--src/options/layout_options.rs7
-rw-r--r--src/tuice/component.rs28
-rw-r--r--src/tuice/component/base/block.rs6
-rw-r--r--src/tuice/component/base/carousel.rs6
-rw-r--r--src/tuice/component/base/column.rs6
-rw-r--r--src/tuice/component/base/container.rs68
-rw-r--r--src/tuice/component/base/mod.rs6
-rw-r--r--src/tuice/component/base/row.rs8
-rw-r--r--src/tuice/component/base/shortcut.rs6
-rw-r--r--src/tuice/component/base/sized_box.rs59
-rw-r--r--src/tuice/component/base/text_table.rs105
-rw-r--r--src/tuice/component/widget/mod.rs0
-rw-r--r--src/tuice/context.rs6
-rw-r--r--src/tuice/layout/bounds.rs17
-rw-r--r--src/tuice/layout/layout_node.rs12
-rw-r--r--src/tuice/layout/length.rs5
-rw-r--r--src/tuice/layout/mod.rs13
-rw-r--r--src/tuice/layout/size.rs11
-rw-r--r--src/tuice/mod.rs3
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::*;