-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement working color picker for all curves
- Loading branch information
Showing
8 changed files
with
394 additions
and
150 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.