use super::{overlay_block, overlay_text_area, render_overlay_fill};
use ratatui::{Frame, layout::Rect, widgets::Clear};
pub(super) mod auth;
pub(super) mod mcp;
pub(super) mod model;
pub(super) mod models;
pub(super) mod rewind;
pub(super) mod sessions;
pub(super) mod skills;
pub(super) mod subagents;
pub(super) mod system_prompt;
pub(super) mod tools;
pub(super) mod usage;
pub(crate) use auth::auth_picker_list_area;
pub(super) use auth::{
draw_active_login, draw_custom_provider_replacement_confirmation, draw_custom_provider_setup,
draw_login_picker, draw_logout_confirmation,
};
pub(super) use mcp::draw_mcp_modal;
pub(crate) use mcp::mcp_modal_list_area;
pub(super) use model::draw_model_picker;
pub(crate) use model::model_picker_list_area;
pub(super) use models::draw_models_modal;
pub(crate) use models::models_modal_list_area;
pub(super) use rewind::draw_rewind_modal;
pub(crate) use rewind::rewind_modal_text_area;
pub(super) use sessions::draw_sessions_modal;
pub(crate) use sessions::{
sessions_modal_areas, sessions_modal_list_area, sessions_modal_preview_area,
};
pub(super) use skills::draw_skills_modal;
pub(crate) use skills::skills_modal_list_area;
pub(super) use subagents::draw_subagents_modal;
pub(crate) use subagents::subagents_modal_list_area;
pub(super) use system_prompt::draw_system_prompt_modal;
pub(crate) use system_prompt::system_prompt_text_area;
pub(super) use tools::draw_tools_modal;
pub(crate) use tools::tools_modal_list_area;
pub(super) use usage::draw_usage_modal;
pub(crate) use usage::usage_modal_text_area;
#[derive(Debug, Clone, Copy)]
pub(in crate::tui::render) struct ModalSpec {
min_width: u16,
min_height: u16,
width_ratio: (u16, u16),
height: ModalHeight,
}
#[derive(Debug, Clone, Copy)]
pub(in crate::tui::render) enum ModalHeight {
Ratio(u16, u16),
Fixed(u16),
}
pub(in crate::tui::render) const SYSTEM_PROMPT_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 5,
width_ratio: (9, 10),
height: ModalHeight::Ratio(4, 5),
};
pub(super) const SESSIONS_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 5,
width_ratio: (9, 10),
height: ModalHeight::Ratio(4, 5),
};
pub(in crate::tui::render) const PICKER_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 5,
width_ratio: (4, 5),
height: ModalHeight::Ratio(3, 5),
};
pub(super) const CONFIRMATION_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 5,
width_ratio: (3, 5),
height: ModalHeight::Fixed(7),
};
pub(super) const CUSTOM_PROVIDER_REPLACEMENT_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 5,
width_ratio: (3, 5),
height: ModalHeight::Fixed(8),
};
pub(super) const CUSTOM_PROVIDER_SETUP_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 7,
width_ratio: (4, 5),
height: ModalHeight::Fixed(14),
};
pub(super) const ACTIVE_LOGIN_MODAL: ModalSpec = ModalSpec {
min_width: 12,
min_height: 7,
width_ratio: (9, 10),
height: ModalHeight::Ratio(2, 3),
};
pub(in crate::tui::render) fn modal_rect(area: Rect, spec: ModalSpec) -> Option<Rect> {
if area.width < spec.min_width || area.height < spec.min_height {
return None;
}
let width = area
.width
.saturating_mul(spec.width_ratio.0)
.saturating_div(spec.width_ratio.1)
.clamp(spec.min_width, area.width);
let height = match spec.height {
ModalHeight::Ratio(numerator, denominator) => area
.height
.saturating_mul(numerator)
.saturating_div(denominator)
.clamp(spec.min_height, area.height),
ModalHeight::Fixed(height) => height.min(area.height).max(spec.min_height),
};
Some(Rect::new(
area.x + area.width.saturating_sub(width) / 2,
area.y + area.height.saturating_sub(height) / 2,
width,
height,
))
}
pub(super) fn modal_text_area(area: Rect, title: &'static str, spec: ModalSpec) -> Option<Rect> {
let modal = modal_rect(area, spec)?;
let block = overlay_block(title);
Some(overlay_text_area(block.inner(modal)))
}
pub(super) fn draw_modal_panel(
frame: &mut Frame<'_>,
area: Rect,
title: &'static str,
spec: ModalSpec,
) -> Option<Rect> {
let modal = modal_rect(area, spec)?;
frame.render_widget(Clear, modal);
let block = overlay_block(title);
let inner = block.inner(modal);
frame.render_widget(block, modal);
Some(render_overlay_fill(frame, inner))
}
pub(super) fn block_inner(area: Rect) -> Rect {
Rect::new(
area.x.saturating_add(1),
area.y.saturating_add(1),
area.width.saturating_sub(2),
area.height.saturating_sub(2),
)
}
pub(super) fn picker_modal_list_area(
area: Rect,
title: &'static str,
header_rows: u16,
) -> Option<Rect> {
let text_area = modal_text_area(area, title, PICKER_MODAL)?;
if text_area.height == 0 {
return None;
}
let list_y = text_area.y.saturating_add(header_rows);
let list_height = text_area
.y
.saturating_add(text_area.height)
.saturating_sub(list_y);
(list_height > 0).then_some(Rect::new(text_area.x, list_y, text_area.width, list_height))
}