Skip to main content

freya_components/
table.rs

1use freya_core::prelude::*;
2use torin::{
3    content::Content,
4    gaps::Gaps,
5    prelude::Alignment,
6    size::Size,
7};
8
9use crate::{
10    define_theme,
11    get_theme,
12    icons::arrow::ArrowIcon,
13};
14
15define_theme! {
16    %[component]
17    pub Table {
18        %[fields]
19        background: Color,
20        arrow_fill: Color,
21        hover_row_background: Color,
22        row_background: Color,
23        divider_fill: Color,
24        corner_radius: CornerRadius,
25        color: Color,
26    }
27}
28
29#[derive(Clone, Copy, PartialEq, Default)]
30pub enum OrderDirection {
31    Up,
32    #[default]
33    Down,
34}
35
36#[derive(PartialEq)]
37pub struct TableArrow {
38    pub order_direction: OrderDirection,
39    key: DiffKey,
40}
41
42impl TableArrow {
43    pub fn new(order_direction: OrderDirection) -> Self {
44        Self {
45            order_direction,
46            key: DiffKey::None,
47        }
48    }
49}
50
51impl KeyExt for TableArrow {
52    fn write_key(&mut self) -> &mut DiffKey {
53        &mut self.key
54    }
55}
56
57impl Component for TableArrow {
58    fn render(&self) -> impl IntoElement {
59        let TableTheme { arrow_fill, .. } =
60            get_theme!(None::<TableThemePartial>, TableThemePreference, "table");
61        let rotate = match self.order_direction {
62            OrderDirection::Down => 0.,
63            OrderDirection::Up => 180.,
64        };
65        ArrowIcon::new().rotate(rotate).fill(arrow_fill)
66    }
67
68    fn render_key(&self) -> DiffKey {
69        self.key.clone().or(self.default_key())
70    }
71}
72
73/// TableHead props (manual)
74#[derive(PartialEq, Default)]
75pub struct TableHead {
76    pub children: Vec<Element>,
77    key: DiffKey,
78}
79
80impl TableHead {
81    pub fn new() -> Self {
82        Self::default()
83    }
84}
85
86impl ChildrenExt for TableHead {
87    fn get_children(&mut self) -> &mut Vec<Element> {
88        &mut self.children
89    }
90}
91
92impl KeyExt for TableHead {
93    fn write_key(&mut self) -> &mut DiffKey {
94        &mut self.key
95    }
96}
97
98impl Component for TableHead {
99    fn render(&self) -> impl IntoElement {
100        rect().width(Size::fill()).children(self.children.clone())
101    }
102
103    fn render_key(&self) -> DiffKey {
104        self.key.clone().or(self.default_key())
105    }
106}
107
108#[derive(PartialEq, Default)]
109pub struct TableBody {
110    pub children: Vec<Element>,
111    key: DiffKey,
112}
113
114impl TableBody {
115    pub fn new() -> Self {
116        Self::default()
117    }
118}
119impl ChildrenExt for TableBody {
120    fn get_children(&mut self) -> &mut Vec<Element> {
121        &mut self.children
122    }
123}
124
125impl KeyExt for TableBody {
126    fn write_key(&mut self) -> &mut DiffKey {
127        &mut self.key
128    }
129}
130
131impl Component for TableBody {
132    fn render(&self) -> impl IntoElement {
133        rect().width(Size::fill()).children(self.children.clone())
134    }
135
136    fn render_key(&self) -> DiffKey {
137        self.key.clone().or(self.default_key())
138    }
139}
140
141#[derive(PartialEq, Clone, Copy)]
142enum TableRowState {
143    Idle,
144    Hovering,
145}
146
147#[derive(PartialEq, Default)]
148pub struct TableRow {
149    pub theme: Option<TableThemePartial>,
150    pub children: Vec<Element>,
151    key: DiffKey,
152}
153
154impl TableRow {
155    pub fn new() -> Self {
156        Self::default()
157    }
158}
159
160impl ChildrenExt for TableRow {
161    fn get_children(&mut self) -> &mut Vec<Element> {
162        &mut self.children
163    }
164}
165
166impl KeyExt for TableRow {
167    fn write_key(&mut self) -> &mut DiffKey {
168        &mut self.key
169    }
170}
171
172impl Component for TableRow {
173    fn render(&self) -> impl IntoElement {
174        let theme = get_theme!(&self.theme, TableThemePreference, "table");
175        let config = use_try_consume::<TableConfig>().unwrap_or_default();
176        let mut state = use_state(|| TableRowState::Idle);
177        let TableTheme {
178            divider_fill,
179            hover_row_background,
180            row_background,
181            ..
182        } = theme;
183        let background = if state() == TableRowState::Hovering {
184            hover_row_background
185        } else {
186            row_background
187        };
188
189        rect()
190            .on_pointer_enter(move |_| state.set(TableRowState::Hovering))
191            .on_pointer_leave(move |_| state.set(TableRowState::Idle))
192            .background(background)
193            .child(
194                rect()
195                    .width(Size::fill())
196                    .horizontal()
197                    .content(Content::Flex)
198                    .children(self.children.iter().enumerate().map(|(index, child)| {
199                        let width = config
200                            .column_widths
201                            .as_ref()
202                            .and_then(|widths| widths.get(index).cloned())
203                            .unwrap_or_else(|| Size::flex(1.));
204
205                        rect().width(width).child(child.clone()).into()
206                    })),
207            )
208            .child(
209                rect()
210                    .height(Size::px(1.))
211                    .width(Size::fill())
212                    .background(divider_fill),
213            )
214    }
215
216    fn render_key(&self) -> DiffKey {
217        self.key.clone().or(self.default_key())
218    }
219}
220
221#[derive(PartialEq)]
222pub struct TableCell {
223    pub children: Vec<Element>,
224    /// optional press handler
225    pub on_press: Option<EventHandler<Event<PressEventData>>>,
226    /// optional visual order direction
227    pub order_direction: Option<OrderDirection>,
228    /// padding as typed Gaps
229    pub padding: Gaps,
230    /// height as typed Size
231    pub height: Size,
232    key: DiffKey,
233}
234
235impl ChildrenExt for TableCell {
236    fn get_children(&mut self) -> &mut Vec<Element> {
237        &mut self.children
238    }
239}
240
241impl Default for TableCell {
242    fn default() -> Self {
243        Self {
244            children: vec![],
245            on_press: None,
246            order_direction: None,
247            padding: Gaps::new_all(5.0),
248            height: Size::px(35.0),
249            key: DiffKey::None,
250        }
251    }
252}
253
254impl TableCell {
255    pub fn new() -> Self {
256        Self::default()
257    }
258
259    pub fn padding(mut self, padding: Gaps) -> Self {
260        self.padding = padding;
261        self
262    }
263
264    pub fn height(mut self, height: impl Into<Size>) -> Self {
265        self.height = height.into();
266        self
267    }
268
269    pub fn on_press(mut self, handler: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
270        self.on_press = Some(handler.into());
271        self
272    }
273
274    pub fn order_direction(mut self, dir: Option<OrderDirection>) -> Self {
275        self.order_direction = dir;
276        self
277    }
278}
279
280impl KeyExt for TableCell {
281    fn write_key(&mut self) -> &mut DiffKey {
282        &mut self.key
283    }
284}
285
286impl Component for TableCell {
287    fn render(&self) -> impl IntoElement {
288        let mut container = rect()
289            .overflow(Overflow::Clip)
290            .padding(self.padding)
291            .width(Size::fill())
292            .main_align(Alignment::End)
293            .cross_align(Alignment::Center)
294            .height(self.height.clone())
295            .horizontal();
296
297        if let Some(on_press) = &self.on_press {
298            let handler = on_press.clone();
299            container = container.on_press(move |e| handler.call(e));
300        }
301
302        if let Some(order_direction) = self.order_direction {
303            container = container.child(
304                rect()
305                    .margin(Gaps::new_all(10.0))
306                    .width(Size::px(10.0))
307                    .height(Size::px(10.0))
308                    .child(TableArrow::new(order_direction)),
309            );
310        }
311
312        container.children(self.children.clone())
313    }
314
315    fn render_key(&self) -> DiffKey {
316        self.key.clone().or(self.default_key())
317    }
318}
319
320/// A table component with rows and columns.
321///
322/// # Example
323///
324/// ```rust
325/// # use freya::prelude::*;
326/// fn app() -> impl IntoElement {
327///     Table::new()
328///         .child(
329///             TableHead::new().child(
330///                 TableRow::new()
331///                     .child(TableCell::new().child("Header 1"))
332///                     .child(TableCell::new().child("Header 2")),
333///             ),
334///         )
335///         .child(
336///             TableBody::new().child(
337///                 TableRow::new()
338///                     .child(TableCell::new().child("Data 1"))
339///                     .child(TableCell::new().child("Data 2")),
340///             ),
341///         )
342///         .child(
343///             TableBody::new().child(
344///                 TableRow::new()
345///                     .child(TableCell::new().child("Data 3"))
346///                     .child(TableCell::new().child("Data 4")),
347///             ),
348///         )
349/// }
350/// # use freya_testing::prelude::*;
351/// # launch_doc(|| {
352/// #   rect().padding(8.).center().expanded().child(
353/// #       app()
354/// #   )
355/// # }, "./images/gallery_table.png")
356/// #   .with_hook(|t| { t.move_cursor((125., 125.)); t.sync_and_update(); })
357/// #   .with_scale_factor(0.9)
358/// #   .render();
359/// ```
360///
361/// # Preview
362/// ![Table Preview][table]
363#[cfg_attr(feature = "docs",
364    doc = embed_doc_image::embed_image!("table", "images/gallery_table.png"),
365)]
366#[derive(PartialEq)]
367pub struct Table {
368    pub height: Size,
369    pub theme: Option<TableThemePartial>,
370    pub column_widths: Option<Vec<Size>>,
371    pub children: Vec<Element>,
372    key: DiffKey,
373}
374
375impl Default for Table {
376    fn default() -> Self {
377        Self {
378            height: Size::Inner,
379            theme: None,
380            column_widths: None,
381            children: vec![],
382            key: DiffKey::None,
383        }
384    }
385}
386
387impl Table {
388    pub fn new() -> Self {
389        Self {
390            ..Default::default()
391        }
392    }
393
394    pub fn height(mut self, height: impl Into<Size>) -> Self {
395        self.height = height.into();
396        self
397    }
398
399    pub fn theme(mut self, theme: TableThemePartial) -> Self {
400        self.theme = Some(theme);
401        self
402    }
403
404    /// Set custom widths for each column.
405    ///
406    /// Accepts any [Size], defaults to [Size::Flex].
407    pub fn column_widths(mut self, widths: impl Into<Vec<Size>>) -> Self {
408        self.column_widths = Some(widths.into());
409        self
410    }
411}
412
413impl ChildrenExt for Table {
414    fn get_children(&mut self) -> &mut Vec<Element> {
415        &mut self.children
416    }
417}
418
419impl KeyExt for Table {
420    fn write_key(&mut self) -> &mut DiffKey {
421        &mut self.key
422    }
423}
424
425#[derive(Clone, Default)]
426pub struct TableConfig {
427    pub column_widths: Option<Vec<Size>>,
428}
429
430impl TableConfig {
431    pub fn new() -> Self {
432        Self::default()
433    }
434
435    pub fn with_column_widths(column_widths: Vec<Size>) -> Self {
436        Self {
437            column_widths: Some(column_widths),
438        }
439    }
440}
441
442impl Component for Table {
443    fn render(&self) -> impl IntoElement {
444        let TableTheme {
445            background,
446            corner_radius,
447            divider_fill,
448            color,
449            ..
450        } = get_theme!(&self.theme, TableThemePreference, "table");
451
452        let config = match &self.column_widths {
453            Some(widths) => TableConfig::with_column_widths(widths.clone()),
454            None => TableConfig::default(),
455        };
456        provide_context(config);
457
458        rect()
459            .overflow(Overflow::Clip)
460            .color(color)
461            .background(background)
462            .corner_radius(corner_radius)
463            .height(self.height.clone())
464            .border(
465                Border::new()
466                    .alignment(BorderAlignment::Outer)
467                    .fill(divider_fill)
468                    .width(1.0),
469            )
470            .children(self.children.clone())
471    }
472
473    fn render_key(&self) -> DiffKey {
474        self.key.clone().or(self.default_key())
475    }
476}