aboutsummaryrefslogtreecommitdiff
path: root/src/ui.rs
diff options
context:
space:
mode:
authorKevin Hoerr <kjhoerr@submelon.dev>2026-04-16 11:37:21 -0400
committerKevin Hoerr <kjhoerr@submelon.dev>2026-04-16 11:37:21 -0400
commit615d44041d35ee1a08ddc66423f67a9d48f53ae9 (patch)
tree9720d6a192e0bae9c7393b8b99ff8ae401ec7f72 /src/ui.rs
parent51623bc995c29937bb0a13d5b71ff908be8d1a1c (diff)
downloadequity-tracker-615d44041d35ee1a08ddc66423f67a9d48f53ae9.tar.gz
equity-tracker-615d44041d35ee1a08ddc66423f67a9d48f53ae9.tar.bz2
equity-tracker-615d44041d35ee1a08ddc66423f67a9d48f53ae9.zip
Switch to distrobox as base for development
Diffstat (limited to 'src/ui.rs')
-rw-r--r--src/ui.rs134
1 files changed, 134 insertions, 0 deletions
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(),
+ "<Left>".blue().bold(),
+ " Increment ".into(),
+ "<Right>".blue().bold(),
+ " Quit ".into(),
+ "<Q> ".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 <Left> Increment <Right> Quit <Q> ━━┛",
+ ]);
+ 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);
+ }
+}