Skip to main content

blockworx/shell/
workspace.rs

1//! What the [navigator](super::navigator) remembers about one document: which of its
2//! views is showing, and whether it is on screen at all.
3//!
4//! It does not remember a width. The size is fixed — *"a fixed size (350 px
5//! or so in the mockup), and a fixed height"* — and a size the user cannot
6//! change is not a size to remember.
7//!
8//! State only. Where the navigator sits and what it looks like is the navigator's;
9//! this is what survives the frame and, through the eframe storage DB, the
10//! session.
11
12/// One view the navigator can show — one segment, one panel body.
13#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
14pub enum PanelView {
15    History,
16    Hierarchy,
17}
18
19impl PanelView {
20    /// The views the navigator lists, in the order it lists them.
21    pub const RAIL: [PanelView; 2] = [PanelView::History, PanelView::Hierarchy];
22
23    /// The middle segment is not a Parts list — an assembly tree of parts
24    /// and subassemblies with instance counts. Blockworx's tree holds
25    /// neither: every row is a block with its own id, there is no
26    /// instancing to count and no parts list to browse, and what the tree
27    /// presents is containment depth. So the segment keeps the name of what
28    /// it shows.
29    pub fn title(self) -> &'static str {
30        match self {
31            PanelView::History => "History",
32            PanelView::Hierarchy => "Hierarchy",
33        }
34    }
35}
36
37/// Whether the navigator is on screen. A view is always named, so re-opening
38/// returns to the one the user left rather than to a default.
39#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
40pub enum Panel {
41    Showing,
42    Collapsed,
43}
44
45impl Panel {
46    fn is_showing(self) -> bool {
47        self == Panel::Showing
48    }
49}
50
51impl From<bool> for Panel {
52    fn from(showing: bool) -> Self {
53        if showing {
54            Panel::Showing
55        } else {
56            Panel::Collapsed
57        }
58    }
59}
60
61/// What the navigator remembers about one document. Persisted through the eframe
62/// storage DB beside the recent-files list.
63#[derive(Clone, Copy, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
64pub struct Workspace {
65    pub view: PanelView,
66    pub panel: Panel,
67}
68
69impl Default for Workspace {
70    fn default() -> Self {
71        Workspace {
72            view: PanelView::History,
73            panel: Panel::Collapsed,
74        }
75    }
76}
77
78impl Workspace {
79    /// Whether `view`'s body is on screen right now.
80    pub fn showing(self, view: PanelView) -> bool {
81        self.panel.is_showing() && self.view == view
82    }
83
84    /// Whether the navigator is on screen at all, whichever view it holds.
85    pub fn open(self) -> bool {
86        self.panel.is_showing()
87    }
88
89    /// A click on `view`'s segment: switch to it, and leave the navigator
90    /// open. Nothing inside the panel ever closes it, so the active segment
91    /// is not a toggle.
92    pub fn show(&mut self, view: PanelView) {
93        self.view = view;
94        self.panel = Panel::Showing;
95    }
96
97    /// Shut the navigator without changing which view it will re-open on.
98    pub fn close(&mut self) {
99        self.panel = Panel::Collapsed;
100    }
101
102    /// The top bar's Browse button: open onto the view last left, or shut.
103    pub fn toggle(&mut self) {
104        self.panel = (!self.open()).into();
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    /// A segment switches the view and never shuts the panel — not even the
113    /// one already showing. Only working dismisses it.
114    #[test]
115    fn a_segment_switches_the_view_and_never_shuts_the_panel() {
116        let mut state = Workspace::default();
117        assert!(!state.showing(PanelView::History), "opens shut");
118
119        state.show(PanelView::History);
120        assert!(state.showing(PanelView::History));
121
122        state.show(PanelView::Hierarchy);
123        assert!(
124            state.showing(PanelView::Hierarchy) && !state.showing(PanelView::History),
125            "picking another view switched nothing",
126        );
127
128        state.show(PanelView::Hierarchy);
129        assert_eq!(
130            state.panel,
131            Panel::Showing,
132            "the active segment shut the panel from inside it",
133        );
134    }
135
136    /// Browse opens onto whatever the navigator was last showing, rather
137    /// than resetting it to a default view.
138    #[test]
139    fn the_toggle_reopens_the_view_the_user_left() {
140        let mut state = Workspace::default();
141        state.show(PanelView::Hierarchy);
142        state.toggle();
143        assert!(!state.open(), "the toggle did not shut the panel");
144        state.toggle();
145        assert!(
146            state.showing(PanelView::Hierarchy),
147            "re-opening lost the view the user left",
148        );
149    }
150
151    /// Two segments, two panel bodies: a view with no segment is
152    /// unreachable, and a segment with no body opens onto nothing.
153    #[test]
154    fn every_segment_names_a_distinct_view() {
155        let titles: std::collections::HashSet<&str> =
156            PanelView::RAIL.iter().map(|v| v.title()).collect();
157        assert_eq!(titles.len(), PanelView::RAIL.len());
158    }
159}