bitcoin_terminal_dashboard/app/ui/
mod.rs

1use std::u16::MAX;
2
3use tui::backend::Backend;
4use tui::layout::{Alignment, Constraint, Direction, Layout};
5use tui::style::{Color, Style};
6use tui::widgets::{Block, BorderType, Borders, Paragraph};
7use tui::Frame;
8
9use self::components::metrics_section_component::{
10    blockchain_data_component, difficulty_data_component, market_data_component,
11    mining_data_component, transactions_data_component,
12};
13use super::state::AppState;
14use crate::app::App;
15mod components;
16
17const ASCII_ART: &str = r#"
18  ___ _ _          _         ___          _    
19 | _ |_) |_ __ ___(_)_ _    |   \ __ _ __| |_  
20 | _ \ |  _/ _/ _ \ | ' \   | |) / _` (_-< ' \ 
21 |___/_|\__\__\___/_|_||_|  |___/\__,_/__/_||_|
22"#;
23
24const BITCOIN_ORANGE_COLOR: Color = Color::Rgb(242, 169, 0);
25
26fn title_component<'a>() -> Paragraph<'a> {
27    Paragraph::new(ASCII_ART.replacen("\n", "", 1))
28        .style(Style::default().fg(BITCOIN_ORANGE_COLOR))
29        .alignment(Alignment::Center)
30        .block(
31            Block::default()
32                .borders(Borders::ALL)
33                .style(Style::default().fg(Color::Cyan))
34                .border_type(BorderType::Rounded),
35        )
36}
37
38pub fn draw<B>(rect: &mut Frame<B>, app: &App)
39where
40    B: Backend,
41{
42    // Get Size of Terminal
43    let size = rect.size();
44
45    // Split terminal into title section and metrics section
46    let app_chunks = Layout::default()
47        .direction(Direction::Vertical)
48        .constraints([Constraint::Length(7), Constraint::Min(10)].as_ref())
49        .split(size);
50
51    let title_chunk = app_chunks[0];
52    let metrics_chunk = app_chunks[1];
53
54    // Render Title
55    let title_component = title_component();
56
57    // Create wrapper Box for Metrics chunk, with stylings
58    let metrics_block = Block::default()
59        .borders(Borders::ALL)
60        .title("Metrics")
61        .border_type(BorderType::Rounded);
62
63    let metric_sections_uis = match app.state() {
64        AppState::Init => vec![],
65        AppState::Initialized(initialized_data) => {
66            vec![
67                market_data_component(&initialized_data),
68                blockchain_data_component(&initialized_data),
69                transactions_data_component(initialized_data),
70                difficulty_data_component(initialized_data),
71                mining_data_component(initialized_data),
72            ]
73        }
74    };
75
76    type Width = u16;
77    enum ScreenWidth {
78        XSmall(Width),
79        Small(Width),
80        Medium(Width),
81        Large(Width),
82        XLarge(Width),
83    }
84
85    let screen_width = match size.width {
86        width @ 0..=100 => ScreenWidth::XSmall(width),
87        width @ 101..=150 => ScreenWidth::Small(width),
88        width @ 151..=200 => ScreenWidth::Medium(width),
89        width @ 201..=250 => ScreenWidth::Large(width),
90        width @ 251..=MAX => ScreenWidth::XLarge(width),
91    };
92
93    let columns_count = match screen_width {
94        ScreenWidth::XSmall(_width) => 1,
95        ScreenWidth::Small(_width) => 2,
96        ScreenWidth::Medium(_width) => 3,
97        ScreenWidth::Large(_width) => 4,
98        ScreenWidth::XLarge(_width) => 5,
99    };
100
101    let horizontal_metrics_chunks_count_per_vertical_chunk = columns_count;
102
103    let minimum_number_of_rows_required =
104        (metric_sections_uis.len() as f64 / columns_count as f64).ceil() as usize;
105
106    let vertical_metrics_chunks_count = match screen_width {
107        ScreenWidth::XSmall(_width) => metric_sections_uis.len(),
108        ScreenWidth::Small(_width) => minimum_number_of_rows_required,
109        ScreenWidth::Medium(_width) => minimum_number_of_rows_required,
110        ScreenWidth::Large(_width) => minimum_number_of_rows_required,
111        ScreenWidth::XLarge(_width) => minimum_number_of_rows_required,
112    };
113
114    let width_per_box = 100.0 / horizontal_metrics_chunks_count_per_vertical_chunk as f64;
115
116    let metric_box_width_percent = match screen_width {
117        ScreenWidth::XSmall(_width) => 100.0,
118        ScreenWidth::Small(_width) => width_per_box,
119        ScreenWidth::Medium(_width) => width_per_box,
120        ScreenWidth::Large(_width) => width_per_box,
121        ScreenWidth::XLarge(_width) => width_per_box,
122    };
123    let height_per_box = 100.0 / vertical_metrics_chunks_count as f64;
124
125    let metric_box_height_constraint: Constraint = match screen_width {
126        ScreenWidth::XSmall(_width) => Constraint::Percentage(height_per_box as u16),
127        ScreenWidth::Small(_width) => Constraint::Percentage(height_per_box as u16),
128        ScreenWidth::Medium(_width) => Constraint::Percentage(height_per_box as u16),
129        ScreenWidth::Large(_width) => Constraint::Percentage(height_per_box as u16),
130        ScreenWidth::XLarge(_width) => Constraint::Percentage(height_per_box as u16),
131    };
132
133    let vertical_constraints = vec![metric_box_height_constraint; vertical_metrics_chunks_count];
134    let vertical_metrics_chunks = Layout::default()
135        .direction(Direction::Vertical)
136        .constraints(vertical_constraints.as_ref())
137        .margin(1)
138        .split(metrics_chunk);
139
140    // RENDER ==================================================================
141    rect.render_widget(title_component, title_chunk);
142    rect.render_widget(metrics_block, metrics_chunk);
143    // rener each metric box
144    vertical_metrics_chunks.iter().enumerate().for_each(
145        |(vertical_metrics_chunk_index, vertical_metric_chunk)| {
146            let horizontal_constraints =
147                vec![
148                    Constraint::Percentage(metric_box_width_percent as u16);
149                    horizontal_metrics_chunks_count_per_vertical_chunk
150                ];
151            let horizontal_metrics_chunks = Layout::default()
152                .direction(Direction::Horizontal)
153                .constraints(horizontal_constraints.as_ref())
154                .split(*vertical_metric_chunk);
155            horizontal_metrics_chunks.iter().enumerate().for_each(
156                |(i, _horizontal_metric_chunk)| {
157                    let index_of_metric_blocks = (vertical_metrics_chunk_index
158                        * horizontal_metrics_chunks_count_per_vertical_chunk)
159                        + i;
160                    let has_iterated_over_all_metric_blocks: bool =
161                        index_of_metric_blocks < metric_sections_uis.len();
162                    if has_iterated_over_all_metric_blocks {
163                        let (metric_block_label, metric_block_value) =
164                            metric_sections_uis[index_of_metric_blocks].clone();
165                        rect.render_widget(
166                            metric_block_label,
167                            horizontal_metrics_chunks[i].clone(),
168                        );
169                        rect.render_widget(metric_block_value, horizontal_metrics_chunks[i].clone())
170                    }
171                },
172            )
173        },
174    );
175}