ui: window resize, update egui (waiting for window dragging/resizing patch), pull to refresh widget, colors optimization, min window size, root panels switch optimization

This commit is contained in:
ardocrat 2024-06-27 12:30:27 +03:00
parent 735e9ad94d
commit 2469d3f021
18 changed files with 755 additions and 389 deletions

168
Cargo.lock generated
View file

@ -23,10 +23,6 @@ name = "accesskit"
version = "0.12.3" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b"
dependencies = [
"enumn",
"serde",
]
[[package]] [[package]]
name = "accesskit_consumer" name = "accesskit_consumer"
@ -199,7 +195,6 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"getrandom 0.2.15", "getrandom 0.2.15",
"once_cell", "once_cell",
"serde",
"version_check", "version_check",
"zerocopy", "zerocopy",
] ]
@ -1698,36 +1693,6 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
version = "0.11.1" version = "0.11.1"
@ -2107,12 +2072,12 @@ dependencies = [
[[package]] [[package]]
name = "d3d12" name = "d3d12"
version = "0.19.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"libloading 0.8.3", "libloading 0.7.4",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -2582,11 +2547,10 @@ dependencies = [
[[package]] [[package]]
name = "ecolor" name = "ecolor"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "20930a432bbd57a6d55e07976089708d4893f3d556cf42a0d79e9e321fa73b10"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde", "emath",
] ]
[[package]] [[package]]
@ -2653,11 +2617,10 @@ dependencies = [
[[package]] [[package]]
name = "eframe" name = "eframe"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "020e2ccef6bbcec71dbc542f7eed64a5846fc3076727f5746da8fd307c91bab2"
dependencies = [ dependencies = [
"ahash 0.8.11",
"bytemuck", "bytemuck",
"cocoa",
"document-features", "document-features",
"egui", "egui",
"egui-wgpu", "egui-wgpu",
@ -2666,17 +2629,18 @@ dependencies = [
"glow", "glow",
"glutin", "glutin",
"glutin-winit", "glutin-winit",
"image 0.24.9", "image 0.25.1",
"js-sys", "js-sys",
"log", "log",
"objc", "objc2 0.5.2",
"objc2-app-kit",
"objc2-foundation",
"parking_lot 0.12.3", "parking_lot 0.12.3",
"percent-encoding", "percent-encoding",
"pollster", "pollster",
"raw-window-handle 0.5.2", "raw-window-handle 0.5.2",
"raw-window-handle 0.6.2", "raw-window-handle 0.6.2",
"static_assertions", "static_assertions",
"thiserror",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -2689,23 +2653,22 @@ dependencies = [
[[package]] [[package]]
name = "egui" name = "egui"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "584c5d1bf9a67b25778a3323af222dbe1a1feb532190e103901187f92c7fe29a"
dependencies = [ dependencies = [
"accesskit", "accesskit",
"ahash 0.8.11", "ahash 0.8.11",
"emath",
"epaint", "epaint",
"log", "log",
"nohash-hasher", "nohash-hasher",
"serde",
] ]
[[package]] [[package]]
name = "egui-wgpu" name = "egui-wgpu"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "469ff65843f88a702b731a1532b7d03b0e8e96d283e70f3a22b0e06c46cb9b37"
dependencies = [ dependencies = [
"ahash 0.8.11",
"bytemuck", "bytemuck",
"document-features", "document-features",
"egui", "egui",
@ -2721,10 +2684,10 @@ dependencies = [
[[package]] [[package]]
name = "egui-winit" name = "egui-winit"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "2e3da0cbe020f341450c599b35b92de4af7b00abde85624fd16f09c885573609"
dependencies = [ dependencies = [
"accesskit_winit", "accesskit_winit",
"ahash 0.8.11",
"arboard", "arboard",
"egui", "egui",
"log", "log",
@ -2738,24 +2701,23 @@ dependencies = [
[[package]] [[package]]
name = "egui_extras" name = "egui_extras"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "1b78779f35ded1a853786c9ce0b43fe1053e10a21ea3b23ebea411805ce41593"
dependencies = [ dependencies = [
"ahash 0.8.11",
"egui", "egui",
"enum-map", "enum-map",
"image 0.24.9", "image 0.25.1",
"log", "log",
"mime_guess2", "mime_guess2",
"resvg", "resvg",
"serde",
] ]
[[package]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "e0e5d975f3c86edc3d35b1db88bb27c15dde7c55d3b5af164968ab5ede3f44ca"
dependencies = [ dependencies = [
"ahash 0.8.11",
"bytemuck", "bytemuck",
"egui", "egui",
"glow", "glow",
@ -2766,15 +2728,6 @@ dependencies = [
"winit", "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]] [[package]]
name = "either" name = "either"
version = "1.12.0" version = "1.12.0"
@ -2803,11 +2756,9 @@ dependencies = [
[[package]] [[package]]
name = "emath" name = "emath"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "e4c3a552cfca14630702449d35f41c84a0d15963273771c6059175a803620f3f"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde",
] ]
[[package]] [[package]]
@ -2895,17 +2846,6 @@ dependencies = [
"syn 2.0.66", "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]] [[package]]
name = "env_filter" name = "env_filter"
version = "0.1.0" version = "0.1.0"
@ -2981,8 +2921,7 @@ dependencies = [
[[package]] [[package]]
name = "epaint" name = "epaint"
version = "0.27.2" version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/emilk/egui/?rev=10571e9da5b3658964adc8b85c7e3f8c1353e0db#10571e9da5b3658964adc8b85c7e3f8c1353e0db"
checksum = "b381f8b149657a4acf837095351839f32cd5c4aec1817fc4df84e18d76334176"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"ahash 0.8.11", "ahash 0.8.11",
@ -2992,7 +2931,6 @@ dependencies = [
"log", "log",
"nohash-hasher", "nohash-hasher",
"parking_lot 0.12.3", "parking_lot 0.12.3",
"serde",
] ]
[[package]] [[package]]
@ -3808,9 +3746,9 @@ dependencies = [
[[package]] [[package]]
name = "gpu-descriptor" name = "gpu-descriptor"
version = "0.2.4" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"gpu-descriptor-types", "gpu-descriptor-types",
@ -3819,9 +3757,9 @@ dependencies = [
[[package]] [[package]]
name = "gpu-descriptor-types" name = "gpu-descriptor-types"
version = "0.1.2" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
] ]
@ -3844,7 +3782,6 @@ dependencies = [
"eframe", "eframe",
"egui", "egui",
"egui_extras", "egui_extras",
"egui_pull_to_refresh",
"env_logger 0.11.3", "env_logger 0.11.3",
"eye", "eye",
"fs-mistrust", "fs-mistrust",
@ -4909,7 +4846,6 @@ dependencies = [
"byteorder", "byteorder",
"color_quant", "color_quant",
"num-traits 0.2.19", "num-traits 0.2.19",
"png",
] ]
[[package]] [[package]]
@ -5613,9 +5549,9 @@ dependencies = [
[[package]] [[package]]
name = "metal" name = "metal"
version = "0.27.0" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"block", "block",
@ -5775,10 +5711,11 @@ dependencies = [
[[package]] [[package]]
name = "naga" name = "naga"
version = "0.19.2" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231"
dependencies = [ dependencies = [
"arrayvec 0.7.4",
"bit-set", "bit-set",
"bitflags 2.5.0", "bitflags 2.5.0",
"codespan-reporting", "codespan-reporting",
@ -6211,7 +6148,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [ dependencies = [
"malloc_buf", "malloc_buf",
"objc_exception",
] ]
[[package]] [[package]]
@ -6366,15 +6302,6 @@ dependencies = [
"objc2-metal", "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]] [[package]]
name = "objc_id" name = "objc_id"
version = "0.1.1" version = "0.1.1"
@ -11004,17 +10931,18 @@ dependencies = [
[[package]] [[package]]
name = "webbrowser" name = "webbrowser"
version = "0.8.15" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e"
dependencies = [ dependencies = [
"block2 0.5.1",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"home", "home",
"jni", "jni",
"log", "log",
"ndk-context", "ndk-context",
"objc", "objc2 0.5.2",
"raw-window-handle 0.5.2", "objc2-foundation",
"url", "url",
"web-sys", "web-sys",
] ]
@ -11056,13 +10984,14 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]] [[package]]
name = "wgpu" name = "wgpu"
version = "0.19.4" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c"
dependencies = [ dependencies = [
"arrayvec 0.7.4", "arrayvec 0.7.4",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"cfg_aliases", "cfg_aliases",
"document-features",
"js-sys", "js-sys",
"log", "log",
"naga", "naga",
@ -11081,15 +11010,16 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-core" name = "wgpu-core"
version = "0.19.4" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" checksum = "d59e0d5fc509601c69e4e1fa06c1eb3c4c9f12956a5e30c79b61ef1c1be7daf0"
dependencies = [ dependencies = [
"arrayvec 0.7.4", "arrayvec 0.7.4",
"bit-vec", "bit-vec",
"bitflags 2.5.0", "bitflags 2.5.0",
"cfg_aliases", "cfg_aliases",
"codespan-reporting", "codespan-reporting",
"document-features",
"indexmap 2.2.6", "indexmap 2.2.6",
"log", "log",
"naga", "naga",
@ -11107,9 +11037,9 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-hal" name = "wgpu-hal"
version = "0.19.4" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3" checksum = "6aa24c3889f885a3fb9133b454c8418bfcfaadcfe4ed3be96ac80e76703b863b"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"arrayvec 0.7.4", "arrayvec 0.7.4",
@ -11129,7 +11059,7 @@ dependencies = [
"js-sys", "js-sys",
"khronos-egl", "khronos-egl",
"libc", "libc",
"libloading 0.8.3", "libloading 0.7.4",
"log", "log",
"metal", "metal",
"naga", "naga",
@ -11152,9 +11082,9 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-types" name = "wgpu-types"
version = "0.19.2" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"js-sys", "js-sys",

View file

@ -11,6 +11,7 @@ build = "src/build/build.rs"
[lib] [lib]
name="grim" name="grim"
crate_type=["cdylib", "rlib"]
[dependencies] [dependencies]
log = "0.4" log = "0.4"
@ -37,7 +38,6 @@ grin_wallet_controller = "5.3.1"
egui = { version = "0.27.2", default-features = false } egui = { version = "0.27.2", default-features = false }
egui_extras = { version = "0.27.2", features = ["image", "svg"] } egui_extras = { version = "0.27.2", features = ["image", "svg"] }
rust-i18n = "2.3.1" rust-i18n = "2.3.1"
egui_pull_to_refresh = "0.4.0"
## other ## other
thiserror = "1.0.58" thiserror = "1.0.58"
@ -110,7 +110,7 @@ dark-light = "1.1.1"
android_logger = "0.13.1" android_logger = "0.13.1"
jni = "0.21.1" jni = "0.21.1"
android-activity = { version = "0.6.0", features = ["game-activity"] } 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"] } winit = { version = "0.29.15", features = ["android-game-activity"] }
eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] } eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] }
@ -120,4 +120,8 @@ eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] }
#grin_store = { path = "../grin-store" } #grin_store = { path = "../grin-store" }
[patch.crates-io] [patch.crates-io]
### fix cross-compilation support for macos ### fix cross-compilation support for macos
openpnp_capture_sys = { git = "https://github.com/ardocrat/openpnp-capture-rs", branch = "cross_compilation_support" } 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" }

View file

@ -14,14 +14,14 @@
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use egui::{Align, Context, CursorIcon, Layout, Margin, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand}; use egui::{Align, Context, CursorIcon, Layout, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand};
use egui::epaint::{RectShape, Shadow}; use egui::epaint::{RectShape};
use crate::{AppConfig, built_info}; use crate::{AppConfig, built_info};
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X}; use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Root, View}; use crate::gui::views::{Root, TitlePanel, View};
lazy_static! { lazy_static! {
/// State to check if platform Back button was pressed. /// State to check if platform Back button was pressed.
@ -32,13 +32,17 @@ lazy_static! {
pub struct App<Platform> { pub struct App<Platform> {
/// Platform specific callbacks handler. /// Platform specific callbacks handler.
pub(crate) platform: Platform, pub(crate) platform: Platform,
/// Main ui content. /// Main ui content.
root: Root root: Root,
/// Last window resize direction.
resize_direction: Option<ResizeDirection>
} }
impl<Platform: PlatformCallbacks> App<Platform> { impl<Platform: PlatformCallbacks> App<Platform> {
pub fn new(platform: Platform) -> Self { pub fn new(platform: Platform) -> Self {
Self { platform, root: Root::default() } Self { platform, root: Root::default(), resize_direction: None }
} }
/// Draw application content. /// Draw application content.
@ -72,180 +76,130 @@ impl<Platform: PlatformCallbacks> App<Platform> {
} }
// Show main content with custom frame on desktop. // Show main content with custom frame on desktop.
if View::is_desktop() { egui::CentralPanel::default()
self.window_frame_ui(ctx); .frame(egui::Frame {
} else { ..Default::default()
egui::CentralPanel::default() })
.frame(egui::Frame { .show(ctx, |ui| {
fill: Colors::fill(), if View::is_desktop() {
stroke: Stroke::NONE, self.desktop_window_ui(ui);
..Default::default() } else {
})
.show(ctx, |ui| {
self.root.ui(ui, &self.platform); self.root.ui(ui, &self.platform);
}); }
} });
} }
/// Draw custom resizeable window frame for desktop. /// Draw custom resizeable window content.
fn window_frame_ui(&mut self, ctx: &Context) { fn desktop_window_ui(&mut self, ui: &mut egui::Ui) {
egui::CentralPanel::default().frame(egui::Frame { let title_stroke_rect = {
inner_margin: Margin::same(Root::WINDOW_FRAME_MARGIN), let mut rect = ui.max_rect().shrink(Root::WINDOW_FRAME_MARGIN);
..Default::default() rect.max.y = Root::WINDOW_FRAME_MARGIN + Root::WINDOW_TITLE_HEIGHT +
}).show(ctx, |ui| { TitlePanel::DEFAULT_HEIGHT + 0.5;
// Draw resize areas. rect
Self::resize_area_ui(ui, ResizeDirection::North); };
Self::resize_area_ui(ui, ResizeDirection::East); let title_stroke = RectShape {
Self::resize_area_ui(ui, ResizeDirection::South); rect: title_stroke_rect,
Self::resize_area_ui(ui, ResizeDirection::West); rounding: Rounding {
Self::resize_area_ui(ui, ResizeDirection::NorthWest); nw: 8.0,
Self::resize_area_ui(ui, ResizeDirection::NorthEast); ne: 8.0,
Self::resize_area_ui(ui, ResizeDirection::SouthEast); sw: 0.0,
Self::resize_area_ui(ui, ResizeDirection::SouthWest); se: 0.0,
// Draw window content. },
self.custom_window_frame(ui); fill: Colors::TRANSPARENT,
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().shrink(Root::WINDOW_FRAME_MARGIN);
let top = Root::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::TRANSPARENT,
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 content_rect = ui.max_rect().shrink(Root::WINDOW_FRAME_MARGIN);
ui.allocate_ui_at_rect(content_rect, |ui| {
self.window_content(ui);
}); });
// Setup resize areas.
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 resize area. /// Draw window content for desktop.
fn resize_area_ui(ui: &egui::Ui, direction: ResizeDirection) { fn window_content(&mut self, ui: &mut egui::Ui) {
let mut rect = ui.max_rect(); let content_rect = ui.max_rect();
rect.min.y -= Root::WINDOW_FRAME_MARGIN;
rect.min.x -= Root::WINDOW_FRAME_MARGIN;
rect.max.y += Root::WINDOW_FRAME_MARGIN;
rect.max.x += Root::WINDOW_FRAME_MARGIN;
// Setup area id, cursor and area rect based on direction. let window_title_rect = {
let (id, cursor, rect) = match direction { let mut rect = content_rect;
ResizeDirection::North => ("n", CursorIcon::ResizeNorth, { rect.max.y = rect.min.y + Root::WINDOW_TITLE_HEIGHT;
rect.min.x += Root::WINDOW_FRAME_MARGIN; rect
rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN;
rect.max.x -= Root::WINDOW_FRAME_MARGIN;
rect
}),
ResizeDirection::East => ("e", CursorIcon::ResizeEast, {
rect.min.y += Root::WINDOW_FRAME_MARGIN;
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN;
rect.max.y -= Root::WINDOW_FRAME_MARGIN;
rect
}),
ResizeDirection::South => ("s", CursorIcon::ResizeSouth, {
rect.min.x += Root::WINDOW_FRAME_MARGIN;
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN;
rect.max.x -= Root::WINDOW_FRAME_MARGIN;
rect
}),
ResizeDirection::West => ("w", CursorIcon::ResizeWest, {
rect.min.y += Root::WINDOW_FRAME_MARGIN;
rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN;
rect.max.y -= Root::WINDOW_FRAME_MARGIN;
rect
}),
ResizeDirection::NorthWest => ("nw", CursorIcon::ResizeNorthWest, {
rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN;
rect.max.x = rect.max.y + Root::WINDOW_FRAME_MARGIN;
rect
}),
ResizeDirection::NorthEast => ("ne", CursorIcon::ResizeNorthEast, {
rect.min.y += Root::WINDOW_FRAME_MARGIN;
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN;
rect.max.y = rect.min.y;
rect
}),
ResizeDirection::SouthEast => ("se", CursorIcon::ResizeSouthEast, {
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN;
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN;
rect
}),
ResizeDirection::SouthWest => ("sw", CursorIcon::ResizeSouthWest, {
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN;
rect.max.y = rect.min.y;
rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN;
rect
}),
}; };
// Draw 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() {
let current_pos = area_resp.interact_pointer_pos();
if let Some(pos) = current_pos {
ui.ctx().send_viewport_cmd(ViewportCommand::BeginResize(direction));
ui.ctx().send_viewport_cmd(ViewportCommand::InnerSize(
pos.to_vec2()
));
}
}
}
/// Draw custom window frame for desktop.
fn custom_window_frame(&mut self, ui: &mut egui::Ui) {
let is_fullscreen = ui.ctx().input(|i| { let is_fullscreen = ui.ctx().input(|i| {
i.viewport().fullscreen.unwrap_or(false) i.viewport().fullscreen.unwrap_or(false)
}); });
let panel_frame = if is_fullscreen {
egui::Frame::default() let window_title_bg = RectShape {
} else { rect: window_title_rect,
egui::Frame { rounding: if is_fullscreen {
fill: Colors::fill(), Rounding::ZERO
stroke: Stroke { width: 1.0, color: egui::Color32::from_gray(210) }, } else {
shadow: Shadow { Rounding {
offset: Default::default(),
blur: Root::WINDOW_FRAME_MARGIN,
spread: 0.5,
color: egui::Color32::from_black_alpha(35),
},
rounding: Rounding {
nw: 8.0, nw: 8.0,
ne: 8.0, ne: 8.0,
sw: 0.0, sw: 0.0,
se: 0.0, se: 0.0,
}, }
..Default::default() },
} fill: Colors::yellow_dark(),
stroke: Stroke::NONE,
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO
}; };
egui::CentralPanel::default().frame(panel_frame).show_inside(ui, |ui| { ui.painter().add(window_title_bg);
let app_rect = ui.max_rect();
let window_title_rect = { // Draw window title.
let mut rect = app_rect; self.window_title_ui(ui, window_title_rect);
rect.max.y = rect.min.y + Root::WINDOW_TITLE_HEIGHT;
rect
};
let window_title_bg = RectShape { let content_rect = {
rect: window_title_rect, let mut rect = content_rect;
rounding: if is_fullscreen { rect.min.y = window_title_rect.max.y;
Rounding::ZERO rect
} else { };
Rounding { // Draw main content.
nw: 8.0, let mut content_ui = ui.child_ui(content_rect, *ui.layout(), None);
ne: 8.0, self.root.ui(&mut content_ui, &self.platform);
sw: 0.0,
se: 0.0,
}
},
fill: Colors::yellow_dark(),
stroke: Stroke::NONE,
fill_texture_id: Default::default(),
uv: Rect::ZERO
};
ui.painter().add(window_title_bg);
// Draw window title.
self.window_title_ui(ui, window_title_rect);
let content_rect = {
let mut rect = app_rect;
rect.min.y = window_title_rect.max.y;
rect
};
// Draw main content.
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
self.root.ui(&mut content_ui, &self.platform);
});
} }
/// Draw custom window title content. /// Draw custom window title content.
@ -289,7 +243,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
egui::Align2::CENTER_CENTER, egui::Align2::CENTER_CENTER,
title_text, title_text,
egui::FontId::proportional(15.0), egui::FontId::proportional(15.0),
egui::Color32::from_gray(60), Colors::title(true),
); );
// Interact with the window title (drag to move window): // Interact with the window title (drag to move window):
@ -341,6 +295,81 @@ impl<Platform: PlatformCallbacks> App<Platform> {
}); });
}); });
} }
/// 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 += Root::WINDOW_FRAME_MARGIN * 2.0;
rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN;
rect.max.x -= Root::WINDOW_FRAME_MARGIN * 2.0;
rect
}),
ResizeDirection::East => ("e", CursorIcon::ResizeEast, {
rect.min.y += Root::WINDOW_FRAME_MARGIN * 2.0;
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN;
rect.max.y -= Root::WINDOW_FRAME_MARGIN * 2.0;
rect
}),
ResizeDirection::South => ("s", CursorIcon::ResizeSouth, {
rect.min.x += Root::WINDOW_FRAME_MARGIN * 2.0;
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN;
rect.max.x -= Root::WINDOW_FRAME_MARGIN * 2.0;
rect
}),
ResizeDirection::West => ("w", CursorIcon::ResizeWest, {
rect.min.y += Root::WINDOW_FRAME_MARGIN * 2.0;
rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN;
rect.max.y -= Root::WINDOW_FRAME_MARGIN * 2.0;
rect
}),
ResizeDirection::NorthWest => ("nw", CursorIcon::ResizeNorthWest, {
rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN * 2.0;
rect.max.x = rect.max.y + Root::WINDOW_FRAME_MARGIN * 2.0;
rect
}),
ResizeDirection::NorthEast => ("ne", CursorIcon::ResizeNorthEast, {
rect.min.y += Root::WINDOW_FRAME_MARGIN * 2.0;
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN * 2.0;
rect.max.y = rect.min.y;
rect
}),
ResizeDirection::SouthEast => ("se", CursorIcon::ResizeSouthEast, {
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN * 2.0;
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN * 2.0;
rect
}),
ResizeDirection::SouthWest => ("sw", CursorIcon::ResizeSouthWest, {
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN * 2.0;
rect.max.y = rect.min.y;
rect.max.x = rect.min.x + Root::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() {
let current_pos = area_resp.interact_pointer_pos();
if let Some(pos) = current_pos {
if self.resize_direction.is_none() {
self.resize_direction = Some(direction.clone());
ui.ctx().send_viewport_cmd(ViewportCommand::BeginResize(direction));
}
ui.ctx().send_viewport_cmd(ViewportCommand::InnerSize(
pos.to_vec2() + egui::vec2(Root::WINDOW_FRAME_MARGIN, Root::WINDOW_FRAME_MARGIN)
));
}
}
if area_resp.drag_stopped() {
self.resize_direction = None;
}
}
} }
/// To draw with egui`s eframe (for wgpu, glow backends and wasm target). /// To draw with egui`s eframe (for wgpu, glow backends and wasm target).

View file

@ -109,7 +109,7 @@ impl Colors {
pub fn gold() -> Color32 { pub fn gold() -> Color32 {
if use_dark() { if use_dark() {
GOLD.linear_multiply(0.9) GOLD.gamma_multiply(0.9)
} else { } else {
GOLD GOLD
} }
@ -125,7 +125,7 @@ impl Colors {
pub fn green() -> Color32 { pub fn green() -> Color32 {
if use_dark() { if use_dark() {
GREEN.linear_multiply(1.3) GREEN.gamma_multiply(1.3)
} else { } else {
GREEN GREEN
} }
@ -133,7 +133,7 @@ impl Colors {
pub fn red() -> Color32 { pub fn red() -> Color32 {
if use_dark() { if use_dark() {
RED.linear_multiply(1.3) RED.gamma_multiply(1.3)
} else { } else {
RED RED
} }
@ -141,7 +141,7 @@ impl Colors {
pub fn blue() -> Color32 { pub fn blue() -> Color32 {
if use_dark() { if use_dark() {
BLUE.linear_multiply(1.3) BLUE.gamma_multiply(1.3)
} else { } else {
BLUE BLUE
} }

View file

@ -39,4 +39,7 @@ mod qr;
pub use qr::*; pub use qr::*;
mod file; mod file;
pub use file::*; pub use file::*;
mod pull_to_refresh;
pub use pull_to_refresh::*;

View file

@ -162,7 +162,7 @@ impl Modal {
fn window_ui(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) { fn window_ui(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
let mut rect = ctx.screen_rect(); let mut rect = ctx.screen_rect();
if View::is_desktop() { if View::is_desktop() {
rect = rect.shrink(7.5); rect = rect.shrink(Root::WINDOW_FRAME_MARGIN - 0.5);
rect.min += egui::vec2(0.0, Root::WINDOW_TITLE_HEIGHT + 0.5); rect.min += egui::vec2(0.0, Root::WINDOW_TITLE_HEIGHT + 0.5);
} }
egui::Window::new("modal_bg_window") egui::Window::new("modal_bg_window")
@ -256,6 +256,7 @@ impl Modal {
rounding, rounding,
fill: Colors::fill(), fill: Colors::fill(),
stroke: Stroke::NONE, stroke: Stroke::NONE,
blur_width: 0.0,
fill_texture_id: Default::default(), fill_texture_id: Default::default(),
uv: Rect::ZERO uv: Rect::ZERO
}; };
@ -288,6 +289,7 @@ impl Modal {
}, },
fill: Colors::yellow(), fill: Colors::yellow(),
stroke: Stroke::NONE, stroke: Stroke::NONE,
blur_width: 0.0,
fill_texture_id: Default::default(), fill_texture_id: Default::default(),
uv: Rect::ZERO uv: Rect::ZERO
}; };

View file

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::time::Duration;
use egui::{Margin, RichText, ScrollArea}; use egui::{Margin, RichText, ScrollArea};
use egui::scroll_area::ScrollBarVisibility; use egui::scroll_area::ScrollBarVisibility;
@ -30,7 +31,7 @@ pub struct NetworkContent {
/// Current integrated node tab content. /// Current integrated node tab content.
node_tab_content: Box<dyn NetworkTab>, node_tab_content: Box<dyn NetworkTab>,
/// Connections content. /// Connections content.
connections: ConnectionsContent connections: ConnectionsContent,
} }
impl Default for NetworkContent { impl Default for NetworkContent {
@ -48,34 +49,20 @@ impl NetworkContent {
let dual_panel = Root::is_dual_panel_mode(ui); let dual_panel = Root::is_dual_panel_mode(ui);
// Show title panel. // Show title panel.
self.title_ui(ui, dual_panel || Root::is_network_panel_open(), show_connections); self.title_ui(ui, show_connections);
// Show integrated node tabs content. // Show integrated node tabs content.
if !show_connections { if !show_connections {
egui::TopBottomPanel::bottom("node_tabs_panel") egui::TopBottomPanel::bottom("node_tabs_panel")
.resizable(false) .resizable(false)
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(),
inner_margin: Margin { inner_margin: Margin {
left: View::get_left_inset() + View::TAB_ITEMS_PADDING, left: View::get_left_inset() + View::TAB_ITEMS_PADDING,
right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING, right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING,
top: View::TAB_ITEMS_PADDING, top: View::TAB_ITEMS_PADDING,
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
}, },
outer_margin: if View::is_desktop() { fill: Colors::fill(),
Margin {
left: -0.5,
right: if dual_panel {
0.0
} else {
-0.5
},
top: 0.0,
bottom: -0.5,
}
} else {
Margin::ZERO
},
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
@ -92,6 +79,20 @@ impl NetworkContent {
.resizable(false) .resizable(false)
.exact_width(ui.available_width()) .exact_width(ui.available_width())
.frame(egui::Frame { .frame(egui::Frame {
outer_margin: if !show_connections {
Margin {
left: -0.5,
right: if !dual_panel {
-0.5
} else {
0.0
},
top: 0.0,
bottom: 0.0,
}
} else {
Margin::ZERO
},
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, !show_connections, |ui| { .show_animated_inside(ui, !show_connections, |ui| {
@ -116,6 +117,20 @@ impl NetworkContent {
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(egui::Frame { .frame(egui::Frame {
stroke: View::item_stroke(), stroke: View::item_stroke(),
outer_margin: if show_connections {
Margin {
left: -0.5,
right: if !dual_panel {
-0.5
} else {
0.0
},
top: 0.0,
bottom: -0.5,
}
} else {
Margin::ZERO
},
inner_margin: Margin { inner_margin: Margin {
left: if show_connections { left: if show_connections {
View::get_left_inset() + 4.0 View::get_left_inset() + 4.0
@ -157,9 +172,11 @@ impl NetworkContent {
}); });
}); });
// Redraw after delay if node is syncing to update stats. // Redraw after delay.
if Node::is_running() { if Node::is_running() {
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY); ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
} else if show_connections {
ui.ctx().request_repaint_after(Duration::from_millis(1000));
} }
} }
@ -199,7 +216,7 @@ impl NetworkContent {
} }
/// Draw title content. /// Draw title content.
fn title_ui(&mut self, ui: &mut egui::Ui, show_panel: bool, show_connections: bool) { fn title_ui(&mut self, ui: &mut egui::Ui, show_connections: bool) {
// Setup values for title panel. // Setup values for title panel.
let title_text = self.node_tab_content.get_type().title().to_uppercase(); let title_text = self.node_tab_content.get_type().title().to_uppercase();
let subtitle_text = Node::get_sync_status_text(); let subtitle_text = Node::get_sync_status_text();
@ -211,7 +228,7 @@ impl NetworkContent {
}; };
// Draw title panel. // Draw title panel.
TitlePanel::ui(TitleType::Single(title_content), show_panel, |ui| { TitlePanel::ui(TitleType::Single(title_content), |ui| {
if !show_connections { if !show_connections {
View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |_| { View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |_| {
AppConfig::toggle_show_connections_network_panel(); AppConfig::toggle_show_connections_network_panel();

View 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
}
}

View file

@ -17,6 +17,7 @@ use std::sync::Arc;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::thread; use std::thread;
use egui::{SizeHint, TextureHandle, TextureOptions}; use egui::{SizeHint, TextureHandle, TextureOptions};
use egui::epaint::RectShape;
use egui::load::SizedTexture; use egui::load::SizedTexture;
use egui_extras::image::load_svg_bytes_with_size; use egui_extras::image::load_svg_bytes_with_size;
use image::{ExtendedColorType, ImageEncoder}; use image::{ExtendedColorType, ImageEncoder};
@ -235,11 +236,12 @@ impl QrCodeContent {
rect.max -= egui::emath::vec2(10.0, 0.0); rect.max -= egui::emath::vec2(10.0, 0.0);
// Create background shape. // Create background shape.
let mut bg_shape = egui::epaint::RectShape { let mut bg_shape = RectShape {
rect, rect,
rounding: egui::Rounding::default(), rounding: egui::Rounding::default(),
fill: egui::Color32::WHITE, fill: egui::Color32::WHITE,
stroke: egui::Stroke::NONE, stroke: egui::Stroke::NONE,
blur_width: 0.0,
fill_texture_id: Default::default(), fill_texture_id: Default::default(),
uv: egui::Rect::ZERO uv: egui::Rect::ZERO
}; };

View file

@ -111,19 +111,23 @@ impl Root {
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui, dual_panel); let (is_panel_open, panel_width) = Self::network_panel_state_width(ui, dual_panel);
// Show network content. // Show network content.
egui::SidePanel::left("network_panel") if is_panel_open {
.resizable(false) egui::SidePanel::left("network_panel")
.exact_width(panel_width) .resizable(false)
.frame(egui::Frame { .exact_width(panel_width)
..Default::default() .frame(egui::Frame {
}) fill: Colors::fill(),
.show_animated_inside(ui, is_panel_open, |ui| { ..Default::default()
self.network.ui(ui, cb); })
}); .show_inside(ui, |ui| {
self.network.ui(ui, cb);
});
}
// Show wallets content. // Show wallets content.
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(),
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
@ -151,7 +155,7 @@ impl Root {
Self::SIDE_PANEL_WIDTH + View::get_left_inset() Self::SIDE_PANEL_WIDTH + View::get_left_inset()
} else { } else {
View::window_size(ui).0 - if View::is_desktop() { View::window_size(ui).0 - if View::is_desktop() {
Root::WINDOW_FRAME_MARGIN * 2.0 Self::WINDOW_FRAME_MARGIN * 2.0
} else { } else {
0.0 0.0
} }

View file

@ -27,7 +27,6 @@ impl TitlePanel {
pub const DEFAULT_HEIGHT: f32 = 54.0; pub const DEFAULT_HEIGHT: f32 = 54.0;
pub fn ui(title: TitleType, pub fn ui(title: TitleType,
show: bool,
mut left_content: impl FnMut(&mut egui::Ui), mut left_content: impl FnMut(&mut egui::Ui),
mut right_content: impl FnMut(&mut egui::Ui), mut right_content: impl FnMut(&mut egui::Ui),
ui: &mut egui::Ui) { ui: &mut egui::Ui) {
@ -52,13 +51,18 @@ impl TitlePanel {
// Draw title panel. // Draw title panel.
egui::TopBottomPanel::top(id) egui::TopBottomPanel::top(id)
.resizable(false) .resizable(false)
.exact_height(Self::DEFAULT_HEIGHT) .exact_height(Self::DEFAULT_HEIGHT + View::get_top_inset())
.frame(egui::Frame { .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(), fill: Colors::yellow(),
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, show, |ui| { .show_inside(ui, |ui| {
StripBuilder::new(ui) StripBuilder::new(ui)
.size(Size::exact(Self::DEFAULT_HEIGHT)) .size(Size::exact(Self::DEFAULT_HEIGHT))
.size(if dual_title { .size(if dual_title {
@ -126,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. /// Draw content for [`TitleType::WithSubTitle`] type.
fn with_sub_title(builder: StripBuilder, title: String, subtitle: String, animate_sub: bool) { fn with_sub_title(builder: StripBuilder, title: String, subtitle: String, animate_sub: bool) {
builder builder

View file

@ -532,6 +532,7 @@ impl View {
}, },
fill: Colors::TRANSPARENT, fill: Colors::TRANSPARENT,
stroke: Self::item_stroke(), stroke: Self::item_stroke(),
blur_width: 0.0,
fill_texture_id: Default::default(), fill_texture_id: Default::default(),
uv: Rect::ZERO uv: Rect::ZERO
}; };

View file

@ -103,16 +103,19 @@ impl WalletsContent {
let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet); let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet);
let content_width = ui.available_width(); let content_width = ui.available_width();
let root_dual_panel = Root::is_dual_panel_mode(ui);
// Flag to check if wallet list is hidden on the screen. // Flag to check if wallet list is hidden on the screen.
let list_hidden = content_width == 0.0 || empty_list || create_wallet 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 && !self.show_wallets_at_dual_panel)
|| (!dual_panel && show_wallet) || || (!dual_panel && show_wallet) ||
(!Root::is_dual_panel_mode(ui) && Root::is_network_panel_open()); (!root_dual_panel && Root::is_network_panel_open());
// Show title panel. // Show title panel.
self.title_ui(ui, dual_panel, create_wallet, show_wallet); self.title_ui(ui, dual_panel, create_wallet, show_wallet);
// Show wallet panel content. // Show wallet panel content.
let wallet_panel_opened = self.wallet_panel_opened();
egui::SidePanel::right("wallet_panel") egui::SidePanel::right("wallet_panel")
.resizable(false) .resizable(false)
.exact_width(wallet_panel_width) .exact_width(wallet_panel_width)
@ -129,11 +132,11 @@ impl WalletsContent {
}, },
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, self.wallet_panel_opened(), |ui| { .show_animated_inside(ui, wallet_panel_opened, |ui| {
// Do not draw content on zero width. // // Do not draw content on zero width.
if content_width == 0.0 { // if content_width == 0.0 {
return; // return;
} // }
if create_wallet || !show_wallet { if create_wallet || !show_wallet {
// Show wallet creation content. // Show wallet creation content.
self.creation_content.ui(ui, cb, |wallet| { self.creation_content.ui(ui, cb, |wallet| {
@ -180,24 +183,6 @@ impl WalletsContent {
top: View::TAB_ITEMS_PADDING, top: View::TAB_ITEMS_PADDING,
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
}, },
outer_margin: if View::is_desktop() {
Margin {
left: if !dual_panel {
-0.5
} else {
0.0
},
right: if !self.wallet_panel_opened() {
-0.5
} else {
0.0
},
top: 0.0,
bottom: -0.5,
}
} else {
Margin::ZERO
},
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
@ -221,6 +206,20 @@ impl WalletsContent {
egui::Frame::default() egui::Frame::default()
} else { } else {
egui::Frame { egui::Frame {
outer_margin: Margin {
left: if !root_dual_panel {
-0.5
} else {
0.0
},
right: if !wallet_panel_opened {
-0.5
} else {
0.0
},
top: 0.0,
bottom: 0.0,
},
stroke: View::item_stroke(), stroke: View::item_stroke(),
fill: Colors::fill_deep(), fill: Colors::fill_deep(),
inner_margin: Margin { inner_margin: Margin {
@ -301,8 +300,7 @@ impl WalletsContent {
}; };
// Draw title panel. // Draw title panel.
let show_title = Root::is_dual_panel_mode(ui) || !Root::is_network_panel_open(); TitlePanel::ui(title_content, |ui| {
TitlePanel::ui(title_content, show_title, |ui| {
if show_wallet && !dual_panel { if show_wallet && !dual_panel {
View::title_button_big(ui, ARROW_LEFT, |_| { View::title_button_big(ui, ARROW_LEFT, |_| {
self.wallets.select(None); self.wallets.select(None);

View file

@ -87,8 +87,11 @@ impl WalletContent {
// Show wallet balance panel not on Settings tab with selected non-repairing // Show wallet balance panel not on Settings tab with selected non-repairing
// wallet, when there is no error and data is not empty. // wallet, when there is no error and data is not empty.
let show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
&& !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing(); && !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())) egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier()))
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(), fill: Colors::fill(),
@ -100,13 +103,17 @@ impl WalletContent {
bottom: 0.0, bottom: 0.0,
}, },
outer_margin: Margin { outer_margin: Margin {
left: 0.0, left: if !dual_panel {
right: 0.0, -0.5
top: 0.0,
bottom: if !dual_panel {
0.0
} else { } else {
0.0
},
right: -0.5,
top: 0.0,
bottom: if dual_panel {
-1.0 -1.0
} else {
-0.5
}, },
}, },
..Default::default() ..Default::default()
@ -124,6 +131,7 @@ impl WalletContent {
}); });
// Show wallet tabs panel. // Show wallet tabs panel.
let show_tabs = !Self::block_navigation_on_sync(wallet);
egui::TopBottomPanel::bottom("wallet_tabs") egui::TopBottomPanel::bottom("wallet_tabs")
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(), fill: Colors::fill(),
@ -133,23 +141,9 @@ impl WalletContent {
top: View::TAB_ITEMS_PADDING, top: View::TAB_ITEMS_PADDING,
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
}, },
outer_margin: if View::is_desktop() {
Margin {
left: if dual_panel {
0.0
} else {
-0.5
},
right: -0.5,
top: 0.0,
bottom: -0.5,
}
} else {
Margin::ZERO
},
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, !Self::block_navigation_on_sync(wallet), |ui| { .show_animated_inside(ui, show_tabs, |ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
// Draw wallet tabs. // Draw wallet tabs.
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| { View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
@ -161,6 +155,16 @@ impl WalletContent {
// Show tab content panel. // Show tab content panel.
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(egui::Frame { .frame(egui::Frame {
outer_margin: Margin {
left: if !dual_panel {
-0.5
} else {
0.0
},
right: -0.5,
top: 0.0,
bottom: 0.0,
},
stroke: View::item_stroke(), stroke: View::item_stroke(),
fill: Colors::white_or_black(false), fill: Colors::white_or_black(false),
..Default::default() ..Default::default()

View file

@ -17,7 +17,6 @@ use std::thread;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea}; use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
use egui::scroll_area::ScrollBarVisibility; use egui::scroll_area::ScrollBarVisibility;
use egui_pull_to_refresh::PullToRefresh;
use grin_core::core::amount_to_hr_string; use grin_core::core::amount_to_hr_string;
use grin_util::ToHex; use grin_util::ToHex;
use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType}; use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType};
@ -26,7 +25,7 @@ use parking_lot::RwLock;
use crate::gui::Colors; 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::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::platform::PlatformCallbacks;
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, Root, View}; use crate::gui::views::{CameraContent, FilePickButton, Modal, PullToRefresh, QrCodeContent, Root, View};
use crate::gui::views::types::ModalPosition; use crate::gui::views::types::ModalPosition;
use crate::gui::views::wallets::types::WalletTab; use crate::gui::views::wallets::types::WalletTab;
use crate::gui::views::wallets::wallet::types::{GRIN, SLATEPACK_MESSAGE_HINT, WalletTabType}; use crate::gui::views::wallets::wallet::types::{GRIN, SLATEPACK_MESSAGE_HINT, WalletTabType};

View file

@ -112,7 +112,7 @@ pub fn app_creator<T: 'static>(app: App<T>) -> eframe::AppCreator
// Setup fonts. // Setup fonts.
setup_fonts(&cc.egui_ctx); setup_fonts(&cc.egui_ctx);
// Return app instance. // Return app instance.
Box::new(app) Ok(Box::new(app))
}) })
} }

View file

@ -51,7 +51,9 @@ fn real_main() {
// Setup window size. // Setup window size.
let (width, height) = AppConfig::window_size(); let (width, height) = AppConfig::window_size();
let mut viewport = egui::ViewportBuilder::default().with_inner_size([width, height]); let mut viewport = egui::ViewportBuilder::default()
.with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
.with_inner_size([width, height]);
// Setup an icon. // Setup an icon.
if let Ok(icon) = from_png_bytes(include_bytes!("../img/icon.png")) { if let Ok(icon) = from_png_bytes(include_bytes!("../img/icon.png")) {

View file

@ -14,6 +14,7 @@
use grin_core::global::ChainTypes; use grin_core::global::ChainTypes;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::gui::views::Root;
use crate::node::{NodeConfig, PeersConfig}; use crate::node::{NodeConfig, PeersConfig};
use crate::Settings; use crate::Settings;
@ -69,10 +70,16 @@ impl Default for AppConfig {
} }
impl AppConfig { impl AppConfig {
/// Default window width. /// Desktop window frame margin sum, horizontal or vertical.
pub const DEFAULT_WIDTH: f32 = 1269.0; const FRAME_MARGIN: f32 = Root::WINDOW_FRAME_MARGIN * 2.0;
/// Default window height. /// Default desktop window width.
pub const DEFAULT_HEIGHT: f32 = 789.0; pub const DEFAULT_WIDTH: f32 = Root::SIDE_PANEL_WIDTH * 3.0 + Self::FRAME_MARGIN;
/// Default desktop window height.
pub const DEFAULT_HEIGHT: f32 = 698.0 + Root::WINDOW_TITLE_HEIGHT + Self::FRAME_MARGIN;
/// Minimal desktop window width.
pub const MIN_WIDTH: f32 = Root::SIDE_PANEL_WIDTH + Self::FRAME_MARGIN;
/// Minimal desktop window height.
pub const MIN_HEIGHT: f32 = 630.0 + Root::WINDOW_TITLE_HEIGHT + Self::FRAME_MARGIN;
/// Application configuration file name. /// Application configuration file name.
pub const FILE_NAME: &'static str = "app.toml"; pub const FILE_NAME: &'static str = "app.toml";