poddy/app/ui/
mod.rs

1pub mod help;
2pub mod state;
3
4use crate::{ui::help::draw_help, App, Args};
5use tui::{
6    backend::Backend,
7    layout::{Alignment, Constraint, Direction, Layout, Rect},
8    style::{Color, Style},
9    widgets::{Block, BorderType, Borders, Paragraph, StatefulWidget, Widget},
10    Frame,
11};
12use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget};
13
14pub trait StateRenderer {
15    fn rect(&self) -> Rect;
16
17    fn render_child<W: Widget>(&mut self, w: W, rect: Rect);
18    fn render_child_stateful<W: StatefulWidget>(&mut self, w: W, state: &mut W::State, rect: Rect);
19
20    fn render<W: Widget>(&mut self, w: W) {
21        self.render_child(w, self.rect());
22    }
23
24    fn render_stateful<W: StatefulWidget>(&mut self, w: W, state: &mut W::State) {
25        self.render_child_stateful(w, state, self.rect());
26    }
27}
28
29impl<'c, 'f, B> StateRenderer for RenderContext<'c, 'f, B>
30where
31    B: Backend,
32{
33    #[inline]
34    fn rect(&self) -> Rect {
35        self.rect
36    }
37
38    fn render_child<W: Widget>(&mut self, w: W, rect: Rect) {
39        self.frame.render_widget(w, rect);
40    }
41
42    fn render_child_stateful<W: StatefulWidget>(&mut self, w: W, state: &mut W::State, rect: Rect) {
43        self.frame.render_stateful_widget(w, rect, state);
44    }
45}
46
47struct RenderContext<'c, 'f, B>
48where
49    B: Backend + 'f,
50{
51    frame: &'c mut Frame<'f, B>,
52    rect: Rect,
53}
54
55pub fn draw<B>(rect: &mut Frame<B>, app: &App)
56where
57    B: Backend,
58{
59    if app.global.help {
60        draw_help(rect)
61    } else {
62        draw_default(rect, app)
63    }
64}
65
66pub fn draw_default<B>(rect: &mut Frame<B>, app: &App)
67where
68    B: Backend,
69{
70    let size = rect.size();
71    // TODO check size
72
73    let logs = app.global().logs;
74    let mut constraints = vec![Constraint::Length(3), Constraint::Percentage(60)];
75
76    if logs {
77        constraints.push(Constraint::Percentage(40));
78    }
79
80    // Vertical layout
81    let chunks = Layout::default()
82        .direction(Direction::Vertical)
83        .constraints(constraints)
84        .split(size);
85
86    // Title block
87    let title = draw_title(&app.args);
88    rect.render_widget(title, chunks[0]);
89
90    // Main
91    app.state().render(RenderContext {
92        frame: rect,
93        rect: chunks[1],
94    });
95
96    // Logs
97    if logs {
98        let logs = draw_logs();
99        rect.render_widget(logs, chunks[2]);
100    }
101}
102
103fn draw_title<'a>(args: &Args) -> Paragraph<'a> {
104    Paragraph::new(format!(
105        "Poddy ({})",
106        args.namespace.as_deref().unwrap_or("<current>")
107    ))
108    .style(Style::default().fg(Color::White))
109    .alignment(Alignment::Center)
110    .block(
111        Block::default()
112            .borders(Borders::ALL)
113            .style(Style::default().fg(Color::White))
114            .border_type(BorderType::Plain),
115    )
116}
117
118fn draw_logs<'a>() -> TuiLoggerWidget<'a> {
119    TuiLoggerWidget::default()
120        .output_timestamp(Some("%H:%M:%S%.3f".into()))
121        .output_level(Some(TuiLoggerLevelOutput::Abbreviated))
122        .style_error(Style::default().fg(Color::Red))
123        .style_debug(Style::default().fg(Color::Green))
124        .style_warn(Style::default().fg(Color::Yellow))
125        .style_trace(Style::default().fg(Color::Gray))
126        .style_info(Style::default().fg(Color::Blue))
127        .block(
128            Block::default()
129                .title("Logs")
130                .border_style(Style::default().fg(Color::White).bg(Color::Black))
131                .borders(Borders::ALL),
132        )
133        .style(Style::default().fg(Color::White).bg(Color::Black))
134}