Custom window title #8
33 changed files with 1190 additions and 408 deletions
168
Cargo.lock
generated
168
Cargo.lock
generated
|
@ -23,10 +23,6 @@ name = "accesskit"
|
|||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b"
|
||||
dependencies = [
|
||||
"enumn",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
|
@ -199,7 +195,6 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
@ -1698,36 +1693,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"cocoa-foundation",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa-foundation"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
|
@ -2107,12 +2072,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "d3d12"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307"
|
||||
checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libloading 0.8.3",
|
||||
"libloading 0.7.4",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
|
@ -2582,11 +2547,10 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
"emath",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2653,11 +2617,10 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
"cocoa",
|
||||
"document-features",
|
||||
"egui",
|
||||
"egui-wgpu",
|
||||
|
@ -2666,17 +2629,18 @@ dependencies = [
|
|||
"glow",
|
||||
"glutin",
|
||||
"glutin-winit",
|
||||
"image 0.24.9",
|
||||
"image 0.25.1",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"parking_lot 0.12.3",
|
||||
"percent-encoding",
|
||||
"pollster",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle 0.6.2",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
|
@ -2689,23 +2653,22 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash 0.8.11",
|
||||
"emath",
|
||||
"epaint",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
"egui",
|
||||
|
@ -2721,10 +2684,10 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"ahash 0.8.11",
|
||||
"arboard",
|
||||
"egui",
|
||||
"log",
|
||||
|
@ -2738,24 +2701,23 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "egui_extras"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b78779f35ded1a853786c9ce0b43fe1053e10a21ea3b23ebea411805ce41593"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"egui",
|
||||
"enum-map",
|
||||
"image 0.24.9",
|
||||
"image 0.25.1",
|
||||
"log",
|
||||
"mime_guess2",
|
||||
"resvg",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
"egui",
|
||||
"glow",
|
||||
|
@ -2766,15 +2728,6 @@ dependencies = [
|
|||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_pull_to_refresh"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c649d50513fc760df6d6ad74b739189f2768e81df3ec734f1f280bcf68e33c75"
|
||||
dependencies = [
|
||||
"egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.12.0"
|
||||
|
@ -2803,11 +2756,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2895,17 +2846,6 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumn"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.85",
|
||||
"quote 1.0.36",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.0"
|
||||
|
@ -2981,8 +2921,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176"
|
||||
source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash 0.8.11",
|
||||
|
@ -2992,7 +2931,6 @@ dependencies = [
|
|||
"log",
|
||||
"nohash-hasher",
|
||||
"parking_lot 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3808,9 +3746,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gpu-descriptor"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
|
||||
checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"gpu-descriptor-types",
|
||||
|
@ -3819,9 +3757,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gpu-descriptor-types"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
|
||||
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
]
|
||||
|
@ -3844,7 +3782,6 @@ dependencies = [
|
|||
"eframe",
|
||||
"egui",
|
||||
"egui_extras",
|
||||
"egui_pull_to_refresh",
|
||||
"env_logger 0.11.3",
|
||||
"eye",
|
||||
"fs-mistrust",
|
||||
|
@ -4909,7 +4846,6 @@ dependencies = [
|
|||
"byteorder",
|
||||
"color_quant",
|
||||
"num-traits 0.2.19",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5613,9 +5549,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25"
|
||||
checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block",
|
||||
|
@ -5775,10 +5711,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "0.19.2"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843"
|
||||
checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.4",
|
||||
"bit-set",
|
||||
"bitflags 2.5.0",
|
||||
"codespan-reporting",
|
||||
|
@ -6211,7 +6148,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
"objc_exception",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6366,15 +6302,6 @@ dependencies = [
|
|||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
|
@ -11004,17 +10931,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webbrowser"
|
||||
version = "0.8.15"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b"
|
||||
checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e"
|
||||
dependencies = [
|
||||
"block2 0.5.1",
|
||||
"core-foundation 0.9.4",
|
||||
"home",
|
||||
"jni",
|
||||
"log",
|
||||
"ndk-context",
|
||||
"objc",
|
||||
"raw-window-handle 0.5.2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation",
|
||||
"url",
|
||||
"web-sys",
|
||||
]
|
||||
|
@ -11056,13 +10984,14 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
|
|||
|
||||
[[package]]
|
||||
name = "wgpu"
|
||||
version = "0.19.4"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01"
|
||||
checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.4",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
"js-sys",
|
||||
"log",
|
||||
"naga",
|
||||
|
@ -11081,15 +11010,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wgpu-core"
|
||||
version = "0.19.4"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a"
|
||||
checksum = "d59e0d5fc509601c69e4e1fa06c1eb3c4c9f12956a5e30c79b61ef1c1be7daf0"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.4",
|
||||
"bit-vec",
|
||||
"bitflags 2.5.0",
|
||||
"cfg_aliases",
|
||||
"codespan-reporting",
|
||||
"document-features",
|
||||
"indexmap 2.2.6",
|
||||
"log",
|
||||
"naga",
|
||||
|
@ -11107,9 +11037,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wgpu-hal"
|
||||
version = "0.19.4"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3"
|
||||
checksum = "6aa24c3889f885a3fb9133b454c8418bfcfaadcfe4ed3be96ac80e76703b863b"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"arrayvec 0.7.4",
|
||||
|
@ -11129,7 +11059,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libloading 0.8.3",
|
||||
"libloading 0.7.4",
|
||||
"log",
|
||||
"metal",
|
||||
"naga",
|
||||
|
@ -11152,9 +11082,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wgpu-types"
|
||||
version = "0.19.2"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805"
|
||||
checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"js-sys",
|
||||
|
|
20
Cargo.toml
20
Cargo.toml
|
@ -9,8 +9,21 @@ keywords = [ "crypto", "grin", "mimblewimble" ]
|
|||
edition = "2021"
|
||||
build = "src/build/build.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "grim-bin"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name="grim"
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[profile.release-apk]
|
||||
inherits = "release"
|
||||
strip = true
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
|
@ -37,7 +50,6 @@ grin_wallet_controller = "5.3.1"
|
|||
egui = { version = "0.27.2", default-features = false }
|
||||
egui_extras = { version = "0.27.2", features = ["image", "svg"] }
|
||||
rust-i18n = "2.3.1"
|
||||
egui_pull_to_refresh = "0.4.0"
|
||||
|
||||
## other
|
||||
thiserror = "1.0.58"
|
||||
|
@ -110,7 +122,7 @@ dark-light = "1.1.1"
|
|||
android_logger = "0.13.1"
|
||||
jni = "0.21.1"
|
||||
android-activity = { version = "0.6.0", features = ["game-activity"] }
|
||||
wgpu = "0.19.1"
|
||||
wgpu = "0.20.1"
|
||||
winit = { version = "0.29.15", features = ["android-game-activity"] }
|
||||
eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] }
|
||||
|
||||
|
@ -121,3 +133,7 @@ eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] }
|
|||
[patch.crates-io]
|
||||
### fix cross-compilation support for macos
|
||||
openpnp_capture_sys = { git = "https://github.com/ardocrat/openpnp-capture-rs", branch = "cross_compilation_support" }
|
||||
### actual version
|
||||
egui = { git = "https://github.com/emilk/egui/", rev="10571e9da5b3658964adc8b85c7e3f8c1353e0db" }
|
||||
egui_extras = { git = "https://github.com/emilk/egui/", rev="10571e9da5b3658964adc8b85c7e3f8c1353e0db" }
|
||||
eframe = { git = "https://github.com/emilk/egui/", rev="10571e9da5b3658964adc8b85c7e3f8c1353e0db" }
|
|
@ -18,7 +18,7 @@ To build and run application go to project directory and run:
|
|||
|
||||
```
|
||||
cargo build --release
|
||||
./target/release/grim
|
||||
./target/release/grim-bin
|
||||
```
|
||||
|
||||
### Android
|
||||
|
|
|
@ -96,7 +96,7 @@ wallets:
|
|||
parse_s3_slatepack_desc: 'Transaktion posten, um das Senden von %{amount} abzuschließen ツ:'
|
||||
resp_slatepack_err: 'Beim Erstellen der Antwort ist ein Fehler aufgetreten. Überprüfen Sie die Eingabedaten:'
|
||||
resp_exists_err: 'Eine solche Transaktion existiert bereits.'
|
||||
resp_canceled_err: 'Eine solche Transaktion wurde schon abgebrochen.
|
||||
resp_canceled_err: 'Eine solche Transaktion wurde schon abgebrochen.'
|
||||
create_request_desc: 'Erstellen Sie eine Anfrage zum Senden oder Empfangen der Gelder:'
|
||||
send_request_desc: 'Sie haben eine Anfrage zum Senden von %{amount} ツ erstellt. Senden Sie diese Nachricht an den Empfänger:'
|
||||
send_slatepack_err: Beim Erstellen der Anfrage zum Senden von Geldern ist ein Fehler aufgetreten. Überprüfen Sie die Eingabedaten.
|
||||
|
@ -142,7 +142,7 @@ transport:
|
|||
bin_file: 'Binärdatei:'
|
||||
conn_line: 'Verbindungsleitung:'
|
||||
bridges_disabled: Brücken deaktiviert
|
||||
bridge_name: 'Brücke %{b}
|
||||
bridge_name: 'Brücke %{b}'
|
||||
network:
|
||||
self: Netzwerk
|
||||
type: 'Netzwerk Typ:'
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
x86|arm)
|
||||
x86|arm|all)
|
||||
;;
|
||||
*)
|
||||
echo "Usage: release_macos.sh [platform]\n - platform: 'x86', 'arm'" >&2
|
||||
echo "Usage: release_macos.sh [platform]\n - platform: 'x86', 'arm', 'all'" >&2
|
||||
exit 1
|
||||
esac
|
||||
|
||||
|
@ -23,23 +24,31 @@ BASEDIR=$(cd $(dirname $0) && pwd)
|
|||
cd ${BASEDIR}
|
||||
cd ..
|
||||
|
||||
# Setup platform argument
|
||||
# Setup platform
|
||||
rustup target add x86_64-apple-darwin
|
||||
rustup target add aarch64-apple-darwin
|
||||
|
||||
rm -rf target/x86_64-apple-darwin
|
||||
rm -rf target/aarch64-apple-darwin
|
||||
|
||||
[[ $1 == "x86" ]] && arch+=(x86_64-apple-darwin)
|
||||
[[ $1 == "arm" ]] && arch+=(aarch64-apple-darwin)
|
||||
[[ $1 == "all" ]] && arch+=(universal2-apple-darwin)
|
||||
|
||||
# Start release build with zig linker for cross-compilation
|
||||
cargo install cargo-zigbuild
|
||||
# zig 0.12 required
|
||||
cargo install cargo-zigbuild@0.18.4
|
||||
cargo zigbuild --release --target ${arch}
|
||||
rm .intentionally-empty-file.o
|
||||
yes | cp -rf target/${arch}/release/grim macos/Grim.app/Contents/MacOS/grim-bin
|
||||
rm -rf .intentionally-empty-file.o
|
||||
yes | cp -rf target/${arch}/release/grim-bin macos/Grim.app/Contents/MacOS
|
||||
|
||||
### Sign .app before distribution:
|
||||
### rcodesign generate-self-signed-certificate
|
||||
### rcodesign sign --pem-file cert.pem macos/Grim.app
|
||||
### Sign .app resources:
|
||||
#rcodesign generate-self-signed-certificate
|
||||
#rcodesign sign --pem-file cert.pem macos/Grim.app
|
||||
|
||||
# Create release package
|
||||
FILE_NAME=Grim-0.1.0-macos-$1.zip
|
||||
rm target/${arch}/release/${FILE_NAME}
|
||||
rm -rf target/${arch}/release/${FILE_NAME}
|
||||
cd macos
|
||||
zip -r ${FILE_NAME} Grim.app
|
||||
mv ${FILE_NAME} ../target/${arch}/release
|
|
@ -24,7 +24,7 @@ cd ..
|
|||
|
||||
# Setup release argument
|
||||
type=$1
|
||||
[[ ${type} == "release" ]] && release_param+=(--release)
|
||||
[[ ${type} == "release" ]] && release_param="--profile release-apk"
|
||||
|
||||
# Setup platform argument
|
||||
[[ $2 == "v7" ]] && arch+=(armeabi-v7a)
|
||||
|
@ -39,21 +39,46 @@ type=$1
|
|||
[[ $2 == "v8" ]] && rustup target install aarch64-linux-android
|
||||
|
||||
# Build native code
|
||||
export CPPFLAGS="-DMDB_USE_ROBUST=0" && export CFLAGS="-DMDB_USE_ROBUST=0" \
|
||||
&& cargo ndk -t ${arch} build ${release_param[@]}
|
||||
cargo install cargo-ndk
|
||||
mkdir -p android/app/src/main/jniLibs
|
||||
|
||||
# Build Android application and launch at all connected devices
|
||||
sed -i -e 's/"rlib"/"rlib","cdylib"/g' Cargo.toml
|
||||
|
||||
# temp fix for https://stackoverflow.com/questions/57193895/error-use-of-undeclared-identifier-pthread-mutex-robust-cargo-build-liblmdb-s
|
||||
success=0
|
||||
export CPPFLAGS="-DMDB_USE_ROBUST=0" && export CFLAGS="-DMDB_USE_ROBUST=0"
|
||||
cargo ndk -t ${arch} -o android/app/src/main/jniLibs build ${release_param}
|
||||
if [ $? -eq 1 ]
|
||||
then
|
||||
unset CPPFLAGS && unset CFLAGS
|
||||
cargo ndk -t ${arch} -o android/app/src/main/jniLibs build ${release_param}
|
||||
fi
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
yes | mkdir -p android/app/src/main/jniLibs/${arch} && cp -f target/${platform}/${type}/libgrim.so android/app/src/main/jniLibs/${arch}
|
||||
success=1
|
||||
fi
|
||||
|
||||
sed -i -e 's/"rlib","cdylib"/"rlib"/g' Cargo.toml
|
||||
|
||||
# Build Android application and launch at all connected devices
|
||||
if [ $success -eq 1 ]
|
||||
then
|
||||
cd android
|
||||
|
||||
# Setup gradle argument
|
||||
[[ $1 == "release" ]] && gradle_param+=(assembleRelease)
|
||||
[[ $1 == "debug" ]] && gradle_param+=(build)
|
||||
|
||||
./gradlew clean
|
||||
# ./gradlew assembleRelease
|
||||
./gradlew build
|
||||
./gradlew ${gradle_param}
|
||||
|
||||
# Setup apk path
|
||||
[[ $1 == "release" ]] && apk_path+=(app/build/outputs/apk/release/app-release.apk)
|
||||
[[ $1 == "debug" ]] && apk_path+=(app/build/outputs/apk/debug/app-debug.apk)
|
||||
|
||||
for SERIAL in $(adb devices | grep -v List | cut -f 1);
|
||||
do
|
||||
# adb -s $SERIAL install app/build/outputs/apk/release/app-release.apk
|
||||
adb -s $SERIAL install app/build/outputs/apk/debug/app-debug.apk
|
||||
adb -s $SERIAL install ${apk_path}
|
||||
sleep 1s
|
||||
adb -s $SERIAL shell am start -n mw.gri.android/.MainActivity;
|
||||
done
|
||||
|
|
|
@ -21,5 +21,5 @@ cargo build ${release_param[@]}
|
|||
# Start application
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
./target/${type}/grim
|
||||
./target/${type}/grim-bin
|
||||
fi
|
348
src/gui/app.rs
348
src/gui/app.rs
|
@ -13,13 +13,16 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use egui::{Context, Modifiers};
|
||||
use lazy_static::lazy_static;
|
||||
use egui::{Align, Context, CursorIcon, Layout, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand};
|
||||
use egui::epaint::{RectShape};
|
||||
use egui::os::OperatingSystem;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::{AppConfig, built_info};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::Root;
|
||||
use crate::gui::views::{Content, TitlePanel, View};
|
||||
|
||||
lazy_static! {
|
||||
/// State to check if platform Back button was pressed.
|
||||
|
@ -30,22 +33,26 @@ lazy_static! {
|
|||
pub struct App<Platform> {
|
||||
/// Platform specific callbacks handler.
|
||||
pub(crate) platform: Platform,
|
||||
|
||||
/// Main ui content.
|
||||
root: Root
|
||||
content: Content,
|
||||
|
||||
/// Last window resize direction.
|
||||
resize_direction: Option<ResizeDirection>
|
||||
}
|
||||
|
||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
pub fn new(platform: Platform) -> Self {
|
||||
Self { platform, root: Root::default() }
|
||||
Self { platform, content: Content::default(), resize_direction: None }
|
||||
}
|
||||
|
||||
/// Draw application content.
|
||||
pub fn ui(&mut self, ctx: &Context) {
|
||||
// Handle Esc keyboard key event and platform Back button key event.
|
||||
let back_button_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
||||
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) || back_button_pressed {
|
||||
self.root.on_back();
|
||||
if back_button_pressed {
|
||||
let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
||||
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) || back_pressed {
|
||||
self.content.on_back();
|
||||
if back_pressed {
|
||||
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
// Request repaint to update previous content.
|
||||
|
@ -54,9 +61,9 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
|
||||
// Handle Close event (on desktop).
|
||||
if ctx.input(|i| i.viewport().close_requested()) {
|
||||
if !self.root.exit_allowed {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose);
|
||||
Root::show_exit_modal();
|
||||
if !self.content.exit_allowed {
|
||||
ctx.send_viewport_cmd(ViewportCommand::CancelClose);
|
||||
Content::show_exit_modal();
|
||||
} else {
|
||||
ctx.input(|i| {
|
||||
if let Some(rect) = i.viewport().inner_rect {
|
||||
|
@ -69,15 +76,313 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
}
|
||||
}
|
||||
|
||||
// Show main content.
|
||||
// Show main content with custom frame on desktop.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
..Default::default()
|
||||
})
|
||||
.show(ctx, |ui| {
|
||||
self.root.ui(ui, &self.platform);
|
||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||
if View::is_desktop() && !is_mac_os {
|
||||
self.desktop_window_ui(ui);
|
||||
} else {
|
||||
if is_mac_os {
|
||||
self.window_title_ui(ui);
|
||||
ui.add_space(-1.0);
|
||||
}
|
||||
self.content.ui(ui, &self.platform);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw custom resizeable window content.
|
||||
fn desktop_window_ui(&mut self, ui: &mut egui::Ui) {
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
|
||||
let title_stroke_rect = {
|
||||
let mut rect = ui.max_rect();
|
||||
if !is_fullscreen {
|
||||
rect = rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
||||
}
|
||||
rect.max.y = if !is_fullscreen {
|
||||
Content::WINDOW_FRAME_MARGIN
|
||||
} else {
|
||||
0.0
|
||||
} + Content::WINDOW_TITLE_HEIGHT + TitlePanel::DEFAULT_HEIGHT + 0.5;
|
||||
rect
|
||||
};
|
||||
let title_stroke = RectShape {
|
||||
rect: title_stroke_rect,
|
||||
rounding: Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
},
|
||||
fill: Colors::yellow(),
|
||||
stroke: Stroke {
|
||||
width: 1.0,
|
||||
color: egui::Color32::from_gray(200)
|
||||
},
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
// Draw title stroke.
|
||||
ui.painter().add(title_stroke);
|
||||
|
||||
let content_stroke_rect = {
|
||||
let mut rect = ui.max_rect();
|
||||
if !is_fullscreen {
|
||||
rect = rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
||||
}
|
||||
let top = Content::WINDOW_TITLE_HEIGHT + TitlePanel::DEFAULT_HEIGHT + 0.5;
|
||||
rect.min += egui::vec2(0.0, top);
|
||||
rect
|
||||
};
|
||||
let content_stroke = RectShape {
|
||||
rect: content_stroke_rect,
|
||||
rounding: Rounding::ZERO,
|
||||
fill: Colors::fill(),
|
||||
stroke: Stroke {
|
||||
width: 1.0,
|
||||
color: Colors::stroke()
|
||||
},
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
// Draw content stroke.
|
||||
ui.painter().add(content_stroke);
|
||||
|
||||
// Draw window content.
|
||||
let mut content_rect = ui.max_rect();
|
||||
if !is_fullscreen {
|
||||
content_rect = content_rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
||||
}
|
||||
ui.allocate_ui_at_rect(content_rect, |ui| {
|
||||
self.window_title_ui(ui);
|
||||
self.window_content(ui);
|
||||
});
|
||||
|
||||
// Setup resize areas.
|
||||
if !is_fullscreen {
|
||||
self.resize_area_ui(ui, ResizeDirection::North);
|
||||
self.resize_area_ui(ui, ResizeDirection::East);
|
||||
self.resize_area_ui(ui, ResizeDirection::South);
|
||||
self.resize_area_ui(ui, ResizeDirection::West);
|
||||
self.resize_area_ui(ui, ResizeDirection::NorthWest);
|
||||
self.resize_area_ui(ui, ResizeDirection::NorthEast);
|
||||
self.resize_area_ui(ui, ResizeDirection::SouthEast);
|
||||
self.resize_area_ui(ui, ResizeDirection::SouthWest);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw window content for desktop.
|
||||
fn window_content(&mut self, ui: &mut egui::Ui) {
|
||||
let content_rect = {
|
||||
let mut rect = ui.max_rect();
|
||||
rect.min.y += Content::WINDOW_TITLE_HEIGHT;
|
||||
rect
|
||||
};
|
||||
// Draw main content.
|
||||
let mut content_ui = ui.child_ui(content_rect, *ui.layout(), None);
|
||||
self.content.ui(&mut content_ui, &self.platform);
|
||||
}
|
||||
|
||||
/// Draw custom window title content.
|
||||
fn window_title_ui(&self, ui: &mut egui::Ui) {
|
||||
let content_rect = ui.max_rect();
|
||||
|
||||
let title_rect = {
|
||||
let mut rect = content_rect;
|
||||
rect.max.y = rect.min.y + Content::WINDOW_TITLE_HEIGHT;
|
||||
rect
|
||||
};
|
||||
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
|
||||
let window_title_bg = RectShape {
|
||||
rect: title_rect,
|
||||
rounding: if is_fullscreen {
|
||||
Rounding::ZERO
|
||||
} else {
|
||||
Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
}
|
||||
},
|
||||
fill: Colors::yellow_dark(),
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
// Draw title background.
|
||||
ui.painter().add(window_title_bg);
|
||||
|
||||
let painter = ui.painter();
|
||||
|
||||
let interact_rect = {
|
||||
let mut rect = title_rect;
|
||||
if !is_fullscreen {
|
||||
rect.min.y += Content::WINDOW_FRAME_MARGIN;
|
||||
}
|
||||
rect
|
||||
};
|
||||
let title_resp = ui.interact(
|
||||
interact_rect,
|
||||
egui::Id::new("window_title"),
|
||||
egui::Sense::click_and_drag(),
|
||||
);
|
||||
|
||||
// Paint the title.
|
||||
let dual_wallets_panel =
|
||||
ui.available_width() >= (Content::SIDE_PANEL_WIDTH * 3.0) + View::get_right_inset();
|
||||
let wallet_panel_opened = self.content.wallets.wallet_panel_opened();
|
||||
let hide_app_name = if dual_wallets_panel {
|
||||
!wallet_panel_opened || (AppConfig::show_wallets_at_dual_panel() &&
|
||||
self.content.wallets.showing_wallet() && !self.content.wallets.creating_wallet())
|
||||
} else if Content::is_dual_panel_mode(ui) {
|
||||
!wallet_panel_opened
|
||||
} else {
|
||||
!Content::is_network_panel_open() && !wallet_panel_opened
|
||||
};
|
||||
let title_text = if hide_app_name {
|
||||
"ツ".to_string()
|
||||
} else {
|
||||
format!("Grim {}", built_info::PKG_VERSION)
|
||||
};
|
||||
painter.text(
|
||||
title_rect.center(),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
title_text,
|
||||
egui::FontId::proportional(15.0),
|
||||
Colors::title(true),
|
||||
);
|
||||
|
||||
// Interact with the window title (drag to move window):
|
||||
if !is_fullscreen && title_resp.double_clicked() {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
}
|
||||
|
||||
if !is_fullscreen && title_resp.drag_started_by(egui::PointerButton::Primary) {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
|
||||
}
|
||||
|
||||
ui.allocate_ui_at_rect(title_rect, |ui| {
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to close window.
|
||||
View::title_button_small(ui, X, |_| {
|
||||
Content::show_exit_modal();
|
||||
});
|
||||
|
||||
// Draw fullscreen button.
|
||||
let fullscreen_icon = if is_fullscreen {
|
||||
ARROWS_IN
|
||||
} else {
|
||||
ARROWS_OUT
|
||||
};
|
||||
View::title_button_small(ui, fullscreen_icon, |ui| {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
});
|
||||
|
||||
// Draw button to minimize window.
|
||||
View::title_button_small(ui, CARET_DOWN, |ui| {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::Minimized(true));
|
||||
});
|
||||
|
||||
// Draw application icon.
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
// Draw button to minimize window.
|
||||
let use_dark = AppConfig::dark_theme().unwrap_or(false);
|
||||
let theme_icon = if use_dark {
|
||||
SUN
|
||||
} else {
|
||||
MOON
|
||||
};
|
||||
View::title_button_small(ui, theme_icon, |ui| {
|
||||
AppConfig::set_dark_theme(!use_dark);
|
||||
crate::setup_visuals(ui.ctx());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Setup window resize area.
|
||||
fn resize_area_ui(&mut self, ui: &egui::Ui, direction: ResizeDirection) {
|
||||
let mut rect = ui.max_rect();
|
||||
|
||||
// Setup area id, cursor and area rect based on direction.
|
||||
let (id, cursor, rect) = match direction {
|
||||
ResizeDirection::North => ("n", CursorIcon::ResizeNorth, {
|
||||
rect.min.x += Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.y = rect.min.y + Content::WINDOW_FRAME_MARGIN;
|
||||
rect.max.x -= Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::East => ("e", CursorIcon::ResizeEast, {
|
||||
rect.min.y += Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.x = rect.max.x - Content::WINDOW_FRAME_MARGIN;
|
||||
rect.max.y -= Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::South => ("s", CursorIcon::ResizeSouth, {
|
||||
rect.min.x += Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.y = rect.max.y - Content::WINDOW_FRAME_MARGIN;
|
||||
rect.max.x -= Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::West => ("w", CursorIcon::ResizeWest, {
|
||||
rect.min.y += Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.x = rect.min.x + Content::WINDOW_FRAME_MARGIN;
|
||||
rect.max.y -= Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::NorthWest => ("nw", CursorIcon::ResizeNorthWest, {
|
||||
rect.max.y = rect.min.y + Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.x = rect.max.y + Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::NorthEast => ("ne", CursorIcon::ResizeNorthEast, {
|
||||
rect.min.x = rect.max.x - Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.y = Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::SouthEast => ("se", CursorIcon::ResizeSouthEast, {
|
||||
rect.min.y = rect.max.y - Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.x = rect.max.x - Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::SouthWest => ("sw", CursorIcon::ResizeSouthWest, {
|
||||
rect.min.y = rect.max.y - Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.x = rect.min.x + Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
};
|
||||
|
||||
// Setup resize area.
|
||||
let id = egui::Id::new("window_resize").with(id);
|
||||
let sense = egui::Sense::drag();
|
||||
let area_resp = ui.interact(rect, id, sense).on_hover_cursor(cursor);
|
||||
if area_resp.dragged() {
|
||||
if self.resize_direction.is_none() {
|
||||
self.resize_direction = Some(direction.clone());
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::BeginResize(direction));
|
||||
}
|
||||
}
|
||||
if area_resp.drag_stopped() {
|
||||
self.resize_direction = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// To draw with egui`s eframe (for wgpu, glow backends and wasm target).
|
||||
|
@ -85,6 +390,19 @@ impl<Platform: PlatformCallbacks> eframe::App for App<Platform> {
|
|||
fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
|
||||
self.ui(ctx);
|
||||
}
|
||||
|
||||
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
||||
if View::is_desktop() {
|
||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||
if is_mac_os {
|
||||
Colors::fill().to_normalized_gamma_f32()
|
||||
} else {
|
||||
egui::Rgba::TRANSPARENT.to_array()
|
||||
}
|
||||
} else {
|
||||
Colors::fill().to_normalized_gamma_f32()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -28,6 +28,7 @@ const DARK_SEMI_TRANSPARENT: Color32 = Color32::from_black_alpha(170);
|
|||
const GOLD: Color32 = Color32::from_rgb(255, 215, 0);
|
||||
|
||||
const YELLOW: Color32 = Color32::from_rgb(254, 241, 2);
|
||||
const YELLOW_DARK: Color32 = Color32::from_rgb(239, 229, 3);
|
||||
|
||||
const GREEN: Color32 = Color32::from_rgb(0, 0x64, 0);
|
||||
|
||||
|
@ -108,7 +109,7 @@ impl Colors {
|
|||
|
||||
pub fn gold() -> Color32 {
|
||||
if use_dark() {
|
||||
GOLD.linear_multiply(0.9)
|
||||
GOLD.gamma_multiply(0.9)
|
||||
} else {
|
||||
GOLD
|
||||
}
|
||||
|
@ -118,9 +119,13 @@ impl Colors {
|
|||
YELLOW
|
||||
}
|
||||
|
||||
pub fn yellow_dark() -> Color32 {
|
||||
YELLOW_DARK
|
||||
}
|
||||
|
||||
pub fn green() -> Color32 {
|
||||
if use_dark() {
|
||||
GREEN.linear_multiply(1.3)
|
||||
GREEN.gamma_multiply(1.3)
|
||||
} else {
|
||||
GREEN
|
||||
}
|
||||
|
@ -128,7 +133,7 @@ impl Colors {
|
|||
|
||||
pub fn red() -> Color32 {
|
||||
if use_dark() {
|
||||
RED.linear_multiply(1.3)
|
||||
RED.gamma_multiply(1.3)
|
||||
} else {
|
||||
RED
|
||||
}
|
||||
|
@ -136,7 +141,7 @@ impl Colors {
|
|||
|
||||
pub fn blue() -> Color32 {
|
||||
if use_dark() {
|
||||
BLUE.linear_multiply(1.3)
|
||||
BLUE.gamma_multiply(1.3)
|
||||
} else {
|
||||
BLUE
|
||||
}
|
||||
|
|
|
@ -31,25 +31,25 @@ lazy_static! {
|
|||
}
|
||||
|
||||
/// Contains main ui content, handles side panel state.
|
||||
pub struct Root {
|
||||
pub struct Content {
|
||||
/// Side panel [`NetworkContent`] content.
|
||||
network: NetworkContent,
|
||||
/// Central panel [`WalletsContent`] content.
|
||||
wallets: WalletsContent,
|
||||
pub wallets: WalletsContent,
|
||||
|
||||
/// Check if app exit is allowed on close event of [`eframe::App`] implementation.
|
||||
pub(crate) exit_allowed: bool,
|
||||
/// Flag to show exit progress at [`Modal`].
|
||||
show_exit_progress: bool,
|
||||
|
||||
/// Flag to check if first it's first draw of content.
|
||||
/// Flag to check it's first draw of content.
|
||||
first_draw: bool,
|
||||
|
||||
/// List of allowed [`Modal`] ids for this [`ModalContainer`].
|
||||
allowed_modal_ids: Vec<&'static str>
|
||||
}
|
||||
|
||||
impl Default for Root {
|
||||
impl Default for Content {
|
||||
fn default() -> Self {
|
||||
// Exit from eframe only for non-mobile platforms.
|
||||
let os = OperatingSystem::from_target_os();
|
||||
|
@ -69,7 +69,7 @@ impl Default for Root {
|
|||
}
|
||||
}
|
||||
|
||||
impl ModalContainer for Root {
|
||||
impl ModalContainer for Content {
|
||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||
&self.allowed_modal_ids
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ impl ModalContainer for Root {
|
|||
}
|
||||
}
|
||||
|
||||
impl Root {
|
||||
impl Content {
|
||||
/// Identifier for exit confirmation [`Modal`].
|
||||
pub const EXIT_MODAL_ID: &'static str = "exit_confirmation_modal";
|
||||
/// Identifier for wallet opening [`Modal`].
|
||||
|
@ -98,45 +98,37 @@ impl Root {
|
|||
|
||||
/// Default width of side panel at application UI.
|
||||
pub const SIDE_PANEL_WIDTH: f32 = 400.0;
|
||||
/// Desktop window title height.
|
||||
pub const WINDOW_TITLE_HEIGHT: f32 = 38.0;
|
||||
/// Margin of window frame at desktop.
|
||||
pub const WINDOW_FRAME_MARGIN: f32 = 6.0;
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Draw modal content for current ui container.
|
||||
self.current_modal_ui(ui, cb);
|
||||
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui);
|
||||
let dual_panel = Self::is_dual_panel_mode(ui);
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui, dual_panel);
|
||||
|
||||
// Show network content.
|
||||
egui::SidePanel::left("network_panel")
|
||||
.resizable(false)
|
||||
.exact_width(panel_width)
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::white_or_black(false),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
||||
// Set content height as window height.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
let window_size = View::window_size(ui);
|
||||
rect.set_height(window_size.1);
|
||||
ui.allocate_ui_at_rect(rect, |ui| {
|
||||
self.network.ui(ui, cb);
|
||||
});
|
||||
});
|
||||
|
||||
// Show wallets content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill_deep(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
// Set content height as window height.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
let window_size = View::window_size(ui);
|
||||
rect.set_height(window_size.1);
|
||||
ui.allocate_ui_at_rect(rect, |ui| {
|
||||
self.wallets.ui(ui, cb);
|
||||
});
|
||||
});
|
||||
|
||||
// Show integrated node warning on Android if needed.
|
||||
if self.first_draw && OperatingSystem::from_target_os() == OperatingSystem::Android &&
|
||||
|
@ -153,19 +145,26 @@ impl Root {
|
|||
}
|
||||
|
||||
/// Get [`NetworkContent`] panel state and width.
|
||||
fn network_panel_state_width(ui: &mut egui::Ui) -> (bool, f32) {
|
||||
let dual_panel_mode = Self::is_dual_panel_mode(ui);
|
||||
let is_panel_open = dual_panel_mode || Self::is_network_panel_open();
|
||||
let panel_width = if dual_panel_mode {
|
||||
fn network_panel_state_width(ui: &mut egui::Ui, dual_panel: bool) -> (bool, f32) {
|
||||
let is_panel_open = dual_panel || Self::is_network_panel_open();
|
||||
let panel_width = if dual_panel {
|
||||
Self::SIDE_PANEL_WIDTH + View::get_left_inset()
|
||||
} else {
|
||||
View::window_size(ui).0
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
View::window_size(ui).0 - if View::is_desktop() && !is_fullscreen &&
|
||||
OperatingSystem::from_target_os() != OperatingSystem::Mac {
|
||||
Self::WINDOW_FRAME_MARGIN * 2.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
(is_panel_open, panel_width)
|
||||
}
|
||||
|
||||
/// Check if ui can show [`NetworkContent`] and [`WalletsContent`] at same time.
|
||||
pub fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
||||
pub fn is_dual_panel_mode(ui: &egui::Ui) -> bool {
|
||||
let (w, h) = View::window_size(ui);
|
||||
// Screen is wide if width is greater than height or just 20% smaller.
|
||||
let is_wide_screen = w > h || w + (w * 0.2) >= h;
|
|
@ -23,8 +23,8 @@ pub use title_panel::*;
|
|||
mod modal;
|
||||
pub use modal::*;
|
||||
|
||||
mod root;
|
||||
pub use root::*;
|
||||
mod content;
|
||||
pub use content::*;
|
||||
|
||||
mod network;
|
||||
pub use network::*;
|
||||
|
@ -40,3 +40,6 @@ pub use qr::*;
|
|||
|
||||
mod file;
|
||||
pub use file::*;
|
||||
|
||||
mod pull_to_refresh;
|
||||
pub use pull_to_refresh::*;
|
|
@ -18,9 +18,10 @@ use parking_lot::RwLock;
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use egui::{Align2, Rect, RichText, Rounding, Stroke, Vec2};
|
||||
use egui::epaint::{RectShape, Shadow};
|
||||
use egui::os::OperatingSystem;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::{Root, View};
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::types::{ModalPosition, ModalState};
|
||||
|
||||
lazy_static! {
|
||||
|
@ -43,9 +44,9 @@ pub struct Modal {
|
|||
|
||||
impl Modal {
|
||||
/// Margin from [`Modal`] window at top/left/right.
|
||||
const DEFAULT_MARGIN: f32 = 6.0;
|
||||
const DEFAULT_MARGIN: f32 = 8.0;
|
||||
/// Maximum width of the content.
|
||||
const DEFAULT_WIDTH: f32 = Root::SIDE_PANEL_WIDTH - (2.0 * Self::DEFAULT_MARGIN);
|
||||
const DEFAULT_WIDTH: f32 = Content::SIDE_PANEL_WIDTH - (2.0 * Self::DEFAULT_MARGIN);
|
||||
|
||||
/// Create closeable [`Modal`] with center position.
|
||||
pub fn new(id: &'static str) -> Self {
|
||||
|
@ -160,7 +161,22 @@ impl Modal {
|
|||
|
||||
/// Draw [`egui::Window`] with provided content.
|
||||
fn window_ui(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
let rect = ctx.screen_rect();
|
||||
let is_fullscreen = ctx.input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||
|
||||
let mut rect = ctx.screen_rect();
|
||||
if View::is_desktop() && !is_mac_os {
|
||||
let margin = if !is_fullscreen {
|
||||
Content::WINDOW_FRAME_MARGIN
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
rect = rect.shrink(margin - 0.5);
|
||||
rect.min += egui::vec2(0.0, Content::WINDOW_TITLE_HEIGHT + 0.5);
|
||||
rect.max.x += 0.5;
|
||||
}
|
||||
egui::Window::new("modal_bg_window")
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
|
@ -180,7 +196,7 @@ impl Modal {
|
|||
let width = f32::min(available_width, Self::DEFAULT_WIDTH);
|
||||
|
||||
// Show main content Window at given position.
|
||||
let (content_align, content_offset) = self.modal_position();
|
||||
let (content_align, content_offset) = self.modal_position(is_fullscreen);
|
||||
let layer_id = egui::Window::new(format!("modal_window_{}", self.id))
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
|
@ -212,13 +228,26 @@ impl Modal {
|
|||
}
|
||||
|
||||
/// Get [`egui::Window`] position based on [`ModalPosition`].
|
||||
fn modal_position(&self) -> (Align2, Vec2) {
|
||||
fn modal_position(&self, is_fullscreen: bool) -> (Align2, Vec2) {
|
||||
let align = match self.position {
|
||||
ModalPosition::CenterTop => Align2::CENTER_TOP,
|
||||
ModalPosition::Center => Align2::CENTER_CENTER
|
||||
};
|
||||
|
||||
let x_align = View::get_left_inset() - View::get_right_inset();
|
||||
let y_align = View::get_top_inset() + Self::DEFAULT_MARGIN;
|
||||
|
||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||
let extra_y = if View::is_desktop() && !is_mac_os {
|
||||
Content::WINDOW_TITLE_HEIGHT + if !is_fullscreen {
|
||||
Content::WINDOW_FRAME_MARGIN
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let y_align = View::get_top_inset() + Self::DEFAULT_MARGIN + extra_y;
|
||||
|
||||
let offset = match self.position {
|
||||
ModalPosition::CenterTop => Vec2::new(x_align, y_align),
|
||||
ModalPosition::Center => Vec2::new(x_align, 0.0)
|
||||
|
@ -248,6 +277,7 @@ impl Modal {
|
|||
rounding,
|
||||
fill: Colors::fill(),
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
|
@ -280,6 +310,7 @@ impl Modal {
|
|||
},
|
||||
fill: Colors::yellow(),
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
|
@ -288,12 +319,12 @@ impl Modal {
|
|||
// Draw title content.
|
||||
let title_resp = ui.allocate_ui_at_rect(rect, |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(9.0);
|
||||
ui.add_space(Self::DEFAULT_MARGIN);
|
||||
ui.label(RichText::new(self.title.as_ref().unwrap())
|
||||
.size(19.0)
|
||||
.color(Colors::title(true))
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
ui.add_space(Self::DEFAULT_MARGIN);
|
||||
});
|
||||
}).response;
|
||||
|
||||
|
|
|
@ -12,14 +12,15 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Margin, RichText, ScrollArea, Stroke};
|
||||
use std::time::Duration;
|
||||
use egui::{Margin, RichText, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROWS_COUNTER_CLOCKWISE, BRIEFCASE, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitlePanel, View};
|
||||
use crate::gui::views::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Content, TitlePanel, View};
|
||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
||||
use crate::gui::views::types::{TitleContentType, TitleType};
|
||||
use crate::node::{Node, NodeError};
|
||||
|
@ -30,7 +31,7 @@ pub struct NetworkContent {
|
|||
/// Current integrated node tab content.
|
||||
node_tab_content: Box<dyn NetworkTab>,
|
||||
/// Connections content.
|
||||
connections: ConnectionsContent
|
||||
connections: ConnectionsContent,
|
||||
}
|
||||
|
||||
impl Default for NetworkContent {
|
||||
|
@ -44,36 +45,38 @@ impl Default for NetworkContent {
|
|||
|
||||
impl NetworkContent {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Flag to show connections or integrated node content.
|
||||
let show_connections = AppConfig::show_connections_network_panel();
|
||||
let dual_panel = Content::is_dual_panel_mode(ui);
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, show_connections);
|
||||
|
||||
// Show integrated node tabs content.
|
||||
egui::TopBottomPanel::bottom("node_tabs_panel")
|
||||
if !show_connections {
|
||||
egui::TopBottomPanel::bottom("node_tabs_content")
|
||||
.min_height(0.5)
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: 4.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
left: View::get_left_inset() + View::TAB_ITEMS_PADDING,
|
||||
right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||
top: View::TAB_ITEMS_PADDING,
|
||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||
},
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !show_connections, |ui| {
|
||||
.show_inside(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show tabs content.
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.tabs_ui(ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Show current node tab content.
|
||||
egui::SidePanel::right("node_tab_content_panel")
|
||||
egui::SidePanel::right("node_tab_content")
|
||||
.resizable(false)
|
||||
.exact_width(ui.available_width())
|
||||
.frame(egui::Frame {
|
||||
|
@ -93,7 +96,6 @@ impl NetworkContent {
|
|||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
// Draw node tab content.
|
||||
self.node_tab_content.ui(ui, cb);
|
||||
});
|
||||
});
|
||||
|
@ -101,11 +103,7 @@ impl NetworkContent {
|
|||
// Show connections content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: if show_connections {
|
||||
View::item_stroke()
|
||||
} else {
|
||||
Stroke::NONE
|
||||
},
|
||||
stroke: View::item_stroke(),
|
||||
inner_margin: Margin {
|
||||
left: if show_connections {
|
||||
View::get_left_inset() + 4.0
|
||||
|
@ -118,15 +116,16 @@ impl NetworkContent {
|
|||
0.0
|
||||
},
|
||||
top: 3.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
bottom: if View::is_desktop() && show_connections {
|
||||
6.0
|
||||
} else {
|
||||
4.0
|
||||
},
|
||||
},
|
||||
fill: Colors::button(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
if !show_connections {
|
||||
return;
|
||||
}
|
||||
ScrollArea::vertical()
|
||||
.id_source("connections_content")
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
|
@ -134,8 +133,8 @@ impl NetworkContent {
|
|||
.show(ui, |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let max_width = if !Root::is_dual_panel_mode(ui) {
|
||||
Root::SIDE_PANEL_WIDTH * 1.3
|
||||
let max_width = if !dual_panel {
|
||||
Content::SIDE_PANEL_WIDTH * 1.3
|
||||
} else {
|
||||
ui.available_width()
|
||||
};
|
||||
|
@ -146,9 +145,11 @@ impl NetworkContent {
|
|||
});
|
||||
});
|
||||
|
||||
// Redraw after delay if node is syncing to update stats.
|
||||
// Redraw after delay.
|
||||
if Node::is_running() {
|
||||
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
||||
} else if show_connections {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +157,7 @@ impl NetworkContent {
|
|||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(4.0, 0.0);
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
// Setup vertical padding inside tab button.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
||||
|
||||
|
@ -202,7 +203,7 @@ impl NetworkContent {
|
|||
// Draw title panel.
|
||||
TitlePanel::ui(TitleType::Single(title_content), |ui| {
|
||||
if !show_connections {
|
||||
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |_| {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
if AppConfig::show_connections_network_panel() {
|
||||
ExternalConnection::start_ext_conn_availability_check();
|
||||
|
@ -210,9 +211,9 @@ impl NetworkContent {
|
|||
});
|
||||
}
|
||||
}, |ui| {
|
||||
if !Root::is_dual_panel_mode(ui) {
|
||||
View::title_button(ui, BRIEFCASE, || {
|
||||
Root::toggle_network_panel();
|
||||
if !Content::is_dual_panel_mode(ui) {
|
||||
View::title_button_big(ui, BRIEFCASE, |_| {
|
||||
Content::toggle_network_panel();
|
||||
});
|
||||
}
|
||||
}, ui);
|
||||
|
|
|
@ -19,7 +19,7 @@ use grin_servers::{DiffBlock, ServerStats};
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{NetworkContent, Root, View};
|
||||
use crate::gui::views::{NetworkContent, Content, View};
|
||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
||||
use crate::node::Node;
|
||||
|
||||
|
@ -66,7 +66,7 @@ impl NetworkTab for NetworkMetrics {
|
|||
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
let stats = server_stats.as_ref().unwrap();
|
||||
// Show emission and difficulty info.
|
||||
info_ui(ui, stats);
|
||||
|
|
|
@ -20,7 +20,7 @@ use grin_servers::WorkerStats;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_SIMPLE_MINUS, FOLDER_SIMPLE_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{NetworkContent, Root, View};
|
||||
use crate::gui::views::{NetworkContent, Content, View};
|
||||
use crate::gui::views::network::setup::StratumSetup;
|
||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
@ -82,7 +82,7 @@ impl NetworkTab for NetworkMining {
|
|||
.show(ui, |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.stratum_server_setup.ui(ui, cb);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ use grin_servers::PeerStats;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, SHARE_NETWORK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{NetworkContent, Root, View};
|
||||
use crate::gui::views::{NetworkContent, Content, View};
|
||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
|
@ -60,7 +60,7 @@ impl NetworkTab for NetworkNode {
|
|||
.show(ui, |ui| {
|
||||
ui.add_space(2.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show node stats content.
|
||||
node_stats_ui(ui);
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ use egui::scroll_area::ScrollBarVisibility;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::ARROW_COUNTER_CLOCKWISE;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Root, View};
|
||||
use crate::gui::views::{Modal, Content, View};
|
||||
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition};
|
||||
|
@ -91,7 +91,7 @@ impl NetworkTab for NetworkSettings {
|
|||
.show(ui, |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Draw node setup section.
|
||||
self.node.ui(ui, cb);
|
||||
|
||||
|
|
370
src/gui/views/pull_to_refresh.rs
Normal file
370
src/gui/views/pull_to_refresh.rs
Normal file
|
@ -0,0 +1,370 @@
|
|||
// Copyright 2024 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 egui::scroll_area::ScrollAreaOutput;
|
||||
use egui::{Sense, Align2, Area, Color32, Id, Rect, Response, Widget, Vec2};
|
||||
use egui::epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
||||
|
||||
/// A spinner widget used to indicate loading.
|
||||
/// This was taken from egui and modified slightly to allow passing a progress value
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
#[derive(Default)]
|
||||
pub struct ProgressSpinner {
|
||||
/// Uses the style's `interact_size` if `None`.
|
||||
size: Option<f32>,
|
||||
color: Option<Color32>,
|
||||
progress: Option<f64>,
|
||||
}
|
||||
|
||||
impl ProgressSpinner {
|
||||
/// Create a new spinner that uses the style's `interact_size` unless changed.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Sets the spinner's size. The size sets both the height and width, as the spinner is always
|
||||
/// square. If the size isn't set explicitly, the active style's `interact_size` is used.
|
||||
#[allow(unused)]
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the spinner's color.
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the spinner's progress.
|
||||
/// Should be in the range `[0.0, 1.0]`.
|
||||
pub fn progress(mut self, progress: impl Into<Option<f64>>) -> Self {
|
||||
self.progress = progress.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Paint the spinner in the given rectangle.
|
||||
pub fn paint_at(&self, ui: &egui::Ui, rect: Rect) {
|
||||
if ui.is_rect_visible(rect) {
|
||||
ui.ctx().request_repaint(); // because it is animated
|
||||
|
||||
let color = self
|
||||
.color
|
||||
.unwrap_or_else(|| ui.visuals().strong_text_color());
|
||||
let radius = (rect.height() / 2.0) - 2.0;
|
||||
let n_points = 20;
|
||||
|
||||
let (start_angle, end_angle) = if let Some(progress) = self.progress {
|
||||
let start_angle = 0f64.to_radians();
|
||||
let end_angle = start_angle + 360f64.to_radians() * progress;
|
||||
(start_angle, end_angle)
|
||||
} else {
|
||||
let time = ui.input(|i| i.time);
|
||||
let start_angle = time * std::f64::consts::TAU;
|
||||
let end_angle = start_angle + 240f64.to_radians() * time.sin();
|
||||
(start_angle, end_angle)
|
||||
};
|
||||
|
||||
let points: Vec<Pos2> = (0..=n_points)
|
||||
.map(|i| {
|
||||
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
rect.center() + radius * vec2(cos as f32, sin as f32)
|
||||
})
|
||||
.collect();
|
||||
ui.painter()
|
||||
.add(Shape::line(points, Stroke::new(3.0, color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ProgressSpinner {
|
||||
fn ui(self, ui: &mut egui::Ui) -> Response {
|
||||
let size = self
|
||||
.size
|
||||
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover());
|
||||
self.paint_at(ui, rect);
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of the pull to refresh widget.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PullToRefreshState {
|
||||
/// The widget is idle, no refresh is happening.
|
||||
Idle,
|
||||
/// The user is dragging.
|
||||
Dragging {
|
||||
/// `distance` is the distance the user dragged.
|
||||
distance: f32,
|
||||
/// `far_enough` is true if the user dragged far enough to trigger a refresh.
|
||||
far_enough: bool,
|
||||
},
|
||||
/// The user dragged far enough to trigger a refresh and released the pointer.
|
||||
DoRefresh,
|
||||
/// The refresh is currently happening.
|
||||
Refreshing,
|
||||
}
|
||||
|
||||
impl PullToRefreshState {
|
||||
fn progress(&self, min_distance: f32) -> Option<f64> {
|
||||
match self {
|
||||
PullToRefreshState::Idle => Some(0.0),
|
||||
PullToRefreshState::Dragging { distance, .. } => {
|
||||
Some((distance / min_distance).min(1.0).max(0.0) as f64)
|
||||
}
|
||||
PullToRefreshState::DoRefresh => Some(1.0),
|
||||
PullToRefreshState::Refreshing => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The response of the pull to refresh widget.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PullToRefreshResponse<T> {
|
||||
/// Current state of the pull to refresh widget.
|
||||
pub state: PullToRefreshState,
|
||||
/// The inner response of the widget you wrapped in [`PullToRefresh::ui`] or [`PullToRefresh::scroll_area_ui`].
|
||||
pub inner: T,
|
||||
}
|
||||
|
||||
impl<T> PullToRefreshResponse<T> {
|
||||
/// Returns true if the user dragged far enough to trigger a refresh.
|
||||
pub fn should_refresh(&self) -> bool {
|
||||
matches!(self.state, PullToRefreshState::DoRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that allows the user to pull to refresh.
|
||||
pub struct PullToRefresh {
|
||||
id: Id,
|
||||
loading: bool,
|
||||
min_refresh_distance: f32,
|
||||
can_refresh: bool,
|
||||
}
|
||||
|
||||
impl PullToRefresh {
|
||||
/// Creates a new pull to refresh widget.
|
||||
/// If `loading` is true, the widget will show the loading indicator.
|
||||
pub fn new(loading: bool) -> Self {
|
||||
Self {
|
||||
id: Id::new("pull_to_refresh"),
|
||||
loading,
|
||||
min_refresh_distance: 100.0,
|
||||
can_refresh: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the minimum distance the user needs to drag to trigger a refresh.
|
||||
pub fn min_refresh_distance(mut self, min_refresh_distance: f32) -> Self {
|
||||
self.min_refresh_distance = min_refresh_distance;
|
||||
self
|
||||
}
|
||||
|
||||
/// You need to provide a id if you use multiple pull to refresh widgets at once.
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
/// If `can_refresh` is false, pulling will not trigger a refresh.
|
||||
pub fn can_refresh(mut self, can_refresh: bool) -> Self {
|
||||
self.can_refresh = can_refresh;
|
||||
self
|
||||
}
|
||||
|
||||
/// Shows the pull to refresh widget.
|
||||
/// Note: If you want to use the pull to refresh widget in a scroll area, use [`Self::scroll_area_ui`].
|
||||
/// You might want to disable text selection via [`egui::style::Interaction`]
|
||||
/// to avoid conflicts with the drag gesture.
|
||||
pub fn ui<T>(
|
||||
self,
|
||||
ui: &mut egui::Ui,
|
||||
content: impl FnOnce(&mut egui::Ui) -> T,
|
||||
) -> PullToRefreshResponse<T> {
|
||||
let mut child = ui.child_ui(ui.available_rect_before_wrap(), *ui.layout(), None);
|
||||
|
||||
let output = content(&mut child);
|
||||
|
||||
let can_refresh = self.can_refresh;
|
||||
let state = self.internal_ui(ui, can_refresh, None, child.min_rect());
|
||||
|
||||
PullToRefreshResponse {
|
||||
state,
|
||||
inner: output,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows the pull to refresh widget, wrapping a [egui::ScrollArea].
|
||||
/// Pass the output of the scroll area to the content function.
|
||||
pub fn scroll_area_ui<T>(
|
||||
self,
|
||||
ui: &mut egui::Ui,
|
||||
content: impl FnOnce(&mut egui::Ui) -> ScrollAreaOutput<T>,
|
||||
) -> PullToRefreshResponse<ScrollAreaOutput<T>> {
|
||||
let scroll_output = content(ui);
|
||||
let content_rect = scroll_output.inner_rect;
|
||||
let can_refresh = scroll_output.state.offset.y == 0.0 && self.can_refresh;
|
||||
// This is the id used in the Sense of the scroll area
|
||||
// I hope this id is stable across egui patches...
|
||||
let allow_dragged_id = scroll_output.id.with("area");
|
||||
let state = self.internal_ui(ui, can_refresh, Some(allow_dragged_id), content_rect);
|
||||
PullToRefreshResponse {
|
||||
state,
|
||||
inner: scroll_output,
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_ui(
|
||||
self,
|
||||
ui: &mut egui::Ui,
|
||||
can_refresh: bool,
|
||||
allow_dragged_id: Option<Id>,
|
||||
content_rect: Rect,
|
||||
) -> PullToRefreshState {
|
||||
let last_state = ui.data_mut(|data| {
|
||||
data.get_temp_mut_or(self.id, PullToRefreshState::Idle)
|
||||
.clone()
|
||||
});
|
||||
|
||||
let mut state = last_state;
|
||||
if self.loading {
|
||||
state = PullToRefreshState::Refreshing;
|
||||
}
|
||||
|
||||
if !self.loading && matches!(state, PullToRefreshState::Refreshing) {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
|
||||
if can_refresh && !self.loading {
|
||||
let sense = ui.interact(content_rect, self.id, Sense::hover());
|
||||
|
||||
let is_something_blocking_drag = ui.ctx().dragged_id().is_some()
|
||||
&& !allow_dragged_id.map_or(false, |id| ui.ctx().is_being_dragged(id));
|
||||
|
||||
if sense.contains_pointer() && !is_something_blocking_drag {
|
||||
let (delta, any_released) = ui.input(|input| {
|
||||
(
|
||||
if input.pointer.is_decidedly_dragging() {
|
||||
Some(input.pointer.delta())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
input.pointer.any_released(),
|
||||
)
|
||||
});
|
||||
if let Some(delta) = delta {
|
||||
if matches!(state, PullToRefreshState::Idle) {
|
||||
state = PullToRefreshState::Dragging {
|
||||
distance: 0.0,
|
||||
far_enough: false,
|
||||
};
|
||||
}
|
||||
if let PullToRefreshState::Dragging { distance: drag, .. } = state.clone() {
|
||||
let dist = drag + delta.y;
|
||||
state = PullToRefreshState::Dragging {
|
||||
distance: dist,
|
||||
far_enough: dist > self.min_refresh_distance,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
if any_released {
|
||||
if let PullToRefreshState::Dragging {
|
||||
far_enough: enough, ..
|
||||
} = state.clone()
|
||||
{
|
||||
if enough {
|
||||
state = PullToRefreshState::DoRefresh;
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
|
||||
if self.loading {
|
||||
state = PullToRefreshState::Refreshing;
|
||||
}
|
||||
|
||||
let spinner_size = Vec2::splat(24.0);
|
||||
|
||||
let progress_for_offset = match &state {
|
||||
PullToRefreshState::Idle => 0.0,
|
||||
PullToRefreshState::Dragging { .. } => {
|
||||
state.progress(self.min_refresh_distance).unwrap_or(1.0)
|
||||
}
|
||||
PullToRefreshState::DoRefresh => 1.0,
|
||||
PullToRefreshState::Refreshing => 1.0,
|
||||
} as f32;
|
||||
|
||||
let anim_progress = ui.ctx().animate_value_with_time(
|
||||
self.id.with("offset_top"),
|
||||
progress_for_offset,
|
||||
ui.style().animation_time,
|
||||
);
|
||||
|
||||
let offset_top = -spinner_size.y + spinner_size.y * anim_progress * 2.0;
|
||||
|
||||
if anim_progress > 0.0 {
|
||||
Area::new(Id::new("Pull to refresh indicator"))
|
||||
.fixed_pos(content_rect.center_top())
|
||||
.pivot(Align2::CENTER_TOP)
|
||||
.show(ui.ctx(), |ui| {
|
||||
let (rect, _) = ui.allocate_exact_size(spinner_size, Sense::hover());
|
||||
|
||||
ui.set_clip_rect(Rect::everything_below(rect.min.y));
|
||||
|
||||
let rect = rect.translate(Vec2::new(0.0, offset_top));
|
||||
|
||||
ui.painter().circle(
|
||||
rect.center(),
|
||||
spinner_size.x / 1.5,
|
||||
ui.style().visuals.widgets.inactive.bg_fill,
|
||||
ui.visuals().widgets.inactive.bg_stroke,
|
||||
);
|
||||
|
||||
let mut spinner_color = ui.style().visuals.widgets.inactive.fg_stroke.color;
|
||||
if anim_progress < 1.0 {
|
||||
spinner_color = Color32::from_rgba_unmultiplied(
|
||||
spinner_color.r(),
|
||||
spinner_color.g(),
|
||||
spinner_color.b(),
|
||||
(spinner_color.a() as f32 * 0.7).round() as u8,
|
||||
);
|
||||
}
|
||||
ProgressSpinner::new()
|
||||
.color(spinner_color)
|
||||
.progress(state.progress(self.min_refresh_distance))
|
||||
.paint_at(ui, rect);
|
||||
});
|
||||
}
|
||||
|
||||
ui.data_mut(|data| {
|
||||
data.insert_temp(self.id, state.clone());
|
||||
});
|
||||
|
||||
state
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ use std::sync::Arc;
|
|||
use parking_lot::RwLock;
|
||||
use std::thread;
|
||||
use egui::{SizeHint, TextureHandle, TextureOptions};
|
||||
use egui::epaint::RectShape;
|
||||
use egui::load::SizedTexture;
|
||||
use egui_extras::image::load_svg_bytes_with_size;
|
||||
use image::{ExtendedColorType, ImageEncoder};
|
||||
|
@ -235,11 +236,12 @@ impl QrCodeContent {
|
|||
rect.max -= egui::emath::vec2(10.0, 0.0);
|
||||
|
||||
// Create background shape.
|
||||
let mut bg_shape = egui::epaint::RectShape {
|
||||
let mut bg_shape = RectShape {
|
||||
rect,
|
||||
rounding: egui::Rounding::default(),
|
||||
fill: egui::Color32::WHITE,
|
||||
stroke: egui::Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: egui::Rect::ZERO
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ use egui::{Margin, Id};
|
|||
use egui_extras::{Size, Strip, StripBuilder};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::{Root, View};
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::types::{TitleContentType, TitleType};
|
||||
|
||||
/// Title panel with left/right action buttons and text in the middle.
|
||||
|
@ -44,20 +44,21 @@ impl TitlePanel {
|
|||
TitleContentType::Title(text) => text,
|
||||
TitleContentType::WithSubTitle(text, _, _) => text
|
||||
};
|
||||
let second_text = match first {
|
||||
TitleContentType::Title(text) => text,
|
||||
TitleContentType::WithSubTitle(text, _, _) => text
|
||||
};
|
||||
let id = Id::from(first_text.to_owned()).with(second_text);
|
||||
let id = Id::from(first_text.to_owned()).with("_dual");
|
||||
(id, true)
|
||||
},
|
||||
};
|
||||
// Draw title panel.
|
||||
egui::TopBottomPanel::top(id)
|
||||
.resizable(false)
|
||||
.exact_height(Self::DEFAULT_HEIGHT)
|
||||
.exact_height(Self::DEFAULT_HEIGHT + View::get_top_inset())
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Self::inner_margin(ui),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui),
|
||||
right: View::far_right_inset_margin(ui),
|
||||
top: View::get_top_inset(),
|
||||
bottom: 0.0,
|
||||
},
|
||||
fill: Colors::yellow(),
|
||||
..Default::default()
|
||||
})
|
||||
|
@ -65,7 +66,7 @@ impl TitlePanel {
|
|||
StripBuilder::new(ui)
|
||||
.size(Size::exact(Self::DEFAULT_HEIGHT))
|
||||
.size(if dual_title {
|
||||
Size::exact(Root::SIDE_PANEL_WIDTH - 2.0 * Self::DEFAULT_HEIGHT)
|
||||
Size::exact(Content::SIDE_PANEL_WIDTH - 2.0 * Self::DEFAULT_HEIGHT)
|
||||
} else {
|
||||
Size::remainder()
|
||||
})
|
||||
|
@ -129,16 +130,6 @@ impl TitlePanel {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculate inner margin based on display insets (cutouts).
|
||||
fn inner_margin(ui: &mut egui::Ui) -> Margin {
|
||||
Margin {
|
||||
left: View::far_left_inset_margin(ui),
|
||||
right: View::far_right_inset_margin(ui),
|
||||
top: View::get_top_inset(),
|
||||
bottom: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw content for [`TitleType::WithSubTitle`] type.
|
||||
fn with_sub_title(builder: StripBuilder, title: String, subtitle: String, animate_sub: bool) {
|
||||
builder
|
||||
|
|
|
@ -33,6 +33,12 @@ use crate::gui::views::types::TextEditOptions;
|
|||
pub struct View;
|
||||
|
||||
impl View {
|
||||
/// Check if current platform is desktop
|
||||
pub fn is_desktop() -> bool {
|
||||
let os = OperatingSystem::from_target_os();
|
||||
os != OperatingSystem::Android && os != OperatingSystem::IOS
|
||||
}
|
||||
|
||||
/// Format timestamp in seconds with local UTC offset.
|
||||
pub fn format_time(ts: i64) -> String {
|
||||
let utc_offset = chrono::Local::now().offset().local_minus_utc();
|
||||
|
@ -76,17 +82,9 @@ impl View {
|
|||
}
|
||||
|
||||
/// Get width and height of app window.
|
||||
pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) {
|
||||
|
||||
ui.ctx().input(|i| {
|
||||
return match i.viewport().inner_rect {
|
||||
None => {
|
||||
let rect = i.screen_rect;
|
||||
pub fn window_size(ui: &egui::Ui) -> (f32, f32) {
|
||||
let rect = ui.ctx().screen_rect();
|
||||
(rect.width(), rect.height())
|
||||
},
|
||||
Some(rect) => (rect.width(), rect.height())
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Callback on Enter key press event.
|
||||
|
@ -179,8 +177,18 @@ impl View {
|
|||
false
|
||||
}
|
||||
|
||||
/// Title button with transparent background fill color, contains only icon.
|
||||
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
|
||||
/// Draw big size title button.
|
||||
pub fn title_button_big(ui: &mut egui::Ui, icon: &str, action: impl FnOnce(&mut egui::Ui)) {
|
||||
Self::title_button(ui, 22.0, icon, action);
|
||||
}
|
||||
|
||||
/// Draw small size title button.
|
||||
pub fn title_button_small(ui: &mut egui::Ui, icon: &str, action: impl FnOnce(&mut egui::Ui)) {
|
||||
Self::title_button(ui, 16.0, icon, action);
|
||||
}
|
||||
|
||||
/// Draw title button with transparent background color, contains only icon.
|
||||
fn title_button(ui: &mut egui::Ui, size: f32, icon: &str, action: impl FnOnce(&mut egui::Ui)) {
|
||||
ui.scope(|ui| {
|
||||
// Disable strokes.
|
||||
ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE;
|
||||
|
@ -190,7 +198,7 @@ impl View {
|
|||
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
||||
|
||||
// Setup text.
|
||||
let wt = RichText::new(icon.to_string()).size(22.0).color(Colors::title(true));
|
||||
let wt = RichText::new(icon.to_string()).size(size).color(Colors::title(true));
|
||||
// Draw button.
|
||||
let br = Button::new(wt)
|
||||
.fill(Colors::TRANSPARENT)
|
||||
|
@ -198,11 +206,14 @@ impl View {
|
|||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
br.surrender_focus();
|
||||
if Self::touched(ui, br) {
|
||||
(action)();
|
||||
(action)(ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Padding for tab items.
|
||||
pub const TAB_ITEMS_PADDING: f32 = 5.0;
|
||||
|
||||
/// Tab button with white background fill color, contains only icon.
|
||||
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, action: impl FnOnce()) {
|
||||
ui.scope(|ui| {
|
||||
|
@ -413,6 +424,7 @@ impl View {
|
|||
.ui(ui);
|
||||
// Show keyboard on click.
|
||||
if text_edit_resp.clicked() {
|
||||
text_edit_resp.request_focus();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
// Setup focus on input field.
|
||||
|
@ -514,6 +526,7 @@ impl View {
|
|||
},
|
||||
fill: Colors::TRANSPARENT,
|
||||
stroke: Self::item_stroke(),
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::AppConfig;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_LOCK, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SPINNER, SUITCASE, WARNING_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Root, TitlePanel, View};
|
||||
use crate::gui::views::{Modal, Content, TitlePanel, View};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions, TitleContentType, TitleType};
|
||||
use crate::gui::views::wallets::creation::WalletCreation;
|
||||
use crate::gui::views::wallets::types::WalletTabType;
|
||||
|
@ -100,14 +100,22 @@ impl WalletsContent {
|
|||
|
||||
// Setup panels parameters.
|
||||
let dual_panel = is_dual_panel_mode(ui);
|
||||
let open_wallet_panel = show_wallet || create_wallet || empty_list;
|
||||
let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet);
|
||||
let content_width = ui.available_width();
|
||||
|
||||
let root_dual_panel = Content::is_dual_panel_mode(ui);
|
||||
|
||||
// Flag to check if wallet list is hidden on the screen.
|
||||
let list_hidden = content_width == 0.0 || empty_list || create_wallet
|
||||
|| (dual_panel && show_wallet && !self.show_wallets_at_dual_panel)
|
||||
|| (!dual_panel && show_wallet) ||
|
||||
(!root_dual_panel && Content::is_network_panel_open());
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, dual_panel, create_wallet, show_wallet);
|
||||
|
||||
// Show wallet panel content.
|
||||
let wallet_panel_opened = self.wallet_panel_opened();
|
||||
egui::SidePanel::right("wallet_panel")
|
||||
.resizable(false)
|
||||
.exact_width(wallet_panel_width)
|
||||
|
@ -124,11 +132,7 @@ impl WalletsContent {
|
|||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, open_wallet_panel, |ui| {
|
||||
// Do not draw content on zero width.
|
||||
if content_width == 0.0 {
|
||||
return;
|
||||
}
|
||||
.show_animated_inside(ui, wallet_panel_opened, |ui| {
|
||||
if create_wallet || !show_wallet {
|
||||
// Show wallet creation content.
|
||||
self.creation_content.ui(ui, cb, |wallet| {
|
||||
|
@ -147,7 +151,7 @@ impl WalletsContent {
|
|||
let mut rect = ui.available_rect_before_wrap();
|
||||
let mut width = ui.available_width();
|
||||
if dual_panel && self.show_wallets_at_dual_panel {
|
||||
width = content_width - Root::SIDE_PANEL_WIDTH;
|
||||
width = content_width - Content::SIDE_PANEL_WIDTH;
|
||||
}
|
||||
rect.set_width(width);
|
||||
// Show wallet content.
|
||||
|
@ -160,29 +164,23 @@ impl WalletsContent {
|
|||
}
|
||||
});
|
||||
|
||||
// Flag to check if wallet list is hidden on the screen.
|
||||
let list_hidden = content_width == 0.0 || empty_list || create_wallet
|
||||
|| (dual_panel && show_wallet && !self.show_wallets_at_dual_panel)
|
||||
|| (!dual_panel && show_wallet);
|
||||
|
||||
// Setup flag to show wallets bottom panel if wallet is not showing
|
||||
// at non-dual panel mode and network is no open or showing at dual panel mode.
|
||||
let show_bottom_panel = !list_hidden &&
|
||||
((!show_wallet && !dual_panel && !Root::is_network_panel_open()) || dual_panel);
|
||||
|
||||
// Show wallets bottom panel.
|
||||
let show_bottom_panel = !list_hidden || dual_panel;
|
||||
if show_bottom_panel {
|
||||
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: 4.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
left: View::get_left_inset() + View::TAB_ITEMS_PADDING,
|
||||
right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||
top: View::TAB_ITEMS_PADDING,
|
||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, show_bottom_panel, |ui| {
|
||||
.show_inside(ui, |ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
|
||||
|
||||
|
@ -193,7 +191,9 @@ impl WalletsContent {
|
|||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Show wallet list.
|
||||
egui::CentralPanel::default()
|
||||
.frame(if list_hidden {
|
||||
egui::Frame::default()
|
||||
|
@ -211,15 +211,29 @@ impl WalletsContent {
|
|||
}
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
// Update ui after 1 sec at single panel mode.
|
||||
if !dual_panel {
|
||||
if !list_hidden && !dual_panel {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
}
|
||||
// Show list of wallets.
|
||||
self.wallet_list_ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if wallet panel is showing.
|
||||
pub fn wallet_panel_opened(&self) -> bool {
|
||||
let empty_list = self.wallets.is_current_list_empty();
|
||||
empty_list || self.creating_wallet() || self.showing_wallet()
|
||||
}
|
||||
|
||||
/// Check if opened wallet is showing.
|
||||
pub fn showing_wallet(&self) -> bool {
|
||||
self.wallets.is_selected_open()
|
||||
}
|
||||
|
||||
/// Check if wallet is creating.
|
||||
pub fn creating_wallet(&self) -> bool {
|
||||
self.creation_content.can_go_back()
|
||||
}
|
||||
|
||||
/// Draw [`TitlePanel`] content.
|
||||
fn title_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
|
@ -263,11 +277,11 @@ impl WalletsContent {
|
|||
// Draw title panel.
|
||||
TitlePanel::ui(title_content, |ui| {
|
||||
if show_wallet && !dual_panel {
|
||||
View::title_button(ui, ARROW_LEFT, || {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
self.wallets.select(None);
|
||||
});
|
||||
} else if create_wallet {
|
||||
View::title_button(ui, ARROW_LEFT, || {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
self.creation_content.back();
|
||||
});
|
||||
} else if show_wallet && dual_panel {
|
||||
|
@ -276,19 +290,19 @@ impl WalletsContent {
|
|||
} else {
|
||||
SUITCASE
|
||||
};
|
||||
View::title_button(ui, list_icon, || {
|
||||
View::title_button_big(ui, list_icon, |_| {
|
||||
self.show_wallets_at_dual_panel = !show_list;
|
||||
AppConfig::toggle_show_wallets_at_dual_panel();
|
||||
});
|
||||
} else if !Root::is_dual_panel_mode(ui) {
|
||||
View::title_button(ui, GLOBE, || {
|
||||
Root::toggle_network_panel();
|
||||
} else if !Content::is_dual_panel_mode(ui) {
|
||||
View::title_button_big(ui, GLOBE, |_| {
|
||||
Content::toggle_network_panel();
|
||||
});
|
||||
};
|
||||
}, |ui| {
|
||||
View::title_button(ui, GEAR, || {
|
||||
View::title_button_big(ui, GEAR, |_| {
|
||||
// Show settings modal.
|
||||
Modal::new(Root::SETTINGS_MODAL)
|
||||
Modal::new(Content::SETTINGS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("settings"))
|
||||
.show();
|
||||
|
@ -309,10 +323,10 @@ impl WalletsContent {
|
|||
|| (show_wallet && !self.show_wallets_at_dual_panel) {
|
||||
ui.available_width()
|
||||
} else {
|
||||
ui.available_width() - Root::SIDE_PANEL_WIDTH
|
||||
ui.available_width() - Content::SIDE_PANEL_WIDTH
|
||||
};
|
||||
if dual_panel && show_wallet && self.show_wallets_at_dual_panel {
|
||||
let min_width = Root::SIDE_PANEL_WIDTH + View::get_right_inset();
|
||||
let min_width = Content::SIDE_PANEL_WIDTH + View::get_right_inset();
|
||||
f32::max(min_width, available_width)
|
||||
} else {
|
||||
available_width
|
||||
|
@ -329,7 +343,7 @@ impl WalletsContent {
|
|||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show application logo and name.
|
||||
View::app_logo_name_version(ui);
|
||||
ui.add_space(15.0);
|
||||
|
@ -588,7 +602,7 @@ impl WalletsContent {
|
|||
|
||||
/// Check if it's possible to show [`WalletsContent`] and [`WalletContent`] panels at same time.
|
||||
fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(ui);
|
||||
let dual_panel_root = Content::is_dual_panel_mode(ui);
|
||||
let max_width = ui.available_width();
|
||||
dual_panel_root && max_width >= (Root::SIDE_PANEL_WIDTH * 2.0) + View::get_right_inset()
|
||||
dual_panel_root && max_width >= (Content::SIDE_PANEL_WIDTH * 2.0) + View::get_right_inset()
|
||||
}
|
|
@ -19,7 +19,7 @@ use grin_util::ZeroingString;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, FOLDER_PLUS, SCAN, SHARE_FAT};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Root, View};
|
||||
use crate::gui::views::{Modal, Content, View};
|
||||
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
||||
use crate::gui::views::wallets::creation::MnemonicSetup;
|
||||
use crate::gui::views::wallets::creation::types::Step;
|
||||
|
@ -73,7 +73,6 @@ impl WalletCreation {
|
|||
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
stroke: View::default_stroke(),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 8.0,
|
||||
right: View::get_right_inset() + 8.0,
|
||||
|
@ -85,7 +84,7 @@ impl WalletCreation {
|
|||
.show_inside(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 2.0, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 2.0, |ui| {
|
||||
self.step_control_ui(ui, on_create, cb);
|
||||
});
|
||||
});
|
||||
|
@ -119,9 +118,9 @@ impl WalletCreation {
|
|||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
let max_width = if self.step == Some(Step::SetupConnection) {
|
||||
Root::SIDE_PANEL_WIDTH * 1.3
|
||||
Content::SIDE_PANEL_WIDTH * 1.3
|
||||
} else {
|
||||
Root::SIDE_PANEL_WIDTH * 2.0
|
||||
Content::SIDE_PANEL_WIDTH * 2.0
|
||||
};
|
||||
View::max_width_ui(ui, max_width, |ui| {
|
||||
self.step_content_ui(ui, cb);
|
||||
|
|
|
@ -17,7 +17,7 @@ use egui::{Id, RichText};
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::PENCIL;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, Root, View};
|
||||
use crate::gui::views::{CameraContent, Modal, Content, View};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult, TextEditOptions};
|
||||
use crate::wallet::Mnemonic;
|
||||
use crate::wallet::types::{PhraseMode, PhraseSize};
|
||||
|
@ -304,6 +304,8 @@ impl MnemonicSetup {
|
|||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Callback to save the word.
|
||||
let mut save = || {
|
||||
self.word_edit = self.word_edit.trim().to_string();
|
||||
|
||||
// Check if word is valid.
|
||||
let word_index = self.word_num_edit - 1;
|
||||
if !self.mnemonic.is_valid_word(&self.word_edit, word_index) {
|
||||
|
@ -432,7 +434,7 @@ impl MnemonicSetup {
|
|||
/// Calculate word list columns count based on available ui width.
|
||||
fn list_columns_count(ui: &mut egui::Ui) -> usize {
|
||||
let w = ui.available_width();
|
||||
let min_panel_w = Root::SIDE_PANEL_WIDTH - 12.0;
|
||||
let min_panel_w = Content::SIDE_PANEL_WIDTH - 12.0;
|
||||
let double_min_panel_w = min_panel_w * 2.0;
|
||||
if w >= min_panel_w * 1.5 && w < double_min_panel_w {
|
||||
3
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::AppConfig;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROWS_CLOCKWISE, BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, COPY, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, SCAN, SPINNER, USERS_THREE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, Root, View};
|
||||
use crate::gui::views::{CameraContent, Modal, Content, View};
|
||||
use crate::gui::views::types::{ModalPosition, QrScanResult, TextEditOptions};
|
||||
use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport, WalletSettings};
|
||||
use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType};
|
||||
|
@ -80,15 +80,18 @@ impl WalletContent {
|
|||
// Show modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
let dual_panel = Root::is_dual_panel_mode(ui);
|
||||
let dual_panel = Content::is_dual_panel_mode(ui);
|
||||
|
||||
let data = wallet.get_data();
|
||||
let data_empty = data.is_none();
|
||||
|
||||
// Show wallet balance panel not on Settings tab with selected non-repairing
|
||||
// wallet, when there is no error and data is not empty.
|
||||
let show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
|
||||
&& !wallet.sync_error() && !wallet.is_repairing();
|
||||
let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
|
||||
&& !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing();
|
||||
if wallet.get_current_ext_conn().is_none() && !Node::is_running() {
|
||||
show_balance = false;
|
||||
}
|
||||
egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier()))
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
|
@ -100,13 +103,17 @@ impl WalletContent {
|
|||
bottom: 0.0,
|
||||
},
|
||||
outer_margin: Margin {
|
||||
left: 0.0,
|
||||
left: if dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
bottom: if !dual_panel {
|
||||
0.0
|
||||
} else {
|
||||
bottom: if dual_panel {
|
||||
-1.0
|
||||
} else {
|
||||
-0.5
|
||||
},
|
||||
},
|
||||
..Default::default()
|
||||
|
@ -117,28 +124,29 @@ impl WalletContent {
|
|||
ui.add_space(1.0);
|
||||
}
|
||||
// Draw account info.
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.account_ui(ui, wallet, data.unwrap(), cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Show wallet tabs panel.
|
||||
egui::TopBottomPanel::bottom("wallet_tabs")
|
||||
let show_tabs = !Self::block_navigation_on_sync(wallet);
|
||||
egui::TopBottomPanel::bottom("wallet_tabs_content")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::get_right_inset() + 4.0,
|
||||
top: 4.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||
right: View::get_right_inset() + View::TAB_ITEMS_PADDING,
|
||||
top: View::TAB_ITEMS_PADDING,
|
||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !Self::block_navigation_on_sync(wallet), |ui| {
|
||||
.show_animated_inside(ui, show_tabs, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Draw wallet tabs.
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.tabs_ui(ui, wallet);
|
||||
});
|
||||
});
|
||||
|
@ -147,6 +155,16 @@ impl WalletContent {
|
|||
// Show tab content panel.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
outer_margin: Margin {
|
||||
left: if dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
},
|
||||
stroke: View::item_stroke(),
|
||||
fill: Colors::white_or_black(false),
|
||||
..Default::default()
|
||||
|
@ -512,7 +530,7 @@ impl WalletContent {
|
|||
fn tabs_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(4.0, 0.0);
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
// Setup vertical padding inside tab button.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
||||
|
||||
|
@ -558,13 +576,13 @@ impl WalletContent {
|
|||
} else if wallet.get_current_ext_conn().is_none() {
|
||||
if !Node::is_running() || Node::is_stopping() {
|
||||
View::center_content(ui, 108.0, |ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.5, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.5, |ui| {
|
||||
let text = t!("wallets.enable_node", "settings" => GEAR_FINE);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||
ui.add_space(8.0);
|
||||
// Show button to enable integrated node at non-dual root panel mode
|
||||
// or when network connections are not showing and node is not stopping
|
||||
let dual_panel_root = Root::is_dual_panel_mode(ui);
|
||||
let dual_panel_root = Content::is_dual_panel_mode(ui);
|
||||
if (!dual_panel_root || AppConfig::show_connections_network_panel())
|
||||
&& !Node::is_stopping() {
|
||||
let enable_text = format!("{} {}", POWER, t!("network.enable_node"));
|
||||
|
@ -622,7 +640,7 @@ impl WalletContent {
|
|||
/// Draw wallet sync progress content.
|
||||
pub fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.5, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.5, |ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(18.0);
|
||||
// Setup sync progress text.
|
||||
|
|
|
@ -24,7 +24,7 @@ use parking_lot::RwLock;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, DOWNLOAD_SIMPLE, PROHIBIT, QR_CODE, SCAN, UPLOAD_SIMPLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, Root, View};
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, Content, View};
|
||||
use crate::gui::views::types::{ModalPosition, QrScanResult, TextEditOptions};
|
||||
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
|
@ -144,7 +144,7 @@ impl WalletTab for WalletMessages {
|
|||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.ui(ui, wallet, cb);
|
||||
});
|
||||
});
|
||||
|
@ -665,6 +665,7 @@ impl WalletMessages {
|
|||
.response;
|
||||
// Show soft keyboard on click.
|
||||
if response_empty && resp.clicked() {
|
||||
resp.request_focus();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
if response_empty && resp.has_focus() {
|
||||
|
|
|
@ -17,7 +17,7 @@ use egui::scroll_area::ScrollBarVisibility;
|
|||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Root, View};
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::wallets::setup::{CommonSetup, ConnectionSetup, RecoverySetup};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
|
@ -80,7 +80,7 @@ impl WalletTab for WalletSettings {
|
|||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show common wallet setup.
|
||||
self.common_setup.ui(ui, wallet, cb);
|
||||
// Show wallet connections setup.
|
||||
|
|
|
@ -26,7 +26,7 @@ use grin_wallet_libwallet::SlatepackAddress;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, QrCodeContent, Root, View};
|
||||
use crate::gui::views::{CameraContent, Modal, QrCodeContent, Content, View};
|
||||
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
|
@ -106,7 +106,7 @@ impl WalletTab for WalletTransport {
|
|||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.ui(ui, wallet, cb);
|
||||
});
|
||||
});
|
||||
|
@ -260,21 +260,6 @@ impl WalletTransport {
|
|||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
});
|
||||
// Setup bridges status text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let bridges_text = match &bridge {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
}
|
||||
};
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::text(false)));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup Tor status text.
|
||||
let is_running = Tor::is_service_running(service_id);
|
||||
|
@ -292,7 +277,24 @@ impl WalletTransport {
|
|||
(X_CIRCLE, t!("transport.disconnected"))
|
||||
};
|
||||
let status_text = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::text(false)));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup bridges status text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let bridges_text = match &bridge {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
}
|
||||
};
|
||||
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@ use std::thread;
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui_pull_to_refresh::PullToRefresh;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_util::ToHex;
|
||||
use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType};
|
||||
|
@ -26,7 +25,7 @@ use parking_lot::RwLock;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, ARROW_CLOCKWISE, BRIDGE, BROOM, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, CLIPBOARD_TEXT, COPY, DOTS_THREE_CIRCLE, FILE_ARCHIVE, FILE_TEXT, GEAR_FINE, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, Root, View};
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, PullToRefresh, QrCodeContent, Content, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::types::WalletTab;
|
||||
use crate::gui::views::wallets::wallet::types::{GRIN, SLATEPACK_MESSAGE_HINT, WalletTabType};
|
||||
|
@ -146,7 +145,7 @@ impl WalletTransactions {
|
|||
let amount_conf = data.info.amount_awaiting_confirmation;
|
||||
let amount_fin = data.info.amount_awaiting_finalization;
|
||||
let amount_locked = data.info.amount_locked;
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show non-zero awaiting confirmation amount.
|
||||
if amount_conf != 0 {
|
||||
let awaiting_conf = amount_to_hr_string(amount_conf, true);
|
||||
|
@ -223,7 +222,7 @@ impl WalletTransactions {
|
|||
.auto_shrink([false; 2])
|
||||
.show_rows(ui, TX_ITEM_HEIGHT, txs.len(), |ui, row_range| {
|
||||
ui.add_space(1.0);
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
let padding = amount_conf != 0 || amount_fin != 0 || amount_locked != 0;
|
||||
for index in row_range {
|
||||
let tx = txs.get(index).unwrap();
|
||||
|
@ -856,6 +855,7 @@ impl WalletTransactions {
|
|||
.show(ui).response;
|
||||
// Show soft keyboard on click.
|
||||
if self.tx_info_finalize && resp.clicked() {
|
||||
resp.request_focus();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
if self.tx_info_finalize && resp.has_focus() {
|
||||
|
|
|
@ -112,7 +112,7 @@ pub fn app_creator<T: 'static>(app: App<T>) -> eframe::AppCreator
|
|||
// Setup fonts.
|
||||
setup_fonts(&cc.egui_ctx);
|
||||
// Return app instance.
|
||||
Box::new(app)
|
||||
Ok(Box::new(app))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -12,8 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use grim::gui::App;
|
||||
use grim::gui::platform::Desktop;
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
pub fn main() {
|
||||
#[allow(dead_code)]
|
||||
|
@ -36,6 +35,7 @@ fn real_main() {
|
|||
|
||||
use std::sync::Arc;
|
||||
use egui::pos2;
|
||||
use egui::os::OperatingSystem;
|
||||
use eframe::icon_data::from_png_bytes;
|
||||
|
||||
let platform = Desktop::default();
|
||||
|
@ -55,6 +55,7 @@ fn real_main() {
|
|||
let (width, height) = AppConfig::window_size();
|
||||
|
||||
let mut viewport = egui::ViewportBuilder::default()
|
||||
.with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
|
||||
.with_inner_size([width, height]);
|
||||
|
||||
// Setup an icon.
|
||||
|
@ -67,21 +68,41 @@ fn real_main() {
|
|||
viewport = viewport.with_position(pos2(x, y));
|
||||
}
|
||||
|
||||
// Setup window decorations.
|
||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||
viewport = viewport
|
||||
.with_fullsize_content_view(true)
|
||||
.with_title_shown(false)
|
||||
.with_titlebar_buttons_shown(false)
|
||||
.with_titlebar_shown(false)
|
||||
.with_transparent(true)
|
||||
.with_decorations(is_mac_os);
|
||||
|
||||
let mut options = eframe::NativeOptions {
|
||||
viewport,
|
||||
..Default::default()
|
||||
};
|
||||
options.renderer = eframe::Renderer::Wgpu;
|
||||
|
||||
// Use Glow renderer for Windows.
|
||||
let is_windows = OperatingSystem::from_target_os() == OperatingSystem::Windows;
|
||||
options.renderer = if is_windows {
|
||||
eframe::Renderer::Glow
|
||||
} else {
|
||||
eframe::Renderer::Wgpu
|
||||
};
|
||||
|
||||
match grim::start(options.clone(), grim::app_creator(App::new(platform.clone()))) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
// Start with Glow renderer on error.
|
||||
Err(e) => {
|
||||
if is_windows {
|
||||
panic!("{}", e);
|
||||
}
|
||||
// Start with another renderer on error.
|
||||
options.renderer = eframe::Renderer::Glow;
|
||||
match grim::start(options, grim::app_creator(App::new(platform))) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
panic!("Impossible to render");
|
||||
Err(e) => {
|
||||
panic!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
use grin_core::global::ChainTypes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::gui::views::Content;
|
||||
|
||||
use crate::node::{NodeConfig, PeersConfig};
|
||||
use crate::Settings;
|
||||
|
@ -69,10 +70,16 @@ impl Default for AppConfig {
|
|||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Default window width.
|
||||
pub const DEFAULT_WIDTH: f32 = 1280.0;
|
||||
/// Default window height.
|
||||
pub const DEFAULT_HEIGHT: f32 = 745.0;
|
||||
/// Desktop window frame margin sum, horizontal or vertical.
|
||||
const FRAME_MARGIN: f32 = Content::WINDOW_FRAME_MARGIN * 2.0;
|
||||
/// Default desktop window width.
|
||||
pub const DEFAULT_WIDTH: f32 = Content::SIDE_PANEL_WIDTH * 3.0 + Self::FRAME_MARGIN;
|
||||
/// Default desktop window height.
|
||||
pub const DEFAULT_HEIGHT: f32 = 706.0;
|
||||
/// Minimal desktop window width.
|
||||
pub const MIN_WIDTH: f32 = Content::SIDE_PANEL_WIDTH + Self::FRAME_MARGIN;
|
||||
/// Minimal desktop window height.
|
||||
pub const MIN_HEIGHT: f32 = 630.0 + Content::WINDOW_TITLE_HEIGHT + Self::FRAME_MARGIN;
|
||||
|
||||
/// Application configuration file name.
|
||||
pub const FILE_NAME: &'static str = "app.toml";
|
||||
|
|
|
@ -8,9 +8,14 @@
|
|||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Version="0.1.0" Language="1033" Name="Grim" Manufacturer="Ardocrat">
|
||||
<Package InstallerVersion="300" Compressed="yes"/>
|
||||
<Package Id="7a18ee67-b049-4462-b18f-9e7748685781" InstallerVersion="300" Compressed="yes"/>
|
||||
<Media Id="1" Cabinet="grim.cab" EmbedCab="yes" />
|
||||
|
||||
<MajorUpgrade
|
||||
DowngradeErrorMessage = "A newer version of [ProductName] is already installed."
|
||||
AllowSameVersionUpgrades = "yes"
|
||||
/>
|
||||
|
||||
<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>
|
||||
<Property Id='ARPPRODUCTICON' Value='ProductICO' />
|
||||
|
||||
|
|
Loading…
Reference in a new issue