diff options
author | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-12 20:29:12 -0500 |
---|---|---|
committer | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-12 20:29:34 -0500 |
commit | 58f78731b33242a576939c9b6a5ea9db9504bee2 (patch) | |
tree | 9fabdd7b9c4a0f9c31dbb56edff2da98894ff7f1 | |
parent | b6b0493333bc8a2154aedab9ec52c59e9a9393cc (diff) |
tuice tuice tuice tuice
-rw-r--r-- | src/app.rs | 4 | ||||
-rw-r--r-- | src/tuice/component.rs | 4 | ||||
-rw-r--r-- | src/tuice/component/base/block.rs | 4 | ||||
-rw-r--r-- | src/tuice/component/base/carousel.rs | 4 | ||||
-rw-r--r-- | src/tuice/component/base/column.rs | 4 | ||||
-rw-r--r-- | src/tuice/component/base/container.rs | 4 | ||||
-rw-r--r-- | src/tuice/component/base/flex.rs | 6 | ||||
-rw-r--r-- | src/tuice/component/base/row.rs | 107 | ||||
-rw-r--r-- | src/tuice/component/base/shortcut.rs | 4 | ||||
-rw-r--r-- | src/tuice/component/base/text_table.rs | 16 | ||||
-rw-r--r-- | src/tuice/context.rs | 31 | ||||
-rw-r--r-- | src/tuice/element.rs | 4 | ||||
-rw-r--r-- | src/tuice/layout/bounds.rs | 27 | ||||
-rw-r--r-- | src/tuice/layout/build_layout.rs | 2 | ||||
-rw-r--r-- | src/tuice/layout/layout_node.rs | 8 | ||||
-rw-r--r-- | src/tuice/layout/size.rs | 27 | ||||
-rw-r--r-- | src/tuice/mod.rs | 3 | ||||
-rw-r--r-- | src/tuice/runtime.rs | 5 |
18 files changed, 204 insertions, 60 deletions
@@ -236,9 +236,7 @@ impl Application for AppState { 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"], - ))])) + Row::with_children(vec![Element::from(TextTable::new(vec!["A", "B", "C"]))]).into() } fn destroy(&mut self) { diff --git a/src/tuice/component.rs b/src/tuice/component.rs index 131f576c..42cc7d53 100644 --- a/src/tuice/component.rs +++ b/src/tuice/component.rs @@ -7,14 +7,14 @@ pub use widget::*; use enum_dispatch::enum_dispatch; use tui::{layout::Rect, Frame}; -use super::{Bounds, Event, LayoutNode, Size, Status}; +use super::{Bounds, DrawContext, Event, LayoutNode, Size, Status}; /// A component displays information and can be interacted with. #[allow(unused_variables)] #[enum_dispatch] pub trait TmpComponent<Message> { /// Draws the component. - fn draw<Backend>(&mut self, area: Rect, frame: &mut Frame<'_, Backend>) + fn draw<Backend>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, Backend>) where Backend: tui::backend::Backend; diff --git a/src/tuice/component/base/block.rs b/src/tuice/component/base/block.rs index 2232f9d7..d1a7e2b9 100644 --- a/src/tuice/component/base/block.rs +++ b/src/tuice/component/base/block.rs @@ -1,11 +1,11 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Event, Status, TmpComponent}; +use crate::tuice::{DrawContext, Event, Status, TmpComponent}; pub struct Block {} impl<Message> TmpComponent<Message> for Block { - fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>) where B: Backend, { diff --git a/src/tuice/component/base/carousel.rs b/src/tuice/component/base/carousel.rs index 62407c9c..2790e372 100644 --- a/src/tuice/component/base/carousel.rs +++ b/src/tuice/component/base/carousel.rs @@ -1,11 +1,11 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Event, Status, TmpComponent}; +use crate::tuice::{DrawContext, Event, Status, TmpComponent}; pub struct Carousel {} impl<Message> TmpComponent<Message> for Carousel { - fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>) where B: Backend, { diff --git a/src/tuice/component/base/column.rs b/src/tuice/component/base/column.rs index 5fdae3dc..0c60d246 100644 --- a/src/tuice/component/base/column.rs +++ b/src/tuice/component/base/column.rs @@ -1,11 +1,11 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Event, Status, TmpComponent}; +use crate::tuice::{DrawContext, Event, Status, TmpComponent}; pub struct Column {} impl<Message> TmpComponent<Message> for Column { - fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>) where B: Backend, { diff --git a/src/tuice/component/base/container.rs b/src/tuice/component/base/container.rs index f085349e..f30937ca 100644 --- a/src/tuice/component/base/container.rs +++ b/src/tuice/component/base/container.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Bounds, Element, Event, LayoutNode, Size, Status, TmpComponent}; +use crate::tuice::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent}; /// A [`Container`] just contains a child, as well as being able to be sized. /// @@ -38,7 +38,7 @@ impl<'a, Message> Container<'a, Message> { } impl<'a, Message> TmpComponent<Message> for Container<'a, Message> { - fn draw<B>(&mut self, area: Rect, _frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, context: DrawContext<'_>, _frame: &mut Frame<'_, B>) where B: Backend, { diff --git a/src/tuice/component/base/flex.rs b/src/tuice/component/base/flex.rs index 29d4709b..4faa2757 100644 --- a/src/tuice/component/base/flex.rs +++ b/src/tuice/component/base/flex.rs @@ -1,6 +1,6 @@ use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Bounds, Element, Event, LayoutNode, Size, Status, TmpComponent}; +use crate::tuice::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent}; pub struct FlexElement<'a, Message> { /// Represents a ratio with other [`FlexElement`]s on how far to expand. @@ -35,11 +35,11 @@ impl<'a, Message> FlexElement<'a, Message> { self } - pub(crate) fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>) + pub(crate) fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>) where B: Backend, { - self.element.draw(area, frame) + self.element.draw(context, frame) } pub(crate) fn on_event( diff --git a/src/tuice/component/base/row.rs b/src/tuice/component/base/row.rs index 93c5b317..75c5177c 100644 --- a/src/tuice/component/base/row.rs +++ b/src/tuice/component/base/row.rs @@ -1,6 +1,9 @@ +use itertools::izip; use tui::{backend::Backend, layout::Rect, Frame}; -use crate::tuice::{Bounds, Event, FlexElement, LayoutNode, Size, Status, TmpComponent}; +use crate::tuice::{ + Bounds, DrawContext, Element, Event, FlexElement, LayoutNode, Size, Status, TmpComponent, +}; #[derive(Default)] pub struct Row<'a, Message> { @@ -18,23 +21,35 @@ impl<'a, Message> Row<'a, Message> { } } - pub fn with_child(mut self) -> Self { + pub fn with_child<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message>>, + { + self.children.push(FlexElement::with_no_flex(child.into())); self } - pub fn with_flex_child(mut self) -> Self { + pub fn with_flex_child<E>(mut self, child: E, flex: u16) -> Self + where + E: Into<Element<'a, Message>>, + { + self.children + .push(FlexElement::with_flex(child.into(), flex)); self } } impl<'a, Message> TmpComponent<Message> for Row<'a, Message> { - fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>) where B: Backend, { - self.children.iter_mut().for_each(|child| { - child.draw(area, frame); - }) + self.children + .iter_mut() + .zip(context.children()) + .for_each(|(child, child_node)| { + child.draw(child_node, frame); + }); } fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status { @@ -43,17 +58,75 @@ impl<'a, Message> TmpComponent<Message> for Row<'a, Message> { fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { let mut remaining_bounds = bounds; + let mut children = vec![LayoutNode::default(); self.children.len()]; + let mut inflexible_children_indexes = vec![]; + let mut offsets = vec![]; + let mut current_x = 0; + let mut current_y = 0; + let mut sizes = Vec::with_capacity(self.children.len()); + let mut current_size = Size::default(); - let child_nodes: Vec<LayoutNode> = self - .children + let mut get_child_size = |child: &FlexElement<'_, Message>, + child_node: &mut LayoutNode, + remaining_bounds: &mut Bounds| { + let size = child.layout(*remaining_bounds, child_node); + current_size += size; + remaining_bounds.shrink_size(size); + offsets.push((current_x, current_y)); + current_x += size.width; + current_y += size.height; + + size + }; + + // We handle inflexible children first, then distribute all remaining + // space to flexible children. + self.children .iter() - .map(|child| { - let mut child_node = LayoutNode::default(); - let size = child.layout(remaining_bounds, &mut child_node); - child_node - }) - .collect(); - - todo!() + .zip(children.iter_mut()) + .enumerate() + .for_each(|(index, (child, child_node))| { + if child.flex == 0 && remaining_bounds.has_space() { + let size = get_child_size(child, child_node, &mut remaining_bounds); + sizes.push(size); + } else { + inflexible_children_indexes.push(index); + sizes.push(Size::default()); + } + }); + + inflexible_children_indexes.into_iter().for_each(|index| { + // The index accesses are safe by above definitions, so we can use unsafe operations. + // If you EVER make changes to above, ensure this invariant still holds! + let child = unsafe { self.children.get_unchecked(index) }; + let child_node = unsafe { children.get_unchecked_mut(index) }; + let size = unsafe { sizes.get_unchecked_mut(index) }; + + *size = get_child_size(child, child_node, &mut remaining_bounds); + }); + + // If there is still remaining space after, distribute the rest if + // appropriate (e.x. current_size is too small for the bounds). + if current_size.width < bounds.min_width { + // For now, we'll cheat and just set it to be equal. + current_size.width = bounds.min_width; + } + if current_size.height < bounds.min_height { + // For now, we'll cheat and just set it to be equal. + current_size.height = bounds.min_height; + } + + // Now that we're done determining sizes, convert all children into the appropriate + // layout nodes. Remember - parents determine children, and so, we determine + // children here! + izip!(sizes, offsets, children.iter_mut()).for_each( + |(size, offset, child): (Size, (u16, u16), &mut LayoutNode)| { + let rect = Rect::new(offset.0, offset.1, size.width, size.height); + child.rect = rect; + }, + ); + node.children = children; + + current_size } } diff --git a/src/tuice/component/base/shortcut.rs b/src/tuice/component/base/shortcut.rs index b17ac0dd..661c45d7 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::{Event, Status, TmpComponent}; +use crate::tuice::{DrawContext, Event, Status, TmpComponent}; /// A [`Component`] to handle keyboard shortcuts and assign actions to them. /// @@ -8,7 +8,7 @@ use crate::tuice::{Event, Status, TmpComponent}; pub struct Shortcut {} impl<Message> TmpComponent<Message> for Shortcut { - fn draw<B>(&mut self, _area: Rect, _frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>) where B: Backend, { diff --git a/src/tuice/component/base/text_table.rs b/src/tuice/component/base/text_table.rs index 03c45e49..b6d04a77 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::{Event, Status, TmpComponent}, + tuice::{DrawContext, Event, Status, TmpComponent}, }; pub use self::table_column::{TextColumn, TextColumnConstraint}; @@ -166,12 +166,14 @@ impl<'a, Message> TextTable<'a, Message> { } impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> { - fn draw<B>(&mut self, area: Rect, frame: &mut Frame<'_, B>) + fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>) where B: Backend, { + let rect = context.rect(); + self.table_gap = if !self.show_gap - || (self.rows.len() + 2 > area.height.into() && area.height < TABLE_GAP_HEIGHT_LIMIT) + || (self.rows.len() + 2 > rect.height.into() && rect.height < TABLE_GAP_HEIGHT_LIMIT) { 0 } else { @@ -179,8 +181,8 @@ impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> { }; let table_extras = 1 + self.table_gap; - let scrollable_height = area.height.saturating_sub(table_extras); - self.update_column_widths(area); + let scrollable_height = rect.height.saturating_sub(table_extras); + self.update_column_widths(rect); // Calculate widths first, since we need them later. let widths = self @@ -195,7 +197,7 @@ impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> { // 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); + .display_start_index(rect, scrollable_height as usize); let end = min(self.state.num_items(), start + scrollable_height as usize); self.rows[start..end].to_vec() @@ -214,7 +216,7 @@ impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> { table = table.highlight_style(self.style_sheet.selected_text); } - frame.render_stateful_widget(table.widths(&widths), area, self.state.tui_state()); + frame.render_stateful_widget(table.widths(&widths), rect, self.state.tui_state()); } fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status { diff --git a/src/tuice/context.rs b/src/tuice/context.rs new file mode 100644 index 00000000..43540e44 --- /dev/null +++ b/src/tuice/context.rs @@ -0,0 +1,31 @@ +use tui::layout::Rect; + +use super::LayoutNode; + +pub struct DrawContext<'a> { + current_node: &'a LayoutNode, + current_offset: (u16, u16), +} + +impl<'a> DrawContext<'_> { + pub(crate) fn new() {} + + pub(crate) fn rect(&self) -> Rect { + self.current_node.rect + } + + pub(crate) fn children(&self) -> impl Iterator<Item = DrawContext<'_>> { + let new_offset = ( + self.current_offset.0 + self.current_node.rect.x, + self.current_offset.1 + self.current_node.rect.y, + ); + + self.current_node + .children + .iter() + .map(move |layout_node| DrawContext { + current_node: layout_node, + current_offset: new_offset, + }) + } +} diff --git a/src/tuice/element.rs b/src/tuice/element.rs index 1148df98..f3e55dca 100644 --- a/src/tuice/element.rs +++ b/src/tuice/element.rs @@ -2,8 +2,8 @@ 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, + Block, Bounds, Carousel, Column, Container, DrawContext, Event, LayoutNode, Row, Shortcut, + Size, Status, TextTable, TmpComponent, }; /// An [`Element`] is an instantiated [`Component`]. diff --git a/src/tuice/layout/bounds.rs b/src/tuice/layout/bounds.rs index 15cf7dcc..581ef6c9 100644 --- a/src/tuice/layout/bounds.rs +++ b/src/tuice/layout/bounds.rs @@ -1,3 +1,5 @@ +use crate::tuice::Size; + /// [`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) @@ -18,12 +20,23 @@ pub struct Bounds { } 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, - } + /// Shrinks the current bounds by some amount. + pub fn shrink(&mut self, width: u16, height: u16) { + self.max_width = self.max_width.saturating_sub(width); + self.max_height = self.max_height.saturating_sub(height); + } + + /// Shrinks by a given [`Size`]. + pub fn shrink_size(&mut self, size: Size) { + self.max_width = self.max_width.saturating_sub(size.width); + self.max_height = self.max_height.saturating_sub(size.height); + } + + /// Returns whether there is any space left in this bound for laying out things. + pub fn has_space(&self) -> bool { + self.min_width > self.max_width + || self.min_height > self.max_height + || self.max_width == 0 + || self.max_height == 0 } } diff --git a/src/tuice/layout/build_layout.rs b/src/tuice/layout/build_layout.rs index 709eed49..241bfd9e 100644 --- a/src/tuice/layout/build_layout.rs +++ b/src/tuice/layout/build_layout.rs @@ -3,7 +3,7 @@ use tui::layout::Rect; use crate::tuice::{Bounds, Element, LayoutNode, TmpComponent}; pub fn build_layout_tree<Message>(area: Rect, root: &Element<'_, Message>) -> LayoutNode { - let mut root_layout_node = LayoutNode::from_area(area); + let mut root_layout_node = LayoutNode::from_rect(area); let bounds = Bounds { min_width: 0, min_height: 0, diff --git a/src/tuice/layout/layout_node.rs b/src/tuice/layout/layout_node.rs index e6a5ba61..979cd6f5 100644 --- a/src/tuice/layout/layout_node.rs +++ b/src/tuice/layout/layout_node.rs @@ -1,15 +1,15 @@ use tui::layout::Rect; -#[derive(Default)] +#[derive(Clone, Default)] pub struct LayoutNode { - pub area: Rect, + pub rect: Rect, pub children: Vec<LayoutNode>, } impl LayoutNode { - pub fn from_area(area: Rect) -> Self { + pub fn from_rect(rect: Rect) -> Self { Self { - area, + rect, children: vec![], } } diff --git a/src/tuice/layout/size.rs b/src/tuice/layout/size.rs index 209cd8f7..0dd95040 100644 --- a/src/tuice/layout/size.rs +++ b/src/tuice/layout/size.rs @@ -1,11 +1,34 @@ +use std::ops::{Add, AddAssign}; + /// 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. +#[derive(Clone, Copy, Default)] pub struct Size { - /// The given width. + /// The width that the component has determined. pub width: u16, - /// The given height. + /// The height that the component has determined. pub height: u16, } + +impl Add for Size { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + width: self.width + rhs.width, + height: self.height + rhs.height, + } + } +} + +impl AddAssign for Size { + fn add_assign(&mut self, rhs: Self) { + *self = Self { + width: self.width + rhs.width, + height: self.height + rhs.height, + } + } +} diff --git a/src/tuice/mod.rs b/src/tuice/mod.rs index 3a33cc60..aeb40e1d 100644 --- a/src/tuice/mod.rs +++ b/src/tuice/mod.rs @@ -17,3 +17,6 @@ pub use layout::*; pub mod element; pub use element::*; + +pub mod context; +pub use context::*; diff --git a/src/tuice/runtime.rs b/src/tuice/runtime.rs index 5c22697e..e5d1048b 100644 --- a/src/tuice/runtime.rs +++ b/src/tuice/runtime.rs @@ -24,8 +24,8 @@ pub(crate) fn launch<A: Application + 'static>( RuntimeEvent::UserInterface(event) => { let mut messages = vec![]; - let bounds = Rect::default(); // TODO: TEMP - match user_interface.on_event(bounds, event, &mut messages) { + let rect = Rect::default(); // FIXME: TEMP + match user_interface.on_event(rect, event, &mut messages) { Status::Captured => {} Status::Ignored => { application.global_event_handler(event, &mut messages); @@ -38,6 +38,7 @@ pub(crate) fn launch<A: Application + 'static>( } user_interface = application.view(); + // FIXME: Draw! } RuntimeEvent::Custom(message) => { application.update(message); |