diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1149e..dee7eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to the `Serial Monitor` crate will be documented in this fil # Unreleased 0.3.x +* implement `egui-file-dialog` and the feature to open `.csv` files. + # 0.3.2 * fixed display of only one dataset bug diff --git a/Cargo.lock b/Cargo.lock index 3c62796..a371f9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,28 +282,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "ashpd" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" -dependencies = [ - "async-fs 2.1.2", - "async-net", - "enumflags2", - "futures-channel", - "futures-util", - "rand", - "raw-window-handle", - "serde", - "serde_repr", - "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus 5.2.0", -] - [[package]] name = "async-broadcast" version = "0.5.1" @@ -433,17 +411,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io 2.4.0", - "blocking", - "futures-lite 2.5.0", -] - [[package]] name = "async-process" version = "1.8.1" @@ -696,6 +663,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "bytemuck" version = "1.21.0" @@ -760,6 +733,37 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + [[package]] name = "cc" version = "1.2.5" @@ -857,6 +861,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.7" @@ -1028,6 +1038,12 @@ dependencies = [ "syn 1.0.109", ] +[[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" @@ -1102,6 +1118,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1149,6 +1186,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecolor" version = "0.30.0" @@ -1216,6 +1259,22 @@ dependencies = [ "serde", ] +[[package]] +name = "egui-file-dialog" +version = "0.8.0" +source = "git+https://github.com/hacknus/egui-file-dialog?branch=sort_by_metadata#6685bf25dbc2b29f2472d588539f7e5dba420a20" +dependencies = [ + "chrono", + "directories", + "dunce", + "egui", + "egui_extras", + "image-meta", + "indexmap 2.7.0", + "serde", + "sysinfo", +] + [[package]] name = "egui-phosphor" version = "0.8.0" @@ -1274,6 +1333,23 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_extras" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7a8198c088b1007108cb2d403bc99a5e370999b200db4f14559610d7330126" +dependencies = [ + "ahash", + "egui", + "ehttp", + "enum-map", + "image", + "log", + "mime_guess2", + "profiling", + "resvg", +] + [[package]] name = "egui_glow" version = "0.30.0" @@ -1315,6 +1391,20 @@ dependencies = [ "emath", ] +[[package]] +name = "ehttp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876" +dependencies = [ + "document-features", + "js-sys", + "ureq", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "either" version = "1.13.0" @@ -1337,6 +1427,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -1410,6 +1521,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "error-code" version = "3.3.1" @@ -1488,6 +1608,12 @@ 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 = "fnv" version = "1.0.7" @@ -1536,15 +1662,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - [[package]] name = "futures-core" version = "0.3.31" @@ -1656,6 +1773,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -1819,6 +1946,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -2028,10 +2161,30 @@ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", "byteorder-lite", + "color_quant", + "gif", "num-traits", "png", ] +[[package]] +name = "image-meta" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5831d8b072b2162a3b4f143081b6dea66175e0d84b6fd5adaa9dc615c31ceaa" +dependencies = [ + "byteorder", + "skeptic", + "strum", + "thiserror", +] + +[[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" @@ -2059,6 +2212,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -2170,6 +2324,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" @@ -2335,6 +2498,22 @@ dependencies = [ "paste", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2715,6 +2894,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.48" @@ -2790,6 +2975,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.7" @@ -2896,12 +3087,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "pollster" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -2962,6 +3147,17 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.6.0", + "memchr", + "unicase", +] + [[package]] name = "quick-xml" version = "0.30.0" @@ -3035,6 +3231,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" @@ -3053,6 +3255,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.11.1" @@ -3089,25 +3302,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] -name = "rfd" -version = "0.15.1" +name = "resvg" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" dependencies = [ - "ashpd", - "block2", - "js-sys", "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "pollster", - "raw-window-handle", - "urlencoding", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.48.0", + "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" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -3122,6 +3351,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-hash" version = "1.1.0" @@ -3164,6 +3399,44 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" @@ -3209,6 +3482,9 @@ name = "semver" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -3268,8 +3544,10 @@ version = "0.3.2" dependencies = [ "csv", "eframe", + "egui-file-dialog", "egui-phosphor", "egui-theme-switch", + "egui_extras", "egui_logger", "egui_plot", "image", @@ -3277,7 +3555,6 @@ dependencies = [ "log", "preferences", "regex", - "rfd", "serde", "serialport", ] @@ -3334,6 +3611,36 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "slab" version = "0.4.9" @@ -3413,6 +3720,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -3439,6 +3752,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 = "strsim" @@ -3446,6 +3762,44 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -3479,6 +3833,17 @@ dependencies = [ "syn 2.0.91", ] +[[package]] +name = "sysinfo" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948512566b1895f93b1592c7574baeb2de842f224f2aab158799ecadb8ebbb46" +dependencies = [ + "core-foundation-sys", + "libc", + "windows 0.57.0", +] + [[package]] name = "tempfile" version = "3.14.0" @@ -3563,6 +3928,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png", "tiny-skia-path", ] @@ -3721,6 +4087,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -3745,6 +4117,28 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.4" @@ -3754,14 +4148,51 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] -name = "urlencoding" -version = "2.1.3" +name = "usvg" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64 0.21.7", + "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" @@ -4017,6 +4448,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "wgpu" version = "23.0.1" @@ -4170,6 +4616,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" @@ -4189,19 +4645,42 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -4213,6 +4692,17 @@ dependencies = [ "syn 2.0.91", ] +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -4224,6 +4714,15 @@ dependencies = [ "syn 2.0.91", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.2.0" @@ -4239,7 +4738,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] @@ -4618,6 +5117,12 @@ version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" +[[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" @@ -4721,42 +5226,6 @@ dependencies = [ "zvariant 4.2.0", ] -[[package]] -name = "zbus" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb67eadba43784b6fb14857eba0d8fc518686d3ee537066eb6086dc318e2c8a1" -dependencies = [ - "async-broadcast 0.7.1", - "async-executor", - "async-fs 2.1.2", - "async-io 2.4.0", - "async-lock 3.4.0", - "async-process 2.3.0", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener 5.3.1", - "futures-core", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "serde", - "serde_repr", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.59.0", - "winnow 0.6.20", - "xdg-home", - "zbus_macros 5.2.0", - "zbus_names 4.1.0", - "zvariant 5.1.0", -] - [[package]] name = "zbus-lockstep" version = "0.4.4" @@ -4808,21 +5277,6 @@ dependencies = [ "zvariant_utils 2.1.0", ] -[[package]] -name = "zbus_macros" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d49ebc960ceb660f2abe40a5904da975de6986f2af0d7884b39eec6528c57" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.91", - "zbus_names 4.1.0", - "zvariant 5.1.0", - "zvariant_utils 3.0.2", -] - [[package]] name = "zbus_names" version = "2.6.1" @@ -4845,18 +5299,6 @@ dependencies = [ "zvariant 4.2.0", ] -[[package]] -name = "zbus_names" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" -dependencies = [ - "serde", - "static_assertions", - "winnow 0.6.20", - "zvariant 5.1.0", -] - [[package]] name = "zbus_xml" version = "4.0.0" @@ -4912,6 +5354,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" @@ -4961,22 +5409,6 @@ dependencies = [ "zvariant_derive 4.2.0", ] -[[package]] -name = "zvariant" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "url", - "winnow 0.6.20", - "zvariant_derive 5.1.0", - "zvariant_utils 3.0.2", -] - [[package]] name = "zvariant_derive" version = "3.15.2" @@ -5003,19 +5435,6 @@ dependencies = [ "zvariant_utils 2.1.0", ] -[[package]] -name = "zvariant_derive" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.91", - "zvariant_utils 3.0.2", -] - [[package]] name = "zvariant_utils" version = "1.0.1" @@ -5037,17 +5456,3 @@ dependencies = [ "quote", "syn 2.0.91", ] - -[[package]] -name = "zvariant_utils" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "static_assertions", - "syn 2.0.91", - "winnow 0.6.20", -] diff --git a/Cargo.toml b/Cargo.toml index e13d616..b33452d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,16 +10,17 @@ homepage = "https://github.com/hacknus/serial-monitor-rust" [dependencies] csv = "1.3" -eframe = { version = "0.30.0", features = ["persistence"] } +eframe = { version = "0.30.0", features = ["persistence", "wayland", "x11"] } egui_plot = "0.30.0" +egui_extras = { version = "0.30.0", features = ["all_loaders"] } egui-phosphor = { version = "0.8.0" } egui-theme-switch = { version = "0.2.3", default-features = true } egui_logger = { git = "https://github.com/hacknus/egui_logger" } +egui-file-dialog = { version = "0.8.0", git = "https://github.com/hacknus/egui-file-dialog", branch = "sort_by_metadata", features = ["information_view"] } image = { version = "0.25", default-features = false, features = ["png"] } keepawake = { version = "0.5.1" } preferences = { version = "2.0.0" } regex = "1" -rfd = "0.15.0" serde = { version = "1.0", features = ["derive"] } serialport = { version = "4.6.1", features = ["serde"] } log = "0.4.22" diff --git a/src/data.rs b/src/data.rs index aa0ddc7..20cffe4 100644 --- a/src/data.rs +++ b/src/data.rs @@ -25,8 +25,8 @@ pub fn get_epoch_ms() -> u128 { #[derive(Clone, Debug)] pub struct Packet { - pub relative_time: u128, - pub absolute_time: u128, + pub relative_time: f64, + pub absolute_time: f64, pub direction: SerialDirection, pub payload: String, } @@ -34,8 +34,8 @@ pub struct Packet { impl Default for Packet { fn default() -> Packet { Packet { - relative_time: 0, - absolute_time: get_epoch_ms(), + relative_time: 0.0, + absolute_time: get_epoch_ms() as f64, direction: SerialDirection::Send, payload: "".to_string(), } @@ -44,10 +44,11 @@ impl Default for Packet { #[derive(Clone, Debug)] pub struct DataContainer { - pub time: Vec, - pub absolute_time: Vec, + pub time: Vec, + pub absolute_time: Vec, pub dataset: Vec>, pub raw_traffic: Vec, + pub loaded_from_file: bool, } impl Default for DataContainer { @@ -57,6 +58,7 @@ impl Default for DataContainer { absolute_time: vec![], dataset: vec![vec![]], raw_traffic: vec![], + loaded_from_file: false, } } } diff --git a/src/gui.rs b/src/gui.rs index d29ad7b..1640808 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -2,7 +2,7 @@ use core::f32; use std::cmp::max; use std::ops::RangeInclusive; use std::path::PathBuf; -use std::sync::mpsc::Sender; +use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, RwLock}; use std::time::Duration; @@ -13,6 +13,8 @@ use eframe::egui::{ }; use eframe::{egui, Storage}; use egui::ThemePreference; +use egui_file_dialog::information_panel::InformationPanel; +use egui_file_dialog::FileDialog; use egui_plot::{log_grid_spacer, GridMark, Legend, Line, Plot, PlotPoint, PlotPoints}; use egui_theme_switch::ThemeSwitch; use preferences::Preferences; @@ -45,6 +47,13 @@ const SAVE_PLOT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new( const CLEAR_PLOT_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(egui::Modifiers::COMMAND, egui::Key::X); +#[derive(Clone)] +pub enum FileDialogState { + Open, + Save, + SavePlot, + None, +} #[derive(PartialEq)] pub enum WindowFeedback { None, @@ -85,7 +94,7 @@ pub fn load_gui_settings() -> GuiSettingsContainer { let gui_settings = GuiSettingsContainer::default(); // save default settings if gui_settings.save(&APP_INFO, PREFS_KEY).is_err() { - println!("failed to save gui_settings"); + log::error!("failed to save gui_settings"); } gui_settings }) @@ -108,12 +117,18 @@ pub struct MyApp { picked_path: PathBuf, plot_location: Option, data: DataContainer, + file_dialog_state: FileDialogState, + file_dialog: FileDialog, + information_panel: InformationPanel, + file_opened: bool, gui_conf: GuiSettingsContainer, device_lock: Arc>, devices_lock: Arc>>, connected_lock: Arc>, data_lock: Arc>, save_tx: Sender, + load_tx: Sender, + load_names_rx: Receiver>, send_tx: Sender, clear_tx: Sender, history: Vec, @@ -134,6 +149,7 @@ pub struct MyApp { #[allow(clippy::too_many_arguments)] impl MyApp { pub fn new( + cc: &eframe::CreationContext, data_lock: Arc>, device_lock: Arc>, devices_lock: Arc>>, @@ -141,15 +157,59 @@ impl MyApp { connected_lock: Arc>, gui_conf: GuiSettingsContainer, save_tx: Sender, + load_tx: Sender, + load_names_rx: Receiver>, send_tx: Sender, clear_tx: Sender, ) -> Self { + let mut file_dialog = FileDialog::default() + //.initial_directory(PathBuf::from("/path/to/app")) + .default_file_name("measurement.csv") + .default_size([600.0, 400.0]) + // .add_quick_access("Project", |s| { + // s.add_path("☆ Examples", "examples"); + // s.add_path("📷 Media", "media"); + // s.add_path("📂 Source", "src"); + // }) + .set_file_icon( + "🖹", + Arc::new(|path| path.extension().unwrap_or_default().to_ascii_lowercase() == "md"), + ) + .set_file_icon( + "", + Arc::new(|path| { + path.file_name().unwrap_or_default().to_ascii_lowercase() == ".gitignore" + }), + ) + .add_file_filter( + "CSV files", + Arc::new(|p| p.extension().unwrap_or_default().to_ascii_lowercase() == "csv"), + ); + // Load the persistent data of the file dialog. + // Alternatively, you can also use the `FileDialog::storage` builder method. + if let Some(storage) = cc.storage { + *file_dialog.storage_mut() = + eframe::get_value(storage, "file_dialog_storage").unwrap_or_default() + } + Self { connected_to_device: false, picked_path: PathBuf::new(), device: "".to_string(), old_device: "".to_string(), data: DataContainer::default(), + file_dialog_state: FileDialogState::None, + file_dialog, + information_panel: InformationPanel::default().add_file_preview("csv", |ui, item| { + ui.label("CSV preview:"); + if let Some(mut content) = item.content() { + egui::ScrollArea::vertical() + .max_height(ui.available_height()) + .show(ui, |ui| { + ui.add(egui::TextEdit::multiline(&mut content).code_editor()); + }); + } + }), connected_lock, device_lock, devices_lock, @@ -158,6 +218,8 @@ impl MyApp { gui_conf, data_lock, save_tx, + load_tx, + load_names_rx, send_tx, clear_tx, plotting_range: usize::MAX, @@ -177,6 +239,7 @@ impl MyApp { show_warning_window: WindowFeedback::None, init: false, show_color_window: ColorWindow::NoShow, + file_opened: false, } } @@ -257,8 +320,20 @@ impl MyApp { if let Ok(read_guard) = self.data_lock.read() { self.data = read_guard.clone(); } + + if self.data.loaded_from_file && self.file_opened { + if let Ok(labels) = + self.load_names_rx.recv_timeout(Duration::from_millis(10)) + { + self.labels = labels; + self.colors = (0..max(self.labels.len(), 1)) + .map(|i| COLORS[i % COLORS.len()]) + .collect(); + self.color_vals = (0..max(self.labels.len(), 1)).map(|_| 0.0).collect(); + } + } if self.serial_devices.number_of_plots[self.device_idx] > 0 { - if self.data.dataset.len() != self.labels.len() { + if self.data.dataset.len() != self.labels.len() && !self.file_opened { self.labels = (0..max(self.data.dataset.len(), 1)) .map(|i| format!("Column {i}")) .collect(); @@ -468,6 +543,9 @@ impl MyApp { let old_name = self.device.clone(); ui.horizontal(|ui| { + if self.file_opened { + ui.disable(); + } let dev_text = self.device.replace("/dev/tty.", ""); ui.horizontal(|ui| { if self.connected_to_device { @@ -720,8 +798,37 @@ impl MyApp { ); }); }); + ui.add_space(5.0); + ui.horizontal(|ui| { + if self.connected_to_device { + ui.disable(); + } + if ui + .button(egui::RichText::new(format!( + "{} Open file", + egui_phosphor::regular::FOLDER_OPEN + ))) + .on_hover_text("Load data from .csv") + .clicked() + { + self.file_dialog_state = FileDialogState::Open; + self.file_dialog.pick_file(); + } + if self.file_opened + && ui + .button(egui::RichText::new( + egui_phosphor::regular::X_SQUARE.to_string(), + )) + .on_hover_text("Close file.") + .clicked() + { + self.file_opened = false; + let _ = self.load_tx.send(PathBuf::new()); + self.file_dialog_state = FileDialogState::None; + } + }); } - fn draw_export_settings(&mut self, ctx: &egui::Context, ui: &mut Ui) { + fn draw_export_settings(&mut self, _ctx: &egui::Context, ui: &mut Ui) { egui::Grid::new("export_settings") .num_columns(2) .spacing(Vec2 { x: 10.0, y: 10.0 }) @@ -736,19 +843,9 @@ impl MyApp { .clicked() || ui.input_mut(|i| i.consume_shortcut(&SAVE_FILE_SHORTCUT)) { - if let Some(path) = rfd::FileDialog::new().save_file() { - self.picked_path = path; - self.picked_path.set_extension("csv"); - if let Err(e) = self.save_tx.send(FileOptions { - file_path: self.picked_path.clone(), - save_absolute_time: self.gui_conf.save_absolute_time, - save_raw_traffic: self.save_raw, - names: self.serial_devices.labels[self.device_idx].clone(), - }) { - log::error!("save_tx thread send failed: {:?}", e); - } - } - }; + self.file_dialog_state = FileDialogState::Save; + self.file_dialog.save_file(); + } if ui .button(egui::RichText::new(format!( @@ -759,7 +856,8 @@ impl MyApp { .clicked() || ui.input_mut(|i| i.consume_shortcut(&SAVE_PLOT_SHORTCUT)) { - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot(Default::default())); + self.file_dialog_state = FileDialogState::SavePlot; + self.file_dialog.save_file(); } ui.end_row(); ui.label("Save Raw Traffic"); @@ -1066,6 +1164,53 @@ impl MyApp { ui.collapsing("Debug logs:", |ui| { egui_logger::logger_ui().show(ui); }); + + match self.file_dialog_state { + FileDialogState::Open => { + if let Some(path) = self + .file_dialog + .update_with_right_panel_ui(ctx, &mut |ui, dia| { + self.information_panel.ui(ui, dia); + }) + .picked() + { + self.picked_path = path.to_path_buf(); + self.file_opened = true; + self.file_dialog_state = FileDialogState::None; + if let Err(e) = self.load_tx.send(self.picked_path.clone()) { + log::error!("load_tx thread send failed: {:?}", e); + } + } + } + FileDialogState::SavePlot => { + if let Some(path) = self.file_dialog.update(ctx).picked() { + self.picked_path = path.to_path_buf(); + self.file_dialog_state = FileDialogState::None; + self.picked_path.set_extension("png"); + + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot( + Default::default(), + )); + } + } + FileDialogState::Save => { + if let Some(path) = self.file_dialog.update(ctx).picked() { + self.picked_path = path.to_path_buf(); + self.file_dialog_state = FileDialogState::None; + self.picked_path.set_extension("csv"); + + if let Err(e) = self.save_tx.send(FileOptions { + file_path: self.picked_path.clone(), + save_absolute_time: self.gui_conf.save_absolute_time, + save_raw_traffic: self.save_raw, + names: self.labels.clone(), + }) { + log::error!("save_tx thread send failed: {:?}", e); + } + } + } + FileDialogState::None => {} + } }); }); } @@ -1110,32 +1255,28 @@ impl eframe::App for MyApp { }); if let (Some(screenshot), Some(plot_location)) = (screenshot, self.plot_location) { - if let Some(mut path) = rfd::FileDialog::new().save_file() { - path.set_extension("png"); - - // for a full size application, we should put this in a different thread, - // so that the GUI doesn't lag during saving - - let pixels_per_point = ctx.pixels_per_point(); - let plot = screenshot.region(&plot_location, Some(pixels_per_point)); - // save the plot to png - image::save_buffer( - &path, - plot.as_raw(), - plot.width() as u32, - plot.height() as u32, - image::ColorType::Rgba8, - ) - .unwrap(); - println!("Image saved to {path:?}."); - } + // for a full size application, we should put this in a different thread, + // so that the GUI doesn't lag during saving + + let pixels_per_point = ctx.pixels_per_point(); + let plot = screenshot.region(&plot_location, Some(pixels_per_point)); + // save the plot to png + image::save_buffer( + &self.picked_path, + plot.as_raw(), + plot.width() as u32, + plot.height() as u32, + image::ColorType::Rgba8, + ) + .unwrap(); + log::info!("Image saved to {:?}.", self.picked_path); } } fn save(&mut self, _storage: &mut dyn Storage) { save_serial_settings(&self.serial_devices); if let Err(err) = self.gui_conf.save(&APP_INFO, PREFS_KEY) { - println!("gui settings save failed: {:?}", err); + log::error!("gui settings save failed: {:?}", err); } } } diff --git a/src/io.rs b/src/io.rs index 15fbddc..46e25a9 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,7 +1,7 @@ use std::error::Error; use std::path::PathBuf; -use csv::WriterBuilder; +use csv::{ReaderBuilder, WriterBuilder}; use crate::DataContainer; @@ -14,6 +14,59 @@ pub struct FileOptions { pub names: Vec, } +pub fn open_from_csv( + data: &mut DataContainer, + csv_options: &mut FileOptions, +) -> Result<(), Box> { + let mut rdr = ReaderBuilder::new() + .has_headers(true) + .from_path(&csv_options.file_path)?; + + csv_options.names = rdr + .headers() + .unwrap() + .into_iter() + .skip(1) + .map(|s| s.to_string()) + .collect::>(); + + // Clear any existing data in the DataContainer + data.absolute_time.clear(); + data.time.clear(); + data.dataset = vec![vec![]; csv_options.names.len()]; + + // Read and parse each record in the CSV + for result in rdr.records() { + let record = result?; + + // Ensure the record has the correct number of fields + if record.len() != csv_options.names.len() + 1 { + return Err("CSV record does not match the expected number of columns".into()); + } + + // Parse the time field (first column) + let time_value = record.get(0).unwrap(); + if csv_options.save_absolute_time { + data.absolute_time.push(time_value.parse()?); + } else { + data.time.push(time_value.parse()?); + } + + // Parse the remaining columns and populate the dataset + for (i, value) in record.iter().skip(1).enumerate() { + if let Some(dataset_column) = data.dataset.get_mut(i) { + dataset_column.push(value.parse()?); + } else { + return Err("Unexpected number of data columns in the CSV".into()); + } + } + } + + data.loaded_from_file = true; + + Ok(()) +} + pub fn save_to_csv(data: &DataContainer, csv_options: &FileOptions) -> Result<(), Box> { let mut wtr = WriterBuilder::new() .has_headers(false) diff --git a/src/main.rs b/src/main.rs index 49c3647..ae98190 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ extern crate preferences; extern crate serde; use std::cmp::max; +use std::path::PathBuf; use std::sync::mpsc::{Receiver, Sender}; use std::sync::{mpsc, Arc, RwLock}; use std::thread; @@ -13,7 +14,7 @@ use std::time::Duration; use crate::data::{DataContainer, Packet}; use crate::gui::{load_gui_settings, MyApp, RIGHT_PANEL_WIDTH}; -use crate::io::{save_to_csv, FileOptions}; +use crate::io::{open_from_csv, save_to_csv, FileOptions}; use crate::serial::{load_serial_settings, serial_thread, Device}; use eframe::egui::{vec2, ViewportBuilder, Visuals}; use eframe::{egui, icon_data}; @@ -51,11 +52,16 @@ fn main_thread( data_lock: Arc>, raw_data_rx: Receiver, save_rx: Receiver, + load_rx: Receiver, + load_names_tx: Sender>, clear_rx: Receiver, ) { // reads data from mutex, samples and saves if needed let mut data = DataContainer::default(); let mut failed_format_counter = 0; + + let mut file_opened = false; + loop { if let Ok(cl) = clear_rx.recv_timeout(Duration::from_millis(1)) { if cl { @@ -63,39 +69,78 @@ fn main_thread( failed_format_counter = 0; } } - - if let Ok(packet) = raw_data_rx.recv_timeout(Duration::from_millis(1)) { - if !packet.payload.is_empty() { - sync_tx.send(true).expect("unable to send sync tx"); - data.raw_traffic.push(packet.clone()); - let split_data = split(&packet.payload); - if data.dataset.is_empty() || failed_format_counter > 10 { - // resetting dataset - data.dataset = vec![vec![]; max(split_data.len(), 1)]; - failed_format_counter = 0; - // println!("resetting dataset. split length = {}, length data.dataset = {}", split_data.len(), data.dataset.len()); - } else if split_data.len() == data.dataset.len() { - // appending data - for (i, set) in data.dataset.iter_mut().enumerate() { - set.push(split_data[i]); - failed_format_counter = 0; - } - data.time.push(packet.relative_time); - data.absolute_time.push(packet.absolute_time); - if data.time.len() != data.dataset[0].len() { + if !file_opened { + if let Ok(packet) = raw_data_rx.recv_timeout(Duration::from_millis(1)) { + data.loaded_from_file = false; + if !packet.payload.is_empty() { + sync_tx.send(true).expect("unable to send sync tx"); + data.raw_traffic.push(packet.clone()); + let split_data = split(&packet.payload); + if data.dataset.is_empty() || failed_format_counter > 10 { // resetting dataset - data.time = vec![]; data.dataset = vec![vec![]; max(split_data.len(), 1)]; + failed_format_counter = 0; + // log::error!("resetting dataset. split length = {}, length data.dataset = {}", split_data.len(), data.dataset.len()); + } else if split_data.len() == data.dataset.len() { + // appending data + for (i, set) in data.dataset.iter_mut().enumerate() { + set.push(split_data[i]); + failed_format_counter = 0; + } + data.time.push(packet.relative_time); + data.absolute_time.push(packet.absolute_time); + if data.time.len() != data.dataset[0].len() { + // resetting dataset + data.time = vec![]; + data.dataset = vec![vec![]; max(split_data.len(), 1)]; + } + } else { + // not same length + failed_format_counter += 1; + // log::error!("not same length in main! length split_data = {}, length data.dataset = {}", split_data.len(), data.dataset.len()) } - } else { - // not same length - failed_format_counter += 1; - // println!("not same length in main! length split_data = {}, length data.dataset = {}", split_data.len(), data.dataset.len()) } - if let Ok(mut write_guard) = data_lock.write() { - *write_guard = data.clone(); + } + } + if let Ok(fp) = load_rx.recv_timeout(Duration::from_millis(10)) { + if let Some(file_ending) = fp.extension() { + match file_ending.to_str().unwrap() { + "csv" => { + file_opened = true; + let mut file_options = FileOptions { + file_path: fp.clone(), + save_absolute_time: false, + save_raw_traffic: false, + names: vec![], + }; + match open_from_csv(&mut data, &mut file_options) { + Ok(_) => { + log::info!("opened {:?}", fp); + load_names_tx + .send(file_options.names) + .expect("unable to send names on channel after loading"); + } + Err(err) => { + file_opened = false; + log::error!("failed opening {:?}: {:?}", fp, err); + } + }; + } + _ => { + file_opened = false; + log::error!("file not supported: {:?} \n Close the file to connect to a spectrometer or open another file.", fp); + continue; + } } + } else { + file_opened = false; } + } else { + file_opened = false; + } + + if let Ok(mut write_guard) = data_lock.write() { + *write_guard = data.clone(); } if let Ok(csv_options) = save_rx.recv_timeout(Duration::from_millis(1)) { @@ -129,6 +174,9 @@ fn main() { let connected_lock = Arc::new(RwLock::new(false)); let (save_tx, save_rx): (Sender, Receiver) = mpsc::channel(); + let (load_tx, load_rx): (Sender, Receiver) = mpsc::channel(); + let (loaded_names_tx, loaded_names_rx): (Sender>, Receiver>) = + mpsc::channel(); let (send_tx, send_rx): (Sender, Receiver) = mpsc::channel(); let (clear_tx, clear_rx): (Sender, Receiver) = mpsc::channel(); let (raw_data_tx, raw_data_rx): (Sender, Receiver) = mpsc::channel(); @@ -138,7 +186,6 @@ fn main() { let serial_devices_lock = devices_lock.clone(); let serial_connected_lock = connected_lock.clone(); - println!("starting connection thread.."); let _serial_thread_handler = thread::spawn(|| { serial_thread( send_rx, @@ -151,9 +198,16 @@ fn main() { let main_data_lock = data_lock.clone(); - println!("starting main thread.."); let _main_thread_handler = thread::spawn(|| { - main_thread(sync_tx, main_data_lock, raw_data_rx, save_rx, clear_rx); + main_thread( + sync_tx, + main_data_lock, + raw_data_rx, + save_rx, + load_rx, + loaded_names_tx, + clear_rx, + ); }); let options = eframe::NativeOptions { @@ -175,13 +229,14 @@ fn main() { if let Err(e) = eframe::run_native( "Serial Monitor", options, - Box::new(|_cc| { + Box::new(|ctx| { let mut fonts = egui::FontDefinitions::default(); egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular); - _cc.egui_ctx.set_fonts(fonts); - _cc.egui_ctx.set_visuals(Visuals::dark()); + ctx.egui_ctx.set_fonts(fonts); + ctx.egui_ctx.set_visuals(Visuals::dark()); + egui_extras::install_image_loaders(&ctx.egui_ctx); - let repaint_signal = _cc.egui_ctx.clone(); + let repaint_signal = ctx.egui_ctx.clone(); thread::spawn(move || loop { if sync_rx.recv().is_ok() { repaint_signal.request_repaint(); @@ -189,6 +244,7 @@ fn main() { }); Ok(Box::new(MyApp::new( + ctx, gui_data_lock, gui_device_lock, gui_devices_lock, @@ -196,11 +252,13 @@ fn main() { gui_connected_lock, gui_settings, save_tx, + load_tx, + loaded_names_rx, send_tx, clear_tx, ))) }), ) { - println!("error: {e:?}"); + log::error!("{e:?}"); } } diff --git a/src/serial.rs b/src/serial.rs index ce5a74e..ed4ee1b 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -47,14 +47,14 @@ pub fn load_serial_settings() -> SerialDevices { pub fn save_serial_settings(serial_configs: &SerialDevices) { if serial_configs.save(&APP_INFO, PREFS_KEY_SERIAL).is_err() { - println!("failed to save gui_settings"); + log::error!("failed to save gui_settings"); } } pub fn clear_serial_settings() { let serial_configs = SerialDevices::default(); if serial_configs.save(&APP_INFO, PREFS_KEY_SERIAL).is_err() { - println!("failed to clear gui_settings"); + log::error!("failed to clear gui_settings"); } } @@ -247,13 +247,13 @@ fn perform_writes( ) { if let Ok(cmd) = send_rx.recv_timeout(Duration::from_millis(1)) { if let Err(e) = serial_write(port, cmd.as_bytes()) { - println!("Error sending command: {e}"); + log::error!("Error sending command: {e}"); return; } let packet = Packet { - relative_time: Instant::now().duration_since(t_zero).as_millis(), - absolute_time: get_epoch_ms(), + relative_time: Instant::now().duration_since(t_zero).as_millis() as f64, + absolute_time: get_epoch_ms() as f64, direction: SerialDirection::Send, payload: cmd, }; @@ -274,8 +274,8 @@ fn perform_reads( let delimiter = if buf.contains("\r\n") { "\r\n" } else { "\0\0" }; buf.split_terminator(delimiter).for_each(|s| { let packet = Packet { - relative_time: Instant::now().duration_since(t_zero).as_millis(), - absolute_time: get_epoch_ms(), + relative_time: Instant::now().duration_since(t_zero).as_millis() as f64, + absolute_time: get_epoch_ms() as f64, direction: SerialDirection::Receive, payload: s.to_owned(), }; @@ -285,7 +285,7 @@ fn perform_reads( // Timeout is ok, just means there is no data to read Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => {} Err(e) => { - println!("Error reading: {:?}", e); + log::error!("Error reading: {:?}", e); } } }