use super::Modal;
#[derive(Default)]
pub struct ModalStack {
inner: Vec<Box<dyn Modal>>,
}
impl ModalStack {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, modal: Box<dyn Modal>) {
self.inner.push(modal);
}
pub fn pop(&mut self) -> Option<Box<dyn Modal>> {
self.inner.pop()
}
pub fn top_mut(&mut self) -> Option<&mut dyn Modal> {
match self.inner.last_mut() {
Some(b) => Some(&mut **b),
None => None,
}
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn any_shown(&self) -> bool {
self.inner.iter().any(|m| m.is_shown())
}
pub fn next_deadline(&self) -> Option<std::time::Instant> {
self.inner.iter().filter_map(|m| m.next_deadline()).min()
}
#[allow(dead_code)] pub fn len(&self) -> usize {
self.inner.len()
}
pub fn remove_first<T: Modal + 'static>(&mut self) -> bool {
if let Some(idx) = self.inner.iter().position(|m| m.as_any().is::<T>()) {
self.inner.remove(idx);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn contains<T: Modal + 'static>(&self) -> bool {
self.inner.iter().any(|m| m.as_any().is::<T>())
}
#[allow(dead_code)]
pub fn count<T: Modal + 'static>(&self) -> usize {
self.inner.iter().filter(|m| m.as_any().is::<T>()).count()
}
pub fn find_first_mut<T: Modal + 'static>(&mut self) -> Option<&mut T> {
self.inner
.iter_mut()
.find(|m| m.as_any().is::<T>())
.and_then(|m| m.as_any_mut().downcast_mut::<T>())
}
pub fn find_first<T: Modal + 'static>(&self) -> Option<&T> {
self.inner
.iter()
.find(|m| m.as_any().is::<T>())
.and_then(|m| m.as_any().downcast_ref::<T>())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::app::modal::types::{ModalOutcome, ModalRenderCtx};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::layout::Rect;
use ratatui::Frame;
use std::any::Any;
struct ModalA;
struct ModalB;
impl Modal for ModalA {
fn render(&mut self, _f: &mut Frame<'_>, _a: Rect, _c: &ModalRenderCtx<'_>) {}
fn handle_key(
&mut self,
_k: KeyEvent,
_app: &mut crate::app::App,
_h: usize,
_w: usize,
) -> ModalOutcome {
ModalOutcome::Continue
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl Modal for ModalB {
fn render(&mut self, _f: &mut Frame<'_>, _a: Rect, _c: &ModalRenderCtx<'_>) {}
fn handle_key(
&mut self,
_k: KeyEvent,
_app: &mut crate::app::App,
_h: usize,
_w: usize,
) -> ModalOutcome {
ModalOutcome::Continue
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[test]
fn push_and_pop_preserve_order() {
let mut stack = ModalStack::new();
stack.push(Box::new(ModalA));
stack.push(Box::new(ModalB));
assert_eq!(stack.len(), 2);
let top = stack.pop().unwrap();
assert!(top.as_any().is::<ModalB>());
assert_eq!(stack.len(), 1);
let bottom = stack.pop().unwrap();
assert!(bottom.as_any().is::<ModalA>());
assert!(stack.is_empty());
}
#[test]
fn contains_detects_present_type() {
let mut stack = ModalStack::new();
stack.push(Box::new(ModalA));
stack.push(Box::new(ModalB));
assert!(stack.contains::<ModalA>());
assert!(stack.contains::<ModalB>());
}
#[test]
fn contains_returns_false_when_absent() {
let mut stack = ModalStack::new();
stack.push(Box::new(ModalA));
assert!(!stack.contains::<ModalB>());
}
#[test]
fn remove_first_drops_queued_modal_below_top() {
let mut stack = ModalStack::new();
stack.push(Box::new(ModalB)); stack.push(Box::new(ModalA)); assert!(stack.remove_first::<ModalB>());
assert_eq!(stack.len(), 1);
assert!(stack.contains::<ModalA>());
assert!(!stack.contains::<ModalB>());
}
#[test]
fn remove_first_returns_false_when_no_match() {
let mut stack = ModalStack::new();
stack.push(Box::new(ModalA));
assert!(!stack.remove_first::<ModalB>());
assert_eq!(stack.len(), 1);
}
#[test]
fn top_mut_returns_topmost_only() {
let mut stack = ModalStack::new();
stack.push(Box::new(ModalA));
stack.push(Box::new(ModalB));
let top = stack.top_mut().unwrap();
assert!(top.as_any().is::<ModalB>());
}
#[test]
fn empty_stack_returns_none() {
let mut stack = ModalStack::new();
assert!(stack.top_mut().is_none());
assert!(stack.pop().is_none());
assert!(stack.is_empty());
}
fn _key(_code: KeyCode) -> KeyEvent {
KeyEvent::new(_code, KeyModifiers::NONE)
}
}