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 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 let chunks = Layout::default()
82 .direction(Direction::Vertical)
83 .constraints(constraints)
84 .split(size);
85
86 let title = draw_title(&app.args);
88 rect.render_widget(title, chunks[0]);
89
90 app.state().render(RenderContext {
92 frame: rect,
93 rect: chunks[1],
94 });
95
96 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}