Support additive colors in color picker
This commit is contained in:
parent
5c8df6925d
commit
10a23d18e1
17 changed files with 239 additions and 106 deletions
|
@ -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`.
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<usize> 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 {
|
||||
if r == 0.0 && b == 0.0 && a == 0.0 {
|
||||
Hsva::default()
|
||||
} else {
|
||||
let (h, s, v) = hsv_from_rgb((r / a, g / a, b / a));
|
||||
Hsva::from_additive_rgb([r, g, b])
|
||||
}
|
||||
} else {
|
||||
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();
|
||||
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<Color32> 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!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<u8>,
|
||||
}
|
||||
|
||||
|
@ -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<Item = super::Color32> + '_ {
|
||||
use super::Color32;
|
||||
let srgba_from_luminance_lut: Vec<Color32> = (0..=255).map(Color32::white_alpha).collect();
|
||||
let srgba_from_luminance_lut: Vec<Color32> =
|
||||
(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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ fn contrast_color(color: impl Into<Rgba>) -> 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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue