Skip to content

Commit e8369ae

Browse files
committed
feat(tui): implement header tabs and footer help bar
draw_ui splits the terminal into header, content area, and footer. Header shows module names as tabs with the selected one bolded and underlined in its theme color. Footer shows keybinding hints. Both respect the border style and chrome colors from config.
1 parent 1349069 commit e8369ae

1 file changed

Lines changed: 96 additions & 1 deletion

File tree

src/tui/mod.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,102 @@ pub fn run_tui(engine: Engine, config: Config) -> Result<()> {
115115
Ok(())
116116
}
117117

118-
fn draw_ui(_frame: &mut ratatui::Frame, _app: &App) {}
118+
fn draw_ui(frame: &mut ratatui::Frame, app: &App) {
119+
let area = frame.area();
120+
let theme = &app.config.theme;
121+
122+
let chrome_border = parse_color(&theme.chrome.border);
123+
let chrome_title = parse_color(&theme.chrome.title);
124+
125+
let border_type = match app.config.tui.borders {
126+
BorderStyle::None => ratatui::widgets::BorderType::Plain,
127+
BorderStyle::Plain => ratatui::widgets::BorderType::Plain,
128+
BorderStyle::Rounded => ratatui::widgets::BorderType::Rounded,
129+
};
130+
131+
let chunks = Layout::default()
132+
.direction(Direction::Vertical)
133+
.constraints([
134+
Constraint::Length(3),
135+
Constraint::Min(10),
136+
Constraint::Length(3),
137+
])
138+
.split(area);
139+
140+
draw_header(frame, chunks[0], app, chrome_border, chrome_title, border_type);
141+
draw_footer(frame, chunks[2], app, chrome_border, border_type);
142+
}
143+
144+
fn draw_header(
145+
frame: &mut ratatui::Frame,
146+
area: Rect,
147+
app: &App,
148+
border_color: ratatui::style::Color,
149+
title_color: ratatui::style::Color,
150+
border_type: ratatui::widgets::BorderType,
151+
) {
152+
let mut tabs: Vec<Span> = Vec::new();
153+
if let Some(snapshot) = &app.snapshot {
154+
for (i, (name, _)) in snapshot.modules.iter().enumerate() {
155+
let module_color = module_fg_color(&app.config, name);
156+
if i == app.selected_tab {
157+
tabs.push(Span::styled(
158+
format!(" [{}] ", name.to_uppercase()),
159+
Style::default()
160+
.fg(module_color)
161+
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
162+
));
163+
} else {
164+
tabs.push(Span::styled(
165+
format!(" {} ", name.to_uppercase()),
166+
Style::default().fg(module_color),
167+
));
168+
}
169+
}
170+
}
171+
172+
let header = Paragraph::new(Line::from(tabs)).block(
173+
Block::default()
174+
.borders(Borders::ALL)
175+
.border_type(border_type)
176+
.border_style(Style::default().fg(border_color))
177+
.title(Span::styled(
178+
" gim ",
179+
Style::default()
180+
.fg(title_color)
181+
.add_modifier(Modifier::BOLD),
182+
)),
183+
);
184+
frame.render_widget(header, area);
185+
}
186+
187+
fn draw_footer(
188+
frame: &mut ratatui::Frame,
189+
area: Rect,
190+
app: &App,
191+
border_color: ratatui::style::Color,
192+
border_type: ratatui::widgets::BorderType,
193+
) {
194+
if !app.config.tui.show_help {
195+
return;
196+
}
197+
198+
let help = Paragraph::new(Line::from(vec![
199+
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
200+
Span::raw(" quit "),
201+
Span::styled("←/→", Style::default().add_modifier(Modifier::BOLD)),
202+
Span::raw(" switch tab "),
203+
Span::styled("Tab", Style::default().add_modifier(Modifier::BOLD)),
204+
Span::raw(" next "),
205+
]))
206+
.block(
207+
Block::default()
208+
.borders(Borders::ALL)
209+
.border_type(border_type)
210+
.border_style(Style::default().fg(border_color)),
211+
);
212+
frame.render_widget(help, area);
213+
}
119214

120215
fn module_theme<'a>(config: &'a Config, name: &str) -> &'a ModuleTheme {
121216
match name {

0 commit comments

Comments
 (0)