use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
text::{Line, Span},
widgets::{Paragraph, Widget},
};
use crate::config::Theme;
#[derive(Debug, Clone, Copy)]
pub struct Button<'a> {
pub label: &'a str,
}
impl<'a> Button<'a> {
pub fn bracketed(label: &'a str) -> Self {
Self { label }
}
fn width(&self) -> u16 {
self.label.chars().count() as u16 + 4
}
fn rendered(&self) -> String {
format!("[ {} ]", self.label)
}
}
pub fn buttons_row_width(buttons: &[Button]) -> u16 {
let labels_w: usize = buttons.iter().map(|b| b.width() as usize).sum();
let gaps = buttons.len().saturating_sub(1) * 2;
(labels_w + gaps) as u16
}
pub fn button_row_width(labels: &[&str]) -> u16 {
let buttons: Vec<Button> = labels.iter().map(|l| Button::bracketed(l)).collect();
buttons_row_width(&buttons)
}
pub fn render_button_row(
area: Rect,
buf: &mut Buffer,
labels: &[&str],
focused_idx: usize,
theme: &Theme,
) -> Vec<Rect> {
let buttons: Vec<Button> = labels.iter().map(|l| Button::bracketed(l)).collect();
render_buttons(area, buf, &buttons, focused_idx, theme)
}
pub fn render_buttons(
area: Rect,
buf: &mut Buffer,
buttons: &[Button],
focused_idx: usize,
theme: &Theme,
) -> Vec<Rect> {
let mut spans: Vec<Span<'_>> = Vec::with_capacity(buttons.len() * 2 + 1);
for (i, button) in buttons.iter().enumerate() {
let style = crate::ui::controls::button_style(i == focused_idx, theme);
spans.push(Span::styled(button.rendered(), style));
if i + 1 < buttons.len() {
spans.push(Span::raw(" "));
}
}
Paragraph::new(Line::from(spans))
.alignment(Alignment::Center)
.style(theme.modal_bg)
.render(area, buf);
let total = buttons_row_width(buttons);
let start_x = area.x + area.width.saturating_sub(total) / 2;
let mut rects = Vec::with_capacity(buttons.len());
let mut x = start_x;
for button in buttons {
let w = button.width();
rects.push(Rect {
x,
y: area.y,
width: w,
height: 1,
});
x += w + 2;
}
rects
}
pub fn render_button_at(
area: Rect,
buf: &mut Buffer,
button: Button,
focused: bool,
disabled: bool,
theme: &Theme,
) -> Rect {
let style = if disabled {
crate::ui::controls::control_label_style(false, true, theme)
} else {
crate::ui::controls::button_style(focused, theme)
};
Paragraph::new(Line::from(Span::styled(button.rendered(), style)))
.alignment(Alignment::Left)
.style(theme.modal_bg)
.render(area, buf);
Rect {
x: area.x,
y: area.y,
width: button.width(),
height: 1,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn width_two_buttons_with_gap() {
assert_eq!(button_row_width(&["Save", "Cancel"]), 8 + 10 + 2);
}
#[test]
fn width_single_button_has_no_gap() {
assert_eq!(button_row_width(&["Ok"]), 6);
}
#[test]
fn width_zero_buttons() {
assert_eq!(button_row_width(&[]), 0);
}
}