Skip to main content

embedded_gui/
screen.rs

1use heapless::Vec;
2
3use crate::{GuiContext, GuiError, input::InputEvent};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct ScreenId(pub u16);
7
8impl ScreenId {
9    pub const fn new(raw: u16) -> Self {
10        Self(raw)
11    }
12
13    pub const fn raw(self) -> u16 {
14        self.0
15    }
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum ScreenCommand {
20    None,
21    Push(ScreenId),
22    Pop,
23    Replace(ScreenId),
24    ClearTo(ScreenId),
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum ScreenStackError {
29    Full,
30    Empty,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum ScreenLifecycleEvent {
35    Mount(ScreenId),
36    Unmount(ScreenId),
37    Pause(ScreenId),
38    Resume(ScreenId),
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct ScreenTransition {
43    pub from: Option<ScreenId>,
44    pub to: Option<ScreenId>,
45    pub command: ScreenCommand,
46}
47
48pub trait Screen<'a, const NODES: usize, const EVENTS: usize, const DIRTY: usize> {
49    fn id(&self) -> ScreenId;
50
51    fn on_mount(
52        &mut self,
53        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
54    ) -> Result<(), GuiError> {
55        Ok(())
56    }
57
58    fn on_unmount(
59        &mut self,
60        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
61    ) -> Result<(), GuiError> {
62        Ok(())
63    }
64
65    fn on_pause(
66        &mut self,
67        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
68    ) -> Result<(), GuiError> {
69        Ok(())
70    }
71
72    fn on_resume(
73        &mut self,
74        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
75    ) -> Result<(), GuiError> {
76        Ok(())
77    }
78
79    fn handle_input(
80        &mut self,
81        _event: InputEvent,
82        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
83    ) -> Result<ScreenCommand, GuiError> {
84        Ok(ScreenCommand::None)
85    }
86
87    fn tick(
88        &mut self,
89        _dt_ms: u32,
90        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
91    ) -> Result<ScreenCommand, GuiError> {
92        Ok(ScreenCommand::None)
93    }
94}
95
96pub struct ScreenStack<const N: usize> {
97    stack: Vec<ScreenId, N>,
98}
99
100impl<const N: usize> ScreenStack<N> {
101    pub const fn new() -> Self {
102        Self { stack: Vec::new() }
103    }
104
105    pub fn with_root(root: ScreenId) -> Result<Self, ScreenStackError> {
106        let mut stack = Self::new();
107        stack.push(root)?;
108        Ok(stack)
109    }
110
111    pub fn with_root_lifecycle<const M: usize>(
112        root: ScreenId,
113        events: &mut Vec<ScreenLifecycleEvent, M>,
114    ) -> Result<Self, ScreenStackError> {
115        let stack = Self::with_root(root)?;
116        push_lifecycle(events, ScreenLifecycleEvent::Mount(root))?;
117        Ok(stack)
118    }
119
120    pub fn push(&mut self, id: ScreenId) -> Result<(), ScreenStackError> {
121        self.stack.push(id).map_err(|_| ScreenStackError::Full)
122    }
123
124    pub fn pop(&mut self) -> Result<ScreenId, ScreenStackError> {
125        self.stack.pop().ok_or(ScreenStackError::Empty)
126    }
127
128    pub fn replace(&mut self, id: ScreenId) -> Result<(), ScreenStackError> {
129        if self.stack.pop().is_none() {
130            return Err(ScreenStackError::Empty);
131        }
132        self.push(id)
133    }
134
135    pub fn clear_to(&mut self, id: ScreenId) -> Result<(), ScreenStackError> {
136        self.stack.clear();
137        self.push(id)
138    }
139
140    pub fn apply(&mut self, command: ScreenCommand) -> Result<(), ScreenStackError> {
141        match command {
142            ScreenCommand::None => Ok(()),
143            ScreenCommand::Push(id) => self.push(id),
144            ScreenCommand::Pop => self.pop().map(|_| ()),
145            ScreenCommand::Replace(id) => self.replace(id),
146            ScreenCommand::ClearTo(id) => self.clear_to(id),
147        }
148    }
149
150    pub fn apply_lifecycle<const M: usize>(
151        &mut self,
152        command: ScreenCommand,
153        events: &mut Vec<ScreenLifecycleEvent, M>,
154    ) -> Result<ScreenTransition, ScreenStackError> {
155        let from = self.current();
156        match command {
157            ScreenCommand::None => Ok(ScreenTransition {
158                from,
159                to: from,
160                command,
161            }),
162            ScreenCommand::Push(id) => {
163                if let Some(current) = self.current() {
164                    push_lifecycle(events, ScreenLifecycleEvent::Pause(current))?;
165                }
166                self.push(id)?;
167                push_lifecycle(events, ScreenLifecycleEvent::Mount(id))?;
168                Ok(ScreenTransition {
169                    from,
170                    to: self.current(),
171                    command,
172                })
173            }
174            ScreenCommand::Pop => {
175                let old = self.pop()?;
176                push_lifecycle(events, ScreenLifecycleEvent::Unmount(old))?;
177                if let Some(current) = self.current() {
178                    push_lifecycle(events, ScreenLifecycleEvent::Resume(current))?;
179                }
180                Ok(ScreenTransition {
181                    from,
182                    to: self.current(),
183                    command,
184                })
185            }
186            ScreenCommand::Replace(id) => {
187                let old = self.pop()?;
188                push_lifecycle(events, ScreenLifecycleEvent::Unmount(old))?;
189                self.push(id)?;
190                push_lifecycle(events, ScreenLifecycleEvent::Mount(id))?;
191                Ok(ScreenTransition {
192                    from,
193                    to: self.current(),
194                    command,
195                })
196            }
197            ScreenCommand::ClearTo(id) => {
198                while let Some(old) = self.stack.pop() {
199                    push_lifecycle(events, ScreenLifecycleEvent::Unmount(old))?;
200                }
201                self.push(id)?;
202                push_lifecycle(events, ScreenLifecycleEvent::Mount(id))?;
203                Ok(ScreenTransition {
204                    from,
205                    to: self.current(),
206                    command,
207                })
208            }
209        }
210    }
211
212    pub fn current(&self) -> Option<ScreenId> {
213        self.stack.last().copied()
214    }
215
216    pub fn as_slice(&self) -> &[ScreenId] {
217        self.stack.as_slice()
218    }
219
220    pub fn len(&self) -> usize {
221        self.stack.len()
222    }
223
224    pub fn is_empty(&self) -> bool {
225        self.stack.is_empty()
226    }
227}
228
229fn push_lifecycle<const M: usize>(
230    events: &mut Vec<ScreenLifecycleEvent, M>,
231    event: ScreenLifecycleEvent,
232) -> Result<(), ScreenStackError> {
233    events.push(event).map_err(|_| ScreenStackError::Full)
234}
235
236impl<const N: usize> Default for ScreenStack<N> {
237    fn default() -> Self {
238        Self::new()
239    }
240}