Skip to main content

corundum_framework_gui/settings/
scope.rs

1//! [`scope_tabs`]: a tab per place a setting can be kept.
2
3use super::layout::marked;
4
5/// One tab of [`scope_tabs`]: a place a setting can be kept — this device or the whole
6/// workgroup, the client or the server. `S` is the host's own type naming them.
7#[derive(Clone, PartialEq, Eq, Debug)]
8pub struct ScopeTab<S> {
9    /// The scope the tab opens.
10    pub scope: S,
11    /// Its name, in the installed language.
12    pub label: String,
13    /// Whether it can be opened now: a workspace's tab without a workspace cannot.
14    pub enabled: bool,
15    /// Whether it holds changes not saved yet; the tab is marked.
16    pub dirty: bool,
17}
18
19impl<S> ScopeTab<S> {
20    /// An enabled tab with nothing unsaved.
21    pub fn new(scope: S, label: impl Into<String>) -> ScopeTab<S> {
22        ScopeTab {
23            scope,
24            label: label.into(),
25            enabled: true,
26            dirty: false,
27        }
28    }
29
30    /// Whether it can be opened now.
31    pub fn enabled(mut self, enabled: bool) -> ScopeTab<S> {
32        self.enabled = enabled;
33        self
34    }
35
36    /// Whether it holds changes not saved yet.
37    pub fn dirty(mut self, dirty: bool) -> ScopeTab<S> {
38        self.dirty = dirty;
39        self
40    }
41}
42
43/// The row of `tabs`, `open` the selected one, with `hint` after them (why a tab is
44/// disabled, say). An open tab that is not among the enabled ones yields to the first that
45/// is. `true` when `open` changed, by a click or by yielding: what was shown for the old
46/// tab (a field's error, say) no longer applies.
47pub fn scope_tabs<S: Copy + PartialEq>(
48    ui: &mut egui::Ui,
49    open: &mut S,
50    tabs: &[ScopeTab<S>],
51    hint: Option<&str>,
52) -> bool {
53    let before = *open;
54    if !tabs.iter().any(|t| t.enabled && t.scope == *open)
55        && let Some(first) = tabs.iter().find(|t| t.enabled)
56    {
57        *open = first.scope;
58    }
59    ui.horizontal_wrapped(|ui| {
60        for tab in tabs {
61            if ui
62                .add_enabled(
63                    tab.enabled,
64                    egui::Button::selectable(*open == tab.scope, marked(&tab.label, tab.dirty)),
65                )
66                .clicked()
67            {
68                *open = tab.scope;
69            }
70        }
71        if let Some(hint) = hint {
72            ui.small(hint);
73        }
74    });
75    *open != before
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::test_support::render;
82
83    #[derive(Clone, Copy, PartialEq, Eq, Debug)]
84    enum Scope {
85        Device,
86        Workgroup,
87        Workspace,
88    }
89
90    fn tabs(workspace: bool) -> Vec<ScopeTab<Scope>> {
91        vec![
92            ScopeTab::new(Scope::Device, "Device").dirty(true),
93            ScopeTab::new(Scope::Workgroup, "Workgroup"),
94            ScopeTab::new(Scope::Workspace, "Workspace").enabled(workspace),
95        ]
96    }
97
98    #[test]
99    fn an_enabled_open_tab_stays() {
100        let mut open = Scope::Workgroup;
101        let mut changed = false;
102        render([600.0, 400.0], |ui| {
103            changed |= scope_tabs(ui, &mut open, &tabs(true), None)
104        });
105        assert_eq!(open, Scope::Workgroup);
106        assert!(!changed);
107    }
108
109    #[test]
110    fn a_disabled_open_tab_yields_to_the_first_enabled() {
111        let mut open = Scope::Workspace;
112        let mut changed = false;
113        render([600.0, 400.0], |ui| {
114            changed |= scope_tabs(ui, &mut open, &tabs(false), Some("No workspace"))
115        });
116        assert_eq!(open, Scope::Device);
117        assert!(changed);
118    }
119
120    #[test]
121    fn with_no_tab_enabled_the_open_one_stays() {
122        let mut open = Scope::Workspace;
123        let none: Vec<_> = tabs(false).into_iter().map(|t| t.enabled(false)).collect();
124        render([600.0, 400.0], |ui| {
125            scope_tabs(ui, &mut open, &none, None);
126        });
127        assert_eq!(open, Scope::Workspace);
128    }
129}