use crate::ui::core::modal::Modal;
use crate::ui::core::widget::{InputEvent, Widget};
use ratatui::{
layout::Rect,
style::{Color, Style},
widgets::{Block, Borders, Clear},
Frame,
};
pub fn centered_rect(parent: Rect, width_percent: f32, height_percent: f32) -> Rect {
let width = (parent.width as f32 * width_percent.clamp(0.0, 1.0)) as u16;
let height = (parent.height as f32 * height_percent.clamp(0.0, 1.0)) as u16;
let x = parent.x + (parent.width.saturating_sub(width)) / 2;
let y = parent.y + (parent.height.saturating_sub(height)) / 2;
Rect::new(x, y, width, height)
}
pub struct ModalWidget {
visible: bool,
title: Option<String>,
width_percent: f32,
height_percent: f32,
style: Style,
}
impl ModalWidget {
pub fn new() -> Self {
Self {
visible: false,
title: None,
width_percent: 0.6,
height_percent: 0.6,
style: Style::default().bg(Color::Black).fg(Color::Cyan),
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_size(mut self, width_percent: f32, height_percent: f32) -> Self {
self.width_percent = width_percent.clamp(0.0, 1.0);
self.height_percent = height_percent.clamp(0.0, 1.0);
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn render<F>(&self, frame: &mut Frame, parent_area: Rect, render_content: F)
where
F: FnOnce(&mut Frame, Rect),
{
if !self.visible {
return;
}
let popup_area = centered_rect(parent_area, self.width_percent, self.height_percent);
frame.render_widget(Clear, popup_area);
let mut block = Block::default().borders(Borders::ALL).style(self.style);
if let Some(title) = &self.title {
block = block.title(title.as_str());
}
let inner_area = block.inner(popup_area);
frame.render_widget(block, popup_area);
render_content(frame, inner_area);
}
pub fn area(&self, parent: Rect) -> Rect {
centered_rect(parent, self.width_percent, self.height_percent)
}
}
impl Default for ModalWidget {
fn default() -> Self {
Self::new()
}
}
impl Widget for ModalWidget {
fn handle_input(&mut self, event: InputEvent) -> bool {
if event == InputEvent::Cancel && self.visible {
self.hide();
true
} else {
false
}
}
fn widget_type(&self) -> &'static str {
"ModalWidget"
}
}
impl Modal for ModalWidget {
fn is_visible(&self) -> bool {
self.visible
}
fn show(&mut self) {
self.visible = true;
}
fn hide(&mut self) {
self.visible = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_centered_rect() {
let parent = Rect::new(0, 0, 100, 50);
let popup = centered_rect(parent, 0.6, 0.5);
assert_eq!(popup.width, 60);
assert_eq!(popup.height, 25);
assert_eq!(popup.x, 20); assert_eq!(popup.y, 12); }
#[test]
fn test_modal_visibility() {
let mut modal = ModalWidget::new();
assert!(!modal.is_visible());
modal.show();
assert!(modal.is_visible());
modal.hide();
assert!(!modal.is_visible());
modal.toggle();
assert!(modal.is_visible());
}
#[test]
fn test_modal_cancel_input() {
let mut modal = ModalWidget::new();
modal.show();
assert!(modal.handle_input(InputEvent::Cancel));
assert!(!modal.is_visible());
}
}