ui: exit confirmation at desktop
This commit is contained in:
parent
126574912e
commit
15d57bc968
3 changed files with 121 additions and 87 deletions
117
src/gui/app.rs
117
src/gui/app.rs
|
@ -12,41 +12,145 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use egui::{Context, Stroke};
|
use egui::{Context, RichText, Stroke};
|
||||||
use egui::os::OperatingSystem;
|
use egui::os::OperatingSystem;
|
||||||
|
|
||||||
use crate::gui::{Colors, Navigator};
|
use crate::gui::{Colors, Navigator};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::screens::Root;
|
use crate::gui::screens::Root;
|
||||||
|
use crate::gui::views::{ModalContainer, View};
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
/// To be implemented by platform-specific application.
|
/// To be implemented at platform-specific module.
|
||||||
pub struct PlatformApp<Platform> {
|
pub struct PlatformApp<Platform> {
|
||||||
pub(crate) app: App,
|
pub(crate) app: App,
|
||||||
pub(crate) platform: Platform,
|
pub(crate) platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
/// Contains main ui content, handles application exit and visual style setup.
|
||||||
/// Contains main screen panel and ui setup.
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
/// Main ui container.
|
||||||
root: Root,
|
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 {
|
impl App {
|
||||||
/// Draw content on main screen panel.
|
/// Draw content on main screen panel.
|
||||||
pub fn ui(&mut self, ctx: &Context, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
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()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
fill: Colors::FILL,
|
fill: Colors::FILL,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show(ctx, |ui| {
|
.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);
|
self.root.ui(ui, frame, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exit from the app.
|
/// Draw exit confirmation modal content.
|
||||||
pub fn exit(frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
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() {
|
match OperatingSystem::from_target_os() {
|
||||||
OperatingSystem::Android => {
|
OperatingSystem::Android => {
|
||||||
cb.exit();
|
cb.exit();
|
||||||
|
@ -55,6 +159,7 @@ impl App {
|
||||||
//TODO: exit on iOS
|
//TODO: exit on iOS
|
||||||
}
|
}
|
||||||
OperatingSystem::Nix | OperatingSystem::Mac | OperatingSystem::Windows => {
|
OperatingSystem::Nix | OperatingSystem::Mac | OperatingSystem::Windows => {
|
||||||
|
self.exit_allowed = true;
|
||||||
frame.close();
|
frame.close();
|
||||||
}
|
}
|
||||||
// Web
|
// Web
|
||||||
|
|
|
@ -45,4 +45,9 @@ impl eframe::App for PlatformApp<Desktop> {
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
self.app.ui(ctx, frame, &self.platform);
|
self.app.ui(ctx, frame, &self.platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_close_event(&mut self) -> bool {
|
||||||
|
self.app.exit_requested = true;
|
||||||
|
self.app.exit_allowed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,15 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use egui::RichText;
|
use crate::gui::Navigator;
|
||||||
use crate::gui::{App, Colors, Navigator};
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::screens::{Account, Accounts, Screen, ScreenId};
|
use crate::gui::screens::{Account, Accounts, Screen, ScreenId};
|
||||||
use crate::gui::views::{ModalContainer, NetworkContainer, View};
|
use crate::gui::views::{NetworkContainer, View};
|
||||||
use crate::node::Node;
|
|
||||||
|
|
||||||
|
/// Main ui container.
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
screens: Vec<Box<dyn Screen>>,
|
screens: Vec<Box<dyn Screen>>,
|
||||||
network_panel: NetworkContainer,
|
network_panel: NetworkContainer,
|
||||||
show_exit_progress: bool,
|
|
||||||
allowed_modal_ids: Vec<&'static str>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Root {
|
impl Default for Root {
|
||||||
|
@ -36,29 +33,13 @@ impl Default for Root {
|
||||||
Box::new(Accounts::default()),
|
Box::new(Accounts::default()),
|
||||||
Box::new(Account::default())
|
Box::new(Account::default())
|
||||||
],
|
],
|
||||||
network_panel: NetworkContainer::default(),
|
network_panel: NetworkContainer::default()
|
||||||
show_exit_progress: false,
|
|
||||||
allowed_modal_ids: vec![
|
|
||||||
Navigator::EXIT_MODAL
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModalContainer for Root {
|
|
||||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
|
||||||
self.allowed_modal_ids.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Root {
|
impl Root {
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
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);
|
let (is_panel_open, panel_width) = Self::side_panel_state_width(frame);
|
||||||
egui::SidePanel::left("network_panel")
|
egui::SidePanel::left("network_panel")
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
|
@ -75,64 +56,7 @@ impl Root {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_modal_content(&mut self,
|
/// Show current screen at central panel based on [`Navigator`] state.
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_current_screen(&mut self,
|
fn show_current_screen(&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
frame: &mut eframe::Frame,
|
frame: &mut eframe::Frame,
|
||||||
|
|
Loading…
Reference in a new issue