From 15d57bc96860c942c9e352bc2307958ccb92776a Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 11 Jul 2023 23:36:48 +0300 Subject: [PATCH] ui: exit confirmation at desktop --- src/gui/app.rs | 117 ++++++++++++++++++++++++++++++-- src/gui/platform/desktop/mod.rs | 5 ++ src/gui/screens/root.rs | 86 ++--------------------- 3 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index 18e85c8..07fc51f 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -12,41 +12,145 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Context, Stroke}; +use egui::{Context, RichText, Stroke}; use egui::os::OperatingSystem; use crate::gui::{Colors, Navigator}; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::Root; +use crate::gui::views::{ModalContainer, View}; use crate::node::Node; -/// To be implemented by platform-specific application. +/// To be implemented at platform-specific module. pub struct PlatformApp { pub(crate) app: App, pub(crate) platform: Platform, } -#[derive(Default)] -/// Contains main screen panel and ui setup. +/// Contains main ui content, handles application exit and visual style setup. pub struct App { + /// Main ui container. root: Root, + + /// Check if app exit is allowed on close event callback. + 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`]. + allowed_modal_ids: Vec<&'static str> +} + +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; + Self { + root: Root::default(), + exit_allowed: allow_to_exit, + exit_requested: false, + show_exit_progress: false, + allowed_modal_ids: vec![ + Navigator::EXIT_MODAL + ] + } + } +} + +impl ModalContainer for App { + fn modal_ids(&self) -> &Vec<&'static str> { + &self.allowed_modal_ids + } } impl App { /// 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()) { + self.exit_modal_content(ui, frame, cb); + } self.root.ui(ui, frame, cb); }); } - /// Exit from the app. - pub fn exit(frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + /// Draw exit confirmation modal content. + fn exit_modal_content(&mut self, + ui: &mut egui::Ui, + frame: &mut eframe::Frame, + cb: &dyn PlatformCallbacks) { + Navigator::modal_ui(ui, |ui, modal| { + if self.show_exit_progress { + if !Node::is_running() { + self.exit(frame, cb); + modal.close(); + } + ui.add_space(16.0); + ui.vertical_centered(|ui| { + View::small_loading_spinner(ui); + ui.add_space(12.0); + ui.label(RichText::new(t!("sync_status.shutdown")) + .size(18.0) + .color(Colors::TEXT)); + }); + ui.add_space(10.0); + } else { + ui.add_space(8.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("modal_exit.description")) + .size(18.0) + .color(Colors::TEXT)); + }); + ui.add_space(10.0); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal_exit.exit"), Colors::WHITE, || { + if !Node::is_running() { + self.exit(frame, cb); + modal.close(); + } else { + Node::stop(true); + modal.disable_closing(); + self.show_exit_progress = true; + } + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { + modal.close(); + }); + }); + }); + ui.add_space(6.0); + }); + } + }); + } + + /// Platform-specific exit from the application. + fn exit(&mut self, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { match OperatingSystem::from_target_os() { OperatingSystem::Android => { cb.exit(); @@ -55,6 +159,7 @@ impl App { //TODO: exit on iOS } OperatingSystem::Nix | OperatingSystem::Mac | OperatingSystem::Windows => { + self.exit_allowed = true; frame.close(); } // Web diff --git a/src/gui/platform/desktop/mod.rs b/src/gui/platform/desktop/mod.rs index b3dc4e7..e866655 100644 --- a/src/gui/platform/desktop/mod.rs +++ b/src/gui/platform/desktop/mod.rs @@ -45,4 +45,9 @@ impl eframe::App for PlatformApp { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { self.app.ui(ctx, frame, &self.platform); } + + fn on_close_event(&mut self) -> bool { + self.app.exit_requested = true; + self.app.exit_allowed + } } diff --git a/src/gui/screens/root.rs b/src/gui/screens/root.rs index 8f0b644..2551362 100644 --- a/src/gui/screens/root.rs +++ b/src/gui/screens/root.rs @@ -13,18 +13,15 @@ // limitations under the License. use std::cmp::min; -use egui::RichText; -use crate::gui::{App, Colors, Navigator}; +use crate::gui::Navigator; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::{Account, Accounts, Screen, ScreenId}; -use crate::gui::views::{ModalContainer, NetworkContainer, View}; -use crate::node::Node; +use crate::gui::views::{NetworkContainer, View}; +/// Main ui container. pub struct Root { screens: Vec>, network_panel: NetworkContainer, - show_exit_progress: bool, - allowed_modal_ids: Vec<&'static str> } impl Default for Root { @@ -36,29 +33,13 @@ impl Default for Root { Box::new(Accounts::default()), Box::new(Account::default()) ], - network_panel: NetworkContainer::default(), - show_exit_progress: false, - allowed_modal_ids: vec![ - Navigator::EXIT_MODAL - ] + network_panel: NetworkContainer::default() } } } -impl ModalContainer for Root { - fn 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) { - // 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::side_panel_state_width(frame); egui::SidePanel::left("network_panel") .resizable(false) @@ -75,64 +56,7 @@ impl Root { }); } - fn exit_modal_content(&mut self, - ui: &mut egui::Ui, - frame: &mut eframe::Frame, - cb: &dyn PlatformCallbacks) { - Navigator::modal_ui(ui, |ui, modal| { - if self.show_exit_progress { - if !Node::is_running() { - App::exit(frame, cb); - modal.close(); - } - ui.add_space(16.0); - ui.vertical_centered(|ui| { - View::small_loading_spinner(ui); - ui.add_space(12.0); - ui.label(RichText::new(t!("sync_status.shutdown")) - .size(18.0) - .color(Colors::TEXT)); - }); - ui.add_space(10.0); - } else { - ui.add_space(8.0); - ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("modal_exit.description")) - .size(18.0) - .color(Colors::TEXT)); - }); - ui.add_space(10.0); - - // Show modal buttons. - ui.scope(|ui| { - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); - - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("modal_exit.exit"), Colors::WHITE, || { - if !Node::is_running() { - App::exit(frame, cb); - modal.close(); - } else { - Node::stop(true); - modal.disable_closing(); - self.show_exit_progress = true; - } - }); - }); - columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::WHITE, || { - modal.close(); - }); - }); - }); - ui.add_space(6.0); - }); - } - }); - } - + /// Show current screen at central panel based on [`Navigator`] state. fn show_current_screen(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame,