use embedded_graphics::{prelude::*, primitives::Rectangle};
use super::{Animation, Curve, FsTheme, ModalLayer, lerp_i32, lerp_u8};
const MODAL_ANIMATION_MS: u32 = 320;
const DIM_ALPHA_MAX: u8 = 96;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ModalState<M> {
Hidden,
Showing { modal: M, animation: Animation },
Visible { modal: M },
Hiding { modal: M, animation: Animation },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModalHost<M> {
state: ModalState<M>,
}
impl<M: Copy> ModalHost<M> {
pub const fn new() -> Self {
Self {
state: ModalState::Hidden,
}
}
pub fn show(&mut self, modal: M) {
self.state = ModalState::Showing {
modal,
animation: Animation::new(MODAL_ANIMATION_MS, Curve::EaseInOut),
};
}
pub fn dismiss(&mut self) {
let modal = match self.state {
ModalState::Showing { modal, .. } | ModalState::Visible { modal } => Some(modal),
ModalState::Hiding { modal, .. } => Some(modal),
ModalState::Hidden => None,
};
if let Some(modal) = modal {
self.state = ModalState::Hiding {
modal,
animation: Animation::new(MODAL_ANIMATION_MS, Curve::EaseInOut),
};
}
}
pub const fn is_animating(&self) -> bool {
matches!(
self.state,
ModalState::Showing { .. } | ModalState::Hiding { .. }
)
}
pub const fn has_modal(&self) -> bool {
!matches!(self.state, ModalState::Hidden)
}
pub fn advance(&mut self, dt_ms: u32) -> bool {
match &mut self.state {
ModalState::Hidden | ModalState::Visible { .. } => false,
ModalState::Showing { modal, animation } => {
let was_running = animation.is_running();
let is_running = animation.advance(dt_ms);
if is_running {
true
} else {
self.state = ModalState::Visible { modal: *modal };
was_running
}
}
ModalState::Hiding { animation, .. } => {
let was_running = animation.is_running();
let is_running = animation.advance(dt_ms);
if is_running {
true
} else {
self.state = ModalState::Hidden;
was_running
}
}
}
}
pub fn current(&self, bounds: Rectangle, theme: &FsTheme) -> Option<ModalLayer<M>> {
self.current_with_panel(bounds, modal_panel(bounds, theme))
}
pub fn current_with_panel(&self, bounds: Rectangle, panel: Rectangle) -> Option<ModalLayer<M>> {
let offscreen = offscreen_offset(bounds, panel);
match self.state {
ModalState::Hidden => None,
ModalState::Visible { modal } => Some(ModalLayer::new(modal, panel, 0, DIM_ALPHA_MAX)),
ModalState::Showing { modal, animation } => {
let progress = animation.progress_permille();
Some(ModalLayer::new(
modal,
panel,
lerp_i32(offscreen, 0, progress),
lerp_u8(0, DIM_ALPHA_MAX, progress),
))
}
ModalState::Hiding { modal, animation } => {
let progress = animation.progress_permille();
Some(ModalLayer::new(
modal,
panel,
lerp_i32(0, offscreen, progress),
lerp_u8(DIM_ALPHA_MAX, 0, progress),
))
}
}
}
}
fn modal_panel(bounds: Rectangle, theme: &FsTheme) -> Rectangle {
let max_width = bounds
.size
.width
.saturating_sub(theme.modal_margin.saturating_mul(2));
let width = ((bounds.size.width.saturating_mul(3)) / 5)
.max(320)
.min(max_width);
let height = (bounds.size.height / 3).max(176).min(208);
let x = bounds.top_left.x + ((bounds.size.width.saturating_sub(width)) / 2) as i32;
let y = bounds.top_left.y + ((bounds.size.height.saturating_sub(height)) / 2) as i32;
Rectangle::new(Point::new(x, y), Size::new(width, height))
}
fn offscreen_offset(bounds: Rectangle, panel: Rectangle) -> i32 {
bounds.top_left.y + bounds.size.height as i32 - panel.top_left.y + 24
}