mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-26 23:24:20 +02:00
tuice tuice tuice tuice
This commit is contained in:
parent
b6b0493333
commit
58f78731b3
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
.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());
|
||||
}
|
||||
});
|
||||
|
||||
todo!()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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 {
|
||||
|
31
src/tuice/context.rs
Normal file
31
src/tuice/context.rs
Normal file
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
@ -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`].
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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![],
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,3 +17,6 @@ pub use layout::*;
|
||||
|
||||
pub mod element;
|
||||
pub use element::*;
|
||||
|
||||
pub mod context;
|
||||
pub use context::*;
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user