ui: refactoring navigation and modal state, stratum content fixes

This commit is contained in:
ardocrat 2023-07-13 03:54:27 +03:00
parent 5542698467
commit 1b8c126e0d
26 changed files with 429 additions and 583 deletions

View file

@ -1,4 +1,6 @@
screen_accounts:
copy: Copy
paste: Paste
accounts:
title: Accounts
network:
self: Network
@ -59,7 +61,7 @@ network_mining:
enable_server: Enable server
disable_server: Disable server
info: 'Mining server is enabled, you can change its settings by selecting %{settings} at the bottom of the screen. Data is updating when devices are connected.'
info_settings: To change the settings of enabled server, you will need to restart the node.
restart_server_required: Server restart is required to apply changes.
rewards_wallet: Wallet for rewards
server: Stratum server
address: Address
@ -85,8 +87,6 @@ network_settings:
foreign_api_secret: Foreign API token
disabled: Disabled
enabled: Enabled
copy: Copy
paste: Paste
ftl: The Future Time Limit (FTL)
ftl_description: Limit on how far into the future, relative to a node's local time in seconds, the timestamp on a new block can be, in order for the block to be accepted.
not_valid_value: Entered value is not valid

View file

@ -1,4 +1,6 @@
screen_accounts:
copy: Копировать
paste: Вставить
accounts:
title: Аккаунты
network:
self: Сеть
@ -59,7 +61,7 @@ network_mining:
enable_server: Включить сервер
disable_server: Выключить сервер
info: 'Сервер майнинга запущен, вы можете изменить его настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.'
info_settings: Для изменения настроек запущенного сервера потребуется перезапуск узла.
restart_server_required: Для применения изменений потребуется перезапуск Stratum сервера.
rewards_wallet: Кошелёк для наград
server: Stratum сервер
address: Адрес
@ -85,8 +87,6 @@ network_settings:
foreign_api_secret: Foreign API токен
disabled: Отключен
enabled: Включен
copy: Копировать
paste: Вставить
ftl: Предел Будущего Времени (FTL)
ftl_description: Насколько далеко в будущем, относительно локального времени узла в секундах, может находиться временная метка на новом блоке для его принятия.
not_valid_value: Введено недопустимое значение

View file

@ -14,11 +14,10 @@
use egui::{Context, RichText, Stroke};
use egui::os::OperatingSystem;
use crate::gui::Colors;
use crate::gui::{Colors, Navigator};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::Root;
use crate::gui::views::{ModalContainer, View};
use crate::gui::views::{Modal, ModalContainer, Root, View};
use crate::node::Node;
/// To be implemented at platform-specific module.
@ -27,16 +26,12 @@ pub struct PlatformApp<Platform> {
pub(crate) platform: Platform,
}
/// Contains main ui content, handles application exit and visual style setup.
/// Contains main ui, handles exit and visual style setup.
pub struct App {
/// Main ui container.
/// Main ui content.
root: Root,
/// Check if app exit is allowed on close event callback.
/// Check if app exit is allowed on close event of [`eframe::App`] platform implementation.
pub(crate) exit_allowed: bool,
/// Called from callback of [`eframe::App`] platform implementation on close event.
pub(crate) exit_requested: bool,
/// Flag to show exit progress at modal.
show_exit_progress: bool,
/// List of allowed modal ids for this [`ModalContainer`].
@ -47,14 +42,13 @@ impl Default for App {
fn default() -> Self {
let os = OperatingSystem::from_target_os();
// Exit from eframe only for non-mobile platforms.
let allow_to_exit = os == OperatingSystem::Android || os == OperatingSystem::IOS;
let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
Self {
root: Root::default(),
exit_allowed: allow_to_exit,
exit_requested: false,
exit_allowed,
show_exit_progress: false,
allowed_modal_ids: vec![
Navigator::EXIT_MODAL
Self::EXIT_MODAL
]
}
}
@ -67,25 +61,22 @@ impl ModalContainer for App {
}
impl App {
/// Identifier for exit confirmation [`Modal`].
pub const EXIT_MODAL: &'static str = "exit_confirmation";
/// Draw content on main screen panel.
pub fn ui(&mut self, ctx: &Context, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
// Show exit modal if window closing was requested.
if self.exit_requested {
Navigator::show_exit_modal();
self.exit_requested = false;
}
// Draw main content.
egui::CentralPanel::default()
.frame(egui::Frame {
fill: Colors::FILL,
..Default::default()
})
.show(ctx, |ui| {
// Draw exit modal content if it's open or exit requested.
let modal_id = Navigator::is_modal_open();
if modal_id.is_some() && self.can_show_modal(modal_id.unwrap()) {
// Draw modal content if it's open.
if self.can_draw_modal() {
self.exit_modal_content(ui, frame, cb);
}
// Draw main content.
self.root.ui(ui, frame, cb);
});
}
@ -95,7 +86,7 @@ impl App {
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
Navigator::modal_ui(ui, |ui, modal| {
Modal::ui(ui, |ui, modal| {
if self.show_exit_progress {
if !Node::is_running() {
self.exit(frame, cb);
@ -156,13 +147,12 @@ impl App {
cb.exit();
}
OperatingSystem::IOS => {
//TODO: exit on iOS
//TODO: exit on iOS.
}
OperatingSystem::Nix | OperatingSystem::Mac | OperatingSystem::Windows => {
self.exit_allowed = true;
frame.close();
}
// Web
OperatingSystem::Unknown => {}
}
}
@ -249,31 +239,10 @@ impl App {
ctx.set_style(style);
}
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Calling when back button is pressed on Android.
pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
Navigator::back();
/// Show exit confirmation modal.
pub fn show_exit_modal() {
let exit_modal = Modal::new(Self::EXIT_MODAL).title(t!("modal_exit.exit"));
Modal::show(exit_modal);
}
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Calling on unexpected Android application termination (removal from recent apps).
pub extern "C" fn Java_mw_gri_android_MainActivity_onTermination(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
Node::stop(false);
}

View file

@ -16,13 +16,9 @@
mod app;
pub use app::{App, PlatformApp};
mod navigator;
pub use navigator::Navigator;
mod colors;
pub use colors::Colors;
pub mod platform;
pub mod screens;
pub mod views;
pub mod icons;
pub mod icons;

View file

@ -1,156 +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 std::collections::BTreeSet;
use std::sync::{RwLock, RwLockWriteGuard};
use std::sync::atomic::{AtomicBool, Ordering};
use lazy_static::lazy_static;
use crate::gui::screens::ScreenId;
use crate::gui::views::Modal;
lazy_static! {
/// Static [`Navigator`] state to be accessible from anywhere.
static ref NAVIGATOR_STATE: RwLock<Navigator> = RwLock::new(Navigator::default());
}
/// Logic of common navigation at ui for screens and modals.
pub struct Navigator {
/// Screen identifiers in navigation stack.
screen_stack: BTreeSet<ScreenId>,
/// Indicator if side panel is open.
side_panel_open: AtomicBool,
/// Modal window to show.
modal: Option<Modal>,
}
impl Default for Navigator {
fn default() -> Self {
Self {
screen_stack: BTreeSet::new(),
side_panel_open: AtomicBool::new(false),
modal: None,
}
}
}
impl Navigator {
/// Identifier for exit [`Modal`].
pub const EXIT_MODAL: &'static str = "exit";
/// 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.
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`].
pub fn to(id: ScreenId) {
NAVIGATOR_STATE.write().unwrap().screen_stack.insert(id);
}
/// Go back at navigation stack, close showing modals first.
pub fn back() {
let mut w_nav = NAVIGATOR_STATE.write().unwrap();
// If Modal is showing and closeable, remove it from Navigator.
if w_nav.modal.is_some() {
let modal = w_nav.modal.as_ref().unwrap();
if modal.is_closeable() {
w_nav.modal = None;
}
return;
}
// Go back at screen stack or show exit confirmation Modal.
if w_nav.screen_stack.len() > 1 {
w_nav.screen_stack.pop_last();
} else {
Self::show_exit_modal_nav(w_nav);
}
}
/// Set exit confirmation [`Modal`].
pub fn show_exit_modal() {
let w_nav = NAVIGATOR_STATE.write().unwrap();
Self::show_exit_modal_nav(w_nav);
}
/// Set exit confirmation [`Modal`] with provided [NAVIGATOR_STATE] lock.
fn show_exit_modal_nav(mut w_nav: RwLockWriteGuard<Navigator>) {
let m = Modal::new(Self::EXIT_MODAL).title(t!("modal_exit.exit"));
w_nav.modal = Some(m);
}
/// Set [`Modal`] to show.
pub fn show_modal(modal: Modal) {
let mut w_nav = NAVIGATOR_STATE.write().unwrap();
w_nav.modal = Some(modal);
}
/// Check if [`Modal`] is open by returning its id, remove it from [`Navigator`] if it's closed.
pub fn is_modal_open() -> Option<&'static str> {
// Check if Modal is showing.
{
if NAVIGATOR_STATE.read().unwrap().modal.is_none() {
return None;
}
}
// Check if Modal is open.
let (is_open, id) = {
let r_nav = NAVIGATOR_STATE.read().unwrap();
let modal = r_nav.modal.as_ref().unwrap();
(modal.is_open(), modal.id)
};
// If Modal is not open, remove it from navigator state.
if !is_open {
let mut w_nav = NAVIGATOR_STATE.write().unwrap();
w_nav.modal = None;
return None;
}
Some(id)
}
/// Draw showing [`Modal`] content if it's opened.
pub fn modal_ui(ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
if let Some(modal) = &NAVIGATOR_STATE.read().unwrap().modal {
if modal.is_open() {
modal.ui(ui, add_content);
}
}
}
/// Change state of side panel to opposite.
pub fn toggle_side_panel() {
let r_nav = NAVIGATOR_STATE.read().unwrap();
let is_open = r_nav.side_panel_open.load(Ordering::Relaxed);
r_nav.side_panel_open.store(!is_open, Ordering::Relaxed);
}
/// Check if side panel is open.
pub fn is_side_panel_open() -> bool {
let r_nav = NAVIGATOR_STATE.read().unwrap();
r_nav.side_panel_open.load(Ordering::Relaxed)
}
}

View file

@ -47,7 +47,7 @@ impl eframe::App for PlatformApp<Desktop> {
}
fn on_close_event(&mut self) -> bool {
self.app.exit_requested = true;
App::show_exit_modal();
self.app.exit_allowed
}
}

View file

@ -1,37 +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.
pub use account::Account;
pub use accounts::Accounts;
pub use root::Root;
use crate::gui::platform::PlatformCallbacks;
mod root;
mod accounts;
mod account;
#[derive(Ord, Eq, PartialOrd, PartialEq)]
pub enum ScreenId {
Accounts,
Account,
}
pub trait Screen {
fn id(&self) -> ScreenId;
fn ui(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks);
}

View file

@ -1,84 +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 std::cmp::min;
use crate::gui::Navigator;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::{Account, Accounts, Screen, ScreenId};
use crate::gui::views::{NetworkContainer, View};
/// Main ui container.
pub struct Root {
screens: Vec<Box<dyn Screen>>,
network_panel: NetworkContainer,
}
impl Default for Root {
fn default() -> Self {
Navigator::init(ScreenId::Accounts);
Self {
screens: vec![
Box::new(Accounts::default()),
Box::new(Account::default())
],
network_panel: NetworkContainer::default()
}
}
}
impl Root {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
let (is_panel_open, panel_width) = Self::side_panel_state_width(frame);
egui::SidePanel::left("network_panel")
.resizable(false)
.exact_width(panel_width)
.frame(egui::Frame::default())
.show_animated_inside(ui, is_panel_open, |ui| {
self.network_panel.ui(ui, frame, cb);
});
egui::CentralPanel::default()
.frame(egui::Frame::default())
.show_inside(ui, |ui| {
self.show_current_screen(ui, frame, cb);
});
}
/// Show current screen at central panel based on [`Navigator`] state.
fn show_current_screen(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
let Self { screens, .. } = self;
for screen in screens.iter_mut() {
if Navigator::is_current(&screen.id()) {
screen.ui(ui, frame, cb);
break;
}
}
}
/// Get side panel state and width.
fn side_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
let dual_panel_mode = View::is_dual_panel_mode(frame);
let is_panel_open = dual_panel_mode || Navigator::is_side_panel_open();
let panel_width = if dual_panel_mode {
min(frame.info().window_info.size.x as i64, View::SIDE_PANEL_MIN_WIDTH) as f32
} else {
frame.info().window_info.size.x
};
(is_panel_open, panel_width)
}
}

View file

@ -12,30 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::ScreenId;
pub struct Account {
}
impl Default for Account {
fn default() -> Self {
Self {
}
}
}
impl super::Screen for Account {
fn id(&self) -> ScreenId {
ScreenId::Account
}
fn ui(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
}
}
mod content;
pub use content::*;

View file

@ -12,26 +12,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::Frame;
use crate::gui::icons::{ARROW_CIRCLE_LEFT, GLOBE, PLUS};
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{GLOBE, PLUS};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::{Screen, ScreenId};
use crate::gui::views::{TitlePanel, TitleAction, View};
use crate::gui::views::{Root, TitleAction, TitlePanel, View};
#[derive(Default)]
pub struct Accounts;
/// Accounts central panel content.
pub struct AccountsContent {
/// List of accounts.
list: Vec<String>
}
impl Screen for Accounts {
fn id(&self) -> ScreenId {
ScreenId::Accounts
impl Default for AccountsContent {
fn default() -> Self {
Self {
list: vec![],
}
}
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
TitlePanel::ui(t!("screen_accounts.title"), if !View::is_dual_panel_mode(frame) {
impl AccountsContent {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
TitlePanel::ui(t!("accounts.title"), if !Root::is_dual_panel_mode(frame) {
TitleAction::new(GLOBE, || {
Navigator::toggle_side_panel();
Root::toggle_network_panel();
})
} else {
None
@ -40,19 +44,13 @@ impl Screen for Accounts {
}), ui);
egui::CentralPanel::default()
.frame(Frame {
.frame(egui::Frame {
stroke: View::DEFAULT_STROKE,
fill: Colors::FILL_DARK,
..Default::default()
})
.show_inside(ui, |ui| {
ui.label(format!("{}Here we go 10000 ツ", ARROW_CIRCLE_LEFT));
if ui.button("TEST").clicked() {
Navigator::to(ScreenId::Account)
};
if ui.button(format!("{}BACK ", ARROW_CIRCLE_LEFT)).clicked() {
Navigator::back()
};
//TODO: accounts list
});
}
}

View file

@ -21,5 +21,11 @@ pub use title_panel::*;
mod modal;
pub use modal::*;
mod root;
pub use root::*;
mod network;
pub use network::*;
pub use network::*;
mod accounts;
pub use accounts::*;

View file

@ -14,21 +14,34 @@
use std::cmp::min;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{RwLock, RwLockWriteGuard};
use egui::{Align2, RichText, Rounding, Stroke, Vec2};
use egui::epaint::RectShape;
use lazy_static::lazy_static;
use crate::gui::Colors;
use crate::gui::views::View;
/// Contains modal ids to draw at current container if possible.
lazy_static! {
/// Showing [`Modal`] state to be accessible from different ui parts.
static ref MODAL_STATE: RwLock<ModalState> = RwLock::new(ModalState::default());
}
#[derive(Default)]
struct ModalState {
modal: Option<Modal>
}
/// Contains ids to draw current [`Modal`] at this ui container if it's possible.
pub trait ModalContainer {
/// List of modal ids to show at current view container.
/// List of [`Modal`] ids to draw at current ui container.
fn modal_ids(&self) -> &Vec<&'static str>;
/// Check if it's possible to show modal.
fn can_show_modal(&self, id: &'static str) -> bool {
self.modal_ids().contains(&id)
/// Check if it's possible to draw [`Modal`] at current ui container.
fn can_draw_modal(&self) -> bool {
let modal_id = Modal::opened();
modal_id.is_some() && self.modal_ids().contains(&modal_id.unwrap())
}
}
@ -38,7 +51,7 @@ pub enum ModalPosition {
Center
}
/// Stores data to draw dialog box/popup at UI, powered by [`egui::Window`].
/// Stores data to draw modal [`egui::Window`] at ui.
pub struct Modal {
/// Identifier for modal.
pub(crate) id: &'static str,
@ -67,34 +80,34 @@ impl Modal {
}
}
/// Setup position of Modal on the screen.
/// Setup position of [`Modal`] on the screen.
pub fn position(mut self, position: ModalPosition) -> Self {
self.position = position;
self
}
/// Check if Modal is open.
/// Check if [`Modal`] is open.
pub fn is_open(&self) -> bool {
self.open.load(Ordering::Relaxed)
}
/// Mark Modal closed.
/// Mark [`Modal`] closed.
pub fn close(&self) {
self.open.store(false, Ordering::Relaxed);
}
/// Setup possibility to close Modal.
/// Setup possibility to close [`Modal`].
pub fn closeable(self, closeable: bool) -> Self {
self.closeable.store(closeable, Ordering::Relaxed);
self
}
/// Disable possibility to close Modal.
/// Disable possibility to close [`Modal`].
pub fn disable_closing(&self) {
self.closeable.store(false, Ordering::Relaxed);
}
/// Check if Modal is closeable.
/// Check if [`Modal`] is closeable.
pub fn is_closeable(&self) -> bool {
self.closeable.load(Ordering::Relaxed)
}
@ -105,8 +118,64 @@ impl Modal {
self
}
/// Show Modal with provided content.
pub fn ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
/// Set [`Modal`] instance to show at ui.
pub fn show(modal: Modal) {
let mut w_nav = MODAL_STATE.write().unwrap();
w_nav.modal = Some(modal);
}
/// Remove [`Modal`] from [`MODAL_STATE`] if it's showing and can be closed.
/// Return `false` if Modal existed in [`MODAL_STATE`] before call.
pub fn on_back() -> bool {
let mut w_state = MODAL_STATE.write().unwrap();
// If Modal is showing and closeable, remove it from state.
if w_state.modal.is_some() {
let modal = w_state.modal.as_ref().unwrap();
if modal.is_closeable() {
w_state.modal = None;
}
return false;
}
true
}
/// Return id of opened [`Modal`] or remove its instance from [`MODAL_STATE`] if it was closed.
pub fn opened() -> Option<&'static str> {
// Check if Modal is showing.
{
if MODAL_STATE.read().unwrap().modal.is_none() {
return None;
}
}
// Check if Modal is open.
let (is_open, id) = {
let r_state = MODAL_STATE.read().unwrap();
let modal = r_state.modal.as_ref().unwrap();
(modal.is_open(), modal.id)
};
// If Modal is not open, remove it from navigator state.
if !is_open {
let mut w_state = MODAL_STATE.write().unwrap();
w_state.modal = None;
return None;
}
Some(id)
}
/// Draw opened [`Modal`] content.
pub fn ui(ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
if let Some(modal) = &MODAL_STATE.read().unwrap().modal {
if modal.is_open() {
modal.draw(ui, add_content);
}
}
}
/// Draw Modal with provided content.
fn draw(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
let mut rect = ui.ctx().screen_rect();
egui::Window::new("modal_bg_window")
.title_bar(false)

View file

@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod container;
mod content;
mod metrics;
mod mining;
mod settings;
mod node;
mod configs;
pub use container::*;
pub use content::*;

View file

@ -14,7 +14,7 @@
use egui::{Id, RichText, TextStyle, Ui, Widget};
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{CLOCK_COUNTDOWN, GRAPH, TIMER, WATCH};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
@ -116,7 +116,7 @@ impl DandelionSetup {
let epoch_modal = Modal::new(Self::EPOCH_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(epoch_modal);
Modal::show(epoch_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -201,7 +201,7 @@ impl DandelionSetup {
let embargo_modal = Modal::new(Self::EMBARGO_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(embargo_modal);
Modal::show(embargo_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -286,7 +286,7 @@ impl DandelionSetup {
let aggregation_modal = Modal::new(Self::AGGREGATION_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(aggregation_modal);
Modal::show(aggregation_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -371,7 +371,7 @@ impl DandelionSetup {
let embargo_modal = Modal::new(Self::STEM_PROBABILITY_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(embargo_modal);
Modal::show(embargo_modal);
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -14,13 +14,14 @@
use eframe::emath::Align;
use egui::{Id, Layout, RichText, TextStyle, Ui, Widget};
use egui::os::OperatingSystem;
use grin_core::global::ChainTypes;
use crate::AppConfig;
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{CLIPBOARD_TEXT, CLOCK_CLOCKWISE, COMPUTER_TOWER, COPY, PLUG, POWER, SHIELD, SHIELD_SLASH};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, NetworkContainer, View};
use crate::gui::views::{Modal, ModalPosition, NetworkContent, View};
use crate::gui::views::network::settings::NetworkSettings;
use crate::node::{Node, NodeConfig};
@ -114,7 +115,7 @@ impl NodeSetup {
// Autorun node setup.
ui.vertical_centered(|ui| {
ui.add_space(6.0);
NetworkContainer::autorun_node_ui(ui);
NetworkContent::autorun_node_ui(ui);
if Node::is_running() {
ui.add_space(2.0);
ui.label(RichText::new(t!("network_settings.restart_node_required"))
@ -224,7 +225,7 @@ impl NodeSetup {
let port_modal = Modal::new(Self::API_PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(port_modal);
Modal::show(port_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -338,7 +339,7 @@ impl NodeSetup {
let port_modal = Modal::new(modal_id)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(port_modal);
Modal::show(port_modal);
cb.show_keyboard();
});
}
@ -354,7 +355,7 @@ impl NodeSetup {
ui.label(RichText::new(description)
.size(18.0)
.color(Colors::GRAY));
ui.add_space(8.0);
ui.add_space(6.0);
// Draw API port text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.secret_edit)
@ -367,36 +368,34 @@ impl NodeSetup {
cb.show_keyboard();
}
ui.add_space(12.0);
// Show buttons to copy/paste text for Android.
if OperatingSystem::from_target_os() == OperatingSystem::Android {
ui.add_space(12.0);
ui.scope(|ui| {
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(12.0, 0.0);
// Show buttons to copy/paste text.
ui.scope(|ui| {
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(12.0, 0.0);
let mut buttons_rect = ui.available_rect_before_wrap();
buttons_rect.set_height(46.0);
ui.allocate_ui_at_rect(buttons_rect, |ui| {
let mut buttons_rect = ui.available_rect_before_wrap();
buttons_rect.set_height(46.0);
ui.allocate_ui_at_rect(buttons_rect, |ui| {
ui.columns(2, |columns| {
columns[0].with_layout(Layout::right_to_left(Align::Center), |ui| {
let copy_title = format!("{} {}", COPY, t!("network_settings.copy"));
View::button(ui, copy_title, Colors::WHITE, || {
cb.copy_string_to_buffer(self.secret_edit.clone());
});
});
columns[1].with_layout(Layout::left_to_right(Align::Center), |ui| {
let paste_title = format!("{} {}",
CLIPBOARD_TEXT,
t!("network_settings.paste"));
View::button(ui, paste_title, Colors::WHITE, || {
self.secret_edit = cb.get_string_from_buffer();
ui.columns(2, |columns| {
columns[0].with_layout(Layout::right_to_left(Align::Center), |ui| {
let copy_title = format!("{} {}", COPY, t!("copy"));
View::button(ui, copy_title, Colors::WHITE, || {
cb.copy_string_to_buffer(self.secret_edit.clone());
});
});
columns[1].with_layout(Layout::left_to_right(Align::Center), |ui| {
let paste_title = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
View::button(ui, paste_title, Colors::WHITE, || {
self.secret_edit = cb.get_string_from_buffer();
});
});
});
});
});
});
});
}
// Show reminder to restart enabled node.
NetworkSettings::node_restart_required_ui(ui);
@ -455,7 +454,7 @@ impl NodeSetup {
let ftl_modal = Modal::new(Self::FTL_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(ftl_modal);
Modal::show(ftl_modal);
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -17,7 +17,7 @@ use egui_extras::{Size, StripBuilder};
use grin_core::global::ChainTypes;
use crate::AppConfig;
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{HANDSHAKE, PLUG, TRASH, GLOBE_SIMPLE, PLUS_CIRCLE, ARROW_FAT_LINES_UP, ARROW_FAT_LINES_DOWN, ARROW_FAT_LINE_UP, PROHIBIT_INSET};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
@ -215,7 +215,7 @@ impl P2PSetup {
let port_modal = Modal::new(Self::PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(port_modal);
Modal::show(port_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -374,7 +374,7 @@ impl P2PSetup {
let peer_modal = Modal::new(modal_id)
.position(ModalPosition::CenterTop)
.title(modal_title);
Navigator::show_modal(peer_modal);
Modal::show(peer_modal);
cb.show_keyboard();
});
}
@ -561,7 +561,7 @@ impl P2PSetup {
let ban_modal = Modal::new(Self::BAN_WINDOW_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(ban_modal);
Modal::show(ban_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -652,7 +652,7 @@ impl P2PSetup {
let max_inbound_modal = Modal::new(Self::MAX_INBOUND_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(max_inbound_modal);
Modal::show(max_inbound_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -738,7 +738,7 @@ impl P2PSetup {
let max_outbound = Modal::new(Self::MAX_OUTBOUND_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(max_outbound);
Modal::show(max_outbound);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -824,7 +824,7 @@ impl P2PSetup {
let min_outbound = Modal::new(Self::MIN_OUTBOUND_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(min_outbound);
Modal::show(min_outbound);
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -14,7 +14,7 @@
use egui::{Id, RichText, TextStyle, Ui, Widget};
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{BEZIER_CURVE, BOUNDING_BOX, CHART_SCATTER, CIRCLES_THREE, CLOCK_COUNTDOWN, HAND_COINS};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
@ -118,7 +118,7 @@ impl PoolSetup {
let fee_modal = Modal::new(Self::FEE_BASE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(fee_modal);
Modal::show(fee_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -203,7 +203,7 @@ impl PoolSetup {
let reorg_modal = Modal::new(Self::REORG_PERIOD_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(reorg_modal);
Modal::show(reorg_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -288,7 +288,7 @@ impl PoolSetup {
let size_modal = Modal::new(Self::POOL_SIZE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(size_modal);
Modal::show(size_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -373,7 +373,7 @@ impl PoolSetup {
let stem_modal = Modal::new(Self::STEMPOOL_SIZE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(stem_modal);
Modal::show(stem_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -458,7 +458,7 @@ impl PoolSetup {
let weight_modal = Modal::new(Self::MAX_WEIGHT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(weight_modal);
Modal::show(weight_modal);
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -14,7 +14,7 @@
use egui::{Id, RichText, TextStyle, Ui, Widget};
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{BARBELL, HARD_DRIVES, PLUG, TIMER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
@ -98,13 +98,15 @@ impl StratumSetup {
View::checkbox(ui, stratum_enabled, t!("network.autorun"), || {
NodeConfig::toggle_stratum_autorun();
});
ui.add_space(4.0);
// Show message to restart node after changing of stratum settings
ui.label(RichText::new(t!("network_mining.info_settings"))
.size(16.0)
.color(Colors::INACTIVE_TEXT)
);
// Show reminder to restart running server.
if Node::get_stratum_stats().is_running {
ui.add_space(2.0);
ui.label(RichText::new(t!("network_mining.restart_server_required"))
.size(16.0)
.color(Colors::INACTIVE_TEXT)
);
}
ui.add_space(8.0);
});
@ -164,7 +166,7 @@ impl StratumSetup {
let port_modal = Modal::new(Self::STRATUM_PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(port_modal);
Modal::show(port_modal);
cb.show_keyboard();
});
ui.add_space(12.0);
@ -206,6 +208,8 @@ impl StratumSetup {
ui.label(RichText::new(t!("network_settings.port_unavailable"))
.size(18.0)
.color(Colors::RED));
} else {
server_restart_required_ui(ui);
}
ui.add_space(12.0);
@ -269,7 +273,7 @@ impl StratumSetup {
let time_modal = Modal::new(Self::ATTEMPT_TIME_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(time_modal);
Modal::show(time_modal);
cb.show_keyboard();
});
ui.add_space(12.0);
@ -303,7 +307,7 @@ impl StratumSetup {
.size(18.0)
.color(Colors::RED));
} else {
NetworkSettings::node_restart_required_ui(ui);
server_restart_required_ui(ui);
}
ui.add_space(12.0);
});
@ -355,7 +359,7 @@ impl StratumSetup {
let diff_modal = Modal::new(Self::MIN_SHARE_DIFF_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Navigator::show_modal(diff_modal);
Modal::show(diff_modal);
cb.show_keyboard();
});
ui.add_space(6.0);
@ -389,7 +393,7 @@ impl StratumSetup {
.size(18.0)
.color(Colors::RED));
} else {
NetworkSettings::node_restart_required_ui(ui);
server_restart_required_ui(ui);
}
ui.add_space(12.0);
});
@ -423,4 +427,15 @@ impl StratumSetup {
ui.add_space(6.0);
});
}
}
/// Reminder to restart enabled node to show on edit setting at [`Modal`].
pub fn server_restart_required_ui(ui: &mut Ui) {
if Node::get_stratum_stats().is_running {
ui.add_space(12.0);
ui.label(RichText::new(t!("network_mining.restart_server_required"))
.size(16.0)
.color(Colors::GREEN)
);
}
}

View file

@ -18,10 +18,10 @@ use egui_extras::{Size, StripBuilder};
use grin_chain::SyncStatus;
use crate::AppConfig;
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalContainer, TitlePanel, View};
use crate::gui::views::{Modal, ModalContainer, Root, TitlePanel, View};
use crate::gui::views::network::configs::dandelion::DandelionSetup;
use crate::gui::views::network::configs::node::NodeSetup;
use crate::gui::views::network::configs::p2p::P2PSetup;
@ -39,6 +39,7 @@ pub trait NetworkTab {
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks);
}
#[derive(PartialEq)]
pub enum NetworkTabType {
Node,
@ -48,7 +49,7 @@ pub enum NetworkTabType {
}
impl NetworkTabType {
pub fn name(&self) -> String {
pub fn title(&self) -> String {
match *self {
NetworkTabType::Node => { t!("network.node") }
NetworkTabType::Metrics => { t!("network.metrics") }
@ -58,12 +59,15 @@ impl NetworkTabType {
}
}
pub struct NetworkContainer {
/// Network side panel content.
pub struct NetworkContent {
/// Current tab view to show at ui.
current_tab: Box<dyn NetworkTab>,
/// [`Modal`] ids allowed at this ui container.
modal_ids: Vec<&'static str>,
}
impl Default for NetworkContainer {
impl Default for NetworkContent {
fn default() -> Self {
Self {
current_tab: Box::new(NetworkNode::default()),
@ -106,18 +110,17 @@ impl Default for NetworkContainer {
}
}
impl ModalContainer for NetworkContainer {
impl ModalContainer for NetworkContent {
fn modal_ids(&self) -> &Vec<&'static str> {
self.modal_ids.as_ref()
}
}
impl NetworkContainer {
impl NetworkContent {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
// Show modal content if it's opened.
let modal_id = Navigator::is_modal_open();
if modal_id.is_some() && self.can_show_modal(modal_id.unwrap()) {
Navigator::modal_ui(ui, |ui, modal| {
if self.can_draw_modal() {
Modal::ui(ui, |ui, modal| {
self.current_tab.as_mut().on_modal_ui(ui, modal, cb);
});
}
@ -169,24 +172,26 @@ impl NetworkContainer {
// Setup vertical padding inside tab button.
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 8.0);
// Draw tab buttons.
let current_type = self.current_tab.get_type();
ui.columns(4, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::tab_button(ui, DATABASE, self.is_current_tab(NetworkTabType::Node), || {
View::tab_button(ui, DATABASE, current_type == NetworkTabType::Node, || {
self.current_tab = Box::new(NetworkNode::default());
});
});
columns[1].vertical_centered_justified(|ui| {
View::tab_button(ui, GAUGE, self.is_current_tab(NetworkTabType::Metrics), || {
View::tab_button(ui, GAUGE, current_type == NetworkTabType::Metrics, || {
self.current_tab = Box::new(NetworkMetrics::default());
});
});
columns[2].vertical_centered_justified(|ui| {
View::tab_button(ui, FACTORY, self.is_current_tab(NetworkTabType::Mining), || {
View::tab_button(ui, FACTORY, current_type == NetworkTabType::Mining, || {
self.current_tab = Box::new(NetworkMining::default());
});
});
columns[3].vertical_centered_justified(|ui| {
View::tab_button(ui, FADERS, self.is_current_tab(NetworkTabType::Settings), || {
View::tab_button(ui, FADERS, current_type == NetworkTabType::Settings, || {
self.current_tab = Box::new(NetworkSettings::default());
});
});
@ -194,11 +199,6 @@ impl NetworkContainer {
});
}
/// Check if current tab equals providing [`NetworkTabType`].
fn is_current_tab(&self, tab_type: NetworkTabType) -> bool {
self.current_tab.get_type() == tab_type
}
/// Draw title content.
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
StripBuilder::new(ui)
@ -217,10 +217,10 @@ impl NetworkContainer {
self.title_text_ui(builder);
});
strip.cell(|ui| {
if !View::is_dual_panel_mode(frame) {
if !Root::is_dual_panel_mode(frame) {
ui.centered_and_justified(|ui| {
View::title_button(ui, CARDHOLDER, || {
Navigator::toggle_side_panel();
Root::toggle_network_panel();
});
});
}
@ -237,7 +237,7 @@ impl NetworkContainer {
strip.cell(|ui| {
ui.add_space(4.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(self.current_tab.get_type().name().to_uppercase())
ui.label(RichText::new(self.current_tab.get_type().title().to_uppercase())
.size(19.0)
.color(Colors::TITLE));
});
@ -291,7 +291,7 @@ impl NetworkContainer {
});
}
/// Draw checkbox with setting to run node on app launch.
/// Draw checkbox to run integrated node on application launch.
pub fn autorun_node_ui(ui: &mut egui::Ui) {
let autostart = AppConfig::autostart_node();
View::checkbox(ui, autostart, t!("network.autorun"), || {

View file

@ -20,9 +20,10 @@ use crate::gui::Colors;
use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::network::{NetworkTab, NetworkTabType};
use crate::gui::views::{Modal, NetworkContainer, View};
use crate::gui::views::{Modal, NetworkContent, View};
use crate::node::Node;
/// Chain metrics tab content.
#[derive(Default)]
pub struct NetworkMetrics;
@ -39,7 +40,7 @@ impl NetworkTab for NetworkMetrics {
let server_stats = Node::get_stats();
// Show message to enable node when it's not running.
if !Node::is_running() {
NetworkContainer::disabled_node_ui(ui);
NetworkContent::disabled_node_ui(ui);
return;
}
@ -94,7 +95,7 @@ impl NetworkTab for NetworkMetrics {
});
ui.add_space(4.0);
// Show difficulty adjustment window info
// Show difficulty adjustment window info.
let difficulty_title = t!(
"network_metrics.difficulty_window",
"size" => stats.diff_stats.window_size
@ -122,7 +123,7 @@ impl NetworkTab for NetworkMetrics {
});
ui.add_space(4.0);
// Show difficulty adjustment window blocks
// Show difficulty adjustment window blocks.
let blocks_size = stats.diff_stats.last_blocks.len();
ScrollArea::vertical()
.id_source("difficulty_scroll")
@ -130,7 +131,7 @@ impl NetworkTab for NetworkMetrics {
.stick_to_bottom(true)
.show_rows(
ui,
DIFF_BLOCK_UI_HEIGHT,
BLOCK_ITEM_HEIGHT,
blocks_size,
|ui, row_range| {
for index in row_range {
@ -144,7 +145,7 @@ impl NetworkTab for NetworkMetrics {
} else {
[false, false]
};
draw_diff_block(ui, db, rounding)
block_item_ui(ui, db, rounding)
}
},
);
@ -153,9 +154,10 @@ impl NetworkTab for NetworkMetrics {
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {}
}
const DIFF_BLOCK_UI_HEIGHT: f32 = 78.30;
const BLOCK_ITEM_HEIGHT: f32 = 78.30;
fn draw_diff_block(ui: &mut egui::Ui, db: &DiffBlock, rounding: [bool; 2]) {
/// Draw block difficulty item.
fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: [bool; 2]) {
// Add space before the first item.
if rounding[0] {
ui.add_space(4.0);
@ -164,8 +166,9 @@ fn draw_diff_block(ui: &mut egui::Ui, db: &DiffBlock, rounding: [bool; 2]) {
ui.horizontal(|ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
// Draw round background.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(DIFF_BLOCK_UI_HEIGHT);
rect.set_height(BLOCK_ITEM_HEIGHT);
ui.painter().rect(
rect,
Rounding {

View file

@ -20,11 +20,12 @@ use grin_servers::WorkerStats;
use crate::gui::Colors;
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, NetworkContainer, View};
use crate::gui::views::{Modal, NetworkContent, View};
use crate::gui::views::network::{NetworkTab, NetworkTabType};
use crate::gui::views::network::configs::stratum::StratumSetup;
use crate::node::{Node, NodeConfig};
/// Mining tab content.
#[derive(Default)]
pub struct NetworkMining {
stratum_server_setup: StratumSetup
@ -40,7 +41,7 @@ impl NetworkTab for NetworkMining {
// Show message to enable node when it's not running.
if !Node::is_running() {
NetworkContainer::disabled_node_ui(ui);
NetworkContent::disabled_node_ui(ui);
return;
}
@ -169,7 +170,7 @@ impl NetworkTab for NetworkMining {
.id_source("stratum_workers_scroll")
.show_rows(
ui,
WORKER_UI_HEIGHT,
WORKER_ITEM_HEIGHT,
workers_size,
|ui, row_range| {
for index in row_range {
@ -183,7 +184,7 @@ impl NetworkTab for NetworkMining {
} else {
[false, false]
};
draw_workers_stats(ui, worker, rounding)
worker_item_ui(ui, worker, rounding)
}
},
);
@ -207,9 +208,10 @@ impl NetworkTab for NetworkMining {
}
}
const WORKER_UI_HEIGHT: f32 = 77.0;
const WORKER_ITEM_HEIGHT: f32 = 77.0;
fn draw_workers_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
/// Draw worker statistics item.
fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) {
// Add space before the first item.
if rounding[0] {
ui.add_space(4.0);
@ -217,8 +219,9 @@ fn draw_workers_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2])
ui.horizontal_wrapped(|ui| {
ui.vertical_centered_justified(|ui| {
// Draw round background.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(WORKER_UI_HEIGHT);
rect.set_height(WORKER_ITEM_HEIGHT);
ui.painter().rect(
rect,
Rounding {
@ -232,94 +235,68 @@ fn draw_workers_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2])
);
ui.add_space(2.0);
ui.horizontal_top(|ui| {
ui.horizontal(|ui| {
ui.add_space(5.0);
// Draw worker connection status.
let (status_text, status_icon, status_color) = match ws.is_connected {
true => (t!("network_mining.connected"), PLUGS_CONNECTED, Colors::BLACK),
false => (t!("network_mining.disconnected"), PLUGS, Colors::INACTIVE_TEXT)
};
ui.add_space(5.0);
ui.heading(RichText::new(status_icon)
let status_line_text = format!("{} {} {}", status_icon, ws.id, status_text);
ui.heading(RichText::new(status_line_text)
.color(status_color)
.size(18.0));
ui.add_space(2.0);
// Draw worker ID.
ui.heading(RichText::new(&ws.id)
.color(status_color)
.size(18.0));
ui.add_space(3.0);
// Draw worker status.
ui.heading(RichText::new(status_text)
.color(status_color)
.size(18.0));
});
ui.horizontal_top(|ui| {
ui.horizontal(|ui| {
ui.add_space(6.0);
ui.heading(RichText::new(BARBELL)
.color(Colors::TITLE)
.size(16.0));
ui.add_space(4.0);
// Draw difficulty.
ui.heading(RichText::new(ws.pow_difficulty.to_string())
let diff_text = format!("{} {}", BARBELL, ws.pow_difficulty);
ui.heading(RichText::new(diff_text)
.color(Colors::TITLE)
.size(16.0));
ui.add_space(6.0);
ui.heading(RichText::new(FOLDER_NOTCH_PLUS)
.color(Colors::GREEN)
.size(16.0));
ui.add_space(3.0);
// Draw accepted shares.
ui.heading(RichText::new(ws.num_accepted.to_string())
let accepted_text = format!("{} {}", FOLDER_NOTCH_PLUS, ws.num_accepted);
ui.heading(RichText::new(accepted_text)
.color(Colors::GREEN)
.size(16.0));
ui.add_space(6.0);
ui.heading(RichText::new(FOLDER_NOTCH_MINUS)
.color(Colors::RED)
.size(16.0));
ui.add_space(3.0);
// Draw rejected shares.
ui.heading(RichText::new(ws.num_rejected.to_string())
let rejected_text = format!("{} {}", FOLDER_NOTCH_MINUS, ws.num_rejected);
ui.heading(RichText::new(rejected_text)
.color(Colors::RED)
.size(16.0));
ui.add_space(6.0);
ui.heading(RichText::new(FOLDER_DASHED)
.color(Colors::GRAY)
.size(16.0));
ui.add_space(3.0);
// Draw stale shares.
ui.heading(RichText::new(ws.num_stale.to_string())
let stale_text = format!("{} {}", FOLDER_DASHED, ws.num_stale);
ui.heading(RichText::new(stale_text)
.color(Colors::GRAY)
.size(16.0));
ui.add_space(6.0);
ui.heading(RichText::new(CUBE)
.color(Colors::TITLE)
.size(16.0));
ui.add_space(3.0);
// Draw blocks found.
ui.heading(RichText::new(ws.num_blocks_found.to_string())
let blocks_found_text = format!("{} {}", CUBE, ws.num_blocks_found);
ui.heading(RichText::new(blocks_found_text)
.color(Colors::TITLE)
.size(16.0));
});
ui.horizontal_top(|ui| {
ui.horizontal(|ui| {
ui.add_space(6.0);
ui.heading(RichText::new(CLOCK_AFTERNOON)
.color(Colors::TITLE)
.size(16.0));
ui.add_space(4.0);
// Draw block time
let seen = ws.last_seen.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
let naive_datetime = NaiveDateTime::from_timestamp_opt(seen as i64, 0).unwrap();
let datetime: DateTime<Utc> = DateTime::from_utc(naive_datetime, Utc);
ui.heading(RichText::new(datetime.to_string())
let date_text = format!("{} {}", CLOCK_AFTERNOON, datetime);
ui.heading(RichText::new(date_text)
.color(Colors::GRAY)
.size(16.0));
});
});
});

View file

@ -20,9 +20,10 @@ use crate::gui::Colors;
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
use crate::gui::views::network::{NetworkContainer, NetworkTab, NetworkTabType};
use crate::gui::views::network::{NetworkContent, NetworkTab, NetworkTabType};
use crate::node::Node;
/// Integrated node tab content.
#[derive(Default)]
pub struct NetworkNode;
@ -35,7 +36,7 @@ impl NetworkTab for NetworkNode {
let server_stats = Node::get_stats();
// Show message to enable node when it's not running.
if !Node::is_running() {
NetworkContainer::disabled_node_ui(ui);
NetworkContent::disabled_node_ui(ui);
return;
}
@ -175,7 +176,7 @@ impl NetworkTab for NetworkNode {
[false, false]
};
ui.vertical_centered(|ui| {
draw_peer_stats(ui, ps, rounding);
peer_item_ui(ui, ps, rounding);
});
}
}
@ -185,7 +186,8 @@ impl NetworkTab for NetworkNode {
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {}
}
fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
/// Draw connected peer info item.
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
ui.vertical(|ui| {
// Draw round background.
let mut rect = ui.available_rect_before_wrap();

View file

@ -14,7 +14,7 @@
use egui::{RichText, ScrollArea};
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::ARROW_COUNTER_CLOCKWISE;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
@ -141,7 +141,7 @@ impl NetworkSettings {
let reset_modal = Modal::new(Self::RESET_SETTINGS_MODAL)
.position(ModalPosition::Center)
.title(t!("modal.confirmation"));
Navigator::show_modal(reset_modal);
Modal::show(reset_modal);
});
// Show reminder to restart enabled node.
@ -207,7 +207,7 @@ impl NetworkSettings {
let port_modal = Modal::new(Self::NODE_RESTART_REQUIRED_MODAL)
.position(ModalPosition::Center)
.title(t!("network.settings"));
Navigator::show_modal(port_modal);
Modal::show(port_modal);
}
}

110
src/gui/views/root.rs Normal file
View file

@ -0,0 +1,110 @@
// 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 std::cmp::min;
use std::sync::atomic::{AtomicBool, Ordering};
use lazy_static::lazy_static;
use crate::gui::App;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{AccountsContent, Modal, NetworkContent};
lazy_static! {
/// To check if side panel is open from any part of ui.
static ref NETWORK_PANEL_OPEN: AtomicBool = AtomicBool::new(false);
}
/// Main ui content, handles network panel state modal state.
#[derive(Default)]
pub struct Root {
network: NetworkContent,
accounts: AccountsContent,
}
impl Root {
/// Default width of side panel at application UI.
pub const SIDE_PANEL_MIN_WIDTH: i64 = 400;
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
let (is_panel_open, panel_width) = Self::side_panel_state_width(frame);
egui::SidePanel::left("network_panel")
.resizable(false)
.exact_width(panel_width)
.frame(egui::Frame::default())
.show_animated_inside(ui, is_panel_open, |ui| {
self.network.ui(ui, frame, cb);
});
egui::CentralPanel::default()
.frame(egui::Frame::default())
.show_inside(ui, |ui| {
self.accounts.ui(ui, frame, cb);
});
}
/// Get side panel state and width.
fn side_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
let dual_panel_mode = Self::is_dual_panel_mode(frame);
let is_panel_open = dual_panel_mode || Self::is_network_panel_open();
let panel_width = if dual_panel_mode {
min(frame.info().window_info.size.x as i64, Self::SIDE_PANEL_MIN_WIDTH) as f32
} else {
frame.info().window_info.size.x
};
(is_panel_open, panel_width)
}
/// Check if ui can show [`NetworkContent`] and [`AccountsContent`] at same time.
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
let w = frame.info().window_info.size.x;
let h = frame.info().window_info.size.y;
// Screen is wide if width is greater than height or just 20% smaller.
let is_wide_screen = w > h || w + (w * 0.2) >= h;
// Dual panel mode is available when window is wide and its width is at least 2 times
// greater than minimal width of the side panel.
is_wide_screen && w >= Self::SIDE_PANEL_MIN_WIDTH as f32 * 2.0
}
/// Toggle [`Network`] panel state.
pub fn toggle_network_panel() {
let is_open = NETWORK_PANEL_OPEN.load(Ordering::Relaxed);
NETWORK_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
}
/// Check if side panel is open.
pub fn is_network_panel_open() -> bool {
NETWORK_PANEL_OPEN.load(Ordering::Relaxed)
}
/// Handle back button press event.
fn on_back() {
if Modal::on_back() {
App::show_exit_modal()
}
}
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Handle back button press event from Android.
pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
Root::on_back();
}

View file

@ -26,22 +26,8 @@ impl View {
/// Default stroke around views.
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Colors::STROKE };
/// Default width of side panel at application UI.
pub const SIDE_PANEL_MIN_WIDTH: i64 = 400;
/// Check if ui can show side panel and screen at same time.
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
let w = frame.info().window_info.size.x;
let h = frame.info().window_info.size.y;
// Screen is wide if width is greater than height or just 20% smaller.
let is_wide_screen = w > h || w + (w * 0.2) >= h;
// Dual panel mode is available when window is wide and its width is at least 2 times
// greater than minimal width of the side panel.
is_wide_screen && w >= Self::SIDE_PANEL_MIN_WIDTH as f32 * 2.0
}
/// Show and cut long text with character.
pub fn ellipsize_text(ui: &mut egui::Ui, text: String, size: f32, color: Color32) {
/// Cut long text with character.
fn ellipsize(text: String, size: f32, color: Color32) -> LayoutJob {
let mut job = LayoutJob::single_section(text, TextFormat {
font_id: FontId::proportional(size), color, ..Default::default()
});
@ -51,7 +37,12 @@ impl View {
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
job
}
/// Show ellipsized text.
pub fn ellipsize_text(ui: &mut egui::Ui, text: String, size: f32, color: Color32) {
ui.label(Self::ellipsize(text, size, color));
}
/// Draw horizontally centered sub-title with space below.
@ -121,7 +112,7 @@ impl View {
/// Draw [`Button`] with specified background fill color.
pub fn button(ui: &mut egui::Ui, text: String, fill_color: Color32, action: impl FnOnce()) {
let button_text = RichText::new(text.to_uppercase()).size(18.0).color(Colors::TEXT_BUTTON);
let button_text = Self::ellipsize(text.to_uppercase(), 18.0, Colors::TEXT_BUTTON);
let br = Button::new(button_text)
.stroke(Self::DEFAULT_STROKE)
.fill(fill_color)

View file

@ -260,7 +260,7 @@ impl Node {
}
}
if stratum_start_requested {
if stratum_start_requested && NODE_STATE.stratum_stats.read().is_running {
NODE_STATE.start_stratum_needed.store(false, Ordering::Relaxed);
}
@ -643,4 +643,17 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_exitAppAfterNodeStop(
) -> jni::sys::jboolean {
let exit_needed = !Node::is_running() && NODE_STATE.exit_after_stop.load(Ordering::Relaxed);
return exit_needed as jni::sys::jboolean;
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
/// Handle unexpected application termination on Android (removal from recent apps).
pub extern "C" fn Java_mw_gri_android_MainActivity_onTermination(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
Node::stop(false);
}