/ tui / src / ui.rs
ui.rs
  1  use ratatui::{
  2      Frame,
  3      layout::{Alignment, Constraint, Layout, Rect},
  4      style::{Color, Modifier, Style},
  5      symbols,
  6      text::{self, Span},
  7      widgets::{
  8          Block, BorderType, Cell, Paragraph, Row, Table, Tabs, Wrap,
  9          canvas::{self, Canvas, Circle, Map, MapResolution, Rectangle},
 10      },
 11  };
 12  use tachyonfx::Duration;
 13  // use tui_big_text::{BigText, PixelSize};
 14  
 15  use crate::app::App;
 16  
 17  pub fn draw(elapsed: Duration, frame: &mut Frame, app: &mut App) {
 18      let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(frame.area());
 19      let tabs = app
 20          .tabs
 21          .titles
 22          .iter()
 23          .map(|t| text::Line::from(Span::styled(*t, Style::default().fg(Color::LightGreen))))
 24          .collect::<Tabs>()
 25          .block(
 26              Block::bordered()
 27                  .title(Span::styled(
 28                      app.title,
 29                      Style::default()
 30                          .fg(Color::LightMagenta)
 31                          .add_modifier(Modifier::BOLD),
 32                  ))
 33                  .title_alignment(Alignment::Center)
 34                  .border_style(Color::Magenta)
 35                  .border_type(BorderType::Double),
 36          )
 37          .highlight_style(Style::default().fg(Color::LightYellow))
 38          .select(app.tabs.index);
 39      frame.render_widget(tabs, chunks[0]);
 40      match app.tabs.index {
 41          0 => draw_first_tab(frame, app, chunks[1]),
 42          1 => draw_second_tab(frame, app, chunks[1]),
 43          2 => draw_third_tab(frame, app, chunks[1]),
 44          _ => {}
 45      };
 46  
 47      let area = frame.area();
 48      app.effects
 49          .process_effects(elapsed, frame.buffer_mut(), area);
 50  }
 51  
 52  fn draw_first_tab(frame: &mut Frame, _app: &mut App, area: Rect) {
 53      draw_text(frame, area);
 54  }
 55  
 56  fn draw_text(frame: &mut Frame, area: Rect) {
 57      use ratatui::text::Line;
 58  
 59      let text = vec![
 60          Line::from(""),
 61          Line::from(vec![Span::styled(
 62              " โš“ Open-Source @ Microvisor Systems & LikeC4 core team ๐ŸงŠ",
 63              Style::default()
 64                  .fg(Color::LightCyan)
 65                  .add_modifier(Modifier::BOLD),
 66          )]),
 67          Line::from(""),
 68          Line::from(Span::styled(
 69              "๐Ÿ—  Site under construction ๐Ÿ—",
 70              Style::default().fg(Color::LightYellow),
 71          )),
 72          Line::from(""),
 73          Line::from(Span::styled(
 74              "๐ŸŽจ ========== ~/artwork ========== ๐ŸŽจ",
 75              Style::default()
 76                  .fg(Color::LightMagenta)
 77                  .add_modifier(Modifier::BOLD),
 78          )),
 79          Line::from(""),
 80          Line::from("๐Ÿ‘น DoomBSD:                 ๐Ÿ—"),
 81          Line::from("๐Ÿงฎ Microvisor:              ๐Ÿ—"),
 82          Line::from("๐Ÿฆ• Mira AMM:                mira.ly"),
 83          Line::from("โŒจ  cuHacking 2025 Platform: docs.cuhacking.ca"),
 84          Line::from(""),
 85          Line::from(Span::styled(
 86              "๐Ÿ“ฑ ========== ~/socials ========== ๐Ÿ“ฑ",
 87              Style::default()
 88                  .fg(Color::LightMagenta)
 89                  .add_modifier(Modifier::BOLD),
 90          )),
 91          Line::from(""),
 92          Line::from("๐Ÿฅ‚ LinkedIn:                linkedin.com/in/mfarabi"),
 93          Line::from("๐Ÿ™ GitHub:                  github.com/MFarabi619/MFarabi619"),
 94          Line::from(""),
 95          Line::from(Span::styled(
 96              "๐Ÿค” =========== ~/todo ============ ๐Ÿค”",
 97              Style::default()
 98                  .fg(Color::LightMagenta)
 99                  .add_modifier(Modifier::BOLD),
100          )),
101          Line::from(""),
102          Line::from(" [DONE] Create something, anything"),
103          Line::from(" [DONE] Deploy to Netlify"),
104          Line::from(" [DONE] Make responsive"),
105          Line::from(" [ ] Self-host from home data center with Kubernetes and/or Kubenix"),
106          Line::from(" [ ] Add hyperlink support"),
107          Line::from(" [ ] Add markdown support"),
108          Line::from(" [ ] Set up CI"),
109          Line::from(" [ ] Create regression test suite"),
110          Line::from(" [ ] Teach others how to do it too"),
111          Line::from(""),
112          Line::from(Span::styled(
113              "๐Ÿž github.com/MFarabi619/MFarabi619/apps/web",
114              Style::default()
115                  .fg(Color::LightCyan)
116                  .add_modifier(Modifier::UNDERLINED),
117          )),
118          Line::from(""),
119          Line::from(vec![
120              Span::raw("Made with utmost โค๏ธโ€๐Ÿ”ฅ by ๐Ÿ™Œ using "),
121              Span::styled("๐Ÿฆ€ Rust", Style::default().fg(Color::Rgb(250, 100, 0))),
122              Span::raw(", "),
123              Span::styled("โ„ Nix", Style::default().fg(Color::Rgb(80, 130, 255))),
124              Span::raw(", "),
125              Span::styled("๐Ÿ‘น FreeBSD", Style::default().fg(Color::LightRed)),
126              Span::raw(", and "),
127              Span::styled("๐Ÿ GNU/Linux ๐Ÿง", Style::default().fg(Color::LightGreen)),
128              Span::raw("."),
129          ]),
130          Line::from(""),
131          Line::from(vec![
132              Span::raw("Site theme heavily inspired by "),
133              Span::styled("Xe Iaso's ", Style::default().fg(Color::LightMagenta)),
134              Span::raw("blog: "),
135              Span::styled("xeiaso.net", Style::default().fg(Color::LightCyan)),
136          ]),
137      ];
138  
139      let block = Block::bordered()
140          .border_style(Color::Magenta)
141          .border_type(BorderType::Double)
142          .title(Span::styled(
143              "๐Ÿ“š README ๐Ÿ“š",
144              Style::default()
145                  .fg(Color::LightMagenta)
146                  .add_modifier(Modifier::BOLD),
147          ));
148  
149      let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });
150      frame.render_widget(paragraph, area);
151  }
152  
153  fn draw_second_tab(frame: &mut Frame, app: &mut App, area: Rect) {
154      let chunks =
155          Layout::horizontal([Constraint::Percentage(100), Constraint::Percentage(00)]).split(area);
156      let up_style = Style::default().fg(Color::LightGreen);
157      let idle_style = Style::default().fg(Color::LightYellow);
158      let failure_style = Style::default()
159          .fg(Color::LightRed)
160          .add_modifier(Modifier::RAPID_BLINK | Modifier::CROSSED_OUT);
161      let rows = app.servers.iter().map(|s| {
162          let style = if s.status == "Up" {
163              up_style
164          } else if s.status == "Idle" {
165              idle_style
166          } else {
167              failure_style
168          };
169          Row::new(vec![
170              s.user, s.hostname, s.chassis, s.os, s.kernel, s.display, s.desktop, s.cpu, s.gpu,
171              s.memory, s.disk, s.uptime, s.terminal, s.location, s.status,
172          ])
173          .style(style)
174      });
175      let table = Table::new(
176          rows,
177          [
178              Constraint::Length(10),
179              Constraint::Length(18),
180              Constraint::Length(18),
181              Constraint::Length(18),
182              Constraint::Length(18),
183              Constraint::Length(18),
184              Constraint::Length(18),
185              Constraint::Length(18),
186              Constraint::Length(15),
187              Constraint::Length(15),
188              Constraint::Length(10),
189              Constraint::Length(10),
190              Constraint::Length(10),
191              Constraint::Length(10),
192              Constraint::Length(10),
193              Constraint::Length(10),
194              Constraint::Length(10),
195          ],
196      )
197      .header(
198          Row::new(vec![
199              "๐Ÿ‘ฟ User",
200              "๐Ÿท Hostname",
201              "๐ŸŽ Chassis",
202              "๐Ÿง OS",
203              "๐Ÿ–ฅ Kernel",
204              "๐ŸŽ Display",
205              "๐Ÿš DE/WM",
206              "๐Ÿง  CPU",
207              "๐Ÿงซ GPU",
208              "๐Ÿ’ฝ Memory",
209              "๐Ÿ’พ Disk",
210              "๐Ÿงซ Uptime",
211              "๐Ÿ•น Terminal",
212              "๐ŸŒ Location",
213              "๐Ÿ” Status",
214          ])
215          .style(Style::default().fg(Color::White))
216          .top_margin(1)
217          .bottom_margin(1),
218      )
219      .block(
220          Block::bordered()
221              .title(Span::styled(
222                  "๐Ÿ“  Servers ๐Ÿ“ ",
223                  Style::default()
224                      .fg(Color::LightMagenta)
225                      .add_modifier(Modifier::BOLD),
226              ))
227              .border_style(Color::Magenta)
228              .border_type(BorderType::Double),
229      );
230      frame.render_widget(table, chunks[0]);
231  
232      let _map = Canvas::default()
233          .block(Block::bordered().title("World"))
234          .paint(|ctx| {
235              ctx.draw(&Map {
236                  color: Color::White,
237                  resolution: MapResolution::High,
238              });
239              ctx.layer();
240              ctx.draw(&Rectangle {
241                  x: 0.0,
242                  y: 30.0,
243                  width: 10.0,
244                  height: 10.0,
245                  color: Color::Yellow,
246              });
247              ctx.draw(&Circle {
248                  x: app.servers[2].coords.1,
249                  y: app.servers[2].coords.0,
250                  radius: 10.0,
251                  color: Color::LightGreen,
252              });
253              for (i, s1) in app.servers.iter().enumerate() {
254                  for s2 in &app.servers[i + 1..] {
255                      ctx.draw(&canvas::Line {
256                          x1: s1.coords.1,
257                          y1: s1.coords.0,
258                          y2: s2.coords.0,
259                          x2: s2.coords.1,
260                          color: Color::Yellow,
261                      });
262                  }
263              }
264              for server in &app.servers {
265                  let color = if server.status == "Up" {
266                      Color::LightGreen
267                  } else {
268                      Color::LightYellow
269                  };
270                  ctx.print(
271                      server.coords.1,
272                      server.coords.0,
273                      Span::styled("X", Style::default().fg(color)),
274                  );
275              }
276          })
277          .marker(if app.enhanced_graphics {
278              symbols::Marker::Braille
279          } else {
280              symbols::Marker::Dot
281          })
282          .x_bounds([-180.0, 180.0])
283          .y_bounds([-90.0, 90.0]);
284      // frame.render_widget(map, chunks[1]);
285  }
286  
287  fn draw_third_tab(frame: &mut Frame, _app: &mut App, area: Rect) {
288      // let chunks = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).split(area);
289      let chunks = Layout::horizontal([Constraint::Ratio(1, 1)]).split(area);
290      let colors = [
291          Color::Reset,
292          Color::Black,
293          Color::Red,
294          Color::Green,
295          Color::Yellow,
296          Color::Blue,
297          Color::LightMagenta,
298          Color::Cyan,
299          Color::Gray,
300          Color::DarkGray,
301          Color::LightRed,
302          Color::LightGreen,
303          Color::LightYellow,
304          Color::LightBlue,
305          Color::LightMagenta,
306          Color::LightCyan,
307          Color::White,
308      ];
309      let items: Vec<Row> = colors
310          .iter()
311          .map(|c| {
312              let cells = vec![
313                  Cell::from(Span::raw(format!("{c:?}: "))),
314                  Cell::from(Span::styled("Foreground", Style::default().fg(*c))),
315                  Cell::from(Span::styled("Background", Style::default().bg(*c))),
316              ];
317              Row::new(cells)
318          })
319          .collect();
320  
321      let table = Table::new(
322          items,
323          [
324              Constraint::Ratio(1, 3),
325              Constraint::Ratio(1, 3),
326              Constraint::Ratio(1, 3),
327              Constraint::Ratio(1, 3),
328              Constraint::Ratio(1, 3),
329          ],
330      )
331      .block(
332          Block::bordered()
333              .title(Span::styled(
334                  "๐Ÿคน workspace ๐Ÿคน",
335                  Style::default()
336                      .fg(Color::LightMagenta)
337                      .add_modifier(Modifier::BOLD),
338              ))
339              .border_style(Color::Magenta)
340              .border_type(BorderType::Double),
341      );
342  
343      frame.render_widget(table, chunks[0]);
344  }