embedded-gui 0.1.0

no_std GUI and HUD primitives for embedded-graphics displays
Documentation
use heapless::Vec;

use crate::{GuiContext, GuiError, input::InputEvent};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScreenId(pub u16);

impl ScreenId {
    pub const fn new(raw: u16) -> Self {
        Self(raw)
    }

    pub const fn raw(self) -> u16 {
        self.0
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScreenCommand {
    None,
    Push(ScreenId),
    Pop,
    Replace(ScreenId),
    ClearTo(ScreenId),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScreenStackError {
    Full,
    Empty,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScreenLifecycleEvent {
    Mount(ScreenId),
    Unmount(ScreenId),
    Pause(ScreenId),
    Resume(ScreenId),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScreenTransition {
    pub from: Option<ScreenId>,
    pub to: Option<ScreenId>,
    pub command: ScreenCommand,
}

pub trait Screen<'a, const NODES: usize, const EVENTS: usize, const DIRTY: usize> {
    fn id(&self) -> ScreenId;

    fn on_mount(
        &mut self,
        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
    ) -> Result<(), GuiError> {
        Ok(())
    }

    fn on_unmount(
        &mut self,
        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
    ) -> Result<(), GuiError> {
        Ok(())
    }

    fn on_pause(
        &mut self,
        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
    ) -> Result<(), GuiError> {
        Ok(())
    }

    fn on_resume(
        &mut self,
        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
    ) -> Result<(), GuiError> {
        Ok(())
    }

    fn handle_input(
        &mut self,
        _event: InputEvent,
        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
    ) -> Result<ScreenCommand, GuiError> {
        Ok(ScreenCommand::None)
    }

    fn tick(
        &mut self,
        _dt_ms: u32,
        _gui: &mut GuiContext<'a, NODES, EVENTS, DIRTY>,
    ) -> Result<ScreenCommand, GuiError> {
        Ok(ScreenCommand::None)
    }
}

pub struct ScreenStack<const N: usize> {
    stack: Vec<ScreenId, N>,
}

impl<const N: usize> ScreenStack<N> {
    pub const fn new() -> Self {
        Self { stack: Vec::new() }
    }

    pub fn with_root(root: ScreenId) -> Result<Self, ScreenStackError> {
        let mut stack = Self::new();
        stack.push(root)?;
        Ok(stack)
    }

    pub fn with_root_lifecycle<const M: usize>(
        root: ScreenId,
        events: &mut Vec<ScreenLifecycleEvent, M>,
    ) -> Result<Self, ScreenStackError> {
        let stack = Self::with_root(root)?;
        push_lifecycle(events, ScreenLifecycleEvent::Mount(root))?;
        Ok(stack)
    }

    pub fn push(&mut self, id: ScreenId) -> Result<(), ScreenStackError> {
        self.stack.push(id).map_err(|_| ScreenStackError::Full)
    }

    pub fn pop(&mut self) -> Result<ScreenId, ScreenStackError> {
        self.stack.pop().ok_or(ScreenStackError::Empty)
    }

    pub fn replace(&mut self, id: ScreenId) -> Result<(), ScreenStackError> {
        if self.stack.pop().is_none() {
            return Err(ScreenStackError::Empty);
        }
        self.push(id)
    }

    pub fn clear_to(&mut self, id: ScreenId) -> Result<(), ScreenStackError> {
        self.stack.clear();
        self.push(id)
    }

    pub fn apply(&mut self, command: ScreenCommand) -> Result<(), ScreenStackError> {
        match command {
            ScreenCommand::None => Ok(()),
            ScreenCommand::Push(id) => self.push(id),
            ScreenCommand::Pop => self.pop().map(|_| ()),
            ScreenCommand::Replace(id) => self.replace(id),
            ScreenCommand::ClearTo(id) => self.clear_to(id),
        }
    }

    pub fn apply_lifecycle<const M: usize>(
        &mut self,
        command: ScreenCommand,
        events: &mut Vec<ScreenLifecycleEvent, M>,
    ) -> Result<ScreenTransition, ScreenStackError> {
        let from = self.current();
        match command {
            ScreenCommand::None => Ok(ScreenTransition {
                from,
                to: from,
                command,
            }),
            ScreenCommand::Push(id) => {
                if let Some(current) = self.current() {
                    push_lifecycle(events, ScreenLifecycleEvent::Pause(current))?;
                }
                self.push(id)?;
                push_lifecycle(events, ScreenLifecycleEvent::Mount(id))?;
                Ok(ScreenTransition {
                    from,
                    to: self.current(),
                    command,
                })
            }
            ScreenCommand::Pop => {
                let old = self.pop()?;
                push_lifecycle(events, ScreenLifecycleEvent::Unmount(old))?;
                if let Some(current) = self.current() {
                    push_lifecycle(events, ScreenLifecycleEvent::Resume(current))?;
                }
                Ok(ScreenTransition {
                    from,
                    to: self.current(),
                    command,
                })
            }
            ScreenCommand::Replace(id) => {
                let old = self.pop()?;
                push_lifecycle(events, ScreenLifecycleEvent::Unmount(old))?;
                self.push(id)?;
                push_lifecycle(events, ScreenLifecycleEvent::Mount(id))?;
                Ok(ScreenTransition {
                    from,
                    to: self.current(),
                    command,
                })
            }
            ScreenCommand::ClearTo(id) => {
                while let Some(old) = self.stack.pop() {
                    push_lifecycle(events, ScreenLifecycleEvent::Unmount(old))?;
                }
                self.push(id)?;
                push_lifecycle(events, ScreenLifecycleEvent::Mount(id))?;
                Ok(ScreenTransition {
                    from,
                    to: self.current(),
                    command,
                })
            }
        }
    }

    pub fn current(&self) -> Option<ScreenId> {
        self.stack.last().copied()
    }

    pub fn as_slice(&self) -> &[ScreenId] {
        self.stack.as_slice()
    }

    pub fn len(&self) -> usize {
        self.stack.len()
    }

    pub fn is_empty(&self) -> bool {
        self.stack.is_empty()
    }
}

fn push_lifecycle<const M: usize>(
    events: &mut Vec<ScreenLifecycleEvent, M>,
    event: ScreenLifecycleEvent,
) -> Result<(), ScreenStackError> {
    events.push(event).map_err(|_| ScreenStackError::Full)
}

impl<const N: usize> Default for ScreenStack<N> {
    fn default() -> Self {
        Self::new()
    }
}