ui: move global modal to root screen, refactor rounded box background painting, optimize center content

This commit is contained in:
ardocrat 2023-06-03 21:35:38 +03:00
parent 5143a39700
commit f95645ea81
8 changed files with 134 additions and 126 deletions

View file

@ -12,15 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Color32, Context, RichText, Spinner, Stroke, Widget};
use egui::{Context, Stroke};
use egui::os::OperatingSystem;
use egui::style::Margin;
use crate::gui::{Colors, Navigator};
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::Root;
use crate::gui::views::{Modal, ModalId, ModalLocation, View};
use crate::node::Node;
pub struct PlatformApp<Platform> {
pub(crate) app: App,
@ -30,7 +27,6 @@ pub struct PlatformApp<Platform> {
#[derive(Default)]
pub struct App {
root: Root,
show_exit_progress: bool
}
impl App {
@ -41,71 +37,11 @@ impl App {
.. Default::default()
})
.show(ctx, |ui| {
if Navigator::is_modal_open(ModalLocation::Global) {
self.show_global_modal(ui, frame, cb);
}
self.root.ui(ui, frame, cb);
});
}
fn show_global_modal(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
let location = ModalLocation::Global;
Navigator::modal_ui(ui, location, |ui, modal| {
match modal.id {
ModalId::Exit => {
if self.show_exit_progress {
if !Node::is_running() {
Self::exit(frame, cb);
modal.close();
}
ui.add_space(16.0);
ui.vertical_centered(|ui| {
Spinner::new().size(42.0).color(Colors::GRAY).ui(ui);
ui.add_space(10.0);
ui.label(RichText::new(Node::get_sync_status_text())
.size(18.0)
.color(Colors::INACTIVE_TEXT)
);
});
ui.add_space(12.0);
} else {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
ui.label(t!("modal_exit.description"));
});
ui.add_space(10.0);
// 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();
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 exit(frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
pub fn exit(frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
match OperatingSystem::from_target_os() {
OperatingSystem::Android => {
cb.exit();

View file

@ -25,7 +25,7 @@ impl Colors {
pub const GOLD: Color32 = Color32::from_rgb(255, 215, 0);
pub const FILL: Color32 = Color32::from_gray(240);
pub const TITLE: Color32 = Color32::from_gray(60);
pub const SUB_TITLE: Color32 = Color32::from_gray(80);
pub const TEXT: Color32 = Color32::from_gray(80);
pub const BUTTON: Color32 = Color32::from_gray(70);
pub const GRAY: Color32 = Color32::from_gray(120);
pub const STROKE: Color32 = Color32::from_gray(190);

View file

@ -14,14 +14,18 @@
use std::cmp::min;
use crate::gui::Navigator;
use egui::{RichText, Spinner, Widget};
use crate::gui::{App, Colors, Navigator};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::screens::{Account, Accounts, Screen, ScreenId};
use crate::gui::views::{Network, View};
use crate::gui::views::{ModalId, ModalLocation, Network, View};
use crate::node::Node;
pub struct Root {
screens: Vec<Box<dyn Screen>>,
network: Network
network: Network,
show_exit_progress: bool
}
impl Default for Root {
@ -33,14 +37,19 @@ impl Default for Root {
Box::new(Accounts::default()),
Box::new(Account::default())
]),
network: Network::default()
network: Network::default(),
show_exit_progress: false
}
}
}
impl Root {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
let (is_panel_open, panel_width) = dual_panel_state_width(frame);
if Navigator::is_modal_open(ModalLocation::Global) {
self.show_global_modal(ui, frame, cb);
}
let (is_panel_open, panel_width) = self.dual_panel_state_width(frame);
egui::SidePanel::left("network_panel")
.resizable(false)
.exact_width(panel_width)
@ -56,6 +65,63 @@ impl Root {
});
}
fn show_global_modal(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
let location = ModalLocation::Global;
Navigator::modal_ui(ui, location, |ui, modal| {
match modal.id {
ModalId::Exit => {
if self.show_exit_progress {
if !Node::is_running() {
App::exit(frame, cb);
modal.close();
}
ui.add_space(16.0);
ui.vertical_centered(|ui| {
Spinner::new().size(48.0).color(Colors::GRAY).ui(ui);
ui.add_space(12.0);
ui.label(RichText::new(t!("sync_status.shutdown"))
.size(17.0)
.color(Colors::TEXT)
);
});
ui.add_space(10.0);
} else {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
ui.label(t!("modal_exit.description"));
});
ui.add_space(10.0);
// 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();
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,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
@ -68,18 +134,18 @@ impl Root {
}
}
}
}
/// Get dual panel state and width
fn dual_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)
/// Get dual panel state and width
fn dual_panel_state_width(&self, 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)
}
}
#[allow(dead_code)]

View file

@ -76,7 +76,6 @@ impl Network {
outer_margin: Margin::same(5.0),
.. Default::default()
})
.resizable(false)
.show_inside(ui, |ui| {
self.draw_tabs(ui);
});
@ -219,7 +218,7 @@ impl Network {
};
// Draw sync text
let status_color_rgba = Rgba::from(Colors::SUB_TITLE) * color_factor;
let status_color_rgba = Rgba::from(Colors::TEXT) * color_factor;
let status_color = Color32::from(status_color_rgba);
View::ellipsize_text(ui, Node::get_sync_status_text(), 15.0, status_color);
@ -234,9 +233,9 @@ impl Network {
});
}
pub fn server_off_content(ui: &mut egui::Ui) {
View::center_content(ui, [ui.available_width() - 48.0, 160.0], |ui| {
let text = t!("network.inactive_message","dots" => DOTS_THREE_OUTLINE_VERTICAL);
pub fn disabled_server_content(ui: &mut egui::Ui) {
View::center_content(ui, 142.0, |ui| {
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
ui.label(RichText::new(text)
.size(16.0)
.color(Colors::INACTIVE_TEXT)

View file

@ -39,14 +39,14 @@ impl NetworkTab for NetworkMetrics {
let server_stats = Node::get_stats();
if server_stats.is_none() || server_stats.as_ref().unwrap().diff_stats.height == 0 {
if !Node::is_running() {
Network::server_off_content(ui);
Network::disabled_server_content(ui);
} else {
View::center_content(ui, [280.0, 160.0], |ui| {
View::center_content(ui, 160.0, |ui| {
Spinner::new().size(104.0).color(Colors::GOLD).ui(ui);
ui.add_space(18.0);
ui.label(RichText::new(t!("network_metrics.loading"))
.size(16.0)
.color(Colors::INACTIVE_TEXT)
.color(Colors::TEXT)
);
});
}

View file

@ -24,6 +24,7 @@ impl NetworkTab for NetworkMining {
}
fn ui(&mut self, ui: &mut Ui) {
todo!()
}
}

View file

@ -35,7 +35,7 @@ impl NetworkTab for NetworkNode {
let server_stats = Node::get_stats();
if !server_stats.is_some() {
if !Node::is_running() {
Network::server_off_content(ui);
Network::disabled_server_content(ui);
} else {
ui.centered_and_justified(|ui| {
Spinner::new().size(104.0).color(Colors::GOLD).ui(ui);

View file

@ -13,7 +13,7 @@
// limitations under the License.
use egui::{Button, PointerState, Response, RichText, Sense, Vec2, Widget};
use egui::epaint::{Color32, FontId, Rounding, Stroke};
use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke};
use egui::epaint::text::TextWrapping;
use egui::text::{LayoutJob, TextFormat};
use egui_extras::{Size, StripBuilder};
@ -56,7 +56,7 @@ impl View {
/// Sub-header with uppercase characters and more lighter color.
pub fn sub_header(ui: &mut egui::Ui, text: String) {
ui.label(RichText::new(text.to_uppercase()).size(16.0).color(Colors::SUB_TITLE));
ui.label(RichText::new(text.to_uppercase()).size(16.0).color(Colors::TEXT));
}
/// Temporary button click optimization for touch screens.
@ -90,7 +90,7 @@ impl View {
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, action: impl FnOnce()) {
let text_color = match active {
true => { Colors::TITLE }
false => { Colors::SUB_TITLE }
false => { Colors::TEXT }
};
let wt = RichText::new(icon.to_string()).size(24.0).color(text_color);
@ -128,52 +128,58 @@ impl View {
/// | label |
pub fn rounded_box(ui: &mut egui::Ui, value: String, label: String, r: [bool; 4]) {
let mut rect = ui.available_rect_before_wrap();
rect.set_height(46.0);
// Draw box background
ui.painter().rect(
// Create background shape.
let mut bg_shape = RectShape {
rect,
Rounding {
rounding: Rounding {
nw: if r[0] { 8.0 } else { 0.0 },
ne: if r[1] { 8.0 } else { 0.0 },
sw: if r[2] { 8.0 } else { 0.0 },
se: if r[3] { 8.0 } else { 0.0 },
},
Colors::WHITE,
Stroke { width: 1.0, color: Colors::ITEM_STROKE },
);
fill: Colors::WHITE,
stroke: Stroke { width: 1.0, color: Colors::ITEM_STROKE },
};
let bg_idx = ui.painter().add(bg_shape);
ui.vertical_centered_justified(|ui| {
// Correct vertical spacing between items
ui.style_mut().spacing.item_spacing.y = -4.0;
// Draw box content.
let content_resp = ui.allocate_ui_at_rect(rect, |ui| {
ui.vertical_centered_justified(|ui| {
// Correct vertical spacing between items.
ui.style_mut().spacing.item_spacing.y = -4.0;
// Draw box value
let mut job = LayoutJob::single_section(value, TextFormat {
font_id: FontId::proportional(18.0),
color: Colors::BLACK,
.. Default::default()
// Draw box value.
let mut job = LayoutJob::single_section(value, TextFormat {
font_id: FontId::proportional(18.0),
color: Colors::BLACK,
..Default::default()
});
job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
// Draw box label.
ui.label(RichText::new(label).color(Colors::GRAY).size(15.0));
});
job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
}).response;
// Draw box label
ui.label(RichText::new(label).color(Colors::GRAY).size(15.0));
});
// Setup background shape to be painted behind box content.
bg_shape.rect = content_resp.rect;
ui.painter().set(bg_idx, bg_shape);
}
/// Draw content in the center of current layout with specified width and height.
pub fn center_content(ui: &mut egui::Ui, w_h: [f32; 2], content: impl FnOnce(&mut egui::Ui)) {
pub fn center_content(ui: &mut egui::Ui, height: f32, content: impl FnOnce(&mut egui::Ui)) {
ui.vertical_centered(|ui| {
let mut rect = ui.available_rect_before_wrap();
let side_margin = (ui.available_width() - w_h[0]) / 2.0;
rect.min += egui::emath::vec2(side_margin, ui.available_height() / 2.0 - w_h[1] / 2.0);
let side_margin = 24.0;
rect.min += egui::emath::vec2(side_margin, ui.available_height() / 2.0 - height / 2.0);
rect.max -= egui::emath::vec2(side_margin, 0.0);
// rect.set_width(w_h[0]);
ui.allocate_ui_at_rect(rect, |ui| {
(content)(ui);
});