Skip to main content

panes/preset/
dashboard.rs

1use std::sync::Arc;
2
3use taffy::prelude::fr;
4
5use crate::builder::LayoutBuilder;
6use crate::error::{ConstraintError, PaneError, TreeError};
7use crate::layout::Layout;
8
9/// Builder for the grid-based dashboard preset layout.
10pub struct Dashboard {
11    cards: Arc<[(Arc<str>, usize)]>,
12    columns: usize,
13    gap: f32,
14}
15
16impl Dashboard {
17    pub(crate) fn new(cards: impl IntoIterator<Item = (impl Into<Arc<str>>, usize)>) -> Self {
18        Self {
19            cards: cards
20                .into_iter()
21                .map(|(k, span)| (k.into(), span))
22                .collect(),
23            columns: 4,
24            gap: 0.0,
25        }
26    }
27
28    /// Set the number of columns.
29    pub fn columns(mut self, columns: usize) -> Self {
30        self.columns = columns;
31        self
32    }
33
34    /// Set the gap between panels.
35    pub fn gap(mut self, gap: f32) -> Self {
36        self.gap = gap;
37        self
38    }
39
40    /// Consume the builder and produce a [`Layout`].
41    pub fn build(&self) -> Result<Layout, PaneError> {
42        match self.cards.is_empty() {
43            true => {
44                return Err(PaneError::InvalidTree(TreeError::DashboardNoCards));
45            }
46            _ => {}
47        }
48        match self.columns {
49            0 => {
50                return Err(PaneError::InvalidTree(TreeError::DashboardNoColumns));
51            }
52            _ => {}
53        }
54
55        let mut b = LayoutBuilder::new();
56        let grid_style = self.grid_root_style();
57
58        b.row(|r| {
59            r.taffy_node(grid_style, |grid| add_cards(grid, &self.cards));
60        })?;
61
62        b.build()
63    }
64
65    fn grid_root_style(&self) -> taffy::Style {
66        let gap_len = taffy::LengthPercentage::length(self.gap);
67        taffy::Style {
68            display: taffy::Display::Grid,
69            size: taffy::Size {
70                width: taffy::Dimension::percent(1.0),
71                height: taffy::Dimension::percent(1.0),
72            },
73            grid_template_columns: vec![fr(1.0); self.columns],
74            grid_auto_rows: vec![fr(1.0)],
75            gap: taffy::Size {
76                width: gap_len,
77                height: gap_len,
78            },
79            ..Default::default()
80        }
81    }
82}
83
84fn card_style(span: usize) -> Result<taffy::Style, PaneError> {
85    let span_u16 = u16::try_from(span)
86        .map_err(|_| PaneError::InvalidConstraint(ConstraintError::GridSpanOverflow(span)))?;
87    Ok(taffy::Style {
88        grid_column: taffy::Line {
89            start: taffy::GridPlacement::Auto,
90            end: taffy::GridPlacement::Span(span_u16),
91        },
92        ..Default::default()
93    })
94}
95
96fn add_cards(ctx: &mut crate::ContainerCtx, cards: &[(Arc<str>, usize)]) {
97    for (kind, span) in cards {
98        match card_style(*span) {
99            Ok(style) => {
100                ctx.taffy_node(style, |inner| {
101                    inner.panel(Arc::clone(kind));
102                });
103            }
104            Err(e) => {
105                ctx.set_error(e);
106                return;
107            }
108        }
109    }
110}
111
112impl Dashboard {
113    /// Consume the builder and produce a [`crate::runtime::LayoutRuntime`].
114    pub fn into_runtime(self) -> Result<crate::runtime::LayoutRuntime, PaneError> {
115        let spans: Arc<[usize]> = self.cards.iter().map(|(_, s)| *s).collect();
116        let kinds: Vec<Arc<str>> = self.cards.iter().map(|(k, _)| Arc::clone(k)).collect();
117        let strategy = crate::strategy::StrategyKind::Dashboard {
118            columns: self.columns,
119            gap: self.gap,
120            spans,
121        };
122        crate::runtime::LayoutRuntime::from_strategy(strategy, &kinds)
123    }
124}
125
126super::impl_preset!(Dashboard);