|
| 1 | +use eframe::egui::{ |
| 2 | + self, lerp, pos2, remap_clamp, vec2, Align2, Color32, Mesh, Pos2, Response, Sense, Shape, |
| 3 | + Stroke, Ui, Vec2, |
| 4 | +}; |
| 5 | + |
| 6 | +// Ten colors that are distinguishable and suitable for colorblind people |
| 7 | +pub const COLORS: [Color32; 10] = [ |
| 8 | + Color32::WHITE, // White |
| 9 | + Color32::from_rgb(230, 159, 0), // Orange |
| 10 | + Color32::from_rgb(86, 180, 233), // Sky Blue |
| 11 | + Color32::from_rgb(0, 158, 115), // Bluish Green |
| 12 | + Color32::from_rgb(240, 228, 66), // Yellow |
| 13 | + Color32::from_rgb(0, 114, 178), // Blue |
| 14 | + Color32::from_rgb(213, 94, 0), // Vermilion (Red-Orange) |
| 15 | + Color32::from_rgb(204, 121, 167), // Reddish Purple |
| 16 | + Color32::from_rgb(121, 94, 56), // Brown |
| 17 | + Color32::from_rgb(0, 204, 204), // Cyan |
| 18 | +]; |
| 19 | + |
| 20 | +fn contrast_color(color: Color32) -> Color32 { |
| 21 | + let intensity = (color.r() as f32 + color.g() as f32 + color.b() as f32) / 3.0 / 255.0; |
| 22 | + if intensity < 0.5 { |
| 23 | + Color32::WHITE |
| 24 | + } else { |
| 25 | + Color32::BLACK |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +pub fn color_picker_widget( |
| 30 | + ui: &mut Ui, |
| 31 | + label: &str, |
| 32 | + color: &mut [Color32], |
| 33 | + index: usize, |
| 34 | +) -> Response { |
| 35 | + // Draw the square |
| 36 | + ui.horizontal(|ui| { |
| 37 | + // Define the desired square size (same as checkbox size) |
| 38 | + let square_size = ui.spacing().interact_size.y * 0.8; |
| 39 | + |
| 40 | + // Allocate a square of the same size as the checkbox |
| 41 | + let (rect, response) = |
| 42 | + ui.allocate_exact_size(egui::vec2(square_size, square_size), Sense::click()); |
| 43 | + |
| 44 | + // Highlight stroke when hovered |
| 45 | + let stroke = if response.hovered() { |
| 46 | + Stroke::new(2.0, Color32::WHITE) // White stroke when hovered |
| 47 | + } else { |
| 48 | + Stroke::NONE // No stroke otherwise |
| 49 | + }; |
| 50 | + |
| 51 | + // Draw the color square with possible hover outline |
| 52 | + ui.painter().rect(rect, 2.0, color[index], stroke); |
| 53 | + ui.label(label); |
| 54 | + response |
| 55 | + }) |
| 56 | + .inner |
| 57 | +} |
| 58 | +pub fn color_picker_window(ctx: &egui::Context, color: &mut Color32, value: &mut f32) -> bool { |
| 59 | + let mut save_button = false; |
| 60 | + |
| 61 | + let _window_response = egui::Window::new("Color Menu") |
| 62 | + // .fixed_pos(Pos2 { x: 800.0, y: 450.0 }) |
| 63 | + .fixed_size(Vec2 { x: 100.0, y: 100.0 }) |
| 64 | + .anchor(Align2::CENTER_CENTER, Vec2 { x: 0.0, y: 0.0 }) |
| 65 | + .collapsible(false) |
| 66 | + .show(ctx, |ui| { |
| 67 | + // We will create two horizontal rows with five squares each |
| 68 | + let square_size = ui.spacing().interact_size.y * 0.8; |
| 69 | + |
| 70 | + ui.vertical(|ui| { |
| 71 | + // First row (5 squares) |
| 72 | + ui.horizontal(|ui| { |
| 73 | + for color_option in &COLORS[0..5] { |
| 74 | + let (rect, response) = ui.allocate_exact_size( |
| 75 | + egui::vec2(square_size, square_size), |
| 76 | + Sense::click(), |
| 77 | + ); |
| 78 | + |
| 79 | + // Handle click to set selected color |
| 80 | + if response.clicked() { |
| 81 | + *color = *color_option; |
| 82 | + } |
| 83 | + |
| 84 | + // Stroke highlighting for hover |
| 85 | + let stroke = if response.hovered() { |
| 86 | + Stroke::new(2.0, Color32::WHITE) |
| 87 | + } else { |
| 88 | + Stroke::NONE |
| 89 | + }; |
| 90 | + |
| 91 | + // Draw the color square |
| 92 | + ui.painter().rect(rect, 2.0, *color_option, stroke); |
| 93 | + } |
| 94 | + }); |
| 95 | + |
| 96 | + // Second row (5 squares) |
| 97 | + ui.horizontal(|ui| { |
| 98 | + for color_option in &COLORS[5..10] { |
| 99 | + let (rect, response) = ui.allocate_exact_size( |
| 100 | + egui::vec2(square_size, square_size), |
| 101 | + Sense::click(), |
| 102 | + ); |
| 103 | + |
| 104 | + // Handle click to set selected color |
| 105 | + if response.clicked() { |
| 106 | + *color = *color_option; |
| 107 | + } |
| 108 | + |
| 109 | + // Stroke highlighting for hover |
| 110 | + let stroke = if response.hovered() { |
| 111 | + Stroke::new(2.0, Color32::WHITE) |
| 112 | + } else { |
| 113 | + Stroke::NONE |
| 114 | + }; |
| 115 | + |
| 116 | + // Draw the color square |
| 117 | + ui.painter().rect(rect, 2.0, *color_option, stroke); |
| 118 | + } |
| 119 | + }); |
| 120 | + |
| 121 | + // Now, create the 1D color bar slider below the grid |
| 122 | + ui.separator(); // Optional visual separator between grid and color bar |
| 123 | + // Add a 1D color slider below the color grid |
| 124 | + let response = color_slider_1d(ui, value, |t| { |
| 125 | + // Generate hue-based colors |
| 126 | + let hue = t * 360.0; // Convert t from [0.0, 1.0] to [0.0, 360.0] |
| 127 | + hsv_to_rgb(hue, 1.0, 1.0) // Full saturation and value |
| 128 | + }); |
| 129 | + if response.clicked() || response.changed() || response.dragged() { |
| 130 | + // Update the selected color based on the slider position |
| 131 | + *color = hsv_to_rgb(*value * 360.0, 1.0, 1.0); // Update color |
| 132 | + } |
| 133 | + ui.add_space(5.0); |
| 134 | + ui.centered_and_justified(|ui| { |
| 135 | + if ui.button("Exit").clicked() { |
| 136 | + save_button = true; |
| 137 | + } |
| 138 | + }); |
| 139 | + }); |
| 140 | + }); |
| 141 | + |
| 142 | + save_button |
| 143 | +} |
| 144 | + |
| 145 | +// Function to create a 1D color slider |
| 146 | +fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color32) -> Response { |
| 147 | + const N: usize = 100; // Number of segments |
| 148 | + |
| 149 | + let desired_size = vec2(ui.spacing().slider_width, ui.spacing().interact_size.y); |
| 150 | + let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag()); |
| 151 | + |
| 152 | + if let Some(mpos) = response.interact_pointer_pos() { |
| 153 | + *value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0); |
| 154 | + } |
| 155 | + |
| 156 | + if ui.is_rect_visible(rect) { |
| 157 | + let visuals = ui.style().interact(&response); |
| 158 | + |
| 159 | + // Fill the color gradient |
| 160 | + let mut mesh = Mesh::default(); |
| 161 | + for i in 0..=N { |
| 162 | + let t = i as f32 / (N as f32); |
| 163 | + let color = color_at(t); |
| 164 | + let x = lerp(rect.left()..=rect.right(), t); |
| 165 | + mesh.colored_vertex(pos2(x, rect.top()), color); |
| 166 | + mesh.colored_vertex(pos2(x, rect.bottom()), color); |
| 167 | + if i < N { |
| 168 | + mesh.add_triangle((2 * i + 0) as u32, (2 * i + 1) as u32, (2 * i + 2) as u32); |
| 169 | + mesh.add_triangle((2 * i + 1) as u32, (2 * i + 2) as u32, (2 * i + 3) as u32); |
| 170 | + } |
| 171 | + } |
| 172 | + ui.painter().add(Shape::mesh(mesh)); |
| 173 | + |
| 174 | + ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); // outline |
| 175 | + |
| 176 | + // Show where the slider is at: |
| 177 | + let x = lerp(rect.left()..=rect.right(), *value); |
| 178 | + let r = rect.height() / 4.0; |
| 179 | + let picked_color = color_at(*value); |
| 180 | + ui.painter().add(Shape::convex_polygon( |
| 181 | + vec![ |
| 182 | + pos2(x, rect.center().y), // tip |
| 183 | + pos2(x + r, rect.bottom()), // right bottom |
| 184 | + pos2(x - r, rect.bottom()), // left bottom |
| 185 | + ], |
| 186 | + picked_color, |
| 187 | + Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)), |
| 188 | + )); |
| 189 | + } |
| 190 | + |
| 191 | + response |
| 192 | +} |
| 193 | + |
| 194 | +// Convert HSV color to RGB |
| 195 | +fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> Color32 { |
| 196 | + let c = value * saturation; |
| 197 | + let x = c * (1.0 - ((hue / 60.0) % 2.0 - 1.0).abs()); |
| 198 | + let m = value - c; |
| 199 | + |
| 200 | + let (r, g, b) = if hue < 60.0 { |
| 201 | + (c, x, 0.0) |
| 202 | + } else if hue < 120.0 { |
| 203 | + (x, c, 0.0) |
| 204 | + } else if hue < 180.0 { |
| 205 | + (0.0, c, x) |
| 206 | + } else if hue < 240.0 { |
| 207 | + (0.0, x, c) |
| 208 | + } else if hue < 300.0 { |
| 209 | + (x, 0.0, c) |
| 210 | + } else { |
| 211 | + (c, 0.0, x) |
| 212 | + }; |
| 213 | + |
| 214 | + Color32::from_rgb( |
| 215 | + ((r + m) * 255.0) as u8, |
| 216 | + ((g + m) * 255.0) as u8, |
| 217 | + ((b + m) * 255.0) as u8, |
| 218 | + ) |
| 219 | +} |
| 220 | + |
| 221 | +// Function to interpolate between two colors |
| 222 | +fn lerp_color(c1: Color32, c2: Color32, t: f32) -> Color32 { |
| 223 | + let r = (c1.r() as f32 * (1.0 - t) + c2.r() as f32 * t).round() as u8; |
| 224 | + let g = (c1.g() as f32 * (1.0 - t) + c2.g() as f32 * t).round() as u8; |
| 225 | + let b = (c1.b() as f32 * (1.0 - t) + c2.b() as f32 * t).round() as u8; |
| 226 | + Color32::from_rgb(r, g, b) |
| 227 | +} |
0 commit comments