mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
Move to semi-builder
This commit is contained in:
parent
8c3a44deba
commit
b0d4fd041a
20
src/app.rs
20
src/app.rs
@ -240,22 +240,30 @@ impl Application for AppState {
|
|||||||
|
|
||||||
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<Self::Message> {
|
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<Self::Message> {
|
||||||
use crate::tuine::FlexElement;
|
use crate::tuine::FlexElement;
|
||||||
use crate::tuine::StatefulTemplate;
|
use crate::tuine::StatefulComponent;
|
||||||
use crate::tuine::TempTable;
|
use crate::tuine::{TempTable, TextTable, TextTableProps};
|
||||||
use crate::tuine::TextTableBuilder;
|
|
||||||
|
|
||||||
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::new(ctx)),
|
||||||
FlexElement::new(TextTableBuilder::new(vec!["D", "E", "F"]).build(ctx)),
|
FlexElement::new(TextTable::build(
|
||||||
|
ctx,
|
||||||
|
TextTableProps::new(vec!["D", "E", "F"]),
|
||||||
|
)),
|
||||||
]),
|
]),
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
.with_flex_child(
|
.with_flex_child(
|
||||||
Flex::row_with_children(vec![
|
Flex::row_with_children(vec![
|
||||||
FlexElement::new(TextTableBuilder::new(vec!["G", "H", "I", "J"]).build(ctx)),
|
FlexElement::new(TextTable::build(
|
||||||
FlexElement::new(TextTableBuilder::new(vec!["K", "L", "M", "N"]).build(ctx)),
|
ctx,
|
||||||
|
TextTableProps::new(vec!["G", "H", "I", "J"]),
|
||||||
|
)),
|
||||||
|
FlexElement::new(TextTable::build(
|
||||||
|
ctx,
|
||||||
|
TextTableProps::new(vec!["L", "EM", "NO", "PQ"]),
|
||||||
|
)),
|
||||||
]),
|
]),
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pub mod text_table;
|
pub mod text_table;
|
||||||
pub use text_table::{TextColumn, TextColumnConstraint, TextTable, TextTableBuilder};
|
pub use text_table::*;
|
||||||
|
|
||||||
pub mod shortcut;
|
pub mod shortcut;
|
||||||
pub use shortcut::Shortcut;
|
pub use shortcut::Shortcut;
|
||||||
|
@ -10,13 +10,13 @@ pub use data_row::DataRow;
|
|||||||
pub mod data_cell;
|
pub mod data_cell;
|
||||||
pub use data_cell::DataCell;
|
pub use data_cell::DataCell;
|
||||||
|
|
||||||
pub mod builder;
|
|
||||||
pub use builder::TextTableBuilder;
|
|
||||||
|
|
||||||
pub mod sort_type;
|
pub mod sort_type;
|
||||||
pub use sort_type::SortType;
|
pub use sort_type::SortType;
|
||||||
|
|
||||||
use std::cmp::min;
|
pub mod props;
|
||||||
|
pub use props::TextTableProps;
|
||||||
|
|
||||||
|
use std::{cmp::min, panic::Location};
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
@ -29,7 +29,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::TABLE_GAP_HEIGHT_LIMIT,
|
constants::TABLE_GAP_HEIGHT_LIMIT,
|
||||||
tuine::{DrawContext, Event, Key, StateContext, Status, TmpComponent},
|
tuine::{DrawContext, Event, Key, StateContext, StatefulComponent, Status, TmpComponent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@ -59,6 +59,40 @@ pub struct TextTable<Message> {
|
|||||||
on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
|
on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Message> StatefulComponent<Message> for TextTable<Message> {
|
||||||
|
type Properties = TextTableProps<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, _>(
|
||||||
|
Location::caller(),
|
||||||
|
|| TextTableState {
|
||||||
|
scroll: Default::default(),
|
||||||
|
sort,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
state.scroll.set_num_items(props.rows.len());
|
||||||
|
props.try_sort_data(state.sort);
|
||||||
|
|
||||||
|
TextTable {
|
||||||
|
key,
|
||||||
|
column_widths: props.column_widths,
|
||||||
|
columns: props.columns,
|
||||||
|
show_gap: props.show_gap,
|
||||||
|
show_selected_entry: props.show_selected_entry,
|
||||||
|
rows: props.rows,
|
||||||
|
style_sheet: props.style_sheet,
|
||||||
|
table_gap: props.table_gap,
|
||||||
|
on_select: props.on_select,
|
||||||
|
on_selected_click: props.on_selected_click,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Message> TextTable<Message> {
|
impl<Message> TextTable<Message> {
|
||||||
fn update_column_widths(&mut self, bounds: Rect) {
|
fn update_column_widths(&mut self, bounds: Rect) {
|
||||||
let total_width = bounds.width;
|
let total_width = bounds.width;
|
||||||
@ -245,8 +279,7 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tuine::{
|
use crate::tuine::{
|
||||||
text_table::{SortType, TextTableBuilder},
|
text_table::SortType, StateMap, StatefulComponent, TextTableProps, ViewContext,
|
||||||
StateMap, StatefulTemplate, ViewContext,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DataRow, TextTable};
|
use super::{DataRow, TextTable};
|
||||||
@ -268,10 +301,13 @@ mod tests {
|
|||||||
let index = 1;
|
let index = 1;
|
||||||
|
|
||||||
let mut map = StateMap::default();
|
let mut map = StateMap::default();
|
||||||
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
|
let ctx = &mut ctx(&mut map);
|
||||||
.default_sort(SortType::Ascending(index))
|
let table: TextTable<Message> = TextTable::build(
|
||||||
.rows(rows)
|
ctx,
|
||||||
.build(&mut ctx(&mut map));
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
|
.default_sort(SortType::Ascending(index))
|
||||||
|
.rows(rows),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.rows.len(),
|
table.rows.len(),
|
||||||
@ -301,11 +337,14 @@ mod tests {
|
|||||||
let new_index = 0;
|
let new_index = 0;
|
||||||
|
|
||||||
let mut map = StateMap::default();
|
let mut map = StateMap::default();
|
||||||
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
|
let ctx = &mut ctx(&mut map);
|
||||||
.default_sort(SortType::Ascending(index))
|
let table: TextTable<Message> = TextTable::build(
|
||||||
.rows(rows)
|
ctx,
|
||||||
.default_sort(SortType::Ascending(new_index))
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
.build(&mut ctx(&mut map));
|
.default_sort(SortType::Ascending(index))
|
||||||
|
.rows(rows)
|
||||||
|
.default_sort(SortType::Ascending(new_index)),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.rows.len(),
|
table.rows.len(),
|
||||||
@ -334,10 +373,13 @@ mod tests {
|
|||||||
let index = 1;
|
let index = 1;
|
||||||
|
|
||||||
let mut map = StateMap::default();
|
let mut map = StateMap::default();
|
||||||
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
|
let ctx = &mut ctx(&mut map);
|
||||||
.default_sort(SortType::Descending(index))
|
let table: TextTable<Message> = TextTable::build(
|
||||||
.rows(rows)
|
ctx,
|
||||||
.build(&mut ctx(&mut map));
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
|
.default_sort(SortType::Descending(index))
|
||||||
|
.rows(rows),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.rows.len(),
|
table.rows.len(),
|
||||||
@ -366,11 +408,14 @@ mod tests {
|
|||||||
let index = 1;
|
let index = 1;
|
||||||
|
|
||||||
let mut map = StateMap::default();
|
let mut map = StateMap::default();
|
||||||
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
|
let ctx = &mut ctx(&mut map);
|
||||||
.rows(rows)
|
let table: TextTable<Message> = TextTable::build(
|
||||||
.default_sort(SortType::Ascending(index))
|
ctx,
|
||||||
.row(DataRow::default().cell("X").cell(0))
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
.build(&mut ctx(&mut map));
|
.rows(rows)
|
||||||
|
.default_sort(SortType::Ascending(index))
|
||||||
|
.row(DataRow::default().cell("X").cell(0)),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.rows.len(),
|
table.rows.len(),
|
||||||
@ -400,10 +445,13 @@ mod tests {
|
|||||||
let row_length = original_rows.len();
|
let row_length = original_rows.len();
|
||||||
|
|
||||||
let mut map = StateMap::default();
|
let mut map = StateMap::default();
|
||||||
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
|
let ctx = &mut ctx(&mut map);
|
||||||
.rows(rows)
|
let table: TextTable<Message> = TextTable::build(
|
||||||
.row(original_rows[3].clone())
|
ctx,
|
||||||
.build(&mut ctx(&mut map));
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
|
.rows(rows)
|
||||||
|
.row(original_rows[3].clone()),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.rows.len(),
|
table.rows.len(),
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
use std::{borrow::Cow, panic::Location};
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::tuine::{StatefulTemplate, ViewContext};
|
use crate::tuine::{DataRow, SortType, TextColumn};
|
||||||
|
|
||||||
use super::{DataRow, SortType, StyleSheet, TextColumn, TextTable, TextTableState};
|
use super::StyleSheet;
|
||||||
|
|
||||||
pub struct TextTableBuilder<Message> {
|
pub struct TextTableProps<Message> {
|
||||||
column_widths: Vec<u16>,
|
pub(crate) column_widths: Vec<u16>,
|
||||||
columns: Vec<TextColumn>,
|
pub(crate) columns: Vec<TextColumn>,
|
||||||
show_gap: bool,
|
pub(crate) show_gap: bool,
|
||||||
show_selected_entry: bool,
|
pub(crate) show_selected_entry: bool,
|
||||||
rows: Vec<DataRow>,
|
pub(crate) rows: Vec<DataRow>,
|
||||||
style_sheet: StyleSheet,
|
pub(crate) style_sheet: StyleSheet,
|
||||||
sort: SortType,
|
pub(crate) sort: SortType,
|
||||||
table_gap: u16,
|
pub(crate) table_gap: u16,
|
||||||
on_select: Option<Box<dyn Fn(usize) -> Message>>,
|
pub(crate) on_select: Option<Box<dyn Fn(usize) -> Message>>,
|
||||||
on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
|
pub(crate) on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> TextTableBuilder<Message> {
|
impl<Message> TextTableProps<Message> {
|
||||||
pub fn new<S: Into<Cow<'static, str>>>(columns: Vec<S>) -> Self {
|
pub fn new<S: Into<Cow<'static, str>>>(columns: Vec<S>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
column_widths: vec![0; columns.len()],
|
column_widths: vec![0; columns.len()],
|
||||||
@ -94,7 +94,7 @@ impl<Message> TextTableBuilder<Message> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_sort_data(&mut self, sort_type: SortType) {
|
pub(crate) fn try_sort_data(&mut self, sort_type: SortType) {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
// TODO: We can avoid some annoying checks by using const generics - this is waiting on
|
// TODO: We can avoid some annoying checks by using const generics - this is waiting on
|
||||||
@ -121,36 +121,3 @@ impl<Message> TextTableBuilder<Message> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> StatefulTemplate<Message> for TextTableBuilder<Message> {
|
|
||||||
type Component = TextTable<Message>;
|
|
||||||
type ComponentState = TextTableState;
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn build(mut self, ctx: &mut ViewContext<'_>) -> Self::Component {
|
|
||||||
let sort = self.sort;
|
|
||||||
let (key, state) = ctx.register_and_mut_state_with_default::<_, Self::ComponentState, _>(
|
|
||||||
Location::caller(),
|
|
||||||
|| TextTableState {
|
|
||||||
scroll: Default::default(),
|
|
||||||
sort,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
state.scroll.set_num_items(self.rows.len());
|
|
||||||
self.try_sort_data(state.sort);
|
|
||||||
|
|
||||||
TextTable {
|
|
||||||
key,
|
|
||||||
column_widths: self.column_widths,
|
|
||||||
columns: self.columns,
|
|
||||||
show_gap: self.show_gap,
|
|
||||||
show_selected_entry: self.show_selected_entry,
|
|
||||||
rows: self.rows,
|
|
||||||
style_sheet: self.style_sheet,
|
|
||||||
table_gap: self.table_gap,
|
|
||||||
on_select: self.on_select,
|
|
||||||
on_selected_click: self.on_selected_click,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,13 +2,13 @@ use crate::tuine::{State, ViewContext};
|
|||||||
|
|
||||||
use super::TmpComponent;
|
use super::TmpComponent;
|
||||||
|
|
||||||
/// A [`StatefulTemplate`] 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 interface.
|
||||||
pub trait StatefulTemplate<Message> {
|
pub trait StatefulComponent<Message>: TmpComponent<Message> {
|
||||||
type Component: TmpComponent<Message>;
|
type Properties;
|
||||||
type ComponentState: State;
|
type ComponentState: State;
|
||||||
|
|
||||||
fn build(self, ctx: &mut ViewContext<'_>) -> Self::Component;
|
fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::tuine::{
|
use crate::tuine::{
|
||||||
text_table::{DataRow, SortType, TextTableBuilder},
|
text_table::{DataRow, SortType, TextTableProps},
|
||||||
Shortcut, StatefulTemplate, TextTable, TmpComponent, ViewContext,
|
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.
|
||||||
@ -12,16 +12,16 @@ impl<Message> TempTable<Message> {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(ctx: &mut ViewContext<'_>) -> Self {
|
pub fn new(ctx: &mut ViewContext<'_>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Shortcut::with_child(
|
inner: Shortcut::with_child(TextTable::build(
|
||||||
TextTableBuilder::new(vec!["Sensor", "Temp"])
|
ctx,
|
||||||
|
TextTableProps::new(vec!["Sensor", "Temp"])
|
||||||
.rows(vec![
|
.rows(vec![
|
||||||
DataRow::default().cell("A").cell(2),
|
DataRow::default().cell("A").cell(2),
|
||||||
DataRow::default().cell("B").cell(3),
|
DataRow::default().cell("B").cell(3),
|
||||||
DataRow::default().cell("C").cell(1),
|
DataRow::default().cell("C").cell(1),
|
||||||
])
|
])
|
||||||
.default_sort(SortType::Ascending(1))
|
.default_sort(SortType::Ascending(1)),
|
||||||
.build(ctx),
|
)),
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user