Move to semi-builder

This commit is contained in:
ClementTsang 2021-12-26 17:26:04 -05:00
parent 8c3a44deba
commit b0d4fd041a
6 changed files with 119 additions and 96 deletions

View File

@ -240,22 +240,30 @@ impl Application for AppState {
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<Self::Message> {
use crate::tuine::FlexElement;
use crate::tuine::StatefulTemplate;
use crate::tuine::TempTable;
use crate::tuine::TextTableBuilder;
use crate::tuine::StatefulComponent;
use crate::tuine::{TempTable, TextTable, TextTableProps};
Flex::column()
.with_flex_child(
Flex::row_with_children(vec![
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,
)
.with_flex_child(
Flex::row_with_children(vec![
FlexElement::new(TextTableBuilder::new(vec!["G", "H", "I", "J"]).build(ctx)),
FlexElement::new(TextTableBuilder::new(vec!["K", "L", "M", "N"]).build(ctx)),
FlexElement::new(TextTable::build(
ctx,
TextTableProps::new(vec!["G", "H", "I", "J"]),
)),
FlexElement::new(TextTable::build(
ctx,
TextTableProps::new(vec!["L", "EM", "NO", "PQ"]),
)),
]),
2,
)

View File

@ -1,5 +1,5 @@
pub mod text_table;
pub use text_table::{TextColumn, TextColumnConstraint, TextTable, TextTableBuilder};
pub use text_table::*;
pub mod shortcut;
pub use shortcut::Shortcut;

View File

@ -10,13 +10,13 @@ pub use data_row::DataRow;
pub mod data_cell;
pub use data_cell::DataCell;
pub mod builder;
pub use builder::TextTableBuilder;
pub mod sort_type;
pub use sort_type::SortType;
use std::cmp::min;
pub mod props;
pub use props::TextTableProps;
use std::{cmp::min, panic::Location};
use tui::{
backend::Backend,
@ -29,7 +29,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{
constants::TABLE_GAP_HEIGHT_LIMIT,
tuine::{DrawContext, Event, Key, StateContext, Status, TmpComponent},
tuine::{DrawContext, Event, Key, StateContext, StatefulComponent, Status, TmpComponent},
};
#[derive(Clone, Debug, Default)]
@ -59,6 +59,40 @@ pub struct TextTable<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> {
fn update_column_widths(&mut self, bounds: Rect) {
let total_width = bounds.width;
@ -245,8 +279,7 @@ impl<Message> TmpComponent<Message> for TextTable<Message> {
#[cfg(test)]
mod tests {
use crate::tuine::{
text_table::{SortType, TextTableBuilder},
StateMap, StatefulTemplate, ViewContext,
text_table::SortType, StateMap, StatefulComponent, TextTableProps, ViewContext,
};
use super::{DataRow, TextTable};
@ -268,10 +301,13 @@ mod tests {
let index = 1;
let mut map = StateMap::default();
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
.default_sort(SortType::Ascending(index))
.rows(rows)
.build(&mut ctx(&mut map));
let ctx = &mut ctx(&mut map);
let table: TextTable<Message> = TextTable::build(
ctx,
TextTableProps::new(vec!["Sensor", "Temp"])
.default_sort(SortType::Ascending(index))
.rows(rows),
);
assert_eq!(
table.rows.len(),
@ -301,11 +337,14 @@ mod tests {
let new_index = 0;
let mut map = StateMap::default();
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
.default_sort(SortType::Ascending(index))
.rows(rows)
.default_sort(SortType::Ascending(new_index))
.build(&mut ctx(&mut map));
let ctx = &mut ctx(&mut map);
let table: TextTable<Message> = TextTable::build(
ctx,
TextTableProps::new(vec!["Sensor", "Temp"])
.default_sort(SortType::Ascending(index))
.rows(rows)
.default_sort(SortType::Ascending(new_index)),
);
assert_eq!(
table.rows.len(),
@ -334,10 +373,13 @@ mod tests {
let index = 1;
let mut map = StateMap::default();
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
.default_sort(SortType::Descending(index))
.rows(rows)
.build(&mut ctx(&mut map));
let ctx = &mut ctx(&mut map);
let table: TextTable<Message> = TextTable::build(
ctx,
TextTableProps::new(vec!["Sensor", "Temp"])
.default_sort(SortType::Descending(index))
.rows(rows),
);
assert_eq!(
table.rows.len(),
@ -366,11 +408,14 @@ mod tests {
let index = 1;
let mut map = StateMap::default();
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
.rows(rows)
.default_sort(SortType::Ascending(index))
.row(DataRow::default().cell("X").cell(0))
.build(&mut ctx(&mut map));
let ctx = &mut ctx(&mut map);
let table: TextTable<Message> = TextTable::build(
ctx,
TextTableProps::new(vec!["Sensor", "Temp"])
.rows(rows)
.default_sort(SortType::Ascending(index))
.row(DataRow::default().cell("X").cell(0)),
);
assert_eq!(
table.rows.len(),
@ -400,10 +445,13 @@ mod tests {
let row_length = original_rows.len();
let mut map = StateMap::default();
let table: TextTable<Message> = TextTableBuilder::new(vec!["Sensor", "Temp"])
.rows(rows)
.row(original_rows[3].clone())
.build(&mut ctx(&mut map));
let ctx = &mut ctx(&mut map);
let table: TextTable<Message> = TextTable::build(
ctx,
TextTableProps::new(vec!["Sensor", "Temp"])
.rows(rows)
.row(original_rows[3].clone()),
);
assert_eq!(
table.rows.len(),

View File

@ -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> {
column_widths: Vec<u16>,
columns: Vec<TextColumn>,
show_gap: bool,
show_selected_entry: bool,
rows: Vec<DataRow>,
style_sheet: StyleSheet,
sort: SortType,
table_gap: u16,
on_select: Option<Box<dyn Fn(usize) -> Message>>,
on_selected_click: Option<Box<dyn Fn(usize) -> Message>>,
pub struct TextTableProps<Message> {
pub(crate) column_widths: Vec<u16>,
pub(crate) columns: Vec<TextColumn>,
pub(crate) show_gap: bool,
pub(crate) show_selected_entry: bool,
pub(crate) rows: Vec<DataRow>,
pub(crate) style_sheet: StyleSheet,
pub(crate) sort: SortType,
pub(crate) table_gap: u16,
pub(crate) on_select: 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 {
Self {
column_widths: vec![0; columns.len()],
@ -94,7 +94,7 @@ impl<Message> TextTableBuilder<Message> {
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;
// 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,
}
}
}

View File

@ -2,13 +2,13 @@ use crate::tuine::{State, ViewContext};
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`].
///
/// Inspired by Flutter's StatefulWidget interface.
pub trait StatefulTemplate<Message> {
type Component: TmpComponent<Message>;
pub trait StatefulComponent<Message>: TmpComponent<Message> {
type Properties;
type ComponentState: State;
fn build(self, ctx: &mut ViewContext<'_>) -> Self::Component;
fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self;
}

View File

@ -1,6 +1,6 @@
use crate::tuine::{
text_table::{DataRow, SortType, TextTableBuilder},
Shortcut, StatefulTemplate, TextTable, TmpComponent, ViewContext,
text_table::{DataRow, SortType, TextTableProps},
Shortcut, StatefulComponent, TextTable, TmpComponent, ViewContext,
};
/// A [`TempTable`] is a text table that is meant to display temperature data.
@ -12,16 +12,16 @@ impl<Message> TempTable<Message> {
#[track_caller]
pub fn new(ctx: &mut ViewContext<'_>) -> Self {
Self {
inner: Shortcut::with_child(
TextTableBuilder::new(vec!["Sensor", "Temp"])
inner: Shortcut::with_child(TextTable::build(
ctx,
TextTableProps::new(vec!["Sensor", "Temp"])
.rows(vec![
DataRow::default().cell("A").cell(2),
DataRow::default().cell("B").cell(3),
DataRow::default().cell("C").cell(1),
])
.default_sort(SortType::Ascending(1))
.build(ctx),
),
.default_sort(SortType::Ascending(1)),
)),
}
}
}