use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use crate::overlays::{Overlay, Transition};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub enum ModalStyle {
Dialog,
Confirm,
Prompt,
FullScreen,
}
#[derive(Debug, Clone)]
pub struct Modal {
pub title: String,
pub content: String,
pub style: ModalStyle,
pub visible: bool,
pub opacity: f32,
pub transition: Transition,
pub transition_progress: f32,
pub border_color: Color,
pub title_color: Color,
pub content_color: Color,
pub bg: Color,
pub selected_index: usize,
pub options: Vec<String>,
}
impl Modal {
pub fn new(title: &str, content: &str) -> Self {
Self {
title: title.to_string(),
content: content.to_string(),
style: ModalStyle::Dialog,
visible: false,
opacity: 1.0,
transition: Transition::Fade,
transition_progress: 0.0,
border_color: Color::rgb(88, 166, 255),
title_color: Color::rgb(201, 209, 217),
content_color: Color::rgb(139, 148, 158),
bg: Color::rgb(22, 27, 34),
selected_index: 0,
options: Vec::new(),
}
}
pub fn with_style(mut self, style: ModalStyle) -> Self {
self.style = style;
self
}
pub fn with_options(mut self, options: Vec<String>) -> Self {
self.options = options;
self
}
pub fn with_border_color(mut self, color: Color) -> Self {
self.border_color = color;
self
}
pub fn with_transition(mut self, t: Transition) -> Self {
self.transition = t;
self
}
pub fn select_next(&mut self) {
if !self.options.is_empty() {
self.selected_index = (self.selected_index + 1) % self.options.len();
}
}
pub fn select_prev(&mut self) {
if !self.options.is_empty() {
self.selected_index = if self.selected_index == 0 {
self.options.len() - 1
} else {
self.selected_index - 1
};
}
}
pub fn selected_option(&self) -> Option<&str> {
self.options.get(self.selected_index).map(|s| s.as_str())
}
fn render_modal(&self, buffer: &mut Buffer, area: Rect) {
let (w, h) = match self.style {
ModalStyle::Dialog => (40.min(area.width), 12.min(area.height)),
ModalStyle::Confirm => (36.min(area.width), 8.min(area.height)),
ModalStyle::Prompt => (50.min(area.width), 6.min(area.height)),
ModalStyle::FullScreen => (area.width, area.height),
};
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let modal_rect = Rect::new(x, y, w, h);
buffer.fill(modal_rect, ' ', Color::WHITE, Some(self.bg));
for dx in 1..w.saturating_sub(1) {
buffer.set(
(x + dx) as usize,
y as usize,
crate::core::buffer::Cell {
ch: '─',
fg: self.border_color,
bg: Some(self.bg),
bold: false,
italic: false,
underlined: false,
},
);
buffer.set(
(x + dx) as usize,
(y + h - 1) as usize,
crate::core::buffer::Cell {
ch: '─',
fg: self.border_color,
bg: Some(self.bg),
bold: false,
italic: false,
underlined: false,
},
);
}
buffer.set(
x as usize,
y as usize,
crate::core::buffer::Cell {
ch: '╭',
fg: self.border_color,
bg: Some(self.bg),
bold: true,
italic: false,
underlined: false,
},
);
buffer.set(
(x + w - 1) as usize,
y as usize,
crate::core::buffer::Cell {
ch: '╮',
fg: self.border_color,
bg: Some(self.bg),
bold: true,
italic: false,
underlined: false,
},
);
buffer.set(
x as usize,
(y + h - 1) as usize,
crate::core::buffer::Cell {
ch: '╰',
fg: self.border_color,
bg: Some(self.bg),
bold: true,
italic: false,
underlined: false,
},
);
buffer.set(
(x + w - 1) as usize,
(y + h - 1) as usize,
crate::core::buffer::Cell {
ch: '╯',
fg: self.border_color,
bg: Some(self.bg),
bold: true,
italic: false,
underlined: false,
},
);
let title_text = format!(" {} ", self.title);
let title_display: String = title_text.chars().take((w - 4) as usize).collect();
let title_x = x + (w.saturating_sub(title_display.len() as u16 + 4)) / 2;
buffer.set_str(
title_x as usize,
y as usize,
&title_display,
self.title_color,
Some(self.bg),
);
let content_display: String = self.content.chars().take((w - 4) as usize).collect();
buffer.set_str(
(x + 2) as usize,
(y + 2) as usize,
&content_display,
self.content_color,
Some(self.bg),
);
if !self.options.is_empty() {
for (i, option) in self.options.iter().enumerate() {
let opt_y = (y + 4 + i as u16).min(y + h - 2);
let selected = i == self.selected_index;
let prefix = if selected { " > " } else { " " };
let opt_display: String = option.chars().take((w - 6) as usize).collect();
let color = if selected {
self.border_color
} else {
self.content_color
};
let text = format!("{}{}", prefix, opt_display);
buffer.set_str(
(x + 2) as usize,
opt_y as usize,
&text,
color,
Some(self.bg),
);
}
}
}
}
impl Overlay for Modal {
fn is_visible(&self) -> bool {
self.visible
}
fn show(&mut self) {
self.visible = true;
self.transition_progress = 0.0;
}
fn hide(&mut self) {
self.visible = false;
}
fn toggle(&mut self) {
if self.visible {
self.hide();
} else {
self.show();
}
}
fn update(&mut self, delta: Duration) {
if self.transition == Transition::Fade && self.transition_progress < 1.0 {
self.transition_progress =
(self.transition_progress + delta.as_secs_f32() * 4.0).min(1.0);
}
}
fn render(&self, buffer: &mut Buffer, area: Rect) {
if self.is_visible() {
self.render_modal(buffer, area);
}
}
fn position(&self, screen: Rect) -> Rect {
screen
}
}