use crate::app::{App, Focus, Mode};
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph};
pub fn draw(frame: &mut Frame, app: &App) {
let area = frame.area();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(area);
let panes = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(35), Constraint::Percentage(65)])
.split(chunks[0]);
render_monitors(frame, panes[0], app);
render_capabilities(frame, panes[1], app);
render_help(frame, chunks[1], app);
match &app.mode {
Mode::EditContinuous { value, max } => render_continuous_popup(frame, area, *value, *max),
Mode::EditChoice { index } => render_choice_popup(frame, area, app, *index),
Mode::Normal => {}
}
}
fn render_monitors(frame: &mut Frame, area: Rect, app: &App) {
let items: Vec<ListItem> = app
.monitors
.iter()
.map(|m| ListItem::new(m.label()))
.collect();
let mut state = ListState::default().with_selected(Some(app.selected_monitor));
let focused = app.focus == Focus::Monitors;
let block = Block::default()
.title(" Monitors ")
.borders(Borders::ALL)
.border_style(focus_style(focused));
let list = List::new(items)
.block(block)
.highlight_style(Style::default().add_modifier(Modifier::REVERSED));
frame.render_stateful_widget(list, area, &mut state);
}
fn render_capabilities(frame: &mut Frame, area: Rect, app: &App) {
let monitor = app.selected_monitor_entry();
let items: Vec<ListItem> = monitor
.features
.iter()
.map(|f| {
let star = if f.favorite { "* " } else { " " };
let value = match f.value {
Some(v) => {
if let Some((_, name)) = f.choices.iter().find(|(c, _)| *c == v.current as u8) {
name.clone()
} else {
format!("{}", v.current)
}
}
None if !f.value_fetched => "...".to_string(),
None => "?".to_string(),
};
ListItem::new(format!(
"{star}0x{:02X} {:<28} {}",
f.feature_code, f.name, value
))
})
.collect();
let mut state = ListState::default().with_selected(Some(app.selected_capability));
let focused = app.focus == Focus::Capabilities;
let block = Block::default()
.title(format!(" Capabilities: {} ", monitor.label()))
.borders(Borders::ALL)
.border_style(focus_style(focused));
let list = List::new(items)
.block(block)
.highlight_style(Style::default().add_modifier(Modifier::REVERSED));
frame.render_stateful_widget(list, area, &mut state);
}
fn render_help(frame: &mut Frame, area: Rect, app: &App) {
let text = if let Some(status) = &app.status {
status.clone()
} else {
"q: quit Tab/\u{2190}\u{2192}: switch pane j/k: move f: favorite Enter: edit r: refresh"
.to_string()
};
frame.render_widget(Paragraph::new(text), area);
}
fn focus_style(focused: bool) -> Style {
if focused {
Style::default().fg(Color::Cyan)
} else {
Style::default()
}
}
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
let width = width.min(area.width);
let height = height.min(area.height);
let x = area.x + (area.width.saturating_sub(width)) / 2;
let y = area.y + (area.height.saturating_sub(height)) / 2;
Rect::new(x, y, width, height)
}
fn render_continuous_popup(frame: &mut Frame, area: Rect, value: u16, max: u16) {
let popup = centered_rect(40, 5, area);
frame.render_widget(Clear, popup);
let ratio = if max == 0 {
0.0
} else {
value as f64 / max as f64
};
let filled = (ratio * 30.0).round() as usize;
let bar = format!(
"[{}{}] {value}/{max}",
"#".repeat(filled),
"-".repeat(30 - filled)
);
let text = format!("{bar}\n\n\u{2190}/\u{2192}: adjust Enter: confirm Esc: cancel");
let block = Block::default().title(" Edit value ").borders(Borders::ALL);
frame.render_widget(Paragraph::new(text).block(block), popup);
}
fn render_choice_popup(frame: &mut Frame, area: Rect, app: &App, index: usize) {
let popup = centered_rect(40, 10, area);
frame.render_widget(Clear, popup);
let choices = app
.selected_capability_entry()
.map(|e| e.choices.clone())
.unwrap_or_default();
let items: Vec<ListItem> = choices
.iter()
.map(|(code, name)| ListItem::new(format!("0x{code:02X} {name}")))
.collect();
let mut state = ListState::default().with_selected(Some(index));
let block = Block::default()
.title(" Select value (Enter: confirm, Esc: cancel) ")
.borders(Borders::ALL);
let list = List::new(items)
.block(block)
.highlight_style(Style::default().add_modifier(Modifier::REVERSED));
frame.render_stateful_widget(list, popup, &mut state);
}