diff --git a/TODO.md b/TODO.md index 3396f667..48b5c5a9 100644 --- a/TODO.md +++ b/TODO.md @@ -53,9 +53,8 @@ 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 - * [ ] user-chosen texture ids (so people can show thing with mipmaps and whatnot) - * [ ] `enum TextureId { Egui, User(u64) }` added to `Triangles` +* [/] 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? diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index 2edc381c..0da8ba48 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -2,7 +2,7 @@ use crate::{ containers::show_tooltip, math::*, - paint::{self, color::WHITE, PaintCmd, Texture, Triangles, Vertex}, + paint::{self, color::WHITE, PaintCmd, Texture, Triangles}, *, }; @@ -21,18 +21,8 @@ impl Texture { size *= ui.available().width() / size.x; } let rect = ui.allocate_space(size); - let top_left = Vertex { - pos: rect.min, - uv: pos2(0.0, 0.0), - color: WHITE, - }; - let bottom_right = Vertex { - pos: rect.max, - uv: pos2(1.0, 1.0), - color: WHITE, - }; let mut triangles = Triangles::default(); - triangles.add_rect(top_left, bottom_right); + triangles.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), WHITE); ui.painter().add(PaintCmd::Triangles(triangles)); let tex_w = self.width as f32; @@ -49,18 +39,12 @@ impl Texture { let u = u.max(texel_radius).min(tex_w - texel_radius); let v = v.max(texel_radius).min(tex_h - texel_radius); - let top_left = Vertex { - pos: zoom_rect.min, - uv: pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h), - color: WHITE, - }; - let bottom_right = Vertex { - pos: zoom_rect.max, - uv: pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h), - color: WHITE, - }; + let uv_rect = Rect::from_min_max( + pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h), + pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h), + ); let mut triangles = Triangles::default(); - triangles.add_rect(top_left, bottom_right); + triangles.add_rect_with_uv(zoom_rect, uv_rect, WHITE); ui.painter().add(PaintCmd::Triangles(triangles)); }); } @@ -70,7 +54,7 @@ impl Texture { impl paint::FontDefinitions { pub fn ui(&mut self, ui: &mut Ui) { for (text_style, (_family, size)) in self.fonts.iter_mut() { - // TODO: radiobutton for family + // TODO: radio button for family ui.add( Slider::f32(size, 4.0..=40.0) .precision(0) diff --git a/egui/src/lib.rs b/egui/src/lib.rs index a7441f35..1f0fd72c 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -75,7 +75,7 @@ pub use { layout::*, math::*, memory::Memory, - paint::{color, PaintJobs, Rgba, Srgba, Stroke, TextStyle, Texture}, + paint::{color, PaintJobs, Rgba, Srgba, Stroke, TextStyle, Texture, TextureId}, painter::Painter, style::Style, types::*, diff --git a/egui/src/math/rect.rs b/egui/src/math/rect.rs index 4c5ee691..152d5fcb 100644 --- a/egui/src/math/rect.rs +++ b/egui/src/math/rect.rs @@ -199,3 +199,10 @@ impl std::fmt::Debug for Rect { write!(f, "[{:?} - {:?}]", self.min, self.max) } } + +/// from (min, max) or (left top, right bottom) +impl From<[Pos2; 2]> for Rect { + fn from([min, max]: [Pos2; 2]) -> Self { + Self { min, max } + } +} diff --git a/egui/src/paint.rs b/egui/src/paint.rs index df0fc4a6..38343531 100644 --- a/egui/src/paint.rs +++ b/egui/src/paint.rs @@ -13,6 +13,6 @@ pub use { color::{Rgba, Srgba}, command::{PaintCmd, Stroke}, fonts::{FontDefinitions, Fonts, TextStyle}, - tessellator::{PaintJobs, PaintOptions, Triangles, Vertex, WHITE_UV}, + tessellator::{PaintJobs, PaintOptions, TextureId, Triangles, Vertex, WHITE_UV}, texture_atlas::Texture, }; diff --git a/egui/src/paint/color.rs b/egui/src/paint/color.rs index 688af27c..404e9a20 100644 --- a/egui/src/paint/color.rs +++ b/egui/src/paint/color.rs @@ -41,6 +41,10 @@ impl Srgba { Self([0, 0, 0, a]) } + pub fn white_alpha(a: u8) -> Self { + Rgba::white_alpha(linear_from_alpha_byte(a)).into() + } + pub const fn additive_luminance(l: u8) -> Self { Self([l, l, l, 0]) } @@ -49,6 +53,27 @@ impl Srgba { pub fn to_opaque(self) -> Self { Rgba::from(self).to_opaque().into() } + + pub fn r(&self) -> u8 { + self.0[0] + } + pub fn g(&self) -> u8 { + self.0[1] + } + pub fn b(&self) -> u8 { + self.0[2] + } + pub fn a(&self) -> u8 { + self.0[3] + } + + pub fn to_array(&self) -> [u8; 4] { + [self.r(), self.g(), self.b(), self.a()] + } + + pub fn to_tuple(&self) -> (u8, u8, u8, u8) { + (self.r(), self.g(), self.b(), self.a()) + } } // ---------------------------------------------------------------------------- diff --git a/egui/src/paint/tessellator.rs b/egui/src/paint/tessellator.rs index fd15c491..2d9f01b8 100644 --- a/egui/src/paint/tessellator.rs +++ b/egui/src/paint/tessellator.rs @@ -14,6 +14,24 @@ use { crate::math::*, }; +/// What texture to use in a `Triangles` mesh. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum TextureId { + /// The Egui font texture. + /// If you don't want to use a texture, pick this and the `WHITE_UV` for uv-coord. + Egui, + + /// Your own texture, defined in any which way you want. + /// Egui won't care. The backend renderer will presumably use this to look up what texture to use. + User(u64), +} + +impl Default for TextureId { + fn default() -> Self { + Self::Egui + } +} + /// The UV coordinate of a white region of the texture mesh. /// The default Egui texture has the top-left corner pixel fully white. /// You need need use a clamping texture sampler for this to work @@ -44,8 +62,12 @@ pub struct Vertex { pub struct Triangles { /// Draw as triangles (i.e. the length is always multiple of three). pub indices: Vec, + /// The vertex data indexed by `indices`. pub vertices: Vec, + + /// The texture to use when drawing these triangles + pub texture_id: TextureId, } /// A clip triangle and some textured triangles. @@ -58,14 +80,34 @@ pub type PaintJobs = Vec; /// ## Helpers for adding impl Triangles { + pub fn with_texture(texture_id: TextureId) -> Self { + Self { + texture_id, + ..Default::default() + } + } + /// Are all indices within the bounds of the contained vertices? pub fn is_valid(&self) -> bool { let n = self.vertices.len() as u32; self.indices.iter().all(|&i| i < n) } + pub fn is_empty(&self) -> bool { + self.indices.is_empty() && self.vertices.is_empty() + } + /// Append all the indices and vertices of `other` to `self`. pub fn append(&mut self, other: &Triangles) { + if self.is_empty() { + self.texture_id = other.texture_id; + } else { + assert_eq!( + self.texture_id, other.texture_id, + "Can't merge Triangles using different textures" + ); + } + let index_offset = self.vertices.len() as u32; for index in &other.indices { self.indices.push(index_offset + index); @@ -74,6 +116,7 @@ impl Triangles { } pub fn colored_vertex(&mut self, pos: Pos2, color: Srgba) { + debug_assert!(self.texture_id == TextureId::Egui); self.vertices.push(Vertex { pos, uv: WHITE_UV, @@ -100,44 +143,42 @@ impl Triangles { self.vertices.reserve(additional); } - /// Uniformly colored rectangle. - pub fn add_rect(&mut self, top_left: Vertex, bottom_right: Vertex) { - debug_assert_eq!(top_left.color, bottom_right.color); - + /// Rectangle with a texture and color. + pub fn add_rect_with_uv(&mut self, pos: Rect, uv: Rect, color: Srgba) { let idx = self.vertices.len() as u32; self.add_triangle(idx + 0, idx + 1, idx + 2); self.add_triangle(idx + 2, idx + 1, idx + 3); - let top_right = Vertex { - pos: pos2(bottom_right.pos.x, top_left.pos.y), - uv: pos2(bottom_right.uv.x, top_left.uv.y), - color: top_left.color, + let right_top = Vertex { + pos: pos.right_top(), + uv: uv.right_top(), + color, }; - let botom_left = Vertex { - pos: pos2(top_left.pos.x, bottom_right.pos.y), - uv: pos2(top_left.uv.x, bottom_right.uv.y), - color: top_left.color, + let left_top = Vertex { + pos: pos.left_top(), + uv: uv.left_top(), + color, }; - self.vertices.push(top_left); - self.vertices.push(top_right); - self.vertices.push(botom_left); - self.vertices.push(bottom_right); + let left_bottom = Vertex { + pos: pos.left_bottom(), + uv: uv.left_bottom(), + color, + }; + let right_bottom = Vertex { + pos: pos.right_bottom(), + uv: uv.right_bottom(), + color, + }; + self.vertices.push(left_top); + self.vertices.push(right_top); + self.vertices.push(left_bottom); + self.vertices.push(right_bottom); } /// Uniformly colored rectangle. pub fn add_colored_rect(&mut self, rect: Rect, color: Srgba) { - self.add_rect( - Vertex { - pos: rect.min, - uv: WHITE_UV, - color, - }, - Vertex { - pos: rect.max, - uv: WHITE_UV, - color, - }, - ) + debug_assert!(self.texture_id == TextureId::Egui); + self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color) } /// This is for platforms that only support 16-bit index buffers. @@ -189,6 +230,7 @@ impl Triangles { .map(|vi| vi - min_vindex) .collect(), vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(), + texture_id: self.texture_id, }); } output @@ -703,19 +745,17 @@ fn tessellate_paint_command( for x_offset in line.x_offsets.iter().take(line.x_offsets.len() - 1) { let c = chars.next().unwrap(); if let Some(glyph) = font.uv_rect(c) { - let mut top_left = Vertex { - pos: pos + glyph.offset + vec2(*x_offset, line.y_min) + text_offset, - uv: pos2(glyph.min.0 as f32 / tex_w, glyph.min.1 as f32 / tex_h), - color, - }; - top_left.pos.x = font.round_to_pixel(top_left.pos.x); // Pixel-perfection. - top_left.pos.y = font.round_to_pixel(top_left.pos.y); // Pixel-perfection. - let bottom_right = Vertex { - pos: top_left.pos + glyph.size, - uv: pos2(glyph.max.0 as f32 / tex_w, glyph.max.1 as f32 / tex_h), - color, - }; - out.add_rect(top_left, bottom_right); + let mut left_top = + pos + glyph.offset + vec2(*x_offset, line.y_min) + text_offset; + left_top.x = font.round_to_pixel(left_top.x); // Pixel-perfection. + left_top.y = font.round_to_pixel(left_top.y); // Pixel-perfection. + + let pos = Rect::from_min_max(left_top, left_top + glyph.size); + let uv = Rect::from_min_max( + pos2(glyph.min.0 as f32 / tex_w, glyph.min.1 as f32 / tex_h), + pos2(glyph.max.0 as f32 / tex_w, glyph.max.1 as f32 / tex_h), + ); + out.add_rect_with_uv(pos, uv, color); } } } @@ -747,7 +787,16 @@ pub fn tessellate_paint_commands( for (clip_rect, cmd) in commands { // TODO: cull(clip_rect, cmd) - if jobs.is_empty() || jobs.last().unwrap().0 != clip_rect { + if let PaintCmd::Triangles(triangles) = cmd { + // Assume non-Egui texture, which means own paint job: + jobs.push((clip_rect, triangles)); + continue; + } + + if jobs.is_empty() + || jobs.last().unwrap().0 != clip_rect + || jobs.last().unwrap().1.texture_id != TextureId::Egui + { jobs.push((clip_rect, Triangles::default())); } diff --git a/egui/src/ui.rs b/egui/src/ui.rs index a6e1058e..c12b7621 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -489,6 +489,11 @@ impl Ui { let rect = self.allocate_space(desired_size); self.painter_at(rect) } + + /// Show an image here with the given size + pub fn image(&mut self, texture_id: TextureId, desired_size: Vec2) -> Response { + self.add(Image::new(texture_id, desired_size)) + } } /// # Colors diff --git a/egui/src/widgets.rs b/egui/src/widgets.rs index 6999e293..7b1cae42 100644 --- a/egui/src/widgets.rs +++ b/egui/src/widgets.rs @@ -9,10 +9,11 @@ use crate::{layout::Direction, *}; pub mod color_picker; +mod image; mod slider; pub(crate) mod text_edit; -pub use {slider::*, text_edit::*}; +pub use {image::Image, slider::*, text_edit::*}; use paint::*; diff --git a/egui/src/widgets/image.rs b/egui/src/widgets/image.rs new file mode 100644 index 00000000..910da4f8 --- /dev/null +++ b/egui/src/widgets/image.rs @@ -0,0 +1,59 @@ +use crate::*; + +#[derive(Clone, Copy, Debug, Default)] +pub struct Image { + texture_id: TextureId, + desired_size: Vec2, + bg_fill: Srgba, + tint: Srgba, +} + +impl Image { + pub fn new(texture_id: TextureId, desired_size: Vec2) -> Self { + Self { + texture_id, + desired_size, + tint: color::WHITE, + ..Default::default() + } + } + + /// 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; + self + } + + /// Multiply image color with this. Default is WHITE (no tint). + pub fn tint(mut self, tint: Srgba) -> Self { + self.tint = tint; + self + } +} + +impl Widget for Image { + fn ui(self, ui: &mut Ui) -> Response { + use paint::*; + let Self { + texture_id, + desired_size, + bg_fill, + tint, + } = self; + let rect = ui.allocate_space(desired_size); + if bg_fill != Default::default() { + let mut triangles = Triangles::default(); + triangles.add_colored_rect(rect, bg_fill); + ui.painter().add(PaintCmd::Triangles(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); + triangles.add_rect_with_uv(rect, uv.into(), tint); + ui.painter().add(PaintCmd::Triangles(triangles)); + } + + ui.interact_hover(rect) + } +}