android + ui: display cutouts (insets) refactoring, optimize platform-specific app, reorganize views
This commit is contained in:
parent
f85f4c9ed7
commit
dbe178f792
20 changed files with 541 additions and 508 deletions
|
@ -1,15 +1,15 @@
|
||||||
package mw.gri.android;
|
package mw.gri.android;
|
||||||
|
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.hardware.SensorManager;
|
|
||||||
import android.os.Bundle;
|
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 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 com.google.androidgamesdk.GameActivity;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -45,46 +45,40 @@ public class MainActivity extends GameActivity {
|
||||||
}
|
}
|
||||||
super.onCreate(null);
|
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.
|
// Register receiver to finish activity from the BackgroundService.
|
||||||
registerReceiver(mBroadcastReceiver, new IntentFilter(FINISH_ACTIVITY_ACTION));
|
registerReceiver(mBroadcastReceiver, new IntentFilter(FINISH_ACTIVITY_ACTION));
|
||||||
|
|
||||||
// Start notification service.
|
// Start notification service.
|
||||||
BackgroundService.start(this);
|
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.
|
// Implemented into native code to handle display cutouts change.
|
||||||
native void onDisplayCutoutsChanged(int[] cutouts);
|
native void onDisplayCutouts(int[] cutouts);
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
// Update display cutouts.
|
|
||||||
onDisplayCutoutsChanged(Utils.getDisplayCutouts(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
onBackButtonPress();
|
onBack();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implemented into native code to handle back button press.
|
// Implemented into native code to handle key code BACK event.
|
||||||
public native void onBackButtonPress();
|
public native void onBack();
|
||||||
|
|
||||||
private boolean mManualExit;
|
private boolean mManualExit;
|
||||||
private final AtomicBoolean mActivityDestroyed = new AtomicBoolean(false);
|
private final AtomicBoolean mActivityDestroyed = new AtomicBoolean(false);
|
||||||
|
|
|
@ -1,52 +1,10 @@
|
||||||
package mw.gri.android;
|
package mw.gri.android;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
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 class Utils {
|
||||||
|
// Convert Pixels to DensityPixels
|
||||||
public static int[] getDisplayCutouts(Activity context) {
|
public static int pxToDp(int px, Context 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) {
|
|
||||||
return (int) (px / context.getResources().getDisplayMetrics().density);
|
return (int) (px / context.getResources().getDisplayMetrics().density);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<style name="Theme.Main" parent="Theme.AppCompat.NoActionBar">
|
<style name="Theme.Main" parent="Theme.AppCompat.NoActionBar">
|
||||||
<item name="android:statusBarColor">@color/yellow</item>
|
<item name="android:statusBarColor">@color/yellow</item>
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
<item name="android:navigationBarColor">@color/black</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
332
src/gui/app.rs
332
src/gui/app.rs
|
@ -12,237 +12,147 @@
|
||||||
// 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, RichText, Stroke};
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
use egui::os::OperatingSystem;
|
|
||||||
|
use egui::Context;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, ModalContainer, Root, View};
|
use crate::gui::views::Root;
|
||||||
use crate::node::Node;
|
|
||||||
|
|
||||||
/// To be implemented at platform-specific module.
|
/// Implements ui entry point and contains platform-specific callbacks.
|
||||||
pub struct PlatformApp<Platform> {
|
pub struct PlatformApp<Platform> {
|
||||||
pub(crate) app: App,
|
/// Platform specific callbacks handler.
|
||||||
pub(crate) platform: Platform,
|
pub(crate) platform: Platform,
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains main ui, handles exit and visual style setup.
|
|
||||||
pub struct App {
|
|
||||||
/// Main ui content.
|
/// Main ui content.
|
||||||
root: Root,
|
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>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl<Platform> PlatformApp<Platform> {
|
||||||
fn default() -> Self {
|
pub fn new(platform: Platform) -> Self {
|
||||||
let os = OperatingSystem::from_target_os();
|
Self { platform, root: Root::default() }
|
||||||
// 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 ModalContainer for App {
|
impl<Platform: PlatformCallbacks> eframe::App for PlatformApp<Platform> {
|
||||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
|
||||||
&self.allowed_modal_ids
|
// Show panels to support display cutouts (insets).
|
||||||
}
|
padding_panels(ctx);
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
// Show main content.
|
||||||
/// 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) {
|
|
||||||
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 modal content if it's open.
|
self.root.ui(ui, frame, &self.platform);
|
||||||
if self.can_draw_modal() {
|
|
||||||
self.exit_modal_content(ui, frame, cb);
|
|
||||||
}
|
|
||||||
// Draw main content.
|
|
||||||
self.root.ui(ui, frame, cb);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw exit confirmation modal content.
|
fn on_close_event(&mut self) -> bool {
|
||||||
fn exit_modal_content(&mut self,
|
Root::show_exit_modal();
|
||||||
ui: &mut egui::Ui,
|
self.root.exit_allowed
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
pub use app::{App, PlatformApp};
|
pub use app::PlatformApp;
|
||||||
|
|
||||||
mod colors;
|
mod colors;
|
||||||
pub use colors::Colors;
|
pub use colors::Colors;
|
||||||
|
|
|
@ -12,11 +12,7 @@
|
||||||
// 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 std::sync::atomic::{AtomicI32, Ordering};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use winit::platform::android::activity::AndroidApp;
|
use winit::platform::android::activity::AndroidApp;
|
||||||
|
|
||||||
use crate::gui::{App, PlatformApp};
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -90,94 +86,3 @@ impl PlatformCallbacks for Android {
|
||||||
env.call_method(activity, "onExit", "()V", &[]).unwrap();
|
env.call_method(activity, "onExit", "()V", &[]).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformApp<Android> {
|
|
||||||
pub fn new(platform: Android) -> Self {
|
|
||||||
Self {
|
|
||||||
app: App::default(),
|
|
||||||
platform,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for PlatformApp<Android> {
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -31,23 +31,3 @@ impl PlatformCallbacks for Desktop {
|
||||||
|
|
||||||
fn exit(&self) {}
|
fn exit(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformApp<Desktop> {
|
|
||||||
pub fn new(platform: Desktop) -> Self {
|
|
||||||
Self {
|
|
||||||
app: App::default(),
|
|
||||||
platform,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for PlatformApp<Desktop> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,13 +17,13 @@ use crate::gui::icons::{GLOBE, PLUS};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Root, TitleAction, TitlePanel, View};
|
use crate::gui::views::{Root, TitleAction, TitlePanel, View};
|
||||||
|
|
||||||
/// Accounts central panel content.
|
/// Accounts content.
|
||||||
pub struct AccountsContent {
|
pub struct Accounts {
|
||||||
/// List of accounts.
|
/// List of accounts.
|
||||||
list: Vec<String>
|
list: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AccountsContent {
|
impl Default for Accounts {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list: vec![],
|
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) {
|
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) {
|
TitlePanel::ui(t!("accounts.title"), if !Root::is_dual_panel_mode(frame) {
|
||||||
TitleAction::new(GLOBE, || {
|
TitleAction::new(GLOBE, || {
|
||||||
Root::toggle_network_panel();
|
Root::toggle_side_panel();
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
|
@ -12,5 +12,5 @@
|
||||||
// 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.
|
||||||
|
|
||||||
mod content;
|
mod accounts;
|
||||||
pub use content::*;
|
pub use accounts::*;
|
|
@ -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::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::network::{NetworkTab, NetworkTabType};
|
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;
|
use crate::node::Node;
|
||||||
|
|
||||||
/// Chain metrics tab content.
|
/// Chain metrics tab content.
|
||||||
|
@ -40,7 +40,7 @@ impl NetworkTab for NetworkMetrics {
|
||||||
let server_stats = Node::get_stats();
|
let server_stats = Node::get_stats();
|
||||||
// Show message to enable node when it's not running.
|
// Show message to enable node when it's not running.
|
||||||
if !Node::is_running() {
|
if !Node::is_running() {
|
||||||
NetworkContent::disabled_node_ui(ui);
|
Network::disabled_node_ui(ui);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@ use grin_servers::WorkerStats;
|
||||||
use crate::gui::Colors;
|
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::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::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::{NetworkTab, NetworkTabType};
|
||||||
use crate::gui::views::network::setup::stratum::StratumSetup;
|
use crate::gui::views::network::setup::StratumSetup;
|
||||||
use crate::node::{Node, NodeConfig};
|
use crate::node::{Node, NodeConfig};
|
||||||
|
|
||||||
/// Mining tab content.
|
/// Mining tab content.
|
||||||
|
@ -41,7 +41,7 @@ impl NetworkTab for NetworkMining {
|
||||||
|
|
||||||
// Show message to enable node when it's not running.
|
// Show message to enable node when it's not running.
|
||||||
if !Node::is_running() {
|
if !Node::is_running() {
|
||||||
NetworkContent::disabled_node_ui(ui);
|
Network::disabled_node_ui(ui);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,20 @@
|
||||||
// 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.
|
||||||
|
|
||||||
mod content;
|
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod mining;
|
pub use metrics::*;
|
||||||
mod settings;
|
|
||||||
mod node;
|
|
||||||
mod setup;
|
|
||||||
|
|
||||||
pub use content::*;
|
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::*;
|
|
@ -21,16 +21,8 @@ use crate::AppConfig;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER};
|
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, ModalContainer, Root, TitlePanel, View};
|
use crate::gui::views::{Modal, ModalContainer, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitleAction, TitleContent, TitlePanel, View};
|
||||||
use crate::gui::views::network::setup::dandelion::DandelionSetup;
|
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
||||||
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::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
pub trait NetworkTab {
|
pub trait NetworkTab {
|
||||||
|
@ -59,15 +51,15 @@ impl NetworkTabType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Network side panel content.
|
/// Network content.
|
||||||
pub struct NetworkContent {
|
pub struct Network {
|
||||||
/// Current tab view to show at ui.
|
/// Current tab view to show at ui.
|
||||||
current_tab: Box<dyn NetworkTab>,
|
current_tab: Box<dyn NetworkTab>,
|
||||||
/// [`Modal`] ids allowed at this ui container.
|
/// [`Modal`] ids allowed at this ui container.
|
||||||
modal_ids: Vec<&'static str>,
|
modal_ids: Vec<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NetworkContent {
|
impl Default for Network {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current_tab: Box::new(NetworkNode::default()),
|
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> {
|
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||||
self.modal_ids.as_ref()
|
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) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||||
// Show modal content if it's opened.
|
// Show modal content if it's opened.
|
||||||
if self.can_draw_modal() {
|
if self.can_draw_modal() {
|
||||||
|
@ -140,7 +132,8 @@ impl NetworkContent {
|
||||||
|
|
||||||
egui::TopBottomPanel::bottom("network_tabs")
|
egui::TopBottomPanel::bottom("network_tabs")
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
outer_margin: Margin::same(4.0),
|
fill: Colors::FILL,
|
||||||
|
inner_margin: Margin::same(4.0),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
|
@ -201,31 +194,43 @@ impl NetworkContent {
|
||||||
|
|
||||||
/// Draw title content.
|
/// Draw title content.
|
||||||
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||||
StripBuilder::new(ui)
|
let title_content = TitleContent::Custom("network_title".to_string(), Box::new(|ui| {
|
||||||
.size(Size::exact(52.0))
|
}));
|
||||||
.size(Size::remainder())
|
TitlePanel::test_ui(title_content, TitleAction::new(DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||||
.size(Size::exact(52.0))
|
//TODO: Show connections
|
||||||
.horizontal(|mut strip| {
|
}), if !Root::is_dual_panel_mode(frame) {
|
||||||
strip.cell(|ui| {
|
TitleAction::new(CARDHOLDER, || {
|
||||||
ui.centered_and_justified(|ui| {
|
Root::toggle_side_panel();
|
||||||
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
})
|
||||||
//TODO: Show connections
|
} else {
|
||||||
});
|
None
|
||||||
});
|
}, ui);
|
||||||
});
|
|
||||||
strip.strip(|builder| {
|
// StripBuilder::new(ui)
|
||||||
self.title_text_ui(builder);
|
// .size(Size::exact(52.0))
|
||||||
});
|
// .size(Size::remainder())
|
||||||
strip.cell(|ui| {
|
// .size(Size::exact(52.0))
|
||||||
if !Root::is_dual_panel_mode(frame) {
|
// .horizontal(|mut strip| {
|
||||||
ui.centered_and_justified(|ui| {
|
// strip.cell(|ui| {
|
||||||
View::title_button(ui, CARDHOLDER, || {
|
// ui.centered_and_justified(|ui| {
|
||||||
Root::toggle_network_panel();
|
// 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.
|
/// Draw title text.
|
|
@ -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::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, View};
|
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;
|
use crate::node::Node;
|
||||||
|
|
||||||
/// Integrated node tab content.
|
/// Integrated node tab content.
|
||||||
|
@ -36,7 +36,7 @@ impl NetworkTab for NetworkNode {
|
||||||
let server_stats = Node::get_stats();
|
let server_stats = Node::get_stats();
|
||||||
// Show message to enable node when it's not running.
|
// Show message to enable node when it's not running.
|
||||||
if !Node::is_running() {
|
if !Node::is_running() {
|
||||||
NetworkContent::disabled_node_ui(ui);
|
Network::disabled_node_ui(ui);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,7 @@ use crate::gui::icons::ARROW_COUNTER_CLOCKWISE;
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, ModalPosition, View};
|
use crate::gui::views::{Modal, ModalPosition, View};
|
||||||
use crate::gui::views::network::{NetworkTab, NetworkTabType};
|
use crate::gui::views::network::{NetworkTab, NetworkTabType};
|
||||||
use crate::gui::views::network::setup::dandelion::DandelionSetup;
|
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
||||||
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::node::{Node, NodeConfig};
|
use crate::node::{Node, NodeConfig};
|
||||||
|
|
||||||
/// Integrated node settings tab content.
|
/// Integrated node settings tab content.
|
||||||
|
|
|
@ -12,8 +12,17 @@
|
||||||
// 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.
|
||||||
|
|
||||||
pub mod stratum;
|
mod node;
|
||||||
pub mod node;
|
pub use node::NodeSetup;
|
||||||
pub mod p2p;
|
|
||||||
pub mod pool;
|
mod p2p;
|
||||||
pub mod dandelion;
|
pub use p2p::P2PSetup;
|
||||||
|
|
||||||
|
mod pool;
|
||||||
|
pub use pool::PoolSetup;
|
||||||
|
|
||||||
|
mod dandelion;
|
||||||
|
pub use dandelion::DandelionSetup;
|
||||||
|
|
||||||
|
mod stratum;
|
||||||
|
pub use stratum::StratumSetup;
|
|
@ -21,7 +21,7 @@ use crate::AppConfig;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{CLIPBOARD_TEXT, CLOCK_CLOCKWISE, COMPUTER_TOWER, COPY, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
use crate::gui::icons::{CLIPBOARD_TEXT, CLOCK_CLOCKWISE, COMPUTER_TOWER, COPY, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
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::gui::views::network::settings::NetworkSettings;
|
||||||
use crate::node::{Node, NodeConfig};
|
use crate::node::{Node, NodeConfig};
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ impl NodeSetup {
|
||||||
// Autorun node setup.
|
// Autorun node setup.
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
NetworkContent::autorun_node_ui(ui);
|
Network::autorun_node_ui(ui);
|
||||||
if Node::is_running() {
|
if Node::is_running() {
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
||||||
|
|
|
@ -14,84 +14,216 @@
|
||||||
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use egui::os::OperatingSystem;
|
||||||
|
use egui::RichText;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
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::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{AccountsContent, Modal, NetworkContent};
|
use crate::gui::views::{Accounts, Modal, ModalContainer, Network, View};
|
||||||
|
use crate::node::Node;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// To check if side panel is open from any part of ui.
|
/// 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.
|
/// Contains main ui content, handles side panel state.
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
network: NetworkContent,
|
/// Side panel content.
|
||||||
accounts: AccountsContent,
|
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 {
|
impl Root {
|
||||||
|
/// Identifier for exit confirmation [`Modal`].
|
||||||
|
pub const EXIT_MODAL_ID: &'static str = "exit_confirmation";
|
||||||
|
|
||||||
/// Default width of side panel at application UI.
|
/// Default width of side panel at application UI.
|
||||||
pub const SIDE_PANEL_MIN_WIDTH: i64 = 400;
|
pub const SIDE_PANEL_MIN_WIDTH: i64 = 400;
|
||||||
|
|
||||||
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) {
|
||||||
|
// 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);
|
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)
|
||||||
.exact_width(panel_width)
|
.exact_width(panel_width)
|
||||||
.frame(egui::Frame::default())
|
.frame(egui::Frame::none())
|
||||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
.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()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame::default())
|
.frame(egui::Frame::none())
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
self.accounts.ui(ui, frame, cb);
|
self.central_content.ui(ui, frame, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get side panel state and width.
|
/// Get side panel state and width.
|
||||||
fn side_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
|
fn side_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
|
||||||
let dual_panel_mode = Self::is_dual_panel_mode(frame);
|
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 {
|
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 {
|
} else {
|
||||||
frame.info().window_info.size.x
|
frame.info().window_info.size.x - side_cutouts
|
||||||
};
|
};
|
||||||
(is_panel_open, panel_width)
|
(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 {
|
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
|
||||||
let w = frame.info().window_info.size.x;
|
let w = frame.info().window_info.size.x;
|
||||||
let h = frame.info().window_info.size.y;
|
let h = frame.info().window_info.size.y;
|
||||||
// Screen is wide if width is greater than height or just 20% smaller.
|
// Screen is wide if width is greater than height or just 20% smaller.
|
||||||
let is_wide_screen = w > h || w + (w * 0.2) >= h;
|
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
|
// 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.
|
// greater than minimal width of the side panel plus display cutouts from both sides.
|
||||||
is_wide_screen && w >= Self::SIDE_PANEL_MIN_WIDTH as f32 * 2.0
|
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.
|
/// Toggle [`Network`] panel state.
|
||||||
pub fn toggle_network_panel() {
|
pub fn toggle_side_panel() {
|
||||||
let is_open = NETWORK_PANEL_OPEN.load(Ordering::Relaxed);
|
let is_open = SIDE_PANEL_OPEN.load(Ordering::Relaxed);
|
||||||
NETWORK_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
|
SIDE_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if side panel is open.
|
/// Check if side panel is open.
|
||||||
pub fn is_network_panel_open() -> bool {
|
pub fn is_side_panel_open() -> bool {
|
||||||
NETWORK_PANEL_OPEN.load(Ordering::Relaxed)
|
SIDE_PANEL_OPEN.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle back button press event.
|
/// Show exit confirmation modal.
|
||||||
fn on_back() {
|
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() {
|
if Modal::on_back() {
|
||||||
App::show_exit_modal()
|
Self::show_exit_modal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,11 +232,14 @@ impl Root {
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
/// Handle back button press event from Android.
|
/// Handle Back key code event from Android.
|
||||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onBackButtonPress(
|
pub extern "C" fn Java_mw_gri_android_MainActivity_onBack(
|
||||||
_env: jni::JNIEnv,
|
_env: jni::JNIEnv,
|
||||||
_class: jni::objects::JObject,
|
_class: jni::objects::JObject,
|
||||||
_activity: jni::objects::JObject,
|
_activity: jni::objects::JObject,
|
||||||
) {
|
) {
|
||||||
Root::on_back();
|
Root::on_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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<dyn Fn(&mut egui::Ui)>)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TitlePanel;
|
pub struct TitlePanel;
|
||||||
|
|
||||||
impl TitlePanel {
|
impl TitlePanel {
|
||||||
pub const DEFAULT_HEIGHT: f32 = 52.0;
|
pub const DEFAULT_HEIGHT: f32 = 52.0;
|
||||||
|
|
||||||
|
pub fn test_ui(title: TitleContent, l: Option<TitleAction>, r: Option<TitleAction>, 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<TitleAction>, r: Option<TitleAction>, ui: &mut egui::Ui) {
|
pub fn ui(title: String, l: Option<TitleAction>, r: Option<TitleAction>, ui: &mut egui::Ui) {
|
||||||
egui::TopBottomPanel::top(Id::from(title.clone()))
|
egui::TopBottomPanel::top(Id::from(title.clone()))
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.exact_height(Self::DEFAULT_HEIGHT)
|
.exact_height(Self::DEFAULT_HEIGHT)
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
|
outer_margin: Margin::same(-1.0),
|
||||||
fill: Colors::YELLOW,
|
fill: Colors::YELLOW,
|
||||||
inner_margin: Margin::same(0.0),
|
|
||||||
outer_margin: Margin::same(0.0),
|
|
||||||
stroke: egui::Stroke::NONE,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -17,12 +17,13 @@ extern crate rust_i18n;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use egui::{Context, Stroke};
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use winit::platform::android::activity::AndroidApp;
|
use winit::platform::android::activity::AndroidApp;
|
||||||
|
|
||||||
pub use settings::{AppConfig, Settings};
|
pub use settings::{AppConfig, Settings};
|
||||||
|
|
||||||
use crate::gui::{App, PlatformApp};
|
use crate::gui::{Colors, PlatformApp};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ mod settings;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
/// Android platform entry point.
|
||||||
fn android_main(app: AndroidApp) {
|
fn android_main(app: AndroidApp) {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
|
@ -54,7 +56,7 @@ fn android_main(app: AndroidApp) {
|
||||||
|
|
||||||
use winit::platform::android::EventLoopBuilderExtAndroid;
|
use winit::platform::android::EventLoopBuilderExtAndroid;
|
||||||
let mut options = eframe::NativeOptions::default();
|
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| {
|
options.wgpu_options.device_descriptor = Arc::new(|adapter| {
|
||||||
let base_limits = wgpu::Limits::downlevel_webgl2_defaults();
|
let base_limits = wgpu::Limits::downlevel_webgl2_defaults();
|
||||||
wgpu::DeviceDescriptor {
|
wgpu::DeviceDescriptor {
|
||||||
|
@ -73,16 +75,17 @@ fn android_main(app: AndroidApp) {
|
||||||
start(options, app_creator(PlatformApp::new(platform)));
|
start(options, app_creator(PlatformApp::new(platform)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [`PlatformApp`] setup for [`eframe`].
|
||||||
pub fn app_creator<T: 'static>(app: PlatformApp<T>) -> eframe::AppCreator
|
pub fn app_creator<T: 'static>(app: PlatformApp<T>) -> eframe::AppCreator
|
||||||
where PlatformApp<T>: eframe::App, T: PlatformCallbacks {
|
where PlatformApp<T>: eframe::App, T: PlatformCallbacks {
|
||||||
Box::new(|cc| {
|
Box::new(|cc| {
|
||||||
App::setup_visuals(&cc.egui_ctx);
|
setup_visuals(&cc.egui_ctx);
|
||||||
App::setup_fonts(&cc.egui_ctx);
|
setup_fonts(&cc.egui_ctx);
|
||||||
//TODO: Setup storage
|
|
||||||
Box::new(app)
|
Box::new(app)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Entry point to start ui with [`eframe`].
|
||||||
pub fn start(mut options: eframe::NativeOptions, app_creator: eframe::AppCreator) {
|
pub fn start(mut options: eframe::NativeOptions, app_creator: eframe::AppCreator) {
|
||||||
options.default_theme = eframe::Theme::Light;
|
options.default_theme = eframe::Theme::Light;
|
||||||
options.renderer = eframe::Renderer::Wgpu;
|
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);
|
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() {
|
fn setup_i18n() {
|
||||||
const DEFAULT_LOCALE: &str = "en";
|
const DEFAULT_LOCALE: &str = "en";
|
||||||
let locale = sys_locale::get_locale().unwrap_or(String::from(DEFAULT_LOCALE));
|
let locale = sys_locale::get_locale().unwrap_or(String::from(DEFAULT_LOCALE));
|
||||||
let locale_str = if locale.contains("-") {
|
let locale_str = if locale.contains("-") {
|
||||||
locale.split("-").next().unwrap_or(DEFAULT_LOCALE)
|
locale.split("-").next().unwrap_or(DEFAULT_LOCALE)
|
||||||
} else {
|
} else {
|
||||||
DEFAULT_LOCALE
|
locale.as_str()
|
||||||
};
|
};
|
||||||
if _rust_i18n_available_locales().contains(&locale_str) {
|
if _rust_i18n_available_locales().contains(&locale_str) {
|
||||||
rust_i18n::set_locale(locale_str);
|
rust_i18n::set_locale(locale_str);
|
||||||
|
|
Loading…
Reference in a new issue