[color-picker] edit your own (s)RGBA arrays
Both with and without premultiplied alpha
This commit is contained in:
parent
b9a3240ca3
commit
bc0d6baefb
6 changed files with 340 additions and 39 deletions
6
TODO.md
6
TODO.md
|
@ -14,7 +14,11 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
||||||
* [x] linear rgb <-> sRGB
|
* [x] linear rgb <-> sRGB
|
||||||
* [x] HSV
|
* [x] HSV
|
||||||
* [x] Color edit button with popup color picker
|
* [x] Color edit button with popup color picker
|
||||||
* [ ] Easily edit users own (s)RGBA quadruplets (`&mut [u8;4]`/`[f32;4]`)
|
* [x] Gamma for value (brightness) slider
|
||||||
|
* [x] Easily edit users own (s)RGBA quadruplets (`&mut [u8;4]`/`[f32;4]`)
|
||||||
|
* [ ] RGB editing without alpha
|
||||||
|
* [ ] Additive blending aware color picker
|
||||||
|
* [ ] Premultiplied alpha is a bit of a pain in the ass. Maybe rethink this a bit.
|
||||||
* Containers
|
* Containers
|
||||||
* [ ] Scroll areas
|
* [ ] Scroll areas
|
||||||
* [x] Vertical scrolling
|
* [x] Vertical scrolling
|
||||||
|
|
|
@ -312,6 +312,7 @@ pub struct DemoWindow {
|
||||||
num_columns: usize,
|
num_columns: usize,
|
||||||
|
|
||||||
widgets: Widgets,
|
widgets: Widgets,
|
||||||
|
colors: ColorWidgets,
|
||||||
layout: LayoutDemo,
|
layout: LayoutDemo,
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
box_painting: BoxPainting,
|
box_painting: BoxPainting,
|
||||||
|
@ -324,6 +325,7 @@ impl Default for DemoWindow {
|
||||||
num_columns: 2,
|
num_columns: 2,
|
||||||
|
|
||||||
widgets: Default::default(),
|
widgets: Default::default(),
|
||||||
|
colors: Default::default(),
|
||||||
layout: Default::default(),
|
layout: Default::default(),
|
||||||
tree: Tree::demo(),
|
tree: Tree::demo(),
|
||||||
box_painting: Default::default(),
|
box_painting: Default::default(),
|
||||||
|
@ -351,6 +353,12 @@ impl DemoWindow {
|
||||||
self.widgets.ui(ui);
|
self.widgets.ui(ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CollapsingHeader::new("Colors")
|
||||||
|
.default_open(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
self.colors.ui(ui);
|
||||||
|
});
|
||||||
|
|
||||||
CollapsingHeader::new("Layout")
|
CollapsingHeader::new("Layout")
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| self.layout.ui(ui));
|
.show(ui, |ui| self.layout.ui(ui));
|
||||||
|
@ -531,7 +539,7 @@ impl Widgets {
|
||||||
|
|
||||||
ui.horizontal_centered(|ui| {
|
ui.horizontal_centered(|ui| {
|
||||||
ui.add(Label::new("Click to select a different text color: ").text_color(self.color));
|
ui.add(Label::new("Click to select a different text color: ").text_color(self.color));
|
||||||
ui.color_edit_button(&mut self.color);
|
ui.color_edit_button_srgba(&mut self.color);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -552,6 +560,78 @@ impl Widgets {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
struct ColorWidgets {
|
||||||
|
srgba_unmul: [u8; 4],
|
||||||
|
srgba_premul: [u8; 4],
|
||||||
|
rgba_unmul: [f32; 4],
|
||||||
|
rgba_premul: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorWidgets {
|
||||||
|
fn default() -> Self {
|
||||||
|
// Approximately the same color.
|
||||||
|
ColorWidgets {
|
||||||
|
srgba_unmul: [0, 255, 183, 127],
|
||||||
|
srgba_premul: [0, 187, 140, 127],
|
||||||
|
rgba_unmul: [0.0, 1.0, 0.5, 0.5],
|
||||||
|
rgba_premul: [0.0, 0.5, 0.25, 0.5],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorWidgets {
|
||||||
|
fn ui(&mut self, ui: &mut Ui) {
|
||||||
|
if ui.button("Reset").clicked {
|
||||||
|
*self = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.label("Egui lets you edit colors stored as either sRGBA or linear RGBA and with or without premultiplied alpha");
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
srgba_unmul,
|
||||||
|
srgba_premul,
|
||||||
|
rgba_unmul,
|
||||||
|
rgba_premul,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
ui.color_edit_button_srgba_unmultiplied(srgba_unmul);
|
||||||
|
ui.label(format!(
|
||||||
|
"sRGBA: {} {} {} {}",
|
||||||
|
srgba_unmul[0], srgba_unmul[1], srgba_unmul[2], srgba_unmul[3],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
ui.color_edit_button_srgba_premultiplied(srgba_premul);
|
||||||
|
ui.label(format!(
|
||||||
|
"sRGBA with premultiplied alpha: {} {} {} {}",
|
||||||
|
srgba_premul[0], srgba_premul[1], srgba_premul[2], srgba_premul[3],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
ui.color_edit_button_rgba_unmultiplied(rgba_unmul);
|
||||||
|
ui.label(format!(
|
||||||
|
"Linear RGBA: {:.02} {:.02} {:.02} {:.02}",
|
||||||
|
rgba_unmul[0], rgba_unmul[1], rgba_unmul[2], rgba_unmul[3],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
ui.color_edit_button_rgba_premultiplied(rgba_premul);
|
||||||
|
ui.label(format!(
|
||||||
|
"Linear RGBA with premultiplied alpha: {:.02} {:.02} {:.02} {:.02}",
|
||||||
|
rgba_premul[0], rgba_premul[1], rgba_premul[2], rgba_premul[3],
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
struct BoxPainting {
|
struct BoxPainting {
|
||||||
|
|
|
@ -208,7 +208,7 @@ impl From<Srgba> for Rgba {
|
||||||
linear_from_srgb_byte(srgba[0]),
|
linear_from_srgb_byte(srgba[0]),
|
||||||
linear_from_srgb_byte(srgba[1]),
|
linear_from_srgb_byte(srgba[1]),
|
||||||
linear_from_srgb_byte(srgba[2]),
|
linear_from_srgb_byte(srgba[2]),
|
||||||
srgba[3] as f32 / 255.0,
|
linear_from_alpha_byte(srgba[3]),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,11 +219,12 @@ impl From<Rgba> for Srgba {
|
||||||
srgb_byte_from_linear(rgba[0]),
|
srgb_byte_from_linear(rgba[0]),
|
||||||
srgb_byte_from_linear(rgba[1]),
|
srgb_byte_from_linear(rgba[1]),
|
||||||
srgb_byte_from_linear(rgba[2]),
|
srgb_byte_from_linear(rgba[2]),
|
||||||
clamp(rgba[3] * 255.0, 0.0..=255.0).round() as u8,
|
alpha_byte_from_linear(rgba[3]),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [0, 255] -> [0, 1]
|
||||||
fn linear_from_srgb_byte(s: u8) -> f32 {
|
fn linear_from_srgb_byte(s: u8) -> f32 {
|
||||||
if s <= 10 {
|
if s <= 10 {
|
||||||
s as f32 / 3294.6
|
s as f32 / 3294.6
|
||||||
|
@ -232,6 +233,11 @@ fn linear_from_srgb_byte(s: u8) -> f32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn linear_from_alpha_byte(a: u8) -> f32 {
|
||||||
|
a as f32 / 255.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [0, 1] -> [0, 255]
|
||||||
fn srgb_byte_from_linear(l: f32) -> u8 {
|
fn srgb_byte_from_linear(l: f32) -> u8 {
|
||||||
if l <= 0.0 {
|
if l <= 0.0 {
|
||||||
0
|
0
|
||||||
|
@ -244,6 +250,10 @@ fn srgb_byte_from_linear(l: f32) -> u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn alpha_byte_from_linear(a: f32) -> u8 {
|
||||||
|
clamp(a * 255.0, 0.0..=255.0).round() as u8
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_srgba_conversion() {
|
fn test_srgba_conversion() {
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
|
@ -274,19 +284,31 @@ impl Hsva {
|
||||||
pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
|
pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
|
||||||
Self { h, s, v, a }
|
Self { h, s, v, a }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Hsva> for Rgba {
|
/// From `sRGBA` with premultiplied alpha
|
||||||
fn from(hsva: Hsva) -> Rgba {
|
pub fn from_srgba_premultiplied(srgba: [u8; 4]) -> Self {
|
||||||
let Hsva { h, s, v, a } = hsva;
|
Self::from_rgba_premultiplied([
|
||||||
let (r, g, b) = rgb_from_hsv((h, s, v));
|
linear_from_srgb_byte(srgba[0]),
|
||||||
Rgba::new(a * r, a * g, a * b, a)
|
linear_from_srgb_byte(srgba[1]),
|
||||||
|
linear_from_srgb_byte(srgba[2]),
|
||||||
|
linear_from_alpha_byte(srgba[3]),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl From<Rgba> for Hsva {
|
/// From `sRGBA` without premultiplied alpha
|
||||||
fn from(rgba: Rgba) -> Hsva {
|
pub fn from_srgba_unmultiplied(srgba: [u8; 4]) -> Self {
|
||||||
|
Self::from_rgba_unmultiplied([
|
||||||
|
linear_from_srgb_byte(srgba[0]),
|
||||||
|
linear_from_srgb_byte(srgba[1]),
|
||||||
|
linear_from_srgb_byte(srgba[2]),
|
||||||
|
linear_from_alpha_byte(srgba[3]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// From linear RGBA with premultiplied alpha
|
||||||
|
pub fn from_rgba_premultiplied(rgba: [f32; 4]) -> Self {
|
||||||
#![allow(clippy::many_single_char_names)]
|
#![allow(clippy::many_single_char_names)]
|
||||||
let Rgba([r, g, b, a]) = rgba;
|
let [r, g, b, a] = rgba;
|
||||||
if a == 0.0 {
|
if a == 0.0 {
|
||||||
Hsva::default()
|
Hsva::default()
|
||||||
} else {
|
} else {
|
||||||
|
@ -294,6 +316,56 @@ impl From<Rgba> for Hsva {
|
||||||
Hsva { h, s, v, a }
|
Hsva { h, s, v, a }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// From linear RGBA without premultiplied alpha
|
||||||
|
pub fn from_rgba_unmultiplied(rgba: [f32; 4]) -> Self {
|
||||||
|
#![allow(clippy::many_single_char_names)]
|
||||||
|
let [r, g, b, a] = rgba;
|
||||||
|
let (h, s, v) = hsv_from_rgb((r, g, b));
|
||||||
|
Hsva { h, s, v, a }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_rgba_premultiplied(&self) -> [f32; 4] {
|
||||||
|
let [r, g, b, a] = self.to_rgba_unmultiplied();
|
||||||
|
[a * r, a * g, a * b, a]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
|
||||||
|
let Hsva { h, s, v, a } = *self;
|
||||||
|
let (r, g, b) = rgb_from_hsv((h, s, v));
|
||||||
|
[r, g, b, a]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_srgba_premultiplied(&self) -> [u8; 4] {
|
||||||
|
let [r, g, b, a] = self.to_rgba_premultiplied();
|
||||||
|
[
|
||||||
|
srgb_byte_from_linear(r),
|
||||||
|
srgb_byte_from_linear(g),
|
||||||
|
srgb_byte_from_linear(b),
|
||||||
|
alpha_byte_from_linear(a),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
|
||||||
|
let [r, g, b, a] = self.to_rgba_unmultiplied();
|
||||||
|
[
|
||||||
|
srgb_byte_from_linear(r),
|
||||||
|
srgb_byte_from_linear(g),
|
||||||
|
srgb_byte_from_linear(b),
|
||||||
|
alpha_byte_from_linear(a),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Hsva> for Rgba {
|
||||||
|
fn from(hsva: Hsva) -> Rgba {
|
||||||
|
Rgba(hsva.to_rgba_premultiplied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Rgba> for Hsva {
|
||||||
|
fn from(rgba: Rgba) -> Hsva {
|
||||||
|
Self::from_rgba_premultiplied(rgba.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Hsva> for Srgba {
|
impl From<Hsva> for Srgba {
|
||||||
|
|
|
@ -437,7 +437,7 @@ impl Stroke {
|
||||||
ui.label(format!("{}: ", text));
|
ui.label(format!("{}: ", text));
|
||||||
ui.add(DragValue::f32(width).speed(0.1).range(0.0..=5.0))
|
ui.add(DragValue::f32(width).speed(0.1).range(0.0..=5.0))
|
||||||
.tooltip_text("Width");
|
.tooltip_text("Width");
|
||||||
ui.color_edit_button(color);
|
ui.color_edit_button_srgba(color);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,6 +454,6 @@ fn ui_slider_vec2(ui: &mut Ui, value: &mut Vec2, range: std::ops::RangeInclusive
|
||||||
fn ui_color(ui: &mut Ui, srgba: &mut Srgba, text: &str) {
|
fn ui_color(ui: &mut Ui, srgba: &mut Srgba, text: &str) {
|
||||||
ui.horizontal_centered(|ui| {
|
ui.horizontal_centered(|ui| {
|
||||||
ui.label(format!("{}: ", text));
|
ui.label(format!("{}: ", text));
|
||||||
ui.color_edit_button(srgba);
|
ui.color_edit_button_srgba(srgba);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -481,12 +481,6 @@ impl Ui {
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a button with the given color.
|
|
||||||
/// If the user clicks the button, a full color picker is shown.
|
|
||||||
pub fn color_edit_button(&mut self, srgba: &mut Srgba) {
|
|
||||||
widgets::color_picker::color_edit_button(self, srgba)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ask to allocate a certain amount of space and return a Painter for that region.
|
/// Ask to allocate a certain amount of space and return a Painter for that region.
|
||||||
///
|
///
|
||||||
/// You may get back a `Painter` with a smaller or larger size than what you desired,
|
/// You may get back a `Painter` with a smaller or larger size than what you desired,
|
||||||
|
@ -497,6 +491,63 @@ impl Ui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Colors
|
||||||
|
impl Ui {
|
||||||
|
/// Shows a button with the given color.
|
||||||
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
|
pub fn color_edit_button_srgba(&mut self, srgba: &mut Srgba) -> Response {
|
||||||
|
widgets::color_picker::color_edit_button_srgba(self, srgba)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a button with the given color.
|
||||||
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
|
pub fn color_edit_button_hsva(&mut self, hsva: &mut Hsva) -> Response {
|
||||||
|
widgets::color_picker::color_edit_button_hsva(self, hsva)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a button with the given color.
|
||||||
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
|
/// The given color is in `sRGBA` space with premultiplied alpha
|
||||||
|
pub fn color_edit_button_srgba_premultiplied(&mut self, srgba: &mut [u8; 4]) -> Response {
|
||||||
|
let mut color = Srgba(*srgba);
|
||||||
|
let response = self.color_edit_button_srgba(&mut color);
|
||||||
|
*srgba = color.0;
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a button with the given color.
|
||||||
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
|
/// The given color is in `sRGBA` space without premultiplied alpha.
|
||||||
|
/// If unsure, what "premultiplied alpha" is, then this is probably the function you want to use.
|
||||||
|
pub fn color_edit_button_srgba_unmultiplied(&mut self, srgba: &mut [u8; 4]) -> Response {
|
||||||
|
let mut hsva = Hsva::from_srgba_unmultiplied(*srgba);
|
||||||
|
let response = self.color_edit_button_hsva(&mut hsva);
|
||||||
|
*srgba = hsva.to_srgba_unmultiplied();
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a button with the given color.
|
||||||
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
|
/// The given color is in linear RGBA space with premultiplied alpha
|
||||||
|
pub fn color_edit_button_rgba_premultiplied(&mut self, rgba: &mut [f32; 4]) -> Response {
|
||||||
|
let mut hsva = Hsva::from_rgba_premultiplied(*rgba);
|
||||||
|
let response = self.color_edit_button_hsva(&mut hsva);
|
||||||
|
*rgba = hsva.to_rgba_premultiplied();
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a button with the given color.
|
||||||
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
|
/// The given color is in linear RGBA space without premultiplied alpha.
|
||||||
|
/// If unsure, what "premultiplied alpha" is, then this is probably the function you want to use.
|
||||||
|
pub fn color_edit_button_rgba_unmultiplied(&mut self, rgba: &mut [f32; 4]) -> Response {
|
||||||
|
let mut hsva = Hsva::from_rgba_unmultiplied(*rgba);
|
||||||
|
let response = self.color_edit_button_hsva(&mut hsva);
|
||||||
|
*rgba = hsva.to_rgba_unmultiplied();
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # Adding Containers / Sub-uis:
|
/// # Adding Containers / Sub-uis:
|
||||||
impl Ui {
|
impl Ui {
|
||||||
pub fn collapsing<R>(
|
pub fn collapsing<R>(
|
||||||
|
|
|
@ -38,7 +38,7 @@ fn background_checkers(painter: &Painter, rect: Rect) {
|
||||||
painter.add(PaintCmd::Triangles(triangles));
|
painter.add(PaintCmd::Triangles(triangles));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_color(ui: &mut Ui, color: Srgba, desired_size: Vec2) -> Rect {
|
fn show_color(ui: &mut Ui, color: Srgba, desired_size: Vec2) -> Response {
|
||||||
let rect = ui.allocate_space(desired_size);
|
let rect = ui.allocate_space(desired_size);
|
||||||
background_checkers(ui.painter(), rect);
|
background_checkers(ui.painter(), rect);
|
||||||
ui.painter().add(PaintCmd::Rect {
|
ui.painter().add(PaintCmd::Rect {
|
||||||
|
@ -47,7 +47,7 @@ fn show_color(ui: &mut Ui, color: Srgba, desired_size: Vec2) -> Rect {
|
||||||
fill: color,
|
fill: color,
|
||||||
stroke: Stroke::new(3.0, color.to_opaque()),
|
stroke: Stroke::new(3.0, color.to_opaque()),
|
||||||
});
|
});
|
||||||
rect
|
ui.interact_hover(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_button(ui: &mut Ui, color: Srgba) -> Response {
|
fn color_button(ui: &mut Ui, color: Srgba) -> Response {
|
||||||
|
@ -184,29 +184,38 @@ fn color_slider_2d(
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva) {
|
fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma) {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
let current_color_size = vec2(
|
let current_color_size = vec2(
|
||||||
ui.style().spacing.slider_width,
|
ui.style().spacing.slider_width,
|
||||||
ui.style().spacing.clickable_diameter * 2.0,
|
ui.style().spacing.clickable_diameter * 2.0,
|
||||||
);
|
);
|
||||||
let current_color_rect = show_color(ui, (*hsva).into(), current_color_size);
|
|
||||||
if ui.hovered(current_color_rect) {
|
|
||||||
show_tooltip_text(ui.ctx(), "Current color");
|
|
||||||
}
|
|
||||||
|
|
||||||
let opaque = Hsva { a: 1.0, ..*hsva };
|
show_color(ui, (*hsva).into(), current_color_size).tooltip_text("Current color");
|
||||||
let Hsva { h, s, v, a } = hsva;
|
|
||||||
color_slider_2d(ui, h, s, |h, s| Hsva::new(h, s, 1.0, 1.0).into())
|
show_color(ui, HsvaGamma { a: 1.0, ..*hsva }.into(), current_color_size)
|
||||||
|
.tooltip_text("Current color (opaque)");
|
||||||
|
|
||||||
|
let opaque = HsvaGamma { a: 1.0, ..*hsva };
|
||||||
|
let HsvaGamma { h, s, v, a } = hsva;
|
||||||
|
color_slider_2d(ui, h, s, |h, s| HsvaGamma::new(h, s, 1.0, 1.0).into())
|
||||||
.tooltip_text("Hue - Saturation");
|
.tooltip_text("Hue - Saturation");
|
||||||
color_slider_2d(ui, v, s, |v, s| Hsva { v, s, ..opaque }.into())
|
color_slider_2d(ui, v, s, |v, s| HsvaGamma { v, s, ..opaque }.into())
|
||||||
.tooltip_text("Value - Saturation");
|
.tooltip_text("Value - Saturation");
|
||||||
ui.label("Alpha:");
|
color_slider_1d(ui, h, |h| HsvaGamma { h, ..opaque }.into()).tooltip_text("Hue");
|
||||||
color_slider_1d(ui, a, |a| Hsva { a, ..opaque }.into()).tooltip_text("Alpha");
|
color_slider_1d(ui, s, |s| HsvaGamma { s, ..opaque }.into()).tooltip_text("Saturation");
|
||||||
|
color_slider_1d(ui, v, |v| HsvaGamma { v, ..opaque }.into()).tooltip_text("Value");
|
||||||
|
color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).tooltip_text("Alpha");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_picker_hsva(ui: &mut Ui, hsva: &mut Hsva) {
|
fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva) {
|
||||||
|
let mut hsvag = HsvaGamma::from(*hsva);
|
||||||
|
color_picker_hsvag_2d(ui, &mut hsvag);
|
||||||
|
*hsva = Hsva::from(hsvag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva) -> Response {
|
||||||
let id = ui.make_position_id().with("foo");
|
let id = ui.make_position_id().with("foo");
|
||||||
let button_response = color_button(ui, (*hsva).into()).tooltip_text("Click to edit color");
|
let button_response = color_button(ui, (*hsva).into()).tooltip_text("Click to edit color");
|
||||||
|
|
||||||
|
@ -231,12 +240,13 @@ fn color_picker_hsva(ui: &mut Ui, hsva: &mut Hsva) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button_response
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: return Response so user can show a tooltip
|
|
||||||
/// Shows a button with the given color.
|
/// Shows a button with the given color.
|
||||||
/// If the user clicks the button, a full color picker is shown.
|
/// If the user clicks the button, a full color picker is shown.
|
||||||
pub fn color_edit_button(ui: &mut Ui, srgba: &mut Srgba) {
|
pub fn color_edit_button_srgba(ui: &mut Ui, srgba: &mut Srgba) -> Response {
|
||||||
// To ensure we keep hue slider when `srgba` is grey we store the
|
// To ensure we keep hue slider when `srgba` is grey we store the
|
||||||
// full `Hsva` in a cache:
|
// full `Hsva` in a cache:
|
||||||
|
|
||||||
|
@ -248,9 +258,93 @@ pub fn color_edit_button(ui: &mut Ui, srgba: &mut Srgba) {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| Hsva::from(*srgba));
|
.unwrap_or_else(|| Hsva::from(*srgba));
|
||||||
|
|
||||||
color_picker_hsva(ui, &mut hsva);
|
let response = color_edit_button_hsva(ui, &mut hsva);
|
||||||
|
|
||||||
*srgba = Srgba::from(hsva);
|
*srgba = Srgba::from(hsva);
|
||||||
|
|
||||||
ui.ctx().memory().color_cache.set(*srgba, hsva);
|
ui.ctx().memory().color_cache.set(*srgba, hsva);
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Like Hsva but with the `v` (value/brightness) being gamma corrected
|
||||||
|
/// so that it is perceptually even in sliders.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
struct HsvaGamma {
|
||||||
|
/// hue 0-1
|
||||||
|
pub h: f32,
|
||||||
|
/// saturation 0-1
|
||||||
|
pub s: f32,
|
||||||
|
/// value 0-1, in gamma-space (perceptually even)
|
||||||
|
pub v: f32,
|
||||||
|
/// alpha 0-1
|
||||||
|
pub a: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HsvaGamma {
|
||||||
|
pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
|
||||||
|
Self { h, s, v, a }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const GAMMA: f32 = 2.2;
|
||||||
|
|
||||||
|
impl From<HsvaGamma> for Rgba {
|
||||||
|
fn from(hsvag: HsvaGamma) -> Rgba {
|
||||||
|
Hsva::from(hsvag).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HsvaGamma> for Srgba {
|
||||||
|
fn from(hsvag: HsvaGamma) -> Srgba {
|
||||||
|
Rgba::from(hsvag).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HsvaGamma> for Hsva {
|
||||||
|
fn from(hsvag: HsvaGamma) -> Hsva {
|
||||||
|
let HsvaGamma { h, s, v, a } = hsvag;
|
||||||
|
Hsva {
|
||||||
|
h,
|
||||||
|
s,
|
||||||
|
v: linear_from_srgb(v),
|
||||||
|
a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Hsva> for HsvaGamma {
|
||||||
|
fn from(hsva: Hsva) -> HsvaGamma {
|
||||||
|
let Hsva { h, s, v, a } = hsva;
|
||||||
|
HsvaGamma {
|
||||||
|
h,
|
||||||
|
s,
|
||||||
|
v: srgb_from_linear(v),
|
||||||
|
a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [0, 1] -> [0, 1]
|
||||||
|
fn linear_from_srgb(s: f32) -> f32 {
|
||||||
|
if s < 0.0 {
|
||||||
|
-linear_from_srgb(-s)
|
||||||
|
} else if s <= 0.04045 {
|
||||||
|
s / 12.92
|
||||||
|
} else {
|
||||||
|
((s + 0.055) / 1.055).powf(2.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [0, 1] -> [0, 1]
|
||||||
|
fn srgb_from_linear(l: f32) -> f32 {
|
||||||
|
if l < 0.0 {
|
||||||
|
-srgb_from_linear(-l)
|
||||||
|
} else if l <= 0.0031308 {
|
||||||
|
12.92 * l
|
||||||
|
} else {
|
||||||
|
1.055 * l.powf(1.0 / 2.4) - 0.055
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue