gui: added node metrics, refactor views

This commit is contained in:
ardocrat 2023-05-18 03:53:38 +03:00
parent 59990f9ee2
commit f4ec1b9ab0
12 changed files with 453 additions and 257 deletions

View file

@ -30,4 +30,10 @@ sync_status:
tx_hashset_save: Finalizing chain state
body_sync: Downloading blocks
body_sync_percent: 'Downloading blocks: %{percent}%'
shutdown: Shutting down
shutdown: Shutting down
emission: Emission
inflation: Inflation
supply: Supply
block_time: Block time
reward: Reward
difficulty_at_blocks: 'Difficulty on %{size} blocks'

View file

@ -30,4 +30,10 @@ sync_status:
tx_hashset_save: Сохранение состояния цепи
body_sync: Загрузка блоков
body_sync_percent: 'Загрузка блоков: %{percent}%'
shutdown: Выключение
shutdown: Выключение
emission: Эмиссия
inflation: Инфляция
supply: Предложение
block_time: Время блока
reward: Награда
difficulty_at_blocks: 'Сложность на %{size} блоках'

View file

@ -15,4 +15,5 @@
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_GRAY: egui::Color32 = egui::Color32::from_gray(120);
// pub const COLOR_LIGHT_GRAY: egui::Color32 = egui::Color32::from_gray(230);

View file

@ -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);

View file

@ -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));

View file

@ -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));
}

View file

@ -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) };

View file

@ -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()

View file

@ -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);
})
});
}

View file

@ -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,
stats.header_stats.last_block_h.to_string(),
t!("hash"),
StatBoxRounding::TopLeft);
View::rounded_box(ui,
stats.header_stats.last_block_h.to_string(),
t!("hash"),
[true, false, false, false]);
});
columns[1].vertical_centered(|ui| {
draw_stat_box(ui,
stats.header_stats.height.to_string(),
t!("height"),
StatBoxRounding::TopRight);
View::rounded_box(ui,
stats.header_stats.height.to_string(),
t!("height"),
[false, true, false, false]);
});
});
ui.columns(2, |columns| {
columns[0].vertical_centered(|ui| {
draw_stat_box(ui,
stats.header_stats.total_difficulty.to_string(),
t!("difficulty"),
StatBoxRounding::BottomLeft);
View::rounded_box(ui,
stats.header_stats.total_difficulty.to_string(),
t!("difficulty"),
[false, false, true, false]);
});
columns[1].vertical_centered(|ui| {
let ts = stats.header_stats.latest_timestamp;
draw_stat_box(ui,
format!("{}", ts.format("%d/%m/%Y %H:%M")),
t!("time_utc"),
StatBoxRounding::BottomRight);
View::rounded_box(ui,
format!("{}", ts.format("%d/%m/%Y %H:%M")),
t!("time_utc"),
[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,
stats.chain_stats.last_block_h.to_string(),
t!("hash"),
StatBoxRounding::TopLeft);
View::rounded_box(ui,
stats.chain_stats.last_block_h.to_string(),
t!("hash"),
[true, false, false, false]);
});
columns[1].vertical_centered(|ui| {
draw_stat_box(ui,
stats.chain_stats.height.to_string(),
t!("height"),
StatBoxRounding::TopRight);
View::rounded_box(ui,
stats.chain_stats.height.to_string(),
t!("height"),
[false, true, false, false]);
});
});
ui.columns(2, |columns| {
columns[0].vertical_centered(|ui| {
draw_stat_box(ui,
stats.chain_stats.total_difficulty.to_string(),
t!("difficulty"),
StatBoxRounding::BottomLeft);
View::rounded_box(ui,
stats.chain_stats.total_difficulty.to_string(),
t!("difficulty"),
[false, false, true, false]);
});
columns[1].vertical_centered(|ui| {
let ts = stats.chain_stats.latest_timestamp;
draw_stat_box(ui,
format!("{}", ts.format("%d/%m/%Y %H:%M")),
t!("time_utc"),
StatBoxRounding::BottomRight);
View::rounded_box(ui,
format!("{}", ts.format("%d/%m/%Y %H:%M")),
t!("time_utc"),
[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,
stats.disk_usage_gb.to_string(),
t!("size"),
StatBoxRounding::BottomLeft);
View::rounded_box(ui,
stats.disk_usage_gb.to_string(),
t!("size"),
[false, false, true, false]);
});
columns[1].vertical_centered(|ui| {
let ts = stats.chain_stats.latest_timestamp;
draw_stat_box(ui,
stats.peer_count.to_string(),
t!("peers"),
StatBoxRounding::BottomRight);
View::rounded_box(ui,
stats.peer_count.to_string(),
t!("peers"),
[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));
});
}

View file

@ -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,58 +88,56 @@ 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) {
if action.is_some() {
let action = action.unwrap();
ui.centered_and_justified(|ui| {
title_button(ui, &action.icon, || {
(action.on_click)(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| {
View::title_button(ui, &action.icon, || {
(action.on_click)(navigator);
});
}
}
fn show_title(title: &Option<String>, ui: &mut egui::Ui) {
if title.is_some() {
ui.centered_and_justified(|ui| {
let title_text = title.as_ref().unwrap().to_uppercase();
let mut job = LayoutJob::single_section(title_text, TextFormat {
font_id: FontId::proportional(20.0),
color: COLOR_DARK,
.. Default::default()
});
job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
});
}
});
}
}
fn show_title(title: &Option<String>, ui: &mut egui::Ui) {
if title.is_some() {
ui.centered_and_justified(|ui| {
let title_text = title.as_ref().unwrap().to_uppercase();
let mut job = LayoutJob::single_section(title_text, TextFormat {
font_id: FontId::proportional(20.0),
color: COLOR_DARK,
.. Default::default()
});
job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
});
}
}

114
src/gui/views/views.rs Normal file
View 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));
});
}
}