summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-12-12 20:29:12 -0500
committerClementTsang <cjhtsang@uwaterloo.ca>2021-12-12 20:29:34 -0500
commit58f78731b33242a576939c9b6a5ea9db9504bee2 (patch)
tree9fabdd7b9c4a0f9c31dbb56edff2da98894ff7f1
parentb6b0493333bc8a2154aedab9ec52c59e9a9393cc (diff)
tuice tuice tuice tuice
-rw-r--r--src/app.rs4
-rw-r--r--src/tuice/component.rs4
-rw-r--r--src/tuice/component/base/block.rs4
-rw-r--r--src/tuice/component/base/carousel.rs4
-rw-r--r--src/tuice/component/base/column.rs4
-rw-r--r--src/tuice/component/base/container.rs4
-rw-r--r--src/tuice/component/base/flex.rs6
-rw-r--r--src/tuice/component/base/row.rs107
-rw-r--r--src/tuice/component/base/shortcut.rs4
-rw-r--r--src/tuice/component/base/text_table.rs16
-rw-r--r--src/tuice/context.rs31
-rw-r--r--src/tuice/element.rs4
-rw-r--r--src/tuice/layout/bounds.rs27
-rw-r--r--src/tuice/layout/build_layout.rs2
-rw-r--r--src/tuice/layout/layout_node.rs8
-rw-r--r--src/tuice/layout/size.rs27
-rw-r--r--src/tuice/mod.rs3
-rw-r--r--src/tuice/runtime.rs5
18 files changed, 204 insertions, 60 deletions
diff --git a/src/app.rs b/src/app.rs
index 002a4f49..851b2b56 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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);