From e7a2f5ce12616add50bc765c68134ea2fa5f3224 Mon Sep 17 00:00:00 2001 From: Volkor <me@volkor.me> Date: Fri, 14 Feb 2025 02:59:40 +1100 Subject: [PATCH] FEAT: use egui_tiles for new ui --- Cargo.lock | 356 +++++++++++----- Cargo.toml | 15 +- assets/icons/lan-connect.svg | 1 + assets/icons/lan-disconnect.svg | 1 + assets/icons/lan-pending.svg | 1 + build.rs | 3 + docs/gui.md | 6 +- src/gui.rs | 178 -------- src/gui/context_menu.rs | 81 ++++ src/gui/mod.rs | 446 +++++++++++++++++++++ src/gui/widgets/about.rs | 71 ++++ src/gui/widgets/debug.rs | 1 + src/gui/widgets/log.rs | 3 + src/gui/widgets/map_view.rs | 7 + src/gui/widgets/message_list.rs | 5 + src/gui/widgets/mod.rs | 47 +++ src/gui/widgets/node_list.rs | 193 +++++++++ src/gui/widgets/settings.rs | 5 + src/gui/widgets/signal_strength_history.rs | 3 + src/main.rs | 7 +- 20 files changed, 1137 insertions(+), 293 deletions(-) create mode 100644 assets/icons/lan-connect.svg create mode 100644 assets/icons/lan-disconnect.svg create mode 100644 assets/icons/lan-pending.svg create mode 100644 build.rs delete mode 100644 src/gui.rs create mode 100644 src/gui/context_menu.rs create mode 100644 src/gui/mod.rs create mode 100644 src/gui/widgets/about.rs create mode 100644 src/gui/widgets/debug.rs create mode 100644 src/gui/widgets/log.rs create mode 100644 src/gui/widgets/map_view.rs create mode 100644 src/gui/widgets/message_list.rs create mode 100644 src/gui/widgets/mod.rs create mode 100644 src/gui/widgets/node_list.rs create mode 100644 src/gui/widgets/settings.rs create mode 100644 src/gui/widgets/signal_strength_history.rs diff --git a/Cargo.lock b/Cargo.lock index 4fa3848..883d2ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,10 @@ name = "accesskit" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" +dependencies = [ + "enumn", + "serde", +] [[package]] name = "accesskit_atspi_common" @@ -134,6 +138,7 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -166,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.6.0", + "bitflags 2.8.0", "cc", "cesu8", "jni 0.21.1", @@ -229,11 +234,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" dependencies = [ "clipboard-win", + "core-graphics", + "image", "log", "objc2", "objc2-app-kit", "objc2-foundation", "parking_lot", + "windows-sys 0.48.0", "x11rb", ] @@ -578,9 +586,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +dependencies = [ + "serde", +] [[package]] name = "bitstream-io" @@ -631,7 +642,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353dc0fbd494ab1d066ffdff16f07acbea46ca63f507e093c07fdf2408d84300" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "bluez-generated", "dbus", "dbus-tokio", @@ -661,7 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b668804e0728a09c83cd9b94c9e1176717ea5522e8a3cb3688c2ac9a5f6e137c" dependencies = [ "async-trait", - "bitflags 2.6.0", + "bitflags 2.8.0", "bluez-async", "dashmap 6.1.0", "dbus", @@ -683,9 +694,13 @@ dependencies = [ [[package]] name = "built" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +checksum = "73848a43c5d63a1251d17adf6c2bf78aa94830e60a335a95eeea45d6ba9e1e4d" +dependencies = [ + "chrono", + "git2", +] [[package]] name = "bumpalo" @@ -764,7 +779,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "log", "polling", "rustix", @@ -817,12 +832,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -1145,19 +1154,20 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d72e9c39f6e11a2e922d04a34ec5e7ef522ea3f5a1acfca7a19d16ad5fe50f5" +checksum = "878e9005799dd739e5d5d89ff7480491c12d0af571d44399bcaefa1ee172dd76" dependencies = [ "bytemuck", "emath", + "serde", ] [[package]] name = "eframe" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f2d9e7ea2d11ec9e98a8683b6eb99f9d7d0448394ef6e0d6d91bd4eb817220" +checksum = "eba4c50d905804fe9ec4e159fde06b9d38f9440228617ab64a03d7a2091ece63" dependencies = [ "ahash", "bytemuck", @@ -1166,7 +1176,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow 0.16.0", + "glow", "glutin", "glutin-winit", "image", @@ -1193,24 +1203,26 @@ dependencies = [ [[package]] name = "egui" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252d52224d35be1535d7fd1d6139ce071fb42c9097773e79f7665604f5596b5e" +checksum = "7d2768eaa6d5c80a6e2a008da1f0e062dff3c83eb2b28605ea2d0732d46e74d6" dependencies = [ "accesskit", "ahash", + "bitflags 2.8.0", "emath", "epaint", "log", "nohash-hasher", "profiling", + "serde", ] [[package]] name = "egui-wgpu" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c1e821d2d8921ef6ce98b258c7e24d9d6aab2ca1f9cdf374eca997e7f67f59" +checksum = "6d8151704bcef6271bec1806c51544d70e79ef20e8616e5eac01facfd9c8c54a" dependencies = [ "ahash", "bytemuck", @@ -1228,13 +1240,14 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e84c2919cd9f3a38a91e8f84ac6a245c19251fd95226ed9fae61d5ea564fce3" +checksum = "ace791b367c1f63e6044aef2f3834904509d1d1a6912fd23ebf3f6a9af92cd84" dependencies = [ "accesskit_winit", "ahash", "arboard", + "bytemuck", "egui", "log", "profiling", @@ -1247,9 +1260,9 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7a8198c088b1007108cb2d403bc99a5e370999b200db4f14559610d7330126" +checksum = "b5b5cf69510eb3d19211fc0c062fb90524f43fe8e2c012967dcf0e2d81cb040f" dependencies = [ "ahash", "egui", @@ -1264,14 +1277,14 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf6264cc7608e3e69a7d57a6175f438275f1b3889c1a551b418277721c95e6" +checksum = "9a53e2374a964c3c793cb0b8ead81bca631f24974bc0b747d1a5622f4e39fdd0" dependencies = [ "ahash", "bytemuck", "egui", - "glow 0.16.0", + "glow", "log", "memoffset", "profiling", @@ -1280,6 +1293,19 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_tiles" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67756b63b283a65bd0534b0c2a5fb1a12a5768bb6383d422147cc93193d09cfc" +dependencies = [ + "ahash", + "egui", + "itertools 0.13.0", + "log", + "serde", +] + [[package]] name = "ehttp" version = "0.5.0" @@ -1302,11 +1328,12 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe73c1207b864ee40aa0b0c038d6092af1030744678c60188a05c28553515d" +checksum = "55b7b6be5ad1d247f11738b0e4699d9c20005ed366f2c29f5ec1f8e1de180bc2" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -1366,11 +1393,22 @@ dependencies = [ "syn 2.0.95", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "epaint" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5666f8d25236293c966fbb3635eac18b04ad1914e3bab55bc7d44b9980cafcac" +checksum = "275b665a7b9611d8317485187e5458750850f9e64604d3c58434bb3fc1d22915" dependencies = [ "ab_glyph", "ahash", @@ -1382,13 +1420,14 @@ dependencies = [ "nohash-hasher", "parking_lot", "profiling", + "serde", ] [[package]] name = "epaint_default_fonts" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f6ddac3e6ac6fd4c3d48bb8b1943472f8da0f43a4303bcd8a18aa594401c80" +checksum = "9343d356d7cac894dacafc161b4654e0881301097bdf32a122ed503d97cb94b6" [[package]] name = "equivalent" @@ -1729,26 +1768,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "gl_generator" -version = "0.14.0" +name = "git2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff" dependencies = [ - "khronos_api", + "bitflags 2.8.0", + "libc", + "libgit2-sys", "log", - "xml-rs", + "openssl-probe", + "openssl-sys", + "url", ] [[package]] -name = "glow" -version = "0.14.2" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", + "khronos_api", + "log", + "xml-rs", ] [[package]] @@ -1769,8 +1811,8 @@ version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03642b8b0cce622392deb0ee3e88511f75df2daac806102597905c3ea1974848" dependencies = [ - "bitflags 2.6.0", - "cfg_aliases 0.2.1", + "bitflags 2.8.0", + "cfg_aliases", "cgl", "core-foundation 0.9.4", "dispatch", @@ -1794,7 +1836,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" dependencies = [ - "cfg_aliases 0.2.1", + "cfg_aliases", "glutin", "raw-window-handle", "winit", @@ -1835,7 +1877,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "gpu-alloc-types", ] @@ -1845,7 +1887,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -1854,7 +1896,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "gpu-descriptor-types", "hashbrown 0.15.2", ] @@ -1865,7 +1907,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -2524,6 +2566,20 @@ dependencies = [ "cc", ] +[[package]] +name = "libgit2-sys" +version = "0.18.0+1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + [[package]] name = "libloading" version = "0.8.6" @@ -2546,7 +2602,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "redox_syscall 0.5.8", ] @@ -2562,6 +2618,32 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libssh2-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2710,11 +2792,11 @@ dependencies = [ [[package]] name = "metal" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "core-graphics-types", "foreign-types", @@ -2821,22 +2903,23 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "naga" -version = "23.1.0" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.6.0", - "cfg_aliases 0.1.1", + "bitflags 2.8.0", + "cfg_aliases", "codespan-reporting", "hexf-parse", "indexmap", "log", "rustc-hash", "spirv", + "strum", "termcolor", - "thiserror 1.0.69", + "thiserror 2.0.9", "unicode-xid", ] @@ -2846,7 +2929,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -2902,9 +2985,9 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "memoffset", ] @@ -3050,7 +3133,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "libc", "objc2", @@ -3066,7 +3149,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-core-location", @@ -3090,7 +3173,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a644b62ffb826a5277f536cf0f701493de420b13d40e700c452c36567771111" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "objc2", "objc2-foundation", ] @@ -3101,7 +3184,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -3143,7 +3226,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "dispatch", "libc", @@ -3168,7 +3251,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -3180,7 +3263,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -3203,7 +3286,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-cloud-kit", @@ -3235,7 +3318,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-core-location", @@ -3257,6 +3340,24 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.48" @@ -3266,6 +3367,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3810,7 +3920,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -3967,7 +4077,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -4003,7 +4113,7 @@ version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", @@ -4211,7 +4321,7 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "779e2977f0cc2ff39708fef48f96f3768ac8ddd8c6caaaab82e83bd240ef99b2" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "core-foundation 0.10.0", "core-foundation-sys", @@ -4340,7 +4450,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -4401,7 +4511,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -4442,6 +4552,28 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.95", +] + [[package]] name = "subtle" version = "2.6.1" @@ -5101,9 +5233,9 @@ dependencies = [ [[package]] name = "walkers" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13954bb3d2611f8f6ddb35a9461632b307ac51adf89ef8fe39998adf2a2342c" +checksum = "e15ae9bf81b8cf852ecacf362a5d720ba0b523add2c806ffb3c57731d259c43a" dependencies = [ "egui", "egui_extras", @@ -5226,7 +5358,7 @@ version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "rustix", "wayland-backend", "wayland-scanner", @@ -5238,7 +5370,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cursor-icon", "wayland-backend", ] @@ -5260,7 +5392,7 @@ version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -5272,7 +5404,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5285,7 +5417,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5376,12 +5508,13 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "23.0.1" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +checksum = "47f55718f85c2fa756edffa0e7f0e0a60aba463d1362b57e23123c58f035e4b6" dependencies = [ "arrayvec", - "cfg_aliases 0.1.1", + "bitflags 2.8.0", + "cfg_aliases", "document-features", "js-sys", "log", @@ -5401,14 +5534,14 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "23.0.1" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +checksum = "82a39b8842dc9ffcbe34346e3ab6d496b32a47f6497e119d762c97fcaae3cb37" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.6.0", - "cfg_aliases 0.1.1", + "bitflags 2.8.0", + "cfg_aliases", "document-features", "indexmap", "log", @@ -5419,26 +5552,26 @@ dependencies = [ "raw-window-handle", "rustc-hash", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.9", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" -version = "23.0.1" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +checksum = "5a782e5056b060b0b4010881d1decddd059e44f2ecd01e2db2971b48ad3627e5" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "bytemuck", - "cfg_aliases 0.1.1", + "cfg_aliases", "core-graphics-types", - "glow 0.14.2", + "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-descriptor", @@ -5452,13 +5585,14 @@ dependencies = [ "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", + "ordered-float", "parking_lot", "profiling", "raw-window-handle", "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.9", "wasm-bindgen", "web-sys", "wgpu-types", @@ -5467,12 +5601,13 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "23.0.0" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "js-sys", + "log", "web-sys", ] @@ -5995,11 +6130,11 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "bytemuck", "calloop", - "cfg_aliases 0.2.1", + "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", "core-graphics", @@ -6123,7 +6258,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "dlib", "log", "once_cell", @@ -6159,13 +6294,16 @@ name = "yamm" version = "0.1.0" dependencies = [ "bincode", + "built", "chrono", "chrono-humanize", "eframe", "egui", "egui_extras", + "egui_tiles", "fern", "figment", + "git2", "image", "lazy_static", "log", diff --git a/Cargo.toml b/Cargo.toml index 54b538c..6e3c367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ name = "yamm" version = "0.1.0" edition = "2021" +authors = ["Volkor <me@volkor.me>"] +build = "build.rs" [dependencies] fern = "0.7.1" @@ -19,10 +21,15 @@ bincode = "1.3.3" chrono-humanize = "0.2.3" # gui -eframe = { version = "0.30.0", features = ["wgpu"] } -egui = "0.30.0" -walkers = "0.33.0" -egui_extras = { version = "0.30.0", features = ["all_loaders"] } +eframe = { version = "0.31.0", features = ["wgpu"] } +egui = "0.31.0" +walkers = "0.34.0" +egui_extras = { version = "0.31.0", features = ["all_loaders"] } image = { version = "0.25", features = ["jpeg", "png", "webp"] } chrono = "0.4.39" longitude = "0.2.1" +egui_tiles = "0.12.0" + +[build-dependencies] +built = { version = "0.7.6", features = ["git2", "chrono"] } +git2 = "0.20.0" \ No newline at end of file diff --git a/assets/icons/lan-connect.svg b/assets/icons/lan-connect.svg new file mode 100644 index 0000000..4dd9b2d --- /dev/null +++ b/assets/icons/lan-connect.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,13V18L3,20H10V18H5V13H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M14,15H20V19H14V15Z" /></svg> \ No newline at end of file diff --git a/assets/icons/lan-disconnect.svg b/assets/icons/lan-disconnect.svg new file mode 100644 index 0000000..5cbdfbd --- /dev/null +++ b/assets/icons/lan-disconnect.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3.88,13.46L2.46,14.88L4.59,17L2.46,19.12L3.88,20.54L6,18.41L8.12,20.54L9.54,19.12L7.41,17L9.54,14.88L8.12,13.46L6,15.59L3.88,13.46M14,15H20V19H14V15Z" /></svg> \ No newline at end of file diff --git a/assets/icons/lan-pending.svg b/assets/icons/lan-pending.svg new file mode 100644 index 0000000..48946b4 --- /dev/null +++ b/assets/icons/lan-pending.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,12V14H5V12H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3,15V17H5V15H3M14,15H20V19H14V15M3,18V20H5V18H3M6,18V20H8V18H6M9,18V20H11V18H9Z" /></svg> \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..1723685 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + built::write_built_file().expect("Failed to acquire build-time information"); +} \ No newline at end of file diff --git a/docs/gui.md b/docs/gui.md index 5b2df1f..a114ee8 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -7,4 +7,8 @@ - https://crates.io/crates/egui-toast ? - Nice graphs - https://github.com/emilk/egui_plot - - use https://github.com/hacknus/serial-monitor-rust they have nice plots, we should steal it \ No newline at end of file + - use https://github.com/hacknus/serial-monitor-rust they have nice plots, we should steal it +- 'Tiling' UI, make it piss easy to change the UI to each user's liking. + - Pop out windows to new ones? Can we also tile them?? + - List of Presets, with naming? + - One single context/alt/top bar for file, edit, whatever. \ No newline at end of file diff --git a/src/gui.rs b/src/gui.rs deleted file mode 100644 index 38824fc..0000000 --- a/src/gui.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::str::FromStr; - -use chrono::DateTime; -use egui_extras::{Column, TableBuilder}; -use meshtastic::protobufs::{config::device_config::Role, HardwareModel}; -use tracing::debug; -use chrono_humanize::{self, HumanTime}; -use longitude::{Distance, DistanceUnit, Location}; -use meshtastic::protobufs::Position; - -use crate::{db::Database, CONFIG}; - -/// Calculates the distance betwen 2 coordinate points. -fn calc_distance_to_node(away: Position) -> Option<f64> { - let home_pos = Database::read_home_node().expect("Failed to find Home Node").position.unwrap(); - - let distance = Location { - // Home Base - latitude: home_pos.latitude_i as f64 * 1e-7, - longitude: home_pos.longitude_i as f64 * 1e-7, - }.distance(&Location { - // Away - latitude: away.latitude_i as f64 * 1e-7, - longitude: away.longitude_i as f64 * 1e-7, - }); - // convert to the right distance depending on settings - match CONFIG.gui.units.as_ref() { - "Centimeters" => Some(distance.in_unit(DistanceUnit::Centimeters)), - "Meters" => Some(distance.in_unit(DistanceUnit::Meters)), - "Kilometers" => Some(distance.in_unit(DistanceUnit::Kilometers)), - "Inches" => Some(distance.in_unit(DistanceUnit::Inches)), - "Feet" => Some(distance.in_unit(DistanceUnit::Feet)), - "Yards" => Some(distance.in_unit(DistanceUnit::Yards)), - "Miles" => Some(distance.in_unit(DistanceUnit::Miles)), - _ => Some(distance.in_unit(DistanceUnit::Kilometers)), // if you can't type, force km. - } -} - -pub(crate) async fn run_gui() { - debug!("Running in GUI Mode!"); - use eframe::{App, egui}; - - struct MyApp; - - impl App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // Table Panel, showing nodes in a list. - egui::CentralPanel::default().show(ctx, |ui| { - // Get nodes from DB - let mut nodes = Database::read_node_list().expect("Failed to read nodes"); - - // Sort nodes by `last_heard` in descending order - nodes.sort_by(|a, b| b.last_heard.cmp(&a.last_heard)); - - TableBuilder::new(ui) - // TODO for the table: - // - Clickable Table Sorting - // - Add more info - // - Auto hide/show scrollbar on smaller devices - // - Add to a panel that we can move around / shitty windowing. - .column(Column::remainder()) // node id - .column(Column::remainder().clip(true)) // node name - .column(Column::remainder()) // snr - .column(Column::remainder()) // dist - .column(Column::remainder()) // hops - .column(Column::remainder().clip(true)) // hw model - .column(Column::remainder()) // role - .column(Column::remainder()) // battery - .column(Column::remainder().clip(true)) // last heard - .striped(true) - .header(15.0, |mut header| { - header.col(|ui| { - ui.heading("Node ID"); - }); - header.col(|ui| { - ui.heading("Name"); - }); - header.col(|ui| { - ui.heading("SNR"); - }); - header.col(|ui| { - - // Get distance units - let unit = match CONFIG.gui.units.as_ref() { - "Centimeters" => "cm", - "Meters" => "m", - "Kilometers" => "km", - "Inches" => "in", - "Feet" => "ft", - "Yards" => "yd", - "Miles" => "mi", - _ => "km", // Default to km if no match found. - }; - - ui.heading(format!("Distance ({})", unit)); - }); - header.col(|ui| { - ui.heading("Hops Away"); - }); - header.col(|ui| { - ui.heading("HW Model"); - }); - header.col(|ui| { - ui.heading("Role"); - }); - header.col(|ui| { - ui.heading("Battery"); - }); - header.col(|ui| { - ui.heading("Last Heard"); - }); - }) - .body(|mut body| { - for node in nodes { - let user = node.user.unwrap(); - let device = node.device_metrics.unwrap(); - // Convert the last heard to a time, then make it nice to read. - let now = chrono::Local::now().to_utc(); - let humanised = if node.last_heard == 0 { - String::from("Unknown") - } else { - let last_heard = DateTime::from_timestamp(node.last_heard.into(), 0).unwrap(); - let duration = last_heard - now; - HumanTime::from(duration).to_string() - }; - - body.row(15.0, |mut row| { - row.col(|ui| { - ui.label(user.id); - }); - row.col(|ui| { - ui.label(format!("{} ({})", user.long_name, user.short_name)); - }); - row.col(|ui| { - ui.label(node.snr.to_string()); - }); - row.col(|ui| { - if let Some(distance) = calc_distance_to_node(node.position.unwrap()) { - if distance.is_nan() { - ui.label("-"); - } else { - ui.label(format!("{:.2}", distance)); - } - } else { - ui.label("-"); - } - }); - row.col(|ui| { - ui.label(node.hops_away.to_string()); - }); - row.col(|ui| { - // Convert the i32 to a HardwareModel object, then convert to string - ui.label(HardwareModel::from_i32(user.hw_model).unwrap_or_default().as_str_name()); - }); - row.col(|ui| { - // Convert the i32 to a Role object, convert to string. - ui.label(Role::from_i32(user.role).unwrap_or_default().as_str_name()); - }); - row.col(|ui| { - ui.label(device.battery_level.to_string()); - }); - row.col(|ui| { - ui.label(humanised); - }); - }); - } - }); - }); - } - } - - let native_options = eframe::NativeOptions::default(); - eframe::run_native( - "Yet Another Meshtastic Server (GUI)", - native_options, - Box::new(|_cc| Ok(Box::new(MyApp))), - ).unwrap(); -} \ No newline at end of file diff --git a/src/gui/context_menu.rs b/src/gui/context_menu.rs new file mode 100644 index 0000000..c064bbe --- /dev/null +++ b/src/gui/context_menu.rs @@ -0,0 +1,81 @@ +/// This controls the big bar at the top +/// you know, the one that has File, Edit, View, whatever else? +/// that one + +#[derive(Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct ContextMenus {} + +impl crate::Demo for ContextMenus { + fn name(&self) -> &'static str { + "☰ Context Menus" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + use crate::View; + egui::Window::new(self.name()) + .vscroll(false) + .resizable(false) + .open(open) + .show(ctx, |ui| self.ui(ui)); + } +} + +impl crate::View for ContextMenus { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.menu_button("Click for menu", Self::nested_menus); + + ui.button("Right-click for menu") + .context_menu(Self::nested_menus); + + if ui.ctx().is_context_menu_open() { + ui.label("Context menu is open"); + } else { + ui.label("Context menu is closed"); + } + }); + + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file!()); + }); + } +} + +impl ContextMenus { + fn nested_menus(ui: &mut egui::Ui) { + ui.set_max_width(200.0); // To make sure we wrap long text + + if ui.button("Open…").clicked() { + ui.close_menu(); + } + ui.menu_button("SubMenu", |ui| { + ui.menu_button("SubMenu", |ui| { + if ui.button("Open…").clicked() { + ui.close_menu(); + } + let _ = ui.button("Item"); + }); + ui.menu_button("SubMenu", |ui| { + if ui.button("Open…").clicked() { + ui.close_menu(); + } + let _ = ui.button("Item"); + }); + let _ = ui.button("Item"); + if ui.button("Open…").clicked() { + ui.close_menu(); + } + }); + ui.menu_button("SubMenu", |ui| { + let _ = ui.button("Item1"); + let _ = ui.button("Item2"); + let _ = ui.button("Item3"); + let _ = ui.button("Item4"); + if ui.button("Open…").clicked() { + ui.close_menu(); + } + }); + let _ = ui.button("Very long text for this item that should be wrapped"); + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000..abc0769 --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,446 @@ +pub mod widgets; +use crate::gui::widgets::WidgetType::About; +use egui::{menu, Color32, FontDefinitions}; +use egui_tiles::{SimplificationOptions, Tile, TileId, Tiles}; +use tracing::debug; + +/// Runs the actual GUI. +/// We contruct the empty tree, headers and basic window stuff here. +/// Widgets are all opened via this, or loaded from a saved state. +pub(crate) async fn run_gui() -> Result<(), eframe::Error> { + debug!("Running in GUI Mode!"); + // run actual ui + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([1920.0, 1080.0]), + ..Default::default() + }; + eframe::run_native( + "Y(et) A(nother) M(eshtastic) S(erver) - v0.1.0", + options, + Box::new(|_cc| { + #[cfg_attr(not(feature = "serde"), allow(unused_mut))] + let mut app = YamsApp::default(); + #[cfg(feature = "serde")] + if let Some(storage) = _cc.storage { + if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) { + app = state; + } + } + Ok(Box::new(app)) + }), + ) +} + +/// This allows us to add new widgets into the UI. +impl YamsApp { + // Add a new pane to the tree, depending on the widget_type. + fn add_about(&mut self, widget_type: widgets::WidgetType,) { + // Find the next free id. (maybe?) + let next_view_num = self.tree.tiles.next_free_id().0 as usize; + // Create a new pane with said view number. + let pane = Pane::with_nr(next_view_num, widget_type); + // Insert it! + let _ = self.tree.tiles.insert_pane(pane); + } +} + +// Everything below here I copied directly from the egui_tiles example, I am adapting it. thanks. + +/// This is the default state for the egui environment. +impl Default for YamsApp { + fn default() -> Self { + // Initialize a variable to keep track of the next view number + let mut next_view_nr = 0; + // Define a closure that will create a new Pane with a unique view number + // and increment the next_view_nr variable for the next time it's called + let mut gen_view = || { + let view = Pane::with_nr(next_view_nr, widgets::WidgetType::NodeList); + next_view_nr += 1; + view + }; + + // Create an empty tiles structure to store a tile (which can contain more tiles) + let mut tiles = egui_tiles::Tiles::default(); + + // Vector to hold the ids of our tabs + let mut tabs = vec![]; + + // Insert a tab tile with 7 children into our tiles and push its id onto the tabs vector + let tab_tile = { + let children = (0..7).map(|_| tiles.insert_pane(gen_view())).collect(); + tiles.insert_tab_tile(children) + }; + tabs.push(tab_tile); + + // Insert a horizontal tile with 7 children into our tiles and push its id onto the tabs vector + tabs.push({ + let children = (0..7).map(|_| tiles.insert_pane(gen_view())).collect(); + tiles.insert_horizontal_tile(children) + }); + + // Insert a vertical tile with 7 children into our tiles and push its id onto the tabs vector + tabs.push({ + let children = (0..7).map(|_| tiles.insert_pane(gen_view())).collect(); + tiles.insert_vertical_tile(children) + }); + + // Insert a grid tile with 11 cells into our tiles and push its id onto the tabs vector + tabs.push({ + let cells = (0..11).map(|_| tiles.insert_pane(gen_view())).collect(); + tiles.insert_grid_tile(cells) + }); + + // Insert a single pane tile into our tiles and push its id onto the tabs vector + tabs.push(tiles.insert_pane(gen_view())); + + // Insert a tab tile with all of our tabs as children into our tiles + let root = tiles.insert_tab_tile(tabs); + + // Create an egui_tiles::Tree structure to represent our tree of views, starting at the root id + let tree = egui_tiles::Tree::new("my_tree", root, tiles); + + // Return a new YamsApp instance with our tree and default behavior + Self { + tree, + behavior: Default::default(), + } + } +} + +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +/// A struct representing a single pane in the GUI. +/// Each pane has a unique number associated with it. +pub struct Pane { + /// The unique number of this pane. + pub nr: usize, + pub widget_type: widgets::WidgetType, +} + +impl std::fmt::Debug for Pane { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Write out the debug representation of the Pane struct. + f.debug_struct("Pane") + .field("nr", &self.nr) // Include the pane's number in the output. + .finish() + } +} + +impl Pane { + /// Creates a new Pane with a unique view number. + pub fn with_nr(nr: usize, widget_type: widgets::WidgetType) -> Self { + Self { nr, widget_type } + } + /// Draws the UI for the Pane. + /// This is where we actually put stuff into our panes, like text or buttons. + /// We also handle dragging of the pane here. + pub fn ui(&self, ui: &mut egui::Ui) -> egui_tiles::UiResponse { + + // Pass the widget type and ui object down for rendering of the correct type. + widgets::render_widget(&self.widget_type, ui); + + // We don't drag these boys. (surely I can remove this?) + egui_tiles::UiResponse::None + } +} + +// The storage for the Tree Behaviour config options +struct TreeBehavior { + simplification_options: egui_tiles::SimplificationOptions, + tab_bar_height: f32, + gap_width: f32, + add_child_to: Option<egui_tiles::TileId>, +} + +// The defaults for how the tree should behave. +impl Default for TreeBehavior { + fn default() -> Self { + Self { + simplification_options: { + SimplificationOptions { + all_panes_must_have_tabs: true, + ..Default::default() + } + }, + tab_bar_height: 24.0, + gap_width: 2.0, + add_child_to: None, + } + } +} + + +impl TreeBehavior { + /// The UI for changing TreeBehaviour at runtime + fn ui(&mut self, ui: &mut egui::Ui) { + let Self { + simplification_options, + tab_bar_height, + gap_width, + add_child_to: _, + } = self; + + // egui::Grid::new("behavior_ui") + // .num_columns(2) + // .show(ui, |ui| { + // ui.label("All panes must have tabs:"); + // ui.checkbox(&mut simplification_options.all_panes_must_have_tabs, ""); + // ui.end_row(); + + // ui.label("Join nested containers:"); + // ui.checkbox( + // &mut simplification_options.join_nested_linear_containers, + // "", + // ); + // ui.end_row(); + + // ui.label("Tab bar height:"); + // ui.add( + // egui::DragValue::new(tab_bar_height) + // .range(0.0..=100.0) + // .speed(1.0), + // ); + // ui.end_row(); + + // ui.label("Gap width:"); + // ui.add(egui::DragValue::new(gap_width).range(0.0..=20.0).speed(1.0)); + // ui.end_row(); + // }); + } +} + + +impl egui_tiles::Behavior<Pane> for TreeBehavior { + fn pane_ui( + &mut self, + ui: &mut egui::Ui, + _tile_id: egui_tiles::TileId, + view: &mut Pane, + ) -> egui_tiles::UiResponse { + view.ui(ui) + } + + fn tab_title_for_pane(&mut self, view: &Pane) -> egui::WidgetText { + // Use default Display for most names, but some need to have shit added! + let mut title = "".to_string(); + match view.widget_type.clone() { + widgets::WidgetType::NodeInfo => { + title = format!("Node (SHORTNAME)"); + // We need to get the nodes name. + // name should be "Node (SHORTNAME)" + // example: "Node (POCO)" + }, + widgets::WidgetType::Log => { + title = format!("{}", widgets::WidgetType::Log) + }, + widgets::WidgetType::MapView => { + title = format!("{}", widgets::WidgetType::MapView) + }, + widgets::WidgetType::Settings => { + title = format!("{}", widgets::WidgetType::Settings) + }, + widgets::WidgetType::SignalStrengthHistorical => { + title = format!("{}", widgets::WidgetType::SignalStrengthHistorical) + }, + widgets::WidgetType::NodeList => { + title = format!("{}", widgets::WidgetType::NodeList) + }, + // why is this the only one not bitching about the widgets:: crap? + About => { + title = format!("{}", widgets::WidgetType::About) + }, + } + title.into() + } + + fn top_bar_right_ui( + &mut self, + _tiles: &egui_tiles::Tiles<Pane>, + ui: &mut egui::Ui, + tile_id: egui_tiles::TileId, + _tabs: &egui_tiles::Tabs, + _scroll_offset: &mut f32, + ) { + if ui.button("➕").clicked() { + self.add_child_to = Some(tile_id); + } + } + + // --- + // Settings: + + fn tab_bar_height(&self, _style: &egui::Style) -> f32 { + self.tab_bar_height + } + + fn gap_width(&self, _style: &egui::Style) -> f32 { + self.gap_width + } + + fn simplification_options(&self) -> egui_tiles::SimplificationOptions { + self.simplification_options + } + + fn is_tab_closable(&self, _tiles: &Tiles<Pane>, _tile_id: TileId) -> bool { + true + } + + fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool { + if let Some(tile) = tiles.get(tile_id) { + match tile { + Tile::Pane(pane) => { + // Single pane removal + let tab_title = self.tab_title_for_pane(pane); + log::debug!("Closing tab: {}, tile ID: {tile_id:?}", tab_title.text()); + } + Tile::Container(container) => { + // Container removal + log::debug!("Closing container: {:?}", container.kind()); + let children_ids = container.children(); + for child_id in children_ids { + if let Some(Tile::Pane(pane)) = tiles.get(*child_id) { + let tab_title = self.tab_title_for_pane(pane); + log::debug!("Closing tab: {}, tile ID: {tile_id:?}", tab_title.text()); + } + } + } + } + } + + // Proceed to removing the tab + true + } +} + +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +struct YamsApp { + tree: egui_tiles::Tree<Pane>, + + #[cfg_attr(feature = "serde", serde(skip))] + behavior: TreeBehavior, +} + +impl eframe::App for YamsApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Initalise Image Loading (for icons) + egui_extras::install_image_loaders(ctx); + + // Context/Menu bar at the top + egui::TopBottomPanel::top("context_bar").show(ctx, |ui| { + // menu bar sits in here + menu::bar(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("Exit").clicked() { + std::process::exit(0); + } + }); + + // List of widgets here, we can add new widgets into the panel from here! + ui.menu_button("Widgets", |ui| { + // Nested menus here + // Add Widget -> Widget Selector + }); + + // UI Tools (reset, split? layouts?) + ui.menu_button("View", |ui| { + if ui.button("Reset Panels").clicked() { + // Nothing yet + } + }); + // Help + ui.menu_button("Help", |ui|{ + if ui.button("Documentation").clicked() { + // open git to the wiki / docs page + } + if ui.button("About").clicked() { + // Create a new about widget (don't show it in the widgets menu?) + // TODO: THIS DOES NOT WORK! + self.add_about(widgets::WidgetType::About); + + } + }); + // Right aligned, logos for showing general status + // Connection Status enum: + // ServerNotRunning: mdiLanDisconnect (message: "YAMS is not running") + // MeshNoConnection: mdiLanPending (message: "YAMS cannot connect to Meshtastic node") + // MeshConnected: mdiLanConnect (message: "YAMS is connected and receiving data") + // TODO + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add(egui::Image::new(egui::include_image!("../../assets/icons/lan-connect.svg")) + .bg_fill(Color32::GREEN) + .alt_text("YAMS is connected and receiving data")) + // outside the image object + .on_hover_text_at_pointer("YAMS is connected and receiving data"); + if ui.button("Reset").clicked() { + *self = Default::default(); + } + self.behavior.ui(ui); + }); + }); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + self.tree.ui(&mut self.behavior, ui); + }); + } + + fn save(&mut self, _storage: &mut dyn eframe::Storage) { + #[cfg(feature = "serde")] + eframe::set_value(_storage, eframe::APP_KEY, &self); + } +} + +fn tree_ui( + ui: &mut egui::Ui, + behavior: &mut dyn egui_tiles::Behavior<Pane>, + tiles: &mut egui_tiles::Tiles<Pane>, + tile_id: egui_tiles::TileId, +) { + // Get the name BEFORE we remove the tile below! + let text = format!( + "{} - {tile_id:?}", + behavior.tab_title_for_tile(tiles, tile_id).text() + ); + + // Temporarily remove the tile to circumvent the borrowchecker + let Some(mut tile) = tiles.remove(tile_id) else { + log::debug!("Missing tile {tile_id:?}"); + return; + }; + + let default_open = true; + egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.id().with((tile_id, "tree")), + default_open, + ) + .show_header(ui, |ui| { + ui.label(text); + let mut visible = tiles.is_visible(tile_id); + ui.checkbox(&mut visible, "Visible"); + tiles.set_visible(tile_id, visible); + }) + .body(|ui| match &mut tile { + egui_tiles::Tile::Pane(_) => {} + egui_tiles::Tile::Container(container) => { + let mut kind = container.kind(); + egui::ComboBox::from_label("Kind") + .selected_text(format!("{kind:?}")) + .show_ui(ui, |ui| { + for typ in egui_tiles::ContainerKind::ALL { + ui.selectable_value(&mut kind, typ, format!("{typ:?}")) + .clicked(); + } + }); + if kind != container.kind() { + container.set_kind(kind); + } + + for &child in container.children() { + tree_ui(ui, behavior, tiles, child); + } + } + }); + + // Put the tile back + tiles.insert(tile_id, tile); +} diff --git a/src/gui/widgets/about.rs b/src/gui/widgets/about.rs new file mode 100644 index 0000000..5e76fd5 --- /dev/null +++ b/src/gui/widgets/about.rs @@ -0,0 +1,71 @@ +use core::f32; + +use egui::{text::TextWrapping, Color32, Label, RichText, Separator}; +use egui_tiles::UiResponse; +use crate::{built_info, gui::Pane}; + +/// Shows information about the program. +/// you know, the normal boring stuff +/// Shows: +/// - cool ascii logo +/// - commit number / build hash or something +/// - link to repo +/// - contributors +/// - random shit + +pub fn about_ui(ui: &mut egui::Ui) { + // Show cool ascii logo + // TODO: make this not wrap or truncate with ... + // double TODO: replace the logo with a cool ascii art of actual yams (vegetable) with text + let mut cool_logo = RichText::new(" +▓██ ██▓ ▄▄▄ ███▄ ▄███▓ ██████ + ▒██ ██▒▒████▄ ▓██▒▀█▀ ██▒▒██ ▒ + ▒██ ██░▒██ ▀█▄ ▓██ ▓██░░ ▓██▄ + ░ ▐██▓░░██▄▄▄▄██ ▒██ ▒██ ▒ ██▒ + ░ ██▒▓░ ▓█ ▓██▒▒██▒ ░██▒▒██████▒▒ + ██▒▒▒ ▒▒ ▓▒█░░ ▒░ ░ ░▒ ▒▓▒ ▒ ░ + ▓██ ░▒░ ▒ ▒▒ ░░ ░ ░░ ░▒ ░ ░ + ▒ ▒ ░░ ░ ▒ ░ ░ ░ ░ ░ + ░ ░ ░ ░ ░ ░ + ░ ░ + ") + .monospace(); + + // Change the colour to green if it's a release build. + let color = if built_info::PROFILE == "release" { + Color32::DARK_GREEN + } else { + Color32::DARK_RED + }; + + // Although we need to disable some stuff to make it not look like crap most of the time... + let text_wrapping = TextWrapping { + max_width: f32::MAX, + overflow_character: None, + ..Default::default() + }; + + ui.add(Label::new(cool_logo.color(color))); + + ui.separator(); + + ui.label(egui::RichText::new("Build Information").heading().strong()); + + // Debug info + // TODO: use the text layout more directly, allow for bold midway through the message (the actual info) + ui.label(format!("Version {}-{} ({}), built for {} with {}. \nBuilt on {}", + built_info::PKG_VERSION, + built_info::PROFILE, + built_info::GIT_COMMIT_HASH_SHORT.unwrap_or_default(), + built_info::TARGET, + built_info::RUSTC_VERSION, + built_info::BUILT_TIME_UTC + )); + + + // Show link to repo + ui.hyperlink("https://git.volkor.me/Volkor/yams"); + + // Show authors/contributors + ui.label(format!("Contributors: {}", built_info::PKG_AUTHORS)); +} \ No newline at end of file diff --git a/src/gui/widgets/debug.rs b/src/gui/widgets/debug.rs new file mode 100644 index 0000000..0f1b51d --- /dev/null +++ b/src/gui/widgets/debug.rs @@ -0,0 +1 @@ +/// should steal the debug inspector / whatever else from egui examples. \ No newline at end of file diff --git a/src/gui/widgets/log.rs b/src/gui/widgets/log.rs new file mode 100644 index 0000000..67d0432 --- /dev/null +++ b/src/gui/widgets/log.rs @@ -0,0 +1,3 @@ +/// This widget shows all log output from the program. +/// You should be able to open multiple of these, so we can have "Serial Log" and "YAMS Log" as two separate instances of this widget. +/// Filtering by log level? \ No newline at end of file diff --git a/src/gui/widgets/map_view.rs b/src/gui/widgets/map_view.rs new file mode 100644 index 0000000..e45c5bb --- /dev/null +++ b/src/gui/widgets/map_view.rs @@ -0,0 +1,7 @@ +/// Shows a Leaflet map of all the nodes picked up. +/// Options for: +/// - Direct connections - coloured lines depending on signal strength? +/// - Indirect connections - deducted from traceroutes / other messages / neighbourinfo +/// - Hiding inactive nodes - set number of hours before greying out, days before hiding completely +/// - List of nodes - optional filter for ones with no gps, but have connections (use neighbours / traceroutes to figure out general location?) +/// - Position History - Shows a nice home assistant style history for each nodes position. \ No newline at end of file diff --git a/src/gui/widgets/message_list.rs b/src/gui/widgets/message_list.rs new file mode 100644 index 0000000..53c096a --- /dev/null +++ b/src/gui/widgets/message_list.rs @@ -0,0 +1,5 @@ +/// Widget displays a list of all messages +/// Specifically: +/// - Sub tree of all channels (with >0 messages) +/// - Filter messages by user (right click user? filters?) +/// - dedicated filter for unknown encrypted messages? diff --git a/src/gui/widgets/mod.rs b/src/gui/widgets/mod.rs new file mode 100644 index 0000000..e1d6d30 --- /dev/null +++ b/src/gui/widgets/mod.rs @@ -0,0 +1,47 @@ +// List of widgets available. + +use std::fmt; +pub mod about; +pub mod node_list; + +#[derive(Clone)] +pub(crate) enum WidgetType { + About, // Shows info relevant about YAMS itself. + NodeList, // Shows nodes in a table + NodeInfo, // Shows information about a specific node + Log, // Shows log output from yams (or serial) + MapView, // Shows Nodes on a map + Settings, // Shows Settings + SignalStrengthHistorical, // Shows the (average) signal strength of all messages (dropdown to select specific nodes? - will that work with non-direct nodes?) +} + +/// Nice formatting for them, for rendering on the tab headers +impl fmt::Display for WidgetType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WidgetType::About => write!(f, "About YAMS"), + WidgetType::NodeList => write!(f, "Node List"), + WidgetType::Log => write!(f, "Log"), + WidgetType::MapView => write!(f, "Map View"), + WidgetType::Settings => write!(f, "Settings"), + WidgetType::SignalStrengthHistorical => write!(f, "Signal Strength Historical"), + WidgetType::NodeInfo => write!(f, "Node"), + } + } +} + +pub fn render_widget(widget_type: &WidgetType, ui: &mut egui::Ui) { + match widget_type { + WidgetType::About => { + about::about_ui(ui); + }, + WidgetType::NodeList => { + node_list::nodelist_ui(ui); + }, + WidgetType::NodeInfo => todo!(), + WidgetType::Log => todo!(), + WidgetType::MapView => todo!(), + WidgetType::Settings => todo!(), + WidgetType::SignalStrengthHistorical => todo!(), + } +} \ No newline at end of file diff --git a/src/gui/widgets/node_list.rs b/src/gui/widgets/node_list.rs new file mode 100644 index 0000000..6793aff --- /dev/null +++ b/src/gui/widgets/node_list.rs @@ -0,0 +1,193 @@ +use chrono::DateTime; +use chrono_humanize::HumanTime; +use egui_extras::{Column, TableBuilder}; +use longitude::{DistanceUnit, Location}; +use meshtastic::protobufs::{channel::Role, HardwareModel, Position}; + +use crate::{db::Database, CONFIG}; + +// The list of nodes +// This lists all the info about each node. +// Sortable by any column, allow hiding columns? +// Nice signal strength bar (also allow clicking to view signal strength history?) +// Filters: +// - Direct Connection (direct neighbours / nodes with 0 hops away) +// - Active (seen in the last `n` minutes) +// - Inactive (not seen in the last `n` minutes) +// - Location (has/doesn't have gps location) + +/// Calculates the distance betwen 2 coordinate points. +fn calc_distance_to_node(away: Position) -> Option<f64> { + let home_pos = Database::read_home_node().expect("Failed to find Home Node").position.unwrap(); + + let distance = Location { + // Home Base + latitude: home_pos.latitude_i as f64 * 1e-7, + longitude: home_pos.longitude_i as f64 * 1e-7, + }.distance(&Location { + // Away + latitude: away.latitude_i as f64 * 1e-7, + longitude: away.longitude_i as f64 * 1e-7, + }); + // convert to the right distance depending on settings + match CONFIG.gui.units.as_ref() { + "Centimeters" => Some(distance.in_unit(DistanceUnit::Centimeters)), + "Meters" => Some(distance.in_unit(DistanceUnit::Meters)), + "Kilometers" => Some(distance.in_unit(DistanceUnit::Kilometers)), + "Inches" => Some(distance.in_unit(DistanceUnit::Inches)), + "Feet" => Some(distance.in_unit(DistanceUnit::Feet)), + "Yards" => Some(distance.in_unit(DistanceUnit::Yards)), + "Miles" => Some(distance.in_unit(DistanceUnit::Miles)), + _ => Some(distance.in_unit(DistanceUnit::Kilometers)), // if you can't type, force km. + } +} + +/// UI Elements for the Node List +pub fn nodelist_ui(ui: &mut egui::Ui) { + // Get nodes from DB + let mut nodes = Database::read_node_list().expect("Failed to read nodes"); + + // Sort nodes by `last_heard` in descending order + nodes.sort_by(|a, b| b.last_heard.cmp(&a.last_heard)); + + TableBuilder::new(ui) + // TODO for the table: + // - Clickable Table Sorting + // - Add more info + // - Auto hide/show scrollbar on smaller devices + // - Add to a panel that we can move around / shitty windowing. + .column(Column::remainder()) // node id + .column(Column::remainder().clip(true)) // node name + .column(Column::remainder()) // snr + .column(Column::remainder()) // dist + .column(Column::remainder()) // hops + .column(Column::remainder().clip(true)) // hw model + .column(Column::remainder()) // role + .column(Column::remainder()) // battery + .column(Column::remainder().clip(true)) // last heard + .striped(true) + .header(15.0, |mut header| { + header.col(|ui| { + ui.heading("Node ID"); + }); + header.col(|ui| { + ui.heading("Name"); + }); + header.col(|ui| { + ui.heading("SNR"); + }); + header.col(|ui| { + + // Get distance units + let unit = match CONFIG.gui.units.as_ref() { + "Centimeters" => "cm", + "Meters" => "m", + "Kilometers" => "km", + "Inches" => "in", + "Feet" => "ft", + "Yards" => "yd", + "Miles" => "mi", + _ => "km", // Default to km if no match found. + }; + + ui.heading(format!("Distance ({})", unit)); + }); + header.col(|ui| { + ui.heading("Hops Away"); + }); + header.col(|ui| { + ui.heading("HW Model"); + }); + header.col(|ui| { + ui.heading("Role"); + }); + header.col(|ui| { + ui.heading("Battery"); + }); + header.col(|ui| { + ui.heading("Last Heard"); + }); + }) + .body(|mut body| { + for node in nodes { + let user = node.user.unwrap(); + let device = node.device_metrics.unwrap(); + // Convert the last heard to a time, then make it nice to read. + let now = chrono::Local::now().to_utc(); + let humanised = if node.last_heard == 0 { + String::from("Unknown") + } else { + let last_heard = DateTime::from_timestamp(node.last_heard.into(), 0).unwrap(); + let duration = last_heard - now; + HumanTime::from(duration).to_string() + }; + + body.row(15.0, |mut row| { + row.col(|ui| { + ui.label(user.id); + }); + row.col(|ui| { + ui.label(format!("{} ({})", user.long_name, user.short_name)); + }); + row.col(|ui| { + ui.label(node.snr.to_string()); + }); + row.col(|ui| { + if let Some(distance) = calc_distance_to_node(node.position.unwrap()) { + if distance.is_nan() { + ui.label("-"); + } else { + ui.label(format!("{:.2}", distance)); + } + } else { + ui.label("-"); + } + }); + row.col(|ui| { + ui.label(node.hops_away.to_string()); + }); + row.col(|ui| { + // Convert the i32 to a HardwareModel object, then convert to string + ui.label(HardwareModel::from_i32(user.hw_model).unwrap_or_default().as_str_name()); + }); + row.col(|ui| { + // Convert the i32 to a Role object, convert to string. + ui.label(Role::from_i32(user.role).unwrap_or_default().as_str_name()); + }); + row.col(|ui| { + ui.label(device.battery_level.to_string()); + }); + row.col(|ui| { + ui.label(humanised); + }); + }); + } + }); +} + + + +// /// Initialise the raw GUI. +// /// We pass all tiling 'widgets' to their own files. +// pub(crate) async fn run_gui() { +// debug!("Running in GUI Mode!"); +// use eframe::{App, egui}; + +// struct MyApp; + +// impl App for MyApp { +// fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { +// // Table Panel, showing nodes in a list. +// egui::CentralPanel::default().show(ctx, |ui| { + +// }); +// } +// } + +// let native_options = eframe::NativeOptions::default(); +// eframe::run_native( +// "Yet Another Meshtastic Server (GUI)", +// native_options, +// Box::new(|_cc| Ok(Box::new(MyApp))), +// ).unwrap(); +// } \ No newline at end of file diff --git a/src/gui/widgets/settings.rs b/src/gui/widgets/settings.rs new file mode 100644 index 0000000..f351a9e --- /dev/null +++ b/src/gui/widgets/settings.rs @@ -0,0 +1,5 @@ +/// Settings +/// allows you to configure global settings for the program. +/// Dark / Light Modes +/// metric / imperial +/// more shit at a later date! \ No newline at end of file diff --git a/src/gui/widgets/signal_strength_history.rs b/src/gui/widgets/signal_strength_history.rs new file mode 100644 index 0000000..3d89780 --- /dev/null +++ b/src/gui/widgets/signal_strength_history.rs @@ -0,0 +1,3 @@ +/// Shows a nice graph of the selected node's SNR/RSSI +/// Ideally, it should look similar to the meshtastic graphs +/// Allow picking multiple nodes - averages? (if you want multiple graphs, use multiple widgets?) diff --git a/src/main.rs b/src/main.rs index f9fdaa8..5d6934e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod packets; mod db; mod config; mod gui; + use crate::config::AppConfig; // Setup the config globally because I can't figure out how to pass it to functions I don't call directly. @@ -107,7 +108,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { // match on gui or server match args[1].as_str() { "server" => run_server().await, - "gui" => gui::run_gui().await, + "gui" => gui::run_gui().await.expect("uh oh"), _ => { error!("Unknown mode: {}", args[1]); info!("Usage: yams <mode>"); @@ -117,4 +118,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { } Ok(()) +} + +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); } \ No newline at end of file -- GitLab