Fix bug with flex generation, unify flex

This commit is contained in:
ClementTsang 2021-12-12 22:52:48 -05:00
parent 37b3d235b9
commit 86edfed892
10 changed files with 270 additions and 212 deletions

View File

@ -29,7 +29,7 @@ use frozen_state::FrozenState;
use crate::{ use crate::{
canvas::Painter, canvas::Painter,
constants, constants,
tuice::{Application, Element, Row}, tuice::{Application, Element, Flex},
units::data_units::DataUnit, units::data_units::DataUnit,
Pid, Pid,
}; };
@ -235,8 +235,14 @@ impl Application for AppState {
} }
fn view(&mut self) -> Element<'static, Self::Message> { fn view(&mut self) -> Element<'static, Self::Message> {
use crate::tuice::FlexElement;
use crate::tuice::TextTable; 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) { fn destroy(&mut self) {

View File

@ -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
}
}

View File

@ -1,60 +1,194 @@
use itertools::izip;
use tui::{backend::Backend, layout::Rect, Frame}; 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}; use crate::tuice::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent};
pub struct FlexElement<'a, Message> { #[derive(Clone, Copy, Debug)]
/// Represents a ratio with other [`FlexElement`]s on how far to expand. pub enum Axis {
pub flex: u16, /// Represents the x-axis.
element: Element<'a, Message>, Horizontal,
/// Represents the y-axis.
Vertical,
} }
impl<'a, Message> FlexElement<'a, Message> { pub struct Flex<'a, Message> {
pub fn new<I: Into<Element<'a, Message>>>(element: I) -> Self { children: Vec<FlexElement<'a, Message>>,
alignment: Axis,
}
impl<'a, Message> Flex<'a, Message> {
pub fn new(alignment: Axis) -> Self {
Self { Self {
flex: 1, children: vec![],
element: element.into(), 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 { Self {
flex, children: vec![],
element: element.into(), 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 { Self {
flex: 0, children: children.into_iter().map(Into::into).collect(),
element: element.into(), alignment: Axis::Horizontal,
} }
} }
pub fn flex(mut self, flex: u16) -> Self { /// Creates a new [`Flex`] with a vertical alignment.
self.flex = flex; pub fn column() -> Self {
Self {
children: vec![],
alignment: Axis::Vertical,
}
}
/// Creates a new [`Flex`] with a vertical alignment with the given children.
pub fn column_with_children<C>(children: Vec<C>) -> Self
where
C: Into<FlexElement<'a, Message>>,
{
Self {
children: children.into_iter().map(Into::into).collect(),
alignment: Axis::Vertical,
}
}
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 self
} }
pub(crate) fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>) 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 Flex<'a, Message> {
fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
where where
B: Backend, B: Backend,
{ {
self.element.draw(context, frame) self.children
.iter_mut()
.zip(context.children())
.for_each(|(child, child_node)| {
child.draw(child_node, frame);
});
} }
pub(crate) fn on_event( fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
&mut self, area: Rect, event: Event, messages: &mut Vec<Message>, // for child in self.children.iter_mut() {
) -> Status { // if let Status::Captured = child.on_event() {
self.element.on_event(area, event, messages) // return Status::Captured;
// }
// }
Status::Ignored
} }
pub(crate) fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size { fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
self.element.layout(bounds, node) 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;
} }
} }
impl<'a, Message> From<Element<'a, Message>> for FlexElement<'a, Message> { sizes.push(size);
fn from(element: Element<'a, Message>) -> Self { } else {
Self { flex: 0, element } 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
} }
} }

View File

@ -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 }
}
}

View File

@ -4,11 +4,8 @@ pub use text_table::{TextColumn, TextColumnConstraint, TextTable};
pub mod shortcut; pub mod shortcut;
pub use shortcut::Shortcut; pub use shortcut::Shortcut;
pub mod row; pub mod flex;
pub use row::Row; pub use flex::{Axis, Flex, FlexElement};
pub mod column;
pub use column::Column;
pub mod block; pub mod block;
pub use block::Block; pub use block::Block;
@ -18,6 +15,3 @@ pub use carousel::Carousel;
pub mod container; pub mod container;
pub use container::Container; pub use container::Container;
pub mod flex;
pub use flex::*;

View File

@ -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
}
}

View File

@ -17,7 +17,11 @@ impl<'a> DrawContext<'_> {
} }
pub(crate) fn rect(&self) -> Rect { 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<'_>> { pub(crate) fn children(&self) -> impl Iterator<Item = DrawContext<'_>> {

View File

@ -2,8 +2,8 @@ use enum_dispatch::enum_dispatch;
use tui::{layout::Rect, Frame}; use tui::{layout::Rect, Frame};
use super::{ use super::{
Block, Bounds, Carousel, Column, Container, DrawContext, Event, LayoutNode, Row, Shortcut, Block, Bounds, Carousel, Container, DrawContext, Event, Flex, LayoutNode, Shortcut, Size,
Size, Status, TextTable, TmpComponent, Status, TextTable, TmpComponent,
}; };
/// An [`Element`] is an instantiated [`Component`]. /// An [`Element`] is an instantiated [`Component`].
@ -11,9 +11,8 @@ use super::{
pub enum Element<'a, Message> { pub enum Element<'a, Message> {
Block, Block,
Carousel, Carousel,
Column,
Container(Container<'a, Message>), Container(Container<'a, Message>),
Row(Row<'a, Message>), Flex(Flex<'a, Message>),
Shortcut, Shortcut,
TextTable(TextTable<'a, Message>), TextTable(TextTable<'a, Message>),
} }

View File

@ -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,
}

View File

@ -1,6 +1,3 @@
pub mod length;
pub use length::Length;
pub mod bounds; pub mod bounds;
pub use bounds::Bounds; pub use bounds::Bounds;