facett-app/src/colorpicker.rs
//! **`ColorPicker` — a themed colour picker with preset swatches.** Wraps egui's
//! colour editor and adds a swatch row + a semantic, observable layer: the chosen
//! colour lives in the struct (FC-1) and is reported as hex (`state_json`).
use serde_json::{Value, json};
/// A colour picker: a swatch row + egui's colour button, over an owned RGBA value.
pub struct ColorPicker {
rgba: [u8; 4],
swatches: Vec<[u8; 4]>,
}
impl ColorPicker {
pub fn new(rgba: [u8; 4]) -> Self {
Self { rgba, swatches: Vec::new() }
}
/// Preset swatches shown as one-click chips.
pub fn swatches(mut self, swatches: impl IntoIterator<Item = [u8; 4]>) -> Self {
self.swatches = swatches.into_iter().collect();
self
}
pub fn color(&self) -> [u8; 4] {
self.rgba
}
pub fn set_color(&mut self, rgba: [u8; 4]) {
self.rgba = rgba;
}
/// The current colour as `#RRGGBBAA`.
pub fn hex(&self) -> String {
let [r, g, b, a] = self.rgba;
format!("#{r:02X}{g:02X}{b:02X}{a:02X}")
}
/// Draw the swatches + colour button; returns the new colour if it changed.
pub fn show(&mut self, ui: &mut egui::Ui) -> Option<[u8; 4]> {
let before = self.rgba;
ui.horizontal(|ui| {
for sw in &self.swatches {
let c = egui::Color32::from_rgba_unmultiplied(sw[0], sw[1], sw[2], sw[3]);
if ui.add(egui::Button::new(" ").fill(c)).clicked() {
self.rgba = *sw;
}
}
let mut c = egui::Color32::from_rgba_unmultiplied(self.rgba[0], self.rgba[1], self.rgba[2], self.rgba[3]);
if ui.color_edit_button_srgba(&mut c).changed() {
self.rgba = [c.r(), c.g(), c.b(), c.a()];
}
ui.monospace(self.hex());
});
(self.rgba != before).then_some(self.rgba)
}
pub fn state_json(&self) -> Value {
json!({ "rgba": self.rgba, "hex": self.hex() })
}
}