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] Gamma for value (brightness) slider
|
||||
* [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
|
||||
* [ ] Premultiplied alpha is a bit of a pain in the ass. Maybe rethink this a bit.
|
||||
* [ ] Hue wheel
|
||||
* Containers
|
||||
* [ ] Scroll areas
|
||||
* [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)`
|
||||
* [ ] Keeping right/bottom on expand. Maybe cover jitteryness with quick animation?
|
||||
* [ ] Make auto-positioning of windows respect permanent side-bars.
|
||||
* [/] Image support
|
||||
* [x] Image support
|
||||
* [x] Show user textures
|
||||
* [ ] API for creating a texture managed by Egui
|
||||
* Backend-agnostic. Good for people doing Egui-apps (games etc).
|
||||
* [ ] Convert font texture to RGBA, or communicate format in initialization?
|
||||
* [ ] Generalized font atlas
|
||||
* [x] API for creating a texture managed by `egui::app::Backend`
|
||||
* Visuals
|
||||
* [x] Pixel-perfect painting (round positions to nearest pixel).
|
||||
* [x] Fix `aa_size`: should be 1, currently fudged at 1.5
|
||||
* [x] Fix thin rounded corners rendering bug (too bright)
|
||||
* [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
|
||||
* [ ] Fix alpha blending / sRGB weirdness in WebGL (EXT_sRGB)
|
||||
* [ ] Thin circles look bad
|
||||
* [ ] Allow adding multiple tooltips to the same widget, showing them all one after the other.
|
||||
* Math
|
||||
|
@ -86,7 +85,8 @@ TODO-list for the Egui project. If you looking for something to do, look here.
|
|||
* [x] Scroll input
|
||||
* [x] Change to resize cursor on hover
|
||||
* [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
|
||||
* [ ] Support canvas that does NOT cover entire screen.
|
||||
* [ ] Support multiple eguis in one web page.
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
//!
|
||||
//! The demo-code is also used in benchmarks and tests.
|
||||
mod app;
|
||||
mod color_test;
|
||||
mod fractal_clock;
|
||||
pub mod toggle_switch;
|
||||
|
||||
pub use {
|
||||
app::{DemoApp, DemoWindow},
|
||||
color_test::ColorTest,
|
||||
fractal_clock::FractalClock,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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,
|
||||
fractal_clock: FractalClock,
|
||||
num_frames_painted: u64,
|
||||
#[serde(skip)]
|
||||
color_test: ColorTest,
|
||||
show_color_test: bool,
|
||||
}
|
||||
|
||||
impl DemoApp {
|
||||
|
@ -189,6 +192,12 @@ impl DemoApp {
|
|||
|
||||
self.num_frames_painted += 1;
|
||||
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);
|
||||
});
|
||||
|
||||
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_location_hash = web_info
|
||||
.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)]
|
||||
pub fn vec2(x: f32, y: f32) -> Vec2 {
|
||||
pub const fn vec2(x: f32, y: f32) -> Vec2 {
|
||||
Vec2 { x, y }
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::math::clamp;
|
|||
/// Alpha channel is in linear space.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
#[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 {
|
||||
type Output = u8;
|
||||
|
@ -49,9 +49,8 @@ impl Srgba {
|
|||
Self([l, l, l, 0])
|
||||
}
|
||||
|
||||
/// Returns an opaque version of self
|
||||
pub fn to_opaque(self) -> Self {
|
||||
Rgba::from(self).to_opaque().into()
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
self.a() == 255
|
||||
}
|
||||
|
||||
pub fn r(&self) -> u8 {
|
||||
|
@ -67,6 +66,11 @@ impl Srgba {
|
|||
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] {
|
||||
[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.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[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 {
|
||||
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 {
|
||||
type Output = Rgba;
|
||||
fn mul(self, factor: f32) -> Rgba {
|
||||
|
|
|
@ -43,6 +43,51 @@ pub enum PaintCmd {
|
|||
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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Stroke {
|
||||
|
|
|
@ -578,7 +578,7 @@ impl Ui {
|
|||
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) {
|
||||
let child_rect = self.available();
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
background_checkers(ui.painter(), rect);
|
||||
ui.painter().add(PaintCmd::Rect {
|
||||
rect,
|
||||
corner_radius: 2.0,
|
||||
fill: color,
|
||||
stroke: Stroke::new(3.0, color.to_opaque()),
|
||||
fill: srgba,
|
||||
stroke: Stroke::new(3.0, srgba.to_opaque()),
|
||||
});
|
||||
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,
|
||||
);
|
||||
|
||||
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)");
|
||||
|
||||
let opaque = HsvaGamma { a: 1.0, ..*hsva };
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::*;
|
|||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Image {
|
||||
texture_id: TextureId,
|
||||
uv: Rect,
|
||||
desired_size: Vec2,
|
||||
bg_fill: Srgba,
|
||||
tint: Srgba,
|
||||
|
@ -12,21 +13,28 @@ impl Image {
|
|||
pub fn new(texture_id: TextureId, desired_size: Vec2) -> Self {
|
||||
Self {
|
||||
texture_id,
|
||||
uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
|
||||
desired_size,
|
||||
tint: color::WHITE,
|
||||
..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.
|
||||
pub fn bg_fill(mut self, bg_fill: Srgba) -> Self {
|
||||
self.bg_fill = bg_fill;
|
||||
pub fn bg_fill(mut self, bg_fill: impl Into<Srgba>) -> Self {
|
||||
self.bg_fill = bg_fill.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Multiply image color with this. Default is WHITE (no tint).
|
||||
pub fn tint(mut self, tint: Srgba) -> Self {
|
||||
self.tint = tint;
|
||||
pub fn tint(mut self, tint: impl Into<Srgba>) -> Self {
|
||||
self.tint = tint.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +44,7 @@ impl Widget for Image {
|
|||
use paint::*;
|
||||
let Self {
|
||||
texture_id,
|
||||
uv,
|
||||
desired_size,
|
||||
bg_fill,
|
||||
tint,
|
||||
|
@ -48,9 +57,8 @@ impl Widget for Image {
|
|||
}
|
||||
{
|
||||
// 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);
|
||||
triangles.add_rect_with_uv(rect, uv.into(), tint);
|
||||
triangles.add_rect_with_uv(rect, uv, tint);
|
||||
ui.painter().add(PaintCmd::Triangles(triangles));
|
||||
}
|
||||
|
||||
|
|
|
@ -207,6 +207,7 @@ impl Painter {
|
|||
gl.bind_texture(Gl::TEXTURE_2D, Some(&self.egui_texture));
|
||||
|
||||
// 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 internal_format = Gl::RGBA;
|
||||
let border = 0;
|
||||
|
|
Loading…
Reference in a new issue