summaryrefslogtreecommitdiffstats
path: root/src/app/widgets/bottom_widgets/net.rs
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-08-31 17:19:05 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2021-09-05 19:09:11 -0400
commiteddc9a16c7ac1be90144e8a247b13d6fd96238cf (patch)
tree8d197acfebf39a02cd8cc4abd0770f4508cbae6f /src/app/widgets/bottom_widgets/net.rs
parent204b4dc3510b70618741f82a1e2df6c17c6dadda (diff)
refactor: move basic mode over
Because writing your own layout system and management is just *so much fun*. Totally. ------------------------------------------------------------------- Moves the basic mode system over to the new drawing/widget system. In the process, it has forced me to completely redo how we do layouts... again. This is because basic mode has widgets that control their own height - this means the height of the columns and rows that wrap it are also affected by the widget's height. The previous system, using a constraint tree and splitting draw Rects via tui-rs' built-in constraint solver, did not support this concept very well. It was not simple to propagate up the widths/heights towards parents while also using tui-rs' built-in constraint solver. In the end, it was easier to just rewrite it using another algorithm. We now follow a process very similar to Flutter's layout system. Relevant links to the Flutter docs are found in the code or below: - https://flutter.dev/docs/development/ui/layout/constraints - https://flutter.dev/docs/resources/inside-flutter#sublinear-layouts The gist of it, however, is that we now instead a few new options for any element in the layout tree. A node can either: - Grow to fill remaining space - Take up as much room as its children - Be a specific length Technically right now, it's not perfect, in that leaf nodes can be as large as their children (which makes no sense), though in that case it just treats it as an expand.
Diffstat (limited to 'src/app/widgets/bottom_widgets/net.rs')
-rw-r--r--src/app/widgets/bottom_widgets/net.rs782
1 files changed, 782 insertions, 0 deletions
diff --git a/src/app/widgets/bottom_widgets/net.rs b/src/app/widgets/bottom_widgets/net.rs
new file mode 100644
index 00000000..00a93c8e
--- /dev/null
+++ b/src/app/widgets/bottom_widgets/net.rs
@@ -0,0 +1,782 @@
+use std::{borrow::Cow, collections::HashMap, time::Instant};
+
+use tui::{
+ backend::Backend,
+ layout::{Constraint, Direction, Layout, Rect},
+ widgets::{Block, Borders},
+ Frame,
+};
+
+use crate::{
+ app::{
+ data_farmer::DataCollection, text_table::SimpleColumn, time_graph::TimeGraphData,
+ AppConfigFields, AxisScaling, Component, TextTable, TimeGraph, Widget,
+ },
+ canvas::Painter,
+ data_conversion::convert_network_data_points,
+ options::layout_options::LayoutRule,
+ units::data_units::DataUnit,
+ utils::gen_util::*,
+};
+
+pub struct NetWidgetState {
+ pub current_display_time: u64,
+ pub autohide_timer: Option<Instant>,
+ // pub draw_max_range_cache: f64,
+ // pub draw_labels_cache: Vec<String>,
+ // pub draw_time_start_cache: f64,
+ // TODO: Re-enable these when we move net details state-side!
+ // pub unit_type: DataUnitTypes,
+ // pub scale_type: AxisScaling,
+}
+
+impl NetWidgetState {
+ pub fn init(
+ current_display_time: u64,
+ autohide_timer: Option<Instant>,
+ // unit_type: DataUnitTypes,
+ // scale_type: AxisScaling,
+ ) -> Self {
+ NetWidgetState {
+ current_display_time,
+ autohide_timer,
+ // draw_max_range_cache: 0.0,
+ // draw_labels_cache: vec![],
+ // draw_time_start_cache: 0.0,
+ // unit_type,
+ // scale_type,
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct NetState {
+ pub force_update: Option<u64>,
+ pub widget_states: HashMap<u64, NetWidgetState>,
+}
+
+impl NetState {
+ pub fn init(widget_states: HashMap<u64, NetWidgetState>) -> Self {
+ NetState {
+ force_update: None,
+ widget_states,
+ }
+ }
+
+ pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> {
+ self.widget_states.get_mut(&widget_id)
+ }
+
+ pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> {
+ self.widget_states.get(&widget_id)
+ }
+}
+
+// --- NEW STUFF BELOW ---
+
+/// Returns the max data point and time given a time.
+fn get_max_entry(
+ rx: &[(f64, f64)], tx: &[(f64, f64)], time_start: f64, network_scale_type: &AxisScaling,
+ network_use_binary_prefix: bool,
+) -> (f64, f64) {
+ /// Determines a "fake" max value in circumstances where we couldn't find one from the data.
+ fn calculate_missing_max(
+ network_scale_type: &AxisScaling, network_use_binary_prefix: bool,
+ ) -> f64 {
+ match network_scale_type {
+ AxisScaling::Log => {
+ if network_use_binary_prefix {
+ LOG_KIBI_LIMIT
+ } else {
+ LOG_KILO_LIMIT
+ }
+ }
+ AxisScaling::Linear => {
+ if network_use_binary_prefix {
+ KIBI_LIMIT_F64
+ } else {
+ KILO_LIMIT_F64
+ }
+ }
+ }
+ }
+
+ // First, let's shorten our ranges to actually look. We can abuse the fact that our rx and tx arrays
+ // are sorted, so we can short-circuit our search to filter out only the relevant data points...
+ let filtered_rx = if let (Some(rx_start), Some(rx_end)) = (
+ rx.iter().position(|(time, _data)| *time >= time_start),
+ rx.iter().rposition(|(time, _data)| *time <= 0.0),
+ ) {
+ Some(&rx[rx_start..=rx_end])
+ } else {
+ None
+ };
+
+ let filtered_tx = if let (Some(tx_start), Some(tx_end)) = (
+ tx.iter().position(|(time, _data)| *time >= time_start),
+ tx.iter().rposition(|(time, _data)| *time <= 0.0),
+ ) {
+ Some(&tx[tx_start..=tx_end])
+ } else {
+ None
+ };
+
+ // Then, find the maximal rx/tx so we know how to scale, and return it.
+ match (filtered_rx, filtered_tx) {
+ (None, None) => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
+ (None, Some(filtered_tx)) => {
+ match filtered_tx
+ .iter()
+ .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
+ {
+ Some((best_time, max_val)) => {
+ if *max_val == 0.0 {
+ (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ )
+ } else {
+ (*best_time, *max_val)
+ }
+ }
+ None => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
+ }
+ }
+ (Some(filtered_rx), None) => {
+ match filtered_rx
+ .iter()
+ .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
+ {
+ Some((best_time, max_val)) => {
+ if *max_val == 0.0 {
+ (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ )
+ } else {
+ (*best_time, *max_val)
+ }
+ }
+ None => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
+ }
+ }
+ (Some(filtered_rx), Some(filtered_tx)) => {
+ match filtered_rx
+ .iter()
+ .chain(filtered_tx)
+ .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
+ {
+ Some((best_time, max_val)) => {
+ if *max_val == 0.0 {
+ (
+ *best_time,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ )
+ } else {
+ (*best_time, *max_val)
+ }
+ }
+ None => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
+ }
+ }
+ }
+}
+
+/// Returns the required max data point and labels.
+fn adjust_network_data_point(
+ max_entry: f64, network_scale_type: &AxisScaling, network_unit_type: &DataUnit,
+ network_use_binary_prefix: bool,
+) -> (f64, Vec<String>) {
+ // So, we're going with an approach like this for linear data:
+ // - Main goal is to maximize the amount of information displayed given a specific height.
+ // We don't want to drown out some data if the ranges are too far though! Nor do we want to filter
+ // out too much data...
+ // - Change the y-axis unit (kilo/kibi, mega/mebi...) dynamically based on max load.
+ //
+ // The idea is we take the top value, build our scale such that each "point" is a scaled version of that.
+ // So for example, let's say I use 390 Mb/s. If I drew 4 segments, it would be 97.5, 195, 292.5, 390, and
+ // probably something like 438.75?
+ //
+ // So, how do we do this in tui-rs? Well, if we are using intervals that tie in perfectly to the max
+ // value we want... then it's actually not that hard. Since tui-rs accepts a vector as labels and will
+ // properly space them all out... we just work with that and space it out properly.
+ //
+ // Dynamic chart idea based off of FreeNAS's chart design.
+ //
+ // ===
+ //
+ // For log data, we just use the old method of log intervals (kilo/mega/giga/etc.). Keep it nice and simple.
+
+ // Now just check the largest unit we correspond to... then proceed to build some entries from there!
+
+ let unit_char = match network_unit_type {
+ DataUnit::Byte => "B",
+ DataUnit::Bit => "b",
+ };
+
+ match network_scale_type {
+ AxisScaling::Linear => {
+ let (k_limit, m_limit, g_limit, t_limit) = if network_use_binary_prefix {
+ (
+ KIBI_LIMIT_F64,
+ MEBI_LIMIT_F64,
+ GIBI_LIMIT_F64,
+ TEBI_LIMIT_F64,
+ )
+ } else {
+ (
+ KILO_LIMIT_F64,
+ MEGA_LIMIT_F64,
+ GIGA_LIMIT_F64,
+ TERA_LIMIT_F64,
+ )
+ };
+
+ let bumped_max_entry = max_entry * 1.5; // We use the bumped up version to calculate our unit type.
+ let (max_value_scaled, unit_prefix, unit_type): (f64, &str, &str) =
+ if bumped_max_entry < k_limit {
+ (max_entry, "", unit_char)
+ } else if bumped_max_entry < m_limit {
+ (
+ max_entry / k_limit,
+ if network_use_binary_prefix { "Ki" } else { "K" },
+ unit_char,
+ )
+ } else if bumped_max_entry < g_limit {
+ (
+ max_entry / m_limit,
+ if network_use_binary_prefix { "Mi" } else { "M" },
+ unit_char,
+ )
+ } else if bumped_max_entry < t_limit {
+ (
+ max_entry / g_limit,
+ if network_use_binary_prefix { "Gi" } else { "G" },
+ unit_char,
+ )
+ } else {
+ (
+ max_entry / t_limit,
+ if network_use_binary_prefix { "Ti" } else { "T" },
+ unit_char,
+ )
+ };
+
+ // Finally, build an acceptable range starting from there, using the given height!
+ // Note we try to put more of a weight on the bottom section vs. the top, since the top has less data.
+
+ let base_unit = max_value_scaled;
+ let labels: Vec<String> = vec![
+ format!("0{}{}", unit_prefix, unit_type),
+ format!("{:.1}", base_unit * 0.5),
+ format!("{:.1}", base_unit),
+ format!("{:.1}", base_unit * 1.5),
+ ]
+ .into_iter()
+ .map(|s| format!("{:>5}", s)) // Pull 5 as the longest legend value is generally going to be 5 digits (if they somehow hit over 5 terabits per second)
+ .collect();
+
+ (bumped_max_entry, labels)
+ }
+ AxisScaling::Log => {
+ let (m_limit, g_limit, t_limit) = if network_use_binary_prefix {
+ (LOG_MEBI_LIMIT, LOG_GIBI_LIMIT, LOG_TEBI_LIMIT)
+ } else {
+ (LOG_MEGA_LIMIT, LOG_GIGA_LIMIT, LOG_TERA_LIMIT)
+ };
+
+ fn get_zero(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "{}0{}",
+ if network_use_binary_prefix { " " } else { " " },
+ unit_char
+ )
+ }
+
+ fn get_k(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Ki" } else { "K" },
+ unit_char
+ )
+ }
+
+ fn get_m(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Mi" } else { "M" },
+ unit_char
+ )
+ }
+
+ fn get_g(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Gi" } else { "G" },
+ unit_char
+ )
+ }
+
+ fn get_t(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Ti" } else { "T" },
+ unit_char
+ )
+ }
+
+ fn get_p(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Pi" } else { "P" },
+ unit_char
+ )
+ }
+
+ if max_entry < m_limit {
+ (
+ m_limit,
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ ],
+ )
+ } else if max_entry < g_limit {
+ (
+ g_limit,
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ get_g(network_use_binary_prefix, unit_char),
+ ],
+ )
+ } else if max_entry < t_limit {
+ (
+ t_limit,
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ get_g(network_use_binary_prefix, unit_char),
+ get_t(network_use_binary_prefix, unit_char),
+ ],
+ )
+ } else {
+ // I really doubt anyone's transferring beyond petabyte speeds...
+ (
+ if network_use_binary_prefix {
+ LOG_PEBI_LIMIT
+ } else {
+ LOG_PETA_LIMIT
+ },
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ get_g(network_use_binary_prefix, unit_char),
+ get_t(network_use_binary_prefix, unit_char),
+ get_p(network_use_binary_prefix, unit_char),
+ ],
+ )
+ }
+ }
+ }
+}
+
+/// A struct containing useful cached information for a [`NetGraph`].
+#[derive(Clone)]
+pub struct NetGraphCache {
+ max_value: f64,
+ cached_upper_bound: f64,
+ labels: Vec<Cow<'static, str>>,
+}
+
+enum NetGraphCacheState {
+ Uncached,
+ Cached(NetGraphCache),
+}
+
+/// A widget denoting network usage via a graph. This version is self-contained within a single [`TimeGraph`];
+/// if you need the older one that splits into two sections, use [`OldNetGraph`], which is built on a [`NetGraph`].
+///
+/// As of now, this is essentially just a wrapper around a [`TimeGraph`].
+pub struct NetGraph {
+ /// The graph itself. Just a [`TimeGraph`].
+ graph: TimeGraph,
+
+ // Cached details for drawing purposes; probably want to move at some point...
+ draw_cache: NetGraphCacheState,
+
+ pub rx_display: String,
+ pub tx_display: String,
+ pub total_rx_display: String,
+ pub total_tx_display: String,
+ pub network_data_rx: Vec<(f64, f64)>,
+ pub network_data_tx: Vec<(f64, f64)>,
+
+ pub scale_type: AxisScaling,
+ pub unit_type: DataUnit,
+ pub use_binary_prefix: bool,
+
+ hide_legend: bool,
+
+ bounds: Rect,
+ width: LayoutRule,
+ height: LayoutRule,
+}
+
+impl NetGraph {
+ /// Creates a new [`NetGraph`] given a [`AppConfigFields`].
+ pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
+ let graph = TimeGraph::from_config(app_config_fields);
+
+ Self {
+ graph,
+ draw_cache: NetGraphCacheState::Uncached,
+ rx_display: Default::default(),
+ tx_display: Default::default(),
+ total_rx_display: Default::default(),
+ total_tx_display: Default::default(),
+ network_data_rx: Default::default(),
+ network_data_tx: Default::default(),
+ scale_type: app_config_fields.network_scale_type.clone(),
+ unit_type: app_config_fields.network_unit_type.clone(),
+ use_binary_prefix: app_config_fields.network_use_binary_prefix,
+ hide_legend: false,
+ bounds: Rect::default(),
+ width: LayoutRule::default(),
+ height: LayoutRule::default(),
+ }
+ }
+
+ /// Hides the legend. Only really useful for [`OldNetGraph`].
+ pub fn hide_legend(mut self) -> Self {
+ self.hide_legend = true;
+ self
+ }
+
+ /// Sets the width.
+ pub fn width(mut self, width: LayoutRule) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the height.
+ pub fn height(mut self, height: LayoutRule) -> Self {
+ self.height = height;
+ self
+ }
+
+ /// Sets the draw cache for a [`NetGraph`].
+ pub fn set_draw_cache(&mut self) {
+ let current_time = -(self.graph.get_current_display_time() as f64);
+ let (_current_max_time, current_max_value) = get_max_entry(
+ &self.network_data_rx,
+ &self.network_data_tx,
+ current_time,
+ &self.scale_type,
+ self.use_binary_prefix,
+ );
+
+ match &mut self.draw_cache {
+ NetGraphCacheState::Uncached => {
+ let (cached_upper_bound, labels) = adjust_network_data_point(
+ current_max_value,
+ &self.scale_type,
+ &self.unit_type,
+ self.use_binary_prefix,
+ );
+
+ let labels: Vec<Cow<'static, str>> = labels.into_iter().map(Into::into).collect();
+
+ self.draw_cache = NetGraphCacheState::Cached(NetGraphCache {
+ max_value: current_max_value,
+ cached_upper_bound,
+ labels: labels.clone(),
+ });
+ }
+ NetGraphCacheState::Cached(cache) => {
+ if (current_max_value - cache.max_value).abs() > f64::EPSILON {
+ // Invalidated.
+ let (upper_bound, labels) = adjust_network_data_point(
+ current_max_value,
+ &self.scale_type,
+ &self.unit_type,
+ self.use_binary_prefix,
+ );
+
+ *cache = NetGraphCache {
+ max_value: current_max_value,
+ cached_upper_bound: upper_bound,
+ labels: labels.into_iter().map(Into::into).collect(),
+ };
+ }
+ }
+ }
+ }
+}
+
+impl Component for NetGraph {
+ fn bounds(&self) -> Rect {
+ self.bounds
+ }
+
+ fn set_bounds(&mut self, new_bounds: Rect) {
+ self.bounds = new_bounds;
+ }
+
+ fn handle_key_event(
+ &mut self, event: crossterm::event::KeyEvent,
+ ) -> crate::app::event::WidgetEventResult {
+ self.graph.handle_key_event(event)
+ }
+
+ fn handle_mouse_event(
+ &mut self, event: crossterm::event::MouseEvent,
+ ) -> crate::app::event::WidgetEventResult {
+ self.graph.handle_mouse_event(event)
+ }
+}
+
+impl Widget for NetGraph {
+ fn get_pretty_name(&self) -> &'static str {
+ "Network"
+ }
+
+ fn draw<B: Backend>(
+ &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
+ ) {
+ let block = Block::default()
+ .border_style(if selected {
+ painter.colours.highlighted_border_style
+ } else {
+ painter.colours.border_style
+ })
+ .borders(Borders::ALL);
+
+ self.set_draw_cache();
+
+ let chart_data = vec![
+ TimeGraphData {
+ data: &self.network_data_rx,
+ label: if self.hide_legend {
+ None
+ } else {
+ Some(self.rx_display.clone().into())
+ },
+ style: painter.colours.rx_style,
+ },
+ TimeGraphData {
+ data: &self.network_data_tx,
+ label: if self.hide_legend {
+ None
+ } else {
+ Some(self.tx_display.clone().into())
+ },
+ style: painter.colours.tx_style,
+ },
+ ];
+
+ let (y_bounds, y_bound_labels) = match &self.draw_cache {
+ NetGraphCacheState::Cached(cache) => ([0.0, cache.cached_upper_bound], &cache.labels),
+ NetGraphCacheState::Uncached => unreachable!(),
+ };
+
+ self.graph.draw_tui_chart(
+ painter,
+ f,
+ &chart_data,
+ y_bound_labels,
+ y_bounds,
+ false,
+ block,
+ area,
+ );
+ }
+
+ fn update_data(&mut self, data_collection: &DataCollection) {
+ let network_data = convert_network_data_points(
+ data_collection,
+ false, // TODO: I think the is_frozen here is also useless; see mem and cpu
+ false,
+ &self.scale_type,
+ &self.unit_type,
+ self.use_binary_prefix,
+ );
+ self.network_data_rx = network_data.rx;
+ self.network_data_tx = network_data.tx;
+ self.rx_display = network_data.rx_display;
+ self.tx_display = network_data.tx_display;
+ if let Some(total_rx_display) = network_data.total_rx_display {
+ self.total_rx_display = total_rx_display;
+ }
+ if let Some(total_tx_display) = network_data.total_tx_display {
+ self.total_tx_display = total_tx_display;
+ }
+ }
+
+ fn width(&self) -> LayoutRule {
+ self.width
+ }
+
+ fn height(&self) -> LayoutRule {
+ self.height
+ }
+}
+
+/// A widget denoting network usage via a graph and a separate, single row table. This is built on [`NetGraph`],
+/// and the main difference is that it also contains a bounding box for the graph + text.
+pub struct OldNetGraph {
+ net_graph: NetGraph,
+ table: TextTable,
+ bounds: Rect,
+ width: LayoutRule,
+ height: LayoutRule,
+}
+
+impl OldNetGraph {
+ /// Creates a new [`OldNetGraph`] from a [`AppConfigFields`].
+ pub fn from_config(config: &AppConfigFields) -> Self {
+ Self {
+ net_graph: NetGraph::from_config(config).hide_legend(),
+ table: TextTable::new(vec![
+ SimpleColumn::new_flex("RX".into(), 0.25),
+ SimpleColumn::new_flex("TX".into(), 0.25),
+ SimpleColumn::new_flex("Total RX".into(), 0.25),
+ SimpleColumn::new_flex("Total TX".into(), 0.25),
+ ])
+ .unselectable(),
+ bounds: Rect::default(),
+ width: LayoutRule::default(),
+ height: LayoutRule::default(),
+ }
+ }
+
+ /// Sets the width.
+ pub fn width(mut self, width: LayoutRule) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the height.
+ pub fn height(mut self, height: LayoutRule) -> Self {
+ self.height = height;
+ self
+ }
+}
+
+impl Component for OldNetGraph {
+ fn bounds(&self) -> Rect {
+ self.bounds
+ }
+
+ fn set_bounds(&mut self, new_bounds: Rect) {
+ self.bounds = new_bounds;
+ }
+
+ fn handle_key_event(
+ &mut self, event: crossterm::event::KeyEvent,
+ ) -> crate::app::event::WidgetEventResult {
+ self.net_graph.handle_key_event(event)
+ }
+
+ fn handle_mouse_event(
+ &mut self, event: crossterm::event::MouseEvent,
+ ) -> crate::app::event::WidgetEventResult {
+ self.net_graph.handle_mouse_event(event)
+ }
+}
+
+impl Widget for OldNetGraph {
+ fn get_pretty_name(&self) -> &'static str {
+ "Network"
+ }
+
+ fn draw<B: Backend>(
+ &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
+ ) {
+ const CONSTRAINTS: [Constraint; 2] = [Constraint::Min(0), Constraint::Length(4)];
+
+ let split_area = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(CONSTRAINTS)
+ .split(area);
+
+ let graph_area = split_area[0];
+ let table_area = split_area[1];
+
+ self.net_graph.draw(painter, f, graph_area, selected);
+
+ let table_block = Block::default()
+ .border_style(if selected {
+ painter.colours.highlighted_border_style
+ } else {
+ painter.colours.border_style
+ })
+ .borders(Borders::ALL);
+ self.table.draw_tui_table(
+ painter,
+ f,
+ &[vec![
+ (
+ self.net_graph.rx_display.clone().into(),
+ None,
+ Some(painter.colours.rx_style),
+ ),
+ (
+ self.net_graph.tx_display.clone().into(),
+ None,
+ Some(painter.colours.tx_style),
+ ),
+ (self.net_graph.total_rx_display.clone().into(), None, None),
+ (self.net_graph.total_tx_display.clone().into(), None, None),
+ ]],
+ table_block,
+ table_area,
+ selected,
+ );
+ }
+
+ fn update_data(&mut self, data_collection: &DataCollection) {
+ let network_data = convert_network_data_points(
+ data_collection,
+ false, // TODO: I think the is_frozen here is also useless; see mem and cpu
+ true,
+ &self.net_graph.scale_type,
+ &self.net_graph.unit_type,
+ self.net_graph.use_binary_prefix,
+ );
+ self.net_graph.network_data_rx = network_data.rx;
+ self.net_graph.network_data_tx = network_data.tx;
+ self.net_graph.rx_display = network_data.rx_display;
+ self.net_graph.tx_display = network_data.tx_display;
+ if let Some(total_rx_display) = network_data.total_rx_display {
+ self.net_graph.total_rx_display = total_rx_display;
+ }
+ if let Some(total_tx_display) = network_data.total_tx_display {
+ self.net_graph.total_tx_display = total_tx_display;
+ }
+ }
+
+ fn width(&self) -> LayoutRule {
+ self.width
+ }
+
+ fn height(&self) -> LayoutRule {
+ self.height
+ }
+}