gui: refactor navigator, add back button handling on android

This commit is contained in:
ardocrat 2023-05-23 23:45:16 +03:00
parent 22c5b945c6
commit 1a7de809c5
10 changed files with 142 additions and 111 deletions

View file

@ -4,6 +4,8 @@ import android.os.Bundle;
import android.os.Process; import android.os.Process;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
import android.util.Log;
import android.view.KeyEvent;
import com.google.androidgamesdk.GameActivity; import com.google.androidgamesdk.GameActivity;
public class MainActivity extends GameActivity { public class MainActivity extends GameActivity {
@ -45,4 +47,15 @@ public class MainActivity extends GameActivity {
BackgroundService.stop(getApplicationContext()); BackgroundService.stop(getApplicationContext());
finish(); finish();
} }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackButtonPress();
return true;
}
return super.onKeyDown(keyCode, event);
}
public native void onBackButtonPress();
} }

View file

@ -35,7 +35,6 @@ impl super::Screen for Account {
fn ui(&mut self, fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
frame: &mut eframe::Frame, frame: &mut eframe::Frame,
nav: &mut Navigator,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
} }

View file

@ -31,25 +31,19 @@ impl Screen for Accounts {
fn ui(&mut self, fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
frame: &mut eframe::Frame, frame: &mut eframe::Frame,
nav: &mut Navigator,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let mut panel: TitlePanel = TitlePanel::new(nav) TitlePanel::new(t!("accounts"))
.title(t!("accounts")) .left_action(
.right_action(TitlePanelAction { if !is_dual_panel_mode(frame) {
icon: GEAR_SIX.into(), TitlePanelAction::new(GLOBE.into(), || {
on_click: Box::new(|nav| { Navigator::toggle_side_panel();
//TODO: open settings })
}), } else {
}); None
if !is_dual_panel_mode(frame) { }
panel = panel.left_action(TitlePanelAction { ).right_action(TitlePanelAction::new(GEAR_SIX.into(), || {
icon: GLOBE.into(), //TODO: settings
on_click: Box::new(|nav|{ })).ui(ui);
nav.toggle_left_panel();
}),
});
}
panel.ui(ui);
egui::CentralPanel::default().frame(Frame { egui::CentralPanel::default().frame(Frame {
stroke: View::DEFAULT_STROKE, stroke: View::DEFAULT_STROKE,
@ -57,10 +51,10 @@ impl Screen for Accounts {
}).show_inside(ui, |ui| { }).show_inside(ui, |ui| {
ui.label(format!("{}Here we go 10000 ツ", ARROW_CIRCLE_LEFT)); ui.label(format!("{}Here we go 10000 ツ", ARROW_CIRCLE_LEFT));
if ui.button("TEST").clicked() { if ui.button("TEST").clicked() {
nav.to(ScreenId::Account) Navigator::to(ScreenId::Account)
}; };
if ui.button(format!("{}BACK ", ARROW_CIRCLE_LEFT)).clicked() { if ui.button(format!("{}BACK ", ARROW_CIRCLE_LEFT)).clicked() {
nav.to(ScreenId::Account) Navigator::to(ScreenId::Account)
}; };
}); });
} }

View file

@ -35,6 +35,5 @@ pub trait Screen {
fn ui(&mut self, fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
frame: &mut eframe::Frame, frame: &mut eframe::Frame,
navigator: &mut Navigator,
cb: &dyn PlatformCallbacks); cb: &dyn PlatformCallbacks);
} }

View file

@ -13,39 +13,66 @@
// limitations under the License. // limitations under the License.
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::RwLock;
use lazy_static::lazy_static;
use crate::gui::screens::ScreenId; use crate::gui::screens::ScreenId;
lazy_static! {
static ref NAVIGATOR_STATE: RwLock<Navigator> = RwLock::new(Navigator::default());
}
pub struct Navigator { pub struct Navigator {
stack: BTreeSet<ScreenId>, screens_stack: BTreeSet<ScreenId>,
pub(crate) left_panel_open: bool, side_panel_open: AtomicBool,
} }
impl Default for Navigator { impl Default for Navigator {
fn default() -> Self { fn default() -> Self {
let mut stack = BTreeSet::new();
stack.insert(ScreenId::Accounts);
Self { Self {
stack, screens_stack: BTreeSet::new(),
left_panel_open: false side_panel_open: AtomicBool::new(false)
} }
} }
} }
impl Navigator { impl Navigator {
pub fn current(&mut self) -> &ScreenId { pub fn init(from: ScreenId) {
self.stack.last().unwrap() let mut w_nav = NAVIGATOR_STATE.write().unwrap();
w_nav.screens_stack.clear();
w_nav.screens_stack.insert(from);
} }
pub fn to(&mut self, id: ScreenId) { pub fn is_current(id: &ScreenId) -> bool {
self.stack.insert(id); let r_nav = NAVIGATOR_STATE.read().unwrap();
r_nav.screens_stack.last().unwrap() == id
} }
pub fn back(&mut self) { pub fn to(id: ScreenId) {
self.stack.pop_last(); NAVIGATOR_STATE.write().unwrap().screens_stack.insert(id);
} }
pub fn toggle_left_panel(&mut self) { pub fn back() {
self.left_panel_open = !self.left_panel_open; let mut w_nav = NAVIGATOR_STATE.write().unwrap();
if w_nav.screens_stack.len() > 1 {
w_nav.screens_stack.pop_last();
} else {
}
} }
}
pub fn toggle_side_panel() {
let w_nav = NAVIGATOR_STATE.write().unwrap();
w_nav.side_panel_open.store(
!w_nav.side_panel_open.load(Ordering::Relaxed),
Ordering::Relaxed
);
}
pub fn is_side_panel_open() -> bool {
let r_nav = NAVIGATOR_STATE.read().unwrap();
r_nav.side_panel_open.load(Ordering::Relaxed)
}
}

View file

@ -20,15 +20,15 @@ use crate::gui::screens::{Account, Accounts, Navigator, Screen, ScreenId};
use crate::gui::views::Network; use crate::gui::views::Network;
pub struct Root { pub struct Root {
navigator: Navigator,
screens: Vec<Box<dyn Screen>>, screens: Vec<Box<dyn Screen>>,
network: Network network: Network
} }
impl Default for Root { impl Default for Root {
fn default() -> Self { fn default() -> Self {
Navigator::init_from(ScreenId::Accounts);
Self { Self {
navigator: Navigator::default(),
screens: (vec![ screens: (vec![
Box::new(Accounts::default()), Box::new(Accounts::default()),
Box::new(Account::default()) Box::new(Account::default())
@ -40,7 +40,7 @@ impl Default for Root {
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) {
let is_network_panel_open = self.navigator.left_panel_open || is_dual_panel_mode(frame); let is_network_panel_open = Navigator::is_side_panel_open() || is_dual_panel_mode(frame);
egui::SidePanel::left("network_panel") egui::SidePanel::left("network_panel")
.resizable(false) .resizable(false)
@ -53,7 +53,7 @@ impl Root {
.. Default::default() .. Default::default()
}) })
.show_animated_inside(ui, is_network_panel_open, |ui| { .show_animated_inside(ui, is_network_panel_open, |ui| {
self.network.ui(ui, frame, &mut self.navigator, cb); self.network.ui(ui, frame, cb);
}); });
egui::CentralPanel::default().frame(egui::Frame { egui::CentralPanel::default().frame(egui::Frame {
@ -68,13 +68,24 @@ impl Root {
ui: &mut egui::Ui, ui: &mut egui::Ui,
frame: &mut eframe::Frame, frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let Self { navigator, screens, .. } = self; let Self { screens, .. } = self;
let current = navigator.current();
for screen in screens.iter_mut() { for screen in screens.iter_mut() {
if screen.id() == *current { if Navigator::is_current(&screen.id()) {
screen.ui(ui, frame, navigator, cb); screen.ui(ui, frame, cb);
break; break;
} }
} }
} }
}
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
Navigator::back();
} }

View file

@ -63,7 +63,6 @@ impl Network {
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
frame: &mut eframe::Frame, frame: &mut eframe::Frame,
nav: &mut Navigator,
_: &dyn PlatformCallbacks) { _: &dyn PlatformCallbacks) {
egui::TopBottomPanel::top("network_title") egui::TopBottomPanel::top("network_title")
@ -76,7 +75,7 @@ impl Network {
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
self.draw_title(ui, frame, nav); self.draw_title(ui, frame);
}); });
egui::TopBottomPanel::bottom("network_tabs") egui::TopBottomPanel::bottom("network_tabs")
@ -145,10 +144,7 @@ impl Network {
} }
} }
fn draw_title(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, nav: &mut Navigator) { fn draw_title(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
// Disable stroke around title buttons on hover
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
StripBuilder::new(ui) StripBuilder::new(ui)
.size(Size::exact(52.0)) .size(Size::exact(52.0))
.vertical(|mut strip| { .vertical(|mut strip| {
@ -172,7 +168,7 @@ impl Network {
if !is_dual_panel_mode(frame) { if !is_dual_panel_mode(frame) {
ui.centered_and_justified(|ui| { ui.centered_and_justified(|ui| {
View::title_button(ui, CARDHOLDER, || { View::title_button(ui, CARDHOLDER, || {
nav.toggle_left_panel(); Navigator::toggle_side_panel();
}); });
}); });
} }

View file

@ -163,7 +163,6 @@ impl NetworkTab for NetworkNode {
[false, false, true, false]); [false, false, true, false]);
}); });
columns[1].vertical_centered(|ui| { columns[1].vertical_centered(|ui| {
let ts = stats.chain_stats.latest_timestamp;
View::rounded_box(ui, View::rounded_box(ui,
stats.peer_count.to_string(), stats.peer_count.to_string(),
t!("peers"), t!("peers"),

View file

@ -23,7 +23,13 @@ use crate::gui::views::View;
pub struct TitlePanelAction { pub struct TitlePanelAction {
pub(crate) icon: Box<str>, pub(crate) icon: Box<str>,
pub(crate) on_click: Box<dyn Fn(&mut Navigator)>, pub(crate) on_click: Box<dyn Fn()>,
}
impl TitlePanelAction {
pub fn new(icon: Box<str>, on_click: fn()) -> Option<Self> {
Option::from(Self { icon, on_click: Box::new(on_click) })
}
} }
#[derive(Default)] #[derive(Default)]
@ -32,41 +38,31 @@ pub struct TitlePanelActions {
right: Option<TitlePanelAction> right: Option<TitlePanelAction>
} }
pub struct TitlePanel<'nav> { pub struct TitlePanel {
title: Option<String>, title: String,
actions: TitlePanelActions, actions: TitlePanelActions,
nav: &'nav mut Navigator
} }
impl<'nav> TitlePanel<'nav> { impl TitlePanel {
pub fn new(nav: &'nav mut Navigator) -> TitlePanel<'nav> { pub fn new(title: String) -> Self {
Self { Self {
title: None, title,
actions: Default::default(), actions: TitlePanelActions::default()
nav,
} }
} }
pub fn title(mut self, title: String) -> Self { pub fn left_action(mut self, action: Option<TitlePanelAction>) -> Self {
self.title = Some(title); self.actions.left = action;
self self
} }
pub fn left_action(mut self, action: TitlePanelAction) -> Self { pub fn right_action(mut self, action: Option<TitlePanelAction>) -> Self {
self.actions.left = Some(action); self.actions.right = action;
self
}
pub fn right_action(mut self, action: TitlePanelAction) -> Self {
self.actions.right = Some(action);
self self
} }
pub fn ui(&mut self, ui: &mut egui::Ui) { pub fn ui(&mut self, ui: &mut egui::Ui) {
// Disable stroke around panel buttons on hover let Self { actions, title } = self;
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
let Self { actions, title, nav } = self;
egui::TopBottomPanel::top("title_panel") egui::TopBottomPanel::top("title_panel")
.resizable(false) .resizable(false)
@ -88,19 +84,19 @@ impl<'nav> TitlePanel<'nav> {
.size(Size::exact(52.0)) .size(Size::exact(52.0))
.horizontal(|mut strip| { .horizontal(|mut strip| {
strip.cell(|ui| { strip.cell(|ui| {
show_action(ui, actions.left.as_ref(), nav); show_action(ui, actions.left.as_ref());
}); });
strip.strip(|builder| { strip.strip(|builder| {
builder builder
.size(Size::remainder()) .size(Size::remainder())
.vertical(|mut strip| { .vertical(|mut strip| {
strip.cell(|ui| { strip.cell(|ui| {
show_title(&*title, ui); show_title(title, ui);
}); });
}); });
}); });
strip.cell(|ui| { strip.cell(|ui| {
show_action(ui, actions.right.as_ref(), nav); show_action(ui, actions.right.as_ref());
}); });
}); });
}); });
@ -109,35 +105,32 @@ impl<'nav> TitlePanel<'nav> {
} }
} }
fn show_action(ui: &mut egui::Ui, action: Option<&TitlePanelAction>, navigator: &mut Navigator) { fn show_action(ui: &mut egui::Ui, action: Option<&TitlePanelAction>) {
if action.is_some() { if action.is_some() {
let action = action.unwrap(); let action = action.unwrap();
ui.centered_and_justified(|ui| { ui.centered_and_justified(|ui| {
View::title_button(ui, &action.icon, || { View::title_button(ui, &action.icon, || {
(action.on_click)(navigator); (action.on_click)();
}); });
}); });
} }
} }
fn show_title(title: &Option<String>, ui: &mut egui::Ui) { fn show_title(title: &String, ui: &mut egui::Ui) {
if title.is_some() { ui.centered_and_justified(|ui| {
ui.centered_and_justified(|ui| { let mut job = LayoutJob::single_section(title.to_uppercase(), TextFormat {
let title_text = title.as_ref().unwrap().to_uppercase(); font_id: FontId::proportional(20.0),
let mut job = LayoutJob::single_section(title_text, TextFormat { color: COLOR_DARK,
font_id: FontId::proportional(20.0), .. Default::default()
color: COLOR_DARK,
.. Default::default()
});
job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
}); });
} job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
});
} }

View file

@ -24,15 +24,20 @@ impl View {
pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Color32::from_gray(190) }; pub const DEFAULT_STROKE: Stroke = Stroke { width: 1.0, color: Color32::from_gray(190) };
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) { pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
let b = egui::widgets::Button::new( ui.scope(|ui| {
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK) // Disable stroke around title buttons on hover
).fill(Color32::TRANSPARENT) ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
.ui(ui).interact(Sense::click_and_drag());
// Click optimization for touch screens let b = egui::widgets::Button::new(
if b.drag_released() || b.clicked() { RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
(action)(); ).fill(Color32::TRANSPARENT)
}; .ui(ui).interact(Sense::click_and_drag());
// Click optimization for touch screens
if b.drag_released() || b.clicked() {
(action)();
};
});
} }
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, mut action: impl FnMut()) { pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, mut action: impl FnMut()) {
@ -53,11 +58,6 @@ impl View {
.fill(color) .fill(color)
.ui(ui).interact(Sense::click_and_drag()); .ui(ui).interact(Sense::click_and_drag());
let vel_y = ui.ctx().input().pointer.delta().y;
let vel_x = ui.ctx().input().pointer.delta().x;
println!("12345, vel {}, {}", vel_y, vel_x);
// Click optimization for touch screens // Click optimization for touch screens
if b.drag_released() || b.clicked() { if b.drag_released() || b.clicked() {
(action)(); (action)();