mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
Fix bug with flex generation, unify flex
This commit is contained in:
parent
37b3d235b9
commit
86edfed892
10
src/app.rs
10
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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
/// Creates a new [`Flex`] with a vertical alignment.
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
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(
|
||||
&mut self, area: Rect, event: Event, messages: &mut Vec<Message>,
|
||||
) -> Status {
|
||||
self.element.on_event(area, event, messages)
|
||||
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
|
||||
}
|
||||
|
||||
pub(crate) fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> Size {
|
||||
self.element.layout(bounds, node)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> From<Element<'a, Message>> for FlexElement<'a, Message> {
|
||||
fn from(element: Element<'a, Message>) -> Self {
|
||||
Self { flex: 0, element }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
88
src/tuice/component/base/flex/flex_element.rs
Normal file
88
src/tuice/component/base/flex/flex_element.rs
Normal 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 }
|
||||
}
|
||||
}
|
@ -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::*;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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<'_>> {
|
||||
|
@ -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>),
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
pub mod length;
|
||||
pub use length::Length;
|
||||
|
||||
pub mod bounds;
|
||||
pub use bounds::Bounds;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user