Add `DragValue`s for RGB(A) in the color picker (#2734)
Added some DragValue widgets in the color_picker widget as input fields for managing the RGBA values. In case the provided values result in a not valid premultiplied alpha RGBA color, a button will appear next to the input fields, to be used to multiply the values with the alpha channel.  Closes <https://github.com/emilk/egui/issues/2716>. --------- Co-authored-by: IVANMK-7 <68190772+IVANMK-7@users.noreply.github.com> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> Co-authored-by: Brian Janssen <tosti007@users.noreply.github.com>
This commit is contained in:
parent
51e5d28b39
commit
bfed2b4195
|
|
@ -28,23 +28,23 @@ impl Hsva {
|
|||
|
||||
/// From `sRGBA` with premultiplied alpha
|
||||
#[inline]
|
||||
pub fn from_srgba_premultiplied(srgba: [u8; 4]) -> Self {
|
||||
pub fn from_srgba_premultiplied([r, g, b, a]: [u8; 4]) -> Self {
|
||||
Self::from_rgba_premultiplied(
|
||||
linear_f32_from_gamma_u8(srgba[0]),
|
||||
linear_f32_from_gamma_u8(srgba[1]),
|
||||
linear_f32_from_gamma_u8(srgba[2]),
|
||||
linear_f32_from_linear_u8(srgba[3]),
|
||||
linear_f32_from_gamma_u8(r),
|
||||
linear_f32_from_gamma_u8(g),
|
||||
linear_f32_from_gamma_u8(b),
|
||||
linear_f32_from_linear_u8(a),
|
||||
)
|
||||
}
|
||||
|
||||
/// From `sRGBA` without premultiplied alpha
|
||||
#[inline]
|
||||
pub fn from_srgba_unmultiplied(srgba: [u8; 4]) -> Self {
|
||||
pub fn from_srgba_unmultiplied([r, g, b, a]: [u8; 4]) -> Self {
|
||||
Self::from_rgba_unmultiplied(
|
||||
linear_f32_from_gamma_u8(srgba[0]),
|
||||
linear_f32_from_gamma_u8(srgba[1]),
|
||||
linear_f32_from_gamma_u8(srgba[2]),
|
||||
linear_f32_from_linear_u8(srgba[3]),
|
||||
linear_f32_from_gamma_u8(r),
|
||||
linear_f32_from_gamma_u8(g),
|
||||
linear_f32_from_gamma_u8(b),
|
||||
linear_f32_from_linear_u8(a),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +83,15 @@ impl Hsva {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_additive_srgb([r, g, b]: [u8; 3]) -> Self {
|
||||
Self::from_additive_rgb([
|
||||
linear_f32_from_gamma_u8(r),
|
||||
linear_f32_from_gamma_u8(g),
|
||||
linear_f32_from_gamma_u8(b),
|
||||
])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rgb(rgb: [f32; 3]) -> Self {
|
||||
let (h, s, v) = hsv_from_rgb(rgb);
|
||||
|
|
@ -131,6 +140,8 @@ impl Hsva {
|
|||
}
|
||||
}
|
||||
|
||||
/// To linear space rgba in 0-1 range.
|
||||
///
|
||||
/// Represents additive colors using a negative alpha.
|
||||
#[inline]
|
||||
pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
|
||||
|
|
@ -150,6 +161,7 @@ impl Hsva {
|
|||
]
|
||||
}
|
||||
|
||||
/// To gamma-space 0-255.
|
||||
#[inline]
|
||||
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
|
||||
let [r, g, b, a] = self.to_rgba_unmultiplied();
|
||||
|
|
|
|||
|
|
@ -829,6 +829,9 @@ pub struct Visuals {
|
|||
|
||||
/// Show a spinner when loading an image.
|
||||
pub image_loading_spinners: bool,
|
||||
|
||||
/// How to display numeric color values.
|
||||
pub numeric_color_space: NumericColorSpace,
|
||||
}
|
||||
|
||||
impl Visuals {
|
||||
|
|
@ -1149,6 +1152,8 @@ impl Visuals {
|
|||
interact_cursor: None,
|
||||
|
||||
image_loading_spinners: true,
|
||||
|
||||
numeric_color_space: NumericColorSpace::GammaByte,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1711,6 +1716,8 @@ impl Visuals {
|
|||
interact_cursor,
|
||||
|
||||
image_loading_spinners,
|
||||
|
||||
numeric_color_space,
|
||||
} = self;
|
||||
|
||||
ui.collapsing("Background Colors", |ui| {
|
||||
|
|
@ -1791,6 +1798,11 @@ impl Visuals {
|
|||
ui.checkbox(image_loading_spinners, "Image loading spinners")
|
||||
.on_hover_text("Show a spinner when an Image is loading");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Color picker type:");
|
||||
numeric_color_space.toggle_button_ui(ui);
|
||||
});
|
||||
|
||||
ui.vertical_centered(|ui| reset_button(ui, self));
|
||||
}
|
||||
}
|
||||
|
|
@ -1918,3 +1930,45 @@ impl HandleShape {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// How to display numeric color values.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum NumericColorSpace {
|
||||
/// RGB is 0-255 in gamma space.
|
||||
///
|
||||
/// Alpha is 0-255 in linear space .
|
||||
GammaByte,
|
||||
|
||||
/// 0-1 in linear space.
|
||||
Linear,
|
||||
// TODO(emilk): add Hex as an option
|
||||
}
|
||||
|
||||
impl NumericColorSpace {
|
||||
pub fn toggle_button_ui(&mut self, ui: &mut Ui) -> crate::Response {
|
||||
let tooltip = match self {
|
||||
Self::GammaByte => "Showing color values in 0-255 gamma space",
|
||||
Self::Linear => "Showing color values in 0-1 linear space",
|
||||
};
|
||||
|
||||
let mut response = ui.button(self.to_string()).on_hover_text(tooltip);
|
||||
if response.clicked() {
|
||||
*self = match self {
|
||||
Self::GammaByte => Self::Linear,
|
||||
Self::Linear => Self::GammaByte,
|
||||
};
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NumericColorSpace {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
NumericColorSpace::GammaByte => write!(f, "U8"),
|
||||
NumericColorSpace::Linear => write!(f, "F"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,50 +216,92 @@ fn color_slider_2d(
|
|||
response
|
||||
}
|
||||
|
||||
/// We use a negative alpha for additive colors within this file (a bit ironic).
|
||||
///
|
||||
/// We use alpha=0 to mean "transparent".
|
||||
fn is_additive_alpha(a: f32) -> bool {
|
||||
a < 0.0
|
||||
}
|
||||
|
||||
/// What options to show for alpha
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Alpha {
|
||||
// Set alpha to 1.0, and show no option for it.
|
||||
/// Set alpha to 1.0, and show no option for it.
|
||||
Opaque,
|
||||
// Only show normal blend options for it.
|
||||
|
||||
/// Only show normal blend options for alpha.
|
||||
OnlyBlend,
|
||||
// Show both blend and additive options.
|
||||
|
||||
/// Show both blend and additive options.
|
||||
BlendOrAdditive,
|
||||
}
|
||||
|
||||
fn color_text_ui(ui: &mut Ui, color: impl Into<Color32>, alpha: Alpha) {
|
||||
let color = color.into();
|
||||
ui.horizontal(|ui| {
|
||||
let [r, g, b, a] = color.to_array();
|
||||
fn color_picker_hsvag_2d(ui: &mut Ui, hsvag: &mut HsvaGamma, alpha: Alpha) {
|
||||
use crate::style::NumericColorSpace;
|
||||
|
||||
if ui.button("📋").on_hover_text("Click to copy").clicked() {
|
||||
if alpha == Alpha::Opaque {
|
||||
ui.ctx().copy_text(format!("{r}, {g}, {b}"));
|
||||
} else {
|
||||
ui.ctx().copy_text(format!("{r}, {g}, {b}, {a}"));
|
||||
let alpha_control = if is_additive_alpha(hsvag.a) {
|
||||
Alpha::Opaque // no alpha control for additive colors
|
||||
} else {
|
||||
alpha
|
||||
};
|
||||
|
||||
match ui.style().visuals.numeric_color_space {
|
||||
NumericColorSpace::GammaByte => {
|
||||
let mut srgba_unmultiplied = Hsva::from(*hsvag).to_srgba_unmultiplied();
|
||||
// Only update if changed to avoid rounding issues.
|
||||
if srgba_edit_ui(ui, &mut srgba_unmultiplied, alpha_control) {
|
||||
if is_additive_alpha(hsvag.a) {
|
||||
let alpha = hsvag.a;
|
||||
|
||||
*hsvag = HsvaGamma::from(Hsva::from_additive_srgb([
|
||||
srgba_unmultiplied[0],
|
||||
srgba_unmultiplied[1],
|
||||
srgba_unmultiplied[2],
|
||||
]));
|
||||
|
||||
// Don't edit the alpha:
|
||||
hsvag.a = alpha;
|
||||
} else {
|
||||
// Normal blending.
|
||||
*hsvag = HsvaGamma::from(Hsva::from_srgba_unmultiplied(srgba_unmultiplied));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if alpha == Alpha::Opaque {
|
||||
ui.label(format!("rgb({r}, {g}, {b})"))
|
||||
.on_hover_text("Red Green Blue");
|
||||
} else {
|
||||
ui.label(format!("rgba({r}, {g}, {b}, {a})"))
|
||||
.on_hover_text("Red Green Blue with premultiplied Alpha");
|
||||
NumericColorSpace::Linear => {
|
||||
let mut rgba_unmultiplied = Hsva::from(*hsvag).to_rgba_unmultiplied();
|
||||
// Only update if changed to avoid rounding issues.
|
||||
if rgba_edit_ui(ui, &mut rgba_unmultiplied, alpha_control) {
|
||||
if is_additive_alpha(hsvag.a) {
|
||||
let alpha = hsvag.a;
|
||||
|
||||
*hsvag = HsvaGamma::from(Hsva::from_rgb([
|
||||
rgba_unmultiplied[0],
|
||||
rgba_unmultiplied[1],
|
||||
rgba_unmultiplied[2],
|
||||
]));
|
||||
|
||||
// Don't edit the alpha:
|
||||
hsvag.a = alpha;
|
||||
} else {
|
||||
// Normal blending.
|
||||
*hsvag = HsvaGamma::from(Hsva::from_rgba_unmultiplied(
|
||||
rgba_unmultiplied[0],
|
||||
rgba_unmultiplied[1],
|
||||
rgba_unmultiplied[2],
|
||||
rgba_unmultiplied[3],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
|
||||
let current_color_size = vec2(ui.spacing().slider_width, ui.spacing().interact_size.y);
|
||||
show_color(ui, *hsva, current_color_size).on_hover_text("Selected color");
|
||||
|
||||
color_text_ui(ui, *hsva, alpha);
|
||||
show_color(ui, *hsvag, current_color_size).on_hover_text("Selected color");
|
||||
|
||||
if alpha == Alpha::BlendOrAdditive {
|
||||
// We signal additive blending by storing a negative alpha (a bit ironic).
|
||||
let a = &mut hsva.a;
|
||||
let mut additive = *a < 0.0;
|
||||
let a = &mut hsvag.a;
|
||||
let mut additive = is_additive_alpha(*a);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Blending:");
|
||||
ui.radio_value(&mut additive, false, "Normal");
|
||||
|
|
@ -274,26 +316,20 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
|
|||
}
|
||||
});
|
||||
}
|
||||
let additive = hsva.a < 0.0;
|
||||
|
||||
let opaque = HsvaGamma { a: 1.0, ..*hsva };
|
||||
let opaque = HsvaGamma { a: 1.0, ..*hsvag };
|
||||
|
||||
if alpha == Alpha::Opaque {
|
||||
hsva.a = 1.0;
|
||||
} else {
|
||||
let a = &mut hsva.a;
|
||||
let HsvaGamma { h, s, v, a: _ } = hsvag;
|
||||
|
||||
if alpha == Alpha::OnlyBlend {
|
||||
if *a < 0.0 {
|
||||
*a = 0.5; // was additive, but isn't allowed to be
|
||||
}
|
||||
color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).on_hover_text("Alpha");
|
||||
} else if !additive {
|
||||
color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).on_hover_text("Alpha");
|
||||
}
|
||||
if false {
|
||||
color_slider_1d(ui, s, |s| HsvaGamma { s, ..opaque }.into()).on_hover_text("Saturation");
|
||||
}
|
||||
|
||||
let HsvaGamma { h, s, v, a: _ } = hsva;
|
||||
if false {
|
||||
color_slider_1d(ui, v, |v| HsvaGamma { v, ..opaque }.into()).on_hover_text("Value");
|
||||
}
|
||||
|
||||
color_slider_2d(ui, s, v, |s, v| HsvaGamma { s, v, ..opaque }.into());
|
||||
|
||||
color_slider_1d(ui, h, |h| {
|
||||
HsvaGamma {
|
||||
|
|
@ -306,15 +342,106 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
|
|||
})
|
||||
.on_hover_text("Hue");
|
||||
|
||||
if false {
|
||||
color_slider_1d(ui, s, |s| HsvaGamma { s, ..opaque }.into()).on_hover_text("Saturation");
|
||||
let additive = is_additive_alpha(hsvag.a);
|
||||
|
||||
if alpha == Alpha::Opaque {
|
||||
hsvag.a = 1.0;
|
||||
} else {
|
||||
let a = &mut hsvag.a;
|
||||
|
||||
if alpha == Alpha::OnlyBlend {
|
||||
if is_additive_alpha(*a) {
|
||||
*a = 0.5; // was additive, but isn't allowed to be
|
||||
}
|
||||
color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).on_hover_text("Alpha");
|
||||
} else if !additive {
|
||||
color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).on_hover_text("Alpha");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn input_type_button_ui(ui: &mut Ui) {
|
||||
let mut input_type = ui.ctx().style().visuals.numeric_color_space;
|
||||
if input_type.toggle_button_ui(ui).changed() {
|
||||
ui.ctx().style_mut(|s| {
|
||||
s.visuals.numeric_color_space = input_type;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows 4 `DragValue` widgets to be used to edit the RGBA u8 values.
|
||||
/// Alpha's `DragValue` is hidden when `Alpha::Opaque`.
|
||||
///
|
||||
/// Returns `true` on change.
|
||||
fn srgba_edit_ui(ui: &mut Ui, [r, g, b, a]: &mut [u8; 4], alpha: Alpha) -> bool {
|
||||
let mut edited = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
input_type_button_ui(ui);
|
||||
|
||||
if ui
|
||||
.button("📋")
|
||||
.on_hover_text("Click to copy color values")
|
||||
.clicked()
|
||||
{
|
||||
if alpha == Alpha::Opaque {
|
||||
ui.ctx().copy_text(format!("{r}, {g}, {b}"));
|
||||
} else {
|
||||
ui.ctx().copy_text(format!("{r}, {g}, {b}, {a}"));
|
||||
}
|
||||
}
|
||||
edited |= DragValue::new(r).speed(0.5).prefix("R ").ui(ui).changed();
|
||||
edited |= DragValue::new(g).speed(0.5).prefix("G ").ui(ui).changed();
|
||||
edited |= DragValue::new(b).speed(0.5).prefix("B ").ui(ui).changed();
|
||||
if alpha != Alpha::Opaque {
|
||||
edited |= DragValue::new(a).speed(0.5).prefix("A ").ui(ui).changed();
|
||||
}
|
||||
});
|
||||
|
||||
edited
|
||||
}
|
||||
|
||||
/// Shows 4 `DragValue` widgets to be used to edit the RGBA f32 values.
|
||||
/// Alpha's `DragValue` is hidden when `Alpha::Opaque`.
|
||||
///
|
||||
/// Returns `true` on change.
|
||||
fn rgba_edit_ui(ui: &mut Ui, [r, g, b, a]: &mut [f32; 4], alpha: Alpha) -> bool {
|
||||
fn drag_value(ui: &mut Ui, prefix: &str, value: &mut f32) -> Response {
|
||||
DragValue::new(value)
|
||||
.speed(0.003)
|
||||
.prefix(prefix)
|
||||
.clamp_range(0.0..=1.0)
|
||||
.custom_formatter(|n, _| format!("{n:.03}"))
|
||||
.ui(ui)
|
||||
}
|
||||
|
||||
if false {
|
||||
color_slider_1d(ui, v, |v| HsvaGamma { v, ..opaque }.into()).on_hover_text("Value");
|
||||
}
|
||||
let mut edited = false;
|
||||
|
||||
color_slider_2d(ui, s, v, |s, v| HsvaGamma { s, v, ..opaque }.into());
|
||||
ui.horizontal(|ui| {
|
||||
input_type_button_ui(ui);
|
||||
|
||||
if ui
|
||||
.button("📋")
|
||||
.on_hover_text("Click to copy color values")
|
||||
.clicked()
|
||||
{
|
||||
if alpha == Alpha::Opaque {
|
||||
ui.ctx().copy_text(format!("{r:.03}, {g:.03}, {b:.03}"));
|
||||
} else {
|
||||
ui.ctx()
|
||||
.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
|
||||
}
|
||||
}
|
||||
|
||||
edited |= drag_value(ui, "R ", r).changed();
|
||||
edited |= drag_value(ui, "G ", g).changed();
|
||||
edited |= drag_value(ui, "B ", b).changed();
|
||||
if alpha != Alpha::Opaque {
|
||||
edited |= drag_value(ui, "A ", a).changed();
|
||||
}
|
||||
});
|
||||
|
||||
edited
|
||||
}
|
||||
|
||||
/// Shows a color picker where the user can change the given [`Hsva`] color.
|
||||
|
|
@ -357,7 +484,7 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res
|
|||
ui.memory_mut(|mem| mem.toggle_popup(popup_id));
|
||||
}
|
||||
|
||||
const COLOR_SLIDER_WIDTH: f32 = 210.0;
|
||||
const COLOR_SLIDER_WIDTH: f32 = 275.0;
|
||||
|
||||
// TODO(emilk): make it easier to show a temporary popup that closes when you click outside it
|
||||
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue