Simple blocks

This commit is contained in:
ClementTsang 2021-12-27 04:37:21 -05:00
parent 8f790d0b75
commit 18bce9f0a0
9 changed files with 157 additions and 19 deletions

View File

@ -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"]),

View File

@ -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,
}
}
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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)),
)),
))),
}
}
}

View File

@ -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>),