diff options
author | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-12 22:52:48 -0500 |
---|---|---|
committer | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-12-12 22:52:48 -0500 |
commit | 86edfed892dbd0e4dc7eaae1f7ea56ac396bc4e5 (patch) | |
tree | d173010f5dd7728b0dede3bfc0a0c94f2cd608dd | |
parent | 37b3d235b998cebf864fa31d1f5ee533cf1cc6e7 (diff) |
Fix bug with flex generation, unify flex
-rw-r--r-- | src/app.rs | 10 | ||||
-rw-r--r-- | src/tuice/component/base/column.rs | 18 | ||||
-rw-r--r-- | src/tuice/component/base/flex.rs | 192 | ||||
-rw-r--r-- | src/tuice/component/base/flex/flex_element.rs | 88 | ||||
-rw-r--r-- | src/tuice/component/base/mod.rs | 10 | ||||
-rw-r--r-- | src/tuice/component/base/row.rs | 132 | ||||
-rw-r--r-- | src/tuice/context.rs | 6 | ||||
-rw-r--r-- | src/tuice/element.rs | 7 | ||||
-rw-r--r-- | src/tuice/layout/length.rs | 14 | ||||
-rw-r--r-- | src/tuice/layout/mod.rs | 3 |
10 files changed, 269 insertions, 211 deletions
@@ -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; |