diff --git a/Cargo.lock b/Cargo.lock index 0ea4525d7a3f394555fca07fecf5dfef5146d156..95df11c4ccfc5f95e55e257648e49b9c23c2f546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ dependencies = [ "accesskit_consumer", "atspi-common", "serde", - "thiserror", + "thiserror 1.0.69", "zvariant", ] @@ -150,12 +150,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" - [[package]] name = "android-activity" version = "0.6.0" @@ -174,7 +168,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -194,9 +188,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arboard" @@ -242,9 +236,9 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener", "event-listener-strategy", @@ -345,7 +339,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -374,13 +368,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -542,22 +536,22 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -574,9 +568,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "calloop" @@ -589,7 +583,7 @@ dependencies = [ "polling", "rustix", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -606,9 +600,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -769,9 +763,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -793,27 +787,27 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -866,7 +860,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -976,7 +970,7 @@ dependencies = [ "egui", "epaint", "log", - "thiserror", + "thiserror 1.0.69", "type-map", "web-time", "wgpu", @@ -1105,7 +1099,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1117,7 +1111,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1138,7 +1132,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1149,7 +1143,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1184,12 +1178,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1211,9 +1205,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener", "pin-project-lite", @@ -1221,15 +1215,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -1244,6 +1238,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.5.0" @@ -1262,7 +1262,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1349,7 +1349,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1547,20 +1547,20 @@ checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "winapi", "windows 0.52.0", ] [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -1574,20 +1574,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "ahash", - "allocator-api2", + "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" - [[package]] name = "hassle-rs" version = "0.11.0" @@ -1598,17 +1591,11 @@ dependencies = [ "com", "libc", "libloading", - "thiserror", + "thiserror 1.0.69", "widestring", "winapi", ] -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1629,11 +1616,11 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1751,7 +1738,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -1798,12 +1785,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown", ] [[package]] @@ -1826,9 +1813,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -1841,7 +1828,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -1863,10 +1850,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1895,15 +1883,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -1917,7 +1905,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -1928,9 +1916,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "litrs" @@ -1984,7 +1972,7 @@ dependencies = [ "quick-xml 0.26.0", "quote", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2057,9 +2045,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", "simd-adler32", @@ -2067,11 +2055,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -2094,7 +2081,7 @@ dependencies = [ "rustc-hash", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -2110,7 +2097,7 @@ dependencies = [ "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2174,7 +2161,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -2204,7 +2191,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -2421,9 +2408,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2492,7 +2479,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -2511,29 +2498,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2560,9 +2547,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -2579,7 +2566,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", @@ -2612,9 +2599,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2655,9 +2642,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2709,9 +2696,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -2806,15 +2793,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2877,6 +2864,7 @@ dependencies = [ "serde_json", "skyward_mavlink", "strum", + "thiserror 2.0.9", "tokio", "tracing", "tracing-subscriber", @@ -2884,9 +2872,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -2902,20 +2890,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -2931,7 +2919,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -3071,7 +3059,7 @@ dependencies = [ "log", "memmap2", "rustix", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -3104,9 +3092,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3164,9 +3152,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -3181,17 +3169,18 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3221,7 +3210,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -3232,7 +3230,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", ] [[package]] @@ -3314,9 +3323,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3325,20 +3334,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -3357,9 +3366,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -3375,9 +3384,9 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "type-map" @@ -3407,15 +3416,15 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" @@ -3437,9 +3446,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -3488,9 +3497,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -3499,36 +3508,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3536,22 +3545,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wayland-backend" @@ -3664,9 +3673,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -3684,9 +3693,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5f07fb9bc8de2ddfe6b24a71a75430673fd679e568c48b52716cef1cfae923" +checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" dependencies = [ "block2", "core-foundation 0.10.0", @@ -3744,7 +3753,7 @@ dependencies = [ "raw-window-handle", "rustc-hash", "smallvec", - "thiserror", + "thiserror 1.0.69", "wgpu-hal", "wgpu-types", ] @@ -3783,7 +3792,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types", @@ -3888,7 +3897,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -3899,7 +3908,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -4128,9 +4137,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.5" +version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" +checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f" dependencies = [ "ahash", "android-activity", @@ -4180,9 +4189,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] @@ -4268,15 +4277,15 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" +checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -4286,13 +4295,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "synstructure", ] @@ -4352,7 +4361,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "zbus-lockstep", "zbus_xml", "zvariant", @@ -4367,7 +4376,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "zvariant_utils", ] @@ -4413,27 +4422,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "synstructure", ] @@ -4456,7 +4465,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] [[package]] @@ -4481,7 +4490,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", "zvariant_utils", ] @@ -4493,5 +4502,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.95", ] diff --git a/Cargo.toml b/Cargo.toml index 975dee222aa720c1a75d662474563cb73f656c5a..9b369adc9d499ce5afb1e682140090e9da747bdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,3 +46,4 @@ egui_extras = "0.29.1" strum = "0.26" anyhow = "1.0" ring-channel = "0.12.0" +thiserror = "2.0.7" diff --git a/src/error.rs b/src/error.rs index f00a0f523aabfcafa08a8a003be6c1fb988fff7d..725a6d35593169ed03c8ee5f2a08e78b982292df 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ pub trait ErrInstrument { type Inner; fn log_expect(self, msg: &str) -> Self::Inner; + fn log_unwrap(self) -> Self::Inner; } impl<T, E> ErrInstrument for Result<T, E> @@ -22,6 +23,16 @@ where } } } + + fn log_unwrap(self) -> Self::Inner { + match self { + Ok(t) => t, + Err(e) => { + error!("Called unwrap on an Err value: {:?}", e); + panic!("Called unwrap on an Err value: {:?}", e); + } + } + } } impl<T> ErrInstrument for Option<T> { @@ -36,4 +47,14 @@ impl<T> ErrInstrument for Option<T> { } } } + + fn log_unwrap(self) -> Self::Inner { + match self { + Some(t) => t, + None => { + error!("Called unwrap on a None value"); + panic!("Called unwrap on a None value"); + } + } + } } diff --git a/src/main.rs b/src/main.rs index 4c268010ecbb74a1660cc1b9584ef925a700ca20..d00f72ae3aa830528f61a4544248a12afd495ea0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ #![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] +#![warn(clippy::panic)] mod error; mod mavlink; @@ -53,7 +55,7 @@ fn main() -> Result<(), eframe::Error> { MSG_MANAGER .set(Mutex::new(MessageBroker::new( // FIXME: Choose where to put the channel size of the MessageBroker - NonZeroUsize::new(50).unwrap(), + NonZeroUsize::new(50).log_unwrap(), ctx.egui_ctx.clone(), ))) .log_expect("Unable to set MessageManager"); diff --git a/src/mavlink.rs b/src/mavlink.rs index 6e0c003f4f8b22ba24392ac27216606084442085..ab38cb5b543273413110c9c704bdea49313022eb 100644 --- a/src/mavlink.rs +++ b/src/mavlink.rs @@ -4,11 +4,13 @@ //! rapid switching between different mavlink versions and profiles (_dialects_). mod base; +mod error; mod message_broker; mod reflection; // Export all the types from the base module as if they were defined in this module pub use base::*; +pub use error::{MavlinkError, Result as MavlinkResult}; pub use message_broker::{MessageBroker, MessageView}; pub use reflection::ReflectionContext; diff --git a/src/mavlink/base.rs b/src/mavlink/base.rs index 8b7a4113627a4960345cddb4afa64c8468d08caf..a1aa3c53a4ef2bab982ecaaef808c7af0e66ffbe 100644 --- a/src/mavlink/base.rs +++ b/src/mavlink/base.rs @@ -14,6 +14,10 @@ pub use skyward_mavlink::{ reflection::ORION_MAVLINK_PROFILE_SERIALIZED as MAVLINK_PROFILE_SERIALIZED, }; +use crate::error::ErrInstrument; + +use super::error::{MavlinkError, Result}; + /// A wrapper around the `MavMessage` struct, adding a received time field. #[derive(Debug, Clone)] pub struct TimedMessage { @@ -37,20 +41,23 @@ impl TimedMessage { pub fn extract_from_message<K, T>( message: &MavMessage, fields: impl IntoIterator<Item = K>, -) -> Vec<T> +) -> Result<Vec<T>> where K: AsRef<str>, T: serde::de::DeserializeOwned + Default, { - let value: serde_json::Value = serde_json::to_value(message).unwrap(); - fields + let value: serde_json::Value = + serde_json::to_value(message).log_expect("MavMessage should be serializable"); + Ok(fields .into_iter() - .map(|field| { + .flat_map(|field| { let field = field.as_ref(); - let value = value.get(field).unwrap(); - serde_json::from_value::<T>(value.clone()).unwrap_or_default() + let value = value + .get(field) + .ok_or(MavlinkError::UnknownField(field.to_string()))?; + serde_json::from_value::<T>(value.clone()).map_err(MavlinkError::from) }) - .collect() + .collect()) } /// Read a stream of bytes and return an iterator of MavLink messages diff --git a/src/mavlink/error.rs b/src/mavlink/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..23975eb59b7384774098dacb2e31f5c5b7f1c878 --- /dev/null +++ b/src/mavlink/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +pub type Result<T> = std::result::Result<T, MavlinkError>; + +#[derive(Debug, Error)] +pub enum MavlinkError { + #[error("Error parsing field: {0}")] + UnknownField(String), + #[error("Error parsing message: {0}")] + ParseError(#[from] serde_json::Error), +} diff --git a/src/mavlink/message_broker.rs b/src/mavlink/message_broker.rs index a04d075ad3f5d8420e64f0a450d997b4e285e838..56f6060ccbc6bcbddf595fb06c3e9278b5b53497 100644 --- a/src/mavlink/message_broker.rs +++ b/src/mavlink/message_broker.rs @@ -18,11 +18,11 @@ use anyhow::{Context, Result}; use egui::{ahash::HashMapExt, IdMap}; use ring_channel::{ring_channel, RingReceiver, RingSender}; use tokio::{net::UdpSocket, task::JoinHandle}; -use tracing::debug; +use tracing::{debug, trace}; use crate::mavlink::byte_parser; -use super::{Message, TimedMessage}; +use super::{MavlinkResult, Message, TimedMessage}; /// Maximum size of the UDP buffer const UDP_BUFFER_SIZE: usize = 65527; @@ -42,10 +42,10 @@ pub trait MessageView { /// Populates the view with the initial messages. This method is called when /// the cache is invalid and the view needs to be populated from the stored /// map of messages - fn populate_view(&mut self, msg_slice: &[TimedMessage]); + fn populate_view(&mut self, msg_slice: &[TimedMessage]) -> MavlinkResult<()>; /// Updates the view with new messages. This method is called when the cache /// is valid, hence the view only needs to be updated with the new messages - fn update_view(&mut self, msg_slice: &[TimedMessage]); + fn update_view(&mut self, msg_slice: &[TimedMessage]) -> MavlinkResult<()>; } /// Responsible for storing & dispatching the Mavlink message received. @@ -90,13 +90,14 @@ impl MessageBroker { /// Refreshes the view given as argument. It handles automatically the cache /// validity based on `is_valid` method of the view. - pub fn refresh_view<V: MessageView>(&mut self, view: &mut V) { + pub fn refresh_view<V: MessageView>(&mut self, view: &mut V) -> MavlinkResult<()> { self.process_incoming_msgs(); if !view.is_valid() || !self.update_queues.contains_key(view.widget_id()) { - self.init_view(view); + self.init_view(view)?; } else { - self.update_view(view); + self.update_view(view)?; } + Ok(()) } /// Stop the listener task from listening to incoming messages, if it is @@ -123,6 +124,7 @@ impl MessageBroker { let mut buf = Box::new([0; UDP_BUFFER_SIZE]); let running_flag = self.running_flag.clone(); + debug!("Spawning listener task at {}", bind_address); let handle = tokio::spawn(async move { let socket = UdpSocket::bind(bind_address) .await @@ -135,6 +137,7 @@ impl MessageBroker { .await .context("Failed to receive message")?; for (_, mav_message) in byte_parser(&buf[..len]) { + debug!("Received message: {:?}", mav_message); tx.send(TimedMessage::just_received(mav_message)) .context("Failed to send message")?; ctx.request_repaint(); @@ -153,27 +156,35 @@ impl MessageBroker { } /// Init a view in case of cache invalidation or first time initialization. - fn init_view<V: MessageView>(&mut self, view: &mut V) { + fn init_view<V: MessageView>(&mut self, view: &mut V) -> MavlinkResult<()> { + trace!("initializing view: {:?}", view.widget_id()); if let Some(messages) = self.messages.get(&view.id_of_interest()) { - view.populate_view(messages); + view.populate_view(messages)?; } self.update_queues .insert(*view.widget_id(), (view.id_of_interest(), VecDeque::new())); + Ok(()) } /// Update a view with new messages, used when the cache is valid. - fn update_view<V: MessageView>(&mut self, view: &mut V) { + fn update_view<V: MessageView>(&mut self, view: &mut V) -> MavlinkResult<()> { + trace!("updating view: {:?}", view.widget_id()); if let Some((_, queue)) = self.update_queues.get_mut(view.widget_id()) { while let Some(msg) = queue.pop_front() { - view.update_view(&[msg]); + view.update_view(&[msg])?; } } + Ok(()) } /// Process the incoming messages from the Mavlink listener, storing them in /// the messages map and updating the update queues. fn process_incoming_msgs(&mut self) { while let Ok(message) = self.rx.try_recv() { + debug!( + "processing received message: {:?}", + message.message.message_name() + ); // first update the update queues for (_, (id, queue)) in self.update_queues.iter_mut() { if *id == message.message.message_id() { diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs index 7fa531ee79242ff8337a80e5afb3004b2592fc38..7f225a8ffd7c10d962fbad6d94b9c4279dc54b9e 100644 --- a/src/mavlink/reflection.rs +++ b/src/mavlink/reflection.rs @@ -8,6 +8,8 @@ use std::collections::HashMap; use mavlink_bindgen::parser::{MavProfile, MavType}; +use crate::error::ErrInstrument; + use super::MAVLINK_PROFILE_SERIALIZED; /// Reflection context for MAVLink messages. @@ -22,7 +24,7 @@ impl ReflectionContext { /// Create a new reflection context. pub fn new() -> Self { let profile: MavProfile = serde_json::from_str(MAVLINK_PROFILE_SERIALIZED) - .expect("Failed to deserialize MavProfile"); + .log_expect("Failed to deserialize MavProfile"); let id_name_map = profile .messages .iter() diff --git a/src/ui.rs b/src/ui.rs index 46926d541a2c15600212afe5fcb2dcb86bda982a..f276231768c6a750ac31d90e1a79a1eb5f42b1b0 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,6 +1,6 @@ mod composable_view; -mod layout_manager; mod panes; +mod persistency; mod shortcuts; mod utils; diff --git a/src/ui/composable_view.rs b/src/ui/composable_view.rs index 7b71f6b6d06d2409b82501aa2892b9b8bcca8a34..174b62de2fb010362c2b9226864649d2e7361955 100644 --- a/src/ui/composable_view.rs +++ b/src/ui/composable_view.rs @@ -1,8 +1,8 @@ -use crate::{mavlink, MSG_MANAGER}; +use crate::{error::ErrInstrument, mavlink, ui::panes::PaneKind, MSG_MANAGER}; use super::{ - layout_manager::LayoutManager, - panes::{Pane, PaneBehavior, PaneKind}, + panes::{Pane, PaneBehavior}, + persistency::{LayoutManager, LayoutManagerWindow}, shortcuts, utils::maximized_pane_ui, }; @@ -14,16 +14,19 @@ use std::{ use egui::{Key, Modifiers}; use egui_tiles::{Behavior, Container, Linear, LinearDir, Tile, TileId, Tiles, Tree}; use serde::{Deserialize, Serialize}; +use tracing::{debug, error, trace, warn}; #[derive(Default)] pub struct ComposableView { /// Persistent state of the app - pub state: ComposableViewState, - - pub layout_manager: LayoutManager, + state: ComposableViewState, + layout_manager: LayoutManager, behavior: ComposableBehavior, maximized_pane: Option<TileId>, + + // == Windows == sources_window: SourceWindow, + layout_manager_window: LayoutManagerWindow, } // An app must implement the `App` trait to define how the ui is built @@ -38,10 +41,12 @@ impl eframe::App for ComposableView { .iter() .find(|(_, tile)| matches!(tile, Tile::Pane(pane) if pane.contains_pointer())) .map(|(id, _)| *id); + trace!("Hovered pane: {:?}", hovered_pane); // Capture any pane action generated by pane children let pane_action = self.behavior.action.take(); let mut pane_action = pane_action.zip(hovered_pane); + trace!("Pane action: {:?}", pane_action); // Capture any pane action generated by keyboard shortcuts if let Some(hovered_pane) = hovered_pane { @@ -61,7 +66,11 @@ impl eframe::App for ComposableView { match action { PaneAction::SplitH => { if self.maximized_pane.is_none() { - let hovered_tile_pane = panes_tree.tiles.remove(hovered_tile).unwrap(); + debug!("Called SplitH on tile {:?}", hovered_tile); + let hovered_tile_pane = panes_tree + .tiles + .remove(hovered_tile) + .log_expect("Hovered tile not found"); let left_pane = panes_tree.tiles.insert_new(hovered_tile_pane); let right_pane = panes_tree.tiles.insert_pane(Pane::default()); panes_tree.tiles.insert( @@ -76,7 +85,11 @@ impl eframe::App for ComposableView { } PaneAction::SplitV => { if self.maximized_pane.is_none() { - let hovered_tile_pane = panes_tree.tiles.remove(hovered_tile).unwrap(); + debug!("Called SplitV on tile {:?}", hovered_tile); + let hovered_tile_pane = panes_tree + .tiles + .remove(hovered_tile) + .log_expect("Hovered tile not found"); let replaced = panes_tree.tiles.insert_new(hovered_tile_pane); let lower_pane = panes_tree.tiles.insert_pane(Pane::default()); panes_tree.tiles.insert( @@ -90,12 +103,17 @@ impl eframe::App for ComposableView { } } PaneAction::Close => { + debug!("Called Close on tile {:?}", hovered_tile); // Ignore if the root pane is the only one if panes_tree.tiles.len() != 1 && self.maximized_pane.is_none() { panes_tree.remove_recursively(hovered_tile); } } PaneAction::Replace(new_pane) => { + debug!( + "Called Replace on tile {:?} with pane {:?}", + hovered_tile, new_pane + ); panes_tree.tiles.insert(hovered_tile, Tile::Pane(*new_pane)); } PaneAction::Maximize => { @@ -140,7 +158,8 @@ impl eframe::App for ComposableView { self.sources_window.visible = !self.sources_window.visible; } if ui.button("Layout Manager").clicked() { - self.layout_manager.toggle_open_state(); + self.layout_manager_window + .toggle_open_state(&self.layout_manager); } // If a pane is maximized show a visual clue @@ -163,23 +182,31 @@ impl eframe::App for ComposableView { } }); - LayoutManager::show(self, ctx); + self.layout_manager_window + .show(ctx, &mut self.layout_manager, &mut self.state); } fn save(&mut self, storage: &mut dyn eframe::Storage) { - self.layout_manager.save_displayed(storage); + self.layout_manager.save_current_layout(storage); } } impl ComposableView { pub fn new(app_name: &str, storage: &dyn eframe::Storage) -> Self { let layout_manager = LayoutManager::new(app_name, storage); - let mut composable_view = Self { + let mut s = Self { layout_manager, ..Self::default() }; - LayoutManager::try_display_selected_layout(&mut composable_view); - composable_view + // Load the selected layout if valid and existing + if let Some(layout) = s.layout_manager.current_layout().cloned() { + s.layout_manager + .load_layout(layout, &mut s.state) + .unwrap_or_else(|e| { + error!("Error loading layout: {}", e); + }); + } + s } } @@ -199,47 +226,29 @@ impl Default for ComposableViewState { } impl ComposableViewState { - pub fn from_file(path: &PathBuf) -> Option<Self> { - match fs::read_to_string(path) { - Ok(json) => match serde_json::from_str::<ComposableViewState>(&json) { - Ok(layout) => Some(layout), - Err(e) => { - eprintln!("Error deserializing layout: {}", e); - None - } - }, - Err(e) => { - eprintln!("Error reading file: {}", e); - None - } - } + pub fn from_file(path: &PathBuf) -> anyhow::Result<Self> { + fs::read_to_string(path) + .and_then(|json| serde_json::from_str::<ComposableViewState>(&json).map_err(Into::into)) + .map_err(|e| anyhow::anyhow!("Error deserializing layout: {}", e)) } - pub fn to_file(&self, path: &Path) { + pub fn to_file(&self, path: &Path) -> anyhow::Result<()> { // Check if the parent path exists, if not create it if let Some(parent) = path.parent() { if !parent.exists() { - match fs::create_dir_all(parent) { - Ok(_) => { - println!("Created directory {:?}", parent); - } - Err(e) => { - eprintln!("Error creating directory: {}", e); - return; - } - } + fs::create_dir_all(parent) + .map_err(|e| anyhow::anyhow!("Error creating directory: {}", e))?; + debug!("Created directory {:?}", parent); } } - match serde_json::to_string_pretty(self) { - Ok(serialized_layout) => { - println!("Saving layout into {:?}", path); - fs::write(path, serialized_layout).unwrap(); - } - Err(e) => { - eprintln!("Error serializing layout: {}", e); - } - } + let serialized_layout = serde_json::to_string_pretty(self) + .map_err(|e| anyhow::anyhow!("Error serializing layout: {}", e))?; + debug!("Serialized layout: {}", serialized_layout); + fs::write(path, serialized_layout) + .map_err(|e| anyhow::anyhow!("Error writing layout: {}", e))?; + + Ok(()) } } diff --git a/src/ui/layout_manager.rs b/src/ui/layout_manager.rs deleted file mode 100644 index 526505f8b600e7607b3b20d4627365746d4727b0..0000000000000000000000000000000000000000 --- a/src/ui/layout_manager.rs +++ /dev/null @@ -1,307 +0,0 @@ -use std::{collections::BTreeMap, fs, path::PathBuf, str::FromStr}; - -use egui::{ - Button, Color32, Context, InnerResponse, RichText, Separator, Stroke, TextEdit, Ui, Vec2, - Widget, -}; -use egui_extras::{Column, Size, StripBuilder, TableBuilder}; -use egui_file::FileDialog; - -use super::{composable_view::ComposableViewState, ComposableView}; - -static LAYOUTS_DIR: &str = "layouts"; -static SELECTED_LAYOUT_KEY: &str = "selected_layout"; - -#[derive(Default)] -pub struct LayoutManager { - open: bool, - - /// Currently dislayed layout in the ui - displayed: Option<PathBuf>, - - /// Currently selected layout in the list, gets reset to the displayed layout when the dialog is opened - selection: Option<PathBuf>, - - text_input: String, - file_dialog: Option<FileDialog>, - layouts: BTreeMap<PathBuf, ComposableViewState>, - layouts_path: PathBuf, -} - -impl LayoutManager { - /// Chooses the layouts path and gets the previously selected layout from storage - pub fn new(app_name: &str, storage: &dyn eframe::Storage) -> Self { - let mut layout_manager = Self { - layouts_path: eframe::storage_dir(app_name).unwrap().join(LAYOUTS_DIR), - selection: storage - .get_string(SELECTED_LAYOUT_KEY) - .map(|path| PathBuf::from_str(path.as_str()).unwrap()), - ..Self::default() - }; - layout_manager.reload_layouts(); - layout_manager - } - - /// Saves in permanent storage the file name of the currently displayed layout - pub fn save_displayed(&self, storage: &mut dyn eframe::Storage) { - if let Some(displayed) = self.displayed.as_ref().map(|s| s.to_str()).flatten() { - storage.set_string(SELECTED_LAYOUT_KEY, displayed.to_string()); - println!("Layout \"{}\" will be displayed next time", displayed) - } - } - - /// Scans the layout directory and reloads the layouts - pub fn reload_layouts(&mut self) { - if let Ok(files) = self.layouts_path.read_dir() { - self.layouts = files - .flat_map(|x| x) - .map(|path| path.path()) - .map(|path| { - if let Some(layout) = ComposableViewState::from_file(&path) { - let path: PathBuf = path.file_stem().unwrap().into(); - Some((path, layout)) - } else { - None - } - }) - .flatten() - .collect(); - } - } - - pub fn get_selected(&self) -> Option<&ComposableViewState> { - self.selection - .as_ref() - .map(|selection| self.layouts.get(selection)) - .flatten() - } - - pub fn delete(&mut self, key: &PathBuf) { - if self.layouts.contains_key(key) { - let _ = fs::remove_file(self.layouts_path.join(key).with_extension("json")); - } - } - - pub fn try_display_selected_layout(cv: &mut ComposableView) { - if let Some(selection) = cv.layout_manager.selection.as_ref() { - if let Some(selected_layout) = cv.layout_manager.layouts.get(selection) { - cv.state = selected_layout.clone(); - cv.layout_manager.displayed = Some(selection.clone()); - } - } - } - - pub fn save_current_layout(cv: &mut ComposableView, name: &String) { - let layouts_path = &cv.layout_manager.layouts_path; - let path = layouts_path.join(name).with_extension("json"); - cv.state.to_file(&path); - cv.layout_manager.selection.replace(name.into()); - cv.layout_manager.displayed.replace(name.into()); - } - - pub fn toggle_open_state(&mut self) { - self.open = !self.open; - - if self.open { - // When opening, we set the selection to the current layout - self.selection = self.displayed.clone(); - } else { - // When closing, we delete also the file dialog - self.file_dialog.take(); - } - } - - pub fn show(cv: &mut ComposableView, ctx: &Context) { - let mut window_visible = cv.layout_manager.open; - egui::Window::new("Layouts Manager") - .collapsible(false) - .open(&mut window_visible) - .show(ctx, |ui| { - // Make sure to reload the layots, this ways the user sees always - // the current content of the layouts folder - cv.layout_manager.reload_layouts(); - - let changed = match cv.layout_manager.get_selected() { - Some(selected_layout) => *selected_layout != cv.state, - None => true, - }; - - // Layouts table - StripBuilder::new(ui) - .size(Size::remainder().at_least(100.0)) - .size(Size::exact(7.0)) - .size(Size::exact(40.0)) - .vertical(|mut strip| { - strip.cell(|ui| LayoutManager::show_layouts_table(ui, cv, changed)); - strip.cell(|ui| { - ui.add(Separator::default().spacing(7.0)); - }); - strip.strip(|builder| { - LayoutManager::show_action_buttons(builder, cv, changed) - }); - }); - }); - cv.layout_manager.open = window_visible; - } - - fn show_layouts_table(ui: &mut Ui, cv: &mut ComposableView, changed: bool) { - let available_height = ui.available_height(); - TableBuilder::new(ui) - .column(Column::remainder()) - .column(Column::auto()) - .column(Column::auto()) - .min_scrolled_height(0.0) - .max_scroll_height(available_height) - .body(|mut body| { - let mut to_select: Option<PathBuf> = None; - let mut to_open: Option<PathBuf> = None; - let mut to_delete: Option<PathBuf> = None; - - for key in cv.layout_manager.layouts.keys() { - let name = key.to_str().unwrap(); - let is_selected = cv - .layout_manager - .selection - .as_ref() - .map_or_else(|| false, |selected_key| selected_key == key); - - let name_button = if is_selected && changed { - Button::new(RichText::new(name).color(Color32::BLACK)) - .stroke(Stroke::new(1.0, Color32::BROWN)) - .fill(Color32::YELLOW) - } else if is_selected && !changed { - Button::new(RichText::new(name).color(Color32::BLACK)) - .stroke(Stroke::new(1.0, Color32::GREEN)) - .fill(Color32::LIGHT_GREEN) - } else { - Button::new(name).fill(Color32::TRANSPARENT) - }; - let open_button = Button::new("↗"); - let delete_button = Button::new("🗑"); - - body.row(20.0, |mut row| { - row.col(|ui| { - let name_button_resp = name_button.ui(ui); - if name_button_resp.clicked() { - to_select = Some(key.clone()); - } - if name_button_resp.double_clicked() { - to_open = Some(key.clone()); - } - }); - row.col(|ui| { - if open_button.ui(ui).clicked() { - to_open = Some(key.clone()); - } - }); - row.col(|ui| { - if delete_button.ui(ui).clicked() { - to_delete = Some(key.clone()); - } - }); - }); - } - - if let Some(to_select) = to_select { - cv.layout_manager.selection.replace(to_select); - } - if let Some(to_open) = to_open { - cv.layout_manager.selection.replace(to_open); - LayoutManager::try_display_selected_layout(cv); - } - if let Some(to_delete) = to_delete { - cv.layout_manager.delete(&to_delete); - } - }); - } - - fn show_action_buttons(builder: StripBuilder, cv: &mut ComposableView, changed: bool) { - builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { - // Load empty and import buttons - strip.cell(|ui| { - // Load empty button - let open_empty_resp = ui.add_sized( - Vec2::new(ui.available_width(), 0.0), - Button::new("Load empty"), - ); - if open_empty_resp.clicked() { - cv.state = ComposableViewState::default(); - cv.layout_manager.selection.take(); - } - - // Import button - let import_layout_resp = - ui.add_sized(Vec2::new(ui.available_width(), 0.0), Button::new("Import")); - if import_layout_resp.clicked() { - let mut file_dialog = FileDialog::open_file(None); - file_dialog.open(); - cv.layout_manager.file_dialog = Some(file_dialog); - } - if let Some(file_dialog) = &mut cv.layout_manager.file_dialog { - if file_dialog.show(ui.ctx()).selected() { - if let Some(file) = file_dialog.path() { - println!("Selected layout to import: {:?}", file); - - let file_name = file.file_name().unwrap(); - let destination = cv.layout_manager.layouts_path.join(file_name); - - // First check if the layouts folder exists - if !cv.layout_manager.layouts_path.exists() { - match fs::create_dir_all(&cv.layout_manager.layouts_path) { - Ok(_) => println!("Created layouts folder"), - Err(e) => { - println!("Error creating layouts folder: {:?}", e) - } - } - } - - match fs::copy(file, destination.clone()) { - Ok(_) => { - println!( - "Layout imported in {}", - destination.to_str().unwrap() - ); - cv.layout_manager.selection.replace(file_name.into()); - cv.layout_manager.reload_layouts(); - LayoutManager::try_display_selected_layout(cv); - } - Err(e) => println!("Error importing layout: {:?}", e), - } - } - } - } - }); - // Layout save ui - strip.cell(|ui| { - let InnerResponse { inner: to_save, .. } = ui.add_enabled_ui(changed, |ui| { - // Text edit - let text_edit_resp = ui.add_sized( - Vec2::new(ui.available_width(), 0.0), - TextEdit::singleline(&mut cv.layout_manager.text_input), - ); - - // Save button - let InnerResponse { - inner: save_button_resp, - .. - } = ui.add_enabled_ui(!cv.layout_manager.text_input.is_empty(), |ui| { - ui.add_sized( - Vec2::new(ui.available_width(), 0.0), - Button::new("Save layout"), - ) - }); - - let to_save = text_edit_resp.lost_focus() - && ui.input(|i| i.key_pressed(egui::Key::Enter)); - let to_save = to_save || save_button_resp.clicked(); - to_save - }); - - if to_save { - let name = cv.layout_manager.text_input.clone(); - LayoutManager::save_current_layout(cv, &name); - } - }); - }); - } -} diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs index 16b90332e33fae3b1ca51f70c133d680bd660f7e..d17d257455a52e70db4765e013126f500f8cba10 100644 --- a/src/ui/panes/plot.rs +++ b/src/ui/panes/plot.rs @@ -6,8 +6,10 @@ use serde::{Deserialize, Serialize}; use source_window::{sources_window, SourceSettings}; use crate::{ + error::ErrInstrument, mavlink::{ - extract_from_message, MessageData, MessageView, TimedMessage, ROCKET_FLIGHT_TM_DATA, + extract_from_message, MavlinkResult, MessageData, MessageView, TimedMessage, + ROCKET_FLIGHT_TM_DATA, }, ui::composable_view::PaneResponse, MSG_MANAGER, @@ -76,7 +78,12 @@ impl PaneBehavior for Plot2DPane { let mut plot_lines = Vec::new(); if self.plot_active { - MSG_MANAGER.get().unwrap().lock().refresh_view(view); + MSG_MANAGER + .get() + .unwrap() + .lock() + .refresh_view(view) + .log_expect("MessageView may be invalid"); let acc_points = &view.points; let field_x = &view.settings.x_field; @@ -163,27 +170,29 @@ impl MessageView for PlotMessageView { self.cache_valid } - fn populate_view(&mut self, msg_slice: &[TimedMessage]) { + fn populate_view(&mut self, msg_slice: &[TimedMessage]) -> MavlinkResult<()> { self.points.clear(); let MsgSources { x_field, y_fields, .. } = &self.settings; for msg in msg_slice { - let x: f64 = extract_from_message(&msg.message, [x_field])[0]; - let ys: Vec<f64> = extract_from_message(&msg.message, y_fields); + let x: f64 = extract_from_message(&msg.message, [x_field])?[0]; + let ys: Vec<f64> = extract_from_message(&msg.message, y_fields)?; self.points.push((x, ys)); } + Ok(()) } - fn update_view(&mut self, msg_slice: &[TimedMessage]) { + fn update_view(&mut self, msg_slice: &[TimedMessage]) -> MavlinkResult<()> { let MsgSources { x_field, y_fields, .. } = &self.settings; for msg in msg_slice { - let x: f64 = extract_from_message(&msg.message, [x_field])[0]; - let ys: Vec<f64> = extract_from_message(&msg.message, y_fields); + let x: f64 = extract_from_message(&msg.message, [x_field])?[0]; + let ys: Vec<f64> = extract_from_message(&msg.message, y_fields)?; self.points.push((x, ys)); } + Ok(()) } } diff --git a/src/ui/persistency.rs b/src/ui/persistency.rs new file mode 100644 index 0000000000000000000000000000000000000000..7998384cd60b8800f37bfd19f3d862e9257f16e4 --- /dev/null +++ b/src/ui/persistency.rs @@ -0,0 +1,5 @@ +mod layout_manager; +mod layout_manager_window; + +pub use layout_manager::LayoutManager; +pub use layout_manager_window::LayoutManagerWindow; diff --git a/src/ui/persistency/layout_manager.rs b/src/ui/persistency/layout_manager.rs new file mode 100644 index 0000000000000000000000000000000000000000..173fea11360bf4f0049957d210992ec38417c5b2 --- /dev/null +++ b/src/ui/persistency/layout_manager.rs @@ -0,0 +1,133 @@ +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, + str::FromStr, +}; + +use tracing::{info, trace, warn}; + +use crate::error::ErrInstrument; + +use super::super::composable_view::ComposableViewState; + +static LAYOUTS_DIR: &str = "layouts"; +static SELECTED_LAYOUT_KEY: &str = "selected_layout"; + +#[derive(Default)] +pub struct LayoutManager { + layouts: BTreeMap<PathBuf, ComposableViewState>, + layouts_path: PathBuf, + current_layout: Option<PathBuf>, +} + +impl LayoutManager { + /// Chooses the layouts path and gets the previously selected layout from storage + pub fn new(app_name: &str, storage: &dyn eframe::Storage) -> Self { + let mut layout_manager = Self { + layouts_path: eframe::storage_dir(app_name) + .log_expect("Unable to get storage dir") + .join(LAYOUTS_DIR), + current_layout: storage + .get_string(SELECTED_LAYOUT_KEY) + .map(|path| PathBuf::from_str(&path).log_expect("Path is not valid")), + ..Self::default() + }; + layout_manager.reload_layouts(); + layout_manager + } + + pub fn current_layout(&self) -> Option<&PathBuf> { + self.current_layout.as_ref() + } + + pub fn layouts_path(&self) -> &PathBuf { + &self.layouts_path + } + + pub fn layouts(&self) -> &BTreeMap<PathBuf, ComposableViewState> { + &self.layouts + } + + /// Saves in permanent storage the file name of the currently displayed layout + pub fn save_current_layout(&self, storage: &mut dyn eframe::Storage) { + if let Some(current_layout) = self.current_layout.as_ref().and_then(|s| s.to_str()) { + storage.set_string(SELECTED_LAYOUT_KEY, current_layout.to_string()); + trace!("Current layout {:?} saved in storage", current_layout); + } + } + + /// Scans the layout directory and reloads the layouts + pub fn reload_layouts(&mut self) { + if let Ok(files) = self.layouts_path.read_dir() { + trace!("Reloading layouts from {:?}", self.layouts_path); + self.layouts = files + .flatten() + .map(|path| path.path()) + .flat_map(|path| match ComposableViewState::from_file(&path) { + Ok(layout) => { + let path: PathBuf = path + .file_stem() + .log_expect("Unable to get file stem") + .into(); + Some((path, layout)) + } + Err(e) => { + warn!("Error loading layout at {:?}: {:?}", path, e); + None + } + }) + .collect(); + } + } + + pub fn get_layout(&self, name: impl Into<PathBuf>) -> Option<&ComposableViewState> { + self.layouts.get(&name.into()) + } + + pub fn save_layout(&mut self, name: &str, state: &ComposableViewState) -> anyhow::Result<()> { + let path = self.layouts_path.join(name).with_extension("json"); + state.to_file(&path)?; + self.reload_layouts(); + Ok(()) + } + + pub fn load_layout( + &mut self, + path: impl AsRef<Path>, + state: &mut ComposableViewState, + ) -> anyhow::Result<()> { + let layout = self + .layouts + .get(path.as_ref()) + .ok_or(anyhow::anyhow!("Layout not found"))?; + *state = layout.clone(); + self.current_layout = Some(path.as_ref().into()); + Ok(()) + } + + pub fn delete(&mut self, key: &PathBuf) -> anyhow::Result<()> { + if self.layouts.contains_key(key) { + info!("Deleting layout {:?}", key); + fs::remove_file(self.layouts_path.join(key).with_extension("json"))?; + } + Ok(()) + } + + // pub fn display_selected_layout(&mut self, state: &mut ComposableViewState) { + // if let Some(selection) = self.selection.as_ref() { + // if let Some(selected_layout) = self.layouts.get(selection) { + // *state = selected_layout.clone(); + // self.displayed = Some(selection.clone()); + // } + // } + // } + + // pub fn save_current_layout(&mut self, state: &ComposableViewState, name: &String) { + // let layouts_path = &self.layouts_path; + // let path = layouts_path.join(name).with_extension("json"); + // state.to_file(&path); + // self.selection.replace(name.into()); + // self.displayed.replace(name.into()); + // } +} diff --git a/src/ui/persistency/layout_manager_window.rs b/src/ui/persistency/layout_manager_window.rs new file mode 100644 index 0000000000000000000000000000000000000000..6ecd567c8a34c89d561b7638eaaaddb70b1546ac --- /dev/null +++ b/src/ui/persistency/layout_manager_window.rs @@ -0,0 +1,260 @@ +use std::{fs, path::PathBuf}; + +use egui::{ + Button, Color32, Context, InnerResponse, RichText, Separator, Stroke, TextEdit, Ui, Vec2, + Widget, +}; +use egui_extras::{Column, Size, StripBuilder, TableBuilder}; +use egui_file::FileDialog; + +use crate::ui::composable_view::ComposableViewState; + +use super::LayoutManager; + +#[derive(Default)] +pub struct LayoutManagerWindow { + visible: bool, + file_dialog: Option<FileDialog>, + text_input: String, + /// Currently selected layout in the list, gets reset to the displayed layout when the dialog is opened + pub selection: Option<PathBuf>, +} + +impl LayoutManagerWindow { + pub fn toggle_open_state(&mut self, layout_manager: &LayoutManager) { + self.visible = !self.visible; + + if self.visible { + // When opening, we set the selection to the current layout + self.selection = layout_manager.current_layout().cloned(); + } else { + // When closing, we delete also the file dialog + self.file_dialog.take(); + } + } + + pub fn show( + &mut self, + ctx: &Context, + layout_manager: &mut LayoutManager, + state: &mut ComposableViewState, + ) { + let LayoutManagerWindow { + visible: window_visible, + file_dialog, + text_input, + selection, + } = self; + egui::Window::new("Layouts Manager") + .collapsible(false) + .open(window_visible) + .show(ctx, |ui| { + // Make sure to reload the layots, this ways the user sees always + // the current content of the layouts folder + layout_manager.reload_layouts(); + + let changed = selection + .as_ref() + .and_then(|path| layout_manager.get_layout(path)) + .map(|layout| layout != state) + .unwrap_or(true); + + // Layouts table + StripBuilder::new(ui) + .size(Size::remainder().at_least(100.0)) + .size(Size::exact(7.0)) + .size(Size::exact(40.0)) + .vertical(|mut strip| { + strip.cell(|ui| { + show_layouts_table(ui, layout_manager, state, selection, changed) + }); + strip.cell(|ui| { + ui.add(Separator::default().spacing(7.0)); + }); + strip.strip(|builder| { + show_action_buttons( + builder, + layout_manager, + state, + file_dialog, + text_input, + selection, + changed, + ) + }); + }); + }); + } +} + +fn show_layouts_table( + ui: &mut Ui, + layout_manager: &mut LayoutManager, + state: &mut ComposableViewState, + selection: &mut Option<PathBuf>, + changed: bool, +) { + let available_height = ui.available_height(); + TableBuilder::new(ui) + .column(Column::remainder()) + .column(Column::auto()) + .column(Column::auto()) + .min_scrolled_height(0.0) + .max_scroll_height(available_height) + .body(|mut body| { + let mut to_select: Option<PathBuf> = None; + let mut to_open: Option<PathBuf> = None; + let mut to_delete: Option<PathBuf> = None; + + for key in layout_manager.layouts().keys() { + let name = key.to_str().unwrap(); + let is_selected = selection + .as_ref() + .map_or_else(|| false, |selected_key| selected_key == key); + + let name_button = if is_selected && changed { + Button::new(RichText::new(name).color(Color32::BLACK)) + .stroke(Stroke::new(1.0, Color32::BROWN)) + .fill(Color32::YELLOW) + } else if is_selected && !changed { + Button::new(RichText::new(name).color(Color32::BLACK)) + .stroke(Stroke::new(1.0, Color32::GREEN)) + .fill(Color32::LIGHT_GREEN) + } else { + Button::new(name).fill(Color32::TRANSPARENT) + }; + let open_button = Button::new("↗"); + let delete_button = Button::new("🗑"); + + body.row(20.0, |mut row| { + row.col(|ui| { + let name_button_resp = name_button.ui(ui); + if name_button_resp.clicked() { + to_select = Some(key.clone()); + } + if name_button_resp.double_clicked() { + to_open = Some(key.clone()); + } + }); + row.col(|ui| { + if open_button.ui(ui).clicked() { + to_open = Some(key.clone()); + } + }); + row.col(|ui| { + if delete_button.ui(ui).clicked() { + to_delete = Some(key.clone()); + } + }); + }); + } + + if let Some(to_select) = to_select { + selection.replace(to_select); + } + if let Some(to_open) = to_open { + layout_manager.load_layout(&to_open, state); + selection.replace(to_open.clone()); + } + if let Some(to_delete) = to_delete { + layout_manager.delete(&to_delete); + } + }); +} + +fn show_action_buttons( + builder: StripBuilder, + layout_manager: &mut LayoutManager, + state: &mut ComposableViewState, + file_dialog: &mut Option<FileDialog>, + text_input: &mut String, + selection: &mut Option<PathBuf>, + changed: bool, +) { + builder.sizes(Size::remainder(), 2).horizontal(|mut strip| { + // Load empty and import buttons + strip.cell(|ui| { + // Load empty button + let open_empty_resp = ui.add_sized( + Vec2::new(ui.available_width(), 0.0), + Button::new("Load empty"), + ); + if open_empty_resp.clicked() { + *state = ComposableViewState::default(); + selection.take(); + } + + // Import button + let import_layout_resp = + ui.add_sized(Vec2::new(ui.available_width(), 0.0), Button::new("Import")); + if import_layout_resp.clicked() { + let mut file_dialog_inner = FileDialog::open_file(None); + file_dialog_inner.open(); + *file_dialog = Some(file_dialog_inner); + } + if let Some(file_dialog) = file_dialog { + if file_dialog.show(ui.ctx()).selected() { + if let Some(file) = file_dialog.path() { + println!("Selected layout to import: {:?}", file); + + let file_name: &std::ffi::OsStr = file.file_name().unwrap(); + let layout_path = layout_manager.layouts_path(); + let destination = layout_path.join(file_name); + + // First check if the layouts folder exists + if !layout_path.exists() { + match fs::create_dir_all(&layout_manager.layouts_path()) { + Ok(_) => println!("Created layouts folder"), + Err(e) => { + println!("Error creating layouts folder: {:?}", e) + } + } + } + + match fs::copy(file, destination.clone()) { + Ok(_) => { + println!("Layout imported in {}", destination.to_str().unwrap()); + selection.replace(file_name.into()); + layout_manager.reload_layouts(); + layout_manager.load_layout(&file_name, state); + } + Err(e) => println!("Error importing layout: {:?}", e), + } + } + } + } + }); + // Layout save ui + strip.cell(|ui| { + let InnerResponse { inner: to_save, .. } = ui.add_enabled_ui(changed, |ui| { + // Text edit + let text_edit_resp = ui.add_sized( + Vec2::new(ui.available_width(), 0.0), + TextEdit::singleline(text_input), + ); + + // Save button + let InnerResponse { + inner: save_button_resp, + .. + } = ui.add_enabled_ui(!text_input.is_empty(), |ui| { + ui.add_sized( + Vec2::new(ui.available_width(), 0.0), + Button::new("Save layout"), + ) + }); + + let to_save = + text_edit_resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + let to_save = to_save || save_button_resp.clicked(); + to_save + }); + + if to_save { + let name = text_input.clone(); + layout_manager.save_layout(&name, &state); + *selection = Some(name.clone().into()); + } + }); + }); +}