1use crate::app::{App, Tab};
6use crate::modal::{self, Modal};
7use crate::tabs;
8use crate::widgets::{EditMode, VimMode};
9use ratatui::Frame;
10use ratatui::layout::{Constraint, Direction, Layout, Rect};
11use ratatui::style::{Color, Modifier, Style};
12use ratatui::text::{Line, Span};
13use ratatui::widgets::{Block, Borders, Clear, Paragraph, Tabs};
14
15pub fn draw(frame: &mut Frame, app: &App) {
16 let area = frame.area();
17 let chunks = Layout::default()
18 .direction(Direction::Vertical)
19 .constraints([
20 Constraint::Length(3),
21 Constraint::Min(1),
22 Constraint::Length(3),
23 ])
24 .split(area);
25
26 draw_tabs(frame, chunks[0], app);
27 draw_body(frame, chunks[1], app);
28 draw_status(frame, chunks[2], app);
29
30 if let Some(modal) = app.modals.top() {
31 draw_modal(frame, area, modal);
32 }
33}
34
35fn draw_tabs(frame: &mut Frame, area: Rect, app: &App) {
36 let titles: Vec<Line> = Tab::ALL.iter().map(|t| Line::from(t.label())).collect();
37 let selected = Tab::ALL
38 .iter()
39 .position(|t| *t == app.active_tab)
40 .unwrap_or(0);
41 let tabs = Tabs::new(titles)
42 .select(selected)
43 .block(Block::default().borders(Borders::ALL).title("nomisync-tui"))
44 .highlight_style(Style::default().add_modifier(Modifier::REVERSED));
45 frame.render_widget(tabs, area);
46}
47
48fn draw_body(frame: &mut Frame, area: Rect, app: &App) {
49 let body = tabs::placeholder_body(app.active_tab);
50 let widget = Paragraph::new(body).block(
51 Block::default()
52 .borders(Borders::ALL)
53 .title(app.active_tab.label()),
54 );
55 frame.render_widget(widget, area);
56}
57
58fn draw_status(frame: &mut Frame, area: Rect, app: &App) {
59 let edit_indicator = match app.command_line.mode() {
60 EditMode::Emacs => "emacs",
61 EditMode::Vim => match app.command_line.vim_mode() {
62 VimMode::Normal => "vim:normal",
63 VimMode::Insert => "vim:insert",
64 },
65 };
66 let content = if app.command_line_active {
67 format!(
68 ":{} (cursor={})",
69 app.command_line.buffer(),
70 app.command_line.cursor()
71 )
72 } else if app.status.is_empty() {
73 format!("[{edit_indicator}] Tab/BTab tabs : cmdline ? help C-v edit-mode q quit")
74 } else {
75 format!("[{edit_indicator}] {}", app.status)
76 };
77 let line = Span::styled(content, Style::default().fg(Color::Gray));
78 let para = Paragraph::new(line).block(Block::default().borders(Borders::ALL));
79 frame.render_widget(para, area);
80}
81
82fn draw_modal(frame: &mut Frame, full: Rect, modal: &Modal) {
83 let area = centered_rect(60, 30, full);
84 frame.render_widget(Clear, area);
85 let (title, body) = modal_content(modal);
86 let widget = Paragraph::new(body).block(Block::default().title(title).borders(Borders::ALL));
87 frame.render_widget(widget, area);
88}
89
90fn modal_content(modal: &Modal) -> (&'static str, String) {
91 match modal {
92 Modal::Help => (
93 "Help",
94 "Keys:\n 1-5 jump to tab\n Tab next tab\n : command palette\n\
95 \n ? this help\n q/Esc close modal\n C-v toggle emacs/vim mode"
96 .to_string(),
97 ),
98 Modal::ConfigSet(m) => {
99 let focus_marker = |field| {
100 if m.focus == field { "▸" } else { " " }
101 };
102 let body = format!(
103 "{} name: [{}]\n{} value: [{}]\n\nEnter to save, Esc to cancel.",
104 focus_marker(modal::ConfigSetField::Name),
105 m.name.buffer(),
106 focus_marker(modal::ConfigSetField::Value),
107 m.value.buffer(),
108 );
109 ("Set config", body)
110 }
111 }
112}
113
114#[must_use]
115pub fn centered_rect(pct_x: u16, pct_y: u16, r: Rect) -> Rect {
116 let popup_layout = Layout::default()
117 .direction(Direction::Vertical)
118 .constraints([
119 Constraint::Percentage((100 - pct_y) / 2),
120 Constraint::Percentage(pct_y),
121 Constraint::Percentage((100 - pct_y) / 2),
122 ])
123 .split(r);
124 Layout::default()
125 .direction(Direction::Horizontal)
126 .constraints([
127 Constraint::Percentage((100 - pct_x) / 2),
128 Constraint::Percentage(pct_x),
129 Constraint::Percentage((100 - pct_x) / 2),
130 ])
131 .split(popup_layout[1])[1]
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn centered_rect_clamps_to_parent() {
140 let parent = Rect::new(0, 0, 100, 100);
141 let r = centered_rect(60, 30, parent);
142 assert!(r.x + r.width <= parent.x + parent.width);
143 assert!(r.y + r.height <= parent.y + parent.height);
144 }
145}