diff --git a/Cargo.lock b/Cargo.lock index 14c6a43485ce14feaf75e20ba9bc0e9d0cc45445..cfcee9b60b6a6ca085468b80076080ff18249e31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,9 +62,9 @@ dependencies = [ "accesskit", "accesskit_consumer", "hashbrown", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -204,9 +204,9 @@ dependencies = [ "core-graphics", "image", "log", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "parking_lot", "windows-sys 0.48.0", "x11rb", @@ -344,7 +344,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -373,13 +373,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -517,7 +517,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", ] [[package]] @@ -556,7 +556,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -793,6 +793,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deranged" version = "0.3.11" @@ -837,7 +843,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -907,9 +913,9 @@ dependencies = [ "image", "js-sys", "log", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "parking_lot", "percent-encoding", "profiling", @@ -997,6 +1003,7 @@ dependencies = [ "log", "mime_guess2", "profiling", + "resvg", ] [[package]] @@ -1064,6 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" dependencies = [ "bytemuck", + "mint", "serde", ] @@ -1091,7 +1099,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1103,7 +1111,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1124,7 +1132,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1135,7 +1143,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1231,11 +1239,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -1255,7 +1269,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1342,7 +1356,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1448,6 +1462,16 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +dependencies = [ + "mint", + "serde", +] + [[package]] name = "glow" version = "0.16.0" @@ -1475,9 +1499,9 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "once_cell", "raw-window-handle", "wayland-sys", @@ -1722,7 +1746,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1759,6 +1783,12 @@ dependencies = [ "tiff", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "immutable-chunkmap" version = "2.0.6" @@ -1770,9 +1800,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown", @@ -1876,6 +1906,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1884,9 +1923,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -1937,9 +1976,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "litemap" @@ -2100,6 +2139,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mint" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" + [[package]] name = "mio" version = "1.0.3" @@ -2226,7 +2271,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2256,7 +2301,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2284,6 +2329,15 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" +dependencies = [ + "objc2-encode", +] + [[package]] name = "objc2-app-kit" version = "0.2.2" @@ -2293,10 +2347,10 @@ dependencies = [ "bitflags 2.9.0", "block2", "libc", - "objc2", + "objc2 0.5.2", "objc2-core-data", "objc2-core-image", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-quartz-core", ] @@ -2308,9 +2362,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2320,8 +2374,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2332,8 +2386,8 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2343,8 +2397,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -2355,9 +2409,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-contacts", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2376,7 +2430,17 @@ dependencies = [ "block2", "dispatch", "libc", - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", ] [[package]] @@ -2386,9 +2450,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2399,8 +2463,8 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2411,8 +2475,8 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -2422,8 +2486,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2434,12 +2498,12 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-link-presentation", "objc2-quartz-core", "objc2-symbols", @@ -2454,8 +2518,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2466,9 +2530,9 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.0", "block2", - "objc2", + "objc2 0.5.2", "objc2-core-location", - "objc2-foundation", + "objc2-foundation 0.2.2", ] [[package]] @@ -2482,9 +2546,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "orbclient" @@ -2570,6 +2634,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.10" @@ -2587,7 +2657,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2655,11 +2725,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy 0.8.23", ] [[package]] @@ -2697,7 +2767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2730,9 +2800,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -2803,6 +2873,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2871,6 +2947,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "resvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +dependencies = [ + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring-channel" version = "0.12.0" @@ -2897,6 +2996,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2924,14 +3029,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys 0.9.2", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -2993,7 +3098,9 @@ dependencies = [ "egui_plot", "egui_tiles", "enum_dispatch", + "glam", "mavlink-bindgen", + "mint", "profiling", "rand 0.9.0", "ring-channel", @@ -3009,13 +3116,14 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "tracing-tracy", + "uuid", ] [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -3031,13 +3139,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3060,7 +3168,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3165,6 +3273,21 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "skyward_mavlink" version = "0.1.1" @@ -3292,6 +3415,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "strum" @@ -3312,7 +3438,17 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.99", + "syn 2.0.100", +] + +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher", ] [[package]] @@ -3328,9 +3464,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -3345,20 +3481,19 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "tempfile" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" +checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" dependencies = [ - "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.1", + "rustix 1.0.2", "windows-sys 0.59.0", ] @@ -3406,7 +3541,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3417,7 +3552,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3483,6 +3618,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png", "tiny-skia-path", ] @@ -3509,9 +3645,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "libc", @@ -3569,7 +3705,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -3749,6 +3885,50 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "usvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] + [[package]] name = "utf16_iter" version = "1.0.5" @@ -3761,6 +3941,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.1", + "serde", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3820,7 +4010,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -3855,7 +4045,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4000,18 +4190,17 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" +checksum = "d5df295f8451142f1856b1bd86a606dfe9587d439bc036e319c827700dbd555e" dependencies = [ - "block2", "core-foundation 0.10.0", "home", "jni", "log", "ndk-context", - "objc2", - "objc2-foundation", + "objc2 0.6.0", + "objc2-foundation 0.3.0", "url", "web-sys", ] @@ -4187,7 +4376,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4198,7 +4387,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4457,9 +4646,9 @@ dependencies = [ "libc", "memmap2", "ndk", - "objc2", + "objc2 0.5.2", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", "percent-encoding", @@ -4488,9 +4677,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] @@ -4589,6 +4778,12 @@ version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "yoke" version = "0.7.5" @@ -4609,7 +4804,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -4669,7 +4864,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "zbus-lockstep", "zbus_xml", "zvariant", @@ -4684,7 +4879,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "zvariant_utils", ] @@ -4718,7 +4913,6 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] @@ -4739,7 +4933,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4750,7 +4944,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4770,7 +4964,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -4793,7 +4987,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -4818,7 +5012,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "zvariant_utils", ] @@ -4830,5 +5024,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index 0d667b73bd61dccb69a207164a57c19fe0034891..819e571068083cb0aa8e10b32407d5a394cda88c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ license = "MIT" # ======= GUI & Rendering ======= egui_tiles = "0.12" eframe = { version = "0.31", features = ["persistence"] } -egui = { version = "0.31", features = ["log"] } -egui_extras = "0.31" +egui = { version = "0.31", features = ["log", "mint"] } +egui_extras = { version = "0.31", features = ["svg"] } egui_plot = "0.31" egui_file = "0.22" # =========== Asynchronous =========== @@ -36,6 +36,9 @@ strum_macros = "0.26" anyhow = "1.0" ring-channel = "0.12.0" thiserror = "2.0.7" +uuid = { version = "1.12.1", features = ["serde", "v7"] } +glam = { version = "0.29", features = ["serde", "mint"] } +mint = "0.5.9" [dependencies.skyward_mavlink] git = "https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git" diff --git a/fonts/JetBrainsMono-VariableFont_wght.ttf b/fonts/JetBrainsMono-VariableFont_wght.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1b3d7f27df2ec21ab07ddb081cd9e384c8a5ef04 Binary files /dev/null and b/fonts/JetBrainsMono-VariableFont_wght.ttf differ diff --git a/fonts/NotoSans-VariableFont_wdth,wght.ttf b/fonts/NotoSans-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9530d84d564033577d4e985b703e5df42e1884ca Binary files /dev/null and b/fonts/NotoSans-VariableFont_wdth,wght.ttf differ diff --git a/icons/pid_symbols/dark/arrow.svg b/icons/pid_symbols/dark/arrow.svg new file mode 100644 index 0000000000000000000000000000000000000000..704c660a9825e3d3db3dd85bca2c95e675ea854f --- /dev/null +++ b/icons/pid_symbols/dark/arrow.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="4" + height="4" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="M 0.7,2 l 2.6,-1.5 v 3 z" + style="fill:#ffffff;stroke:#ffffff;stroke-opacity:1;stroke-width:0.2;stroke-dasharray:none;stroke-linejoin:round;fill-opacity:1" /> + <path + id="path2" + d="M 0,2 h 4" + style="stroke:#ffffff;stroke-opacity:1;fill:none;stroke-width:0.2;stroke-dasharray:none" /> +</svg> diff --git a/icons/pid_symbols/dark/burst_disk.svg b/icons/pid_symbols/dark/burst_disk.svg new file mode 100644 index 0000000000000000000000000000000000000000..436826c9945a24a0df16a45f377432cf3ebe56e6 --- /dev/null +++ b/icons/pid_symbols/dark/burst_disk.svg @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="4" + height="6" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="M 0.5,0 V 6" + id="path1" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="m 1.5,0 v 1 a 2,2 0 1 1 0,4 v 1" + id="path2" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="m 0,3 h 0.5" + id="path3" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="m 3.5,3 h 0.5" + id="path4" /> +</svg> diff --git a/icons/pid_symbols/dark/check_valve.svg b/icons/pid_symbols/dark/check_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..abe1c9c9e831df5c07b75356da01c79065b88d1e --- /dev/null +++ b/icons/pid_symbols/dark/check_valve.svg @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="5" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <circle + style="fill:#ffffff;stroke:none" + id="path2" + cx="1" + cy="4.5" + r="0.5" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,2.5 h 1 z" + id="path1-5-2" + style="stroke-width:0.2;stroke-dasharray:none" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,2.5 H 1 Z" + id="path1-5" + style="stroke-width:0.2;stroke-dasharray:none" /> + <path + fill="none" + stroke="#ffffff" + d="m 1,0.5 v 4 l 8,-4 v 4" + id="path1" + style="stroke-width:0.2;stroke-dasharray:none;stroke-linejoin:round" /> +</svg> diff --git a/icons/pid_symbols/dark/flexible_connection.svg b/icons/pid_symbols/dark/flexible_connection.svg new file mode 100644 index 0000000000000000000000000000000000000000..3811057224ac66268aea0b1658ac778491224917 --- /dev/null +++ b/icons/pid_symbols/dark/flexible_connection.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="6" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="M 0,3 H 1" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path2" + d="m 9,3 h 1" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path3" + d="M 1,0 V 6 M 2,6 V 0" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path4" + d="M 8,0 V 6 M 9,0 v 6" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path5" + d="M 2,3 H 3 A 2,2 0 1 1 7,3 H 8" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> +</svg> diff --git a/icons/pid_symbols/dark/manual_valve.svg b/icons/pid_symbols/dark/manual_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..d942a790529c6d391955a5729c635a4c94617ddf --- /dev/null +++ b/icons/pid_symbols/dark/manual_valve.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="5" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,2.5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,2.5 h 1" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 1,0.5 v 4 l 8,-4 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> +</svg> diff --git a/icons/pid_symbols/dark/motor_valve.svg b/icons/pid_symbols/dark/motor_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..94f0dd9648821b42a0727c77ddb885b2a057f84d --- /dev/null +++ b/icons/pid_symbols/dark/motor_valve.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#ffffff" + d="M 1,3 V 7 L 9,3 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="M 5,2.5 V 5" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,5 h 1" + id="path4" + style="stroke-width:0.2" /> + <circle + style="fill:none;stroke:#ffffff;stroke-width:0.2" + id="circle1" + cx="5" + cy="1.5" + r="1" /> + <path + style="fill:#ffffff;stroke:#ffffff;stroke-opacity:1;stroke-width:0.05;stroke-dasharray:none;stroke-linejoin:round" + d="M 4.5,2 L 4.5,1 L 4.7,1 L 5,1.9 L 5.3,1 L 5.5,1 L 5.5,2 L 5.4,2 L 5.4,1.1 L 5.1,2 L 4.9,2 L 4.6,1.1 L 4.6,2 Z" + id="text1" /> +</svg> diff --git a/icons/pid_symbols/dark/motor_valve_green.svg b/icons/pid_symbols/dark/motor_valve_green.svg new file mode 100644 index 0000000000000000000000000000000000000000..c5e62636f99c5e7e9c9f0cb42563f167fc4c7d48 --- /dev/null +++ b/icons/pid_symbols/dark/motor_valve_green.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#ffffff" + d="M 1,3 V 7 L 9,3 v 4 z" + id="path1" + style="fill:#00ff00;stroke-width:0.2;stroke-linejoin:round" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="M 5,2.5 V 5" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,5 h 1" + id="path4" + style="stroke-width:0.2" /> + <circle + style="fill:none;stroke:#ffffff;stroke-width:0.2" + id="circle1" + cx="5" + cy="1.5" + r="1" /> + <path + style="fill:#ffffff;stroke:#ffffff;stroke-opacity:1;stroke-width:0.05;stroke-dasharray:none;stroke-linejoin:round" + d="M 4.5,2 L 4.5,1 L 4.7,1 L 5,1.9 L 5.3,1 L 5.5,1 L 5.5,2 L 5.4,2 L 5.4,1.1 L 5.1,2 L 4.9,2 L 4.6,1.1 L 4.6,2 Z" + id="text1" /> +</svg> diff --git a/icons/pid_symbols/dark/motor_valve_red.svg b/icons/pid_symbols/dark/motor_valve_red.svg new file mode 100644 index 0000000000000000000000000000000000000000..cf1404caf8354abab263d067909b3073768fb5d8 --- /dev/null +++ b/icons/pid_symbols/dark/motor_valve_red.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#ffffff" + d="M 1,3 V 7 L 9,3 v 4 z" + id="path1" + style="fill:#ff0000;stroke-width:0.2;stroke-linejoin:round" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="M 5,2.5 V 5" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,5 h 1" + id="path4" + style="stroke-width:0.2" /> + <circle + style="fill:none;stroke:#ffffff;stroke-width:0.2" + id="circle1" + cx="5" + cy="1.5" + r="1" /> + <path + style="fill:#ffffff;stroke:#ffffff;stroke-opacity:1;stroke-width:0.05;stroke-dasharray:none;stroke-linejoin:round" + d="M 4.5,2 L 4.5,1 L 4.7,1 L 5,1.9 L 5.3,1 L 5.5,1 L 5.5,2 L 5.4,2 L 5.4,1.1 L 5.1,2 L 4.9,2 L 4.6,1.1 L 4.6,2 Z" + id="text1" /> +</svg> diff --git a/icons/pid_symbols/dark/pressure_gauge.svg b/icons/pid_symbols/dark/pressure_gauge.svg new file mode 100644 index 0000000000000000000000000000000000000000..501d98ce9545e7faf45682009d3d151b4d77937b --- /dev/null +++ b/icons/pid_symbols/dark/pressure_gauge.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="7" + height="7" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="m 3.5,6.5 v 0.5" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path2" + d="M 1.73,5.43 2.43,4.56 M 1,3.5 H 2 M 1.73,1.73 2.43,2.43 M 3.5,1 V 2 M 5.27,1.73 4.56,2.43 M 6,3.5 H 5 M 5.27,5.27 4.56,4.56" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <circle + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="circle1" + cx="3.5" + cy="3.5" + r="3" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + d="M 2.7,6.3 3.5,3.5 4.3,6.3" + id="path3" /> +</svg> diff --git a/icons/pid_symbols/dark/pressure_regulator.svg b/icons/pid_symbols/dark/pressure_regulator.svg new file mode 100644 index 0000000000000000000000000000000000000000..e1e7f414c302c5f89f73e4e5a433d2ce4372293b --- /dev/null +++ b/icons/pid_symbols/dark/pressure_regulator.svg @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="10" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,7 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,7 h 1" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="M 1,5 V 9 L 9,5 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="M 5,7 V 4" + id="path4" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="M 5,7 8,4 V 1 H 5 v 1" + id="path5" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + id="path6" + d="M 3,4 A 2,2 0 0 1 5,2 2,2 0 0 1 7,4 H 5 Z" /> +</svg> diff --git a/icons/pid_symbols/dark/pressure_transducer.svg b/icons/pid_symbols/dark/pressure_transducer.svg new file mode 100644 index 0000000000000000000000000000000000000000..6a9883e49728c75e2318b94017dd3fac458381b9 --- /dev/null +++ b/icons/pid_symbols/dark/pressure_transducer.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="7" + height="7" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="M 3.5,6.5 V 7" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path3" + d="M 2,4.2 V 1.5 H 2.5 A 0.75,0.75 0 1 1 2.5,3 H 2 M 4,4 H 5 M 4.5,4 V 1.5 M 4,1.5 h 1" + style="fill:none;stroke:#ffffff;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path2" + d="M 0.7,4.5 H 6.3" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <circle + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="circle1" + cx="3.5" + cy="3.5" + r="3" /> +</svg> diff --git a/icons/pid_symbols/dark/quick_connector.svg b/icons/pid_symbols/dark/quick_connector.svg new file mode 100644 index 0000000000000000000000000000000000000000..679e517e01f4e54f6d61e79eb68a0cda1899dcf4 --- /dev/null +++ b/icons/pid_symbols/dark/quick_connector.svg @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="6" + height="5" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="m 1,0.5 v 4 h 4 v -4 z" + id="path1" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="M 0,2.5 H 1" + id="path3" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="M 5,2.5 H 6" + id="path4" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none" + d="M 3,0 V 5" + id="path5" /> +</svg> diff --git a/icons/pid_symbols/dark/relief_valve.svg b/icons/pid_symbols/dark/relief_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..5b1e2b0bcc294d855300e10e6c2befe97af8aa6f --- /dev/null +++ b/icons/pid_symbols/dark/relief_valve.svg @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="6" + height="10" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + d="M 3,6 1.5,8.6 h 3 z" + stroke-width="0,1" + id="path1" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" + d="M 3,8.6 V 10" + id="path2" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.4;stroke-dasharray:none;stroke-opacity:1" + d="M 4.8,4.4 V 7.6" + id="path3" /> + <path + d="M 3,6 V 5 L 2,4.5 4,3.5 2,2.5 4,1.5 3,1 V 0" + stroke-width="1.13386" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" + id="path4" /> + <path + d="m 3,6 2.6,1.5 v -3 z" + stroke-width="0,1" + id="path5" + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /> + <circle + cx="3" + cy="6" + fill="#ffffff" + id="circle1" + r="0.5" /> +</svg> diff --git a/icons/pid_symbols/dark/three_way_valve.svg b/icons/pid_symbols/dark/three_way_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..6b738f7450c050c47baed3a6cbf5359173f477d3 --- /dev/null +++ b/icons/pid_symbols/dark/three_way_valve.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#ffffff" + d="M 0,3 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="m 9,3 h 1" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#ffffff" + d="M 1,1 V 5 L 9,1 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none" + d="M 5,3 7,7 H 3 Z" + id="path4" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none" + d="M 5,7 v 1" + id="path5" /> +</svg> diff --git a/icons/pid_symbols/dark/vessel.svg b/icons/pid_symbols/dark/vessel.svg new file mode 100644 index 0000000000000000000000000000000000000000..2034f174f9fb4043cf2b0af80ec37d5b6bab164d --- /dev/null +++ b/icons/pid_symbols/dark/vessel.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="8.1999998" + height="15.2" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="" /> + <path + style="fill:none;stroke:#ffffff;stroke-width:0.2;stroke-linejoin:round" + d="m 0.1,13.6 a 4,1.5 0 0 0 8,0 v -12 a 4,1.5 0 0 0 -8,0 z" + id="path2" /> +</svg> diff --git a/icons/pid_symbols/light/arrow.svg b/icons/pid_symbols/light/arrow.svg new file mode 100644 index 0000000000000000000000000000000000000000..a5a1f22d00b5a055878fd1c45fc486e95d0673aa --- /dev/null +++ b/icons/pid_symbols/light/arrow.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="4" + height="4" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="M 0.7,2 l 2.6,-1.5 v 3 z" + style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:0.2;stroke-dasharray:none;stroke-linejoin:round;fill-opacity:1" /> + <path + id="path2" + d="M 0,2 h 4" + style="stroke:#000000;stroke-opacity:1;fill:none;stroke-width:0.2;stroke-dasharray:none" /> +</svg> diff --git a/icons/pid_symbols/light/burst_disk.svg b/icons/pid_symbols/light/burst_disk.svg new file mode 100644 index 0000000000000000000000000000000000000000..fc0a6ebf94b9881ec5658e48da6e537844b6f420 --- /dev/null +++ b/icons/pid_symbols/light/burst_disk.svg @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="4" + height="6" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="M 0.5,0 V 6" + id="path1" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="m 1.5,0 v 1 a 2,2 0 1 1 0,4 v 1" + id="path2" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="m 0,3 h 0.5" + id="path3" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="m 3.5,3 h 0.5" + id="path4" /> +</svg> diff --git a/icons/pid_symbols/light/check_valve.svg b/icons/pid_symbols/light/check_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..6f5752dc17d8e7e9bfef693156a71b0f5fc6c043 --- /dev/null +++ b/icons/pid_symbols/light/check_valve.svg @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="5" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <circle + style="fill:#000000;stroke:none" + id="path2" + cx="1" + cy="4.5" + r="0.5" /> + <path + fill="none" + stroke="#000000" + d="m 9,2.5 h 1 z" + id="path1-5-2" + style="stroke-width:0.2;stroke-dasharray:none" /> + <path + fill="none" + stroke="#000000" + d="M 0,2.5 H 1 Z" + id="path1-5" + style="stroke-width:0.2;stroke-dasharray:none" /> + <path + fill="none" + stroke="#000000" + d="m 1,0.5 v 4 l 8,-4 v 4" + id="path1" + style="stroke-width:0.2;stroke-dasharray:none;stroke-linejoin:round" /> +</svg> diff --git a/icons/pid_symbols/light/flexible_connection.svg b/icons/pid_symbols/light/flexible_connection.svg new file mode 100644 index 0000000000000000000000000000000000000000..4f2330a8d56087043b7c8c611503c5e8e0a62a51 --- /dev/null +++ b/icons/pid_symbols/light/flexible_connection.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="6" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="M 0,3 H 1" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path2" + d="m 9,3 h 1" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path3" + d="M 1,0 V 6 M 2,6 V 0" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path4" + d="M 8,0 V 6 M 9,0 v 6" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path5" + d="M 2,3 H 3 A 2,2 0 1 1 7,3 H 8" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> +</svg> diff --git a/icons/pid_symbols/light/manual_valve.svg b/icons/pid_symbols/light/manual_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..87d477fa760575b9f48056d6cc4e5de233d22642 --- /dev/null +++ b/icons/pid_symbols/light/manual_valve.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="5" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#000000" + d="M 0,2.5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 9,2.5 h 1" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 1,0.5 v 4 l 8,-4 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> +</svg> diff --git a/icons/pid_symbols/light/motor_valve.svg b/icons/pid_symbols/light/motor_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..e80e4a92860053fa8b8ea8301afd9168aff2cc28 --- /dev/null +++ b/icons/pid_symbols/light/motor_valve.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#000000" + d="M 1,3 V 7 L 9,3 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> + <path + fill="none" + stroke="#000000" + d="M 0,5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="M 5,2.5 V 5" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 9,5 h 1" + id="path4" + style="stroke-width:0.2" /> + <circle + style="fill:none;stroke:#000000;stroke-width:0.2" + id="circle1" + cx="5" + cy="1.5" + r="1" /> + <path + style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:0.05;stroke-dasharray:none;stroke-linejoin:round" + d="M 4.5,2 L 4.5,1 L 4.7,1 L 5,1.9 L 5.3,1 L 5.5,1 L 5.5,2 L 5.4,2 L 5.4,1.1 L 5.1,2 L 4.9,2 L 4.6,1.1 L 4.6,2 Z" + id="text1" /> +</svg> diff --git a/icons/pid_symbols/light/motor_valve_green.svg b/icons/pid_symbols/light/motor_valve_green.svg new file mode 100644 index 0000000000000000000000000000000000000000..ef8cea865f8d42cf205e78aad98e3e95a3cf582e --- /dev/null +++ b/icons/pid_symbols/light/motor_valve_green.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#000000" + d="M 1,3 V 7 L 9,3 v 4 z" + id="path1" + style="fill:#00ff00;stroke-width:0.2;stroke-linejoin:round" /> + <path + fill="none" + stroke="#000000" + d="M 0,5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="M 5,2.5 V 5" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 9,5 h 1" + id="path4" + style="stroke-width:0.2" /> + <circle + style="fill:none;stroke:#000000;stroke-width:0.2" + id="circle1" + cx="5" + cy="1.5" + r="1" /> + <path + style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:0.05;stroke-dasharray:none;stroke-linejoin:round" + d="M 4.5,2 L 4.5,1 L 4.7,1 L 5,1.9 L 5.3,1 L 5.5,1 L 5.5,2 L 5.4,2 L 5.4,1.1 L 5.1,2 L 4.9,2 L 4.6,1.1 L 4.6,2 Z" + id="text1" /> +</svg> diff --git a/icons/pid_symbols/light/motor_valve_red.svg b/icons/pid_symbols/light/motor_valve_red.svg new file mode 100644 index 0000000000000000000000000000000000000000..7e947e9ea86e240dd37554369a789bc28e996529 --- /dev/null +++ b/icons/pid_symbols/light/motor_valve_red.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#000000" + d="M 1,3 V 7 L 9,3 v 4 z" + id="path1" + style="fill:#ff0000;stroke-width:0.2;stroke-linejoin:round" /> + <path + fill="none" + stroke="#000000" + d="M 0,5 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="M 5,2.5 V 5" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 9,5 h 1" + id="path4" + style="stroke-width:0.2" /> + <circle + style="fill:none;stroke:#000000;stroke-width:0.2" + id="circle1" + cx="5" + cy="1.5" + r="1" /> + <path + style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:0.05;stroke-dasharray:none;stroke-linejoin:round" + d="M 4.5,2 L 4.5,1 L 4.7,1 L 5,1.9 L 5.3,1 L 5.5,1 L 5.5,2 L 5.4,2 L 5.4,1.1 L 5.1,2 L 4.9,2 L 4.6,1.1 L 4.6,2 Z" + id="text1" /> +</svg> diff --git a/icons/pid_symbols/light/pressure_gauge.svg b/icons/pid_symbols/light/pressure_gauge.svg new file mode 100644 index 0000000000000000000000000000000000000000..50eb200e9de4ac115c1925f4604b051818cf1b41 --- /dev/null +++ b/icons/pid_symbols/light/pressure_gauge.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="7" + height="7" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="m 3.5,6.5 v 0.5" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path2" + d="M 1.73,5.43 2.43,4.56 M 1,3.5 H 2 M 1.73,1.73 2.43,2.43 M 3.5,1 V 2 M 5.27,1.73 4.56,2.43 M 6,3.5 H 5 M 5.27,5.27 4.56,4.56" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <circle + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="circle1" + cx="3.5" + cy="3.5" + r="3" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + d="M 2.7,6.3 3.5,3.5 4.3,6.3" + id="path3" /> +</svg> diff --git a/icons/pid_symbols/light/pressure_regulator.svg b/icons/pid_symbols/light/pressure_regulator.svg new file mode 100644 index 0000000000000000000000000000000000000000..ae77edc28e573ba1ef3d6fab400e7c2719994400 --- /dev/null +++ b/icons/pid_symbols/light/pressure_regulator.svg @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="10" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#000000" + d="M 0,7 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 9,7 h 1" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="M 1,5 V 9 L 9,5 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="M 5,7 V 4" + id="path4" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="M 5,7 8,4 V 1 H 5 v 1" + id="path5" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + id="path6" + d="M 3,4 A 2,2 0 0 1 5,2 2,2 0 0 1 7,4 H 5 Z" /> +</svg> diff --git a/icons/pid_symbols/light/pressure_transducer.svg b/icons/pid_symbols/light/pressure_transducer.svg new file mode 100644 index 0000000000000000000000000000000000000000..f7b68083ca5d9acb8c38c80904402e3ab6d75864 --- /dev/null +++ b/icons/pid_symbols/light/pressure_transducer.svg @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="7" + height="7" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="M 3.5,6.5 V 7" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path3" + d="M 2,4.2 V 1.5 H 2.5 A 0.75,0.75 0 1 1 2.5,3 H 2 M 4,4 H 5 M 4.5,4 V 1.5 M 4,1.5 h 1" + style="fill:none;stroke:#000000;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /> + <path + id="path2" + d="M 0.7,4.5 H 6.3" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" /> + <circle + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="circle1" + cx="3.5" + cy="3.5" + r="3" /> +</svg> diff --git a/icons/pid_symbols/light/quick_connector.svg b/icons/pid_symbols/light/quick_connector.svg new file mode 100644 index 0000000000000000000000000000000000000000..10b0146baea439c3d304962f1b6b92b3847291c1 --- /dev/null +++ b/icons/pid_symbols/light/quick_connector.svg @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="6" + height="5" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="m 1,0.5 v 4 h 4 v -4 z" + id="path1" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="M 0,2.5 H 1" + id="path3" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="M 5,2.5 H 6" + id="path4" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none" + d="M 3,0 V 5" + id="path5" /> +</svg> diff --git a/icons/pid_symbols/light/relief_valve.svg b/icons/pid_symbols/light/relief_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..5635bf87134262044dc56243df6547d599856446 --- /dev/null +++ b/icons/pid_symbols/light/relief_valve.svg @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="6" + height="10" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + d="M 3,6 1.5,8.6 h 3 z" + stroke-width="0,1" + id="path1" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" + d="M 3,8.6 V 10" + id="path2" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.4;stroke-dasharray:none;stroke-opacity:1" + d="M 4.8,4.4 V 7.6" + id="path3" /> + <path + d="M 3,6 V 5 L 2,4.5 4,3.5 2,2.5 4,1.5 3,1 V 0" + stroke-width="1.13386" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" + id="path4" /> + <path + d="m 3,6 2.6,1.5 v -3 z" + stroke-width="0,1" + id="path5" + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /> + <circle + cx="3" + cy="6" + fill="#000000" + id="circle1" + r="0.5" /> +</svg> diff --git a/icons/pid_symbols/light/three_way_valve.svg b/icons/pid_symbols/light/three_way_valve.svg new file mode 100644 index 0000000000000000000000000000000000000000..18abf1a506d4d99f4afd50d45b84036dc9caff76 --- /dev/null +++ b/icons/pid_symbols/light/three_way_valve.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="10" + height="8" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + fill="none" + stroke="#000000" + d="M 0,3 H 1" + id="path2" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="m 9,3 h 1" + id="path3" + style="stroke-width:0.2" /> + <path + fill="none" + stroke="#000000" + d="M 1,1 V 5 L 9,1 v 4 z" + id="path1" + style="stroke-width:0.2;stroke-linejoin:round" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none" + d="M 5,3 7,7 H 3 Z" + id="path4" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round;stroke-dasharray:none" + d="M 5,7 v 1" + id="path5" /> +</svg> diff --git a/icons/pid_symbols/light/vessel.svg b/icons/pid_symbols/light/vessel.svg new file mode 100644 index 0000000000000000000000000000000000000000..fabb93e5098aaa9d72dc5618943cc980a5188df9 --- /dev/null +++ b/icons/pid_symbols/light/vessel.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="8.1999998" + height="15.2" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <path + id="path1" + d="" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linejoin:round" + d="m 0.1,13.6 a 4,1.5 0 0 0 8,0 v -12 a 4,1.5 0 0 0 -8,0 z" + id="path2" /> +</svg> diff --git a/justfile b/justfile index ea6682b72a8b80b385a49c56e482096d98c68fb6..f537b2ebde3647d48e89f8c9074c4958475ef967 100644 --- a/justfile +++ b/justfile @@ -9,7 +9,7 @@ test *ARGS: cargo nextest run {{ARGS}} run LEVEL="debug": - RUST_LOG=segs={{LEVEL}} cargo r + RUST_BACKTRACE=full RUST_LOG=segs={{LEVEL}} cargo r doc: cargo doc --no-deps --open diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs index e3ed70e723913efad04c48e64e1bf43777ec1ad0..4162f24df0f391d151a7c8664e6a747be13bb9cc 100644 --- a/src/mavlink/reflection.rs +++ b/src/mavlink/reflection.rs @@ -98,6 +98,21 @@ impl ReflectionContext { .map(|f| f.to_mav_field(msg.id, self).ok()) .collect() } + + pub fn get_all_state_fields( + &'static self, + message_id: impl MessageLike, + ) -> Option<Vec<IndexedField>> { + let msg = message_id.to_mav_message(self).ok()?; + msg.fields + .iter() + .filter(|f| { + f.name.to_lowercase().ends_with("state") + || f.name.to_lowercase().ends_with("status") + }) + .map(|f| f.to_mav_field(msg.id, self).ok()) + .collect() + } } #[derive(Debug, Clone)] diff --git a/src/ui.rs b/src/ui.rs index 077abdba17c67d09579d71aa56bf547fbe52910f..a1f7030e2a67392adec41c719f71b3899227d18a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,6 @@ mod app; pub mod cache; +pub mod font; mod panes; mod persistency; mod shortcuts; diff --git a/src/ui/app.rs b/src/ui/app.rs index 632d259eed8f0166222081bbee6eac571732552c..ad8fa9fe0387921d5596236afa2f415efecbe8e5 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -258,6 +258,12 @@ impl eframe::App for App { impl App { pub fn new(app_name: &str, ctx: &CreationContext) -> Self { + // Load the image loaders + egui_extras::install_image_loaders(&ctx.egui_ctx); + + // Install the fonts + super::font::add_font(&ctx.egui_ctx); + let mut layout_manager = if let Some(storage) = ctx.storage { LayoutManager::new(app_name, storage) } else { @@ -422,7 +428,7 @@ impl Behavior<Pane> for AppBehavior { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct PaneResponse { pub action_called: Option<PaneAction>, pub drag_response: egui_tiles::UiResponse, diff --git a/src/ui/cache.rs b/src/ui/cache.rs index 031a361506c55edec402f8fc099aae7810d1e494..db6e95f540f6370bf103b2b0dc87eb485e4def49 100644 --- a/src/ui/cache.rs +++ b/src/ui/cache.rs @@ -158,7 +158,7 @@ impl ChangeTracker { /// ``` /// let initial_tracker = ChangeTracker::record_initial_state(&state); /// ``` - pub fn record_initial_state<T: Hash>(state: &T) -> Self { + pub fn record_initial_state<T: Hash>(state: T) -> Self { let mut hasher = DefaultHasher::new(); state.hash(&mut hasher); let integrity_digest = hasher.finish(); @@ -187,7 +187,7 @@ impl ChangeTracker { /// println!("The state has changed."); /// } /// ``` - pub fn has_changed<T: Hash>(&self, state: &T) -> bool { + pub fn has_changed<T: Hash>(&self, state: T) -> bool { let mut hasher = DefaultHasher::new(); state.hash(&mut hasher); self.integrity_digest != hasher.finish() diff --git a/src/ui/font.rs b/src/ui/font.rs new file mode 100644 index 0000000000000000000000000000000000000000..de5864a2b2da49bc1c0731184931bd235e1b97ea --- /dev/null +++ b/src/ui/font.rs @@ -0,0 +1,30 @@ +use egui::epaint::text::{FontInsert, InsertFontFamily}; + +pub const NOTO: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/fonts/NotoSans-VariableFont_wdth,wght.ttf" +)); + +pub const JETBRAINS: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/fonts/JetBrainsMono-VariableFont_wght.ttf" +)); + +pub fn add_font(ctx: &egui::Context) { + ctx.add_font(FontInsert::new( + "noto_sans", + egui::FontData::from_static(NOTO), + vec![InsertFontFamily { + family: egui::FontFamily::Proportional, + priority: egui::epaint::text::FontPriority::Highest, + }], + )); + ctx.add_font(FontInsert::new( + "jetbrains_mono", + egui::FontData::from_static(JETBRAINS), + vec![InsertFontFamily { + family: egui::FontFamily::Monospace, + priority: egui::epaint::text::FontPriority::Highest, + }], + )); +} diff --git a/src/ui/panes.rs b/src/ui/panes.rs index 34c56503162878e5c7cffd58321617b7814f1223..977fbda1ee08dd7f9fac4c989573e74c1fbf8e96 100644 --- a/src/ui/panes.rs +++ b/src/ui/panes.rs @@ -1,5 +1,6 @@ mod default; mod messages_viewer; +mod pid_drawing_tool; pub mod plot; use egui_tiles::TileId; @@ -11,7 +12,7 @@ use crate::mavlink::{MavMessage, TimedMessage}; use super::app::PaneResponse; -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, Debug)] pub struct Pane { pub pane: PaneKind, } @@ -87,6 +88,9 @@ pub enum PaneKind { #[strum(message = "Plot 2D")] Plot2D(plot::Plot2DPane), + + #[strum(message = "Pid")] + PidOld(pid_drawing_tool::PidPane), } impl Default for PaneKind { diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs new file mode 100644 index 0000000000000000000000000000000000000000..204b86aed73673678b68daa8ef7961125a1a8032 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool.rs @@ -0,0 +1,459 @@ +mod connections; +mod elements; +mod grid; +mod symbols; + +use connections::Connection; +use core::f32; +use egui::{Color32, Context, CursorIcon, PointerButton, Response, Sense, Theme, Ui}; +use egui_tiles::TileId; +use elements::Element; +use glam::Vec2; +use grid::GridInfo; +use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator; +use symbols::{Symbol, icons::Icon}; + +use crate::{ + MAVLINK_PROFILE, + error::ErrInstrument, + mavlink::{GSE_TM_DATA, MessageData, TimedMessage, reflection::MessageLike}, + ui::{app::PaneResponse, cache::ChangeTracker, utils::egui_to_glam}, +}; + +use super::PaneBehavior; + +#[derive(Clone, Debug)] +enum Action { + Connect(usize), + ContextMenu(Vec2), + DragElement(usize), + DragConnection(usize, usize), + DragGrid, +} + +/// Piping and instrumentation diagram +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct PidPane { + // Persistent internal state + elements: Vec<Element>, + connections: Vec<Connection>, + grid: GridInfo, + message_subscription_id: u32, + + // UI settings + center_content: bool, + + // Temporary internal state + #[serde(skip)] + action: Option<Action>, + #[serde(skip)] + editable: bool, + #[serde(skip)] + is_subs_window_visible: bool, +} + +impl Default for PidPane { + fn default() -> Self { + Self { + elements: Vec::new(), + connections: Vec::new(), + grid: GridInfo::default(), + message_subscription_id: GSE_TM_DATA::ID, + center_content: false, + action: None, + editable: false, + is_subs_window_visible: false, + } + } +} + +impl PartialEq for PidPane { + fn eq(&self, other: &Self) -> bool { + self.elements == other.elements + && self.connections == other.connections + && self.grid == other.grid + && self.center_content == other.center_content + } +} + +impl PaneBehavior for PidPane { + fn ui(&mut self, ui: &mut egui::Ui, _: TileId) -> PaneResponse { + let theme = PidPane::find_theme(ui.ctx()); + + if self.center_content && !self.editable { + self.center(ui); + } + + if self.editable { + self.draw_grid(ui, theme); + } + self.draw_connections(ui, theme); + self.elements_ui(ui, theme); + + // Handle things that require knowing the position of the pointer + let (_, response) = ui.allocate_at_least(ui.max_rect().size(), Sense::click_and_drag()); + if let Some(pointer_pos) = response.hover_pos().map(|p| egui_to_glam(p.to_vec2())) { + if self.editable { + self.handle_zoom(ui, theme, pointer_pos); + } + + // Set grab icon when hovering something + let hovers_element = self.hovers_element(pointer_pos).is_some(); + let hovers_connection_point = self.hovers_connection_point(pointer_pos).is_some(); + if self.editable && (hovers_element || hovers_connection_point) { + ui.ctx() + .output_mut(|output| output.cursor_icon = CursorIcon::Grab); + } + + self.detect_action(&response, pointer_pos); + self.handle_actions(&response, pointer_pos); + } + + // The context menu does not need the pointer's position. + // If active it has to be shown even if the pointer goes off screen. + if let Some(Action::ContextMenu(pointer_pos)) = self.action.clone() { + response.context_menu(|ui| self.draw_context_menu(ui, pointer_pos)); + } + + let change_tracker = ChangeTracker::record_initial_state(self.message_subscription_id); + egui::Window::new("Subscription") + .id(ui.auto_id_with("sub_settings")) + .auto_sized() + .collapsible(true) + .movable(true) + .open(&mut self.is_subs_window_visible) + .show(ui.ctx(), |ui| { + subscription_window(ui, &mut self.message_subscription_id) + }); + if change_tracker.has_changed(self.message_subscription_id) { + self.reset_subscriptions(); + } + + PaneResponse::default() + } + + fn contains_pointer(&self) -> bool { + false + } + + fn update(&mut self, messages: &[TimedMessage]) { + if let Some(msg) = messages.last() { + for element in &mut self.elements { + element.update(&msg.message, self.message_subscription_id); + } + } + } + + fn get_message_subscription(&self) -> Option<u32> { + Some(self.message_subscription_id) + } +} + +impl PidPane { + /// Returns the currently used theme + fn find_theme(ctx: &Context) -> Theme { + // In Egui you can either decide a theme or use the system one. + // If the system theme cannot be determined, a fallback theme can be set. + ctx.options(|options| match options.theme_preference { + egui::ThemePreference::Light => Theme::Light, + egui::ThemePreference::Dark => Theme::Dark, + egui::ThemePreference::System => match ctx.system_theme() { + Some(Theme::Light) => Theme::Light, + Some(Theme::Dark) => Theme::Dark, + None => options.fallback_theme, + }, + }) + } + + fn dots_color(theme: Theme) -> Color32 { + match theme { + Theme::Dark => Color32::DARK_GRAY, + Theme::Light => Color32::BLACK.gamma_multiply(0.2), + } + } + + /// Returns the index of the element the point is on, if any + fn hovers_element(&self, p_s: Vec2) -> Option<usize> { + self.elements + .iter() + .position(|elem| elem.contains(self.grid.screen_to_grid(p_s))) + } + + /// Return the connection and segment indexes where the position is on, if any + fn hovers_connection(&self, p_s: Vec2) -> Option<(usize, usize)> { + self.connections + .iter() + .enumerate() + .find_map(|(conn_idx, conn)| { + let segm_idx = conn.contains(self, p_s); + Some(conn_idx).zip(segm_idx) + }) + } + + fn hovers_connection_point(&self, p_s: Vec2) -> Option<(usize, usize)> { + self.connections + .iter() + .enumerate() + .find_map(|(conn_idx, conn)| { + let p_idx = conn.hovers_point(self.grid.screen_to_grid(p_s)); + Some(conn_idx).zip(p_idx) + }) + } + + fn draw_grid(&self, ui: &Ui, theme: Theme) { + let painter = ui.painter(); + let window_rect = ui.max_rect(); + let dot_color = PidPane::dots_color(theme); + + let offset_x = (self.grid.zero_pos.x % self.grid.size()) as i32; + let offset_y = (self.grid.zero_pos.y % self.grid.size()) as i32; + + let start_x = + (window_rect.min.x / self.grid.size()) as i32 * self.grid.size() as i32 + offset_x; + let end_x = (window_rect.max.x / self.grid.size() + 2.0) as i32 * self.grid.size() as i32 + + offset_x; + let start_y = + (window_rect.min.y / self.grid.size()) as i32 * self.grid.size() as i32 + offset_y; + let end_y = (window_rect.max.y / self.grid.size() + 2.0) as i32 * self.grid.size() as i32 + + offset_y; + + for x in (start_x..end_x).step_by(self.grid.size() as usize) { + for y in (start_y..end_y).step_by(self.grid.size() as usize) { + let rect = egui::Rect::from_min_size( + egui::Pos2::new(x as f32, y as f32), + egui::Vec2::new(2.0, 2.0), + ); + painter.rect_filled(rect, 0.0, dot_color); + } + } + } + + fn draw_connections(&self, ui: &Ui, theme: Theme) { + let painter = ui.painter(); + + for conn in &self.connections { + conn.draw(self, painter, theme); + } + } + + fn elements_ui(&mut self, ui: &mut Ui, theme: Theme) { + for element in &mut self.elements { + ui.scope(|ui| { + element.ui(ui, &self.grid, theme, self.message_subscription_id); + }); + } + } + + fn draw_context_menu(&mut self, ui: &mut Ui, pointer_pos: Vec2) { + ui.set_max_width(180.0); // To make sure we wrap long text + + if !self.editable { + if ui.button("Enable editing").clicked() { + self.editable = true; + self.action.take(); + ui.close_menu(); + } + ui.checkbox(&mut self.center_content, "Center"); + return; + } + + if let Some(elem_idx) = self.hovers_element(pointer_pos) { + if ui.button("Connect").clicked() { + self.action = Some(Action::Connect(elem_idx)); + ui.close_menu(); + } + if ui.button("Delete").clicked() { + self.delete_element(elem_idx); + self.action.take(); + ui.close_menu(); + } + self.elements[elem_idx].context_menu(ui); + } else if let Some((conn_idx, segm_idx)) = self.hovers_connection(pointer_pos) { + if ui.button("Split").clicked() { + self.connections[conn_idx].split(segm_idx, self.grid.screen_to_grid(pointer_pos)); + self.action.take(); + ui.close_menu(); + } + if ui.button("Change start anchor").clicked() { + let conn = &mut self.connections[conn_idx]; + conn.start_anchor = + (conn.start_anchor + 1) % self.elements[conn.start].anchor_points_len(); + self.action.take(); + ui.close_menu(); + } + if ui.button("Change end anchor").clicked() { + let conn = &mut self.connections[conn_idx]; + conn.end_anchor = + (conn.end_anchor + 1) % self.elements[conn.end].anchor_points_len(); + self.action.take(); + ui.close_menu(); + } + } else { + ui.menu_button("Symbols", |ui| { + for symbol in Symbol::iter() { + if let Symbol::Icon(_) = symbol { + ui.menu_button("Icons", |ui| { + for icon in Icon::iter() { + if ui.button(icon.to_string()).clicked() { + self.elements.push(Element::new( + self.grid.screen_to_grid(pointer_pos).round(), + Symbol::Icon(icon), + )); + self.action.take(); + ui.close_menu(); + } + } + }); + } else if ui.button(symbol.to_string()).clicked() { + self.elements.push(Element::new( + self.grid.screen_to_grid(pointer_pos).round(), + symbol, + )); + self.action.take(); + ui.close_menu(); + } + } + }); + } + + if ui.button("Pane subscription settings…").clicked() { + self.is_subs_window_visible = true; + ui.close_menu(); + } + + if ui.button("Disable editing").clicked() { + self.editable = false; + ui.close_menu(); + } + } + + /// Removes an element from the diagram + fn delete_element(&mut self, elem_idx: usize) { + // First delete connection referencing this element + self.connections.retain(|elem| !elem.connected(elem_idx)); + + // Then the element + self.elements.remove(elem_idx); + } + + fn center(&mut self, ui: &Ui) { + let ui_center = egui_to_glam(ui.max_rect().center().to_vec2()); + + // Chain elements positions and connection mid points + let points: Vec<Vec2> = self + .elements + .iter() + .map(|e| e.center()) + .chain(self.connections.iter().flat_map(|conn| conn.points())) + .collect(); + + let min_x = points + .iter() + .map(|p| p.x) + .min_by(|a, b| a.total_cmp(b)) + .log_unwrap(); + let min_y = points + .iter() + .map(|p| p.y) + .min_by(|a, b| a.total_cmp(b)) + .log_unwrap(); + let min = Vec2::new(min_x, min_y); + + let max_x = points + .iter() + .map(|p| p.x) + .max_by(|a, b| a.total_cmp(b)) + .log_unwrap(); + let max_y = points + .iter() + .map(|p| p.y) + .max_by(|a, b| a.total_cmp(b)) + .log_unwrap(); + let max = Vec2::new(max_x, max_y); + + self.grid.zero_pos = ui_center - min.midpoint(max) * self.grid.size(); + } + + fn handle_zoom(&mut self, ui: &Ui, theme: Theme, pointer_pos: Vec2) { + let scroll_delta = ui.input(|i| i.raw_scroll_delta).y; + if scroll_delta != 0.0 { + self.grid.apply_scroll_delta(scroll_delta, pointer_pos); + + // Invalidate the cache to redraw the images + for icon in Icon::iter() { + let img: egui::ImageSource = icon.get_image(theme); + ui.ctx().forget_image(img.uri().log_unwrap()); + } + } + } + + fn detect_action(&mut self, response: &Response, pointer_pos: Vec2) { + if response.clicked_by(PointerButton::Secondary) { + self.action = Some(Action::ContextMenu(pointer_pos)); + } else if self.editable { + if response.drag_started() { + if response.dragged_by(PointerButton::Middle) { + self.action = Some(Action::DragGrid); + } else if let Some(drag_element_action) = + self.hovers_element(pointer_pos).map(Action::DragElement) + { + self.action = Some(drag_element_action); + } else if let Some(drag_connection_point) = self + .hovers_connection_point(pointer_pos) + .map(|(idx1, idx2)| Action::DragConnection(idx1, idx2)) + { + self.action = Some(drag_connection_point); + } + } else if response.drag_stopped() { + self.action.take(); + } + } + } + + fn handle_actions(&mut self, response: &Response, pointer_pos: Vec2) { + match self.action { + Some(Action::Connect(start)) => { + if response.clicked() { + if let Some(end) = self.hovers_element(pointer_pos) { + if start != end { + self.connections.push(Connection::new(start, 0, end, 0)); + } + self.action.take(); + } + } + } + Some(Action::DragElement(idx)) => { + let pointer_pos_g = self.grid.screen_to_grid(pointer_pos).round(); + self.elements[idx].set_center_at(pointer_pos_g); + } + Some(Action::DragConnection(conn_idx, point_idx)) => { + let pointer_pos_g = self.grid.screen_to_grid(pointer_pos).round(); + self.connections[conn_idx].set_point(point_idx, pointer_pos_g); + } + Some(Action::DragGrid) => { + self.grid.zero_pos += egui_to_glam(response.drag_delta()); + } + // Context menu has to be handled outside since it does not reuquire the pointer's position + Some(Action::ContextMenu(_)) => {} + None => {} + } + } + + fn reset_subscriptions(&mut self) { + for element in &mut self.elements { + element.reset_subscriptions(); + } + } +} + +fn subscription_window(ui: &mut Ui, msg_id: &mut u32) { + let current_msg = msg_id.to_mav_message(&MAVLINK_PROFILE).log_unwrap(); + egui::ComboBox::from_label("Message subscription") + .selected_text(current_msg.name.as_str()) + .show_ui(ui, |ui| { + for msg in MAVLINK_PROFILE.get_sorted_msgs() { + ui.selectable_value(msg_id, msg.id, &msg.name); + } + }); +} diff --git a/src/ui/panes/pid_drawing_tool/connections.rs b/src/ui/panes/pid_drawing_tool/connections.rs new file mode 100644 index 0000000000000000000000000000000000000000..3cde5f8e6a6ac5ecca442e120f25cd9044bdfff7 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/connections.rs @@ -0,0 +1,166 @@ +use egui::{Color32, CornerRadius, Painter, Rect, Stroke, StrokeKind, Theme}; +use glam::{Mat2, Vec2}; +use serde::{Deserialize, Serialize}; + +use crate::{error::ErrInstrument, ui::utils::glam_to_egui}; + +use super::{ + PidPane, + grid::{CONNECTION_LINE_THICKNESS, CONNECTION_LINE_THRESHOLD, CONNECTION_POINT_SIZE, GridInfo}, +}; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct Connection { + /// Index of the start element + pub start: usize, + pub start_anchor: usize, + + /// Index of the end element + pub end: usize, + pub end_anchor: usize, + + /// Mid points in grid coordinates + points_g: Vec<Vec2>, +} + +impl Connection { + pub fn new(start: usize, start_anchor: usize, end: usize, end_anchor: usize) -> Self { + Self { + start, + start_anchor, + end, + end_anchor, + points_g: Vec::new(), + } + } + + /// Mid points in grid coordinates + pub fn points(&self) -> Vec<Vec2> { + self.points_g.clone() + } + + /// Return the index of the segment the point is on, if any + pub fn contains(&self, pid: &PidPane, p_s: Vec2) -> Option<usize> { + let p_g = pid.grid.screen_to_grid(p_s); + let mut points = Vec::new(); + + // Append start point + points.push(pid.elements[self.start].anchor_point(self.start_anchor)); + + // Append all midpoints + self.points_g.iter().for_each(|&p| points.push(p)); + + // Append end point + points.push(pid.elements[self.end].anchor_point(self.end_anchor)); + + // Check each segment + for i in 0..(points.len() - 1) { + let a = points[i]; + let b = points[i + 1]; + if hovers_segment(&pid.grid, p_g, a, b) { + return Some(i); + } + } + + None + } + + /// Checks if the connection references the given element index + pub fn connected(&self, elem_idx: usize) -> bool { + self.start == elem_idx || self.end == elem_idx + } + + /// Returns the index of the point the point is on, if any + pub fn hovers_point(&self, p_g: Vec2) -> Option<usize> { + self.points_g + .iter() + .position(|p| p.distance(p_g) < CONNECTION_POINT_SIZE) + } + + /// Splits a segment of the connection with a new point + pub fn split(&mut self, idx: usize, p_g: Vec2) { + self.points_g.insert(idx, p_g); + } + + /// Sets the poisition of one of the path points + pub fn set_point(&mut self, idx: usize, p_g: Vec2) { + self.points_g[idx] = p_g; + } + + fn line_color(theme: Theme) -> Color32 { + match theme { + Theme::Light => Color32::BLACK, + Theme::Dark => Color32::WHITE, + } + } + + pub fn draw(&self, pid: &PidPane, painter: &Painter, theme: Theme) { + let color = Connection::line_color(theme); + + let start = pid.elements[self.start].anchor_point(self.start_anchor); + let start = pid.grid.grid_to_screen(start); + let end = pid.elements[self.end].anchor_point(self.end_anchor); + let end = pid.grid.grid_to_screen(end); + + // Draw line segments + if self.points_g.is_empty() { + Connection::draw_segment(&pid.grid, painter, color, start, end); + } else { + let points: Vec<Vec2> = self + .points_g + .iter() + .map(|p| pid.grid.grid_to_screen(*p)) + .collect(); + Connection::draw_segment( + &pid.grid, + painter, + color, + start, + *points.first().log_unwrap(), + ); + for i in 0..(points.len() - 1) { + Connection::draw_segment(&pid.grid, painter, color, points[i], points[i + 1]); + } + Connection::draw_segment(&pid.grid, painter, color, *points.last().log_unwrap(), end); + + if pid.editable { + for point in points { + painter.rect( + Rect::from_center_size( + glam_to_egui(point).to_pos2(), + egui::Vec2::splat(CONNECTION_POINT_SIZE * pid.grid.size()), + ), + CornerRadius::ZERO, + Color32::DARK_GRAY, + Stroke::NONE, + StrokeKind::Middle, + ); + } + } + } + } + + fn draw_segment(grid: &GridInfo, painter: &Painter, color: Color32, a: Vec2, b: Vec2) { + painter.line_segment( + [glam_to_egui(a).to_pos2(), glam_to_egui(b).to_pos2()], + (CONNECTION_LINE_THICKNESS * grid.size(), color), + ); + } +} + +/// True if p hovers the segment defined by a and b +fn hovers_segment(grid: &GridInfo, p_g: Vec2, a_g: Vec2, b_g: Vec2) -> bool { + if a_g != b_g { + let segment_g = b_g - a_g; + let rotm = Mat2::from_angle(-segment_g.to_angle()); + + // Rototranslate the point in the segment frame with a as origin + let p_s = rotm * (p_g - a_g); + + let y_threshold = CONNECTION_LINE_THRESHOLD / grid.size(); + 0.0 <= p_s.x && p_s.x <= segment_g.length() && p_s.y.abs() <= y_threshold + } else { + // If a and b are the same point, prevent adding another + false + } +} diff --git a/src/ui/panes/pid_drawing_tool/elements.rs b/src/ui/panes/pid_drawing_tool/elements.rs new file mode 100644 index 0000000000000000000000000000000000000000..ddcf65e9a0463491e4618710a0deb6b3214d7851 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/elements.rs @@ -0,0 +1,135 @@ +use std::f32::consts::FRAC_PI_2; + +use egui::{Theme, Ui}; +use glam::{Mat2, Vec2}; +use serde::{Deserialize, Serialize}; + +use crate::mavlink::MavMessage; + +use super::{ + grid::GridInfo, + symbols::{Symbol, SymbolBehavior}, +}; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Element { + /// Anchor postion in grid coordinates, top-left corner + position: glam::Vec2, + + /// Symbol to be displayed + symbol: Symbol, + + /// Rotation in radiants + rotation: f32, +} + +impl PartialEq for Element { + fn eq(&self, other: &Self) -> bool { + self.position == other.position + && self.symbol == other.symbol + && self.rotation == other.rotation + } +} + +impl Element { + pub fn new(center: Vec2, symbol: Symbol) -> Self { + Self { + position: center - symbol.size() / 2.0, + symbol, + rotation: 0.0, + } + } + + /// Check if the given position is inside the element + pub fn contains(&self, p_g: Vec2) -> bool { + // First we need to do a rotostranslation from the grid's frame to the element's frame + let rotm = Mat2::from_angle(-self.rotation); + let p_e = rotm * (p_g - self.position); + + // The bounding box is just the size + let min_e = Vec2::ZERO; + let max_e = self.symbol.size(); + + // Check if the point is in the bounding box + min_e.x <= p_e.x && p_e.x <= max_e.x && min_e.y <= p_e.y && p_e.y <= max_e.y + } + + /// Moves the element such that its center is at the given position + pub fn set_center_at(&mut self, p_g: Vec2) { + // Rotation matrix from element's frame to grid's frame + let rotm_e_to_g = Mat2::from_angle(self.rotation); + + // Center in grid's frame + let center_g = rotm_e_to_g * self.size() / 2.0; + + self.position = p_g - center_g; + } + + pub fn context_menu(&mut self, ui: &mut Ui) { + if let Symbol::Icon(_) = &mut self.symbol { + if ui.button("Rotate 90° ⟲").clicked() { + self.rotate(-FRAC_PI_2); + ui.close_menu(); + } + if ui.button("Rotate 90° ⟳").clicked() { + self.rotate(FRAC_PI_2); + ui.close_menu(); + } + } + self.symbol.context_menu(ui); + } + + /// Rotate the element by its center + pub fn rotate(&mut self, rotation: f32) { + // Current center position relative to the top-left point in the grid reference frame + let center_g = Mat2::from_angle(self.rotation) * self.symbol.size() / 2.0; + + // Rotate the position by the element's center + self.position += (Mat2::IDENTITY - Mat2::from_angle(rotation)) * center_g; + + // Update absolute rotation + self.rotation += rotation; + } + + /// Returns the position of one anchor point in grid coordinates + pub fn anchor_point(&self, idx: usize) -> Vec2 { + if let Some(anchor_points) = self.symbol.anchor_points() { + // Rotation matrix from element's frame to grid's frame + let rotm_e_to_g = Mat2::from_angle(self.rotation); + + // Then rotate and translate the anchor points + rotm_e_to_g * anchor_points[idx] + self.position + } else { + Vec2::ZERO + } + } + + pub fn anchor_points_len(&self) -> usize { + self.symbol.anchor_points().map_or(0, |v| v.len()) + } + + /// Size in grid units + pub fn size(&self) -> Vec2 { + self.symbol.size() + } + + /// Position of the element's center in grid frame + pub fn center(&self) -> Vec2 { + self.position + Mat2::from_angle(self.rotation) * self.size() * 0.5 + } + + pub fn ui(&mut self, ui: &mut Ui, grid: &GridInfo, theme: Theme, msg: u32) { + let pos = grid.grid_to_screen(self.position); + let size = grid.size(); + self.symbol.paint(ui, theme, pos, size, self.rotation); + self.symbol.subscriptions_ui(ui, msg); + } + + pub fn update(&mut self, message: &MavMessage, subscribed_msg_id: u32) { + self.symbol.update(message, subscribed_msg_id); + } + + pub fn reset_subscriptions(&mut self) { + self.symbol.reset_subscriptions(); + } +} diff --git a/src/ui/panes/pid_drawing_tool/grid.rs b/src/ui/panes/pid_drawing_tool/grid.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa74330e567662659f3c2a6da2678ff805d043ec --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/grid.rs @@ -0,0 +1,60 @@ +use core::f32; + +use glam::Vec2; +use serde::{Deserialize, Serialize}; + +const DEFAULT_SIZE: f32 = 10.0; +const MIN_SIZE: f32 = 5.0; +const MAX_SIZE: f32 = 50.0; +const SCROLL_DELTA: f32 = 1.0; + +pub const CONNECTION_LINE_THRESHOLD: f32 = 5.0; // Pixels +pub const CONNECTION_LINE_THICKNESS: f32 = 0.2; // Grid units +pub const CONNECTION_POINT_SIZE: f32 = 1.0; // Grid units + +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GridInfo { + pub zero_pos: Vec2, + size: f32, +} + +impl Default for GridInfo { + fn default() -> Self { + Self { + zero_pos: Vec2::ZERO, + size: DEFAULT_SIZE, + } + } +} + +impl GridInfo { + /// Returns the grid size + pub fn size(&self) -> f32 { + self.size + } + + /// Applies the scroll delta at the given position (in screen coordinates) + pub fn apply_scroll_delta(&mut self, delta: f32, pos_s: Vec2) { + if delta == 0.0 || delta == f32::NAN { + return; + } + + let old_size = self.size; + let delta = delta.signum() * SCROLL_DELTA; + self.size = (self.size + delta).clamp(MIN_SIZE, MAX_SIZE); + + if self.size != old_size { + self.zero_pos += (delta / old_size) * (self.zero_pos - pos_s); + } + } + + /// Grid to screen coordinates transformation + pub fn grid_to_screen(&self, p_g: Vec2) -> Vec2 { + p_g * self.size + self.zero_pos + } + + /// Screen to grid coordinates transformation + pub fn screen_to_grid(&self, p_s: Vec2) -> Vec2 { + (p_s - self.zero_pos) / self.size + } +} diff --git a/src/ui/panes/pid_drawing_tool/symbols.rs b/src/ui/panes/pid_drawing_tool/symbols.rs new file mode 100644 index 0000000000000000000000000000000000000000..6bbbe4c4557b5afeab304c095ef99615ec251021 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols.rs @@ -0,0 +1,53 @@ +pub mod icons; +mod labels; + +use egui::{Theme, Ui}; +use enum_dispatch::enum_dispatch; +use glam::Vec2; +use icons::Icon; +use labels::Label; +use serde::{Deserialize, Serialize}; +use strum_macros::{Display, EnumIter}; + +use crate::mavlink::MavMessage; + +#[derive(Clone, Serialize, Deserialize, PartialEq, EnumIter, Display, Debug)] +#[enum_dispatch] +pub enum Symbol { + Icon(Icon), + Label(Label), +} + +impl Default for Symbol { + fn default() -> Self { + Symbol::Icon(Icon::default()) + } +} + +#[enum_dispatch(Symbol)] +pub trait SymbolBehavior { + /// Resets the subscriptions settings. + /// IMPORTANT: This method should be called every time the msg_id changes. + fn reset_subscriptions(&mut self); + + /// Updates the symbol based on the received message. + fn update(&mut self, message: &MavMessage, subscribed_msg_id: u32); + + /// Renders the symbol on the UI. + fn paint(&mut self, ui: &mut Ui, theme: Theme, pos: Vec2, size: f32, rotation: f32); + + /// Renders further elements related to the subscriptions settings + fn subscriptions_ui(&mut self, ui: &mut Ui, mavlink_id: u32); + + /// Anchor point in grid coordinates relative to the element's center + /// + /// These vectors include the current rotation of the element. + /// They are cached to avoid recomputing the rotation. + fn anchor_points(&self) -> Option<Vec<Vec2>>; + + /// Symbol size in grid coordinates + fn size(&self) -> Vec2; + + #[allow(unused_variables)] + fn context_menu(&mut self, ui: &mut Ui) {} +} diff --git a/src/ui/panes/pid_drawing_tool/symbols/icons.rs b/src/ui/panes/pid_drawing_tool/symbols/icons.rs new file mode 100644 index 0000000000000000000000000000000000000000..fd3378b72d9db4c98733ace0f8935dfcdeadeeac --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols/icons.rs @@ -0,0 +1,227 @@ +mod motor_valve; + +use egui::{ImageSource, Theme, Ui}; +use glam::Vec2; +use motor_valve::MotorValve; +use serde::{Deserialize, Serialize}; +use strum_macros::{Display, EnumIter}; + +use crate::{mavlink::MavMessage, ui::utils::glam_to_egui}; + +use super::SymbolBehavior; + +#[derive(Clone, Serialize, Deserialize, PartialEq, EnumIter, Display, Debug, Default)] +pub enum Icon { + #[default] + Arrow, + BurstDisk, + CheckValve, + FlexibleConnection, + ManualValve, + MotorValve(MotorValve), + PressureGauge, + PressureRegulator, + PressureTransducer, + QuickConnector, + ReliefValve, + ThreeWayValve, + Vessel, +} + +impl Icon { + pub fn get_image(&self, theme: Theme) -> ImageSource { + match (&self, theme) { + (Icon::Arrow, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/arrow.svg") + } + (Icon::Arrow, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/arrow.svg") + } + (Icon::BurstDisk, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/burst_disk.svg") + } + (Icon::BurstDisk, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/burst_disk.svg") + } + (Icon::ManualValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/manual_valve.svg") + } + (Icon::ManualValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/manual_valve.svg") + } + (Icon::CheckValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/check_valve.svg") + } + (Icon::CheckValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/check_valve.svg") + } + (Icon::ReliefValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/relief_valve.svg") + } + (Icon::ReliefValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/relief_valve.svg") + } + (Icon::MotorValve(state), Theme::Light) => match state.last_value { + None => { + egui::include_image!("../../../../../icons/pid_symbols/light/motor_valve.svg") + } + Some(true) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/motor_valve_green.svg" + ) + } + Some(false) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/motor_valve_red.svg" + ) + } + }, + (Icon::MotorValve(state), Theme::Dark) => match state.last_value { + None => { + egui::include_image!("../../../../../icons/pid_symbols/dark/motor_valve.svg") + } + Some(true) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/motor_valve_green.svg" + ) + } + Some(false) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/motor_valve_red.svg" + ) + } + }, + (Icon::ThreeWayValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/three_way_valve.svg") + } + (Icon::ThreeWayValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/three_way_valve.svg") + } + (Icon::PressureRegulator, Theme::Light) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/pressure_regulator.svg" + ) + } + (Icon::PressureRegulator, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/pressure_regulator.svg") + } + (Icon::QuickConnector, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/quick_connector.svg") + } + (Icon::QuickConnector, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/quick_connector.svg") + } + (Icon::PressureTransducer, Theme::Light) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/pressure_transducer.svg" + ) + } + (Icon::PressureTransducer, Theme::Dark) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/pressure_transducer.svg" + ) + } + (Icon::PressureGauge, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/pressure_gauge.svg") + } + (Icon::PressureGauge, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/pressure_gauge.svg") + } + (Icon::FlexibleConnection, Theme::Light) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/flexible_connection.svg" + ) + } + (Icon::FlexibleConnection, Theme::Dark) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/flexible_connection.svg" + ) + } + (Icon::Vessel, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/vessel.svg") + } + (Icon::Vessel, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/vessel.svg") + } + } + } +} + +impl SymbolBehavior for Icon { + fn update(&mut self, message: &MavMessage, subscribed_msg_id: u32) { + if let Icon::MotorValve(state) = self { + state.update(message, subscribed_msg_id) + } + } + + fn reset_subscriptions(&mut self) { + if let Icon::MotorValve(state) = self { + state.reset_subscriptions() + } + } + + fn paint(&mut self, ui: &mut Ui, theme: Theme, pos: glam::Vec2, size: f32, rotation: f32) { + let center = glam_to_egui(pos).to_pos2(); + let image_rect = egui::Rect::from_min_size(center, glam_to_egui(self.size() * size)); + egui::Image::new(self.get_image(theme)) + .rotate(rotation, egui::Vec2::splat(0.0)) + .paint_at(ui, image_rect); + } + + fn subscriptions_ui(&mut self, ui: &mut Ui, mavlink_id: u32) { + if let Icon::MotorValve(state) = self { + state.subscriptions_ui(ui, mavlink_id) + } + } + + fn context_menu(&mut self, ui: &mut Ui) { + if let Icon::MotorValve(state) = self { + if ui.button("Icon subscription settings…").clicked() { + state.is_subs_window_visible = true; + ui.close_menu(); + } + } + } + + fn anchor_points(&self) -> Option<Vec<glam::Vec2>> { + Some( + match self { + Icon::Arrow => vec![(0.0, 2.0), (4.0, 2.0)], + Icon::BurstDisk => vec![(0.0, 3.0), (4.0, 3.0)], + Icon::CheckValve => vec![(0.0, 2.5), (10.0, 2.5)], + Icon::FlexibleConnection => vec![(0.0, 3.0), (10.0, 3.0)], + Icon::ManualValve => vec![(0.0, 2.5), (10.0, 2.5)], + Icon::MotorValve(_) => vec![(0.0, 5.0), (10.0, 5.0)], + Icon::PressureGauge => vec![(3.5, 7.0)], + Icon::PressureRegulator => vec![(0.0, 7.0), (10.0, 7.0)], + Icon::PressureTransducer => vec![(3.5, 7.0)], + Icon::QuickConnector => vec![(0.0, 2.5), (6.0, 2.5)], + Icon::ReliefValve => vec![(3.0, 10.0)], + Icon::ThreeWayValve => vec![(0.0, 3.0), (10.0, 3.0), (5.0, 8.0)], + Icon::Vessel => vec![(0.0, 7.6), (8.2, 7.6), (4.1, 0.0), (4.1, 15.1)], + } + .iter() + .map(|&p| p.into()) + .collect(), + ) + } + + fn size(&self) -> Vec2 { + match self { + Icon::Arrow => (4.0, 4.0), + Icon::BurstDisk => (4.0, 6.0), + Icon::CheckValve => (10.0, 5.0), + Icon::FlexibleConnection => (10.0, 6.0), + Icon::ManualValve => (10.0, 5.0), + Icon::MotorValve(_) => (10.0, 8.0), + Icon::PressureGauge => (7.0, 7.0), + Icon::PressureRegulator => (10.0, 10.0), + Icon::PressureTransducer => (7.0, 7.0), + Icon::QuickConnector => (6.0, 5.0), + Icon::ReliefValve => (6.0, 10.0), + Icon::ThreeWayValve => (10.0, 8.0), + Icon::Vessel => (8.2, 15.2), + } + .into() + } +} diff --git a/src/ui/panes/pid_drawing_tool/symbols/icons/motor_valve.rs b/src/ui/panes/pid_drawing_tool/symbols/icons/motor_valve.rs new file mode 100644 index 0000000000000000000000000000000000000000..1dd94786496619faae503531191fe0eb9637b770 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols/icons/motor_valve.rs @@ -0,0 +1,85 @@ +use egui::{RichText, Ui, Window}; +use serde::{Deserialize, Serialize}; + +use crate::{ + MAVLINK_PROFILE, + error::ErrInstrument, + mavlink::{MavMessage, Message, reflection::IndexedField}, + ui::cache::ChangeTracker, +}; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Default, Debug)] +pub struct MotorValve { + subscribed_field: Option<IndexedField>, + + /// false = closed, true = open + #[serde(skip)] + pub last_value: Option<bool>, + #[serde(skip)] + pub is_subs_window_visible: bool, +} + +impl MotorValve { + pub fn update(&mut self, msg: &MavMessage, subscribed_msg_id: u32) { + // Reset field if msg_id has changed + if let Some(inner_field) = &self.subscribed_field { + if inner_field.msg_id() != subscribed_msg_id { + self.subscribed_field = None; + } + } + + if let Some(field) = &self.subscribed_field { + if msg.message_id() == subscribed_msg_id { + let value = field.extract_as_f64(msg).log_unwrap(); + self.last_value = Some(value != 0.0); + } + } + } + + pub fn reset_subscriptions(&mut self) { + self.subscribed_field = None; + self.last_value = None; + } + + pub fn subscriptions_ui(&mut self, ui: &mut Ui, mavlink_id: u32) { + let change_tracker = ChangeTracker::record_initial_state(&self.subscribed_field); + Window::new("Subscriptions") + .id(ui.auto_id_with("subs_settings")) + .auto_sized() + .collapsible(true) + .movable(true) + .open(&mut self.is_subs_window_visible) + .show(ui.ctx(), |ui| { + subscription_window(ui, mavlink_id, &mut self.subscribed_field) + }); + // reset last_value if the subscribed field has changed + if change_tracker.has_changed(&self.subscribed_field) { + self.last_value = None; + } + } +} + +fn subscription_window(ui: &mut Ui, msg_id: u32, field: &mut Option<IndexedField>) { + // Get all fields available for subscription + let fields = MAVLINK_PROFILE.get_all_state_fields(msg_id).log_unwrap(); + + // If no fields available for subscription + if fields.is_empty() { + ui.label( + RichText::new("No fields available for subscription") + .underline() + .strong(), + ); + return; + }; + + // Otherwise, select the first field available + let field = field.get_or_insert(fields[0].to_owned()); + egui::ComboBox::from_label("field") + .selected_text(&field.field().name) + .show_ui(ui, |ui| { + for msg in fields.iter() { + ui.selectable_value(field, msg.to_owned(), &msg.field().name); + } + }); +} diff --git a/src/ui/panes/pid_drawing_tool/symbols/labels.rs b/src/ui/panes/pid_drawing_tool/symbols/labels.rs new file mode 100644 index 0000000000000000000000000000000000000000..e20c55140a091fc3146f4d60a542fa33ae01c1d1 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols/labels.rs @@ -0,0 +1,153 @@ +use serde::{Deserialize, Serialize}; + +use egui::{ + Align2, Color32, CornerRadius, FontId, RichText, Stroke, StrokeKind, Theme, Ui, Window, +}; +use glam::Vec2; + +use crate::{ + MAVLINK_PROFILE, + error::ErrInstrument, + mavlink::{MavMessage, Message, reflection::IndexedField}, + ui::{ + cache::ChangeTracker, + utils::{egui_to_glam, glam_to_egui}, + }, +}; + +use super::SymbolBehavior; + +const FONT_SIZE: f32 = 2.0; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Label { + subscribed_field: Option<IndexedField>, + size: Vec2, + + #[serde(skip)] + last_value: Option<f32>, + #[serde(skip)] + is_subs_window_visible: bool, +} + +impl Default for Label { + fn default() -> Self { + Self { + subscribed_field: None, + last_value: Some(0.0), + size: Vec2::new(FONT_SIZE * 0.6 * 4.0, FONT_SIZE), + is_subs_window_visible: false, + } + } +} + +impl SymbolBehavior for Label { + fn update(&mut self, message: &MavMessage, subscribed_msg_id: u32) { + if let Some(subscribed_field) = &self.subscribed_field { + if message.message_id() == subscribed_msg_id { + let value = subscribed_field.extract_as_f64(message).log_unwrap(); + self.last_value = Some(value as f32); + } + } + } + + fn reset_subscriptions(&mut self) { + self.subscribed_field = None; + self.last_value = None; + } + + fn paint(&mut self, ui: &mut Ui, theme: Theme, pos: Vec2, size: f32, _: f32) { + let painter = ui.painter(); + let color = match theme { + Theme::Light => Color32::BLACK, + Theme::Dark => Color32::WHITE, + }; + + let unit = self + .subscribed_field + .as_ref() + .and_then(|f| f.field().unit.as_deref()) + .unwrap_or(""); + let text = match self.last_value { + Some(value) => format!("{:.2} {}", value, unit), + None => "N/A".to_string(), + }; + let text_rect = painter.text( + glam_to_egui(pos).to_pos2(), + Align2::LEFT_TOP, + text, + FontId::monospace(FONT_SIZE * size), + color, + ); + self.size = egui_to_glam(text_rect.size()) / size; + painter.rect( + egui::Rect::from_min_size( + glam_to_egui(pos).to_pos2(), + glam_to_egui(self.size()) * size, + ), + CornerRadius::ZERO, + Color32::TRANSPARENT, + Stroke::NONE, + StrokeKind::Middle, + ); + } + + fn subscriptions_ui(&mut self, ui: &mut Ui, mavlink_id: u32) { + let change_tracker = ChangeTracker::record_initial_state(&self.subscribed_field); + Window::new("Subscriptions") + .id(ui.auto_id_with("subs_settings")) + .auto_sized() + .collapsible(true) + .movable(true) + .open(&mut self.is_subs_window_visible) + .show(ui.ctx(), |ui| { + subscription_window(ui, mavlink_id, &mut self.subscribed_field) + }); + // reset last_value if the subscribed field has changed + if change_tracker.has_changed(&self.subscribed_field) { + self.last_value = None; + } + } + + fn context_menu(&mut self, ui: &mut Ui) { + if ui.button("Label subscription settings…").clicked() { + self.is_subs_window_visible = true; + ui.close_menu(); + } + } + + fn anchor_points(&self) -> Option<Vec<Vec2>> { + None + } + + fn size(&self) -> Vec2 { + self.size + } +} + +fn subscription_window(ui: &mut Ui, msg_id: u32, field: &mut Option<IndexedField>) { + // Get all fields available for subscription + let fields = MAVLINK_PROFILE + .get_plottable_fields(msg_id) + .log_expect("Invalid message id"); + + // If no fields available for subscription + if fields.is_empty() { + ui.label( + RichText::new("No fields available for subscription") + .underline() + .strong(), + ); + return; + } + + // Otherwise, select the first field available + let field = field.get_or_insert(fields[0].to_owned()); + egui::ComboBox::from_label("field") + .selected_text(&field.field().name) + .show_ui(ui, |ui| { + for msg in fields.iter() { + ui.selectable_value(field, msg.to_owned(), &msg.field().name); + } + }); +} diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 7ad863175181d3b06766858c500a07707bcefc79..0c2ae3f7257254b414d9974507003ee1443a30bf 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -42,3 +42,15 @@ pub fn vertically_centered( .response } } + +#[inline(always)] +pub fn egui_to_glam(p: egui::Vec2) -> glam::Vec2 { + let p: mint::Vector2<f32> = p.into(); + glam::Vec2::from(p) +} + +#[inline(always)] +pub fn glam_to_egui(p: glam::Vec2) -> egui::Vec2 { + let p: mint::Vector2<f32> = p.into(); + egui::Vec2::from(p) +}