diff --git a/src/app.rs b/src/app.rs index d058bf83..f02df4e3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -240,22 +240,30 @@ impl Application for AppState { fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element { 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, ) diff --git a/src/tuine/component/base/mod.rs b/src/tuine/component/base/mod.rs index ea1aeb11..505395dc 100644 --- a/src/tuine/component/base/mod.rs +++ b/src/tuine/component/base/mod.rs @@ -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; diff --git a/src/tuine/component/base/text_table/mod.rs b/src/tuine/component/base/text_table/mod.rs index 229d5dc5..59e200a2 100644 --- a/src/tuine/component/base/text_table/mod.rs +++ b/src/tuine/component/base/text_table/mod.rs @@ -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 { on_selected_click: Option Message>>, } +impl StatefulComponent for TextTable { + type Properties = TextTableProps; + + 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 TextTable { fn update_column_widths(&mut self, bounds: Rect) { let total_width = bounds.width; @@ -245,8 +279,7 @@ impl TmpComponent for TextTable { #[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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = TextTable::build( + ctx, + TextTableProps::new(vec!["Sensor", "Temp"]) + .rows(rows) + .row(original_rows[3].clone()), + ); assert_eq!( table.rows.len(), diff --git a/src/tuine/component/base/text_table/builder.rs b/src/tuine/component/base/text_table/props.rs similarity index 66% rename from src/tuine/component/base/text_table/builder.rs rename to src/tuine/component/base/text_table/props.rs index 63d08b3c..ae436066 100644 --- a/src/tuine/component/base/text_table/builder.rs +++ b/src/tuine/component/base/text_table/props.rs @@ -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 { - column_widths: Vec, - columns: Vec, - show_gap: bool, - show_selected_entry: bool, - rows: Vec, - style_sheet: StyleSheet, - sort: SortType, - table_gap: u16, - on_select: Option Message>>, - on_selected_click: Option Message>>, +pub struct TextTableProps { + pub(crate) column_widths: Vec, + pub(crate) columns: Vec, + pub(crate) show_gap: bool, + pub(crate) show_selected_entry: bool, + pub(crate) rows: Vec, + pub(crate) style_sheet: StyleSheet, + pub(crate) sort: SortType, + pub(crate) table_gap: u16, + pub(crate) on_select: Option Message>>, + pub(crate) on_selected_click: Option Message>>, } -impl TextTableBuilder { +impl TextTableProps { pub fn new>>(columns: Vec) -> Self { Self { column_widths: vec![0; columns.len()], @@ -94,7 +94,7 @@ impl TextTableBuilder { 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 TextTableBuilder { } } } - -impl StatefulTemplate for TextTableBuilder { - type Component = TextTable; - 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, - } - } -} diff --git a/src/tuine/component/stateful.rs b/src/tuine/component/stateful.rs index f7711aa5..47efe627 100644 --- a/src/tuine/component/stateful.rs +++ b/src/tuine/component/stateful.rs @@ -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 { - type Component: TmpComponent; +pub trait StatefulComponent: TmpComponent { + type Properties; type ComponentState: State; - fn build(self, ctx: &mut ViewContext<'_>) -> Self::Component; + fn build(ctx: &mut ViewContext<'_>, props: Self::Properties) -> Self; } diff --git a/src/tuine/component/widget/temp_table.rs b/src/tuine/component/widget/temp_table.rs index 91eaf04b..61a2bc3c 100644 --- a/src/tuine/component/widget/temp_table.rs +++ b/src/tuine/component/widget/temp_table.rs @@ -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 TempTable { #[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)), + )), } } }