gui: added node metrics, refactor views
This commit is contained in:
parent
59990f9ee2
commit
f4ec1b9ab0
12 changed files with 453 additions and 257 deletions
|
@ -31,3 +31,9 @@ sync_status:
|
|||
body_sync: Downloading blocks
|
||||
body_sync_percent: 'Downloading blocks: %{percent}%'
|
||||
shutdown: Shutting down
|
||||
emission: Emission
|
||||
inflation: Inflation
|
||||
supply: Supply
|
||||
block_time: Block time
|
||||
reward: Reward
|
||||
difficulty_at_blocks: 'Difficulty on %{size} blocks'
|
|
@ -31,3 +31,9 @@ sync_status:
|
|||
body_sync: Загрузка блоков
|
||||
body_sync_percent: 'Загрузка блоков: %{percent}%'
|
||||
shutdown: Выключение
|
||||
emission: Эмиссия
|
||||
inflation: Инфляция
|
||||
supply: Предложение
|
||||
block_time: Время блока
|
||||
reward: Награда
|
||||
difficulty_at_blocks: 'Сложность на %{size} блоках'
|
|
@ -16,3 +16,4 @@ pub const COLOR_YELLOW: egui::Color32 = egui::Color32::from_rgb(254, 241, 2);
|
|||
pub const COLOR_LIGHT: egui::Color32 = egui::Color32::from_gray(240);
|
||||
pub const COLOR_DARK: egui::Color32 = egui::Color32::from_gray(60);
|
||||
pub const COLOR_GRAY: egui::Color32 = egui::Color32::from_gray(120);
|
||||
// pub const COLOR_LIGHT_GRAY: egui::Color32 = egui::Color32::from_gray(230);
|
|
@ -85,7 +85,18 @@ impl PlatformApp<Android> {
|
|||
}
|
||||
|
||||
fn setup_visuals(ctx: &egui::Context) {
|
||||
// Setup style
|
||||
let mut style = (*ctx.style()).clone();
|
||||
|
||||
// Make scroll-bar thinner
|
||||
style.spacing.scroll_bar_width = 4.0;
|
||||
//
|
||||
style.spacing.item_spacing = egui::vec2(0.0, 0.0);
|
||||
ctx.set_style(style);
|
||||
|
||||
// Setup visuals
|
||||
let mut visuals = egui::Visuals::light();
|
||||
|
||||
// Disable stroke around panels by default
|
||||
visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
|
||||
ctx.set_visuals(visuals);
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::gui::app::is_dual_panel_mode;
|
|||
use crate::gui::icons::{ARROW_CIRCLE_LEFT, GEAR_SIX, GLOBE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::screens::{Navigator, Screen, ScreenId};
|
||||
use crate::gui::views::{DEFAULT_STROKE, TitlePanel, TitlePanelAction};
|
||||
use crate::gui::views::{TitlePanel, TitlePanelAction, View};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Accounts {}
|
||||
|
@ -52,7 +52,7 @@ impl Screen for Accounts {
|
|||
panel.ui(ui);
|
||||
|
||||
egui::CentralPanel::default().frame(Frame {
|
||||
stroke: DEFAULT_STROKE,
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
.. Default::default()
|
||||
}).show_inside(ui, |ui| {
|
||||
ui.label(format!("{}Here we go 10000 ツ", ARROW_CIRCLE_LEFT));
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::epaint::{Color32, Stroke};
|
||||
use egui::{RichText, Sense, Widget};
|
||||
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_LIGHT};
|
||||
use crate::gui::views::DEFAULT_STROKE;
|
||||
|
||||
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
|
||||
).fill(Color32::TRANSPARENT)
|
||||
.ui(ui).interact(Sense::click_and_drag());
|
||||
|
||||
// Click optimization for touch screens
|
||||
if b.drag_released() || b.clicked() {
|
||||
(action)();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, mut action: impl FnMut()) {
|
||||
let stroke = match active {
|
||||
true => { Stroke::NONE }
|
||||
false => { DEFAULT_STROKE }
|
||||
};
|
||||
|
||||
let color = match active {
|
||||
true => { COLOR_LIGHT }
|
||||
false => { Color32::WHITE }
|
||||
};
|
||||
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
|
||||
).min_size(ui.available_size_before_wrap())
|
||||
.stroke(stroke)
|
||||
.fill(color)
|
||||
.ui(ui).interact(Sense::click_and_drag());
|
||||
|
||||
// Click optimization for touch screens
|
||||
if b.drag_released() || b.clicked() {
|
||||
(action)();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sub_title(ui: &mut egui::Ui, text: String, color: Color32) {
|
||||
ui.label(RichText::new(text).size(17.0).color(color));
|
||||
}
|
|
@ -14,22 +14,21 @@
|
|||
|
||||
use eframe::epaint::{Color32, Stroke};
|
||||
|
||||
pub use crate::gui::views::network::Network;
|
||||
pub use crate::gui::views::title_panel::{TitlePanel, TitlePanelAction, TitlePanelActions};
|
||||
|
||||
pub mod common;
|
||||
mod views;
|
||||
pub use self::views::View;
|
||||
|
||||
mod title_panel;
|
||||
pub use self::title_panel::{TitlePanel, TitlePanelAction};
|
||||
|
||||
mod network;
|
||||
mod network_node;
|
||||
mod network_tuning;
|
||||
mod network_metrics;
|
||||
pub use self::network::Network;
|
||||
|
||||
pub trait NetworkTab {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, node: &mut crate::node::Node);
|
||||
fn name(&self) -> &String;
|
||||
fn ui(&mut self, ui: &mut egui::Ui, node: &mut crate::node::Node);
|
||||
}
|
||||
|
||||
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Color32::from_gray(190) };
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ use crate::gui::colors::{COLOR_DARK, COLOR_YELLOW};
|
|||
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::screens::Navigator;
|
||||
use crate::gui::views::{DEFAULT_STROKE, NetworkTab};
|
||||
use crate::gui::views::common::{tab_button, title_button};
|
||||
use crate::gui::views::{NetworkTab, View};
|
||||
use crate::gui::views::network_metrics::NetworkMetrics;
|
||||
use crate::gui::views::network_node::NetworkNode;
|
||||
use crate::node::Node;
|
||||
|
||||
|
@ -45,7 +45,9 @@ pub struct Network {
|
|||
node: Node,
|
||||
|
||||
current_mode: Mode,
|
||||
|
||||
node_view: NetworkNode,
|
||||
metrics_view: NetworkMetrics,
|
||||
}
|
||||
|
||||
impl Default for Network {
|
||||
|
@ -54,7 +56,8 @@ impl Default for Network {
|
|||
Self {
|
||||
node,
|
||||
current_mode: Mode::Node,
|
||||
node_view: NetworkNode::default()
|
||||
node_view: NetworkNode::default(),
|
||||
metrics_view: NetworkMetrics::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +93,7 @@ impl Network {
|
|||
});
|
||||
|
||||
egui::CentralPanel::default().frame(egui::Frame {
|
||||
stroke: DEFAULT_STROKE,
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
inner_margin: Margin::same(4.0),
|
||||
fill: Color32::WHITE,
|
||||
.. Default::default()
|
||||
|
@ -102,24 +105,27 @@ impl Network {
|
|||
}
|
||||
|
||||
fn draw_tabs(&mut self, ui: &mut egui::Ui) {
|
||||
//Setup spacing between tabs
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0);
|
||||
|
||||
ui.columns(4, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
tab_button(ui, DATABASE, self.current_mode == Mode::Node, || {
|
||||
View::tab_button(ui, DATABASE, self.current_mode == Mode::Node, || {
|
||||
self.current_mode = Mode::Node;
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
tab_button(ui, GAUGE, self.current_mode == Mode::Metrics, || {
|
||||
View::tab_button(ui, GAUGE, self.current_mode == Mode::Metrics, || {
|
||||
self.current_mode = Mode::Metrics;
|
||||
});
|
||||
});
|
||||
columns[2].vertical_centered(|ui| {
|
||||
tab_button(ui, FACTORY, self.current_mode == Mode::Miner, || {
|
||||
View::tab_button(ui, FACTORY, self.current_mode == Mode::Miner, || {
|
||||
self.current_mode = Mode::Miner;
|
||||
});
|
||||
});
|
||||
columns[3].vertical_centered(|ui| {
|
||||
tab_button(ui, FADERS, self.current_mode == Mode::Tuning, || {
|
||||
View::tab_button(ui, FADERS, self.current_mode == Mode::Tuning, || {
|
||||
self.current_mode = Mode::Tuning;
|
||||
});
|
||||
});
|
||||
|
@ -132,8 +138,12 @@ impl Network {
|
|||
Mode::Node => {
|
||||
self.node_view.ui(ui, &mut self.node);
|
||||
}
|
||||
Mode::Metrics => {}
|
||||
Mode::Tuning => {}
|
||||
Mode::Metrics => {
|
||||
self.metrics_view.ui(ui, &mut self.node);
|
||||
}
|
||||
Mode::Tuning => {
|
||||
self.node_view.ui(ui, &mut self.node);
|
||||
}
|
||||
Mode::Miner => {}
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +163,7 @@ impl Network {
|
|||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
//TODO: Actions for node
|
||||
});
|
||||
});
|
||||
|
@ -164,7 +174,7 @@ impl Network {
|
|||
strip.cell(|ui| {
|
||||
if !is_dual_panel_mode(frame) {
|
||||
ui.centered_and_justified(|ui| {
|
||||
title_button(ui, CARDHOLDER, || {
|
||||
View::title_button(ui, CARDHOLDER, || {
|
||||
nav.toggle_left_panel();
|
||||
});
|
||||
});
|
||||
|
@ -181,7 +191,7 @@ impl Network {
|
|||
self.node_view.name()
|
||||
}
|
||||
Mode::Metrics => {
|
||||
self.node_view.name()
|
||||
self.metrics_view.name()
|
||||
}
|
||||
Mode::Miner => {
|
||||
self.node_view.name()
|
||||
|
|
|
@ -12,8 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::Ui;
|
||||
use crate::gui::views::NetworkTab;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use eframe::epaint::{Color32, Rounding, Stroke};
|
||||
use egui::{RichText, ScrollArea, Spinner, Widget};
|
||||
use grin_servers::DiffBlock;
|
||||
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_GRAY};
|
||||
use crate::gui::icons::{AT, CALENDAR_PLUS, COINS, CUBE, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
|
||||
use crate::gui::views::{NetworkTab, View};
|
||||
use crate::node::Node;
|
||||
|
||||
pub struct NetworkMetrics {
|
||||
|
@ -28,12 +34,185 @@ impl Default for NetworkMetrics {
|
|||
}
|
||||
}
|
||||
|
||||
const BLOCK_REWARD: f64 = 60.0;
|
||||
// 1 year is calculated as 365 days and 6 hours (31557600).
|
||||
const YEARLY_SUPPLY: f64 = ((60 * 60 * 24 * 365) + 6 * 60 * 60) as f64;
|
||||
|
||||
impl NetworkTab for NetworkMetrics {
|
||||
fn ui(&mut self, ui: &mut Ui, node: &mut Node) {
|
||||
|
||||
}
|
||||
|
||||
fn name(&self) -> &String {
|
||||
&self.title
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, node: &mut Node) {
|
||||
let server_stats = node.state.get_stats();
|
||||
if !server_stats.is_some() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
Spinner::new().size(42.0).color(COLOR_GRAY).ui(ui);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let stats = server_stats.as_ref().unwrap();
|
||||
|
||||
// Show emission info
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::sub_title(ui, format!("{} {}", COINS, t!("emission")), COLOR_DARK);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
let supply = stats.header_stats.height as f64 * BLOCK_REWARD;
|
||||
let rate = (YEARLY_SUPPLY * 100.0) / supply;
|
||||
|
||||
ui.columns(3, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::rounded_box(ui,
|
||||
format!("{}ツ", BLOCK_REWARD),
|
||||
t!("reward"),
|
||||
[true, false, true, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::rounded_box(ui,
|
||||
format!("{:.2}%", rate),
|
||||
t!("inflation"),
|
||||
[false, false, false, false]);
|
||||
});
|
||||
columns[2].vertical_centered(|ui| {
|
||||
View::rounded_box(ui,
|
||||
supply.to_string(),
|
||||
t!("supply"),
|
||||
[false, true, false, true]);
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Show difficulty window info
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
let title = t!("difficulty_at_blocks", "size" => stats.diff_stats.window_size);
|
||||
View::sub_title(ui, format!("{} {}", HOURGLASS_MEDIUM, title), COLOR_DARK);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
ui.columns(3, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::rounded_box(ui,
|
||||
stats.diff_stats.height.to_string(),
|
||||
t!("height"),
|
||||
[true, false, true, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::rounded_box(ui,
|
||||
format!("{}s", stats.diff_stats.average_block_time),
|
||||
t!("block_time"),
|
||||
[false, false, false, false]);
|
||||
});
|
||||
columns[2].vertical_centered(|ui| {
|
||||
View::rounded_box(ui,
|
||||
stats.diff_stats.average_difficulty.to_string(),
|
||||
t!("difficulty"),
|
||||
[false, true, false, true]);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw difficulty window blocks
|
||||
let blocks_size = stats.diff_stats.last_blocks.len();
|
||||
ScrollArea::vertical().auto_shrink([false; 2]).stick_to_bottom(true).show_rows(
|
||||
ui,
|
||||
DIFF_BLOCK_HEIGHT,
|
||||
blocks_size,
|
||||
|ui, row_range| {
|
||||
for (index, db) in stats.diff_stats.last_blocks.iter().enumerate() {
|
||||
let rounding = if blocks_size == 1 {
|
||||
[true, true]
|
||||
} else if index == 0 {
|
||||
[true, false]
|
||||
} else if index == blocks_size - 1 {
|
||||
[false, true]
|
||||
} else {
|
||||
[false, false]
|
||||
};
|
||||
draw_diff_block(ui, db, rounding)
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const DIFF_BLOCK_HEIGHT: f32 = 77.0;
|
||||
|
||||
fn draw_diff_block(ui: &mut egui::Ui, db: &DiffBlock, rounding: [bool; 2]) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(DIFF_BLOCK_HEIGHT);
|
||||
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
Rounding {
|
||||
nw: if rounding[0] { 8.0 } else { 0.0 },
|
||||
ne: if rounding[0] { 8.0 } else { 0.0 },
|
||||
sw: if rounding[1] { 8.0 } else { 0.0 },
|
||||
se: if rounding[1] { 8.0 } else { 0.0 },
|
||||
},
|
||||
Color32::WHITE,
|
||||
Stroke { width: 1.0, color: Color32::from_gray(230) }
|
||||
);
|
||||
|
||||
ui.add_space(2.0);
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.heading(RichText::new(HASH)
|
||||
.color(Color32::BLACK)
|
||||
.size(16.0));
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Draw block hash
|
||||
ui.heading(RichText::new(db.block_hash.to_string())
|
||||
.color(Color32::BLACK)
|
||||
.size(16.0));
|
||||
});
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.heading(RichText::new(CUBE_TRANSPARENT)
|
||||
.color(COLOR_DARK)
|
||||
.size(16.0));
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Draw block difficulty and height
|
||||
ui.heading(RichText::new(db.difficulty.to_string())
|
||||
.color(COLOR_DARK)
|
||||
.size(16.0));
|
||||
ui.add_space(2.0);
|
||||
ui.heading(RichText::new(AT).color(COLOR_DARK).size(16.0));
|
||||
ui.add_space(2.0);
|
||||
ui.heading(RichText::new(db.block_height.to_string())
|
||||
.color(COLOR_DARK)
|
||||
.size(16.0));
|
||||
});
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.heading(RichText::new(TIMER)
|
||||
.color(COLOR_GRAY)
|
||||
.size(16.0));
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Draw block time
|
||||
ui.heading(RichText::new(format!("{}s", db.duration))
|
||||
.color(COLOR_GRAY)
|
||||
.size(16.0));
|
||||
ui.add_space(2.0);
|
||||
ui.heading(RichText::new(HOURGLASS_LOW).color(COLOR_GRAY).size(16.0));
|
||||
ui.add_space(2.0);
|
||||
|
||||
let naive_datetime = NaiveDateTime::from_timestamp_opt(db.time as i64, 0);
|
||||
if naive_datetime.is_some() {
|
||||
let datetime: DateTime<Utc> = DateTime::from_utc(naive_datetime.unwrap(), Utc);
|
||||
ui.heading(RichText::new(datetime.to_string())
|
||||
.color(COLOR_GRAY)
|
||||
.size(16.0));
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
})
|
||||
});
|
||||
}
|
|
@ -12,22 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, Local, Offset, TimeZone, Utc};
|
||||
use chrono::format::DelayedFormat;
|
||||
use eframe::emath::Vec2;
|
||||
use eframe::epaint::{FontId, Stroke};
|
||||
use eframe::epaint::text::{LayoutJob, TextFormat, TextWrapping};
|
||||
use eframe::epaint::Stroke;
|
||||
use egui::{Color32, RichText, Rounding, ScrollArea, Spinner, Widget};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use grin_servers::common::stats::TxStats;
|
||||
use grin_servers::PeerStats;
|
||||
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_GRAY, COLOR_LIGHT, COLOR_YELLOW};
|
||||
use crate::gui::icons::{AT, CUBE, DEVICES, DOWNLOAD_SIMPLE, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
|
||||
use crate::gui::views::{DEFAULT_STROKE, NetworkTab};
|
||||
use crate::gui::views::common::sub_title;
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_GRAY};
|
||||
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
|
||||
use crate::gui::views::{NetworkTab, View};
|
||||
use crate::node::Node;
|
||||
|
||||
pub struct NetworkNode {
|
||||
|
@ -43,6 +35,10 @@ impl Default for NetworkNode {
|
|||
}
|
||||
|
||||
impl NetworkTab for NetworkNode {
|
||||
fn name(&self) -> &String {
|
||||
&self.title
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, node: &mut Node) {
|
||||
let server_stats = node.state.get_stats();
|
||||
if !server_stats.is_some() {
|
||||
|
@ -54,97 +50,86 @@ impl NetworkTab for NetworkNode {
|
|||
|
||||
let stats = server_stats.as_ref().unwrap();
|
||||
|
||||
// Make scroll bar thinner
|
||||
ui.style_mut().spacing.scroll_bar_width = 4.0;
|
||||
|
||||
ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
// Disable item spacing
|
||||
ui.style_mut().spacing.item_spacing = Vec2::new(0.0, 0.0);
|
||||
|
||||
// Show header stats
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
sub_title(ui, format!("{} {}", FLOW_ARROW, t!("header")), COLOR_DARK);
|
||||
View::sub_title(ui, format!("{} {}", FLOW_ARROW, t!("header")), COLOR_DARK);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.header_stats.last_block_h.to_string(),
|
||||
t!("hash"),
|
||||
StatBoxRounding::TopLeft);
|
||||
[true, false, false, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.header_stats.height.to_string(),
|
||||
t!("height"),
|
||||
StatBoxRounding::TopRight);
|
||||
[false, true, false, false]);
|
||||
});
|
||||
});
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.header_stats.total_difficulty.to_string(),
|
||||
t!("difficulty"),
|
||||
StatBoxRounding::BottomLeft);
|
||||
[false, false, true, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let ts = stats.header_stats.latest_timestamp;
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
format!("{}", ts.format("%d/%m/%Y %H:%M")),
|
||||
t!("time_utc"),
|
||||
StatBoxRounding::BottomRight);
|
||||
[false, false, false, true]);
|
||||
});
|
||||
});
|
||||
|
||||
// Show block stats
|
||||
ui.add_space(5.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
sub_title(ui, format!("{} {}", CUBE, t!("block")), COLOR_DARK);
|
||||
View::sub_title(ui, format!("{} {}", CUBE, t!("block")), COLOR_DARK);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.chain_stats.last_block_h.to_string(),
|
||||
t!("hash"),
|
||||
StatBoxRounding::TopLeft);
|
||||
[true, false, false, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.chain_stats.height.to_string(),
|
||||
t!("height"),
|
||||
StatBoxRounding::TopRight);
|
||||
[false, true, false, false]);
|
||||
});
|
||||
});
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.chain_stats.total_difficulty.to_string(),
|
||||
t!("difficulty"),
|
||||
StatBoxRounding::BottomLeft);
|
||||
[false, false, true, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let ts = stats.chain_stats.latest_timestamp;
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
format!("{}", ts.format("%d/%m/%Y %H:%M")),
|
||||
t!("time_utc"),
|
||||
StatBoxRounding::BottomRight);
|
||||
[false, false, false, true]);
|
||||
});
|
||||
});
|
||||
|
||||
// Show data stats
|
||||
ui.add_space(5.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
sub_title(ui, format!("{} {}", SHARE_NETWORK, t!("data")), COLOR_DARK);
|
||||
View::sub_title(ui, format!("{} {}", SHARE_NETWORK, t!("data")), COLOR_DARK);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
let tx_stat = match &stats.tx_stats {
|
||||
|
@ -153,7 +138,10 @@ impl NetworkTab for NetworkNode {
|
|||
format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels)
|
||||
}
|
||||
};
|
||||
draw_stat_box(ui, tx_stat, t!("main_pool"), StatBoxRounding::TopLeft);
|
||||
View::rounded_box(ui,
|
||||
tx_stat,
|
||||
t!("main_pool"),
|
||||
[true, false, false, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let stem_tx_stat = match &stats.tx_stats {
|
||||
|
@ -162,23 +150,25 @@ impl NetworkTab for NetworkNode {
|
|||
format!("{} ({})", stx.stem_pool_size, stx.stem_pool_kernels)
|
||||
}
|
||||
};
|
||||
draw_stat_box(ui, stem_tx_stat, t!("stem_pool"), StatBoxRounding::TopRight);
|
||||
View::rounded_box(ui,
|
||||
stem_tx_stat,
|
||||
t!("stem_pool"),
|
||||
[false, true, false, false]);
|
||||
});
|
||||
});
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.disk_usage_gb.to_string(),
|
||||
t!("size"),
|
||||
StatBoxRounding::BottomLeft);
|
||||
[false, false, true, false]);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let ts = stats.chain_stats.latest_timestamp;
|
||||
draw_stat_box(ui,
|
||||
View::rounded_box(ui,
|
||||
stats.peer_count.to_string(),
|
||||
t!("peers"),
|
||||
StatBoxRounding::BottomRight);
|
||||
[false, false, false, true]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -186,15 +176,14 @@ impl NetworkTab for NetworkNode {
|
|||
if stats.peer_count > 0 {
|
||||
ui.add_space(5.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
sub_title(ui, format!("{} {}", HANDSHAKE, t!("peers")), COLOR_DARK);
|
||||
View::sub_title(ui,format!("{} {}", HANDSHAKE, t!("peers")), COLOR_DARK);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
for (index, ps) in stats.peer_stats.iter().enumerate() {
|
||||
let rounding = if index == 0 {
|
||||
if stats.peer_count == 1 {
|
||||
[true, true];
|
||||
}
|
||||
let rounding = if stats.peer_count == 1 {
|
||||
[true, true]
|
||||
} else if index == 0 {
|
||||
[true, false]
|
||||
} else if index == &stats.peer_stats.len() - 1 {
|
||||
[false, true]
|
||||
|
@ -208,10 +197,6 @@ impl NetworkTab for NetworkNode {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn name(&self) -> &String {
|
||||
&self.title
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
||||
|
@ -231,14 +216,11 @@ fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
|||
);
|
||||
|
||||
ui.add_space(2.0);
|
||||
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.heading(RichText::new(PLUGS_CONNECTED)
|
||||
.color(Color32::BLACK)
|
||||
.size(18.0));
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw peer address
|
||||
|
@ -246,14 +228,11 @@ fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
|||
.color(Color32::BLACK)
|
||||
.size(18.0));
|
||||
});
|
||||
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.heading(RichText::new(PACKAGE)
|
||||
.color(COLOR_DARK)
|
||||
.size(16.0));
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw peer difficulty and height
|
||||
|
@ -270,11 +249,9 @@ fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
|||
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.heading(RichText::new(DEVICES)
|
||||
.color(COLOR_GRAY)
|
||||
.size(16.0));
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw peer user-agent
|
||||
|
@ -282,51 +259,5 @@ fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
|
|||
.color(COLOR_GRAY)
|
||||
.size(16.0));
|
||||
});
|
||||
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum StatBoxRounding {
|
||||
TopLeft, TopRight, BottomRight, BottomLeft
|
||||
}
|
||||
|
||||
fn draw_stat_box(ui: &mut egui::Ui, value: String, label: String, rounding: StatBoxRounding) {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(46.0);
|
||||
|
||||
// Draw box background
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
Rounding {
|
||||
nw: if rounding == StatBoxRounding::TopLeft { 8.0 } else { 0.0 },
|
||||
ne: if rounding == StatBoxRounding::TopRight { 8.0 } else { 0.0 },
|
||||
sw: if rounding == StatBoxRounding::BottomLeft { 8.0 } else { 0.0 },
|
||||
se: if rounding == StatBoxRounding::BottomRight { 8.0 } else { 0.0 },
|
||||
},
|
||||
Color32::WHITE,
|
||||
Stroke { width: 1.0, color: Color32::from_gray(230) },
|
||||
);
|
||||
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
// Correct vertical spacing between items
|
||||
ui.style_mut().spacing.item_spacing.y = -4.0;
|
||||
|
||||
// Draw box value
|
||||
let mut job = LayoutJob::single_section(value, TextFormat {
|
||||
font_id: FontId::proportional(18.0),
|
||||
color: Color32::BLACK,
|
||||
.. Default::default()
|
||||
});
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: false,
|
||||
overflow_character: Option::from('﹍'),
|
||||
..Default::default()
|
||||
};
|
||||
ui.label(job);
|
||||
|
||||
// Draw box label
|
||||
ui.label(RichText::new(label).color(COLOR_GRAY).size(15.0));
|
||||
});
|
||||
}
|
|
@ -16,10 +16,10 @@ use eframe::epaint::{FontId, Stroke};
|
|||
use eframe::epaint::text::{LayoutJob, TextFormat, TextWrapping};
|
||||
use egui::style::Margin;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_YELLOW};
|
||||
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_YELLOW};
|
||||
use crate::gui::screens::Navigator;
|
||||
use crate::gui::views::common::title_button;
|
||||
use crate::gui::views::View;
|
||||
|
||||
pub struct TitlePanelAction {
|
||||
pub(crate) icon: Box<str>,
|
||||
|
@ -88,33 +88,32 @@ impl<'nav> TitlePanel<'nav> {
|
|||
.size(Size::exact(52.0))
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
Self::show_action(ui, actions.left.as_ref(), nav);
|
||||
show_action(ui, actions.left.as_ref(), nav);
|
||||
});
|
||||
strip.strip(|builder| {
|
||||
builder
|
||||
.size(Size::remainder())
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
Self::show_title(&*title, ui);
|
||||
show_title(&*title, ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
Self::show_action(ui, actions.right.as_ref(), nav);
|
||||
show_action(ui, actions.right.as_ref(), nav);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn show_action(ui: &mut egui::Ui,
|
||||
action: Option<&TitlePanelAction>,
|
||||
navigator: &mut Navigator) {
|
||||
fn show_action(ui: &mut egui::Ui, action: Option<&TitlePanelAction>, navigator: &mut Navigator) {
|
||||
if action.is_some() {
|
||||
let action = action.unwrap();
|
||||
ui.centered_and_justified(|ui| {
|
||||
title_button(ui, &action.icon, || {
|
||||
View::title_button(ui, &action.icon, || {
|
||||
(action.on_click)(navigator);
|
||||
});
|
||||
});
|
||||
|
@ -141,5 +140,4 @@ impl<'nav> TitlePanel<'nav> {
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
114
src/gui/views/views.rs
Normal file
114
src/gui/views/views.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::epaint::{Color32, FontId, Rounding, Stroke};
|
||||
use eframe::epaint::text::{LayoutJob, TextFormat, TextWrapping};
|
||||
use egui::{RichText, Sense, Widget};
|
||||
|
||||
use crate::gui::colors::{COLOR_DARK, COLOR_GRAY, COLOR_LIGHT};
|
||||
|
||||
pub struct View;
|
||||
|
||||
impl View {
|
||||
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Color32::from_gray(190) };
|
||||
|
||||
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
|
||||
).fill(Color32::TRANSPARENT)
|
||||
.ui(ui).interact(Sense::click_and_drag());
|
||||
|
||||
// Click optimization for touch screens
|
||||
if b.drag_released() || b.clicked() {
|
||||
(action)();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, mut action: impl FnMut()) {
|
||||
let stroke = match active {
|
||||
true => { Stroke::NONE }
|
||||
false => { Self::DEFAULT_STROKE }
|
||||
};
|
||||
|
||||
let color = match active {
|
||||
true => { COLOR_LIGHT }
|
||||
false => { Color32::WHITE }
|
||||
};
|
||||
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
|
||||
).min_size(ui.available_size_before_wrap())
|
||||
.stroke(stroke)
|
||||
.fill(color)
|
||||
.ui(ui).interact(Sense::click_and_drag());
|
||||
|
||||
|
||||
let vel_y = ui.ctx().input().pointer.delta().y;
|
||||
let vel_x = ui.ctx().input().pointer.delta().x;
|
||||
println!("12345, vel {}, {}", vel_y, vel_x);
|
||||
|
||||
// Click optimization for touch screens
|
||||
if b.drag_released() || b.clicked() {
|
||||
(action)();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sub_title(ui: &mut egui::Ui, text: String, color: Color32) {
|
||||
ui.label(RichText::new(text).size(17.0).color(color));
|
||||
}
|
||||
|
||||
/// Draw rounded box with some value and label in the middle
|
||||
/// where is r = [top_left, top_right, bottom_left, bottom_right]
|
||||
/// | VALUE |
|
||||
/// | label |
|
||||
pub fn rounded_box(ui: &mut egui::Ui, value: String, label: String, r: [bool; 4]) {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(46.0);
|
||||
|
||||
// Draw box background
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
Rounding {
|
||||
nw: if r[0] { 8.0 } else { 0.0 },
|
||||
ne: if r[1] { 8.0 } else { 0.0 },
|
||||
sw: if r[2] { 8.0 } else { 0.0 },
|
||||
se: if r[3] { 8.0 } else { 0.0 },
|
||||
},
|
||||
Color32::WHITE,
|
||||
Stroke { width: 1.0, color: Color32::from_gray(230) },
|
||||
);
|
||||
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
// Correct vertical spacing between items
|
||||
ui.style_mut().spacing.item_spacing.y = -4.0;
|
||||
|
||||
// Draw box value
|
||||
let mut job = LayoutJob::single_section(value, TextFormat {
|
||||
font_id: FontId::proportional(18.0),
|
||||
color: Color32::BLACK,
|
||||
.. Default::default()
|
||||
});
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: false,
|
||||
overflow_character: Option::from('﹍'),
|
||||
..Default::default()
|
||||
};
|
||||
ui.label(job);
|
||||
|
||||
// Draw box label
|
||||
ui.label(RichText::new(label).color(COLOR_GRAY).size(15.0));
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue