use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use crate::primitives::display_width::str_width;
use super::{FocusState, ToggleColors, ToggleLayout, ToggleState};
fn pad_to_display_width(label: &str, width: u16) -> String {
let width = width as usize;
let padding = width.saturating_sub(str_width(label));
let mut padded = String::with_capacity(label.len() + padding);
padded.push_str(label);
padded.extend(std::iter::repeat_n(' ', padding));
padded
}
pub fn render_toggle(
frame: &mut Frame,
area: Rect,
state: &ToggleState,
colors: &ToggleColors,
) -> ToggleLayout {
render_toggle_aligned(frame, area, state, colors, None)
}
pub fn render_toggle_aligned(
frame: &mut Frame,
area: Rect,
state: &ToggleState,
colors: &ToggleColors,
label_width: Option<u16>,
) -> ToggleLayout {
if area.height == 0 || area.width < 4 {
return ToggleLayout {
checkbox_area: Rect::default(),
full_area: area,
};
}
let (bracket_color, check_color, label_color) = match state.focus {
FocusState::Normal => (colors.bracket, colors.checkmark, colors.label),
FocusState::Focused => (colors.focused_fg, colors.focused_fg, colors.focused_fg),
FocusState::Hovered => (colors.focused_fg, colors.focused_fg, colors.focused_fg),
FocusState::Disabled => (colors.disabled, colors.disabled, colors.disabled),
};
let label_display_width = str_width(&state.label) as u16;
let actual_label_width = label_width
.unwrap_or(label_display_width)
.max(label_display_width);
let padded_label = pad_to_display_width(&state.label, actual_label_width);
const CHIP_WIDTH: u16 = 3;
let line = if state.inherited {
Line::from(vec![
Span::styled(padded_label, Style::default().fg(label_color)),
Span::styled(": ", Style::default().fg(label_color)),
Span::styled("[", Style::default().fg(bracket_color)),
Span::styled("-", Style::default().fg(bracket_color)),
Span::styled("]", Style::default().fg(bracket_color)),
])
} else if state.checked {
Line::from(vec![
Span::styled(padded_label, Style::default().fg(label_color)),
Span::styled(": ", Style::default().fg(label_color)),
Span::styled("[", Style::default().fg(bracket_color)),
Span::styled(
"v",
Style::default()
.fg(check_color)
.add_modifier(ratatui::style::Modifier::BOLD),
),
Span::styled("]", Style::default().fg(bracket_color)),
])
} else {
Line::from(vec![
Span::styled(padded_label, Style::default().fg(label_color)),
Span::styled(": ", Style::default().fg(label_color)),
Span::styled("[", Style::default().fg(bracket_color)),
Span::styled(" ", Style::default().fg(bracket_color)),
Span::styled("]", Style::default().fg(bracket_color)),
])
};
let paragraph = Paragraph::new(line);
frame.render_widget(paragraph, area);
let label_overhead = actual_label_width.saturating_add(2);
let checkbox_start = area.x.saturating_add(label_overhead);
let chip_avail = area.width.saturating_sub(label_overhead.min(area.width));
let checkbox_area = Rect::new(checkbox_start, area.y, CHIP_WIDTH.min(chip_avail), 1);
let full_width = (actual_label_width + 2 + CHIP_WIDTH).min(area.width);
let full_area = Rect::new(area.x, area.y, full_width, 1);
ToggleLayout {
checkbox_area,
full_area,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pad_to_display_width_uses_terminal_columns() {
let padded = pad_to_display_width("你好", 6);
assert_eq!(str_width(&padded), 6);
assert_eq!(padded, "你好 ");
}
}