diff --git a/app/src/main/java/mw/gri/android/MainActivity.java b/app/src/main/java/mw/gri/android/MainActivity.java
index 73bea32..5b8ecb6 100644
--- a/app/src/main/java/mw/gri/android/MainActivity.java
+++ b/app/src/main/java/mw/gri/android/MainActivity.java
@@ -1,15 +1,15 @@
package mw.gri.android;
import android.content.*;
-import android.content.pm.PackageManager;
-import android.hardware.SensorManager;
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 android.view.OrientationEventListener;
+import android.view.View;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
import com.google.androidgamesdk.GameActivity;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -45,46 +45,40 @@ public class MainActivity extends GameActivity {
}
super.onCreate(null);
- // Callback to update display cutouts at native code.
- OrientationEventListener orientationEventListener = new OrientationEventListener(this,
- SensorManager.SENSOR_DELAY_NORMAL) {
- @Override
- public void onOrientationChanged(int orientation) {
- onDisplayCutoutsChanged(Utils.getDisplayCutouts(MainActivity.this));
- }
- };
- if (orientationEventListener.canDetectOrientation()) {
- orientationEventListener.enable();
- }
-
// Register receiver to finish activity from the BackgroundService.
registerReceiver(mBroadcastReceiver, new IntentFilter(FINISH_ACTIVITY_ACTION));
// Start notification service.
BackgroundService.start(this);
+
+ // Listener for display cutouts to pass values into native code.
+ View content = getWindow().getDecorView().findViewById(android.R.id.content);
+ ViewCompat.setOnApplyWindowInsetsListener(content, (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ int[] cutouts = new int[]{0, 0, 0, 0};
+ cutouts[0] = Utils.pxToDp(systemBars.top, this);
+ cutouts[1] = Utils.pxToDp(systemBars.right, this);
+ cutouts[2] = Utils.pxToDp(systemBars.bottom, this);
+ cutouts[3] = Utils.pxToDp(systemBars.left, this);
+ onDisplayCutouts(cutouts);
+ return insets;
+ });
}
// Implemented into native code to handle display cutouts change.
- native void onDisplayCutoutsChanged(int[] cutouts);
-
- @Override
- protected void onResume() {
- super.onResume();
- // Update display cutouts.
- onDisplayCutoutsChanged(Utils.getDisplayCutouts(this));
- }
+ native void onDisplayCutouts(int[] cutouts);
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- onBackButtonPress();
+ onBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
- // Implemented into native code to handle back button press.
- public native void onBackButtonPress();
+ // Implemented into native code to handle key code BACK event.
+ public native void onBack();
private boolean mManualExit;
private final AtomicBoolean mActivityDestroyed = new AtomicBoolean(false);
diff --git a/app/src/main/java/mw/gri/android/Utils.java b/app/src/main/java/mw/gri/android/Utils.java
index be417aa..546b5ab 100644
--- a/app/src/main/java/mw/gri/android/Utils.java
+++ b/app/src/main/java/mw/gri/android/Utils.java
@@ -1,52 +1,10 @@
package mw.gri.android;
-import android.app.Activity;
import android.content.Context;
-import android.os.Build;
-import android.view.DisplayCutout;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import androidx.core.graphics.Insets;
-import androidx.core.view.DisplayCutoutCompat;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
public class Utils {
-
- public static int[] getDisplayCutouts(Activity context) {
- int[] cutouts = new int[]{0, 0, 0, 0};
- if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
- WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets();
- android.graphics.Insets barsInsets = insets.getInsets(WindowInsets.Type.systemBars());
- android.graphics.Insets cutoutsInsets = insets.getInsets(WindowInsets.Type.displayCutout());
- cutouts[0] = pxToDp(Integer.max(barsInsets.top, cutoutsInsets.top), context);
- cutouts[1] = pxToDp(Integer.max(barsInsets.right, cutoutsInsets.right), context);
- cutouts[2] = pxToDp(Integer.max(barsInsets.bottom, cutoutsInsets.bottom), context);
- cutouts[3] = pxToDp(Integer.max(barsInsets.left, cutoutsInsets.left), context);
- } else if (Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.Q) {
- DisplayCutout displayCutout = context.getWindowManager().getDefaultDisplay().getCutout();
- cutouts[0] = displayCutout.getSafeInsetBottom();
- cutouts[1] = displayCutout.getSafeInsetRight();
- cutouts[2] = displayCutout.getSafeInsetBottom();
- cutouts[3] = displayCutout.getSafeInsetLeft();
- } else {
- WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(context.getWindow().getDecorView());
- if (insets != null) {
- DisplayCutoutCompat displayCutout = insets.getDisplayCutout();
- Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
- if (displayCutout != null) {
- cutouts[0] = pxToDp(Integer.max(displayCutout.getSafeInsetTop(), systemBars.top), context);
- cutouts[1] = pxToDp(Integer.max(displayCutout.getSafeInsetRight(), systemBars.right), context);
- cutouts[2] = pxToDp(Integer.max(displayCutout.getSafeInsetBottom(), systemBars.bottom), context);
- cutouts[3] = pxToDp(Integer.max(displayCutout.getSafeInsetLeft(), systemBars.left), context);
- }
- }
- }
- return cutouts;
- }
-
- private static int pxToDp(int px, Context context) {
+ // Convert Pixels to DensityPixels
+ public static int pxToDp(int px, Context context) {
return (int) (px / context.getResources().getDisplayMetrics().density);
}
}
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 90df0e7..90b5ad8 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -2,6 +2,7 @@
\ No newline at end of file
diff --git a/src/gui/app.rs b/src/gui/app.rs
index 61906c7..26ec926 100644
--- a/src/gui/app.rs
+++ b/src/gui/app.rs
@@ -12,237 +12,147 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use egui::{Context, RichText, Stroke};
-use egui::os::OperatingSystem;
+use std::sync::atomic::{AtomicI32, Ordering};
+
+use egui::Context;
+use lazy_static::lazy_static;
+
use crate::gui::Colors;
-
use crate::gui::platform::PlatformCallbacks;
-use crate::gui::views::{Modal, ModalContainer, Root, View};
-use crate::node::Node;
+use crate::gui::views::Root;
-/// To be implemented at platform-specific module.
+/// Implements ui entry point and contains platform-specific callbacks.
pub struct PlatformApp {
- pub(crate) app: App,
+ /// Platform specific callbacks handler.
pub(crate) platform: Platform,
-}
-
-/// Contains main ui, handles exit and visual style setup.
-pub struct App {
/// Main ui content.
- root: Root,
- /// Check if app exit is allowed on close event of [`eframe::App`] platform implementation.
- pub(crate) exit_allowed: 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>
+ root: Root
}
-impl Default for App {
- fn default() -> Self {
- let os = OperatingSystem::from_target_os();
- // Exit from eframe only for non-mobile platforms.
- let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
- Self {
- root: Root::default(),
- exit_allowed,
- show_exit_progress: false,
- allowed_modal_ids: vec![
- Self::EXIT_MODAL
- ]
- }
+impl PlatformApp {
+ pub fn new(platform: Platform) -> Self {
+ Self { platform, root: Root::default() }
}
}
-impl ModalContainer for App {
- fn modal_ids(&self) -> &Vec<&'static str> {
- &self.allowed_modal_ids
- }
-}
+impl eframe::App for PlatformApp {
+ fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
+ // Show panels to support display cutouts (insets).
+ padding_panels(ctx);
-impl App {
- /// Identifier for exit confirmation [`Modal`].
- pub const EXIT_MODAL: &'static str = "exit_confirmation";
-
- /// Draw content on main screen panel.
- pub fn ui(&mut self, ctx: &Context, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
+ // Show main content.
egui::CentralPanel::default()
.frame(egui::Frame {
fill: Colors::FILL,
..Default::default()
})
.show(ctx, |ui| {
- // Draw modal content if it's open.
- if self.can_draw_modal() {
- self.exit_modal_content(ui, frame, cb);
- }
- // Draw main content.
- self.root.ui(ui, frame, cb);
+ self.root.ui(ui, frame, &self.platform);
});
}
- /// Draw exit confirmation modal content.
- fn exit_modal_content(&mut self,
- ui: &mut egui::Ui,
- frame: &mut eframe::Frame,
- cb: &dyn PlatformCallbacks) {
- 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();
- }
- OperatingSystem::IOS => {
- //TODO: exit on iOS.
- }
- OperatingSystem::Nix | OperatingSystem::Mac | OperatingSystem::Windows => {
- self.exit_allowed = true;
- frame.close();
- }
- OperatingSystem::Unknown => {}
- }
- }
-
- /// Setup application styles.
- pub fn setup_visuals(ctx: &Context) {
- let mut style = (*ctx.style()).clone();
- // Setup spacing for buttons.
- style.spacing.button_padding = egui::vec2(12.0, 8.0);
- // Make scroll-bar thinner.
- style.spacing.scroll_bar_width = 4.0;
- // Disable spacing between items.
- style.spacing.item_spacing = egui::vec2(0.0, 0.0);
- // Setup radio button/checkbox size and spacing.
- style.spacing.icon_width = 24.0;
- style.spacing.icon_width_inner = 14.0;
- style.spacing.icon_spacing = 10.0;
- // Setup style
- ctx.set_style(style);
-
- let mut visuals = egui::Visuals::light();
- // Setup selection color.
- visuals.selection.stroke = Stroke { width: 1.0, color: Colors::TEXT };
- visuals.selection.bg_fill = Colors::GOLD;
- // Disable stroke around panels by default
- visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
- // Setup visuals
- ctx.set_visuals(visuals);
- }
-
- /// Setup application fonts.
- pub fn setup_fonts(ctx: &Context) {
- use egui::FontFamily::Proportional;
-
- let mut fonts = egui::FontDefinitions::default();
-
- fonts.font_data.insert(
- "phosphor".to_owned(),
- egui::FontData::from_static(include_bytes!(
- "../../fonts/phosphor.ttf"
- )).tweak(egui::FontTweak {
- scale: 1.0,
- y_offset_factor: -0.30,
- y_offset: 0.0,
- baseline_offset_factor: 0.30,
- }),
- );
- fonts
- .families
- .entry(Proportional)
- .or_default()
- .insert(0, "phosphor".to_owned());
-
- fonts.font_data.insert(
- "noto".to_owned(),
- egui::FontData::from_static(include_bytes!(
- "../../fonts/noto_sc_reg.otf"
- )).tweak(egui::FontTweak {
- scale: 1.0,
- y_offset_factor: -0.25,
- y_offset: 0.0,
- baseline_offset_factor: 0.17,
- }),
- );
- fonts
- .families
- .entry(Proportional)
- .or_default()
- .insert(0, "noto".to_owned());
-
- ctx.set_fonts(fonts);
-
- use egui::FontId;
- use egui::TextStyle::*;
-
- let mut style = (*ctx.style()).clone();
- style.text_styles = [
- (Heading, FontId::new(20.0, Proportional)),
- (Body, FontId::new(16.0, Proportional)),
- (Button, FontId::new(18.0, Proportional)),
- (Small, FontId::new(12.0, Proportional)),
- (Monospace, FontId::new(16.0, Proportional)),
- ].into();
-
- ctx.set_style(style);
- }
-
- /// Show exit confirmation modal.
- pub fn show_exit_modal() {
- let exit_modal = Modal::new(Self::EXIT_MODAL).title(t!("modal_exit.exit"));
- Modal::show(exit_modal);
+ fn on_close_event(&mut self) -> bool {
+ Root::show_exit_modal();
+ self.root.exit_allowed
}
}
+
+/// Draw panels to support display cutouts (insets).
+fn padding_panels(ctx: &Context) {
+ egui::TopBottomPanel::top("top_padding_panel")
+ .frame(egui::Frame {
+ inner_margin: egui::style::Margin::same(0.0),
+ fill: Colors::YELLOW,
+ ..Default::default()
+ })
+ .show_separator_line(false)
+ .resizable(false)
+ .exact_height(get_top_display_cutout())
+ .show(ctx, |_ui| {});
+
+ egui::TopBottomPanel::bottom("bottom_padding_panel")
+ .frame(egui::Frame {
+ inner_margin: egui::style::Margin::same(0.0),
+ fill: Colors::BLACK,
+ ..Default::default()
+ })
+ .show_separator_line(false)
+ .resizable(false)
+ .exact_height(get_bottom_display_cutout())
+ .show(ctx, |_ui| {});
+
+ egui::SidePanel::right("right_padding_panel")
+ .frame(egui::Frame {
+ inner_margin: egui::style::Margin::same(0.0),
+ fill: Colors::YELLOW,
+ ..Default::default()
+ })
+ .show_separator_line(false)
+ .resizable(false)
+ .max_width(get_right_display_cutout())
+ .show(ctx, |_ui| {});
+
+ egui::SidePanel::left("left_padding_panel")
+ .frame(egui::Frame {
+ inner_margin: egui::style::Margin::same(0.0),
+ fill: Colors::YELLOW,
+ ..Default::default()
+ })
+ .show_separator_line(false)
+ .resizable(false)
+ .max_width(get_left_display_cutout())
+ .show(ctx, |_ui| {});
+}
+
+/// Get top display cutout (inset) size.
+pub fn get_top_display_cutout() -> f32 {
+ TOP_DISPLAY_CUTOUT.load(Ordering::Relaxed) as f32
+}
+
+/// Get right display cutout (inset) size.
+pub fn get_right_display_cutout() -> f32 {
+ RIGHT_DISPLAY_CUTOUT.load(Ordering::Relaxed) as f32
+}
+
+/// Get bottom display cutout (inset) size.
+pub fn get_bottom_display_cutout() -> f32 {
+ BOTTOM_DISPLAY_CUTOUT.load(Ordering::Relaxed) as f32
+}
+
+/// Get left display cutout (inset) size.
+pub fn get_left_display_cutout() -> f32 {
+ LEFT_DISPLAY_CUTOUT.load(Ordering::Relaxed) as f32
+}
+
+/// Fields to handle platform-specific display cutouts (insets).
+lazy_static! {
+ static ref TOP_DISPLAY_CUTOUT: AtomicI32 = AtomicI32::new(0);
+ static ref RIGHT_DISPLAY_CUTOUT: AtomicI32 = AtomicI32::new(0);
+ static ref BOTTOM_DISPLAY_CUTOUT: AtomicI32 = AtomicI32::new(0);
+ static ref LEFT_DISPLAY_CUTOUT: AtomicI32 = AtomicI32::new(0);
+}
+
+#[allow(dead_code)]
+#[cfg(target_os = "android")]
+#[allow(non_snake_case)]
+#[no_mangle]
+/// Callback from Java code to update display cutouts (insets).
+pub extern "C" fn Java_mw_gri_android_MainActivity_onDisplayCutouts(
+ _env: jni::JNIEnv,
+ _class: jni::objects::JObject,
+ cutouts: jni::sys::jarray
+) {
+ use jni::objects::{JObject, JPrimitiveArray};
+
+ let mut array: [i32; 4] = [0; 4];
+ unsafe {
+ let j_obj = JObject::from_raw(cutouts);
+ let j_arr = JPrimitiveArray::from(j_obj);
+ _env.get_int_array_region(j_arr, 0, array.as_mut()).unwrap();
+ }
+ TOP_DISPLAY_CUTOUT.store(array[0], Ordering::Relaxed);
+ RIGHT_DISPLAY_CUTOUT.store(array[1], Ordering::Relaxed);
+ BOTTOM_DISPLAY_CUTOUT.store(array[2], Ordering::Relaxed);
+ LEFT_DISPLAY_CUTOUT.store(array[3], Ordering::Relaxed);
+}
+
diff --git a/src/gui/mod.rs b/src/gui/mod.rs
index 40c7952..0fc208c 100644
--- a/src/gui/mod.rs
+++ b/src/gui/mod.rs
@@ -14,7 +14,7 @@
mod app;
-pub use app::{App, PlatformApp};
+pub use app::PlatformApp;
mod colors;
pub use colors::Colors;
diff --git a/src/gui/platform/android/mod.rs b/src/gui/platform/android/mod.rs
index 383517b..da1b479 100644
--- a/src/gui/platform/android/mod.rs
+++ b/src/gui/platform/android/mod.rs
@@ -12,11 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::sync::atomic::{AtomicI32, Ordering};
-use lazy_static::lazy_static;
use winit::platform::android::activity::AndroidApp;
-
-use crate::gui::{App, PlatformApp};
use crate::gui::platform::PlatformCallbacks;
#[derive(Clone)]
@@ -89,95 +85,4 @@ impl PlatformCallbacks for Android {
};
env.call_method(activity, "onExit", "()V", &[]).unwrap();
}
-}
-
-impl PlatformApp {
- pub fn new(platform: Android) -> Self {
- Self {
- app: App::default(),
- platform,
- }
- }
-}
-
-impl eframe::App for PlatformApp {
- fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
- padding_panels(ctx);
- self.app.ui(ctx, frame, &self.platform);
- }
-}
-
-fn padding_panels(ctx: &egui::Context) {
- egui::TopBottomPanel::top("top_padding_panel")
- .frame(egui::Frame {
- inner_margin: egui::style::Margin::same(0.0),
- fill: ctx.style().visuals.panel_fill,
- ..Default::default()
- })
- .show_separator_line(false)
- .resizable(false)
- .exact_height(DISPLAY_CUTOUT_TOP.load(Ordering::Relaxed) as f32)
- .show(ctx, |_ui| {});
-
- egui::TopBottomPanel::bottom("bottom_padding_panel")
- .frame(egui::Frame {
- inner_margin: egui::style::Margin::same(0.0),
- fill: ctx.style().visuals.panel_fill,
- ..Default::default()
- })
- .show_separator_line(false)
- .resizable(false)
- .exact_height(DISPLAY_CUTOUT_BOTTOM.load(Ordering::Relaxed) as f32)
- .show(ctx, |_ui| {});
-
- egui::SidePanel::right("right_padding_panel")
- .frame(egui::Frame {
- inner_margin: egui::style::Margin::same(0.0),
- fill: ctx.style().visuals.panel_fill,
- ..Default::default()
- })
- .show_separator_line(false)
- .resizable(false)
- .max_width(DISPLAY_CUTOUT_RIGHT.load(Ordering::Relaxed) as f32)
- .show(ctx, |_ui| {});
-
- egui::SidePanel::left("left_padding_panel")
- .frame(egui::Frame {
- inner_margin: egui::style::Margin::same(0.0),
- fill: ctx.style().visuals.panel_fill,
- ..Default::default()
- })
- .show_separator_line(false)
- .resizable(false)
- .max_width(DISPLAY_CUTOUT_LEFT.load(Ordering::Relaxed) as f32)
- .show(ctx, |_ui| {});
-}
-
-lazy_static! {
- static ref DISPLAY_CUTOUT_TOP: AtomicI32 = AtomicI32::new(0);
- static ref DISPLAY_CUTOUT_RIGHT: AtomicI32 = AtomicI32::new(0);
- static ref DISPLAY_CUTOUT_BOTTOM: AtomicI32 = AtomicI32::new(0);
- static ref DISPLAY_CUTOUT_LEFT: AtomicI32 = AtomicI32::new(0);
-}
-
-#[allow(non_snake_case)]
-#[no_mangle]
-/// Callback from Java code to update display cutouts.
-pub extern "C" fn Java_mw_gri_android_MainActivity_onDisplayCutoutsChanged(
- _env: jni::JNIEnv,
- _class: jni::objects::JObject,
- cutouts: jni::sys::jarray
-) {
- use jni::objects::{JObject, JPrimitiveArray};
-
- let mut array: [i32; 4] = [0; 4];
- unsafe {
- let j_obj = JObject::from_raw(cutouts);
- let j_arr = JPrimitiveArray::from(j_obj);
- _env.get_int_array_region(j_arr, 0, array.as_mut()).unwrap();
- }
- DISPLAY_CUTOUT_TOP.store(array[0], Ordering::Relaxed);
- DISPLAY_CUTOUT_RIGHT.store(array[1], Ordering::Relaxed);
- DISPLAY_CUTOUT_BOTTOM.store(array[2], Ordering::Relaxed);
- DISPLAY_CUTOUT_LEFT.store(array[3], Ordering::Relaxed);
}
\ No newline at end of file
diff --git a/src/gui/platform/desktop/mod.rs b/src/gui/platform/desktop/mod.rs
index eaec138..534ab58 100644
--- a/src/gui/platform/desktop/mod.rs
+++ b/src/gui/platform/desktop/mod.rs
@@ -31,23 +31,3 @@ impl PlatformCallbacks for Desktop {
fn exit(&self) {}
}
-
-impl PlatformApp {
- pub fn new(platform: Desktop) -> Self {
- Self {
- app: App::default(),
- platform,
- }
- }
-}
-
-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 {
- App::show_exit_modal();
- self.app.exit_allowed
- }
-}
diff --git a/src/gui/views/accounts/content.rs b/src/gui/views/accounts/accounts.rs
similarity index 90%
rename from src/gui/views/accounts/content.rs
rename to src/gui/views/accounts/accounts.rs
index 9d52b7b..112a0f4 100644
--- a/src/gui/views/accounts/content.rs
+++ b/src/gui/views/accounts/accounts.rs
@@ -17,13 +17,13 @@ use crate::gui::icons::{GLOBE, PLUS};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Root, TitleAction, TitlePanel, View};
-/// Accounts central panel content.
-pub struct AccountsContent {
+/// Accounts content.
+pub struct Accounts {
/// List of accounts.
list: Vec
}
-impl Default for AccountsContent {
+impl Default for Accounts {
fn default() -> Self {
Self {
list: vec![],
@@ -31,11 +31,11 @@ impl Default for AccountsContent {
}
}
-impl AccountsContent {
+impl Accounts {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
TitlePanel::ui(t!("accounts.title"), if !Root::is_dual_panel_mode(frame) {
TitleAction::new(GLOBE, || {
- Root::toggle_network_panel();
+ Root::toggle_side_panel();
})
} else {
None
diff --git a/src/gui/views/accounts.rs b/src/gui/views/accounts/mod.rs
similarity index 94%
rename from src/gui/views/accounts.rs
rename to src/gui/views/accounts/mod.rs
index 6bde807..56e29d6 100644
--- a/src/gui/views/accounts.rs
+++ b/src/gui/views/accounts/mod.rs
@@ -12,5 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-mod content;
-pub use content::*;
\ No newline at end of file
+mod accounts;
+pub use accounts::*;
\ No newline at end of file
diff --git a/src/gui/views/network/metrics.rs b/src/gui/views/network/metrics.rs
index aef6da3..5210f29 100644
--- a/src/gui/views/network/metrics.rs
+++ b/src/gui/views/network/metrics.rs
@@ -20,7 +20,7 @@ use crate::gui::Colors;
use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::network::{NetworkTab, NetworkTabType};
-use crate::gui::views::{Modal, NetworkContent, View};
+use crate::gui::views::{Modal, Network, View};
use crate::node::Node;
/// Chain metrics tab content.
@@ -40,7 +40,7 @@ impl NetworkTab for NetworkMetrics {
let server_stats = Node::get_stats();
// Show message to enable node when it's not running.
if !Node::is_running() {
- NetworkContent::disabled_node_ui(ui);
+ Network::disabled_node_ui(ui);
return;
}
diff --git a/src/gui/views/network/mining.rs b/src/gui/views/network/mining.rs
index 0c3bbca..68ad511 100644
--- a/src/gui/views/network/mining.rs
+++ b/src/gui/views/network/mining.rs
@@ -20,9 +20,9 @@ use grin_servers::WorkerStats;
use crate::gui::Colors;
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON};
use crate::gui::platform::PlatformCallbacks;
-use crate::gui::views::{Modal, NetworkContent, View};
+use crate::gui::views::{Modal, Network, View};
use crate::gui::views::network::{NetworkTab, NetworkTabType};
-use crate::gui::views::network::setup::stratum::StratumSetup;
+use crate::gui::views::network::setup::StratumSetup;
use crate::node::{Node, NodeConfig};
/// Mining tab content.
@@ -41,7 +41,7 @@ impl NetworkTab for NetworkMining {
// Show message to enable node when it's not running.
if !Node::is_running() {
- NetworkContent::disabled_node_ui(ui);
+ Network::disabled_node_ui(ui);
return;
}
diff --git a/src/gui/views/network.rs b/src/gui/views/network/mod.rs
similarity index 83%
rename from src/gui/views/network.rs
rename to src/gui/views/network/mod.rs
index f506d7c..069e213 100644
--- a/src/gui/views/network.rs
+++ b/src/gui/views/network/mod.rs
@@ -12,11 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-mod content;
mod metrics;
-mod mining;
-mod settings;
-mod node;
-mod setup;
+pub use metrics::*;
-pub use content::*;
\ No newline at end of file
+mod mining;
+pub use mining::*;
+
+mod settings;
+pub use settings::*;
+
+mod node;
+pub use node::*;
+
+mod setup;
+pub use setup::*;
+
+mod network;
+pub use network::*;
\ No newline at end of file
diff --git a/src/gui/views/network/content.rs b/src/gui/views/network/network.rs
similarity index 83%
rename from src/gui/views/network/content.rs
rename to src/gui/views/network/network.rs
index d6fb801..33bb76e 100644
--- a/src/gui/views/network/content.rs
+++ b/src/gui/views/network/network.rs
@@ -21,16 +21,8 @@ use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER};
use crate::gui::platform::PlatformCallbacks;
-use crate::gui::views::{Modal, ModalContainer, Root, TitlePanel, View};
-use crate::gui::views::network::setup::dandelion::DandelionSetup;
-use crate::gui::views::network::setup::node::NodeSetup;
-use crate::gui::views::network::setup::p2p::P2PSetup;
-use crate::gui::views::network::setup::pool::PoolSetup;
-use crate::gui::views::network::setup::stratum::StratumSetup;
-use crate::gui::views::network::metrics::NetworkMetrics;
-use crate::gui::views::network::mining::NetworkMining;
-use crate::gui::views::network::node::NetworkNode;
-use crate::gui::views::network::settings::NetworkSettings;
+use crate::gui::views::{Modal, ModalContainer, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitleAction, TitleContent, TitlePanel, View};
+use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
use crate::node::Node;
pub trait NetworkTab {
@@ -59,15 +51,15 @@ impl NetworkTabType {
}
}
-/// Network side panel content.
-pub struct NetworkContent {
+/// Network content.
+pub struct Network {
/// Current tab view to show at ui.
current_tab: Box,
/// [`Modal`] ids allowed at this ui container.
modal_ids: Vec<&'static str>,
}
-impl Default for NetworkContent {
+impl Default for Network {
fn default() -> Self {
Self {
current_tab: Box::new(NetworkNode::default()),
@@ -110,13 +102,13 @@ impl Default for NetworkContent {
}
}
-impl ModalContainer for NetworkContent {
+impl ModalContainer for Network {
fn modal_ids(&self) -> &Vec<&'static str> {
self.modal_ids.as_ref()
}
}
-impl NetworkContent {
+impl Network {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
// Show modal content if it's opened.
if self.can_draw_modal() {
@@ -140,7 +132,8 @@ impl NetworkContent {
egui::TopBottomPanel::bottom("network_tabs")
.frame(egui::Frame {
- outer_margin: Margin::same(4.0),
+ fill: Colors::FILL,
+ inner_margin: Margin::same(4.0),
..Default::default()
})
.show_inside(ui, |ui| {
@@ -201,31 +194,43 @@ impl NetworkContent {
/// Draw title content.
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
- StripBuilder::new(ui)
- .size(Size::exact(52.0))
- .size(Size::remainder())
- .size(Size::exact(52.0))
- .horizontal(|mut strip| {
- strip.cell(|ui| {
- ui.centered_and_justified(|ui| {
- View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
- //TODO: Show connections
- });
- });
- });
- strip.strip(|builder| {
- self.title_text_ui(builder);
- });
- strip.cell(|ui| {
- if !Root::is_dual_panel_mode(frame) {
- ui.centered_and_justified(|ui| {
- View::title_button(ui, CARDHOLDER, || {
- Root::toggle_network_panel();
- });
- });
- }
- });
- });
+ let title_content = TitleContent::Custom("network_title".to_string(), Box::new(|ui| {
+ }));
+ TitlePanel::test_ui(title_content, TitleAction::new(DOTS_THREE_OUTLINE_VERTICAL, || {
+ //TODO: Show connections
+ }), if !Root::is_dual_panel_mode(frame) {
+ TitleAction::new(CARDHOLDER, || {
+ Root::toggle_side_panel();
+ })
+ } else {
+ None
+ }, ui);
+
+ // StripBuilder::new(ui)
+ // .size(Size::exact(52.0))
+ // .size(Size::remainder())
+ // .size(Size::exact(52.0))
+ // .horizontal(|mut strip| {
+ // strip.cell(|ui| {
+ // ui.centered_and_justified(|ui| {
+ // View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
+ // //TODO: Show connections
+ // });
+ // });
+ // });
+ // strip.strip(|builder| {
+ // self.title_text_ui(builder);
+ // });
+ // strip.cell(|ui| {
+ // if !Root::is_dual_panel_mode(frame) {
+ // ui.centered_and_justified(|ui| {
+ // View::title_button(ui, CARDHOLDER, || {
+ // Root::toggle_side_panel();
+ // });
+ // });
+ // }
+ // });
+ // });
}
/// Draw title text.
diff --git a/src/gui/views/network/node.rs b/src/gui/views/network/node.rs
index ebd33dc..f4ffc78 100644
--- a/src/gui/views/network/node.rs
+++ b/src/gui/views/network/node.rs
@@ -20,7 +20,7 @@ use crate::gui::Colors;
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
-use crate::gui::views::network::{NetworkContent, NetworkTab, NetworkTabType};
+use crate::gui::views::network::{Network, NetworkTab, NetworkTabType};
use crate::node::Node;
/// Integrated node tab content.
@@ -36,7 +36,7 @@ impl NetworkTab for NetworkNode {
let server_stats = Node::get_stats();
// Show message to enable node when it's not running.
if !Node::is_running() {
- NetworkContent::disabled_node_ui(ui);
+ Network::disabled_node_ui(ui);
return;
}
diff --git a/src/gui/views/network/settings.rs b/src/gui/views/network/settings.rs
index cc72775..676b827 100644
--- a/src/gui/views/network/settings.rs
+++ b/src/gui/views/network/settings.rs
@@ -19,11 +19,7 @@ use crate::gui::icons::ARROW_COUNTER_CLOCKWISE;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
use crate::gui::views::network::{NetworkTab, NetworkTabType};
-use crate::gui::views::network::setup::dandelion::DandelionSetup;
-use crate::gui::views::network::setup::node::NodeSetup;
-use crate::gui::views::network::setup::p2p::P2PSetup;
-use crate::gui::views::network::setup::pool::PoolSetup;
-use crate::gui::views::network::setup::stratum::StratumSetup;
+use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
use crate::node::{Node, NodeConfig};
/// Integrated node settings tab content.
diff --git a/src/gui/views/network/setup.rs b/src/gui/views/network/setup/mod.rs
similarity index 75%
rename from src/gui/views/network/setup.rs
rename to src/gui/views/network/setup/mod.rs
index 6dae75d..9770fca 100644
--- a/src/gui/views/network/setup.rs
+++ b/src/gui/views/network/setup/mod.rs
@@ -12,8 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-pub mod stratum;
-pub mod node;
-pub mod p2p;
-pub mod pool;
-pub mod dandelion;
\ No newline at end of file
+mod node;
+pub use node::NodeSetup;
+
+mod p2p;
+pub use p2p::P2PSetup;
+
+mod pool;
+pub use pool::PoolSetup;
+
+mod dandelion;
+pub use dandelion::DandelionSetup;
+
+mod stratum;
+pub use stratum::StratumSetup;
\ No newline at end of file
diff --git a/src/gui/views/network/setup/node.rs b/src/gui/views/network/setup/node.rs
index c10b4a2..8078011 100644
--- a/src/gui/views/network/setup/node.rs
+++ b/src/gui/views/network/setup/node.rs
@@ -21,7 +21,7 @@ use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{CLIPBOARD_TEXT, CLOCK_CLOCKWISE, COMPUTER_TOWER, COPY, PLUG, POWER, SHIELD, SHIELD_SLASH};
use crate::gui::platform::PlatformCallbacks;
-use crate::gui::views::{Modal, ModalPosition, NetworkContent, View};
+use crate::gui::views::{Modal, ModalPosition, Network, View};
use crate::gui::views::network::settings::NetworkSettings;
use crate::node::{Node, NodeConfig};
@@ -115,7 +115,7 @@ impl NodeSetup {
// Autorun node setup.
ui.vertical_centered(|ui| {
ui.add_space(6.0);
- NetworkContent::autorun_node_ui(ui);
+ Network::autorun_node_ui(ui);
if Node::is_running() {
ui.add_space(2.0);
ui.label(RichText::new(t!("network_settings.restart_node_required"))
diff --git a/src/gui/views/root.rs b/src/gui/views/root.rs
index db84446..52b4e17 100644
--- a/src/gui/views/root.rs
+++ b/src/gui/views/root.rs
@@ -14,84 +14,216 @@
use std::cmp::min;
use std::sync::atomic::{AtomicBool, Ordering};
+use egui::os::OperatingSystem;
+use egui::RichText;
use lazy_static::lazy_static;
-use crate::gui::App;
+use crate::gui::app::{get_left_display_cutout, get_right_display_cutout};
+use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
-use crate::gui::views::{AccountsContent, Modal, NetworkContent};
+use crate::gui::views::{Accounts, Modal, ModalContainer, Network, View};
+use crate::node::Node;
lazy_static! {
/// To check if side panel is open from any part of ui.
- static ref NETWORK_PANEL_OPEN: AtomicBool = AtomicBool::new(false);
+ static ref SIDE_PANEL_OPEN: AtomicBool = AtomicBool::new(false);
}
-/// Main ui content, handles network panel state modal state.
-#[derive(Default)]
+/// Contains main ui content, handles side panel state.
pub struct Root {
- network: NetworkContent,
- accounts: AccountsContent,
+ /// Side panel content.
+ side_panel: Network,
+ /// Central panel content.
+ central_content: Accounts,
+
+ /// Check if app exit is allowed on close event of [`eframe::App`] platform implementation.
+ pub(crate) exit_allowed: 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 Root {
+ fn default() -> Self {
+ // Exit from eframe only for non-mobile platforms.
+ let os = OperatingSystem::from_target_os();
+ let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
+ Self {
+ side_panel: Network::default(),
+ central_content: Accounts::default(),
+ exit_allowed,
+ show_exit_progress: false,
+ allowed_modal_ids: vec![
+ Self::EXIT_MODAL_ID
+ ],
+ }
+ }
+}
+
+impl ModalContainer for Root {
+ fn modal_ids(&self) -> &Vec<&'static str> {
+ &self.allowed_modal_ids
+ }
}
impl Root {
+ /// Identifier for exit confirmation [`Modal`].
+ pub const EXIT_MODAL_ID: &'static str = "exit_confirmation";
+
/// Default width of side panel at application UI.
pub const SIDE_PANEL_MIN_WIDTH: i64 = 400;
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
+ // Show opened exit confirmation Modal content.
+ if self.can_draw_modal() {
+ self.exit_modal_content(ui, frame, cb);
+ }
+
+ // Show network content on side panel.
let (is_panel_open, panel_width) = Self::side_panel_state_width(frame);
egui::SidePanel::left("network_panel")
.resizable(false)
.exact_width(panel_width)
- .frame(egui::Frame::default())
+ .frame(egui::Frame::none())
.show_animated_inside(ui, is_panel_open, |ui| {
- self.network.ui(ui, frame, cb);
+ self.side_panel.ui(ui, frame, cb);
});
+ // Show accounts content on central panel.
egui::CentralPanel::default()
- .frame(egui::Frame::default())
+ .frame(egui::Frame::none())
.show_inside(ui, |ui| {
- self.accounts.ui(ui, frame, cb);
+ self.central_content.ui(ui, frame, cb);
});
}
/// Get side panel state and width.
fn side_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
let dual_panel_mode = Self::is_dual_panel_mode(frame);
- let is_panel_open = dual_panel_mode || Self::is_network_panel_open();
+ let is_panel_open = dual_panel_mode || Self::is_side_panel_open();
+ let side_cutouts = get_left_display_cutout() + get_right_display_cutout();
let panel_width = if dual_panel_mode {
- min(frame.info().window_info.size.x as i64, Self::SIDE_PANEL_MIN_WIDTH) as f32
+ let available_width = (frame.info().window_info.size.x - side_cutouts) as i64;
+ min(available_width, Self::SIDE_PANEL_MIN_WIDTH) as f32
} else {
- frame.info().window_info.size.x
+ frame.info().window_info.size.x - side_cutouts
};
(is_panel_open, panel_width)
}
- /// Check if ui can show [`NetworkContent`] and [`AccountsContent`] at same time.
+ /// Check if ui can show [`Network`] and [`Accounts`] at same time.
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
let w = frame.info().window_info.size.x;
let h = frame.info().window_info.size.y;
// Screen is wide if width is greater than height or just 20% smaller.
let is_wide_screen = w > h || w + (w * 0.2) >= h;
// Dual panel mode is available when window is wide and its width is at least 2 times
- // greater than minimal width of the side panel.
- is_wide_screen && w >= Self::SIDE_PANEL_MIN_WIDTH as f32 * 2.0
+ // greater than minimal width of the side panel plus display cutouts from both sides.
+ let side_cutouts = get_left_display_cutout() + get_right_display_cutout();
+ is_wide_screen && w >= (Self::SIDE_PANEL_MIN_WIDTH as f32 * 2.0) + side_cutouts
}
/// Toggle [`Network`] panel state.
- pub fn toggle_network_panel() {
- let is_open = NETWORK_PANEL_OPEN.load(Ordering::Relaxed);
- NETWORK_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
+ pub fn toggle_side_panel() {
+ let is_open = SIDE_PANEL_OPEN.load(Ordering::Relaxed);
+ SIDE_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
}
/// Check if side panel is open.
- pub fn is_network_panel_open() -> bool {
- NETWORK_PANEL_OPEN.load(Ordering::Relaxed)
+ pub fn is_side_panel_open() -> bool {
+ SIDE_PANEL_OPEN.load(Ordering::Relaxed)
}
- /// Handle back button press event.
- fn on_back() {
+ /// Show exit confirmation modal.
+ pub fn show_exit_modal() {
+ let exit_modal = Modal::new(Self::EXIT_MODAL_ID).title(t!("modal_exit.exit"));
+ Modal::show(exit_modal);
+ }
+
+ /// Draw exit confirmation modal content.
+ fn exit_modal_content(&mut self,
+ ui: &mut egui::Ui,
+ frame: &mut eframe::Frame,
+ cb: &dyn PlatformCallbacks) {
+ 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();
+ }
+ OperatingSystem::IOS => {
+ //TODO: exit on iOS.
+ }
+ OperatingSystem::Nix | OperatingSystem::Mac | OperatingSystem::Windows => {
+ self.exit_allowed = true;
+ frame.close();
+ }
+ OperatingSystem::Unknown => {}
+ }
+ }
+
+ /// Handle platform-specific Back key code event.
+ pub fn on_back() {
if Modal::on_back() {
- App::show_exit_modal()
+ Self::show_exit_modal()
}
}
}
@@ -100,11 +232,14 @@ impl Root {
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
-/// Handle back button press event from Android.
-pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress(
+/// Handle Back key code event from Android.
+pub extern "C" fn Java_mw_gri_android_MainActivity_onBack(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
_activity: jni::objects::JObject,
) {
Root::on_back();
-}
\ No newline at end of file
+}
+
+
+
diff --git a/src/gui/views/title_panel.rs b/src/gui/views/title_panel.rs
index 68b8a64..64f6a91 100644
--- a/src/gui/views/title_panel.rs
+++ b/src/gui/views/title_panel.rs
@@ -30,20 +30,64 @@ impl TitleAction {
}
}
+/// Represents title content, can be text or callback to draw custom title.
+pub enum TitleContent {
+ Text(String),
+ /// First argument is identifier for panel.
+ Custom(String, Box)
+}
+
pub struct TitlePanel;
impl TitlePanel {
pub const DEFAULT_HEIGHT: f32 = 52.0;
+ pub fn test_ui(title: TitleContent, l: Option, r: Option, ui: &mut egui::Ui) {
+ let id = match &title {
+ TitleContent::Text(text) => Id::from(text.clone()),
+ TitleContent::Custom(text, _) => Id::from(text.clone())
+ };
+ egui::TopBottomPanel::top(id)
+ .resizable(false)
+ .exact_height(Self::DEFAULT_HEIGHT)
+ .frame(egui::Frame {
+ outer_margin: Margin::same(-1.0),
+ fill: Colors::YELLOW,
+ ..Default::default()
+ })
+ .show_inside(ui, |ui| {
+ StripBuilder::new(ui)
+ .size(Size::exact(Self::DEFAULT_HEIGHT))
+ .size(Size::remainder())
+ .size(Size::exact(Self::DEFAULT_HEIGHT))
+ .horizontal(|mut strip| {
+ strip.cell(|ui| {
+ Self::draw_action(ui, l);
+ });
+ strip.cell(|ui| {
+ match title {
+ TitleContent::Text(text) => {
+ Self::draw_title(ui, text);
+ }
+ TitleContent::Custom(_, cb) => {
+ (cb)(ui);
+ }
+ }
+ });
+ strip.cell(|ui| {
+ Self::draw_action(ui, r);
+ });
+ });
+ });
+ }
+
pub fn ui(title: String, l: Option, r: Option, ui: &mut egui::Ui) {
egui::TopBottomPanel::top(Id::from(title.clone()))
.resizable(false)
.exact_height(Self::DEFAULT_HEIGHT)
.frame(egui::Frame {
+ outer_margin: Margin::same(-1.0),
fill: Colors::YELLOW,
- inner_margin: Margin::same(0.0),
- outer_margin: Margin::same(0.0),
- stroke: egui::Stroke::NONE,
..Default::default()
})
.show_inside(ui, |ui| {
diff --git a/src/lib.rs b/src/lib.rs
index fc7892e..a345c74 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,12 +17,13 @@ extern crate rust_i18n;
use std::sync::Arc;
+use egui::{Context, Stroke};
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
pub use settings::{AppConfig, Settings};
-use crate::gui::{App, PlatformApp};
+use crate::gui::{Colors, PlatformApp};
use crate::gui::platform::PlatformCallbacks;
use crate::node::Node;
@@ -37,6 +38,7 @@ mod settings;
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[no_mangle]
+/// Android platform entry point.
fn android_main(app: AndroidApp) {
#[cfg(debug_assertions)]
{
@@ -54,7 +56,7 @@ fn android_main(app: AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
let mut options = eframe::NativeOptions::default();
- // Use limits are guaranteed to be compatible with Android devices.
+ // Setup limits that are guaranteed to be compatible with Android devices.
options.wgpu_options.device_descriptor = Arc::new(|adapter| {
let base_limits = wgpu::Limits::downlevel_webgl2_defaults();
wgpu::DeviceDescriptor {
@@ -73,16 +75,17 @@ fn android_main(app: AndroidApp) {
start(options, app_creator(PlatformApp::new(platform)));
}
+/// [`PlatformApp`] setup for [`eframe`].
pub fn app_creator(app: PlatformApp) -> eframe::AppCreator
where PlatformApp: eframe::App, T: PlatformCallbacks {
Box::new(|cc| {
- App::setup_visuals(&cc.egui_ctx);
- App::setup_fonts(&cc.egui_ctx);
- //TODO: Setup storage
+ setup_visuals(&cc.egui_ctx);
+ setup_fonts(&cc.egui_ctx);
Box::new(app)
})
}
+/// Entry point to start ui with [`eframe`].
pub fn start(mut options: eframe::NativeOptions, app_creator: eframe::AppCreator) {
options.default_theme = eframe::Theme::Light;
options.renderer = eframe::Renderer::Wgpu;
@@ -97,13 +100,97 @@ pub fn start(mut options: eframe::NativeOptions, app_creator: eframe::AppCreator
let _ = eframe::run_native("Grim", options, app_creator);
}
+/// Setup application [`egui::Style`] and [`egui::Visuals`].
+pub fn setup_visuals(ctx: &Context) {
+ let mut style = (*ctx.style()).clone();
+ // Setup spacing for buttons.
+ style.spacing.button_padding = egui::vec2(12.0, 8.0);
+ // Make scroll-bar thinner.
+ style.spacing.scroll_bar_width = 4.0;
+ // Disable spacing between items.
+ style.spacing.item_spacing = egui::vec2(0.0, 0.0);
+ // Setup radio button/checkbox size and spacing.
+ style.spacing.icon_width = 24.0;
+ style.spacing.icon_width_inner = 14.0;
+ style.spacing.icon_spacing = 10.0;
+ // Setup style
+ ctx.set_style(style);
+
+ let mut visuals = egui::Visuals::light();
+ // Setup selection color.
+ visuals.selection.stroke = Stroke { width: 1.0, color: Colors::TEXT };
+ visuals.selection.bg_fill = Colors::GOLD;
+ // Disable stroke around panels by default
+ visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
+ // Setup visuals
+ ctx.set_visuals(visuals);
+}
+
+/// Setup application fonts.
+pub fn setup_fonts(ctx: &Context) {
+ use egui::FontFamily::Proportional;
+
+ let mut fonts = egui::FontDefinitions::default();
+
+ fonts.font_data.insert(
+ "phosphor".to_owned(),
+ egui::FontData::from_static(include_bytes!(
+ "../fonts/phosphor.ttf"
+ )).tweak(egui::FontTweak {
+ scale: 1.0,
+ y_offset_factor: -0.30,
+ y_offset: 0.0,
+ baseline_offset_factor: 0.30,
+ }),
+ );
+ fonts
+ .families
+ .entry(Proportional)
+ .or_default()
+ .insert(0, "phosphor".to_owned());
+
+ fonts.font_data.insert(
+ "noto".to_owned(),
+ egui::FontData::from_static(include_bytes!(
+ "../fonts/noto_sc_reg.otf"
+ )).tweak(egui::FontTweak {
+ scale: 1.0,
+ y_offset_factor: -0.25,
+ y_offset: 0.0,
+ baseline_offset_factor: 0.17,
+ }),
+ );
+ fonts
+ .families
+ .entry(Proportional)
+ .or_default()
+ .insert(0, "noto".to_owned());
+
+ ctx.set_fonts(fonts);
+
+ use egui::FontId;
+ use egui::TextStyle::*;
+
+ let mut style = (*ctx.style()).clone();
+ style.text_styles = [
+ (Heading, FontId::new(20.0, Proportional)),
+ (Body, FontId::new(16.0, Proportional)),
+ (Button, FontId::new(18.0, Proportional)),
+ (Small, FontId::new(12.0, Proportional)),
+ (Monospace, FontId::new(16.0, Proportional)),
+ ].into();
+
+ ctx.set_style(style);
+}
+
+/// Setup translations.
fn setup_i18n() {
const DEFAULT_LOCALE: &str = "en";
let locale = sys_locale::get_locale().unwrap_or(String::from(DEFAULT_LOCALE));
let locale_str = if locale.contains("-") {
locale.split("-").next().unwrap_or(DEFAULT_LOCALE)
} else {
- DEFAULT_LOCALE
+ locale.as_str()
};
if _rust_i18n_available_locales().contains(&locale_str) {
rust_i18n::set_locale(locale_str);