From cae5cc20e4011f706da82949bb66ac5a22fe3c19 Mon Sep 17 00:00:00 2001 From: hacknus Date: Mon, 7 Oct 2024 18:16:02 +0200 Subject: [PATCH] implement working color picker for all curves --- Cargo.lock | 186 ++++++++++++++++-------------------- Cargo.toml | 2 +- src/color_picker.rs | 227 ++++++++++++++++++++++++++++++++++++++++++++ src/data.rs | 4 +- src/gui.rs | 84 ++++++++++++---- src/io.rs | 3 +- src/main.rs | 25 +---- src/serial.rs | 13 ++- 8 files changed, 394 insertions(+), 150 deletions(-) create mode 100644 src/color_picker.rs diff --git a/Cargo.lock b/Cargo.lock index e479b74..7993778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093" +checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a" dependencies = [ "async-fs 2.1.2", "async-net", @@ -286,9 +286,13 @@ dependencies = [ "futures-channel", "futures-util", "rand", + "raw-window-handle", "serde", "serde_repr", "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", "zbus 4.4.0", ] @@ -692,9 +696,9 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", @@ -747,9 +751,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.22" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -1142,9 +1146,9 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5629649a8ae57c73f175f4a96419905a8102cfbfcbce96ea25a826bbf468e990" +checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" dependencies = [ "bytemuck", "emath", @@ -1153,9 +1157,9 @@ dependencies = [ [[package]] name = "eframe" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712634e63d86f5eb8e30f880bc4803b79dcc82539aec1a28fde86ed839daed24" +checksum = "8ac2645a9bf4826eb4e91488b1f17b8eaddeef09396706b2f14066461338e24f" dependencies = [ "ahash", "bytemuck", @@ -1191,9 +1195,9 @@ dependencies = [ [[package]] name = "egui" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bab3b3572566257a497b5f87d2cccaf7f7f122d4b8b620cba0493becc7955e" +checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" dependencies = [ "accesskit", "ahash", @@ -1224,9 +1228,9 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba69456826abf572ed95b658b265c01f07a23d615d6f029eedc9ee5f13ddf788" +checksum = "d00fd5d06d8405397e64a928fa0ef3934b3c30273ea7603e3dc4627b1f7a1a82" dependencies = [ "ahash", "bytemuck", @@ -1243,9 +1247,9 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642c749bf221b5a3ecae3144c98b837729d87b9fde6c39a6ad00f07b71dbee94" +checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" dependencies = [ "accesskit_winit", "ahash", @@ -1262,9 +1266,9 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1beb57a3c942fac2f058655188c79ac1cd200555e4f3684cd0c965ceb3a67" +checksum = "bf3c1f5cd8dfe2ade470a218696c66cf556fcfd701e7830fa2e9f4428292a2a1" dependencies = [ "ahash", "egui", @@ -1276,9 +1280,9 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea182206896187f7a2fcc207a1573785fc31330cb245f6cebcf663ea933f8d20" +checksum = "0e39bccc683cd43adab530d8f21a13eb91e80de10bcc38c3f1c16601b6f62b26" dependencies = [ "ahash", "bytemuck", @@ -1310,9 +1314,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af86c4efae11da2a3dcbb4afebd0e9ed1916345e8d187b4051d443c8bd79af93" +checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" dependencies = [ "bytemuck", "serde", @@ -1379,9 +1383,9 @@ dependencies = [ [[package]] name = "epaint" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e11ec86a4d85e1350578ba20b2d89977ed937f3faab32e1c3ec81d20c1842" +checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" dependencies = [ "ab_glyph", "ahash", @@ -1397,9 +1401,9 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5202b64bef2b2c42a7f6e2e5b40fa83dd04aa61fdb08bfd116553adc149fe47a" +checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" [[package]] name = "equivalent" @@ -1539,24 +1543,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1588,9 +1592,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1599,21 +1603,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -1832,6 +1836,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hassle-rs" version = "0.11.0" @@ -1929,12 +1939,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -2256,7 +2266,7 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.5.0", + "indexmap 2.6.0", "log", "rustc-hash", "spirv", @@ -2417,17 +2427,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2631,23 +2630,11 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "orbclient" @@ -2670,9 +2657,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ "ttf-parser", ] @@ -2701,7 +2688,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.6", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -2726,18 +2713,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", @@ -2780,7 +2767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.5.0", + "indexmap 2.6.0", "quick-xml 0.32.0", "serde", "time", @@ -2836,12 +2823,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -2903,9 +2884,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -2999,9 +2980,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -3043,18 +3024,17 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "rfd" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251" +checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e" dependencies = [ "ashpd", - "block", - "dispatch", + "block2", "js-sys", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2", + "objc2-app-kit", + "objc2-foundation", "pollster", "raw-window-handle", "urlencoding", @@ -3604,7 +3584,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "toml_datetime 0.6.8", "winnow 0.5.40", ] @@ -3615,7 +3595,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "toml_datetime 0.6.8", "winnow 0.6.20", ] @@ -3663,9 +3643,9 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" [[package]] name = "type-map" @@ -3713,9 +3693,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -4045,7 +4025,7 @@ dependencies = [ "bitflags 2.6.0", "cfg_aliases 0.1.1", "document-features", - "indexmap 2.5.0", + "indexmap 2.6.0", "log", "naga", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 801dac1..0dd9203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ preferences = { git = "https://github.com/andybarron/preferences-rs" } rand = "0.8.5" realfft = "3.3.0" regex = "1" -rfd = "0.14" +rfd = "0.15.0" safe-transmute = "0.11" serde = { version = "1.0", features = ["derive"] } serialport = { git = "https://github.com/serialport/serialport-rs", features = ["serde"] } diff --git a/src/color_picker.rs b/src/color_picker.rs new file mode 100644 index 0000000..a5124d4 --- /dev/null +++ b/src/color_picker.rs @@ -0,0 +1,227 @@ +use eframe::egui::{ + self, lerp, pos2, remap_clamp, vec2, Align2, Color32, Mesh, Pos2, Response, Sense, Shape, + Stroke, Ui, Vec2, +}; + +// Ten colors that are distinguishable and suitable for colorblind people +pub const COLORS: [Color32; 10] = [ + Color32::WHITE, // White + Color32::from_rgb(230, 159, 0), // Orange + Color32::from_rgb(86, 180, 233), // Sky Blue + Color32::from_rgb(0, 158, 115), // Bluish Green + Color32::from_rgb(240, 228, 66), // Yellow + Color32::from_rgb(0, 114, 178), // Blue + Color32::from_rgb(213, 94, 0), // Vermilion (Red-Orange) + Color32::from_rgb(204, 121, 167), // Reddish Purple + Color32::from_rgb(121, 94, 56), // Brown + Color32::from_rgb(0, 204, 204), // Cyan +]; + +fn contrast_color(color: Color32) -> Color32 { + let intensity = (color.r() as f32 + color.g() as f32 + color.b() as f32) / 3.0 / 255.0; + if intensity < 0.5 { + Color32::WHITE + } else { + Color32::BLACK + } +} + +pub fn color_picker_widget( + ui: &mut Ui, + label: &str, + color: &mut [Color32], + index: usize, +) -> Response { + // Draw the square + ui.horizontal(|ui| { + // Define the desired square size (same as checkbox size) + let square_size = ui.spacing().interact_size.y * 0.8; + + // Allocate a square of the same size as the checkbox + let (rect, response) = + ui.allocate_exact_size(egui::vec2(square_size, square_size), Sense::click()); + + // Highlight stroke when hovered + let stroke = if response.hovered() { + Stroke::new(2.0, Color32::WHITE) // White stroke when hovered + } else { + Stroke::NONE // No stroke otherwise + }; + + // Draw the color square with possible hover outline + ui.painter().rect(rect, 2.0, color[index], stroke); + ui.label(label); + response + }) + .inner +} +pub fn color_picker_window(ctx: &egui::Context, color: &mut Color32, value: &mut f32) -> bool { + let mut save_button = false; + + let _window_response = egui::Window::new("Color Menu") + // .fixed_pos(Pos2 { x: 800.0, y: 450.0 }) + .fixed_size(Vec2 { x: 100.0, y: 100.0 }) + .anchor(Align2::CENTER_CENTER, Vec2 { x: 0.0, y: 0.0 }) + .collapsible(false) + .show(ctx, |ui| { + // We will create two horizontal rows with five squares each + let square_size = ui.spacing().interact_size.y * 0.8; + + ui.vertical(|ui| { + // First row (5 squares) + ui.horizontal(|ui| { + for color_option in &COLORS[0..5] { + let (rect, response) = ui.allocate_exact_size( + egui::vec2(square_size, square_size), + Sense::click(), + ); + + // Handle click to set selected color + if response.clicked() { + *color = *color_option; + } + + // Stroke highlighting for hover + let stroke = if response.hovered() { + Stroke::new(2.0, Color32::WHITE) + } else { + Stroke::NONE + }; + + // Draw the color square + ui.painter().rect(rect, 2.0, *color_option, stroke); + } + }); + + // Second row (5 squares) + ui.horizontal(|ui| { + for color_option in &COLORS[5..10] { + let (rect, response) = ui.allocate_exact_size( + egui::vec2(square_size, square_size), + Sense::click(), + ); + + // Handle click to set selected color + if response.clicked() { + *color = *color_option; + } + + // Stroke highlighting for hover + let stroke = if response.hovered() { + Stroke::new(2.0, Color32::WHITE) + } else { + Stroke::NONE + }; + + // Draw the color square + ui.painter().rect(rect, 2.0, *color_option, stroke); + } + }); + + // Now, create the 1D color bar slider below the grid + ui.separator(); // Optional visual separator between grid and color bar + // Add a 1D color slider below the color grid + let response = color_slider_1d(ui, value, |t| { + // Generate hue-based colors + let hue = t * 360.0; // Convert t from [0.0, 1.0] to [0.0, 360.0] + hsv_to_rgb(hue, 1.0, 1.0) // Full saturation and value + }); + if response.clicked() || response.changed() || response.dragged() { + // Update the selected color based on the slider position + *color = hsv_to_rgb(*value * 360.0, 1.0, 1.0); // Update color + } + ui.add_space(5.0); + ui.centered_and_justified(|ui| { + if ui.button("Exit").clicked() { + save_button = true; + } + }); + }); + }); + + save_button +} + +// Function to create a 1D color slider +fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color32) -> Response { + const N: usize = 100; // Number of segments + + let desired_size = vec2(ui.spacing().slider_width, ui.spacing().interact_size.y); + let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag()); + + if let Some(mpos) = response.interact_pointer_pos() { + *value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0); + } + + if ui.is_rect_visible(rect) { + let visuals = ui.style().interact(&response); + + // Fill the color gradient + let mut mesh = Mesh::default(); + for i in 0..=N { + let t = i as f32 / (N as f32); + let color = color_at(t); + let x = lerp(rect.left()..=rect.right(), t); + mesh.colored_vertex(pos2(x, rect.top()), color); + mesh.colored_vertex(pos2(x, rect.bottom()), color); + if i < N { + mesh.add_triangle((2 * i + 0) as u32, (2 * i + 1) as u32, (2 * i + 2) as u32); + mesh.add_triangle((2 * i + 1) as u32, (2 * i + 2) as u32, (2 * i + 3) as u32); + } + } + ui.painter().add(Shape::mesh(mesh)); + + ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline + + // Show where the slider is at: + let x = lerp(rect.left()..=rect.right(), *value); + let r = rect.height() / 4.0; + let picked_color = color_at(*value); + ui.painter().add(Shape::convex_polygon( + vec![ + pos2(x, rect.center().y), // tip + pos2(x + r, rect.bottom()), // right bottom + pos2(x - r, rect.bottom()), // left bottom + ], + picked_color, + Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)), + )); + } + + response +} + +// Convert HSV color to RGB +fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> Color32 { + let c = value * saturation; + let x = c * (1.0 - ((hue / 60.0) % 2.0 - 1.0).abs()); + let m = value - c; + + let (r, g, b) = if hue < 60.0 { + (c, x, 0.0) + } else if hue < 120.0 { + (x, c, 0.0) + } else if hue < 180.0 { + (0.0, c, x) + } else if hue < 240.0 { + (0.0, x, c) + } else if hue < 300.0 { + (x, 0.0, c) + } else { + (c, 0.0, x) + }; + + Color32::from_rgb( + ((r + m) * 255.0) as u8, + ((g + m) * 255.0) as u8, + ((b + m) * 255.0) as u8, + ) +} + +// Function to interpolate between two colors +fn lerp_color(c1: Color32, c2: Color32, t: f32) -> Color32 { + let r = (c1.r() as f32 * (1.0 - t) + c2.r() as f32 * t).round() as u8; + let g = (c1.g() as f32 * (1.0 - t) + c2.g() as f32 * t).round() as u8; + let b = (c1.b() as f32 * (1.0 - t) + c2.b() as f32 * t).round() as u8; + Color32::from_rgb(r, g, b) +} diff --git a/src/data.rs b/src/data.rs index 595253d..993371d 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,3 +1,5 @@ +use crate::color_picker::COLORS; +use eframe::egui::Color32; use std::fmt; use std::time::{SystemTime, UNIX_EPOCH}; @@ -45,7 +47,6 @@ impl Default for Packet { #[derive(Clone, Debug)] pub struct DataContainer { pub time: Vec, - pub names: Vec, pub absolute_time: Vec, pub dataset: Vec>, pub raw_traffic: Vec, @@ -55,7 +56,6 @@ impl Default for DataContainer { fn default() -> DataContainer { DataContainer { time: vec![], - names: vec!["Column 0".to_string()], absolute_time: vec![], dataset: vec![vec![]], raw_traffic: vec![], diff --git a/src/gui.rs b/src/gui.rs index ba4ac8a..31147d7 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,4 +1,5 @@ use core::f32; +use std::cmp::max; use std::ops::RangeInclusive; use std::path::PathBuf; use std::sync::mpsc::Sender; @@ -6,9 +7,7 @@ use std::sync::{Arc, RwLock}; use std::time::Duration; use eframe::egui::panel::Side; -use eframe::egui::{ - Align2, FontFamily, FontId, KeyboardShortcut, Pos2, Sense, Vec2, Visuals, -}; +use eframe::egui::{Align2, Color32, FontFamily, FontId, KeyboardShortcut, Pos2, Sense, Vec2, Visuals}; use eframe::{egui, Storage}; use egui::ThemePreference; use egui_plot::{log_grid_spacer, GridMark, Legend, Line, Plot, PlotPoint, PlotPoints}; @@ -22,6 +21,7 @@ use crate::serial::{clear_serial_settings, save_serial_settings, Device, SerialD use crate::toggle::toggle; use crate::FileOptions; use crate::{APP_INFO, PREFS_KEY}; +use crate::color_picker::{color_picker_widget, color_picker_window, COLORS}; const MAX_FPS: f64 = 60.0; @@ -169,6 +169,11 @@ pub fn load_gui_settings() -> GuiSettingsContainer { }) } +pub enum ColorWindow { + NoShow, + ColorIndex(usize) +} + pub struct MyApp { connected_to_device: bool, command: String, @@ -195,6 +200,10 @@ pub struct MyApp { history: Vec, index: usize, eol: String, + colors: Vec, + color_vals: Vec, + labels: Vec, + show_color_window: ColorWindow, show_sent_cmds: bool, show_timestamps: bool, save_raw: bool, @@ -245,11 +254,15 @@ impl MyApp { show_timestamps: true, save_raw: false, eol: "\\r\\n".to_string(), + colors: vec![COLORS[0]], + color_vals: vec![0.0], + labels: vec!["Column 0".to_string()], history: vec![], index: 0, plot_location: None, do_not_show_clear_warning: false, show_warning_window: WindowFeedback::None, + show_color_window: ColorWindow::NoShow, } } @@ -323,6 +336,18 @@ impl MyApp { self.data = read_guard.clone(); } + if self.data.dataset.len() != self.labels.len() { + self.labels = (0..max(self.data.dataset.len(), 1)) + .map(|i| format!("Column {i}")) + .collect(); + self.colors = (0..max(self.data.dataset.len(), 1)) + .map(|i| COLORS[i % COLORS.len()]) + .collect(); + self.color_vals = (0..max(self.data.dataset.len(), 1)) + .map(|i| 0.0) + .collect(); + } + let mut graphs: Vec> = vec![vec![]; self.data.dataset.len()]; let window = self.data.dataset[0] .len() @@ -359,11 +384,11 @@ impl MyApp { let plot_inner = signal_plot.show(ui, |signal_plot_ui| { for (i, graph) in graphs.iter().enumerate() { // this check needs to be here for when we change devices (not very elegant) - if i < self.serial_devices.labels[self.device_idx].len() { + if i < self.labels.len() { signal_plot_ui.line( Line::new(PlotPoints::Owned(graph.to_vec())).name( - &self.serial_devices.labels[self.device_idx][i], - ), + &self.labels[i], + ).color(self.colors[i]), ); } } @@ -738,6 +763,7 @@ impl MyApp { 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(), }) { print_to_console( &self.print_lock, @@ -806,33 +832,53 @@ impl MyApp { clear_serial_settings(); } if ui.button("Reset Labels").clicked() { - self.serial_devices.labels[self.device_idx] = self.data.names.clone(); + // self.serial_devices.labels[self.device_idx] = self.serial_devices.labels.clone(); } }); - if self.data.names.len() == 1 { + if self.labels.len() == 1 { ui.label("Detected 1 Dataset:"); } else { - ui.label(format!("Detected {} Datasets:", self.data.names.len())); + ui.label(format!("Detected {} Datasets:", self.labels.len())); } ui.add_space(5.0); - for i in 0..self.data.names.len().min(10) { + for i in 0..self.labels.len().min(10) { // if init, set names to what has been stored in the device last time if init { - self.names_tx.send(self.serial_devices.labels[self.device_idx].clone()).expect("Failed to send names"); + self.names_tx.send(self.labels.clone()).expect("Failed to send names"); init = false; } - if self.serial_devices.labels[self.device_idx].len() <= i { + + if self.labels.len() <= i { break; } + ui.horizontal(|ui| { + + let response = color_picker_widget(ui,"", &mut self.colors,i ); + + // Check if the square was clicked and toggle color picker window + if response.clicked() { + self.show_color_window = ColorWindow::ColorIndex(i); + }; - if ui.add( - egui::TextEdit::singleline(&mut self.serial_devices.labels[self.device_idx][i]) - .desired_width(0.95 * RIGHT_PANEL_WIDTH) - ).on_hover_text("Use custom names for your Datasets.").changed() { - self.names_tx.send(self.serial_devices.labels[self.device_idx].clone()).expect("Failed to send names"); - }; + if ui.add( + egui::TextEdit::singleline(&mut self.labels[i]) + .desired_width(0.95 * RIGHT_PANEL_WIDTH) + ).on_hover_text("Use custom names for your Datasets.").changed() { + self.names_tx.send(self.labels.clone()).expect("Failed to send names"); + }; + }); } - if self.data.names.len() > 10 { + + match self.show_color_window { + ColorWindow::NoShow => {} + ColorWindow::ColorIndex(index) => { + if color_picker_window(ui.ctx(), &mut self.colors[index], &mut self.color_vals[index]) { + self.show_color_window = ColorWindow::NoShow; + } + } + } + + if self.labels.len() > 10 { ui.label("Only renaming up to 10 Datasets is currently supported."); } }); diff --git a/src/io.rs b/src/io.rs index 437d084..15fbddc 100644 --- a/src/io.rs +++ b/src/io.rs @@ -11,6 +11,7 @@ pub struct FileOptions { pub file_path: PathBuf, pub save_absolute_time: bool, pub save_raw_traffic: bool, + pub names: Vec, } pub fn save_to_csv(data: &DataContainer, csv_options: &FileOptions) -> Result<(), Box> { @@ -19,7 +20,7 @@ pub fn save_to_csv(data: &DataContainer, csv_options: &FileOptions) -> Result<() .from_path(&csv_options.file_path)?; // serialize does not work, so we do it with a loop.. let mut header = vec!["Time [ms]".to_string()]; - header.extend_from_slice(&data.names); + header.extend_from_slice(&csv_options.names); wtr.write_record(header)?; for j in 0..data.dataset[0].len() { let time = if csv_options.save_absolute_time { diff --git a/src/main.rs b/src/main.rs index ca47a20..8b7d876 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,15 +11,16 @@ use std::sync::{mpsc, Arc, RwLock}; use std::thread; use std::time::Duration; -use eframe::egui::{vec2, ViewportBuilder, Visuals}; -use eframe::{egui, icon_data}; -use preferences::AppInfo; - +use crate::color_picker::COLORS; use crate::data::{DataContainer, Packet}; use crate::gui::{load_gui_settings, print_to_console, MyApp, Print, RIGHT_PANEL_WIDTH}; use crate::io::{save_to_csv, FileOptions}; use crate::serial::{load_serial_settings, serial_thread, Device}; +use eframe::egui::{vec2, ViewportBuilder, Visuals}; +use eframe::{egui, icon_data}; +use preferences::AppInfo; +mod color_picker; mod data; mod gui; mod io; @@ -49,7 +50,6 @@ fn main_thread( data_lock: Arc>, print_lock: Arc>>, raw_data_rx: Receiver, - names_rx: Receiver>, save_rx: Receiver, clear_rx: Receiver, ) { @@ -64,10 +64,6 @@ fn main_thread( } } - if let Ok(names) = names_rx.recv_timeout(Duration::from_millis(1)) { - data.names = names; - } - if let Ok(packet) = raw_data_rx.recv_timeout(Duration::from_millis(1)) { if !packet.payload.is_empty() { data.raw_traffic.push(packet.clone()); @@ -75,11 +71,6 @@ fn main_thread( if data.dataset.is_empty() || failed_format_counter > 10 { // resetting dataset data.dataset = vec![vec![]; max(split_data.len(), 1)]; - if data.names.len() != split_data.len() { - data.names = (0..max(split_data.len(), 1)) - .map(|i| format!("Column {i}")) - .collect(); - } 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() { @@ -94,11 +85,6 @@ fn main_thread( // resetting dataset data.time = vec![]; data.dataset = vec![vec![]; max(split_data.len(), 1)]; - if data.names.len() != split_data.len() { - data.names = (0..max(split_data.len(), 1)) - .map(|i| format!("Column {i}")) - .collect(); - } } } else { // not same length @@ -177,7 +163,6 @@ fn main() { main_data_lock, main_print_lock, raw_data_rx, - names_rx, save_rx, clear_rx, ); diff --git a/src/serial.rs b/src/serial.rs index 1349c3d..de11c8d 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,12 +1,13 @@ +use eframe::egui::Color32; +use preferences::Preferences; +use serde::{Deserialize, Serialize}; +use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits}; use std::io::{BufRead, BufReader}; use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; -use preferences::Preferences; -use serde::{Deserialize, Serialize}; -use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits}; - +use crate::color_picker::COLORS; use crate::data::{get_epoch_ms, SerialDirection}; use crate::{print_to_console, Packet, Print, APP_INFO, PREFS_KEY_SERIAL}; @@ -14,6 +15,8 @@ use crate::{print_to_console, Packet, Print, APP_INFO, PREFS_KEY_SERIAL}; pub struct SerialDevices { pub devices: Vec, pub labels: Vec>, + pub colors: Vec>, + pub color_vals: Vec>, pub number_of_plots: Vec, } @@ -22,6 +25,8 @@ impl Default for SerialDevices { SerialDevices { devices: vec![Device::default()], labels: vec![vec!["Column 0".to_string()]], + colors: vec![vec![COLORS[0]]], + color_vals: vec![vec![0.0]], number_of_plots: vec![1], } }