bitcoin_terminal_dashboard/app/ui/
mod.rs1use 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 let size = rect.size();
44
45 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 let title_component = title_component();
56
57 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 rect.render_widget(title_component, title_chunk);
142 rect.render_widget(metrics_block, metrics_chunk);
143 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}