liftof_tui/tabs/
tab_home.rs

1//! The front page of the app, including a view of received packets
2
3use std::collections::{
4  HashMap,
5  VecDeque,
6};
7
8use std::time::{
9  Instant,
10  //Duration
11};
12
13use std::sync::{
14  Arc,
15  Mutex,
16};
17use ratatui::prelude::*;
18
19//use ratatui::terminal::Frame;
20use ratatui::Frame;
21use ratatui::layout::Rect;
22use ratatui::widgets::{
23  Block,
24  BorderType,
25  Borders,
26  Paragraph,
27  Table,
28  Row,
29};
30
31//use liftof_lib::LIFTOF_LOGO_SHOW;
32use gondola_core::prelude::LIFTOF_LOGO_SHOW;
33
34use crate::colors::ColorTheme;
35
36
37#[derive(Debug, Clone)]
38pub struct HomeTab<'a> {
39  pub theme      : ColorTheme,
40  pub streamer   : Arc<Mutex<VecDeque<String>>>,
41  pub pack_stat  : Arc<Mutex<HashMap<&'a str, (usize,usize)>>>,
42  pub stream     : String,
43  pub stream_max : usize, 
44  start_time     : Instant,
45}
46
47impl HomeTab<'_> {
48  pub fn new(theme     : ColorTheme,
49             streamer  : Arc<Mutex<VecDeque<String>>>,
50             pack_stat : Arc<Mutex<HashMap<&str,(usize,usize)>>>) -> HomeTab<'_> {
51    HomeTab {
52      theme,
53      streamer, 
54      pack_stat,
55      stream     : String::from(""),
56      stream_max : 30,
57      start_time : Instant::now(),
58    }
59  }
60
61  pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
62    let main_chunks = Layout::default()
63      .direction(Direction::Horizontal)
64      .constraints(
65          [Constraint::Percentage(60),
66           Constraint::Percentage(40)].as_ref(),
67      )
68      .split(*main_window);
69    
70    let upper_chunks = Layout::default()
71        .direction(Direction::Vertical)
72        .constraints(
73            [Constraint::Percentage(75),
74            Constraint::Percentage(25)].as_ref(),
75        )
76        .split(main_chunks[0]);
77 
78    let mut rows      = Vec::<Row>::new();
79    let mut sum_pack  = 0;
80    let mut sum_bytes = 0;
81    let passed_time   = self.start_time.elapsed().as_secs_f64();
82    match self.pack_stat.lock() {
83      Err(_err) => (),
84      Ok(mut _stat) =>  {
85        for k in _stat.keys() {
86          //stat_string_render += "  -- -- -- -- -- -- -- -- -- --\n";
87          if _stat[k].0 != 0 {
88            sum_pack  += _stat[k].0;
89            sum_bytes += _stat[k].1;
90            if k.contains("HB"){ // heartbeats
91              rows.push(Row::new(vec![format!("  \u{1f493} {:.1}", _stat[k].0),
92                                      format!("{:.1}", (_stat[k].0 as f64)/passed_time,),
93                                      format!("{:.1}", 0.008*(_stat[k].1 as f64)/passed_time,),
94                                      format!("[{}]", k)]));
95            } else {
96              rows.push(Row::new(vec![format!("  \u{279f} {:.1}", _stat[k].0),
97                                      format!("{:.1}", (_stat[k].0 as f64)/passed_time,),
98                                      format!("{:.1}", 0.008*(_stat[k].1 as f64)/passed_time,),
99                                      format!("[{}]", k)]));
100            }
101          }
102        }
103      } 
104    }
105    rows.push(Row::new(vec!["  \u{FE4C}\u{FE4C}\u{FE4C}","\u{FE4C}\u{FE4C}","\u{FE4C}\u{FE4C}\u{FE4C}\u{FE4C}\u{FE4C}\u{FE4C}"])); 
106    rows.push(Row::new(vec![format!("  \u{279f}{}", sum_pack),
107                       format!("{:.1}/s", (sum_pack as f64)/passed_time),
108                       format!("{:.1}kBit/s", 0.008*&(sum_bytes as f64)/passed_time),
109                       format!("[TOTAL]")]));
110    
111    let widths = [Constraint::Percentage(30),
112                  Constraint::Percentage(10),
113                  Constraint::Percentage(30),
114                  Constraint::Percentage(30)];
115    let table  = Table::new(rows, widths)
116      .column_spacing(1)
117      .header(
118        Row::new(vec!["  N", "\u{1f4e6}/s", "kBit/s", "Type"])
119        .bottom_margin(1)
120        .top_margin(1)
121        .style(Style::new().add_modifier(Modifier::UNDERLINED))
122      )
123      .block(Block::new()
124             .title("Packet summary \u{1f4e6}")
125             .borders(Borders::ALL)
126             .border_type(BorderType::Rounded)
127             )
128      .style(self.theme.style());
129
130    let main_view = Paragraph::new(LIFTOF_LOGO_SHOW)
131    .style(self.theme.style())
132    .alignment(Alignment::Center)
133    .block(
134      Block::default()
135        .borders(Borders::NONE)
136    );
137    
138    match self.streamer.lock() {
139      Err(_err) => (),
140      Ok(mut _vecdeque) =>  {
141        self.stream = _vecdeque
142            .iter()
143            .cloned() // Clone each string to avoid moving ownership
144            .collect::<Vec<String>>()
145            .join("\n");
146        //if _vecdeque.len() > self.stream_max {
147        while _vecdeque.len() > self.stream_max {
148          _vecdeque.pop_front();
149        }
150      }, 
151    }
152    //let stream : String = String::from("");
153    let side_view = Paragraph::new(self.stream.clone())
154    .style(self.theme.style())
155    .alignment(Alignment::Left)
156    .block(
157      Block::default()
158        .borders(Borders::ALL)
159        .border_type(BorderType::Rounded)
160        .title("Stream")
161    );
162    frame.render_widget(main_view,       main_chunks[0]);
163    frame.render_widget(side_view,       upper_chunks[1]);
164    frame.render_widget(table,           main_chunks[1])
165  }
166}
167