Skip to content

Commit

Permalink
implement working color picker for all curves
Browse files Browse the repository at this point in the history
  • Loading branch information
hacknus committed Oct 7, 2024
1 parent 25e5563 commit cae5cc2
Show file tree
Hide file tree
Showing 8 changed files with 394 additions and 150 deletions.
186 changes: 83 additions & 103 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
227 changes: 227 additions & 0 deletions src/color_picker.rs
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 2 additions & 2 deletions src/data.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::color_picker::COLORS;
use eframe::egui::Color32;
use std::fmt;
use std::time::{SystemTime, UNIX_EPOCH};

Expand Down Expand Up @@ -45,7 +47,6 @@ impl Default for Packet {
#[derive(Clone, Debug)]
pub struct DataContainer {
pub time: Vec<u128>,
pub names: Vec<String>,
pub absolute_time: Vec<u128>,
pub dataset: Vec<Vec<f32>>,
pub raw_traffic: Vec<Packet>,
Expand All @@ -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![],
Expand Down
Loading

0 comments on commit cae5cc2

Please sign in to comment.