diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f92db61..ed50e990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed 🔧 * Renamed `Srgba` to `Color32`. +* All color contructions now starts with `from_`, e.g. `Color32::from_rgb`. * Renamed `FontFamily::VariableWidth` to `FontFamily::Proportional`. * Removed `pixels_per_point` from `FontDefinitions`. diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index d1adc7f2..d9d85ace 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -73,7 +73,7 @@ impl Frame { Self { margin: Vec2::new(10.0, 10.0), corner_radius: 5.0, - fill: Color32::black_alpha(250), + fill: Color32::from_black_alpha(250), stroke: style.visuals.widgets.noninteractive.bg_stroke, ..Default::default() } diff --git a/egui/src/paint/color.rs b/egui/src/paint/color.rs index 838e74f1..4e752d5a 100644 --- a/egui/src/paint/color.rs +++ b/egui/src/paint/color.rs @@ -70,19 +70,19 @@ impl Color32 { Self([r, g, b, a]) } - pub const fn gray(l: u8) -> Self { + pub const fn from_gray(l: u8) -> Self { Self([l, l, l, 255]) } - pub const fn black_alpha(a: u8) -> Self { + pub const fn from_black_alpha(a: u8) -> Self { Self([0, 0, 0, a]) } - pub fn white_alpha(a: u8) -> Self { - Rgba::white_alpha(linear_from_alpha_byte(a)).into() + pub fn from_white_alpha(a: u8) -> Self { + Rgba::from_white_alpha(linear_from_alpha_byte(a)).into() } - pub const fn additive_luminance(l: u8) -> Self { + pub const fn from_additive_luminance(l: u8) -> Self { Self([l, l, l, 0]) } @@ -138,43 +138,49 @@ impl std::ops::IndexMut for Rgba { } impl Rgba { - pub const TRANSPARENT: Rgba = Rgba::new(0.0, 0.0, 0.0, 0.0); - pub const BLACK: Rgba = Rgba::new(0.0, 0.0, 0.0, 1.0); - pub const WHITE: Rgba = Rgba::new(1.0, 1.0, 1.0, 1.0); - pub const RED: Rgba = Rgba::new(1.0, 0.0, 0.0, 1.0); - pub const GREEN: Rgba = Rgba::new(0.0, 1.0, 0.0, 1.0); - pub const BLUE: Rgba = Rgba::new(0.0, 0.0, 1.0, 1.0); + pub const TRANSPARENT: Rgba = Rgba::from_rgba_premultiplied(0.0, 0.0, 0.0, 0.0); + pub const BLACK: Rgba = Rgba::from_rgb(0.0, 0.0, 0.0); + pub const WHITE: Rgba = Rgba::from_rgb(1.0, 1.0, 1.0); + pub const RED: Rgba = Rgba::from_rgb(1.0, 0.0, 0.0); + pub const GREEN: Rgba = Rgba::from_rgb(0.0, 1.0, 0.0); + pub const BLUE: Rgba = Rgba::from_rgb(0.0, 0.0, 1.0); - pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + pub const fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { Self([r, g, b, a]) } - pub const fn rgb(r: f32, g: f32, b: f32) -> Self { + pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self { Self([r, g, b, 1.0]) } - pub const fn gray(l: f32) -> Self { + pub const fn from_gray(l: f32) -> Self { Self([l, l, l, 1.0]) } - pub fn luminance_alpha(l: f32, a: f32) -> Self { + pub fn from_luminance_alpha(l: f32, a: f32) -> Self { debug_assert!(0.0 <= l && l <= 1.0); debug_assert!(0.0 <= a && a <= 1.0); Self([l * a, l * a, l * a, a]) } /// Transparent black - pub fn black_alpha(a: f32) -> Self { + pub fn from_black_alpha(a: f32) -> Self { debug_assert!(0.0 <= a && a <= 1.0); Self([0.0, 0.0, 0.0, a]) } /// Transparent white - pub fn white_alpha(a: f32) -> Self { + pub fn from_white_alpha(a: f32) -> Self { debug_assert!(0.0 <= a && a <= 1.0); Self([a, a, a, a]) } + /// Return an additive version of this color (alpha = 0) + pub fn additive(self) -> Self { + let [r, g, b, _] = self.0; + Self([r, g, b, 0.0]) + } + /// Multiply with e.g. 0.5 to make us half transparent pub fn multiply(self, alpha: f32) -> Self { Self([ @@ -206,11 +212,11 @@ impl Rgba { /// Returns an opaque version of self pub fn to_opaque(&self) -> Self { if self.a() == 0.0 { - // additive or fully transparent - Self::new(self.r(), self.g(), self.b(), 1.0) + // Additive or fully transparent black. + Self::from_rgba_premultiplied(self.r(), self.g(), self.b(), 1.0) } else { - // un-multiply alpha - Self::new( + // un-multiply alpha: + Self::from_rgba_premultiplied( self.r() / self.a(), self.g() / self.a(), self.b() / self.a(), @@ -345,7 +351,7 @@ pub struct Hsva { pub s: f32, /// value 0-1 pub v: f32, - /// alpha 0-1 + /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). pub a: f32, } @@ -375,33 +381,79 @@ impl Hsva { } /// From linear RGBA with premultiplied alpha - pub fn from_rgba_premultiplied(rgba: [f32; 4]) -> Self { + pub fn from_rgba_premultiplied([r, g, b, a]: [f32; 4]) -> Self { #![allow(clippy::many_single_char_names)] - let [r, g, b, a] = rgba; if a == 0.0 { - Hsva::default() + if r == 0.0 && b == 0.0 && a == 0.0 { + Hsva::default() + } else { + Hsva::from_additive_rgb([r, g, b]) + } } else { - let (h, s, v) = hsv_from_rgb((r / a, g / a, b / a)); + let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]); Hsva { h, s, v, a } } } /// From linear RGBA without premultiplied alpha - pub fn from_rgba_unmultiplied(rgba: [f32; 4]) -> Self { + pub fn from_rgba_unmultiplied([r, g, b, a]: [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)); + let (h, s, v) = hsv_from_rgb([r, g, b]); Hsva { h, s, v, a } } + pub fn from_additive_rgb(rgb: [f32; 3]) -> Self { + let (h, s, v) = hsv_from_rgb(rgb); + Hsva { + h, + s, + v, + a: -0.5, // anything negative is treated as additive + } + } + + pub fn from_rgb(rgb: [f32; 3]) -> Self { + let (h, s, v) = hsv_from_rgb(rgb); + Hsva { h, s, v, a: 1.0 } + } + + pub fn from_srgb([r, g, b]: [u8; 3]) -> Self { + Self::from_rgb([ + linear_from_gamma_byte(r), + linear_from_gamma_byte(g), + linear_from_gamma_byte(b), + ]) + } + + // ------------------------------------------------------------------------ + + pub fn to_rgb(&self) -> [f32; 3] { + rgb_from_hsv((self.h, self.s, self.v)) + } + + pub fn to_srgb(&self) -> [u8; 3] { + let [r, g, b] = self.to_rgb(); + [ + gamma_byte_from_linear(r), + gamma_byte_from_linear(g), + gamma_byte_from_linear(b), + ] + } + pub fn to_rgba_premultiplied(&self) -> [f32; 4] { let [r, g, b, a] = self.to_rgba_unmultiplied(); - [a * r, a * g, a * b, a] + let additive = a < 0.0; + if additive { + [r, g, b, 0.0] + } else { + [a * r, a * g, a * b, a] + } } + /// Represents additive colors using a negative alpha. 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)); + let [r, g, b] = rgb_from_hsv((h, s, v)); [r, g, b, a] } @@ -421,7 +473,7 @@ impl Hsva { gamma_byte_from_linear(r), gamma_byte_from_linear(g), gamma_byte_from_linear(b), - alpha_byte_from_linear(a), + alpha_byte_from_linear(a.abs()), ] } } @@ -449,7 +501,7 @@ impl From for Hsva { } /// All ranges in 0-1, rgb is linear. -pub fn hsv_from_rgb((r, g, b): (f32, f32, f32)) -> (f32, f32, f32) { +pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) { #![allow(clippy::float_cmp)] #![allow(clippy::many_single_char_names)] let min = r.min(g.min(b)); @@ -473,7 +525,7 @@ pub fn hsv_from_rgb((r, g, b): (f32, f32, f32)) -> (f32, f32, f32) { } /// All ranges in 0-1, rgb is linear. -pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> (f32, f32, f32) { +pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] { #![allow(clippy::many_single_char_names)] let h = (h.fract() + 1.0).fract(); // wrap let s = clamp(s, 0.0..=1.0); @@ -484,12 +536,12 @@ pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> (f32, f32, f32) { let t = v * (1.0 - (1.0 - f) * s); match (h * 6.0).floor() as i32 % 6 { - 0 => (v, t, p), - 1 => (q, v, p), - 2 => (p, v, t), - 3 => (p, q, v), - 4 => (t, p, v), - 5 => (v, p, q), + 0 => [v, t, p], + 1 => [q, v, p], + 2 => [p, v, t], + 3 => [p, q, v], + 4 => [t, p, v], + 5 => [v, p, q], _ => unreachable!(), } } diff --git a/egui/src/paint/shadow.rs b/egui/src/paint/shadow.rs index e2763723..38350d3c 100644 --- a/egui/src/paint/shadow.rs +++ b/egui/src/paint/shadow.rs @@ -13,7 +13,7 @@ impl Shadow { pub fn small() -> Self { Self { extrusion: 8.0, - color: Color32::black_alpha(64), + color: Color32::from_black_alpha(64), } } @@ -21,7 +21,7 @@ impl Shadow { pub fn big() -> Self { Self { extrusion: 32.0, - color: Color32::black_alpha(96), + color: Color32::from_black_alpha(96), } } diff --git a/egui/src/paint/texture_atlas.rs b/egui/src/paint/texture_atlas.rs index 05c79973..a1d114c1 100644 --- a/egui/src/paint/texture_atlas.rs +++ b/egui/src/paint/texture_atlas.rs @@ -7,7 +7,7 @@ pub struct Texture { pub version: u64, pub width: usize, pub height: usize, - /// luminance and alpha, linear space 0-255 + /// White color with the given alpha (linear space 0-255). pub pixels: Vec, } @@ -15,7 +15,8 @@ impl Texture { /// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom. pub fn srgba_pixels(&'_ self) -> impl Iterator + '_ { use super::Color32; - let srgba_from_luminance_lut: Vec = (0..=255).map(Color32::white_alpha).collect(); + let srgba_from_luminance_lut: Vec = + (0..=255).map(Color32::from_white_alpha).collect(); self.pixels .iter() .map(move |&l| srgba_from_luminance_lut[l as usize]) @@ -47,7 +48,7 @@ impl std::ops::IndexMut<(usize, usize)> for Texture { pub struct TextureAtlas { texture: Texture, - /// Used for when adding new rects + /// Used for when allocating new rectangles. cursor: (usize, usize), row_height: usize, } @@ -78,7 +79,7 @@ impl TextureAtlas { pub fn allocate(&mut self, (w, h): (usize, usize)) -> (usize, usize) { /// On some low-precision GPUs (my old iPad) characters get muddled up /// if we don't add some empty pixels between the characters. - /// On modern high-precision GPUs this is not be needed. + /// On modern high-precision GPUs this is not needed. const PADDING: usize = 1; assert!(w <= self.texture.width); diff --git a/egui/src/painter.rs b/egui/src/painter.rs index fab13a93..857a35df 100644 --- a/egui/src/painter.rs +++ b/egui/src/painter.rs @@ -147,7 +147,7 @@ impl Painter { self.add(PaintCmd::Rect { rect: frame_rect, corner_radius: 0.0, - fill: Color32::black_alpha(240), + fill: Color32::from_black_alpha(240), stroke: Stroke::new(1.0, Color32::RED), }); self.galley(rect.min, galley, text_style, Color32::RED); diff --git a/egui/src/style.rs b/egui/src/style.rs index 246aaeee..7ff02d6a 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -279,7 +279,7 @@ impl Default for Visuals { override_text_color: None, widgets: Default::default(), selection: Default::default(), - dark_bg_color: Color32::black_alpha(140), + dark_bg_color: Color32::from_black_alpha(140), hyperlink_color: Color32::from_rgb(90, 170, 255), window_corner_radius: 10.0, window_shadow: Shadow::big(), @@ -296,8 +296,11 @@ impl Default for Visuals { impl Default for Selection { fn default() -> Self { Self { - bg_fill: Rgba::new(0.0, 0.5, 1.0, 0.0).multiply(0.15).into(), // additive! - stroke: Stroke::new(1.0, Rgba::new(0.3, 0.6, 1.0, 1.0)), + bg_fill: Rgba::from_rgb(0.0, 0.5, 1.0) + .additive() + .multiply(0.10) + .into(), + stroke: Stroke::new(1.0, Rgba::from_rgb(0.3, 0.6, 1.0)), } } } @@ -306,39 +309,39 @@ impl Default for Widgets { fn default() -> Self { Self { active: WidgetVisuals { - bg_fill: Rgba::luminance_alpha(0.10, 0.5).into(), + bg_fill: Rgba::from_luminance_alpha(0.10, 0.5).into(), bg_stroke: Stroke::new(2.0, Color32::WHITE), corner_radius: 4.0, fg_fill: Color32::from_rgb(120, 120, 200), fg_stroke: Stroke::new(2.0, Color32::WHITE), }, hovered: WidgetVisuals { - bg_fill: Rgba::luminance_alpha(0.06, 0.5).into(), - bg_stroke: Stroke::new(1.0, Rgba::white_alpha(0.5)), + bg_fill: Rgba::from_luminance_alpha(0.06, 0.5).into(), + bg_stroke: Stroke::new(1.0, Rgba::from_white_alpha(0.5)), corner_radius: 4.0, fg_fill: Color32::from_rgb(100, 100, 150), - fg_stroke: Stroke::new(1.5, Color32::gray(240)), + fg_stroke: Stroke::new(1.5, Color32::from_gray(240)), }, inactive: WidgetVisuals { - bg_fill: Rgba::luminance_alpha(0.04, 0.5).into(), - bg_stroke: Stroke::new(1.0, Rgba::white_alpha(0.06)), // default window outline. Should be pretty readable + bg_fill: Rgba::from_luminance_alpha(0.04, 0.5).into(), + bg_stroke: Stroke::new(1.0, Rgba::from_white_alpha(0.06)), // default window outline. Should be pretty readable corner_radius: 4.0, fg_fill: Color32::from_rgb(60, 60, 80), - fg_stroke: Stroke::new(1.0, Color32::gray(200)), // Should NOT look grayed out! + fg_stroke: Stroke::new(1.0, Color32::from_gray(200)), // Should NOT look grayed out! }, disabled: WidgetVisuals { - bg_fill: Rgba::luminance_alpha(0.02, 0.5).into(), - bg_stroke: Stroke::new(0.5, Color32::gray(70)), + bg_fill: Rgba::from_luminance_alpha(0.02, 0.5).into(), + bg_stroke: Stroke::new(0.5, Color32::from_gray(70)), corner_radius: 4.0, fg_fill: Color32::from_rgb(50, 50, 50), - fg_stroke: Stroke::new(1.0, Color32::gray(140)), // Should look grayed out + fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // Should look grayed out }, noninteractive: WidgetVisuals { - bg_stroke: Stroke::new(1.0, Rgba::white_alpha(0.06)), - bg_fill: Rgba::luminance_alpha(0.010, 0.975).into(), // window background + bg_stroke: Stroke::new(1.0, Rgba::from_white_alpha(0.06)), + bg_fill: Rgba::from_luminance_alpha(0.010, 0.975).into(), // window background corner_radius: 4.0, fg_fill: Default::default(), - fg_stroke: Stroke::new(1.0, Color32::gray(160)), // text color + fg_stroke: Stroke::new(1.0, Color32::from_gray(160)), // text color }, } } diff --git a/egui/src/ui.rs b/egui/src/ui.rs index f3ca243c..a53b3a31 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -814,13 +814,35 @@ 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 Color32) -> Response { - widgets::color_picker::color_edit_button_srgba(self, srgba) + color_picker::color_edit_button_srgba(self, srgba, color_picker::Alpha::BlendOrAdditive) } /// 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) + color_picker::color_edit_button_hsva(self, hsva, color_picker::Alpha::BlendOrAdditive) + } + + /// Shows a button with the given color. + /// If the user clicks the button, a full color picker is shown. + /// The given color is in `sRGB` space. + pub fn color_edit_button_srgb(&mut self, srgb: &mut [u8; 3]) -> Response { + let mut hsva = Hsva::from_srgb(*srgb); + let response = + color_picker::color_edit_button_hsva(self, &mut hsva, color_picker::Alpha::Opaque); + *srgb = hsva.to_srgb(); + 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 RGB space. + pub fn color_edit_button_rgb(&mut self, rgb: &mut [f32; 3]) -> Response { + let mut hsva = Hsva::from_rgb(*rgb); + let response = + color_picker::color_edit_button_hsva(self, &mut hsva, color_picker::Alpha::Opaque); + *rgb = hsva.to_rgb(); + response } /// Shows a button with the given color. @@ -839,7 +861,8 @@ impl Ui { /// 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); + let response = + color_picker::color_edit_button_hsva(self, &mut hsva, color_picker::Alpha::OnlyBlend); *srgba = hsva.to_srgba_unmultiplied(); response } @@ -849,7 +872,11 @@ impl Ui { /// 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); + let response = color_picker::color_edit_button_hsva( + self, + &mut hsva, + color_picker::Alpha::BlendOrAdditive, + ); *rgba = hsva.to_rgba_premultiplied(); response } @@ -860,7 +887,8 @@ impl Ui { /// 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); + let response = + color_picker::color_edit_button_hsva(self, &mut hsva, color_picker::Alpha::OnlyBlend); *rgba = hsva.to_rgba_unmultiplied(); response } diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 3da236a5..8f945c4c 100644 --- a/egui/src/widgets/color_picker.rs +++ b/egui/src/widgets/color_picker.rs @@ -16,11 +16,11 @@ fn contrast_color(color: impl Into) -> Color32 { /// Number of vertices per dimension in the color sliders. /// We need at least 6 for hues, and more for smooth 2D areas. /// Should always be a multiple of 6 to hit the peak hues in HSV/HSL (every 60°). -const N: u32 = 6 * 3; +const N: u32 = 6 * 6; fn background_checkers(painter: &Painter, rect: Rect) { - let mut top_color = Color32::gray(128); - let mut bottom_color = Color32::gray(32); + let mut top_color = Color32::from_gray(128); + let mut bottom_color = Color32::from_gray(32); let checker_size = Vec2::splat(rect.height() / 2.0); let n = (rect.width() / checker_size.x).round() as u32; @@ -184,20 +184,68 @@ fn color_slider_2d( response } -fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma) { +/// What options to show for alpha +#[derive(Clone, Copy, PartialEq)] +pub enum Alpha { + // Set alpha to 1.0, and show no option for it. + Opaque, + // Only show normal blend options for it. + OnlyBlend, + // Show both blend and additive options. + BlendOrAdditive, +} + +fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) { ui.vertical(|ui| { let current_color_size = vec2( ui.style().spacing.slider_width, ui.style().spacing.interact_size.y * 2.0, ); - show_color(ui, *hsva, current_color_size).on_hover_text("Current color"); - - show_color(ui, HsvaGamma { a: 1.0, ..*hsva }, current_color_size) - .on_hover_text("Current color (opaque)"); - let opaque = HsvaGamma { a: 1.0, ..*hsva }; - let HsvaGamma { h, s, v, a } = hsva; + + if alpha == Alpha::Opaque { + hsva.a = 1.0; + show_color(ui, *hsva, current_color_size).on_hover_text("Current color"); + } else { + let a = &mut hsva.a; + + // We signal additive blending by storing a negative alpha (a bit ironic). + let mut additive = *a < 0.0; + + if alpha == Alpha::OnlyBlend { + if additive { + *a = 0.5; + } + + color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).on_hover_text("Alpha"); + } else { + ui.horizontal(|ui| { + ui.label("Blending:"); + ui.radio_value(&mut additive, false, "Normal"); + ui.radio_value(&mut additive, true, "Additive"); + + if additive { + *a = -a.abs(); + } + + if !additive { + *a = a.abs(); + } + }); + + if !additive { + color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()) + .on_hover_text("Alpha"); + } + } + + show_color(ui, *hsva, current_color_size).on_hover_text("Current color"); + show_color(ui, opaque, current_color_size).on_hover_text("Current color (opaque)"); + } + + let HsvaGamma { h, s, v, a: _ } = hsva; + color_slider_2d(ui, h, s, |h, s| HsvaGamma::new(h, s, 1.0, 1.0).into()) .on_hover_text("Hue - Saturation"); color_slider_2d(ui, v, s, |v, s| HsvaGamma { v, s, ..opaque }.into()) @@ -205,17 +253,16 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma) { color_slider_1d(ui, h, |h| HsvaGamma { h, ..opaque }.into()).on_hover_text("Hue"); color_slider_1d(ui, s, |s| HsvaGamma { s, ..opaque }.into()).on_hover_text("Saturation"); color_slider_1d(ui, v, |v| HsvaGamma { v, ..opaque }.into()).on_hover_text("Value"); - color_slider_1d(ui, a, |a| HsvaGamma { a, ..opaque }.into()).on_hover_text("Alpha"); }); } -fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva) { +fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) { let mut hsvag = HsvaGamma::from(*hsva); - color_picker_hsvag_2d(ui, &mut hsvag); + color_picker_hsvag_2d(ui, &mut hsvag, alpha); *hsva = Hsva::from(hsvag); } -pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva) -> Response { +pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response { let pupup_id = ui.auto_id_with("popup"); let button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color"); @@ -228,8 +275,9 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva) -> Response { .order(Order::Foreground) .default_pos(button_response.rect.max) .show(ui.ctx(), |ui| { + ui.style_mut().spacing.slider_width = 256.0; Frame::popup(ui.style()).show(ui, |ui| { - color_picker_hsva_2d(ui, hsva); + color_picker_hsva_2d(ui, hsva, alpha); }) }); @@ -246,7 +294,7 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva) -> 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_srgba(ui: &mut Ui, srgba: &mut Color32) -> Response { +pub fn color_edit_button_srgba(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> Response { // To ensure we keep hue slider when `srgba` is grey we store the // full `Hsva` in a cache: @@ -258,7 +306,7 @@ pub fn color_edit_button_srgba(ui: &mut Ui, srgba: &mut Color32) -> Response { .cloned() .unwrap_or_else(|| Hsva::from(*srgba)); - let response = color_edit_button_hsva(ui, &mut hsva); + let response = color_edit_button_hsva(ui, &mut hsva, alpha); *srgba = Color32::from(hsva); @@ -279,7 +327,7 @@ struct HsvaGamma { pub s: f32, /// value 0-1, in gamma-space (perceptually even) pub v: f32, - /// alpha 0-1 + /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). pub a: f32, } diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index 27eb4174..da8385af 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -39,8 +39,9 @@ impl epi::App for ColorTest { if frame.is_web() { ui.colored_label( RED, - "NOTE: The current WebGL backend does NOT pass the color test!", + "NOTE: The WebGL backend does NOT pass the color test." ); + ui.small("This is because WebGL does not support a linear framebuffer blending (not even WebGL2!).\nMaybe when WebGL3 becomes mainstream in 2030 the web can finally get colors right?"); ui.separator(); } ScrollArea::auto_sized().show(ui, |ui| { @@ -56,8 +57,7 @@ impl ColorTest { ui: &mut Ui, mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, ) { - ui.label("This is made to test if your Egui painter backend is set up correctly"); - ui.label("It is meant to ensure you do proper sRGBA decoding of both texture and vertex colors, and blend using premultiplied alpha."); + ui.label("This is made to test that the Egui painter backend is set up correctly, so that all colors are interpolated and blended in linear space with premultiplied alpha."); ui.label("If everything is set up correctly, all groups of gradients will look uniform"); ui.checkbox(&mut self.vertex_gradients, "Vertex gradients"); @@ -85,8 +85,8 @@ impl ColorTest { ui.wrap(|ui| { ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients - let tex_color = Rgba::new(1.0, 0.25, 0.25, 1.0); - let vertex_color = Rgba::new(0.5, 0.75, 0.75, 1.0); + let tex_color = Rgba::from_rgb(1.0, 0.25, 0.25); + let vertex_color = Rgba::from_rgb(0.5, 0.75, 0.75); ui.horizontal(|ui| { let color_size = ui.style().spacing.interact_size; diff --git a/egui_demo_lib/src/apps/demo/dancing_strings.rs b/egui_demo_lib/src/apps/demo/dancing_strings.rs index daf8de6e..babd2e17 100644 --- a/egui_demo_lib/src/apps/demo/dancing_strings.rs +++ b/egui_demo_lib/src/apps/demo/dancing_strings.rs @@ -57,7 +57,7 @@ impl super::View for DancingStrings { let thickness = 10.0 / mode; cmds.push(paint::PaintCmd::line( points, - Stroke::new(thickness, Color32::additive_luminance(196)), + Stroke::new(thickness, Color32::from_additive_luminance(196)), )); } diff --git a/egui_demo_lib/src/apps/demo/demo_window.rs b/egui_demo_lib/src/apps/demo/demo_window.rs index 7fe5db68..3c514721 100644 --- a/egui_demo_lib/src/apps/demo/demo_window.rs +++ b/egui_demo_lib/src/apps/demo/demo_window.rs @@ -93,7 +93,7 @@ impl DemoWindow { let painter = ui.painter(); let c = response.rect.center(); let r = response.rect.width() / 2.0 - 1.0; - let color = Color32::gray(128); + let color = Color32::from_gray(128); let stroke = Stroke::new(1.0, color); painter.circle_stroke(c, r, stroke); painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke); @@ -210,7 +210,7 @@ impl BoxPainting { ui.painter().rect( response.rect, self.corner_radius, - Color32::gray(64), + Color32::from_gray(64), Stroke::new(self.stroke_width, Color32::WHITE), ); } diff --git a/egui_demo_lib/src/apps/demo/toggle_switch.rs b/egui_demo_lib/src/apps/demo/toggle_switch.rs index 9a02584b..e7e9f57d 100644 --- a/egui_demo_lib/src/apps/demo/toggle_switch.rs +++ b/egui_demo_lib/src/apps/demo/toggle_switch.rs @@ -40,8 +40,8 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { // "how should something that is being interacted with be painted?". // This will, for instance, give us different colors when the widget is hovered or clicked. let visuals = ui.style().interact(&response); - let off_bg_fill = egui::Rgba::new(0.0, 0.0, 0.0, 0.0); - let on_bg_fill = egui::Rgba::new(0.0, 0.5, 0.25, 1.0); + let off_bg_fill = egui::Rgba::TRANSPARENT; + let on_bg_fill = egui::Rgba::from_rgb(0.0, 0.5, 0.25); let bg_fill = egui::lerp(off_bg_fill..=on_bg_fill, how_on); // All coordinates are in absolute screen coordinates so we use `rect` to place the elements. let rect = response.rect; @@ -67,8 +67,8 @@ fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { let how_on = ui.ctx().animate_bool(response.id, *on); let visuals = ui.style().interact(&response); - let off_bg_fill = egui::Rgba::new(0.0, 0.0, 0.0, 0.0); - let on_bg_fill = egui::Rgba::new(0.0, 0.5, 0.25, 1.0); + let off_bg_fill = egui::Rgba::TRANSPARENT; + let on_bg_fill = egui::Rgba::from_rgb(0.0, 0.5, 0.25); let bg_fill = egui::lerp(off_bg_fill..=on_bg_fill, how_on); let rect = response.rect; let radius = 0.5 * rect.height(); diff --git a/egui_demo_lib/src/apps/demo/widgets.rs b/egui_demo_lib/src/apps/demo/widgets.rs index 318a7df1..e407f5f0 100644 --- a/egui_demo_lib/src/apps/demo/widgets.rs +++ b/egui_demo_lib/src/apps/demo/widgets.rs @@ -35,7 +35,7 @@ impl Default for Widgets { count: 0, sliders: Default::default(), angle: std::f32::consts::TAU / 3.0, - color: (Rgba::new(0.0, 1.0, 0.5, 1.0) * 0.75).into(), + color: (Rgba::from_rgb(0.0, 1.0, 0.5) * 0.75).into(), single_line_text_input: "Hello World!".to_owned(), multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(), toggle_switch: false, diff --git a/egui_demo_lib/src/apps/fractal_clock.rs b/egui_demo_lib/src/apps/fractal_clock.rs index 17e8931c..a9262d16 100644 --- a/egui_demo_lib/src/apps/fractal_clock.rs +++ b/egui_demo_lib/src/apps/fractal_clock.rs @@ -58,7 +58,7 @@ impl FractalClock { ui.expand_to_include_rect(painter.clip_rect()); Frame::popup(ui.style()) - .fill(Rgba::luminance_alpha(0.02, 0.5).into()) + .fill(Rgba::from_luminance_alpha(0.02, 0.5).into()) .stroke(Stroke::none()) .show(ui, |ui| { ui.set_max_width(270.0); @@ -160,7 +160,7 @@ impl FractalClock { for (i, hand) in hands.iter().enumerate() { let center = pos2(0.0, 0.0); let end = center + hand.vec; - paint_line([center, end], Color32::additive_luminance(255), width); + paint_line([center, end], Color32::from_additive_luminance(255), width); if i < 2 { nodes.push(Node { pos: end, @@ -190,7 +190,7 @@ impl FractalClock { }; paint_line( [a.pos, b.pos], - Color32::additive_luminance(luminance_u8), + Color32::from_additive_luminance(luminance_u8), width, ); new_nodes.push(b); diff --git a/egui_demo_lib/src/frame_history.rs b/egui_demo_lib/src/frame_history.rs index 097d988e..be4173b8 100644 --- a/egui_demo_lib/src/frame_history.rs +++ b/egui_demo_lib/src/frame_history.rs @@ -78,7 +78,7 @@ impl FrameHistory { }]; let rect = rect.shrink(4.0); - let line_stroke = Stroke::new(1.0, Color32::additive_luminance(128)); + let line_stroke = Stroke::new(1.0, Color32::from_additive_luminance(128)); if let Some(mouse_pos) = ui.input().mouse.pos { if rect.contains(mouse_pos) { @@ -100,7 +100,7 @@ impl FrameHistory { } } - let circle_color = Color32::additive_luminance(196); + let circle_color = Color32::from_additive_luminance(196); let radius = 2.0; let right_side_time = ui.input().time; // Time at right side of screen diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 4959925f..c540c52a 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -109,7 +109,7 @@ impl Painter { .chunks(texture.width as usize) .map(|row| { row.iter() - .map(|&a| Color32::white_alpha(a).to_tuple()) + .map(|&a| Color32::from_white_alpha(a).to_tuple()) .collect() }) .collect();