ui: add checkbox view, move spinners creation to views, autorun option when server is disabled, refactor network tabs to hold only current instance

This commit is contained in:
ardocrat 2023-06-17 13:23:31 +03:00
parent b91bc2f7e7
commit d34889a801
14 changed files with 173 additions and 100 deletions

View file

@ -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:

View file

@ -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:

View file

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

View file

@ -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<Navigator> = 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<Navigator>) {
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)) {

View file

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

View file

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

View file

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

View file

@ -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<dyn NetworkTab>,
}
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();
});
});
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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::<AppConfig>(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<T: Serialize>(config: &T, name: &str) {
pub fn write_to_file<T: Serialize>(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();
}
}