Skip to main content

freya_components/
table.rs

1use freya_core::prelude::*;
2use torin::{
3    content::Content,
4    gaps::Gaps,
5    node::Node,
6    prelude::Alignment,
7    size::Size,
8};
9
10use crate::{
11    define_theme,
12    get_theme,
13};
14
15define_theme! {
16    for = Table;
17    theme_field = theme;
18
19    %[component]
20    pub Table {
21        %[fields]
22        background: Color,
23        hover_row_background: Color,
24        row_background: Color,
25        divider_fill: Color,
26        corner_radius: CornerRadius,
27        color: Color,
28    }
29}
30
31/// Resolved theme and column widths a [Table] shares with its rows.
32#[derive(Clone)]
33pub struct TableConfig {
34    pub theme: TableTheme,
35    pub column_widths: Option<Vec<Size>>,
36}
37
38#[derive(PartialEq)]
39pub struct TableRow {
40    pub children: Vec<Element>,
41    layout: LayoutData,
42    key: DiffKey,
43}
44
45impl Default for TableRow {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl TableRow {
52    pub fn new() -> Self {
53        Self {
54            children: vec![],
55            layout: Node {
56                width: Size::fill(),
57                ..Default::default()
58            }
59            .into(),
60            key: DiffKey::None,
61        }
62    }
63}
64
65impl LayoutExt for TableRow {
66    fn layout_mut(&mut self) -> &mut LayoutData {
67        &mut self.layout
68    }
69}
70
71impl ContainerExt for TableRow {}
72
73impl ChildrenExt for TableRow {
74    fn children_mut(&mut self) -> &mut Vec<Element> {
75        &mut self.children
76    }
77}
78
79impl KeyExt for TableRow {
80    fn key_mut(&mut self) -> &mut DiffKey {
81        &mut self.key
82    }
83}
84
85impl Component for TableRow {
86    fn render(&self) -> impl IntoElement {
87        let TableConfig {
88            theme,
89            column_widths,
90        } = use_try_consume::<TableConfig>().unwrap_or_else(|| TableConfig {
91            theme: get_theme!(None::<TableThemePartial>, TableThemePreference, "table"),
92            column_widths: None,
93        });
94        let mut hovering = use_state(|| false);
95        let background = if hovering() {
96            theme.hover_row_background
97        } else {
98            theme.row_background
99        };
100
101        rect()
102            .layout(self.layout.clone())
103            .horizontal()
104            .content(Content::Flex)
105            .cross_align(Alignment::Center)
106            .background(background)
107            .border(Border::new().fill(theme.divider_fill).width(BorderWidth {
108                bottom: 1.,
109                ..Default::default()
110            }))
111            .on_pointer_enter(move |_| hovering.set(true))
112            .on_pointer_leave(move |_| hovering.set(false))
113            .children(self.children.iter().enumerate().map(|(index, child)| {
114                let width = column_widths
115                    .as_ref()
116                    .and_then(|widths| widths.get(index).cloned())
117                    .unwrap_or_else(|| Size::flex(1.));
118
119                rect()
120                    .width(width)
121                    .overflow(Overflow::Clip)
122                    .padding(Gaps::new_all(5.0))
123                    .horizontal()
124                    .main_align(Alignment::End)
125                    .cross_align(Alignment::Center)
126                    .child(child.clone())
127            }))
128    }
129
130    fn render_key(&self) -> DiffKey {
131        self.key.clone().or(self.default_key())
132    }
133}
134
135/// A table component with rows and columns.
136///
137/// # Example
138///
139/// ```rust
140/// # use freya::prelude::*;
141/// fn app() -> impl IntoElement {
142///     Table::new()
143///         .child(TableRow::new().child("Header 1").child("Header 2"))
144///         .child(TableRow::new().child("Data 1").child("Data 2"))
145///         .child(TableRow::new().child("Data 3").child("Data 4"))
146/// }
147/// ```
148///
149/// See the [interactive components demo](https://freyaui.dev/demo).
150#[derive(PartialEq, Default)]
151pub struct Table {
152    pub theme: Option<TableThemePartial>,
153    pub column_widths: Option<Vec<Size>>,
154    pub children: Vec<Element>,
155    layout: LayoutData,
156    key: DiffKey,
157}
158
159impl Table {
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    pub fn theme(mut self, theme: TableThemePartial) -> Self {
165        self.theme = Some(theme);
166        self
167    }
168
169    /// Set custom widths for each column.
170    ///
171    /// Accepts any [Size], defaults to [Size::Flex].
172    pub fn column_widths(mut self, widths: impl Into<Vec<Size>>) -> Self {
173        self.column_widths = Some(widths.into());
174        self
175    }
176}
177
178impl ChildrenExt for Table {
179    fn children_mut(&mut self) -> &mut Vec<Element> {
180        &mut self.children
181    }
182}
183
184impl KeyExt for Table {
185    fn key_mut(&mut self) -> &mut DiffKey {
186        &mut self.key
187    }
188}
189
190impl LayoutExt for Table {
191    fn layout_mut(&mut self) -> &mut LayoutData {
192        &mut self.layout
193    }
194}
195
196impl ContainerExt for Table {}
197
198impl Component for Table {
199    fn render(&self) -> impl IntoElement {
200        let theme = get_theme!(&self.theme, TableThemePreference, "table");
201        provide_context(TableConfig {
202            theme: theme.clone(),
203            column_widths: self.column_widths.clone(),
204        });
205
206        rect()
207            .layout(self.layout.clone())
208            .overflow(Overflow::Clip)
209            .color(theme.color)
210            .background(theme.background)
211            .corner_radius(theme.corner_radius)
212            .border(
213                Border::new()
214                    .alignment(BorderAlignment::Outer)
215                    .fill(theme.divider_fill)
216                    .width(1.0),
217            )
218            .children(self.children.clone())
219    }
220
221    fn render_key(&self) -> DiffKey {
222        self.key.clone().or(self.default_key())
223    }
224}