use embedded_graphics::{prelude::Point, primitives::Rectangle};
use heapless::Vec;
use super::{Animation, Curve, Layer, lerp_i32};
const STACK_ANIMATION_MS: u32 = 320;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StackError {
Busy,
Full,
RootLocked,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StackLayers<K> {
pub base: Layer<K>,
pub overlay: Option<Layer<K>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Transition<K> {
Push {
from: K,
to: K,
animation: Animation,
},
Pop {
from: K,
to: K,
animation: Animation,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HeaderTransition<K> {
Push { from: K, to: K, progress: u16 },
Pop { from: K, to: K, progress: u16 },
}
#[derive(Debug)]
pub struct StackNav<K, const N: usize> {
stack: Vec<K, N>,
transition: Option<Transition<K>>,
}
impl<K: Copy, const N: usize> StackNav<K, N> {
pub fn new(root: K) -> Self {
let mut stack = Vec::new();
let _ = stack.push(root);
Self {
stack,
transition: None,
}
}
pub fn top(&self) -> K {
*self
.stack
.last()
.expect("stack always contains a root view")
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn is_animating(&self) -> bool {
self.transition.is_some()
}
pub(crate) fn previous(&self) -> Option<K> {
self.stack.get(self.stack.len().checked_sub(2)?).copied()
}
pub(crate) fn header_transition(&self) -> Option<HeaderTransition<K>> {
match self.transition {
None => None,
Some(Transition::Push {
from,
to,
animation,
}) => Some(HeaderTransition::Push {
from,
to,
progress: animation.progress_permille(),
}),
Some(Transition::Pop {
from,
to,
animation,
}) => Some(HeaderTransition::Pop {
from,
to,
progress: animation.progress_permille(),
}),
}
}
pub fn push(&mut self, key: K) -> Result<(), StackError> {
if self.transition.is_some() {
return Err(StackError::Busy);
}
let from = self.top();
self.stack.push(key).map_err(|_| StackError::Full)?;
self.transition = Some(Transition::Push {
from,
to: key,
animation: Animation::new(STACK_ANIMATION_MS, Curve::EaseInOut),
});
Ok(())
}
pub fn pop(&mut self) -> Result<K, StackError> {
if self.transition.is_some() {
return Err(StackError::Busy);
}
if self.stack.len() <= 1 {
return Err(StackError::RootLocked);
}
let from = self.stack.pop().ok_or(StackError::RootLocked)?;
let to = self.top();
self.transition = Some(Transition::Pop {
from,
to,
animation: Animation::new(STACK_ANIMATION_MS, Curve::EaseInOut),
});
Ok(from)
}
pub fn advance(&mut self, dt_ms: u32) -> bool {
let Some(mut transition) = self.transition.take() else {
return false;
};
let was_running = match &transition {
Transition::Push { animation, .. } | Transition::Pop { animation, .. } => {
animation.is_running()
}
};
let is_running = match &mut transition {
Transition::Push { animation, .. } | Transition::Pop { animation, .. } => {
animation.advance(dt_ms)
}
};
if is_running {
self.transition = Some(transition);
}
was_running
}
pub fn layers(&self, frame: Rectangle) -> StackLayers<K> {
let width = frame.size.width as i32;
let idle = Layer::new(self.top(), frame, Point::zero());
match self.transition {
None => StackLayers {
base: idle,
overlay: None,
},
Some(Transition::Push {
from,
to,
animation,
}) => {
let progress = animation.progress_permille();
let base = Layer::new(from, frame, Point::zero());
let overlay = Layer::new(to, frame, Point::new(lerp_i32(width, 0, progress), 0));
StackLayers {
base,
overlay: Some(overlay),
}
}
Some(Transition::Pop {
from,
to,
animation,
}) => {
let progress = animation.progress_permille();
let base = Layer::new(to, frame, Point::zero());
let overlay = Layer::new(from, frame, Point::new(lerp_i32(0, width, progress), 0));
StackLayers {
base,
overlay: Some(overlay),
}
}
}
}
}