mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-04-08 17:05:59 +02:00
hmm
This commit is contained in:
parent
f1ec2fd70f
commit
bf81a389b8
@ -43,8 +43,15 @@ pub struct FinalWidget {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
pub enum LayoutRule {
|
||||
/// Let the child decide how big to make the current node.
|
||||
Child,
|
||||
|
||||
/// Expand to whatever space is left; the `ratio` determines how
|
||||
/// much space to take if there are more than one
|
||||
/// [`LayoutRule::Expand`] component.
|
||||
Expand { ratio: u32 },
|
||||
|
||||
/// Take up exactly `length` space if possible.
|
||||
Length { length: u16 },
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
pub mod base;
|
||||
pub use base::*;
|
||||
|
||||
pub mod widget;
|
||||
pub use widget::*;
|
||||
|
||||
use tui::{layout::Rect, Frame};
|
||||
|
||||
use super::{Event, Status};
|
||||
use super::{Bounds, Context, Event, LayoutNode, Size, Status};
|
||||
|
||||
/// A component displays information and can be interacted with.
|
||||
#[allow(unused_variables)]
|
||||
@ -11,14 +14,23 @@ pub trait Component<Message, Backend>
|
||||
where
|
||||
Backend: tui::backend::Backend,
|
||||
{
|
||||
/// Handles an [`Event`]. Defaults to just ignoring the event.
|
||||
fn on_event(&mut self, bounds: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
||||
/// Draws the component.
|
||||
fn draw(&mut self, area: Rect, context: &Context, frame: &mut Frame<'_, Backend>);
|
||||
|
||||
/// How a component should react to an [`Event`].
|
||||
///
|
||||
/// Defaults to just ignoring the event.
|
||||
fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
||||
Status::Ignored
|
||||
}
|
||||
|
||||
/// Returns the desired layout of the component. Defaults to returning
|
||||
fn layout(&self) {}
|
||||
|
||||
/// Draws the component.
|
||||
fn draw(&mut self, bounds: Rect, frame: &mut Frame<'_, Backend>);
|
||||
/// How a component should size itself and its children, given some [`Bounds`].
|
||||
///
|
||||
/// Defaults to returning a [`Size`] that fills up the bounds given.
|
||||
fn layout(&self, bounds: Bounds) -> Size {
|
||||
Size {
|
||||
width: bounds.max_width,
|
||||
height: bounds.max_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
use crate::tuice::{Component, Event, Status};
|
||||
use crate::tuice::{Component, Context, Event, Status};
|
||||
|
||||
pub struct Block {}
|
||||
|
||||
@ -8,11 +8,11 @@ impl<Message, B> Component<Message, B> for Block
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) {
|
||||
fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
Status::Ignored
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
use crate::tuice::{Component, Event, Status};
|
||||
use crate::tuice::{Component, Context, Event, Status};
|
||||
|
||||
pub struct Carousel {}
|
||||
|
||||
@ -8,11 +8,11 @@ impl<Message, B> Component<Message, B> for Carousel
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) {
|
||||
fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
Status::Ignored
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
use crate::tuice::{Component, Event, Status};
|
||||
use crate::tuice::{Component, Context, Event, Status};
|
||||
|
||||
pub struct Column {}
|
||||
|
||||
@ -8,11 +8,11 @@ impl<Message, B> Component<Message, B> for Column
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) {
|
||||
fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
Status::Ignored
|
||||
}
|
||||
}
|
||||
|
68
src/tuice/component/base/container.rs
Normal file
68
src/tuice/component/base/container.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
use crate::tuice::{Bounds, Component, Context, Event, LayoutNode, Length, Size, Status};
|
||||
|
||||
pub struct Container<'a, Message, B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
width: Length,
|
||||
height: Length,
|
||||
child: Box<dyn Component<Message, B> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, B> Container<'a, Message, B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
pub fn new(child: Box<dyn Component<Message, B> + 'a>) -> Self {
|
||||
Self {
|
||||
width: Length::Flex,
|
||||
height: Length::Flex,
|
||||
child,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, B> Component<Message, B> for Container<'a, Message, B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn layout(&self, bounds: Bounds) -> Size {
|
||||
let width = match self.width {
|
||||
Length::Flex => {
|
||||
todo!()
|
||||
}
|
||||
Length::FlexRatio(ratio) => {
|
||||
todo!()
|
||||
}
|
||||
Length::Fixed(length) => length.clamp(bounds.min_width, bounds.max_width),
|
||||
Length::Child => {
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
|
||||
let height = match self.height {
|
||||
Length::Flex => {
|
||||
todo!()
|
||||
}
|
||||
Length::FlexRatio(ratio) => {
|
||||
todo!()
|
||||
}
|
||||
Length::Fixed(length) => length.clamp(bounds.min_height, bounds.max_height),
|
||||
Length::Child => {
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
|
||||
Size { height, width }
|
||||
}
|
||||
}
|
@ -15,3 +15,9 @@ pub use block::Block;
|
||||
|
||||
pub mod carousel;
|
||||
pub use carousel::Carousel;
|
||||
|
||||
pub mod sized_box;
|
||||
pub use sized_box::SizedBox;
|
||||
|
||||
pub mod container;
|
||||
pub use container::Container;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
use crate::tuice::{Component, Event, Status};
|
||||
use crate::tuice::{Bounds, Component, Context, Event, Size, Status};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Row<'a, Message, B>
|
||||
@ -29,14 +29,14 @@ impl<'a, Message, B> Component<Message, B> for Row<'a, Message, B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, bounds: Rect, frame: &mut Frame<'_, B>) {
|
||||
fn draw(&mut self, area: Rect, context: &Context, frame: &mut Frame<'_, B>) {
|
||||
self.children.iter_mut().for_each(|child| {
|
||||
// TODO: This is just temp! We need layout!
|
||||
child.draw(bounds, frame);
|
||||
child.draw(area, context, frame);
|
||||
})
|
||||
}
|
||||
|
||||
fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
Status::Ignored
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use tui::{backend::Backend, layout::Rect, Frame};
|
||||
|
||||
use crate::tuice::{Component, Event, Status};
|
||||
use crate::tuice::{Component, Context, Event, Status};
|
||||
|
||||
/// A [`Component`] to handle keyboard shortcuts and assign actions to them.
|
||||
///
|
||||
@ -11,11 +11,11 @@ impl<Message, B> Component<Message, B> for Shortcut
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, _bounds: Rect, _frame: &mut Frame<'_, B>) {
|
||||
fn draw(&mut self, _area: Rect, _context: &Context, _frame: &mut Frame<'_, B>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_event(&mut self, _bounds: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
||||
Status::Ignored
|
||||
}
|
||||
}
|
||||
|
59
src/tuice/component/base/sized_box.rs
Normal file
59
src/tuice/component/base/sized_box.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use tui::backend::Backend;
|
||||
|
||||
use crate::tuice::{Component, Length};
|
||||
|
||||
pub struct SizedBox<'a, Message, B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
width: Length,
|
||||
height: Length,
|
||||
child: Box<dyn Component<Message, B> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, B> SizedBox<'a, Message, B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
/// Creates a new [`SizedBox`] for a child component
|
||||
/// with a [`Length::Flex`] width and height.
|
||||
pub fn new(child: Box<dyn Component<Message, B> + 'a>) -> Self {
|
||||
Self {
|
||||
width: Length::Flex,
|
||||
height: Length::Flex,
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`SizedBox`] for a child component
|
||||
/// with a [`Length::Flex`] height.
|
||||
pub fn with_width(child: Box<dyn Component<Message, B> + 'a>, width: Length) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height: Length::Flex,
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`SizedBox`] for a child component
|
||||
/// with a [`Length::Flex`] width.
|
||||
pub fn with_height(child: Box<dyn Component<Message, B> + 'a>, height: Length) -> Self {
|
||||
Self {
|
||||
width: Length::Flex,
|
||||
height,
|
||||
child,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`SizedBox`].
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`SizedBox`].
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{
|
||||
constants::TABLE_GAP_HEIGHT_LIMIT,
|
||||
tuice::{Component, Event, Status},
|
||||
tuice::{Component, Context, Event, Status},
|
||||
};
|
||||
|
||||
pub use self::table_column::{TextColumn, TextColumnConstraint};
|
||||
@ -179,7 +179,55 @@ impl<'a, Message, B> Component<Message, B> for TextTable<'a, Message>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn on_event(&mut self, bounds: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
||||
fn draw(&mut self, area: Rect, context: &Context, frame: &mut Frame<'_, B>) {
|
||||
self.table_gap = if !self.show_gap
|
||||
|| (self.rows.len() + 2 > area.height.into() && area.height < TABLE_GAP_HEIGHT_LIMIT)
|
||||
{
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let table_extras = 1 + self.table_gap;
|
||||
let scrollable_height = area.height.saturating_sub(table_extras);
|
||||
self.update_column_widths(area);
|
||||
|
||||
// Calculate widths first, since we need them later.
|
||||
let widths = self
|
||||
.column_widths
|
||||
.iter()
|
||||
.map(|column| Constraint::Length(*column))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Then calculate rows. We truncate the amount of data read based on height,
|
||||
// as well as truncating some entries based on available width.
|
||||
let data_slice = {
|
||||
// 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);
|
||||
let end = min(self.state.num_items(), start + scrollable_height as usize);
|
||||
|
||||
self.rows[start..end].to_vec()
|
||||
};
|
||||
|
||||
// Now build up our headers...
|
||||
let header = Row::new(self.columns.iter().map(|column| column.name.clone()))
|
||||
.style(self.style_sheet.table_header)
|
||||
.bottom_margin(self.table_gap);
|
||||
|
||||
let mut table = Table::new(data_slice)
|
||||
.header(header)
|
||||
.style(self.style_sheet.text);
|
||||
|
||||
if self.show_selected_entry {
|
||||
table = table.highlight_style(self.style_sheet.selected_text);
|
||||
}
|
||||
|
||||
frame.render_stateful_widget(table.widths(&widths), area, self.state.tui_state());
|
||||
}
|
||||
|
||||
fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
||||
use crate::tuice::MouseBoundIntersect;
|
||||
use crossterm::event::{MouseButton, MouseEventKind};
|
||||
|
||||
@ -194,10 +242,10 @@ where
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse_event) => {
|
||||
if mouse_event.does_mouse_intersect_bounds(bounds) {
|
||||
if mouse_event.does_mouse_intersect_bounds(area) {
|
||||
match mouse_event.kind {
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
let y = mouse_event.row - bounds.top();
|
||||
let y = mouse_event.row - area.top();
|
||||
|
||||
if self.sortable && y == 0 {
|
||||
todo!()
|
||||
@ -230,55 +278,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, bounds: Rect, frame: &mut Frame<'_, B>) {
|
||||
self.table_gap = if !self.show_gap
|
||||
|| (self.rows.len() + 2 > bounds.height.into()
|
||||
&& bounds.height < TABLE_GAP_HEIGHT_LIMIT)
|
||||
{
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let table_extras = 1 + self.table_gap;
|
||||
let scrollable_height = bounds.height.saturating_sub(table_extras);
|
||||
self.update_column_widths(bounds);
|
||||
|
||||
// Calculate widths first, since we need them later.
|
||||
let widths = self
|
||||
.column_widths
|
||||
.iter()
|
||||
.map(|column| Constraint::Length(*column))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Then calculate rows. We truncate the amount of data read based on height,
|
||||
// as well as truncating some entries based on available width.
|
||||
let data_slice = {
|
||||
// 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(bounds, scrollable_height as usize);
|
||||
let end = min(self.state.num_items(), start + scrollable_height as usize);
|
||||
|
||||
self.rows[start..end].to_vec()
|
||||
};
|
||||
|
||||
// Now build up our headers...
|
||||
let header = Row::new(self.columns.iter().map(|column| column.name.clone()))
|
||||
.style(self.style_sheet.table_header)
|
||||
.bottom_margin(self.table_gap);
|
||||
|
||||
let mut table = Table::new(data_slice)
|
||||
.header(header)
|
||||
.style(self.style_sheet.text);
|
||||
|
||||
if self.show_selected_entry {
|
||||
table = table.highlight_style(self.style_sheet.selected_text);
|
||||
}
|
||||
|
||||
frame.render_stateful_widget(table.widths(&widths), bounds, self.state.tui_state());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
0
src/tuice/component/widget/mod.rs
Normal file
0
src/tuice/component/widget/mod.rs
Normal file
6
src/tuice/context.rs
Normal file
6
src/tuice/context.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use crate::app::layout_manager::LayoutNode;
|
||||
|
||||
/// Internal management for drawing and the like.
|
||||
pub struct Context {
|
||||
layout_root: LayoutNode,
|
||||
}
|
17
src/tuice/layout/bounds.rs
Normal file
17
src/tuice/layout/bounds.rs
Normal file
@ -0,0 +1,17 @@
|
||||
/// [`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)
|
||||
/// of a child, which is passed back up to the parent.
|
||||
pub struct Bounds {
|
||||
/// The minimal width available.
|
||||
pub min_width: u16,
|
||||
|
||||
/// The minimal height available.
|
||||
pub min_height: u16,
|
||||
|
||||
/// The maximal width available.
|
||||
pub max_width: u16,
|
||||
|
||||
/// The maximal height available.
|
||||
pub max_height: u16,
|
||||
}
|
12
src/tuice/layout/layout_node.rs
Normal file
12
src/tuice/layout/layout_node.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use tui::layout::Rect;
|
||||
|
||||
/// A node for the layout tree.
|
||||
pub enum LayoutNode {
|
||||
Leaf {
|
||||
area: Rect,
|
||||
},
|
||||
Branch {
|
||||
area: Rect,
|
||||
children: Vec<LayoutNode>,
|
||||
},
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/// Which strategy to use while laying out widgets.
|
||||
/// Which strategy to use while laying out things.
|
||||
pub enum Length {
|
||||
/// Fill in remaining space. Equivalent to `Length::FlexRatio(1)`.
|
||||
Flex,
|
||||
@ -8,4 +8,7 @@ pub enum Length {
|
||||
|
||||
/// Fill in a fixed amount of space.
|
||||
Fixed(u16),
|
||||
|
||||
/// Let the child determine how large to make the component.
|
||||
Child,
|
||||
}
|
||||
|
@ -1,2 +1,15 @@
|
||||
pub mod length;
|
||||
pub use length::Length;
|
||||
|
||||
pub mod bounds;
|
||||
pub use bounds::Bounds;
|
||||
|
||||
pub mod size;
|
||||
pub use size::Size;
|
||||
|
||||
pub mod layout_node;
|
||||
pub use layout_node::LayoutNode;
|
||||
|
||||
pub fn build_layout() -> LayoutNode {
|
||||
todo!()
|
||||
}
|
||||
|
11
src/tuice/layout/size.rs
Normal file
11
src/tuice/layout/size.rs
Normal file
@ -0,0 +1,11 @@
|
||||
/// 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.
|
||||
pub struct Size {
|
||||
/// The given width.
|
||||
pub width: u16,
|
||||
|
||||
/// The given height.
|
||||
pub height: u16,
|
||||
}
|
@ -14,3 +14,6 @@ pub use runtime::RuntimeEvent;
|
||||
|
||||
pub mod layout;
|
||||
pub use layout::*;
|
||||
|
||||
pub mod context;
|
||||
pub use context::*;
|
||||
|
Loading…
x
Reference in New Issue
Block a user