Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add epaint::Brush for controlling RectShape texturing #5565

Merged
merged 4 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions crates/egui/src/widgets/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use epaint::{
use crate::{
load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll},
pos2, Color32, Context, Id, Mesh, Painter, Rect, Response, Rounding, Sense, Shape, Spinner,
Stroke, TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType,
TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType,
};

/// A widget which displays an image.
Expand Down Expand Up @@ -822,15 +822,10 @@ pub fn paint_texture_at(
painter.add(Shape::mesh(mesh));
}
None => {
painter.add(RectShape {
rect,
rounding: options.rounding,
fill: options.tint,
stroke: Stroke::NONE,
blur_width: 0.0,
fill_texture_id: texture.id,
uv: options.uv,
});
painter.add(
RectShape::filled(rect, options.rounding, options.tint)
.with_texture(texture.id, options.uv),
);
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions crates/epaint/src/brush.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{Rect, TextureId};

/// Controls texturing of a [`crate::RectShape`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Brush {
/// If the rect should be filled with a texture, which one?
///
/// The texture is multiplied with [`crate::RectShape::fill`].
pub fill_texture_id: TextureId,

/// What UV coordinates to use for the texture?
///
/// To display a texture, set [`Self::fill_texture_id`],
/// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`.
///
/// Use [`Rect::ZERO`] to turn off texturing.
pub uv: Rect,
}
2 changes: 2 additions & 0 deletions crates/epaint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)]

mod brush;
pub mod color;
pub mod image;
mod margin;
Expand All @@ -44,6 +45,7 @@ pub mod util;
mod viewport;

pub use self::{
brush::Brush,
color::ColorMode,
image::{ColorImage, FontImage, ImageData, ImageDelta},
margin::Margin,
Expand Down
17 changes: 9 additions & 8 deletions crates/epaint/src/shape_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ pub fn adjust_colors(
fill,
stroke,
blur_width: _,
fill_texture_id: _,
uv: _,
brush: _,
}) => {
adjust_color(fill);
adjust_color(&mut stroke.color);
Expand All @@ -87,7 +86,7 @@ pub fn adjust_colors(
}

if !galley.is_empty() {
let galley = std::sync::Arc::make_mut(galley);
let galley = Arc::make_mut(galley);
for row in &mut galley.rows {
for vertex in &mut row.visuals.mesh.vertices {
adjust_color(&mut vertex.color);
Expand All @@ -96,11 +95,13 @@ pub fn adjust_colors(
}
}

Shape::Mesh(Mesh {
indices: _,
vertices,
texture_id: _,
}) => {
Shape::Mesh(mesh) => {
let Mesh {
indices: _,
vertices,
texture_id: _,
} = Arc::make_mut(mesh);

for v in vertices {
adjust_color(&mut v.color);
}
Expand Down
72 changes: 41 additions & 31 deletions crates/epaint/src/shapes/rect_shape.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::sync::Arc;

use crate::*;

/// How to paint a rectangle.
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RectShape {
pub rect: Rect,
Expand All @@ -28,18 +30,23 @@ pub struct RectShape {
/// The blur is currently implemented using a simple linear blur in sRGBA gamma space.
pub blur_width: f32,

/// If the rect should be filled with a texture, which one?
/// Controls texturing, if any.
///
/// The texture is multiplied with [`Self::fill`].
pub fill_texture_id: TextureId,
/// Since most rectangles do not have a texture, this is optional and in an `Arc`,
/// so that [`RectShape`] is kept small..
pub brush: Option<Arc<Brush>>,
}

/// What UV coordinates to use for the texture?
///
/// To display a texture, set [`Self::fill_texture_id`],
/// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`.
///
/// Use [`Rect::ZERO`] to turn off texturing.
pub uv: Rect,
#[test]
fn rect_shape_size() {
assert_eq!(
std::mem::size_of::<RectShape>(), 48,
"RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
);
assert!(
std::mem::size_of::<RectShape>() <= 64,
"RectShape is getting way too big!"
);
}

impl RectShape {
Expand All @@ -57,8 +64,7 @@ impl RectShape {
fill: fill_color.into(),
stroke: stroke.into(),
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO,
brush: Default::default(),
}
}

Expand All @@ -68,29 +74,14 @@ impl RectShape {
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
) -> Self {
Self {
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: Default::default(),
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO,
}
Self::new(rect, rounding, fill_color, Stroke::NONE)
}

/// The stroke extends _outside_ the [`Rect`].
#[inline]
pub fn stroke(rect: Rect, rounding: impl Into<Rounding>, stroke: impl Into<Stroke>) -> Self {
Self {
rect,
rounding: rounding.into(),
fill: Default::default(),
stroke: stroke.into(),
blur_width: 0.0,
fill_texture_id: Default::default(),
uv: Rect::ZERO,
}
let fill = Color32::TRANSPARENT;
Self::new(rect, rounding, fill, stroke)
}

/// If larger than zero, the edges of the rectangle
Expand All @@ -105,6 +96,16 @@ impl RectShape {
self
}

/// Set the texture to use when painting this rectangle, if any.
#[inline]
pub fn with_texture(mut self, fill_texture_id: TextureId, uv: Rect) -> Self {
self.brush = Some(Arc::new(Brush {
fill_texture_id,
uv,
}));
self
}

/// The visual bounding rectangle (includes stroke width)
#[inline]
pub fn visual_bounding_rect(&self) -> Rect {
Expand All @@ -115,6 +116,15 @@ impl RectShape {
self.rect.expand(width + self.blur_width / 2.0)
}
}

/// The texture to use when painting this rectangle, if any.
///
/// If no texture is set, this will return [`TextureId::default`].
pub fn fill_texture_id(&self) -> TextureId {
self.brush
.as_ref()
.map_or_else(TextureId::default, |brush| brush.fill_texture_id)
}
}

impl From<RectShape> for Shape {
Expand Down
30 changes: 26 additions & 4 deletions crates/epaint/src/shapes/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ pub enum Shape {
/// A general triangle mesh.
///
/// Can be used to display images.
Mesh(Mesh),
///
/// Wrapped in an [`Arc`] to minimize the size of [`Shape`].
Mesh(Arc<Mesh>),

/// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
QuadraticBezier(QuadraticBezierShape),
Expand All @@ -68,6 +70,18 @@ pub enum Shape {
Callback(PaintCallback),
}

#[test]
fn shape_size() {
assert_eq!(
std::mem::size_of::<Shape>(), 64,
"Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
);
assert!(
std::mem::size_of::<Shape>() <= 64,
"Shape is getting way too big!"
);
}

#[test]
fn shape_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
Expand All @@ -84,6 +98,13 @@ impl From<Vec<Self>> for Shape {
impl From<Mesh> for Shape {
#[inline(always)]
fn from(mesh: Mesh) -> Self {
Self::Mesh(mesh.into())
}
}

impl From<Arc<Mesh>> for Shape {
#[inline(always)]
fn from(mesh: Arc<Mesh>) -> Self {
Self::Mesh(mesh)
}
}
Expand Down Expand Up @@ -314,7 +335,8 @@ impl Shape {
}

#[inline]
pub fn mesh(mesh: Mesh) -> Self {
pub fn mesh(mesh: impl Into<Arc<Mesh>>) -> Self {
let mesh = mesh.into();
debug_assert!(mesh.is_valid());
Self::Mesh(mesh)
}
Expand Down Expand Up @@ -369,7 +391,7 @@ impl Shape {
if let Self::Mesh(mesh) = self {
mesh.texture_id
} else if let Self::Rect(rect_shape) = self {
rect_shape.fill_texture_id
rect_shape.fill_texture_id()
} else {
crate::TextureId::default()
}
Expand Down Expand Up @@ -446,7 +468,7 @@ impl Shape {
galley.rect = transform.scaling * galley.rect;
}
Self::Mesh(mesh) => {
mesh.transform(transform);
Arc::make_mut(mesh).transform(transform);
}
Self::QuadraticBezier(bezier_shape) => {
bezier_shape.points[0] = transform * bezier_shape.points[0];
Expand Down
14 changes: 9 additions & 5 deletions crates/epaint/src/tessellator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,7 @@ impl Tessellator {
return;
}

out.append(mesh);
out.append_ref(&mesh);
}
Shape::LineSegment { points, stroke } => {
self.tessellate_line_segment(points, stroke, out);
Expand Down Expand Up @@ -1693,14 +1693,14 @@ impl Tessellator {
/// * `rect`: the rectangle to tessellate.
/// * `out`: triangles are appended to this.
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
let brush = rect.brush.as_ref();
let RectShape {
mut rect,
mut rounding,
fill,
stroke,
mut blur_width,
fill_texture_id,
uv,
..
} = *rect;

if self.options.coarse_tessellation_culling
Expand Down Expand Up @@ -1775,7 +1775,11 @@ impl Tessellator {
path.add_line_loop(&self.scratchpad_points);
let path_stroke = PathStroke::from(stroke).outside();

if uv.is_positive() {
if let Some(brush) = brush {
let crate::Brush {
fill_texture_id,
uv,
} = **brush;
// Textured
let uv_from_pos = |p: Pos2| {
pos2(
Expand Down Expand Up @@ -2173,7 +2177,7 @@ impl Tessellator {

profiling::scope!("distribute results", tessellated.len().to_string());
for (index, mesh) in tessellated {
shapes[index].shape = Shape::Mesh(mesh);
shapes[index].shape = Shape::Mesh(mesh.into());
}
}

Expand Down
Loading