add color test window to troubleshoot your Egui painter backend
egui_glium passes the test, but the egui_web WebGL painter fails it.
This commit is contained in:
parent
d49aec4079
commit
1069ad8496
11 changed files with 490 additions and 27 deletions
16
TODO.md
16
TODO.md
|
@ -17,9 +17,10 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
||||||
* [x] Color edit button with popup color picker
|
* [x] Color edit button with popup color picker
|
||||||
* [x] Gamma for value (brightness) slider
|
* [x] Gamma for value (brightness) slider
|
||||||
* [x] Easily edit users own (s)RGBA quadruplets (`&mut [u8;4]`/`[f32;4]`)
|
* [x] Easily edit users own (s)RGBA quadruplets (`&mut [u8;4]`/`[f32;4]`)
|
||||||
* [ ] RGB editing without alpha
|
* [x] RGB editing without alpha
|
||||||
* [ ] Additive blending aware color picker
|
* [ ] Additive blending aware color picker
|
||||||
* [ ] Premultiplied alpha is a bit of a pain in the ass. Maybe rethink this a bit.
|
* [ ] Premultiplied alpha is a bit of a pain in the ass. Maybe rethink this a bit.
|
||||||
|
* [ ] Hue wheel
|
||||||
* Containers
|
* Containers
|
||||||
* [ ] Scroll areas
|
* [ ] Scroll areas
|
||||||
* [x] Vertical scrolling
|
* [x] Vertical scrolling
|
||||||
|
@ -53,19 +54,17 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
||||||
* [ ] Positioning preference: `window.preference(Top, Right)`
|
* [ ] Positioning preference: `window.preference(Top, Right)`
|
||||||
* [ ] Keeping right/bottom on expand. Maybe cover jitteryness with quick animation?
|
* [ ] Keeping right/bottom on expand. Maybe cover jitteryness with quick animation?
|
||||||
* [ ] Make auto-positioning of windows respect permanent side-bars.
|
* [ ] Make auto-positioning of windows respect permanent side-bars.
|
||||||
* [/] Image support
|
* [x] Image support
|
||||||
* [x] Show user textures
|
* [x] Show user textures
|
||||||
* [ ] API for creating a texture managed by Egui
|
* [x] API for creating a texture managed by `egui::app::Backend`
|
||||||
* Backend-agnostic. Good for people doing Egui-apps (games etc).
|
|
||||||
* [ ] Convert font texture to RGBA, or communicate format in initialization?
|
|
||||||
* [ ] Generalized font atlas
|
|
||||||
* Visuals
|
* Visuals
|
||||||
* [x] Pixel-perfect painting (round positions to nearest pixel).
|
* [x] Pixel-perfect painting (round positions to nearest pixel).
|
||||||
* [x] Fix `aa_size`: should be 1, currently fudged at 1.5
|
* [x] Fix `aa_size`: should be 1, currently fudged at 1.5
|
||||||
* [x] Fix thin rounded corners rendering bug (too bright)
|
* [x] Fix thin rounded corners rendering bug (too bright)
|
||||||
* [x] Smoother animation (e.g. ease-out)? NO: animation are too brief for subtelty
|
* [x] Smoother animation (e.g. ease-out)? NO: animation are too brief for subtelty
|
||||||
* [ ] Veriy alpha and sRGB correctness
|
* [x] Veriy alpha and sRGB correctness
|
||||||
* [x] sRGBA decode in fragment shader
|
* [x] sRGBA decode in fragment shader
|
||||||
|
* [ ] Fix alpha blending / sRGB weirdness in WebGL (EXT_sRGB)
|
||||||
* [ ] Thin circles look bad
|
* [ ] Thin circles look bad
|
||||||
* [ ] Allow adding multiple tooltips to the same widget, showing them all one after the other.
|
* [ ] Allow adding multiple tooltips to the same widget, showing them all one after the other.
|
||||||
* Math
|
* Math
|
||||||
|
@ -86,7 +85,8 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
||||||
* [x] Scroll input
|
* [x] Scroll input
|
||||||
* [x] Change to resize cursor on hover
|
* [x] Change to resize cursor on hover
|
||||||
* [x] Port most code to Rust
|
* [x] Port most code to Rust
|
||||||
* [x] Read url fragment and redirect to a subpage (e.g. different examples apps)
|
* [x] Read url fragment and redirect to a subpage (e.g. different examples apps)]
|
||||||
|
* [ ] Fix WebGL colors/beldning (try EXT_sRGB)
|
||||||
* [ ] Embeddability
|
* [ ] Embeddability
|
||||||
* [ ] Support canvas that does NOT cover entire screen.
|
* [ ] Support canvas that does NOT cover entire screen.
|
||||||
* [ ] Support multiple eguis in one web page.
|
* [ ] Support multiple eguis in one web page.
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
//!
|
//!
|
||||||
//! The demo-code is also used in benchmarks and tests.
|
//! The demo-code is also used in benchmarks and tests.
|
||||||
mod app;
|
mod app;
|
||||||
|
mod color_test;
|
||||||
mod fractal_clock;
|
mod fractal_clock;
|
||||||
pub mod toggle_switch;
|
pub mod toggle_switch;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
app::{DemoApp, DemoWindow},
|
app::{DemoApp, DemoWindow},
|
||||||
|
color_test::ColorTest,
|
||||||
fractal_clock::FractalClock,
|
fractal_clock::FractalClock,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{app, color::*, containers::*, demos::FractalClock, paint::*, widgets::*, *};
|
use crate::{app, color::*, containers::*, demos::*, paint::*, widgets::*, *};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ pub struct DemoApp {
|
||||||
demo_window: DemoWindow,
|
demo_window: DemoWindow,
|
||||||
fractal_clock: FractalClock,
|
fractal_clock: FractalClock,
|
||||||
num_frames_painted: u64,
|
num_frames_painted: u64,
|
||||||
|
#[serde(skip)]
|
||||||
|
color_test: ColorTest,
|
||||||
|
show_color_test: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DemoApp {
|
impl DemoApp {
|
||||||
|
@ -189,6 +192,12 @@ impl DemoApp {
|
||||||
|
|
||||||
self.num_frames_painted += 1;
|
self.num_frames_painted += 1;
|
||||||
ui.label(format!("Total frames painted: {}", self.num_frames_painted));
|
ui.label(format!("Total frames painted: {}", self.num_frames_painted));
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.checkbox(
|
||||||
|
"Show color blend test (debug backend painter)",
|
||||||
|
&mut self.show_color_test,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +207,25 @@ impl app::App for DemoApp {
|
||||||
self.backend_ui(ui, backend);
|
self.backend_ui(ui, backend);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
show_color_test,
|
||||||
|
color_test,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
if *show_color_test {
|
||||||
|
let mut tex_loader = |size: (usize, usize), pixels: &[Srgba]| {
|
||||||
|
backend.new_texture_srgba_premultiplied(size, pixels)
|
||||||
|
};
|
||||||
|
Window::new("Color Test")
|
||||||
|
.default_size(vec2(1024.0, 1024.0))
|
||||||
|
.scroll(true)
|
||||||
|
.open(show_color_test)
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
color_test.ui(ui, &mut tex_loader);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let web_info = backend.web_info();
|
let web_info = backend.web_info();
|
||||||
let web_location_hash = web_info
|
let web_location_hash = web_info
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
359
egui/src/demos/color_test.rs
Normal file
359
egui/src/demos/color_test.rs
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
use crate::widgets::color_picker::show_color;
|
||||||
|
use crate::*;
|
||||||
|
use color::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub type TextureLoader<'a> = dyn FnMut((usize, usize), &[crate::Srgba]) -> TextureId + 'a;
|
||||||
|
|
||||||
|
const GRADIENT_SIZE: Vec2 = vec2(256.0, 24.0);
|
||||||
|
|
||||||
|
pub struct ColorTest {
|
||||||
|
tex_mngr: TextureManager,
|
||||||
|
vertex_gradients: bool,
|
||||||
|
texture_gradients: bool,
|
||||||
|
srgb: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorTest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
tex_mngr: Default::default(),
|
||||||
|
vertex_gradients: true,
|
||||||
|
texture_gradients: true,
|
||||||
|
srgb: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorTest {
|
||||||
|
pub fn ui(&mut self, ui: &mut Ui, tex_loader: &mut TextureLoader<'_>) {
|
||||||
|
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("If everything is set up correctly, all groups of gradients will look uniform");
|
||||||
|
|
||||||
|
ui.checkbox("Vertex gradients", &mut self.vertex_gradients);
|
||||||
|
ui.checkbox("Texture gradients", &mut self.texture_gradients);
|
||||||
|
ui.checkbox("Show naive sRGBA horror", &mut self.srgb);
|
||||||
|
|
||||||
|
ui.heading("sRGB color test");
|
||||||
|
ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500");
|
||||||
|
ui.add_custom(|ui| {
|
||||||
|
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
|
||||||
|
let g = Gradient::one_color(Srgba::new(255, 165, 0, 255));
|
||||||
|
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
|
||||||
|
self.tex_gradient(
|
||||||
|
ui,
|
||||||
|
tex_loader,
|
||||||
|
"orange rgb(255, 165, 0) - texture",
|
||||||
|
WHITE,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.label("Test that vertex color times texture color is done in linear space:");
|
||||||
|
ui.add_custom(|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);
|
||||||
|
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
let color_size = vec2(2.0, 1.0) * ui.style().spacing.clickable_diameter;
|
||||||
|
ui.label("texture");
|
||||||
|
show_color(ui, tex_color, color_size);
|
||||||
|
ui.label(" * ");
|
||||||
|
show_color(ui, vertex_color, color_size);
|
||||||
|
ui.label(" vertex color =");
|
||||||
|
});
|
||||||
|
{
|
||||||
|
let g = Gradient::one_color(Srgba::from(tex_color * vertex_color));
|
||||||
|
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||||
|
self.tex_gradient(ui, tex_loader, "Ground truth (texture)", WHITE, &g);
|
||||||
|
}
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
let g = Gradient::one_color(Srgba::from(tex_color));
|
||||||
|
let tex = self.tex_mngr.get(tex_loader, &g);
|
||||||
|
let texel_offset = 0.5 / (g.0.len() as f32);
|
||||||
|
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||||
|
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
|
||||||
|
.tooltip_text(format!("A texture that is {} texels wide", g.0.len()));
|
||||||
|
ui.label("GPU result");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// TODO: test color multiplication (image tint),
|
||||||
|
// to make sure vertex and texture color multiplication is done in linear space.
|
||||||
|
|
||||||
|
self.show_gradients(ui, tex_loader, WHITE, (RED, GREEN));
|
||||||
|
if self.srgb {
|
||||||
|
ui.label("Notice the darkening in the center of the naive sRGB interpolation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
self.show_gradients(ui, tex_loader, RED, (TRANSPARENT, GREEN));
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
self.show_gradients(ui, tex_loader, WHITE, (TRANSPARENT, GREEN));
|
||||||
|
if self.srgb {
|
||||||
|
ui.label(
|
||||||
|
"Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// TODO: another ground truth where we do the alpha-blending against the background also.
|
||||||
|
// TODO: exactly the same thing, but with vertex colors (no textures)
|
||||||
|
self.show_gradients(ui, tex_loader, WHITE, (TRANSPARENT, BLACK));
|
||||||
|
ui.separator();
|
||||||
|
self.show_gradients(ui, tex_loader, BLACK, (TRANSPARENT, WHITE));
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.label("Additive blending: add more and more blue to the red background:");
|
||||||
|
self.show_gradients(ui, tex_loader, RED, (TRANSPARENT, Srgba::new(0, 0, 255, 0)));
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_gradients(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
tex_loader: &mut TextureLoader<'_>,
|
||||||
|
bg_fill: Srgba,
|
||||||
|
(left, right): (Srgba, Srgba),
|
||||||
|
) {
|
||||||
|
let is_opaque = left.is_opaque() && right.is_opaque();
|
||||||
|
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
let color_size = vec2(2.0, 1.0) * ui.style().spacing.clickable_diameter;
|
||||||
|
if !is_opaque {
|
||||||
|
ui.label("Background:");
|
||||||
|
show_color(ui, bg_fill, color_size);
|
||||||
|
}
|
||||||
|
ui.label("gradient");
|
||||||
|
show_color(ui, left, color_size);
|
||||||
|
ui.label("-");
|
||||||
|
show_color(ui, right, color_size);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_custom(|ui| {
|
||||||
|
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
|
||||||
|
if is_opaque {
|
||||||
|
let g = Gradient::ground_truth_linear_gradient(left, right);
|
||||||
|
self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g);
|
||||||
|
self.tex_gradient(
|
||||||
|
ui,
|
||||||
|
tex_loader,
|
||||||
|
"Ground Truth (CPU gradient) - texture",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill);
|
||||||
|
self.vertex_gradient(
|
||||||
|
ui,
|
||||||
|
"Ground Truth (CPU gradient, CPU blending) - vertices",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
self.tex_gradient(
|
||||||
|
ui,
|
||||||
|
tex_loader,
|
||||||
|
"Ground Truth (CPU gradient, CPU blending) - texture",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
let g = Gradient::ground_truth_linear_gradient(left, right);
|
||||||
|
self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g);
|
||||||
|
self.tex_gradient(
|
||||||
|
ui,
|
||||||
|
tex_loader,
|
||||||
|
"CPU gradient, GPU blending - texture",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let g = Gradient::texture_gradient(left, right);
|
||||||
|
self.vertex_gradient(
|
||||||
|
ui,
|
||||||
|
"Triangle mesh of width 2 (test vertex decode and interpolation)",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
self.tex_gradient(
|
||||||
|
ui,
|
||||||
|
tex_loader,
|
||||||
|
"Texture of width 2 (test texture sampler)",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.srgb {
|
||||||
|
let g =
|
||||||
|
Gradient::ground_truth_bad_srgba_gradient(left, right).with_bg_fill(bg_fill);
|
||||||
|
self.vertex_gradient(
|
||||||
|
ui,
|
||||||
|
"Triangle mesh with naive sRGBA interpolation (WRONG)",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
self.tex_gradient(
|
||||||
|
ui,
|
||||||
|
tex_loader,
|
||||||
|
"Naive sRGBA interpolation (WRONG)",
|
||||||
|
bg_fill,
|
||||||
|
&g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tex_gradient(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
tex_loader: &mut TextureLoader<'_>,
|
||||||
|
label: &str,
|
||||||
|
bg_fill: Srgba,
|
||||||
|
gradient: &Gradient,
|
||||||
|
) {
|
||||||
|
if !self.texture_gradients {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
let tex = self.tex_mngr.get(tex_loader, gradient);
|
||||||
|
let texel_offset = 0.5 / (gradient.0.len() as f32);
|
||||||
|
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||||
|
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
|
||||||
|
.tooltip_text(format!(
|
||||||
|
"A texture that is {} texels wide",
|
||||||
|
gradient.0.len()
|
||||||
|
));
|
||||||
|
ui.label(label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Srgba, gradient: &Gradient) {
|
||||||
|
if !self.vertex_gradients {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ui.horizontal_centered(|ui| {
|
||||||
|
vertex_gradient(ui, bg_fill, gradient).tooltip_text(format!(
|
||||||
|
"A triangle mesh that is {} vertices wide",
|
||||||
|
gradient.0.len()
|
||||||
|
));
|
||||||
|
ui.label(label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex_gradient(ui: &mut Ui, bg_fill: Srgba, gradient: &Gradient) -> Response {
|
||||||
|
use crate::paint::*;
|
||||||
|
let rect = ui.allocate_space(GRADIENT_SIZE);
|
||||||
|
if bg_fill != Default::default() {
|
||||||
|
let mut triangles = Triangles::default();
|
||||||
|
triangles.add_colored_rect(rect, bg_fill);
|
||||||
|
ui.painter().add(PaintCmd::Triangles(triangles));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let n = gradient.0.len();
|
||||||
|
assert!(n >= 2);
|
||||||
|
let mut triangles = Triangles::default();
|
||||||
|
for (i, &color) in gradient.0.iter().enumerate() {
|
||||||
|
let t = i as f32 / (n as f32 - 1.0);
|
||||||
|
let x = lerp(rect.range_x(), t);
|
||||||
|
triangles.colored_vertex(pos2(x, rect.top()), color);
|
||||||
|
triangles.colored_vertex(pos2(x, rect.bottom()), color);
|
||||||
|
if i < n - 1 {
|
||||||
|
let i = i as u32;
|
||||||
|
triangles.add_triangle(2 * i, 2 * i + 1, 2 * i + 2);
|
||||||
|
triangles.add_triangle(2 * i + 1, 2 * i + 2, 2 * i + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.painter().add(PaintCmd::Triangles(triangles));
|
||||||
|
}
|
||||||
|
ui.interact_hover(rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||||
|
struct Gradient(pub Vec<Srgba>);
|
||||||
|
|
||||||
|
impl Gradient {
|
||||||
|
pub fn one_color(srgba: Srgba) -> Self {
|
||||||
|
Self(vec![srgba, srgba])
|
||||||
|
}
|
||||||
|
pub fn texture_gradient(left: Srgba, right: Srgba) -> Self {
|
||||||
|
Self(vec![left, right])
|
||||||
|
}
|
||||||
|
pub fn ground_truth_linear_gradient(left: Srgba, right: Srgba) -> Self {
|
||||||
|
let left = Rgba::from(left);
|
||||||
|
let right = Rgba::from(right);
|
||||||
|
|
||||||
|
let n = 255;
|
||||||
|
Self(
|
||||||
|
(0..=n)
|
||||||
|
.map(|i| {
|
||||||
|
let t = i as f32 / n as f32;
|
||||||
|
Srgba::from(lerp(left..=right, t))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// This is how a bad person blends `sRGBA`
|
||||||
|
pub fn ground_truth_bad_srgba_gradient(left: Srgba, right: Srgba) -> Self {
|
||||||
|
let n = 255;
|
||||||
|
Self(
|
||||||
|
(0..=n)
|
||||||
|
.map(|i| {
|
||||||
|
let t = i as f32 / n as f32;
|
||||||
|
Srgba([
|
||||||
|
lerp((left[0] as f32)..=(right[0] as f32), t).round() as u8, // Don't ever do this please!
|
||||||
|
lerp((left[1] as f32)..=(right[1] as f32), t).round() as u8, // Don't ever do this please!
|
||||||
|
lerp((left[2] as f32)..=(right[2] as f32), t).round() as u8, // Don't ever do this please!
|
||||||
|
lerp((left[3] as f32)..=(right[3] as f32), t).round() as u8, // Don't ever do this please!
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do premultiplied alpha-aware blending of the gradient on top of the fill color
|
||||||
|
pub fn with_bg_fill(self, bg: Srgba) -> Self {
|
||||||
|
let bg = Rgba::from(bg);
|
||||||
|
Self(
|
||||||
|
self.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|fg| {
|
||||||
|
let fg = Rgba::from(fg);
|
||||||
|
Srgba::from(bg * (1.0 - fg.a()) + fg)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_pixel_row(&self) -> Vec<Srgba> {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TextureManager(HashMap<Gradient, TextureId>);
|
||||||
|
|
||||||
|
impl TextureManager {
|
||||||
|
fn get(&mut self, tex_loader: &mut TextureLoader<'_>, gradient: &Gradient) -> TextureId {
|
||||||
|
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||||
|
let pixels = gradient.to_pixel_row();
|
||||||
|
let width = pixels.len();
|
||||||
|
let height = 1;
|
||||||
|
tex_loader((width, height), &pixels)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ pub struct Vec2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn vec2(x: f32, y: f32) -> Vec2 {
|
pub const fn vec2(x: f32, y: f32) -> Vec2 {
|
||||||
Vec2 { x, y }
|
Vec2 { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::math::clamp;
|
||||||
/// Alpha channel is in linear space.
|
/// Alpha channel is in linear space.
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Srgba(pub [u8; 4]);
|
pub struct Srgba(pub(crate) [u8; 4]);
|
||||||
|
|
||||||
impl std::ops::Index<usize> for Srgba {
|
impl std::ops::Index<usize> for Srgba {
|
||||||
type Output = u8;
|
type Output = u8;
|
||||||
|
@ -49,9 +49,8 @@ impl Srgba {
|
||||||
Self([l, l, l, 0])
|
Self([l, l, l, 0])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an opaque version of self
|
pub fn is_opaque(&self) -> bool {
|
||||||
pub fn to_opaque(self) -> Self {
|
self.a() == 255
|
||||||
Rgba::from(self).to_opaque().into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn r(&self) -> u8 {
|
pub fn r(&self) -> u8 {
|
||||||
|
@ -67,6 +66,11 @@ impl Srgba {
|
||||||
self.0[3]
|
self.0[3]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an opaque version of self
|
||||||
|
pub fn to_opaque(self) -> Self {
|
||||||
|
Rgba::from(self).to_opaque().into()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_array(&self) -> [u8; 4] {
|
pub fn to_array(&self) -> [u8; 4] {
|
||||||
[self.r(), self.g(), self.b(), self.a()]
|
[self.r(), self.g(), self.b(), self.a()]
|
||||||
}
|
}
|
||||||
|
@ -94,7 +98,7 @@ pub const LIGHT_BLUE: Srgba = srgba(140, 160, 255, 255);
|
||||||
/// 0-1 linear space `RGBA` color with premultiplied alpha.
|
/// 0-1 linear space `RGBA` color with premultiplied alpha.
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Rgba(pub [f32; 4]);
|
pub struct Rgba(pub(crate) [f32; 4]);
|
||||||
|
|
||||||
impl std::ops::Index<usize> for Rgba {
|
impl std::ops::Index<usize> for Rgba {
|
||||||
type Output = f32;
|
type Output = f32;
|
||||||
|
@ -200,6 +204,18 @@ impl std::ops::Add for Rgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Mul<Rgba> for Rgba {
|
||||||
|
type Output = Rgba;
|
||||||
|
fn mul(self, other: Rgba) -> Rgba {
|
||||||
|
Rgba([
|
||||||
|
self[0] * other[0],
|
||||||
|
self[1] * other[1],
|
||||||
|
self[2] * other[2],
|
||||||
|
self[3] * other[3],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Mul<f32> for Rgba {
|
impl std::ops::Mul<f32> for Rgba {
|
||||||
type Output = Rgba;
|
type Output = Rgba;
|
||||||
fn mul(self, factor: f32) -> Rgba {
|
fn mul(self, factor: f32) -> Rgba {
|
||||||
|
|
|
@ -43,6 +43,51 @@ pub enum PaintCmd {
|
||||||
Triangles(Triangles),
|
Triangles(Triangles),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PaintCmd {
|
||||||
|
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
|
||||||
|
Self::LineSegment {
|
||||||
|
points,
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle_filled(center: Pos2, radius: f32, fill_color: impl Into<Srgba>) -> Self {
|
||||||
|
Self::Circle {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
fill: fill_color.into(),
|
||||||
|
stroke: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle_stroke(center: Pos2, radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||||
|
Self::Circle {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rect_filled(rect: Rect, corner_radius: f32, fill_color: impl Into<Srgba>) -> Self {
|
||||||
|
Self::Rect {
|
||||||
|
rect,
|
||||||
|
corner_radius,
|
||||||
|
fill: fill_color.into(),
|
||||||
|
stroke: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rect_stroke(rect: Rect, corner_radius: f32, stroke: impl Into<Stroke>) -> Self {
|
||||||
|
Self::Rect {
|
||||||
|
rect,
|
||||||
|
corner_radius,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: stroke.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Stroke {
|
pub struct Stroke {
|
||||||
|
|
|
@ -578,7 +578,7 @@ impl Ui {
|
||||||
self.allocate_space(child_ui.bounding_size())
|
self.allocate_space(child_ui.bounding_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a child ui
|
/// Create a child ui. You can use this to temporarily change the Style of a sub-region, for instance.
|
||||||
pub fn add_custom<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
|
pub fn add_custom<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
|
||||||
let child_rect = self.available();
|
let child_rect = self.available();
|
||||||
let mut child_ui = self.child_ui(child_rect);
|
let mut child_ui = self.child_ui(child_rect);
|
||||||
|
|
|
@ -38,14 +38,18 @@ 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) -> Response {
|
pub fn show_color(ui: &mut Ui, color: impl Into<Srgba>, desired_size: Vec2) -> Response {
|
||||||
|
show_srgba(ui, color.into(), desired_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_srgba(ui: &mut Ui, srgba: 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 {
|
||||||
rect,
|
rect,
|
||||||
corner_radius: 2.0,
|
corner_radius: 2.0,
|
||||||
fill: color,
|
fill: srgba,
|
||||||
stroke: Stroke::new(3.0, color.to_opaque()),
|
stroke: Stroke::new(3.0, srgba.to_opaque()),
|
||||||
});
|
});
|
||||||
ui.interact_hover(rect)
|
ui.interact_hover(rect)
|
||||||
}
|
}
|
||||||
|
@ -191,9 +195,9 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma) {
|
||||||
ui.style().spacing.clickable_diameter * 2.0,
|
ui.style().spacing.clickable_diameter * 2.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
show_color(ui, (*hsva).into(), current_color_size).tooltip_text("Current color");
|
show_color(ui, *hsva, current_color_size).tooltip_text("Current color");
|
||||||
|
|
||||||
show_color(ui, HsvaGamma { a: 1.0, ..*hsva }.into(), current_color_size)
|
show_color(ui, HsvaGamma { a: 1.0, ..*hsva }, current_color_size)
|
||||||
.tooltip_text("Current color (opaque)");
|
.tooltip_text("Current color (opaque)");
|
||||||
|
|
||||||
let opaque = HsvaGamma { a: 1.0, ..*hsva };
|
let opaque = HsvaGamma { a: 1.0, ..*hsva };
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::*;
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
texture_id: TextureId,
|
texture_id: TextureId,
|
||||||
|
uv: Rect,
|
||||||
desired_size: Vec2,
|
desired_size: Vec2,
|
||||||
bg_fill: Srgba,
|
bg_fill: Srgba,
|
||||||
tint: Srgba,
|
tint: Srgba,
|
||||||
|
@ -12,21 +13,28 @@ impl Image {
|
||||||
pub fn new(texture_id: TextureId, desired_size: Vec2) -> Self {
|
pub fn new(texture_id: TextureId, desired_size: Vec2) -> Self {
|
||||||
Self {
|
Self {
|
||||||
texture_id,
|
texture_id,
|
||||||
|
uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
|
||||||
desired_size,
|
desired_size,
|
||||||
tint: color::WHITE,
|
tint: color::WHITE,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Select UV range. Default is (0,0) in top-left, (1,1) bottom right.
|
||||||
|
pub fn uv(mut self, uv: impl Into<Rect>) -> Self {
|
||||||
|
self.uv = uv.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// A solid color to put behind the image. Useful for transparent images.
|
/// A solid color to put behind the image. Useful for transparent images.
|
||||||
pub fn bg_fill(mut self, bg_fill: Srgba) -> Self {
|
pub fn bg_fill(mut self, bg_fill: impl Into<Srgba>) -> Self {
|
||||||
self.bg_fill = bg_fill;
|
self.bg_fill = bg_fill.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Multiply image color with this. Default is WHITE (no tint).
|
/// Multiply image color with this. Default is WHITE (no tint).
|
||||||
pub fn tint(mut self, tint: Srgba) -> Self {
|
pub fn tint(mut self, tint: impl Into<Srgba>) -> Self {
|
||||||
self.tint = tint;
|
self.tint = tint.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +44,7 @@ impl Widget for Image {
|
||||||
use paint::*;
|
use paint::*;
|
||||||
let Self {
|
let Self {
|
||||||
texture_id,
|
texture_id,
|
||||||
|
uv,
|
||||||
desired_size,
|
desired_size,
|
||||||
bg_fill,
|
bg_fill,
|
||||||
tint,
|
tint,
|
||||||
|
@ -48,9 +57,8 @@ impl Widget for Image {
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TODO: builder pattern for Triangles
|
// TODO: builder pattern for Triangles
|
||||||
let uv = [pos2(0.0, 0.0), pos2(1.0, 1.0)];
|
|
||||||
let mut triangles = Triangles::with_texture(texture_id);
|
let mut triangles = Triangles::with_texture(texture_id);
|
||||||
triangles.add_rect_with_uv(rect, uv.into(), tint);
|
triangles.add_rect_with_uv(rect, uv, tint);
|
||||||
ui.painter().add(PaintCmd::Triangles(triangles));
|
ui.painter().add(PaintCmd::Triangles(triangles));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,7 @@ impl Painter {
|
||||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture));
|
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture));
|
||||||
|
|
||||||
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
// TODO: https://developer.mozilla.org/en-US/docs/Web/API/EXT_sRGB
|
||||||
|
// https://www.khronos.org/registry/webgl/extensions/EXT_sRGB/
|
||||||
let level = 0;
|
let level = 0;
|
||||||
let internal_format = Gl::RGBA;
|
let internal_format = Gl::RGBA;
|
||||||
let border = 0;
|
let border = 0;
|
||||||
|
|
Loading…
Reference in a new issue