use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
widgets::Widget,
};
pub struct Menu<'a> {
items: &'a [&'a str],
cursor: usize,
}
impl<'a> Menu<'a> {
pub fn new(items: &'a [&'a str]) -> Self {
Self { items, cursor: 0 }
}
pub fn cursor(mut self, cursor: usize) -> Self {
self.cursor = cursor;
self
}
pub fn rect(&self, area: Rect) -> Rect {
let height = (self.items.len() + 2) as u16;
let width = self.items.iter().map(|s| s.len() + 4).max().unwrap_or(18) as u16;
Rect {
x: area.x,
y: area.y,
width: width.min(area.width),
height: height.min(area.height),
}
}
pub fn hit_test(&self, position: (u16, u16), area: Rect) -> Option<usize> {
let menu_rect = self.rect(area);
if position.0 >= menu_rect.x
&& position.0 < menu_rect.x + menu_rect.width
&& position.1 > menu_rect.y
&& position.1 < menu_rect.y + 1 + self.items.len() as u16
{
Some((position.1 - menu_rect.y - 1) as usize)
} else {
None
}
}
}
impl<'a> Widget for Menu<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let menu_rect = self.rect(area);
let border_style = Style::default().fg(Color::DarkGray);
let text_style = Style::default().fg(Color::White);
buf.set_style(menu_rect, Style::default().bg(Color::DarkGray));
for row in menu_rect.y..menu_rect.y + menu_rect.height {
for col in menu_rect.x..menu_rect.x + menu_rect.width {
if let Some(cell) = buf.cell_mut((col, row)) {
let is_border = row == menu_rect.y
|| row == menu_rect.y + menu_rect.height - 1
|| col == menu_rect.x
|| col == menu_rect.x + menu_rect.width - 1;
if is_border {
cell.set_char(' ');
cell.set_style(border_style.bg(Color::Rgb(36, 36, 36)));
}
}
}
}
for (i, item) in self.items.iter().enumerate() {
let item_y = menu_rect.y + 1 + i as u16;
let is_selected = i == self.cursor;
let item_fg = if is_selected {
Color::Black
} else {
Color::White
};
let item_bg = if is_selected {
Color::LightBlue
} else {
Color::Rgb(36, 36, 36)
};
let inner_width = (menu_rect.width as usize).saturating_sub(2);
let label = format!("{:<width$}", format!(" {} ", item), width = inner_width);
for (j, ch) in label.chars().take(inner_width).enumerate() {
if let Some(cell) = buf.cell_mut((menu_rect.x + 1 + j as u16, item_y)) {
cell.set_char(ch);
cell.set_style(text_style.fg(item_fg).bg(item_bg));
}
}
}
}
}