pub mod confirm;
pub mod help;
pub mod new_session;
pub mod quickjump;
pub mod recents;
pub mod rename;
pub mod section;
pub mod theme;
use crossterm::event::KeyEvent;
use ratatui::layout::Rect;
use ratatui::Frame;
use crate::events::{Command, SessionSpec};
use crate::ui::Theme;
pub enum ModalData {
FillSessionSpec(SessionSpec),
}
pub enum ModalResult {
Consumed,
#[allow(dead_code)]
PassThrough,
Close(Option<Command>),
CloseWithData(ModalData),
Push(Box<dyn Modal>),
EmitCommand(Command),
}
pub trait Modal: Send {
fn id(&self) -> &'static str;
fn render(&self, frame: &mut Frame<'_>, area: Rect, theme: &Theme);
fn handle(&mut self, key: KeyEvent) -> ModalResult;
fn on_child_closed(&mut self, _data: ModalData) {}
}
#[derive(Default)]
pub struct ModalStack {
stack: Vec<Box<dyn Modal>>,
}
impl std::fmt::Debug for ModalStack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ModalStack")
.field("depth", &self.stack.len())
.field(
"top",
&self.stack.last().map(|m| m.id()).unwrap_or("<empty>"),
)
.finish()
}
}
impl ModalStack {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn len(&self) -> usize {
self.stack.len()
}
pub fn push(&mut self, modal: Box<dyn Modal>) {
self.stack.push(modal);
}
pub fn pop(&mut self) -> Option<Box<dyn Modal>> {
self.stack.pop()
}
pub fn top_id(&self) -> Option<&'static str> {
self.stack.last().map(|m| m.id())
}
pub fn render(&self, frame: &mut Frame<'_>, area: Rect, theme: &Theme) {
if self.stack.is_empty() {
return;
}
dim_background(frame, area, theme);
for modal in &self.stack {
modal.render(frame, area, theme);
}
}
pub fn dispatch(&mut self, key: KeyEvent) -> StackDispatch {
if self.stack.is_empty() {
return StackDispatch::PassThrough;
}
let top = self.stack.last_mut().unwrap();
match top.handle(key) {
ModalResult::Consumed => StackDispatch::Consumed,
ModalResult::PassThrough => StackDispatch::PassThrough,
ModalResult::Close(cmd) => {
self.stack.pop();
StackDispatch::Closed(cmd)
}
ModalResult::CloseWithData(data) => {
self.stack.pop();
if let Some(parent) = self.stack.last_mut() {
parent.on_child_closed(data);
}
StackDispatch::Consumed
}
ModalResult::Push(child) => {
self.stack.push(child);
StackDispatch::Consumed
}
ModalResult::EmitCommand(cmd) => StackDispatch::Emit(cmd),
}
}
}
pub enum StackDispatch {
Consumed,
PassThrough,
Closed(Option<Command>),
Emit(Command),
}
fn dim_background(frame: &mut Frame<'_>, area: Rect, theme: &Theme) {
let buf = frame.buffer_mut();
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
let cell = &mut buf[(x, y)];
cell.set_fg(theme.dim_fg);
cell.set_bg(theme.bg);
}
}
}
pub fn center_rect(outer: Rect, width: u16, height: u16) -> Rect {
let w = width.min(outer.width);
let h = height.min(outer.height);
let x = outer.x + outer.width.saturating_sub(w) / 2;
let y = outer.y + outer.height.saturating_sub(h) / 2;
Rect::new(x, y, w, h)
}