use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::Line;
use ratatui::widgets::{Block, Borders, Cell, Paragraph, Row, Table};
use crate::app::setting::SettingsManager;
use crate::ui::state::help_action;
use crate::ui::{Page, style};
const ROW_HIGHLIGHT_SYMBOL: &str = "";
const TABLE_COLUMN_SPACING: u16 = 2;
pub struct SettingsPage<'a> {
manager: &'a mut SettingsManager,
}
impl<'a> SettingsPage<'a> {
pub fn new(manager: &'a mut SettingsManager) -> Self {
Self { manager }
}
}
impl Page for SettingsPage<'_> {
fn render(&mut self, f: &mut Frame, area: Rect) {
let chunks = Layout::default()
.constraints([Constraint::Min(0), Constraint::Length(1)])
.margin(1)
.split(area);
let main_area = chunks[0];
let footer_area = chunks[1];
let selected_style = Style::default().bg(style::palette::SURFACE);
let header_style = Style::default()
.bg(style::palette::SURFACE)
.fg(style::palette::TEXT_MUTED)
.add_modifier(Modifier::BOLD);
let header_cells = ["Setting", "Value"].iter().map(|h| Cell::from(*h));
let header = Row::new(header_cells)
.style(header_style)
.height(1)
.bottom_margin(1);
let rows = settings_table_rows(self.manager.settings_rows());
let table = Table::new(
rows,
[Constraint::Percentage(50), Constraint::Percentage(50)],
)
.column_spacing(TABLE_COLUMN_SPACING)
.header(header)
.block(Block::default().borders(Borders::ALL).title("Settings"))
.row_highlight_style(selected_style)
.highlight_symbol(ROW_HIGHLIGHT_SYMBOL);
f.render_stateful_widget(table, main_area, &mut self.manager.table_state);
let footer = Paragraph::new(settings_footer_line(self.manager));
f.render_widget(footer, footer_area);
}
}
fn settings_footer_line(manager: &SettingsManager) -> Line<'static> {
settings_footer_line_for_mode(manager.is_editing_text_input(), manager.footer_hint())
}
fn settings_footer_line_for_mode(is_editing_text_input: bool, footer_hint: &str) -> Line<'static> {
if is_editing_text_input {
return Line::from(footer_hint.to_string());
}
let actions = help_action::settings_footer_actions();
help_action::footer_line(&actions)
}
fn settings_table_rows(settings_rows: Vec<(&'static str, String)>) -> Vec<Row<'static>> {
settings_rows
.into_iter()
.map(|(setting_name, setting_value)| {
Row::new(vec![
Cell::from(setting_name),
Cell::from(setting_value.clone()),
])
.height(settings_row_height(&setting_value))
})
.collect()
}
fn settings_row_height(setting_value: &str) -> u16 {
let row_line_count = setting_value.lines().count().max(1);
u16::try_from(row_line_count).unwrap_or(u16::MAX)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_row_highlight_symbol_uses_background_only_selection() {
let highlight_symbol = ROW_HIGHLIGHT_SYMBOL;
let is_empty_symbol = highlight_symbol.is_empty();
assert!(is_empty_symbol);
}
#[test]
fn test_settings_table_column_spacing_is_wider_for_readability() {
let expected_spacing = 2;
let spacing = TABLE_COLUMN_SPACING;
assert_eq!(spacing, expected_spacing);
}
#[test]
fn test_settings_footer_line_uses_inline_hint_while_editing() {
let footer_hint = "Editing open commands";
let footer_line = settings_footer_line_for_mode(true, footer_hint);
assert_eq!(footer_line, Line::from(footer_hint.to_string()));
}
#[test]
fn test_settings_footer_line_uses_shared_actions_in_list_mode() {
let footer_hint = "unused while not editing";
let expected_line = help_action::footer_line(&help_action::settings_footer_actions());
let footer_line = settings_footer_line_for_mode(false, footer_hint);
assert_eq!(footer_line, expected_line);
}
#[test]
fn test_settings_row_height_expands_for_multiline_values() {
let setting_value = "line one\nline two\nline three";
let row_height = settings_row_height(setting_value);
assert_eq!(row_height, 3);
}
#[test]
fn test_settings_row_height_keeps_empty_values_visible() {
let setting_value = "";
let row_height = settings_row_height(setting_value);
assert_eq!(row_height, 1);
}
#[test]
fn test_settings_row_height_saturates_at_u16_max() {
let setting_value = &"\n".repeat(usize::from(u16::MAX) + 1);
let row_height = settings_row_height(setting_value);
assert_eq!(row_height, u16::MAX);
}
}