use crate::charset::Charset;
use crate::video_buffer::{Cell, VideoBuffer};
use crossterm::style::Color;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MenuAction {
Copy,
Paste,
SelectAll,
CopyWindow,
#[allow(dead_code)]
Close,
}
#[derive(Debug, Clone)]
pub struct MenuItem {
pub label: String,
pub shortcut: Option<char>,
pub action: Option<MenuAction>,
pub is_separator: bool,
}
impl MenuItem {
pub fn new(label: &str, shortcut: Option<char>, action: MenuAction) -> Self {
Self {
label: label.to_string(),
shortcut,
action: Some(action),
is_separator: false,
}
}
pub fn separator() -> Self {
Self {
label: String::new(),
shortcut: None,
action: None,
is_separator: true,
}
}
}
pub struct ContextMenu {
pub x: u16,
pub y: u16,
pub items: Vec<MenuItem>,
pub selected_index: usize,
pub visible: bool,
}
impl ContextMenu {
pub fn new(x: u16, y: u16) -> Self {
let items = vec![
MenuItem::new("Copy", Some('C'), MenuAction::Copy),
MenuItem::new("Paste", Some('V'), MenuAction::Paste),
MenuItem::new("Select All", Some('A'), MenuAction::SelectAll),
MenuItem::separator(),
MenuItem::new("Copy Window", Some('W'), MenuAction::CopyWindow),
];
Self {
x,
y,
items,
selected_index: 0,
visible: false,
}
}
pub fn show(&mut self, x: u16, y: u16) {
self.x = x;
self.y = y;
self.visible = true;
self.selected_index = 0;
}
pub fn hide(&mut self) {
self.visible = false;
}
#[allow(dead_code)]
pub fn select_previous(&mut self) {
if self.selected_index > 0 {
self.selected_index -= 1;
while self.selected_index > 0 && self.items[self.selected_index].is_separator {
self.selected_index -= 1;
}
}
}
#[allow(dead_code)]
pub fn select_next(&mut self) {
if self.selected_index < self.items.len() - 1 {
self.selected_index += 1;
while self.selected_index < self.items.len() - 1
&& self.items[self.selected_index].is_separator
{
self.selected_index += 1;
}
}
}
pub fn get_selected_action(&self) -> Option<MenuAction> {
self.items.get(self.selected_index)?.action
}
pub fn contains_point(&self, x: u16, y: u16) -> bool {
if !self.visible {
return false;
}
let width = self.calculate_width();
let height = self.items.len() as u16 + 2;
x >= self.x && x < self.x + width && y >= self.y && y < self.y + height
}
fn calculate_width(&self) -> u16 {
let max_label_len = self
.items
.iter()
.map(|item| item.label.len())
.max()
.unwrap_or(0);
(max_label_len + 8) as u16
}
pub fn render(&self, buffer: &mut VideoBuffer, charset: &Charset) {
if !self.visible {
return;
}
let width = self.calculate_width();
let height = self.items.len() as u16 + 2;
let fg_color = Color::White;
let bg_color = Color::Black;
let selected_fg = Color::Black;
let selected_bg = Color::White;
buffer.set(
self.x,
self.y,
Cell::new(charset.border_top_left(), fg_color, bg_color),
);
for dx in 1..width - 1 {
buffer.set(
self.x + dx,
self.y,
Cell::new(charset.border_horizontal(), fg_color, bg_color),
);
}
buffer.set(
self.x + width - 1,
self.y,
Cell::new(charset.border_top_right(), fg_color, bg_color),
);
for (i, item) in self.items.iter().enumerate() {
let row = self.y + 1 + i as u16;
let is_selected = i == self.selected_index && !item.is_separator;
buffer.set(
self.x,
row,
Cell::new(charset.border_vertical(), fg_color, bg_color),
);
if item.is_separator {
for dx in 1..width - 1 {
buffer.set(
self.x + dx,
row,
Cell::new(charset.border_horizontal(), fg_color, bg_color),
);
}
} else {
let item_fg = if is_selected { selected_fg } else { fg_color };
let item_bg = if is_selected { selected_bg } else { bg_color };
let mut dx = 1;
buffer.set(self.x + dx, row, Cell::new(' ', item_fg, item_bg));
dx += 1;
for ch in item.label.chars() {
buffer.set(self.x + dx, row, Cell::new(ch, item_fg, item_bg));
dx += 1;
}
while dx < width - 3 {
buffer.set(self.x + dx, row, Cell::new(' ', item_fg, item_bg));
dx += 1;
}
if let Some(shortcut) = item.shortcut {
buffer.set(self.x + dx, row, Cell::new(shortcut, item_fg, item_bg));
dx += 1;
}
while dx < width - 1 {
buffer.set(self.x + dx, row, Cell::new(' ', item_fg, item_bg));
dx += 1;
}
}
buffer.set(
self.x + width - 1,
row,
Cell::new(charset.border_vertical(), fg_color, bg_color),
);
}
let bottom_y = self.y + height - 1;
buffer.set(
self.x,
bottom_y,
Cell::new(charset.border_bottom_left(), fg_color, bg_color),
);
for dx in 1..width - 1 {
buffer.set(
self.x + dx,
bottom_y,
Cell::new(charset.border_horizontal(), fg_color, bg_color),
);
}
buffer.set(
self.x + width - 1,
bottom_y,
Cell::new(charset.border_bottom_right(), fg_color, bg_color),
);
self.render_shadow(buffer, width, height, charset);
}
fn render_shadow(&self, buffer: &mut VideoBuffer, width: u16, height: u16, charset: &Charset) {
let shadow_fg = Color::DarkGrey;
let shadow_bg = Color::Black;
for dy in 1..=height {
let x = self.x + width;
let y = self.y + dy;
buffer.set(x, y, Cell::new(charset.shadow, shadow_fg, shadow_bg));
}
for dx in 2..=width {
let x = self.x + dx;
let y = self.y + height;
buffer.set(x, y, Cell::new(charset.shadow, shadow_fg, shadow_bg));
}
}
}