mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-25 22:55:06 +02:00
Simple blocks
This commit is contained in:
parent
8f790d0b75
commit
18bce9f0a0
@ -246,7 +246,7 @@ impl Application for AppState {
|
||||
Flex::column()
|
||||
.with_flex_child(
|
||||
Flex::row_with_children(vec![
|
||||
FlexElement::new(TempTable::new(ctx)),
|
||||
FlexElement::new(TempTable::build(ctx)),
|
||||
FlexElement::new(TextTable::build(
|
||||
ctx,
|
||||
TextTableProps::new(vec!["D", "E", "F"]),
|
||||
|
@ -1,23 +1,143 @@
|
||||
use tui::{backend::Backend, Frame};
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
use crate::tuine::{DrawContext, Event, StateContext, Status, TmpComponent};
|
||||
use tui::{backend::Backend, layout::Rect, style::Style, widgets::Borders, Frame};
|
||||
|
||||
pub struct Block {}
|
||||
use crate::tuine::{
|
||||
Bounds, DrawContext, Event, LayoutNode, Size, StateContext, Status, TmpComponent,
|
||||
};
|
||||
|
||||
impl<Message> TmpComponent<Message> for Block {
|
||||
/// A set of styles for a [`Block`].
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct StyleSheet {
|
||||
text: Style,
|
||||
border: Style,
|
||||
}
|
||||
|
||||
/// A [`Block`] is a widget that draws a border around a child [`Component`], as well as optional
|
||||
/// titles.
|
||||
pub struct Block<Message, Child>
|
||||
where
|
||||
Child: TmpComponent<Message>,
|
||||
{
|
||||
_pd: PhantomData<Message>,
|
||||
child: Option<Child>,
|
||||
borders: Borders,
|
||||
style_sheet: StyleSheet,
|
||||
left_text: Option<Cow<'static, str>>,
|
||||
right_text: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl<Message, Child> Block<Message, Child>
|
||||
where
|
||||
Child: TmpComponent<Message>,
|
||||
{
|
||||
pub fn with_child(child: Child) -> Self {
|
||||
Self {
|
||||
_pd: Default::default(),
|
||||
child: Some(child),
|
||||
borders: Borders::all(),
|
||||
style_sheet: Default::default(),
|
||||
left_text: None,
|
||||
right_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child(mut self, child: Option<Child>) -> Self {
|
||||
self.child = child;
|
||||
self
|
||||
}
|
||||
|
||||
fn inner_rect(&self, original: Rect) -> Rect {
|
||||
let mut inner = original;
|
||||
|
||||
if self.borders.intersects(Borders::LEFT) {
|
||||
inner.x = inner.x.saturating_add(1).min(inner.right());
|
||||
inner.width = inner.width.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::TOP)
|
||||
|| self.left_text.is_some()
|
||||
|| self.right_text.is_some()
|
||||
{
|
||||
inner.y = inner.y.saturating_add(1).min(inner.bottom());
|
||||
inner.height = inner.height.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::RIGHT) {
|
||||
inner.width = inner.width.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::BOTTOM) {
|
||||
inner.height = inner.height.saturating_sub(1);
|
||||
}
|
||||
inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Child> TmpComponent<Message> for Block<Message, Child>
|
||||
where
|
||||
Child: TmpComponent<Message>,
|
||||
{
|
||||
fn draw<B>(
|
||||
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: &DrawContext<'_>,
|
||||
_frame: &mut Frame<'_, B>,
|
||||
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
|
||||
frame: &mut Frame<'_, B>,
|
||||
) where
|
||||
B: Backend,
|
||||
{
|
||||
todo!()
|
||||
let rect = draw_ctx.global_rect();
|
||||
|
||||
frame.render_widget(
|
||||
tui::widgets::Block::default()
|
||||
.borders(self.borders)
|
||||
.border_style(self.style_sheet.border),
|
||||
rect,
|
||||
);
|
||||
|
||||
if let Some(child) = &mut self.child {
|
||||
if let Some(child_draw_ctx) = draw_ctx.children().next() {
|
||||
child.draw(state_ctx, &child_draw_ctx, frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: &DrawContext<'_>, _event: Event,
|
||||
_messages: &mut Vec<Message>,
|
||||
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, event: Event,
|
||||
messages: &mut Vec<Message>,
|
||||
) -> Status {
|
||||
if let Some(child_draw_ctx) = draw_ctx.children().next() {
|
||||
if let Some(child) = &mut self.child {
|
||||
return child.on_event(state_ctx, &child_draw_ctx, event, messages);
|
||||
}
|
||||
}
|
||||
|
||||
Status::Ignored
|
||||
}
|
||||
|
||||
fn layout(&self, bounds: Bounds, node: &mut LayoutNode) -> crate::tuine::Size {
|
||||
if let Some(child) = &self.child {
|
||||
// Reduce bounds based on borders
|
||||
let inner_rect = self.inner_rect(Rect::new(0, 0, bounds.max_width, bounds.max_height));
|
||||
let child_bounds = Bounds {
|
||||
min_width: bounds.min_width,
|
||||
min_height: bounds.min_height,
|
||||
max_width: inner_rect.width,
|
||||
max_height: inner_rect.height,
|
||||
};
|
||||
|
||||
let mut child_node = LayoutNode::default();
|
||||
let child_size = child.layout(child_bounds, &mut child_node);
|
||||
|
||||
child_node.rect = Rect::new(
|
||||
inner_rect.x,
|
||||
inner_rect.y,
|
||||
child_size.width,
|
||||
child_size.height,
|
||||
);
|
||||
node.children = vec![child_node];
|
||||
|
||||
child_size
|
||||
} else {
|
||||
Size {
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub mod text_table;
|
||||
pub use text_table::*;
|
||||
pub use text_table::{DataCell, DataRow, SortType, TextColumn, TextTable, TextTableProps};
|
||||
|
||||
pub mod shortcut;
|
||||
pub use shortcut::Shortcut;
|
||||
|
@ -32,6 +32,7 @@ use crate::{
|
||||
tuine::{DrawContext, Event, Key, StateContext, StatefulComponent, Status, TmpComponent},
|
||||
};
|
||||
|
||||
/// A set of styles for a [`TextTable`].
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct StyleSheet {
|
||||
text: Style,
|
||||
@ -114,7 +115,6 @@ impl<Message> StatefulComponent<Message> for TextTable<Message> {
|
||||
|
||||
type ComponentState = TextTableState;
|
||||
|
||||
#[track_caller]
|
||||
fn build(ctx: &mut crate::tuine::ViewContext<'_>, mut props: Self::Properties) -> Self {
|
||||
let sort = props.sort;
|
||||
let (key, state) = ctx.register_and_mut_state_with_default::<_, Self::ComponentState, _>(
|
||||
@ -126,6 +126,7 @@ impl<Message> StatefulComponent<Message> for TextTable<Message> {
|
||||
);
|
||||
|
||||
state.scroll.set_num_items(props.rows.len());
|
||||
state.sort.prune_length(props.columns.len());
|
||||
props.try_sort_data(state.sort);
|
||||
|
||||
TextTable {
|
||||
|
@ -5,6 +5,19 @@ pub enum SortType {
|
||||
Descending(usize),
|
||||
}
|
||||
|
||||
impl SortType {
|
||||
pub(crate) fn prune_length(&mut self, num_columns: usize) {
|
||||
match self {
|
||||
SortType::Unsortable => {}
|
||||
SortType::Ascending(column) | SortType::Descending(column) => {
|
||||
if *column >= num_columns {
|
||||
*column = num_columns - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortType {
|
||||
fn default() -> Self {
|
||||
Self::Unsortable
|
||||
|
@ -7,6 +7,9 @@ pub use widget::*;
|
||||
pub mod stateful;
|
||||
pub use stateful::*;
|
||||
|
||||
// pub mod stateless;
|
||||
// pub use stateless::*;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use tui::Frame;
|
||||
|
||||
|
@ -5,10 +5,11 @@ use super::TmpComponent;
|
||||
/// A [`StatefulComponent`] is a builder-style pattern for building a stateful
|
||||
/// [`Component`].
|
||||
///
|
||||
/// Inspired by Flutter's StatefulWidget interface.
|
||||
/// Inspired by Flutter's [StatefulWidget class](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).
|
||||
pub trait StatefulComponent<Message>: TmpComponent<Message> {
|
||||
type Properties;
|
||||
type ComponentState: State;
|
||||
|
||||
#[track_caller]
|
||||
fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self;
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
use crate::tuine::{
|
||||
text_table::{DataRow, SortType, TextTableProps},
|
||||
Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext,
|
||||
Block, Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext,
|
||||
};
|
||||
|
||||
/// A [`TempTable`] is a text table that is meant to display temperature data.
|
||||
pub struct TempTable<Message> {
|
||||
inner: Shortcut<Message, TextTable<Message>>,
|
||||
inner: Block<Message, Shortcut<Message, TextTable<Message>>>,
|
||||
}
|
||||
|
||||
impl<Message> TempTable<Message> {
|
||||
#[track_caller]
|
||||
pub fn new(ctx: &mut ViewContext<'_>) -> Self {
|
||||
pub fn build(ctx: &mut ViewContext<'_>) -> Self {
|
||||
Self {
|
||||
inner: Shortcut::with_child(TextTable::build(
|
||||
inner: Block::with_child(Shortcut::with_child(TextTable::build(
|
||||
ctx,
|
||||
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||
.rows(vec![
|
||||
@ -21,7 +21,7 @@ impl<Message> TempTable<Message> {
|
||||
DataRow::default().cell("C").cell(1),
|
||||
])
|
||||
.default_sort(SortType::Ascending(1)),
|
||||
)),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ pub enum Element<Message, C = Empty>
|
||||
where
|
||||
C: TmpComponent<Message>,
|
||||
{
|
||||
Block,
|
||||
Block(Block<Message, C>),
|
||||
Carousel,
|
||||
Container(Container<Message>),
|
||||
Flex(Flex<Message>),
|
||||
|
Loading…
x
Reference in New Issue
Block a user