From d34889a8016f238a6b9c3b938c3fffcc3a7b5cb1 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Sat, 17 Jun 2023 13:23:31 +0300 Subject: [PATCH] ui: add checkbox view, move spinners creation to views, autorun option when server is disabled, refactor network tabs to hold only current instance --- locales/en.yml | 3 +- locales/ru.yml | 3 +- src/gui/app.rs | 4 ++ src/gui/navigator.rs | 18 +++--- src/gui/screens/root.rs | 4 +- src/gui/views/mod.rs | 9 +-- src/gui/views/modal.rs | 2 +- src/gui/views/network.rs | 98 +++++++++++++------------------ src/gui/views/network_metrics.rs | 13 ++-- src/gui/views/network_mining.rs | 24 +++++++- src/gui/views/network_node.rs | 17 ++++-- src/gui/views/network_settings.rs | 19 ++++++ src/gui/views/views.rs | 37 +++++++++++- src/settings.rs | 22 ++++--- 14 files changed, 173 insertions(+), 100 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 555cdb0..cf384d8 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -8,6 +8,7 @@ network: enable: Enable disable: Disable restart: Restart + autorun: Autorun disabled_server: 'Enable server or choose another connection method by pressing %{dots} on top left corner of the screen.' sync_status: server_restarting: Server is restarting @@ -27,7 +28,7 @@ sync_status: tx_hashset_range_proofs_validation: 'Validating state (range proofs): %{percent}%' tx_hashset_kernels_validation: 'Validating state (kernels): %{percent}%' tx_hashset_save: Finalizing chain state - body_sync: Downloading blockchain + body_sync: Downloading blocks body_sync_percent: 'Downloading blocks: %{percent}%' shutdown: Server is shutting down network_node: diff --git a/locales/ru.yml b/locales/ru.yml index 4caa952..f2061a8 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -8,6 +8,7 @@ network: enable: Включить disable: Выключить restart: Перезапустить + autorun: Автозапуск disabled_server: 'Включите сервер или выберите другой способ подключения, нажав %{dots} в левом верхнем углу экрана.' sync_status: server_restarting: Сервер перезапускается @@ -27,7 +28,7 @@ sync_status: tx_hashset_range_proofs_validation: 'Проверка доказательств: %{percent}%' tx_hashset_kernels_validation: 'Проверка ядер: %{percent}%' tx_hashset_save: Сохранение состояния цепи - body_sync: Загрузка блокчейна + body_sync: Загрузка блоков body_sync_percent: 'Загрузка блоков: %{percent}%' shutdown: Выключение сервера network_node: diff --git a/src/gui/app.rs b/src/gui/app.rs index 0f2e265..208fb53 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -72,6 +72,10 @@ impl App { style.spacing.scroll_bar_width = 4.0; // Disable spacing between items. style.spacing.item_spacing = egui::vec2(0.0, 0.0); + // Setup radio button/checkbox size and spacing. + style.spacing.icon_width = 24.0; + style.spacing.icon_width_inner = 14.0; + style.spacing.icon_spacing = 10.0; ctx.set_style(style); diff --git a/src/gui/navigator.rs b/src/gui/navigator.rs index be39d75..c1baaec 100644 --- a/src/gui/navigator.rs +++ b/src/gui/navigator.rs @@ -22,7 +22,7 @@ use crate::gui::screens::ScreenId; use crate::gui::views::{Modal, ModalId, ModalLocation}; lazy_static! { - /// Static [Navigator] state to be accessible from anywhere. + /// Static [`Navigator`] state to be accessible from anywhere. static ref NAVIGATOR_STATE: RwLock = RwLock::new(Navigator::default()); } @@ -53,20 +53,20 @@ impl Default for Navigator { } impl Navigator { - /// Initialize navigation from provided [ScreenId]. + /// Initialize navigation from provided [`ScreenId`]. pub fn init(from: ScreenId) { let mut w_nav = NAVIGATOR_STATE.write().unwrap(); w_nav.screen_stack.clear(); w_nav.screen_stack.insert(from); } - /// Check if provided [ScreenId] is current. + /// Check if provided [`ScreenId`] is current. pub fn is_current(id: &ScreenId) -> bool { let r_nav = NAVIGATOR_STATE.read().unwrap(); r_nav.screen_stack.last().unwrap() == id } - /// Navigate to screen with provided [ScreenId]. + /// Navigate to screen with provided [`ScreenId`]. pub fn to(id: ScreenId) { NAVIGATOR_STATE.write().unwrap().screen_stack.insert(id); } @@ -110,19 +110,19 @@ impl Navigator { } } - /// Open exit confirmation [Modal]. + /// Open exit confirmation [`Modal`]. pub fn open_exit_modal() { let w_nav = NAVIGATOR_STATE.write().unwrap(); Self::open_exit_modal_nav(w_nav); } - /// Open exit confirmation [Modal] with provided [NAVIGATOR_STATE] lock. + /// Open exit confirmation [`Modal`] with provided [NAVIGATOR_STATE] lock. fn open_exit_modal_nav(mut w_nav: RwLockWriteGuard) { let m = Modal::new(ModalId::Exit, ModalLocation::Global).title(t!("modal_exit.exit")); w_nav.global_modal = Some(m); } - /// Open [Modal] at specified location. + /// Open [`Modal`] at specified location. pub fn open_modal(modal: Modal) { let mut w_nav = NAVIGATOR_STATE.write().unwrap(); match modal.location { @@ -138,7 +138,7 @@ impl Navigator { } } - /// Check if [Modal] is open at specified location and remove it from [Navigator] if closed. + /// Check if [`Modal`] is open at specified location and remove it from [`Navigator`] otherwise. pub fn is_modal_open(location: ModalLocation) -> bool { // Check if Modal is showing. { @@ -176,7 +176,7 @@ impl Navigator { true } - /// Show [Modal] with provided location at app UI. + /// Show [`Modal`] with provided location at app UI. pub fn modal_ui(ui: &mut egui::Ui, location: ModalLocation, add_content: impl FnOnce(&mut egui::Ui, &Modal)) { diff --git a/src/gui/screens/root.rs b/src/gui/screens/root.rs index 90f118b..833233a 100644 --- a/src/gui/screens/root.rs +++ b/src/gui/screens/root.rs @@ -14,8 +14,6 @@ use std::cmp::min; -use egui::{Spinner, Widget}; - use crate::gui::{App, Colors, Navigator}; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::{Account, Accounts, Screen, ScreenId}; @@ -80,7 +78,7 @@ impl Root { } ui.add_space(16.0); ui.vertical_centered(|ui| { - Spinner::new().size(48.0).color(Colors::GOLD).ui(ui); + View::small_loading_spinner(ui); ui.add_space(12.0); ui.label(t!("sync_status.shutdown")); }); diff --git a/src/gui/views/mod.rs b/src/gui/views/mod.rs index 4753255..c99769c 100644 --- a/src/gui/views/mod.rs +++ b/src/gui/views/mod.rs @@ -22,14 +22,9 @@ mod modal; pub use modal::*; mod network; -pub use network::Network; +pub use network::*; mod network_node; mod network_settings; mod network_metrics; -mod network_mining; - -pub trait NetworkTab { - fn name(&self) -> String; - fn ui(&mut self, ui: &mut egui::Ui); -} \ No newline at end of file +mod network_mining; \ No newline at end of file diff --git a/src/gui/views/modal.rs b/src/gui/views/modal.rs index b106ee0..5f4f411 100644 --- a/src/gui/views/modal.rs +++ b/src/gui/views/modal.rs @@ -133,7 +133,7 @@ impl Modal { ui.set_min_size(ui.available_size()); }); - // Show main content Window at give position + // Show main content Window at given position let layer_id = egui::Window::new(self.window_id(false)) .title_bar(false) .resizable(false) diff --git a/src/gui/views/network.rs b/src/gui/views/network.rs index 949998b..4bd7443 100644 --- a/src/gui/views/network.rs +++ b/src/gui/views/network.rs @@ -22,35 +22,36 @@ use grin_chain::SyncStatus; use crate::gui::{Colors, Navigator}; use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{NetworkTab, View}; use crate::gui::views::network_metrics::NetworkMetrics; use crate::gui::views::network_mining::NetworkMining; use crate::gui::views::network_node::NetworkNode; +use crate::gui::views::network_settings::NetworkSettings; +use crate::gui::views::View; use crate::node::Node; +use crate::Settings; + +pub trait NetworkTab { + fn get_type(&self) -> NetworkTabType; + fn name(&self) -> String; + fn ui(&mut self, ui: &mut egui::Ui); +} #[derive(PartialEq)] -enum Mode { +pub enum NetworkTabType { Node, Metrics, Mining, - Tuning + Settings } pub struct Network { - current_mode: Mode, - - node_view: NetworkNode, - metrics_view: NetworkMetrics, - mining_view: NetworkMining, + current_tab: Box, } impl Default for Network { fn default() -> Self { Self { - current_mode: Mode::Node, - node_view: NetworkNode::default(), - metrics_view: NetworkMetrics::default(), - mining_view: NetworkMining::default() + current_tab: Box::new(NetworkNode::default()), } } } @@ -87,7 +88,7 @@ impl Network { .. Default::default() }) .show_inside(ui, |ui| { - self.draw_tab_content(ui); + self.current_tab.ui(ui); }); } @@ -100,44 +101,33 @@ impl Network { ui.columns(4, |columns| { columns[0].vertical_centered_justified(|ui| { - View::tab_button(ui, DATABASE, self.current_mode == Mode::Node, || { - self.current_mode = Mode::Node; - }); + View::tab_button(ui, DATABASE, + self.current_tab.get_type() == NetworkTabType::Node, || { + self.current_tab = Box::new(NetworkNode::default()); + }); }); columns[1].vertical_centered_justified(|ui| { - View::tab_button(ui, GAUGE, self.current_mode == Mode::Metrics, || { - self.current_mode = Mode::Metrics; - }); + View::tab_button(ui, GAUGE, + self.current_tab.get_type() == NetworkTabType::Metrics, || { + self.current_tab = Box::new(NetworkMetrics::default()); + }); }); columns[2].vertical_centered_justified(|ui| { - View::tab_button(ui, FACTORY, self.current_mode == Mode::Mining, || { - self.current_mode = Mode::Mining; - }); + View::tab_button(ui, FACTORY, + self.current_tab.get_type() == NetworkTabType::Mining, || { + self.current_tab = Box::new(NetworkMining::default()); + }); }); columns[3].vertical_centered_justified(|ui| { - View::tab_button(ui, FADERS, self.current_mode == Mode::Tuning, || { - self.current_mode = Mode::Tuning; - }); + View::tab_button(ui, FADERS, + self.current_tab.get_type() == NetworkTabType::Settings, || { + self.current_tab = Box::new(NetworkSettings::default()); + }); }); }); }); } - fn draw_tab_content(&mut self, ui: &mut egui::Ui) { - match self.current_mode { - Mode::Node => { - self.node_view.ui(ui); - } - Mode::Metrics => { - self.metrics_view.ui(ui); - } - Mode::Tuning => { - self.node_view.ui(ui); - } - Mode::Mining => {} - } - } - fn draw_title(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { StripBuilder::new(ui) .size(Size::exact(52.0)) @@ -173,21 +163,6 @@ impl Network { } fn draw_title_text(&self, builder: StripBuilder) { - let title_text = match &self.current_mode { - Mode::Node => { - self.node_view.name() - } - Mode::Metrics => { - self.metrics_view.name() - } - Mode::Mining => { - self.mining_view.name() - } - Mode::Tuning => { - self.node_view.name() - } - }; - builder .size(Size::remainder()) .size(Size::exact(32.0)) @@ -195,7 +170,7 @@ impl Network { strip.cell(|ui| { ui.add_space(2.0); ui.vertical_centered(|ui| { - ui.label(RichText::new(title_text.to_uppercase()) + ui.label(RichText::new(self.current_tab.name().to_uppercase()) .size(18.0) .color(Colors::TITLE)); }); @@ -233,7 +208,7 @@ impl Network { } pub fn disabled_server_content(ui: &mut egui::Ui) { - View::center_content(ui, 142.0, |ui| { + View::center_content(ui, 162.0, |ui| { let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL); ui.label(RichText::new(text) .size(16.0) @@ -245,6 +220,15 @@ impl Network { View::button(ui, format!("{} {}", POWER, t!("network.enable")), Colors::GOLD, || { Node::start(); }); + + ui.add_space(4.0); + + let autostart: bool = Settings::get_app_config().auto_start_node; + View::checkbox(ui, autostart, t!("network.autorun"), || { + let mut w_app_config = Settings::get_app_config_to_update(); + w_app_config.auto_start_node = !autostart; + w_app_config.save(); + }); }); } } diff --git a/src/gui/views/network_metrics.rs b/src/gui/views/network_metrics.rs index adec76d..6809034 100644 --- a/src/gui/views/network_metrics.rs +++ b/src/gui/views/network_metrics.rs @@ -14,12 +14,12 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use eframe::epaint::{Color32, Rounding, Stroke}; -use egui::{RichText, ScrollArea, Spinner, Widget}; +use egui::{RichText, ScrollArea}; use grin_servers::DiffBlock; use crate::gui::Colors; use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER}; -use crate::gui::views::{Network, NetworkTab, View}; +use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; use crate::node::Node; #[derive(Default)] @@ -30,18 +30,23 @@ const BLOCK_REWARD: f64 = 60.0; const YEARLY_SUPPLY: f64 = ((60 * 60 * 24 * 365) + 6 * 60 * 60) as f64; impl NetworkTab for NetworkMetrics { + fn get_type(&self) -> NetworkTabType { + NetworkTabType::Metrics + } + fn name(&self) -> String { t!("network.metrics") } fn ui(&mut self, ui: &mut egui::Ui) { let server_stats = Node::get_stats(); + // Show loading spinner when stats are not available or message when server is not enabled. if server_stats.is_none() || server_stats.as_ref().unwrap().diff_stats.height == 0 { if !Node::is_running() { Network::disabled_server_content(ui); } else { View::center_content(ui, 160.0, |ui| { - Spinner::new().size(104.0).color(Colors::GOLD).ui(ui); + View::big_loading_spinner(ui); ui.add_space(18.0); ui.label(RichText::new(t!("network_metrics.loading")) .size(16.0) @@ -54,7 +59,7 @@ impl NetworkTab for NetworkMetrics { let stats = server_stats.as_ref().unwrap(); - // Show emission info + // Show emission info. ui.vertical_centered_justified(|ui| { View::sub_header(ui, format!("{} {}", COINS, t!("network_metrics.emission"))); }); diff --git a/src/gui/views/network_mining.rs b/src/gui/views/network_mining.rs index 045109f..a87a3ac 100644 --- a/src/gui/views/network_mining.rs +++ b/src/gui/views/network_mining.rs @@ -12,19 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::Ui; -use crate::gui::views::NetworkTab; +use crate::gui::Colors; +use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; +use crate::node::Node; #[derive(Default)] pub struct NetworkMining; impl NetworkTab for NetworkMining { + fn get_type(&self) -> NetworkTabType { + NetworkTabType::Mining + } + fn name(&self) -> String { t!("network.mining") } - fn ui(&mut self, ui: &mut Ui) { + fn ui(&mut self, ui: &mut egui::Ui) { + let server_stats = Node::get_stats(); + // Show loading spinner when stats are not available or message when server is not enabled. + if !server_stats.is_some() { + if !Node::is_running() { + Network::disabled_server_content(ui); + } else { + ui.centered_and_justified(|ui| { + View::big_loading_spinner(ui); + }); + } + return; + } + let stats = server_stats.as_ref().unwrap(); } } \ No newline at end of file diff --git a/src/gui/views/network_node.rs b/src/gui/views/network_node.rs index 32183ea..61a962a 100644 --- a/src/gui/views/network_node.rs +++ b/src/gui/views/network_node.rs @@ -13,30 +13,35 @@ // limitations under the License. use eframe::epaint::Stroke; -use egui::{Color32, RichText, Rounding, ScrollArea, Spinner, Widget}; +use egui::{Color32, RichText, Rounding, ScrollArea}; use grin_servers::PeerStats; use crate::gui::Colors; use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK}; -use crate::gui::views::{Network, NetworkTab, View}; +use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; use crate::node::Node; #[derive(Default)] pub struct NetworkNode; impl NetworkTab for NetworkNode { + fn get_type(&self) -> NetworkTabType { + NetworkTabType::Metrics + } + fn name(&self) -> String { t!("network.node") } fn ui(&mut self, ui: &mut egui::Ui) { let server_stats = Node::get_stats(); + // Show loading spinner when stats are not available or message when server is not enabled. if !server_stats.is_some() { if !Node::is_running() { Network::disabled_server_content(ui); } else { ui.centered_and_justified(|ui| { - Spinner::new().size(104.0).color(Colors::GOLD).ui(ui); + View::big_loading_spinner(ui); }); } return; @@ -83,7 +88,7 @@ impl NetworkTab for NetworkNode { }); // Show block stats - ui.add_space(6.0); + ui.add_space(4.0); ui.vertical_centered_justified(|ui| { View::sub_header(ui, format!("{} {}", CUBE, t!("network_node.block"))); }); @@ -119,7 +124,7 @@ impl NetworkTab for NetworkNode { }); // Show data stats - ui.add_space(6.0); + ui.add_space(4.0); ui.vertical_centered_justified(|ui| { View::sub_header(ui, format!("{} {}", SHARE_NETWORK, t!("network_node.data"))); }); @@ -167,7 +172,7 @@ impl NetworkTab for NetworkNode { // Show peers stats when available if stats.peer_count > 0 { - ui.add_space(6.0); + ui.add_space(4.0); ui.vertical_centered_justified(|ui| { View::sub_header(ui, format!("{} {}", HANDSHAKE, t!("network_node.peers"))); }); diff --git a/src/gui/views/network_settings.rs b/src/gui/views/network_settings.rs index 2035f55..1bb394c 100644 --- a/src/gui/views/network_settings.rs +++ b/src/gui/views/network_settings.rs @@ -11,3 +11,22 @@ // 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 crate::gui::views::{NetworkTab, NetworkTabType}; + +#[derive(Default)] +pub struct NetworkSettings; + +impl NetworkTab for NetworkSettings { + fn get_type(&self) -> NetworkTabType { + NetworkTabType::Settings + } + + fn name(&self) -> String { + t!("network.settings") + } + + fn ui(&mut self, ui: &mut egui::Ui) { + + } +} \ No newline at end of file diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index f4a0991..75e0a5c 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Button, PointerState, Response, RichText, Sense, Widget}; +use egui::{Button, PointerState, Response, RichText, Sense, Spinner, Widget}; use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke}; use egui::epaint::text::TextWrapping; use egui::text::{LayoutJob, TextFormat}; use crate::gui::Colors; +use crate::gui::icons::{CHECK_SQUARE, SQUARE}; pub struct View; @@ -184,4 +185,38 @@ impl View { }); }); } + + /// Draw big gold loading spinner. + pub fn big_loading_spinner(ui: &mut egui::Ui) { + Spinner::new().size(104.0).color(Colors::GOLD).ui(ui); + } + + /// Draw small gold loading spinner. + pub fn small_loading_spinner(ui: &mut egui::Ui) { + Spinner::new().size(48.0).color(Colors::GOLD).ui(ui); + } + + // let wt = RichText::new(text.to_uppercase()).size(18.0).color(Colors::BUTTON); + // let br = Button::new(wt) + // .stroke(Self::DEFAULT_STROKE) + // .fill(fill_color) + // .ui(ui).interact(Sense::click_and_drag()); + // + // Self::on_button_click(ui, br, action); + + /// Draw button that looks like checkbox with callback to change value. + pub fn checkbox(ui: &mut egui::Ui, value: bool, text: String, cb: impl FnOnce()) { + let text_value = match value { + true => { format!("{} {}", CHECK_SQUARE, text)} + false => { format!("{} {}", SQUARE, text)} + }; + let wt = RichText::new(text_value).size(18.0).color(Colors::BUTTON); + let br = Button::new(wt) + .frame(false) + .stroke(Stroke::NONE) + .fill(Colors::TRANSPARENT) + .ui(ui).interact(Sense::click_and_drag()); + + Self::on_button_click(ui, br, cb); + } } \ No newline at end of file diff --git a/src/settings.rs b/src/settings.rs index 840e72e..43a1e8b 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -15,7 +15,7 @@ use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; -use std::sync::{Arc, RwLock, RwLockReadGuard}; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use grin_config::ConfigError; use grin_core::global::ChainTypes; @@ -38,14 +38,14 @@ pub struct AppConfig { /// Run node server on startup. pub auto_start_node: bool, /// Chain type for node server. - pub chain_type: ChainTypes + pub node_chain_type: ChainTypes } impl Default for AppConfig { fn default() -> Self { Self { auto_start_node: false, - chain_type: ChainTypes::default(), + node_chain_type: ChainTypes::default(), } } } @@ -57,12 +57,16 @@ impl AppConfig { let parsed = Settings::read_from_file::(config_path.clone()); if !config_path.exists() || parsed.is_err() { let default_config = AppConfig::default(); - Settings::write_to_file(&default_config, config_path.to_str().unwrap()); + Settings::write_to_file(&default_config, config_path); default_config } else { parsed.unwrap() } } + + pub fn save(&self) { + Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None)); + } } pub struct Settings { @@ -74,7 +78,7 @@ impl Settings { /// Initialize settings with app and node configs from the disk. fn init() -> Self { let app_config = AppConfig::init(); - let chain_type = app_config.chain_type; + let chain_type = app_config.node_chain_type; Self { app_config: Arc::new(RwLock::new(app_config)), node_config: Arc::new(RwLock::new(NodeConfig::init(&chain_type))) @@ -89,6 +93,10 @@ impl Settings { SETTINGS_STATE.app_config.read().unwrap() } + pub fn get_app_config_to_update() -> RwLockWriteGuard<'static, AppConfig> { + SETTINGS_STATE.app_config.write().unwrap() + } + /// Get working directory path for application. pub fn get_working_path(chain_type: Option<&ChainTypes>) -> PathBuf { // Check if dir exists @@ -131,9 +139,9 @@ impl Settings { } /// Write config to a file - pub fn write_to_file(config: &T, name: &str) { + pub fn write_to_file(config: &T, path: PathBuf) { let conf_out = toml::to_string(config).unwrap(); - let mut file = File::create(name).unwrap(); + let mut file = File::create(path.to_str().unwrap()).unwrap(); file.write_all(conf_out.as_bytes()).unwrap(); } } \ No newline at end of file