use crate::agent::ui::theme::current_theme;
use crate::tui::Component;
use crate::tui::keybindings::{ACTION_SELECT_CANCEL, ACTION_SELECT_CONFIRM, get_keybindings};
use crossterm::event::KeyEvent;
pub struct ConfirmOverlay {
title: String,
message: String,
on_confirm: Option<Box<dyn FnOnce()>>,
on_cancel: Option<Box<dyn FnOnce()>>,
selected: bool, done: bool,
}
impl ConfirmOverlay {
pub fn new(title: impl Into<String>, message: impl Into<String>) -> Self {
Self {
title: title.into(),
message: message.into(),
on_confirm: None,
on_cancel: None,
selected: true, done: false,
}
}
pub fn on_confirm<F>(&mut self, f: F)
where
F: FnOnce() + 'static,
{
self.on_confirm = Some(Box::new(f));
}
pub fn on_cancel<F>(&mut self, f: F)
where
F: FnOnce() + 'static,
{
self.on_cancel = Some(Box::new(f));
}
}
impl Component for ConfirmOverlay {
fn render(&mut self, width: usize) -> Vec<String> {
let theme = current_theme();
let mut lines: Vec<String> = Vec::new();
lines.push(theme.dim(&"─".repeat(width.saturating_sub(2))));
lines.push(String::new());
lines.push(format!(" {}", theme.bold(&theme.accent(&self.title))));
lines.push(String::new());
let max_text_width = width.saturating_sub(4); if max_text_width > 10 {
let mut remaining = self.message.as_str();
while !remaining.is_empty() {
let break_at = if remaining.len() <= max_text_width {
remaining.len()
} else {
let slice = &remaining[..max_text_width];
let last_space = slice.rfind(' ').unwrap_or(max_text_width);
if last_space == 0 {
max_text_width
} else {
last_space
}
};
lines.push(format!(" {}", &remaining[..break_at]));
remaining = remaining[break_at..].trim_start();
}
} else {
lines.push(format!(" {}", self.message));
}
lines.push(String::new());
let yes_style = if self.selected {
theme.bold(&theme.fg_key(crate::agent::ui::theme::ThemeKey::Success, "[Y] Yes"))
} else {
theme.dim("[Y] Yes")
};
let no_style = if !self.selected {
theme.bold(&theme.fg_key(crate::agent::ui::theme::ThemeKey::Error, "[N] No"))
} else {
theme.dim("[N] No")
};
lines.push(format!(" {} {}", yes_style, no_style));
lines.push(String::new());
lines.push(format!(
" {}",
theme.dim("Tab/← →: switch · Enter: confirm · Esc: cancel")
));
lines.push(String::new());
lines.push(theme.dim(&"─".repeat(width.saturating_sub(2))));
lines
}
fn handle_input(&mut self, key: &KeyEvent) -> bool {
if self.done {
return false;
}
let kb = get_keybindings();
if kb.matches(key, ACTION_SELECT_CANCEL) {
self.done = true;
if let Some(cb) = self.on_cancel.take() {
cb();
}
return false;
}
if kb.matches(key, ACTION_SELECT_CONFIRM)
|| key.code == crossterm::event::KeyCode::Char('y')
{
self.done = true;
if let Some(cb) = self.on_confirm.take() {
cb();
}
return false;
}
if key.code == crossterm::event::KeyCode::Char('n') {
self.done = true;
if let Some(cb) = self.on_cancel.take() {
cb();
}
return false;
}
if key.code == crossterm::event::KeyCode::Tab
|| key.code == crossterm::event::KeyCode::Right
|| key.code == crossterm::event::KeyCode::Left
{
self.selected = !self.selected;
return true;
}
false
}
}