mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
Simple blocks
This commit is contained in:
parent
8f790d0b75
commit
18bce9f0a0
@ -246,7 +246,7 @@ impl Application for AppState {
|
|||||||
Flex::column()
|
Flex::column()
|
||||||
.with_flex_child(
|
.with_flex_child(
|
||||||
Flex::row_with_children(vec![
|
Flex::row_with_children(vec![
|
||||||
FlexElement::new(TempTable::new(ctx)),
|
FlexElement::new(TempTable::build(ctx)),
|
||||||
FlexElement::new(TextTable::build(
|
FlexElement::new(TextTable::build(
|
||||||
ctx,
|
ctx,
|
||||||
TextTableProps::new(vec!["D", "E", "F"]),
|
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>(
|
fn draw<B>(
|
||||||
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: &DrawContext<'_>,
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>,
|
||||||
_frame: &mut Frame<'_, B>,
|
frame: &mut Frame<'_, B>,
|
||||||
) where
|
) where
|
||||||
B: Backend,
|
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(
|
fn on_event(
|
||||||
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: &DrawContext<'_>, _event: Event,
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: &DrawContext<'_>, event: Event,
|
||||||
_messages: &mut Vec<Message>,
|
messages: &mut Vec<Message>,
|
||||||
) -> Status {
|
) -> 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
|
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 mod text_table;
|
||||||
pub use text_table::*;
|
pub use text_table::{DataCell, DataRow, SortType, TextColumn, TextTable, TextTableProps};
|
||||||
|
|
||||||
pub mod shortcut;
|
pub mod shortcut;
|
||||||
pub use shortcut::Shortcut;
|
pub use shortcut::Shortcut;
|
||||||
|
@ -32,6 +32,7 @@ use crate::{
|
|||||||
tuine::{DrawContext, Event, Key, StateContext, StatefulComponent, Status, TmpComponent},
|
tuine::{DrawContext, Event, Key, StateContext, StatefulComponent, Status, TmpComponent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A set of styles for a [`TextTable`].
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct StyleSheet {
|
pub struct StyleSheet {
|
||||||
text: Style,
|
text: Style,
|
||||||
@ -114,7 +115,6 @@ impl<Message> StatefulComponent<Message> for TextTable<Message> {
|
|||||||
|
|
||||||
type ComponentState = TextTableState;
|
type ComponentState = TextTableState;
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn build(ctx: &mut crate::tuine::ViewContext<'_>, mut props: Self::Properties) -> Self {
|
fn build(ctx: &mut crate::tuine::ViewContext<'_>, mut props: Self::Properties) -> Self {
|
||||||
let sort = props.sort;
|
let sort = props.sort;
|
||||||
let (key, state) = ctx.register_and_mut_state_with_default::<_, Self::ComponentState, _>(
|
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.scroll.set_num_items(props.rows.len());
|
||||||
|
state.sort.prune_length(props.columns.len());
|
||||||
props.try_sort_data(state.sort);
|
props.try_sort_data(state.sort);
|
||||||
|
|
||||||
TextTable {
|
TextTable {
|
||||||
|
@ -5,6 +5,19 @@ pub enum SortType {
|
|||||||
Descending(usize),
|
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 {
|
impl Default for SortType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Unsortable
|
Self::Unsortable
|
||||||
|
@ -7,6 +7,9 @@ pub use widget::*;
|
|||||||
pub mod stateful;
|
pub mod stateful;
|
||||||
pub use stateful::*;
|
pub use stateful::*;
|
||||||
|
|
||||||
|
// pub mod stateless;
|
||||||
|
// pub use stateless::*;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use tui::Frame;
|
use tui::Frame;
|
||||||
|
|
||||||
|
@ -5,10 +5,11 @@ use super::TmpComponent;
|
|||||||
/// A [`StatefulComponent`] is a builder-style pattern for building a stateful
|
/// A [`StatefulComponent`] is a builder-style pattern for building a stateful
|
||||||
/// [`Component`].
|
/// [`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> {
|
pub trait StatefulComponent<Message>: TmpComponent<Message> {
|
||||||
type Properties;
|
type Properties;
|
||||||
type ComponentState: State;
|
type ComponentState: State;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self;
|
fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
use crate::tuine::{
|
use crate::tuine::{
|
||||||
text_table::{DataRow, SortType, TextTableProps},
|
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.
|
/// A [`TempTable`] is a text table that is meant to display temperature data.
|
||||||
pub struct TempTable<Message> {
|
pub struct TempTable<Message> {
|
||||||
inner: Shortcut<Message, TextTable<Message>>,
|
inner: Block<Message, Shortcut<Message, TextTable<Message>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> TempTable<Message> {
|
impl<Message> TempTable<Message> {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(ctx: &mut ViewContext<'_>) -> Self {
|
pub fn build(ctx: &mut ViewContext<'_>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Shortcut::with_child(TextTable::build(
|
inner: Block::with_child(Shortcut::with_child(TextTable::build(
|
||||||
ctx,
|
ctx,
|
||||||
TextTableProps::new(vec!["Sensor", "Temp"])
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
.rows(vec![
|
.rows(vec![
|
||||||
@ -21,7 +21,7 @@ impl<Message> TempTable<Message> {
|
|||||||
DataRow::default().cell("C").cell(1),
|
DataRow::default().cell("C").cell(1),
|
||||||
])
|
])
|
||||||
.default_sort(SortType::Ascending(1)),
|
.default_sort(SortType::Ascending(1)),
|
||||||
)),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub enum Element<Message, C = Empty>
|
|||||||
where
|
where
|
||||||
C: TmpComponent<Message>,
|
C: TmpComponent<Message>,
|
||||||
{
|
{
|
||||||
Block,
|
Block(Block<Message, C>),
|
||||||
Carousel,
|
Carousel,
|
||||||
Container(Container<Message>),
|
Container(Container<Message>),
|
||||||
Flex(Flex<Message>),
|
Flex(Flex<Message>),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user