Skip to main content

tilo_app/framework/views/
stack.rs

1//! A window's views as a stack: back and forward always work, and returning
2//! to a view puts it back as it was left, scrolled to the same place with the
3//! same selection.
4
5/// Where a person was in a view: what a return to it restores.
6#[derive(Clone, Debug, Default, PartialEq)]
7pub struct ViewPlace {
8    /// How far the view was scrolled, in logical pixels.
9    pub scroll: f64,
10    /// The ids of the items selected in it, in order.
11    pub selection: Vec<String>,
12    /// The id of the item that held the keyboard focus.
13    pub focus: Option<String>,
14}
15
16/// A view the stack holds and where the person was in it.
17#[derive(Clone, Debug, PartialEq)]
18pub struct ViewEntry<V> {
19    pub view: V,
20    pub place: ViewPlace,
21}
22
23/// The views of one window: the one shown, the ones Back returns to and the
24/// ones Forward goes on to.
25///
26/// Every move hands in where the person is in the view being left, so the
27/// stack can give it back on return. Opening a view drops what Forward held,
28/// as a browser does.
29#[derive(Clone, Debug)]
30pub struct ViewStack<V> {
31    back: Vec<ViewEntry<V>>,
32    current: ViewEntry<V>,
33    forward: Vec<ViewEntry<V>>,
34}
35
36impl<V: Clone + PartialEq> ViewStack<V> {
37    /// A stack showing `root`, with nothing to go back or forward to.
38    #[must_use]
39    pub fn new(root: V) -> Self {
40        Self {
41            back: Vec::new(),
42            current: ViewEntry {
43                view: root,
44                place: ViewPlace::default(),
45            },
46            forward: Vec::new(),
47        }
48    }
49
50    /// The view shown.
51    #[must_use]
52    pub fn current(&self) -> &V {
53        &self.current.view
54    }
55
56    /// Where the person is in the view shown, as last handed in.
57    #[must_use]
58    pub fn place(&self) -> &ViewPlace {
59        &self.current.place
60    }
61
62    #[must_use]
63    pub fn can_go_back(&self) -> bool {
64        !self.back.is_empty()
65    }
66
67    #[must_use]
68    pub fn can_go_forward(&self) -> bool {
69        !self.forward.is_empty()
70    }
71
72    #[must_use]
73    pub fn previous(&self) -> Option<&V> {
74        self.back.last().map(|entry| &entry.view)
75    }
76
77    /// Open `view`, leaving the one shown at `left`. Opening the view
78    /// already shown changes nothing. Returns whether the view changed; a new
79    /// view starts at the top.
80    pub fn open(&mut self, view: V, left: ViewPlace) -> bool {
81        if view == self.current.view {
82            return false;
83        }
84        self.forward.clear();
85        let entry = ViewEntry {
86            view,
87            place: ViewPlace::default(),
88        };
89        let mut leaving = std::mem::replace(&mut self.current, entry);
90        leaving.place = left;
91        self.back.push(leaving);
92        true
93    }
94
95    /// Show `view` in place of the one shown, without a step to go back
96    /// through: a query view whose text changed as it was typed. Where the
97    /// person was is kept.
98    pub fn replace(&mut self, view: V) {
99        self.current.view = view;
100    }
101
102    /// Go back, leaving the view shown at `left`. Returns the view now shown
103    /// and where to put the person in it; `None` with nothing to go back to.
104    pub fn back(&mut self, left: ViewPlace) -> Option<&ViewEntry<V>> {
105        let entry = self.back.pop()?;
106        let mut leaving = std::mem::replace(&mut self.current, entry);
107        leaving.place = left;
108        self.forward.push(leaving);
109        Some(&self.current)
110    }
111
112    /// Go forward again, leaving the view shown at `left`.
113    pub fn forward(&mut self, left: ViewPlace) -> Option<&ViewEntry<V>> {
114        let entry = self.forward.pop()?;
115        let mut leaving = std::mem::replace(&mut self.current, entry);
116        leaving.place = left;
117        self.back.push(leaving);
118        Some(&self.current)
119    }
120
121    /// Hand in where the person is in the view shown, without moving.
122    pub fn note_place(&mut self, place: ViewPlace) {
123        self.current.place = place;
124    }
125
126    /// Forget every held view `gone` says no longer exists (a deleted
127    /// playlist's page); the view shown is kept, and its caller decides.
128    pub fn forget(&mut self, mut gone: impl FnMut(&V) -> bool) {
129        self.back.retain(|entry| !gone(&entry.view));
130        self.forward.retain(|entry| !gone(&entry.view));
131    }
132}