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.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import android.view.KeyEvent;
import com.google.androidgamesdk.GameActivity;
public class MainActivity extends GameActivity {
@ -45,4 +47,15 @@ public class MainActivity extends GameActivity {
BackgroundService.stop(getApplicationContext());
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,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
nav: &mut Navigator,
cb: &dyn PlatformCallbacks) {
}

View file

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

View file

@ -13,39 +13,66 @@
// limitations under the License.
use std::collections::BTreeSet;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::RwLock;
use lazy_static::lazy_static;
use crate::gui::screens::ScreenId;
lazy_static! {
static ref NAVIGATOR_STATE: RwLock<Navigator> = RwLock::new(Navigator::default());
}
pub struct Navigator {
stack: BTreeSet<ScreenId>,
pub(crate) left_panel_open: bool,
screens_stack: BTreeSet<ScreenId>,
side_panel_open: AtomicBool,
}
impl Default for Navigator {
fn default() -> Self {
let mut stack = BTreeSet::new();
stack.insert(ScreenId::Accounts);
Self {
stack,
left_panel_open: false
screens_stack: BTreeSet::new(),
side_panel_open: AtomicBool::new(false)
}
}
}
impl Navigator {
pub fn current(&mut self) -> &ScreenId {
self.stack.last().unwrap()
pub fn init(from: ScreenId) {
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) {
self.stack.insert(id);
pub fn is_current(id: &ScreenId) -> bool {
let r_nav = NAVIGATOR_STATE.read().unwrap();
r_nav.screens_stack.last().unwrap() == id
}
pub fn back(&mut self) {
self.stack.pop_last();
pub fn to(id: ScreenId) {
NAVIGATOR_STATE.write().unwrap().screens_stack.insert(id);
}
pub fn toggle_left_panel(&mut self) {
self.left_panel_open = !self.left_panel_open;
pub fn back() {
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;
pub struct Root {
navigator: Navigator,
screens: Vec<Box<dyn Screen>>,
network: Network
}
impl Default for Root {
fn default() -> Self {
Navigator::init_from(ScreenId::Accounts);
Self {
navigator: Navigator::default(),
screens: (vec![
Box::new(Accounts::default()),
Box::new(Account::default())
@ -40,7 +40,7 @@ impl Default for Root {
impl Root {
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")
.resizable(false)
@ -53,7 +53,7 @@ impl Root {
.. Default::default()
})
.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 {
@ -68,13 +68,24 @@ impl Root {
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
cb: &dyn PlatformCallbacks) {
let Self { navigator, screens, .. } = self;
let current = navigator.current();
let Self { screens, .. } = self;
for screen in screens.iter_mut() {
if screen.id() == *current {
screen.ui(ui, frame, navigator, cb);
if Navigator::is_current(&screen.id()) {
screen.ui(ui, frame, cb);
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,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
nav: &mut Navigator,
_: &dyn PlatformCallbacks) {
egui::TopBottomPanel::top("network_title")
@ -76,7 +75,7 @@ impl Network {
..Default::default()
})
.show_inside(ui, |ui| {
self.draw_title(ui, frame, nav);
self.draw_title(ui, frame);
});
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) {
// Disable stroke around title buttons on hover
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
fn draw_title(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
StripBuilder::new(ui)
.size(Size::exact(52.0))
.vertical(|mut strip| {
@ -172,7 +168,7 @@ impl Network {
if !is_dual_panel_mode(frame) {
ui.centered_and_justified(|ui| {
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]);
});
columns[1].vertical_centered(|ui| {
let ts = stats.chain_stats.latest_timestamp;
View::rounded_box(ui,
stats.peer_count.to_string(),
t!("peers"),

View file

@ -23,7 +23,13 @@ use crate::gui::views::View;
pub struct TitlePanelAction {
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)]
@ -32,41 +38,31 @@ pub struct TitlePanelActions {
right: Option<TitlePanelAction>
}
pub struct TitlePanel<'nav> {
title: Option<String>,
pub struct TitlePanel {
title: String,
actions: TitlePanelActions,
nav: &'nav mut Navigator
}
impl<'nav> TitlePanel<'nav> {
pub fn new(nav: &'nav mut Navigator) -> TitlePanel<'nav> {
impl TitlePanel {
pub fn new(title: String) -> Self {
Self {
title: None,
actions: Default::default(),
nav,
title,
actions: TitlePanelActions::default()
}
}
pub fn title(mut self, title: String) -> Self {
self.title = Some(title);
pub fn left_action(mut self, action: Option<TitlePanelAction>) -> Self {
self.actions.left = action;
self
}
pub fn left_action(mut self, action: TitlePanelAction) -> Self {
self.actions.left = Some(action);
self
}
pub fn right_action(mut self, action: TitlePanelAction) -> Self {
self.actions.right = Some(action);
pub fn right_action(mut self, action: Option<TitlePanelAction>) -> Self {
self.actions.right = action;
self
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
// Disable stroke around panel buttons on hover
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
let Self { actions, title, nav } = self;
let Self { actions, title } = self;
egui::TopBottomPanel::top("title_panel")
.resizable(false)
@ -88,19 +84,19 @@ impl<'nav> TitlePanel<'nav> {
.size(Size::exact(52.0))
.horizontal(|mut strip| {
strip.cell(|ui| {
show_action(ui, actions.left.as_ref(), nav);
show_action(ui, actions.left.as_ref());
});
strip.strip(|builder| {
builder
.size(Size::remainder())
.vertical(|mut strip| {
strip.cell(|ui| {
show_title(&*title, ui);
show_title(title, 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() {
let action = action.unwrap();
ui.centered_and_justified(|ui| {
View::title_button(ui, &action.icon, || {
(action.on_click)(navigator);
(action.on_click)();
});
});
}
}
fn show_title(title: &Option<String>, ui: &mut egui::Ui) {
if title.is_some() {
ui.centered_and_justified(|ui| {
let title_text = title.as_ref().unwrap().to_uppercase();
let mut job = LayoutJob::single_section(title_text, TextFormat {
font_id: FontId::proportional(20.0),
color: COLOR_DARK,
.. Default::default()
});
job.wrap = TextWrapping {
max_rows: 1,
break_anywhere: false,
overflow_character: Option::from(''),
..Default::default()
};
ui.label(job);
fn show_title(title: &String, ui: &mut egui::Ui) {
ui.centered_and_justified(|ui| {
let mut job = LayoutJob::single_section(title.to_uppercase(), TextFormat {
font_id: FontId::proportional(20.0),
color: COLOR_DARK,
.. Default::default()
});
}
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 fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
let b = egui::widgets::Button::new(
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
).fill(Color32::TRANSPARENT)
.ui(ui).interact(Sense::click_and_drag());
ui.scope(|ui| {
// Disable stroke around title buttons on hover
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
// Click optimization for touch screens
if b.drag_released() || b.clicked() {
(action)();
};
let b = egui::widgets::Button::new(
RichText::new(icon.to_string()).size(24.0).color(COLOR_DARK)
).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()) {
@ -53,11 +58,6 @@ impl View {
.fill(color)
.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
if b.drag_released() || b.clicked() {
(action)();