use super::theme::MissionControlTheme;
use ratatui::{
Frame,
layout::Rect,
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Clear},
};
pub(crate) fn modal_area(area: Rect, width_percent: u16, height_percent: u16) -> Rect {
compact_modal_area(
area,
width_percent.clamp(30, 80),
height_percent.clamp(30, 80),
)
}
pub(crate) fn compact_modal_area(area: Rect, width_percent: u16, height_percent: u16) -> Rect {
let width = (u32::from(area.width) * u32::from(width_percent.min(100)) / 100) as u16;
let height = (u32::from(area.height) * u32::from(height_percent.min(100)) / 100) as u16;
Rect::new(
area.x.saturating_add((area.width - width) / 2),
area.y.saturating_add((area.height - height) / 2),
width,
height,
)
}
fn close_control_label(hint: &str) -> Line<'_> {
Line::from(vec![Span::raw(hint), Span::raw(" [X]")]).right_aligned()
}
pub(crate) fn close_control_area(area: Rect, hint: Option<&str>) -> Option<Rect> {
let width = close_control_label(hint?)
.width()
.min(usize::from(area.width.saturating_sub(2))) as u16;
(width > 0 && area.height > 1)
.then(|| Rect::new(area.right().saturating_sub(width + 1), area.y, width, 1))
}
pub(crate) fn close_control_clicked(
mouse: crossterm::event::MouseEvent,
control: Option<Rect>,
) -> bool {
mouse.kind == crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left)
&& control.is_some_and(|area| area.contains((mouse.column, mouse.row).into()))
}
pub(crate) fn modal_block<'a>(
title: impl Into<Line<'a>>,
hotkeys: impl Into<Line<'a>>,
theme: MissionControlTheme,
close_hint: Option<&'a str>,
) -> Block<'a> {
let mut block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Plain)
.style(theme.app())
.title_top(title.into().left_aligned())
.title_bottom(hotkeys.into().left_aligned());
if let Some(hint) = close_hint {
block = block.title_top(close_control_label(hint));
}
block
}
pub(crate) fn draw_modal_container<'a>(
frame: &mut Frame<'_>,
area: Rect,
title: impl Into<Line<'a>>,
hotkeys: impl Into<Line<'a>>,
theme: MissionControlTheme,
close_hint: Option<&'a str>,
) -> Rect {
let close_hint = close_hint.filter(|_| area.height > 1);
let block = modal_block(title, hotkeys, theme, close_hint);
let inner = block.inner(area);
if !area.is_empty() {
let left = area.x.saturating_sub(2);
let top = area.y.saturating_sub(1);
let surround = Rect::new(
left,
top,
area.right().saturating_add(2).saturating_sub(left),
area.bottom().saturating_add(1).saturating_sub(top),
)
.intersection(frame.area());
frame.render_widget(Clear, surround);
frame.render_widget(
Block::default().style(Style::default().bg(Color::Rgb(0, 0, 0))),
surround,
);
}
frame.render_widget(Clear, area);
frame.render_widget(block, area);
inner
}
pub(crate) fn modal_tab(label: &str, selected: bool, theme: MissionControlTheme) -> Span<'static> {
if selected {
Span::styled(
format!("[{label}]"),
Style::default()
.fg(theme.text_accent())
.bg(theme.surface_selection())
.add_modifier(Modifier::BOLD),
)
} else {
Span::styled(format!(" {label} "), theme.muted(false))
}
}