ui: modal refactoring, update some translations, optimize port text edit size and network messages when node is stopping

This commit is contained in:
ardocrat 2023-06-26 18:51:19 +03:00
parent 4682560763
commit 7f1c6579b1
11 changed files with 171 additions and 225 deletions

View file

@ -10,7 +10,7 @@ network:
disable: Disable
restart: Restart
autorun: Autorun
disabled_server: 'Enable integrated node or choose another connection method by pressing %{dots} in the top-left corner of the screen.'
disabled_server: 'Enable integrated node or add another connection method by pressing %{dots} in the top-left corner of the screen.'
sync_status:
node_restarting: Node is restarting
node_down: Node is down
@ -58,7 +58,7 @@ network_mining:
server_setup: Stratum server setup
enable_server: Enable server
server_setting: 'Enable stratum server or change more settings by selecting %{settings} at the bottom of the screen. App restart is required to change settings of the running server.'
info: 'Mining server is enabled, you can change settings by selecting %{settings} at the bottom of the screen. Data is updating when devices are connected.'
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.'
no_ip_addresses: There are no available IP addresses on your system, stratum server cannot be started, check your network connectivity.
port_unavailable: Specified port is unavailable
rewards_wallet: Wallet for rewards

View file

@ -10,7 +10,7 @@ network:
disable: Выключить
restart: Перезапустить
autorun: Автозапуск
disabled_server: 'Включите встроенный узел или выберите другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.'
disabled_server: 'Включите встроенный узел или добавьте другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.'
sync_status:
node_restarting: Узел перезапускается
node_down: Узел выключен
@ -58,7 +58,7 @@ network_mining:
server_setup: Настройка stratum-сервера
enable_server: Включить сервер
server_setting: 'Включите stratum-сервер или измените больше настроек, выбрав %{settings} внизу экрана. Для изменения настроек запущенного сервера потребуется перезапуск приложения.'
info: 'Сервер майнинга запущен, вы можете изменить настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.'
info: 'Сервер майнинга запущен, вы можете изменить его настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.'
no_ip_addresses: В вашей системе отсутствуют доступные IP адреса, запуск stratum-сервера невозможен, проверьте ваше подключение к сети.
port_unavailable: Указанный порт недоступен
rewards_wallet: Кошелёк для наград

View file

@ -19,25 +19,21 @@ use std::sync::atomic::{AtomicBool, Ordering};
use lazy_static::lazy_static;
use crate::gui::screens::ScreenId;
use crate::gui::views::{Modal, ModalLocation};
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 navigation for UI, stores screen identifiers stack, open modals and side panel state.
/// Logic of navigation at ui, stores screen identifiers stack, showing modal and side panel state.
pub struct Navigator {
/// Screen identifiers in navigation stack.
screen_stack: BTreeSet<ScreenId>,
/// Indicator if side panel is open.
side_panel_open: AtomicBool,
/// Modal state to show globally above panel and screen.
global_modal: Option<Modal>,
/// Modal state to show on the side panel.
side_panel_modal: Option<Modal>,
/// Modal state to show on the screen.
screen_modal: Option<Modal>,
/// Modal window to show.
modal: Option<Modal>,
}
impl Default for Navigator {
@ -45,9 +41,7 @@ impl Default for Navigator {
Self {
screen_stack: BTreeSet::new(),
side_panel_open: AtomicBool::new(false),
global_modal: None,
side_panel_modal: None,
screen_modal: None,
modal: None,
}
}
}
@ -78,119 +72,72 @@ impl Navigator {
pub fn back() {
let mut w_nav = NAVIGATOR_STATE.write().unwrap();
// If global Modal is showing and closeable, remove it from Navigator.
if w_nav.global_modal.is_some() {
let global_modal = w_nav.global_modal.as_ref().unwrap();
if global_modal.is_closeable() {
w_nav.global_modal = None;
// 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;
}
// If side panel Modal is showing and closeable, remove it from Navigator.
if w_nav.side_panel_modal.is_some() {
let side_panel_modal = w_nav.side_panel_modal.as_ref().unwrap();
if side_panel_modal.is_closeable() {
w_nav.side_panel_modal = None;
}
return;
}
// If screen Modal is showing and closeable, remove it from Navigator.
if w_nav.screen_modal.is_some() {
let screen_modal = w_nav.screen_modal.as_ref().unwrap();
if screen_modal.is_closeable() {
w_nav.screen_modal = None;
}
return;
}
// Go back at screen stack or show exit confirmation Modal.
// Go back at screen stack or set exit confirmation Modal.
if w_nav.screen_stack.len() > 1 {
w_nav.screen_stack.pop_last();
} else {
Self::open_exit_modal_nav(w_nav);
Self::show_exit_modal_nav(w_nav);
}
}
/// Open exit confirmation [`Modal`].
pub fn open_exit_modal() {
/// Set exit confirmation [`Modal`].
pub fn show_exit_modal() {
let w_nav = NAVIGATOR_STATE.write().unwrap();
Self::open_exit_modal_nav(w_nav);
Self::show_exit_modal_nav(w_nav);
}
/// Open exit confirmation [`Modal`] with provided [NAVIGATOR_STATE] lock.
fn open_exit_modal_nav(mut w_nav: RwLockWriteGuard<Navigator>) {
let m = Modal::new(Self::EXIT_MODAL, ModalLocation::Global).title(t!("modal_exit.exit"));
w_nav.global_modal = Some(m);
/// 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);
}
/// Open [`Modal`] at specified location.
pub fn open_modal(modal: Modal) {
/// Set [`Modal`] to show.
pub fn show_modal(modal: Modal) {
let mut w_nav = NAVIGATOR_STATE.write().unwrap();
match modal.location {
ModalLocation::Global => {
w_nav.global_modal = Some(modal);
}
ModalLocation::SidePanel => {
w_nav.side_panel_modal = Some(modal);
}
ModalLocation::Screen => {
w_nav.screen_modal = Some(modal);
}
}
w_nav.modal = Some(modal);
}
/// 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 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.
{
let r_nav = NAVIGATOR_STATE.read().unwrap();
let showing = match location {
ModalLocation::Global => { r_nav.global_modal.is_some() }
ModalLocation::SidePanel => { r_nav.side_panel_modal.is_some() }
ModalLocation::Screen => { r_nav.screen_modal.is_some() }
};
if !showing {
return false;
if NAVIGATOR_STATE.read().unwrap().modal.is_none() {
return None;
}
}
// Check if Modal is open.
let is_open = {
let (is_open, id) = {
let r_nav = NAVIGATOR_STATE.read().unwrap();
match location {
ModalLocation::Global => { r_nav.global_modal.as_ref().unwrap().is_open() }
ModalLocation::SidePanel => { r_nav.side_panel_modal.as_ref().unwrap().is_open() }
ModalLocation::Screen => {r_nav.screen_modal.as_ref().unwrap().is_open() }
}
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();
match location {
ModalLocation::Global => { w_nav.global_modal = None }
ModalLocation::SidePanel => { w_nav.side_panel_modal = None }
ModalLocation::Screen => { w_nav.screen_modal = None }
w_nav.modal = None;
return None;
}
return false;
}
true
Some(id)
}
/// 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)) {
let r_nav = NAVIGATOR_STATE.read().unwrap();
let modal = match location {
ModalLocation::Global => { &r_nav.global_modal }
ModalLocation::SidePanel => { &r_nav.side_panel_modal }
ModalLocation::Screen => { &r_nav.screen_modal }
};
if modal.is_some() {
modal.as_ref().unwrap().ui(ui, add_content);
/// 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);
}
}
}

View file

@ -13,17 +13,17 @@
// limitations under the License.
use std::cmp::min;
use crate::gui::{App, Colors, Navigator};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::{Account, Accounts, Screen, ScreenId};
use crate::gui::views::{ModalLocation, Network, View};
use crate::gui::views::{ModalContainer, Network, View};
use crate::node::Node;
pub struct Root {
screens: Vec<Box<dyn Screen>>,
network: Network,
show_exit_progress: bool
show_exit_progress: bool,
allowed_modal_ids: Vec<&'static str>
}
impl Default for Root {
@ -31,23 +31,34 @@ impl Default for Root {
Navigator::init(ScreenId::Accounts);
Self {
screens: (vec![
screens: vec![
Box::new(Accounts::default()),
Box::new(Account::default())
]),
],
network: Network::default(),
show_exit_progress: false
show_exit_progress: false,
allowed_modal_ids: vec![
Navigator::EXIT_MODAL
]
}
}
}
impl ModalContainer for Root {
fn allowed_modal_ids(&self) -> &Vec<&'static str> {
self.allowed_modal_ids.as_ref()
}
}
impl Root {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
if Navigator::is_modal_open(ModalLocation::Global) {
self.show_global_modal(ui, frame, cb);
// Draw exit modal content if it's open.
let modal_id = Navigator::is_modal_open();
if modal_id.is_some() && self.can_show_modal(modal_id.unwrap()) {
self.exit_modal_content(ui, frame, cb);
}
let (is_panel_open, panel_width) = self.dual_panel_state_width(frame);
let (is_panel_open, panel_width) = Self::side_panel_state_width(frame);
egui::SidePanel::left("network_panel")
.resizable(false)
.exact_width(panel_width)
@ -63,13 +74,11 @@ impl Root {
});
}
fn show_global_modal(&mut self,
fn exit_modal_content(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
Navigator::modal_ui(ui, ModalLocation::Global, |ui, modal| {
match modal.id {
Navigator::EXIT_MODAL => {
Navigator::modal_ui(ui, |ui, modal| {
if self.show_exit_progress {
if !Node::is_running() {
App::exit(frame, cb);
@ -116,9 +125,6 @@ impl Root {
ui.add_space(6.0);
});
}
}
_ => {}
}
});
}
@ -135,8 +141,8 @@ impl Root {
}
}
/// Get dual panel state and width
fn dual_panel_state_width(&self, frame: &mut eframe::Frame) -> (bool, f32) {
/// 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 {

View file

@ -15,27 +15,25 @@
use std::cmp::min;
use std::sync::atomic::{AtomicBool, Ordering};
use egui::{Align2, RichText, Rounding, Sense, Stroke, Vec2};
use egui::{Align2, RichText, Rounding, Stroke, Vec2};
use egui::epaint::RectShape;
use crate::gui::Colors;
use crate::gui::views::View;
/// Location for [`Modal`] at application UI.
pub enum ModalLocation {
/// To draw globally above side panel and screen.
Global,
/// To draw on the side panel.
SidePanel,
/// To draw on the screen.
Screen
/// Contains modal ids to draw at current container if possible.
pub trait ModalContainer {
fn allowed_modal_ids(&self) -> &Vec<&'static str>;
/// Check if it's possible to show modal.
fn can_show_modal(&self, id: &'static str) -> bool {
self.allowed_modal_ids().contains(&id)
}
}
/// Position of [`Modal`] on the screen at provided [`ModalLocation`].
/// Position of [`Modal`] on the screen.
pub enum ModalPosition {
/// Center-top position.
CenterTop,
/// Center of the location.
Center
}
@ -43,8 +41,6 @@ pub enum ModalPosition {
pub struct Modal {
/// Identifier for modal.
pub(crate) id: &'static str,
/// Location at UI.
pub(crate) location: ModalLocation,
/// Position on the screen.
position: ModalPosition,
/// Flag to show the content.
@ -59,11 +55,10 @@ impl Modal {
/// Default width of the content.
const DEFAULT_WIDTH: i64 = 380;
/// Create open and closeable Modal with center position.
pub fn new(id: &'static str, location: ModalLocation) -> Self {
/// Create opened and closeable Modal with center position.
pub fn new(id: &'static str) -> Self {
Self {
id,
location,
position: ModalPosition::Center,
open: AtomicBool::new(true),
closeable: AtomicBool::new(true),
@ -112,12 +107,11 @@ impl Modal {
/// Show Modal with provided content.
pub fn ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
// Show background Window at full available size.
egui::Window::new(self.window_id(true))
egui::Window::new("modal_bg_window")
.title_bar(false)
.resizable(false)
.collapsible(false)
.fixed_pos(ui.next_widget_position())
.fixed_size(ui.available_size())
.fixed_size(ui.ctx().used_size())
.frame(egui::Frame {
fill: Colors::SEMI_TRANSPARENT,
..Default::default()
@ -131,7 +125,7 @@ impl Modal {
// Show main content Window at given position.
let (content_align, content_offset) = self.modal_position();
let layer_id = egui::Window::new(self.window_id(false))
let layer_id = egui::Window::new("modal_window")
.title_bar(false)
.resizable(false)
.collapsible(false)
@ -154,21 +148,6 @@ impl Modal {
}
/// Generate identifier for inner [`egui::Window`] parts based on [`ModalLocation`].
fn window_id(&self, background: bool) -> &'static str {
match self.location {
ModalLocation::Global => {
if background { "global.bg" } else { "global" }
}
ModalLocation::SidePanel => {
if background { "side_panel.bg" } else { "side_panel" }
}
ModalLocation::Screen => {
if background { "global.bg" } else { "global" }
}
}
}
/// Get [`egui::Window`] position based on [`ModalPosition`].
fn modal_position(&self) -> (Align2, Vec2) {
let align = match self.position {

View file

@ -16,7 +16,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener};
use std::str::FromStr;
use std::time::Duration;
use egui::{Color32, lerp, Rgba, RichText, Stroke};
use egui::{Color32, lerp, Rgba, RichText};
use egui::style::Margin;
use egui_extras::{Size, StripBuilder};
use grin_chain::SyncStatus;
@ -24,11 +24,12 @@ use grin_chain::SyncStatus;
use crate::gui::{Colors, Navigator};
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalLocation, View};
use crate::gui::views::{Modal, ModalContainer, 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::settings_stratum::StratumServerSetup;
use crate::node::Node;
use crate::Settings;
@ -59,21 +60,32 @@ impl NetworkTabType {
pub struct Network {
current_tab: Box<dyn NetworkTab>,
allowed_modal_ids: Vec<&'static str>,
}
impl Default for Network {
fn default() -> Self {
Self {
current_tab: Box::new(NetworkNode::default()),
allowed_modal_ids: vec![
StratumServerSetup::STRATUM_PORT_MODAL
]
}
}
}
impl ModalContainer for Network {
fn allowed_modal_ids(&self) -> &Vec<&'static str> {
self.allowed_modal_ids.as_ref()
}
}
impl Network {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
// Show modal if it's opened.
if Navigator::is_modal_open(ModalLocation::SidePanel) {
Navigator::modal_ui(ui, ModalLocation::SidePanel, |ui, modal| {
// 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| {
self.current_tab.as_mut().on_modal_ui(ui, modal, cb);
});
}

View file

@ -43,11 +43,13 @@ impl NetworkTab for NetworkMetrics {
} else {
View::center_content(ui, 162.0, |ui| {
View::big_loading_spinner(ui);
if !Node::is_stopping() {
ui.add_space(18.0);
ui.label(RichText::new(t!("network_metrics.loading"))
.size(16.0)
.color(Colors::INACTIVE_TEXT)
);
}
});
}
return;

View file

@ -13,11 +13,11 @@
// limitations under the License.
use chrono::{DateTime, NaiveDateTime, Utc};
use egui::{Response, RichText, Rounding, ScrollArea, Stroke, TextStyle, Widget};
use egui::{RichText, Rounding, ScrollArea, Stroke};
use grin_chain::SyncStatus;
use grin_servers::WorkerStats;
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, COMPUTER_TOWER, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, PLUGS, PLUGS_CONNECTED, POLYGON};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View};
@ -45,11 +45,13 @@ impl NetworkTab for NetworkMining {
} else {
View::center_content(ui, 162.0, |ui| {
View::big_loading_spinner(ui);
if !Node::is_stopping() {
ui.add_space(18.0);
ui.label(RichText::new(t!("network_mining.loading"))
.size(16.0)
.color(Colors::INACTIVE_TEXT)
);
}
});
}
return;

View file

@ -20,7 +20,7 @@ use egui::{RichText, TextStyle, Widget};
use crate::gui::{Colors, Navigator};
use crate::gui::icons::WRENCH;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalLocation, ModalPosition, Network, View};
use crate::gui::views::{Modal, ModalPosition, Network, View};
use crate::node::NodeConfig;
/// Stratum server setup ui section.
@ -59,7 +59,7 @@ impl StratumServerSetup {
ui.add_space(4.0);
// Show error message when IP addresses are not available on the system.
let mut addrs = Network::get_ip_list();
let addrs = Network::get_ip_list();
if addrs.is_empty() {
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("network_mining.no_ip_addresses"))
@ -102,11 +102,10 @@ impl StratumServerSetup {
);
// Show stratum port modal.
let port_modal = Modal::new(Self::STRATUM_PORT_MODAL,
ModalLocation::SidePanel)
let port_modal = Modal::new(Self::STRATUM_PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_port"));
Navigator::open_modal(port_modal);
Navigator::show_modal(port_modal);
cb.show_keyboard();
});
ui.add_space(14.0);
@ -138,8 +137,8 @@ impl StratumServerSetup {
// Draw stratum port text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.stratum_port_edit)
.font(TextStyle::Button)
.desired_width(48.0)
.font(TextStyle::Heading)
.desired_width(58.0)
.cursor_at_end(true)
.ui(ui);
text_edit_resp.request_focus();

View file

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{AboveOrBelow, Button, PointerState, Response, RichText, ScrollArea, Sense, Spinner, Widget, WidgetText};
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::{CARET_DOWN, CHECK_SQUARE, CIRCLE, RADIO_BUTTON, SQUARE};
use crate::gui::{Colors, Navigator};
use crate::gui::icons::{CHECK_SQUARE, SQUARE};
pub struct View;
@ -29,7 +29,7 @@ impl View {
/// 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.
/// 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;

View file

@ -25,7 +25,6 @@ use grin_core::global::ChainTypes;
use grin_servers::{Server, ServerStats};
use jni::sys::{jboolean, jstring};
use lazy_static::lazy_static;
use log::info;
use crate::Settings;