gui: fix button touch, add icons, optimize screen panels (in progress)
729
Cargo.lock
generated
16
Cargo.toml
|
@ -29,16 +29,22 @@ openssl-sys = { version = "0.9.82", features = ["vendored"] }
|
|||
#grin_wallet_config = "5.1.0"
|
||||
#grin_wallet_util = "5.1.0"
|
||||
|
||||
egui = "0.20.1"
|
||||
## ui
|
||||
pollster = "0.3.0"
|
||||
tracing-subscriber = "0.3"
|
||||
egui_demo_lib = "0.20.0"
|
||||
|
||||
wgpu = "0.14.0"
|
||||
egui = "0.20.1"
|
||||
egui_extras = { version = "0.20.0", features = [ "svg" ] }
|
||||
eframe = { version = "0.20.1", features = [ "wgpu" ] }
|
||||
egui_demo_lib = "0.20.0"
|
||||
|
||||
## grin_servers
|
||||
futures = "0.3"
|
||||
|
||||
## other
|
||||
once_cell = "1.10.0"
|
||||
rust-i18n = "1.1.4"
|
||||
sys-locale = "0.3.0"
|
||||
|
||||
[patch.crates-io]
|
||||
winit = { git = "https://github.com/rib/winit", branch = "android-activity" }
|
||||
|
||||
|
@ -55,5 +61,5 @@ jni = "0.21.1"
|
|||
winit = { version = "0.27.2", features = [ "android-game-activity" ] }
|
||||
|
||||
[lib]
|
||||
name="grin_android"
|
||||
name="grim_android"
|
||||
crate_type=["cdylib"]
|
|
@ -8,10 +8,10 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "mw.gri.android"
|
||||
minSdk 27
|
||||
targetSdk 33
|
||||
minSdk 24
|
||||
targetSdk 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "0.1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -32,9 +32,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
|
||||
// To use the Android Frame Pacing library
|
||||
//implementation "androidx.games:games-frame-pacing:1.9.1"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="Grin"
|
||||
android:label="Grim"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Main">
|
||||
|
@ -22,7 +22,7 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="grin_android" />
|
||||
<meta-data android:name="android.app.lib_name" android:value="grim_android" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 48 KiB |
|
@ -8,7 +8,7 @@ import com.google.androidgamesdk.GameActivity;
|
|||
public class MainActivity extends GameActivity {
|
||||
|
||||
static {
|
||||
System.loadLibrary("grin_android");
|
||||
System.loadLibrary("grim_android");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -1,170 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 7.6 KiB |
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FCEF03</color>
|
||||
</resources>
|
|
@ -1,17 +1,5 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Main" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorPrimary">@color/yellow</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
|
||||
<!-- <item name="android:statusBarColor">?attr/colorPrimary</item>-->
|
||||
<!-- <item name="android:windowLightStatusBar">true</item>-->
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
|
||||
<!-- <item name="android:navigationBarColor">?attr/colorPrimary</item>-->
|
||||
<!-- <item name="android:windowLightNavigationBar">true</item>-->
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -1,10 +1,11 @@
|
|||
export CPPFLAGS="-DMDB_USE_ROBUST=0" && export CFLAGS="-DMDB_USE_ROBUST=0" && cargo ndk -t arm64-v8a build
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
yes | cp -f target/aarch64-linux-android/debug/libgrin_android.so app/src/main/jniLibs/arm64-v8a
|
||||
yes | cp -f target/aarch64-linux-android/debug/libgrim_android.so app/src/main/jniLibs/arm64-v8a
|
||||
./gradlew clean
|
||||
./gradlew build
|
||||
#./gradlew installDebug
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
sleep 1s
|
||||
adb shell am start -n mw.gri.android/.MainActivity
|
||||
fi
|
1
locales/en.yml
Normal file
|
@ -0,0 +1 @@
|
|||
wallets: Wallets
|
1
locales/ru.yml
Normal file
|
@ -0,0 +1 @@
|
|||
wallets: Кошельки
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Grin Developers
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,8 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Build hooks to spit out version+build time info
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
|
17
src/grim.rs
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Grin Developers
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -59,10 +59,25 @@ fn main() {
|
|||
}
|
||||
|
||||
fn start(mut options: NativeOptions, app_creator: AppCreator) {
|
||||
setup_i18n();
|
||||
|
||||
options.renderer = Renderer::Wgpu;
|
||||
eframe::run_native("Grim", options, app_creator);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
if crate::available_locales().contains(&locale_str) {
|
||||
rust_i18n::set_locale(locale_str);
|
||||
}
|
||||
}
|
||||
|
||||
mod built_info {
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
}
|
||||
|
|
145
src/gui/app.rs
|
@ -13,8 +13,14 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeSet;
|
||||
use eframe::emath::Align;
|
||||
use eframe::Frame;
|
||||
use egui::Context;
|
||||
use egui::{Color32, Context, Id, Layout, RichText, Sense, Separator, Stroke, Ui, Widget};
|
||||
use egui::epaint::Shadow;
|
||||
use egui::panel::PanelState;
|
||||
use egui::style::Margin;
|
||||
use wgpu::Color;
|
||||
use crate::gui::PlatformCallbacks;
|
||||
use crate::gui::screens::{Screen};
|
||||
|
||||
|
@ -25,7 +31,8 @@ pub struct PlatformApp<Platform> {
|
|||
|
||||
pub struct Screens {
|
||||
screens: Vec<Box<dyn Screen>>,
|
||||
current: String,
|
||||
navigation: BTreeSet<String>,
|
||||
menu_open: bool,
|
||||
}
|
||||
|
||||
impl Default for Screens {
|
||||
|
@ -37,15 +44,133 @@ impl Default for Screens {
|
|||
}
|
||||
|
||||
impl Screens {
|
||||
pub fn from_screens(screens: Vec<Box<dyn Screen>>) -> Self {
|
||||
let current = screens[0].name().to_string();
|
||||
Self {
|
||||
screens,
|
||||
current,
|
||||
fn from_screens(screens: Vec<Box<dyn Screen>>) -> Self {
|
||||
let current = screens[0].name().to_owned();
|
||||
let mut navigation = BTreeSet::new();
|
||||
navigation.insert(current);
|
||||
Self { screens, navigation, menu_open: false }
|
||||
}
|
||||
|
||||
fn show_screens(&mut self, ui: &mut Ui, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
let Self { screens, navigation, .. } = self;
|
||||
for screen in screens {
|
||||
let id = screen.name();
|
||||
let show = navigation.contains(id.as_str());
|
||||
if show {
|
||||
screen.show(ui, frame, cb);
|
||||
}
|
||||
Self::set_show_screen(navigation, id, show);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ctx: &Context, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
|
||||
fn set_show_screen(navigation: &mut BTreeSet<String>, key: String, show: bool) {
|
||||
if show {
|
||||
if !navigation.contains(key.as_str()) {
|
||||
navigation.insert(key.to_owned());
|
||||
}
|
||||
} else {
|
||||
navigation.remove(key.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_screen_title(&self) -> &String {
|
||||
self.navigation.last().unwrap()
|
||||
}
|
||||
|
||||
fn menu_is_open(&mut self, frame: &mut Frame) -> bool {
|
||||
return self.menu_open || is_landscape(frame);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
let menu_open = self.menu_is_open(frame);
|
||||
|
||||
egui::TopBottomPanel::top("title_panel")
|
||||
.resizable(false)
|
||||
// .default_height(120.0)
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin::same(0.0),
|
||||
outer_margin: Margin::same(0.0),
|
||||
rounding: Default::default(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(" + ").size(38.0)
|
||||
).fill(Color32::TRANSPARENT).ui(ui).interact(Sense::click_and_drag());
|
||||
if b.drag_released() || b.clicked() {
|
||||
//TODO: Add wallet
|
||||
//self.menu_open = !menu_open
|
||||
};
|
||||
});
|
||||
ui.with_layout(Layout::top_down(Align::Center), |ui| {
|
||||
ui.heading(self.current_screen_title())
|
||||
});
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
let b = egui::widgets::Button::new(
|
||||
RichText::new(" = ").size(38.0)
|
||||
).fill(Color32::TRANSPARENT).ui(ui).interact(Sense::click_and_drag());
|
||||
if b.drag_released() || b.clicked() {
|
||||
self.menu_open = !menu_open
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().frame(egui::containers::Frame{
|
||||
outer_margin: Margin {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
// top: 120.0,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
},
|
||||
inner_margin: Margin::same(3.0),
|
||||
fill: ui.ctx().style().visuals.panel_fill,
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.show_screens(ui, frame, cb);
|
||||
});
|
||||
|
||||
ui.style_mut().visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
|
||||
|
||||
// egui::SidePanel::right("screens_content")
|
||||
// .resizable(false)
|
||||
// .min_width(frame.info().window_info.size.x)
|
||||
// .frame(egui::Frame {
|
||||
// inner_margin: Margin::same(3.0),
|
||||
// outer_margin: Margin::same(0.0),
|
||||
// rounding: Default::default(),
|
||||
// shadow: Shadow::NONE,
|
||||
// fill: Color32::KHAKI,
|
||||
// stroke: Stroke::NONE,
|
||||
// })
|
||||
// .show_inside(ui, |ui| {
|
||||
// self.show_screens(ui, frame, cb);
|
||||
// });
|
||||
|
||||
egui::SidePanel::left("menu_panel")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin::same(0.0),
|
||||
outer_margin: Margin::same(0.0),
|
||||
rounding: Default::default(),
|
||||
shadow: Shadow::NONE,
|
||||
fill: Color32::YELLOW,
|
||||
stroke: Stroke::NONE,
|
||||
})
|
||||
.show_animated_inside(ui, menu_open, |ui| {
|
||||
//TODO: Menu content
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("💻 Backend");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_landscape(frame: &mut Frame) -> bool {
|
||||
return frame.info().window_info.size.x > frame.info().window_info.size.y
|
||||
}
|
||||
|
||||
|
|
|
@ -14,13 +14,11 @@
|
|||
|
||||
mod app;
|
||||
pub use app::PlatformApp;
|
||||
pub use app::is_landscape;
|
||||
|
||||
pub mod screens;
|
||||
pub mod platform;
|
||||
|
||||
pub trait Ui {
|
||||
fn ui(&mut self, ui: &mut egui::Ui);
|
||||
}
|
||||
pub mod screens;
|
||||
pub mod views;
|
||||
|
||||
pub trait PlatformCallbacks {
|
||||
fn show_keyboard(&mut self);
|
||||
|
|
|
@ -12,10 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Color32, FontTweak, Visuals};
|
||||
use egui::epaint::Shadow;
|
||||
|
||||
use jni::objects::{JObject, JPrimitiveArray};
|
||||
use winit::platform::android::activity::AndroidApp;
|
||||
|
||||
use crate::gui::{PlatformApp, PlatformCallbacks};
|
||||
|
@ -23,9 +19,9 @@ use crate::gui::app::Screens;
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Android {
|
||||
pub android_app: AndroidApp,
|
||||
pub cutouts: [i32; 4],
|
||||
pub window_size: [f32; 2]
|
||||
android_app: AndroidApp,
|
||||
cutouts: [i32; 4],
|
||||
window_size: [f32; 2]
|
||||
}
|
||||
|
||||
impl Android {
|
||||
|
@ -58,32 +54,80 @@ impl PlatformCallbacks for Android {
|
|||
|
||||
impl PlatformApp<Android> {
|
||||
pub fn new(cc: &eframe::CreationContext<'_>, platform: Android) -> Self {
|
||||
setup_fonts(&cc.egui_ctx);
|
||||
Self::setup_visuals(&cc.egui_ctx);
|
||||
Self::setup_fonts(&cc.egui_ctx);
|
||||
Self {
|
||||
screens: Screens::default(),
|
||||
platform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_fonts(ctx: &egui::Context) {
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
fonts.font_data.insert(
|
||||
"roboto".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!(
|
||||
"../../../../fonts/roboto.ttf"
|
||||
)).tweak(FontTweak {
|
||||
scale: 1.0,
|
||||
y_offset_factor: -0.20,
|
||||
y_offset: 0.0
|
||||
}),
|
||||
);
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "roboto".to_owned());
|
||||
ctx.set_fonts(fonts);
|
||||
fn setup_visuals(ctx: &egui::Context) {
|
||||
ctx.set_visuals(egui::Visuals::light());
|
||||
}
|
||||
|
||||
fn setup_fonts(ctx: &egui::Context) {
|
||||
use egui::FontFamily::Proportional;
|
||||
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
fonts.font_data.insert(
|
||||
"roboto".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!(
|
||||
"../../../../fonts/roboto.ttf"
|
||||
)).tweak(egui::FontTweak {
|
||||
scale: 1.0,
|
||||
y_offset_factor: -0.20,
|
||||
y_offset: 0.0
|
||||
}),
|
||||
);
|
||||
fonts
|
||||
.families
|
||||
.entry(Proportional)
|
||||
.or_default()
|
||||
.insert(0, "roboto".to_owned());
|
||||
ctx.set_fonts(fonts);
|
||||
|
||||
use egui::FontId;
|
||||
use egui::TextStyle::*;
|
||||
|
||||
let mut style = (*ctx.style()).clone();
|
||||
|
||||
style.text_styles = [
|
||||
(Heading, FontId::new(24.0, Proportional)),
|
||||
(Body, FontId::new(18.0, Proportional)),
|
||||
(Monospace, FontId::new(18.0, Proportional)),
|
||||
(Button, FontId::new(18.0, Proportional)),
|
||||
(Small, FontId::new(12.0, Proportional)),
|
||||
].into();
|
||||
|
||||
ctx.set_style(style);
|
||||
}
|
||||
|
||||
fn get_display_cutouts(app: &AndroidApp) -> [i32; 4] {
|
||||
use jni::objects::{JObject, JPrimitiveArray};
|
||||
|
||||
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as _) }.unwrap();
|
||||
let mut env = vm.attach_current_thread().unwrap();
|
||||
let activity = unsafe {
|
||||
JObject::from_raw(app.activity_as_ptr() as jni::sys::jobject)
|
||||
};
|
||||
let _res = env
|
||||
.call_method(
|
||||
activity,
|
||||
"getDisplayCutouts",
|
||||
"()[I",
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let mut array: [i32; 4] = [0; 4];
|
||||
let object: jni::sys::jobject = unsafe { _res.as_jni().l };
|
||||
unsafe {
|
||||
env.get_int_array_region(JPrimitiveArray::from(
|
||||
JObject::from_raw(object)), 0, array.as_mut()
|
||||
).unwrap();
|
||||
}
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for PlatformApp<Android> {
|
||||
|
@ -93,80 +137,66 @@ impl eframe::App for PlatformApp<Android> {
|
|||
if _x != self.platform.window_size[0] || _y != self.platform.window_size[1] {
|
||||
self.platform.window_size[0] = _x;
|
||||
self.platform.window_size[1] = _y;
|
||||
self.platform.cutouts = get_display_cutouts(&self.platform.android_app);
|
||||
self.platform.cutouts = Self::get_display_cutouts(&self.platform.android_app);
|
||||
}
|
||||
|
||||
let is_dark = ctx.style().visuals.dark_mode;
|
||||
egui::TopBottomPanel::top("top_padding_panel")
|
||||
.frame(egui::Frame {
|
||||
shadow: Shadow::NONE,
|
||||
fill: if is_dark {Color32::BLACK} else {Color32::WHITE},
|
||||
..Default::default()
|
||||
})
|
||||
.show_separator_line(false)
|
||||
.resizable(false)
|
||||
.exact_height(self.platform.cutouts[0] as f32)
|
||||
.show(ctx, |ui| {});
|
||||
padding_panels(self, ctx);
|
||||
|
||||
egui::TopBottomPanel::bottom("bottom_padding_panel")
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
shadow: Shadow::NONE,
|
||||
fill: if is_dark {Color32::BLACK} else {Color32::WHITE},
|
||||
..Default::default()
|
||||
inner_margin: egui::style::Margin::same(0.0),
|
||||
outer_margin: egui::style::Margin::same(0.0),
|
||||
fill: ctx.style().visuals.panel_fill,
|
||||
.. Default::default()
|
||||
})
|
||||
.show_separator_line(false)
|
||||
.resizable(false)
|
||||
.exact_height(self.platform.cutouts[2] as f32)
|
||||
.show(ctx, |ui| {});
|
||||
|
||||
egui::SidePanel::right("right_padding_panel")
|
||||
.frame(egui::Frame {
|
||||
shadow: Shadow::NONE,
|
||||
fill: if is_dark {Color32::BLACK} else {Color32::WHITE},
|
||||
..Default::default()
|
||||
})
|
||||
.show_separator_line(false)
|
||||
.resizable(false)
|
||||
.default_width(self.platform.cutouts[1] as f32)
|
||||
.show(ctx, |ui| {});
|
||||
|
||||
egui::SidePanel::left("left_padding_panel")
|
||||
.frame(egui::Frame {
|
||||
shadow: Shadow::NONE,
|
||||
fill: if is_dark {Color32::BLACK} else {Color32::WHITE},
|
||||
..Default::default()
|
||||
})
|
||||
.show_separator_line(false)
|
||||
.resizable(false)
|
||||
.default_width(self.platform.cutouts[3] as f32)
|
||||
.show(ctx, |ui| {});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
self.screens.ui(ctx, frame, &self.platform);
|
||||
});
|
||||
.show(ctx, |ui| {
|
||||
self.screens.ui(ui, frame, &self.platform);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display_cutouts(app: &AndroidApp) -> [i32; 4] {
|
||||
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as _) }.unwrap();
|
||||
let mut env = vm.attach_current_thread().unwrap();
|
||||
let activity = unsafe {
|
||||
JObject::from_raw(app.activity_as_ptr() as jni::sys::jobject)
|
||||
};
|
||||
let _res = env
|
||||
.call_method(
|
||||
activity,
|
||||
"getDisplayCutouts",
|
||||
"()[I",
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let mut array: [i32; 4] = [0; 4];
|
||||
let object: jni::sys::jobject = unsafe { _res.as_jni().l };
|
||||
unsafe {
|
||||
env.get_int_array_region(JPrimitiveArray::from(
|
||||
JObject::from_raw(object)), 0, array.as_mut()
|
||||
).unwrap();
|
||||
}
|
||||
array
|
||||
}
|
||||
fn padding_panels(app: &PlatformApp<Android>, 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(app.platform.cutouts[0] 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(app.platform.cutouts[2] 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(app.platform.cutouts[1] 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(app.platform.cutouts[3] as f32)
|
||||
.show(ctx, |ui| {});
|
||||
}
|
|
@ -13,8 +13,13 @@
|
|||
// limitations under the License.
|
||||
|
||||
mod wallets;
|
||||
|
||||
pub use wallets::Wallets;
|
||||
use crate::gui::PlatformCallbacks;
|
||||
|
||||
pub trait Screen {
|
||||
fn name(&self) -> &'static str;
|
||||
fn name(&self) -> String;
|
||||
fn show(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks);
|
||||
|
||||
// fn option_on_panel(&mut self, ui: &mut egui::Ui);
|
||||
}
|
||||
|
|
|
@ -12,34 +12,36 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::ScrollArea;
|
||||
use crate::gui::Ui;
|
||||
use std::ops::Deref;
|
||||
use eframe::Frame;
|
||||
use egui::{ScrollArea, Ui};
|
||||
|
||||
use crate::gui::app::Screens;
|
||||
use crate::gui::{PlatformCallbacks};
|
||||
|
||||
pub struct Wallets {
|
||||
|
||||
}
|
||||
|
||||
impl Default for Wallets {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui for Wallets {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
impl super::Screen for Wallets {
|
||||
fn name(&self) -> String {
|
||||
t!("wallets")
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut Ui, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
for item in 1..=50 {
|
||||
ui.heading(format!("This is future Wallet {}", item));
|
||||
for item in 1..=55 {
|
||||
ui.heading(format!("This is longest future Wallet #{}", item));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Screen for Wallets {
|
||||
fn name(&self) -> &'static str {
|
||||
"Wallets"
|
||||
}
|
||||
}
|
14
src/gui/views/menu.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
22
src/gui/views/mod.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::gui::PlatformCallbacks;
|
||||
|
||||
mod navigation;
|
||||
mod menu;
|
||||
|
||||
pub trait View {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks);
|
||||
}
|
32
src/gui/views/navigation.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::Frame;
|
||||
use egui::Ui;
|
||||
use crate::gui::PlatformCallbacks;
|
||||
use crate::gui::views::View;
|
||||
|
||||
struct NavigationPanel {
|
||||
title: str
|
||||
}
|
||||
|
||||
impl View for NavigationPanel {
|
||||
fn ui(&mut self, ui: &mut Ui, frame: &mut Frame, cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigationPanel {
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Grin Developers
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,8 +12,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
i18n!("locales");
|
||||
|
||||
mod node;
|
||||
mod wallet;
|
||||
mod gui;
|
||||
|
||||
pub mod grim;
|
||||
pub mod grim;
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Grin Developers
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// // Copyright 2023 The Grin Developers
|
||||
// //
|
||||
// // Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// // you may not use this file except in compliance with the License.
|
||||
// // You may obtain a copy of the License at
|
||||
// //
|
||||
// // http://www.apache.org/licenses/LICENSE-2.0
|
||||
// //
|
||||
// // Unless required by applicable law or agreed to in writing, software
|
||||
// // distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// // See the License for the specific language governing permissions and
|
||||
// // limitations under the License.
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::mpsc;
|
||||
use grin_config::{config, GlobalConfig};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Grin Developers
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2023 The Grin Developers
|
||||
// Copyright 2023 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
|