pub fn color_picker_compact(ui: &mut egui::Ui, color: &mut [f32; 4], alpha: bool) -> bool {
let old = HsvaGamma::from(to_color32(*color));
let HsvaGamma {
mut h,
mut s,
mut v,
mut a,
} = old;
color_slider_2d(ui, &mut s, &mut v, |s, v| {
HsvaGamma {
h: old.h,
s,
v,
a: 1.0,
}
.into()
});
color_slider_1d(ui, &mut h, |h| {
HsvaGamma {
h,
s: 1.0,
v: 1.0,
a: 1.0,
}
.into()
});
if alpha {
color_slider_1d(ui, &mut a, |a| {
HsvaGamma {
a,
..HsvaGamma { h, s, v, a: 1.0 }
}
.into()
});
} else {
a = 1.0;
}
let new = HsvaGamma { h, s, v, a };
if old != new {
*color = from_color32(new.into());
true
} else {
false
}
}
fn to_color32(c: [f32; 4]) -> egui::Color32 {
let b = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
egui::Color32::from_rgba_unmultiplied(b(c[0]), b(c[1]), b(c[2]), b(c[3]))
}
fn from_color32(c: egui::Color32) -> [f32; 4] {
let [r, g, b, a] = c.to_srgba_unmultiplied(); [
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
]
}
use egui::{Painter, Response, Sense, Ui, epaint, lerp, remap_clamp};
use epaint::ecolor::{Color32, HsvaGamma, Rgba};
use epaint::{Mesh, Rect, Shape, Stroke, StrokeKind, Vec2, pos2, vec2};
fn contrast_color(color: impl Into<Rgba>) -> Color32 {
if color.into().intensity() < 0.5 {
Color32::WHITE
} else {
Color32::BLACK
}
}
const N: u32 = 6 * 6;
fn background_checkers(painter: &Painter, rect: Rect) {
let rect = rect.shrink(0.5); if !rect.is_positive() {
return;
}
let dark_color = Color32::from_gray(32);
let bright_color = Color32::from_gray(128);
let checker_size = Vec2::splat(rect.height() / 2.0);
let n = (rect.width() / checker_size.x).round() as u32;
let mut mesh = Mesh::default();
mesh.add_colored_rect(rect, dark_color);
let mut top = true;
for i in 0..n {
let x = lerp(rect.left()..=rect.right(), i as f32 / (n as f32));
let small_rect = if top {
Rect::from_min_size(pos2(x, rect.top()), checker_size)
} else {
Rect::from_min_size(pos2(x, rect.center().y), checker_size)
};
mesh.add_colored_rect(small_rect, bright_color);
top = !top;
}
painter.add(Shape::mesh(mesh));
}
fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color32) -> Response {
#![expect(clippy::identity_op)]
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);
background_checkers(ui.painter(), rect);
{
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, 2 * i + 1, 2 * i + 2);
mesh.add_triangle(2 * i + 1, 2 * i + 2, 2 * i + 3);
}
}
ui.painter().add(Shape::mesh(mesh));
}
ui.painter()
.rect_stroke(rect, 0.0, visuals.bg_stroke, StrokeKind::Inside);
{
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), pos2(x + r, rect.bottom()), pos2(x - r, rect.bottom()), ],
picked_color,
Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
));
}
}
response
}
fn color_slider_2d(
ui: &mut Ui,
x_value: &mut f32,
y_value: &mut f32,
color_at: impl Fn(f32, f32) -> Color32,
) -> Response {
let desired_size = Vec2::splat(ui.spacing().slider_width);
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag());
if let Some(mpos) = response.interact_pointer_pos() {
*x_value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
}
if ui.is_rect_visible(rect) {
let visuals = ui.style().interact(&response);
let mut mesh = Mesh::default();
for xi in 0..=N {
for yi in 0..=N {
let xt = xi as f32 / (N as f32);
let yt = yi as f32 / (N as f32);
let color = color_at(xt, yt);
let x = lerp(rect.left()..=rect.right(), xt);
let y = lerp(rect.bottom()..=rect.top(), yt);
mesh.colored_vertex(pos2(x, y), color);
if xi < N && yi < N {
let x_offset = 1;
let y_offset = N + 1;
let tl = yi * y_offset + xi;
mesh.add_triangle(tl, tl + x_offset, tl + y_offset);
mesh.add_triangle(tl + x_offset, tl + y_offset, tl + y_offset + x_offset);
}
}
}
ui.painter().add(Shape::mesh(mesh));
ui.painter()
.rect_stroke(rect, 0.0, visuals.bg_stroke, StrokeKind::Inside);
let x = lerp(rect.left()..=rect.right(), *x_value);
let y = lerp(rect.bottom()..=rect.top(), *y_value);
let picked_color = color_at(*x_value, *y_value);
ui.painter().add(epaint::CircleShape {
center: pos2(x, y),
radius: rect.width() / 12.0,
fill: picked_color,
stroke: Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)),
});
}
response
}