summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-12-12 22:52:48 -0500
committerClementTsang <cjhtsang@uwaterloo.ca>2021-12-12 22:52:48 -0500
commit86edfed892dbd0e4dc7eaae1f7ea56ac396bc4e5 (patch)
treed173010f5dd7728b0dede3bfc0a0c94f2cd608dd
parent37b3d235b998cebf864fa31d1f5ee533cf1cc6e7 (diff)
Fix bug with flex generation, unify flex
-rw-r--r--src/app.rs10
-rw-r--r--src/tuice/component/base/column.rs18
-rw-r--r--src/tuice/component/base/flex.rs192
-rw-r--r--src/tuice/component/base/flex/flex_element.rs88
-rw-r--r--src/tuice/component/base/mod.rs10
-rw-r--r--src/tuice/component/base/row.rs132
-rw-r--r--src/tuice/context.rs6
-rw-r--r--src/tuice/element.rs7
-rw-r--r--src/tuice/layout/length.rs14
-rw-r--r--src/tuice/layout/mod.rs3
10 files changed, 269 insertions, 211 deletions
diff --git a/src/app.rs b/src/app.rs
index 851b2b56..3554bef5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -29,7 +29,7 @@ use frozen_state::FrozenState;
use crate::{
canvas::Painter,
constants,
- tuice::{Application, Element, Row},
+ tuice::{Application, Element, Flex},
units::data_units::DataUnit,
Pid,
};
@@ -235,8 +235,14 @@ impl Application for AppState {
}
fn view(&mut self) -> Element<'static, Self::Message> {
+ use crate::tuice::FlexElement;
use crate::tuice::TextTable;
- Row::with_children(vec![Element::from(TextTable::new(vec!["A", "B", "C"]))]).into()
+
+ Flex::row_with_children(vec![
+ FlexElement::new(TextTable::new(vec!["A", "B", "C"])),
+ FlexElement::new(TextTable::new(vec!["D", "E", "F"])),
+ ])
+ .into()
}
fn destroy(&mut self) {
diff --git a/src/tuice/component/base/column.rs b/src/tuice/component/base/column.rs
deleted file mode 100644
index 0c60d246..00000000
--- a/src/tuice/component/base/column.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use tui::{backend::Backend, layout::Rect, Frame};
-
-use crate::tuice::{DrawContext, Event, Status, TmpComponent};
-
-pub struct Column {}
-
-impl<Message> TmpComponent<Message> for Column {
- fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>)
- where
- B: Backend,
- {
- todo!()
- }
-
- fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
- Status::Ignored
- }
-}
diff --git a/src/tuice/component/base/flex.rs b/src/tuice/component/base/flex.rs
index fc7c8844..9520dc82 100644
--- a/src/tuice/component/base/flex.rs
+++ b/src/tuice/component/base/flex.rs
@@ -1,60 +1,194 @@
+use itertools::izip;
use tui::{backend::Backend, layout::Rect, Frame};
+pub mod flex_element;
+pub use flex_element::FlexElement;
+
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.
- pub flex: u16,
- element: Element<'a, Message>,
+#[derive(Clone, Copy, Debug)]
+pub enum Axis {
+ /// Represents the x-axis.
+ Horizontal,
+
+ /// Represents the y-axis.
+ Vertical,
}
-impl<'a, Message> FlexElement<'a, Message> {
- pub fn new<I: Into<Element<'a, Message>>>(element: I) -> Self {
+pub struct Flex<'a, Message> {
+ children: Vec<FlexElement<'a, Message>>,
+ alignment: Axis,
+}
+
+impl<'a, Message> Flex<'a, Message> {
+ pub fn new(alignment: Axis) -> Self {
Self {
- flex: 1,
- element: element.into(),
+ children: vec![],
+ alignment,
}
}
- pub fn with_flex<I: Into<Element<'a, Message>>>(element: I, flex: u16) -> Self {
+ /// Creates a new [`Flex`] with a horizontal alignment.
+ pub fn row() -> Self {
Self {
- flex,
- element: element.into(),
+ children: vec![],
+ alignment: Axis::Horizontal,
}
}
- pub fn with_no_flex<I: Into<Element<'a, Message>>>(element: I) -> Self {
+ /// Creates a new [`Flex`] with a horizontal alignment with the given children.
+ pub fn row_with_children<C>(children: Vec<C>) -> Self
+ where
+ C: Into<FlexElement<'a, Message>>,
+ {
Self {
- flex: 0,
- element: element.into(),
+ children: children.into_iter().map(Into::into).collect(),
+ alignment: Axis::Horizontal,
}
}
- pub fn flex(mut self, flex: u16) -> Self {
- self.flex = flex;
- self
+ /// Creates a new [`Flex`] with a vertical alignment.
+ pub fn column() -> Self {
+ Self {
+ children: vec![],
+ alignment: Axis::Vertical,
+ }
}
- pub(crate) fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
+ /// Creates a new [`Flex`] with a vertical alignment with the given children.
+ pub fn column_with_children<C>(children: Vec<C>) -> Self
where
- B: Backend,
+ C: Into<FlexElement<'a, Message>>,
{
- self.element.draw(context, frame)
+ Self {
+ children: children.into_iter().map(Into::into).collect(),
+ alignment: Axis::Vertical,
+ }
}
- pub(crate) fn on_event(
- &mut self, area: Rect, event: Event, messages: &mut Vec<Message>,
- ) -> Status {
- self.element.on_event(area, event, messages)
+ 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(crate) fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
- self.element.layout(bounds, node)
+ 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> From<Element<'a, Message>> for FlexElement<'a, Message> {
- fn from(element: Element<'a, Message>) -> Self {
- Self { flex: 0, element }
+impl<'a, Message> TmpComponent<Message> for Flex<'a, Message> {
+ fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
+ where
+ B: Backend,
+ {
+ 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 {
+ // for child in self.children.iter_mut() {
+ // if let Status::Captured = child.on_event() {
+ // return Status::Captured;
+ // }
+ // }
+
+ Status::Ignored
+ }
+
+ 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 flexible_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 mut total_flex = 0;
+
+ // Our general approach is to first handle inflexible children first,
+ // then distribute all remaining space to flexible children.
+ self.children
+ .iter()
+ .zip(children.iter_mut())
+ .enumerate()
+ .for_each(|(index, (child, child_node))| {
+ if child.flex == 0 && remaining_bounds.has_space() {
+ let size = child.child_layout(remaining_bounds, child_node);
+ current_size += size;
+ remaining_bounds.shrink_size(size);
+ offsets.push((current_x, current_y));
+
+ match self.alignment {
+ Axis::Horizontal => {
+ current_x += size.width;
+ }
+ Axis::Vertical => {
+ current_y += size.height;
+ }
+ }
+
+ sizes.push(size);
+ } else {
+ total_flex += child.flex;
+ flexible_children_indexes.push(index);
+ sizes.push(Size::default());
+ }
+ });
+
+ flexible_children_indexes.into_iter().for_each(|index| {
+ // The index accesses are assumed to be safe by above definitions.
+ // This means that we can use the unsafe operations below.
+ //
+ // NB: If you **EVER** make changes in this function, ensure these assumptions
+ // still hold!
+ 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) };
+
+ let new_size =
+ child.ratio_layout(remaining_bounds, total_flex, child_node, self.alignment);
+ current_size += new_size;
+ offsets.push((current_x, current_y));
+ current_x += new_size.width;
+
+ *size = new_size;
+ });
+
+ // 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/flex/flex_element.rs b/src/tuice/component/base/flex/flex_element.rs
new file mode 100644
index 00000000..43982f56
--- /dev/null
+++ b/src/tuice/component/base/flex/flex_element.rs
@@ -0,0 +1,88 @@
+use tui::{backend::Backend, layout::Rect, Frame};
+
+use crate::tuice::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent};
+
+use super::Axis;
+
+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, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
+ where
+ B: Backend,
+ {
+ self.element.draw(context, frame)
+ }
+
+ pub(crate) fn on_event(
+ &mut self, area: Rect, event: Event, messages: &mut Vec<Message>,
+ ) -> Status {
+ self.element.on_event(area, event, messages)
+ }
+
+ /// Assumes the flex is 0. Just calls layout on its child.
+ pub(crate) fn child_layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
+ self.element.layout(bounds, node)
+ }
+
+ /// Assumes the flex is NOT 0. Will call layout on its children, but will ignore
+ /// its sizing.
+ ///
+ /// **Note it does NOT check for div by zero!** Please check this yourself.
+ pub(crate) fn ratio_layout(
+ &self, bounds: Bounds, total_flex: u16, node: &mut LayoutNode, parent_alignment: Axis,
+ ) -> Size {
+ let (width, height) = match parent_alignment {
+ Axis::Horizontal => (bounds.max_width * self.flex / total_flex, bounds.max_height),
+ Axis::Vertical => (bounds.max_width, bounds.max_height * self.flex / total_flex),
+ };
+
+ self.element.layout(
+ Bounds {
+ min_width: width,
+ min_height: height,
+ max_width: width,
+ max_height: height,
+ },
+ node,
+ );
+
+ Size { width, height }
+ }
+}
+
+impl<'a, Message> From<Element<'a, Message>> for FlexElement<'a, Message> {
+ fn from(element: Element<'a, Message>) -> Self {
+ Self { flex: 0, element }
+ }
+}
diff --git a/src/tuice/component/base/mod.rs b/src/tuice/component/base/mod.rs
index 237621a5..7e7932c4 100644
--- a/src/tuice/component/base/mod.rs
+++ b/src/tuice/component/base/mod.rs
@@ -4,11 +4,8 @@ pub use text_table::{TextColumn, TextColumnConstraint, TextTable};
pub mod shortcut;
pub use shortcut::Shortcut;
-pub mod row;
-pub use row::Row;
-
-pub mod column;
-pub use column::Column;
+pub mod flex;
+pub use flex::{Axis, Flex, FlexElement};
pub mod block;
pub use block::Block;
@@ -18,6 +15,3 @@ pub use carousel::Carousel;
pub mod container;
pub use container::Container;
-
-pub mod flex;
-pub use flex::*;
diff --git a/src/tuice/component/base/row.rs b/src/tuice/component/base/row.rs
deleted file mode 100644
index 75c5177c..00000000
--- a/src/tuice/component/base/row.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-use itertools::izip;
-use tui::{backend::Backend, layout::Rect, Frame};
-
-use crate::tuice::{
- Bounds, DrawContext, Element, Event, FlexElement, LayoutNode, Size, Status, TmpComponent,
-};
-
-#[derive(Default)]
-pub struct Row<'a, Message> {
- children: Vec<FlexElement<'a, Message>>,
-}
-
-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<FlexElement<'a, Message>>,
- {
- Self {
- children: children.into_iter().map(Into::into).collect(),
- }
- }
-
- 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<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, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
- where
- B: Backend,
- {
- 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 {
- Status::Ignored
- }
-
- 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 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()
- .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/context.rs b/src/tuice/context.rs
index f40742e3..0de909cb 100644
--- a/src/tuice/context.rs
+++ b/src/tuice/context.rs
@@ -17,7 +17,11 @@ impl<'a> DrawContext<'_> {
}
pub(crate) fn rect(&self) -> Rect {
- self.current_node.rect
+ let mut rect = self.current_node.rect;
+ rect.x += self.current_offset.0;
+ rect.y += self.current_offset.1;
+
+ rect
}
pub(crate) fn children(&self) -> impl Iterator<Item = DrawContext<'_>> {
diff --git a/src/tuice/element.rs b/src/tuice/element.rs
index f3e55dca..fe859e79 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, DrawContext, Event, LayoutNode, Row, Shortcut,
- Size, Status, TextTable, TmpComponent,
+ Block, Bounds, Carousel, Container, DrawContext, Event, Flex, LayoutNode, Shortcut, Size,
+ Status, TextTable, TmpComponent,
};
/// An [`Element`] is an instantiated [`Component`].
@@ -11,9 +11,8 @@ use super::{
pub enum Element<'a, Message> {
Block,
Carousel,
- Column,
Container(Container<'a, Message>),
- Row(Row<'a, Message>),
+ Flex(Flex<'a, Message>),
Shortcut,
TextTable(TextTable<'a, Message>),
}
diff --git a/src/tuice/layout/length.rs b/src/tuice/layout/length.rs
deleted file mode 100644
index eb63a895..00000000
--- a/src/tuice/layout/length.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-/// Which strategy to use while laying out things.
-pub enum Length {
- /// Fill in remaining space. Equivalent to `Length::FlexRatio(1)`.
- Flex,
-
- /// Fill in remaining space, with the value being a ratio.
- FlexRatio(u16),
-
- /// 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 631694b0..fd52cab8 100644
--- a/src/tuice/layout/mod.rs
+++ b/src/tuice/layout/mod.rs
@@ -1,6 +1,3 @@
-pub mod length;
-pub use length::Length;
-
pub mod bounds;
pub use bounds::Bounds;