From 615d44041d35ee1a08ddc66423f67a9d48f53ae9 Mon Sep 17 00:00:00 2001 From: Kevin Hoerr Date: Thu, 16 Apr 2026 11:37:21 -0400 Subject: Switch to distrobox as base for development --- src/ui.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/ui.rs (limited to 'src/ui.rs') diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..1632623 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,134 @@ +use crate::app::App; +use miette::ErrReport; +use ratatui::{ + Frame, + layout::{Constraint, Direction, Layout, Rect}, + style::{Style, Stylize}, + symbols::border, + text::{Line, Span, Text}, + widgets::{Block, Clear, Paragraph, Widget}, +}; + +pub fn draw(frame: &mut Frame, app: &App) { + let rect = draw_layout(app); + frame.render_widget(rect, frame.area()); + + if let Some(error) = app.get_error() { + let popup = error_popup(error); + + let area = centered_rect(40, 25, frame.area()); + frame.render_widget(Clear, area); + frame.render_widget(popup, area); + } +} + +fn draw_layout(app: &App) -> impl Widget { + let title = Line::from(" Counter App Tutorial ".bold()); + let instructions = Line::from(vec![ + " Decrement ".into(), + "".blue().bold(), + " Increment ".into(), + "".blue().bold(), + " Quit ".into(), + " ".blue().bold(), + ]); + let block = Block::bordered() + .title(title.centered()) + .title_bottom(instructions.centered()) + .border_set(border::THICK); + + let counter_text = Text::from(vec![Line::from(vec![ + "Value: ".into(), + app.display_counter().yellow(), + ])]); + + Paragraph::new(counter_text).centered().block(block) +} + +/// helper function to create a centered rect using up certain percentage of the available rect `r` +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + // Cut the given rectangle into three vertical pieces + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .split(r); + // Then cut the middle vertical piece into three width-wise pieces + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .split(popup_layout[1])[1] // Return the middle chunk +} + +fn error_popup(error: &ErrReport) -> impl Widget { + let title = Line::from("Error").bold(); + let popup = Block::bordered() + .title(title.centered()) + .border_set(border::DOUBLE); + + let error_text = error_text(error); + + Paragraph::new(error_text).block(popup) +} + +fn error_text(error: &ErrReport) -> Text<'_> { + let errname_spans = vec![ + Span::raw("Error "), + Span::styled("x", Style::new().red()), + Span::raw(" "), + Span::raw(error.to_string()), + ]; + + let line1 = Line::default().spans(errname_spans); + let help_spans = vec![ + Span::styled(" help: ", Style::new().cyan()), + Span::raw(error.help().map_or("".into(), |x| x.to_string())), + ]; + let line2 = Line::default().spans(help_spans); + + Text::from(vec![line1, line2]) +} + +#[cfg(test)] +mod tests { + + use super::*; + use ratatui::{buffer::Buffer, layout::Rect, style::Style}; + + #[test] + fn render_success() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + + let rect = Rect::new(0, 0, 50, 4); + let mut buf = Buffer::empty(rect); + + let res = draw_layout(&app); + res.render(rect, &mut buf); + + let mut expected = Buffer::with_lines(vec![ + "┏━━━━━━━━━━━━━ Counter App Tutorial ━━━━━━━━━━━━━┓", + "┃ Value: 0 ┃", + "┃ ┃", + "┗━ Decrement Increment Quit ━━┛", + ]); + let title_style = Style::new().bold(); + let counter_style = Style::new().yellow(); + let key_style = Style::new().blue().bold(); + + expected.set_style(Rect::new(14, 0, 22, 1), title_style); + expected.set_style(Rect::new(28, 1, 1, 1), counter_style); + expected.set_style(Rect::new(13, 3, 6, 1), key_style); + expected.set_style(Rect::new(30, 3, 7, 1), key_style); + expected.set_style(Rect::new(43, 3, 4, 1), key_style); + + assert_eq!(buf, expected); + } +} -- cgit v1.3