mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 21:30:03 -04:00
Support images with rounded corners (#3257)
* Add `Rect::ZERO` * Add `Rounding::ZERO` * Add `RectShape::new` * Add `Image::rounding` to support images with rounded corners
This commit is contained in:
@@ -120,13 +120,13 @@ impl Mesh {
|
||||
pub fn append_ref(&mut self, other: &Mesh) {
|
||||
crate::epaint_assert!(other.is_valid());
|
||||
|
||||
if !self.is_empty() {
|
||||
if self.is_empty() {
|
||||
self.texture_id = other.texture_id;
|
||||
} else {
|
||||
assert_eq!(
|
||||
self.texture_id, other.texture_id,
|
||||
"Can't merge Mesh using different textures"
|
||||
);
|
||||
} else {
|
||||
self.texture_id = other.texture_id;
|
||||
}
|
||||
|
||||
let index_offset = self.vertices.len() as u32;
|
||||
|
||||
@@ -282,6 +282,8 @@ impl Shape {
|
||||
pub fn texture_id(&self) -> super::TextureId {
|
||||
if let Shape::Mesh(mesh) = self {
|
||||
mesh.texture_id
|
||||
} else if let Shape::Rect(rect_shape) = self {
|
||||
rect_shape.fill_texture_id
|
||||
} else {
|
||||
super::TextureId::default()
|
||||
}
|
||||
@@ -406,6 +408,8 @@ pub struct PathShape {
|
||||
|
||||
/// Color and thickness of the line.
|
||||
pub stroke: Stroke,
|
||||
// TODO(emilk): Add texture support either by supplying uv for each point,
|
||||
// or by some transform from points to uv (e.g. a callback or a linear transform matrix).
|
||||
}
|
||||
|
||||
impl PathShape {
|
||||
@@ -476,7 +480,7 @@ impl From<PathShape> for Shape {
|
||||
pub struct RectShape {
|
||||
pub rect: Rect,
|
||||
|
||||
/// How rounded the corners are. Use `Rounding::none()` for no rounding.
|
||||
/// How rounded the corners are. Use `Rounding::ZERO` for no rounding.
|
||||
pub rounding: Rounding,
|
||||
|
||||
/// How to fill the rectangle.
|
||||
@@ -484,9 +488,37 @@ pub struct RectShape {
|
||||
|
||||
/// The thickness and color of the outline.
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// If the rect should be filled with a texture, which one?
|
||||
///
|
||||
/// The texture is multiplied with [`Self::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))`.
|
||||
pub uv: Rect,
|
||||
}
|
||||
|
||||
impl RectShape {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
) -> Self {
|
||||
Self {
|
||||
rect,
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filled(
|
||||
rect: Rect,
|
||||
@@ -498,6 +530,8 @@ impl RectShape {
|
||||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,6 +542,8 @@ impl RectShape {
|
||||
rounding: rounding.into(),
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +585,7 @@ pub struct Rounding {
|
||||
impl Default for Rounding {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::none()
|
||||
Self::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +602,14 @@ impl From<f32> for Rounding {
|
||||
}
|
||||
|
||||
impl Rounding {
|
||||
/// No rounding on any corner.
|
||||
pub const ZERO: Self = Self {
|
||||
nw: 0.0,
|
||||
ne: 0.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn same(radius: f32) -> Self {
|
||||
Self {
|
||||
@@ -577,6 +621,7 @@ impl Rounding {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[deprecated = "Use Rounding::ZERO"]
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
nw: 0.0,
|
||||
|
||||
@@ -492,6 +492,20 @@ impl Path {
|
||||
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
|
||||
fill_closed_path(feathering, &mut self.0, color, out);
|
||||
}
|
||||
|
||||
/// Like [`Self::fill`] but with texturing.
|
||||
///
|
||||
/// The `uv_from_pos` is called for each vertex position.
|
||||
pub fn fill_with_uv(
|
||||
&mut self,
|
||||
feathering: f32,
|
||||
color: Color32,
|
||||
texture_id: TextureId,
|
||||
uv_from_pos: impl Fn(Pos2) -> Pos2,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
fill_closed_path_with_uv(feathering, &mut self.0, color, texture_id, uv_from_pos, out);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod path {
|
||||
@@ -508,7 +522,7 @@ pub mod path {
|
||||
|
||||
let r = clamp_radius(rounding, rect);
|
||||
|
||||
if r == Rounding::none() {
|
||||
if r == Rounding::ZERO {
|
||||
let min = rect.min;
|
||||
let max = rect.max;
|
||||
path.reserve(4);
|
||||
@@ -728,6 +742,89 @@ fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`fill_closed_path`] but with texturing.
|
||||
///
|
||||
/// The `uv_from_pos` is called for each vertex position.
|
||||
fn fill_closed_path_with_uv(
|
||||
feathering: f32,
|
||||
path: &mut [PathPoint],
|
||||
color: Color32,
|
||||
texture_id: TextureId,
|
||||
uv_from_pos: impl Fn(Pos2) -> Pos2,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
if color == Color32::TRANSPARENT {
|
||||
return;
|
||||
}
|
||||
|
||||
if out.is_empty() {
|
||||
out.texture_id = texture_id;
|
||||
} else {
|
||||
assert_eq!(
|
||||
out.texture_id, texture_id,
|
||||
"Mixing different `texture_id` in the same "
|
||||
);
|
||||
}
|
||||
|
||||
let n = path.len() as u32;
|
||||
if feathering > 0.0 {
|
||||
if cw_signed_area(path) < 0.0 {
|
||||
// Wrong winding order - fix:
|
||||
path.reverse();
|
||||
for point in path.iter_mut() {
|
||||
point.normal = -point.normal;
|
||||
}
|
||||
}
|
||||
|
||||
out.reserve_triangles(3 * n as usize);
|
||||
out.reserve_vertices(2 * n as usize);
|
||||
let color_outer = Color32::TRANSPARENT;
|
||||
let idx_inner = out.vertices.len() as u32;
|
||||
let idx_outer = idx_inner + 1;
|
||||
|
||||
// The fill:
|
||||
for i in 2..n {
|
||||
out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
|
||||
}
|
||||
|
||||
// The feathering:
|
||||
let mut i0 = n - 1;
|
||||
for i1 in 0..n {
|
||||
let p1 = &path[i1 as usize];
|
||||
let dm = 0.5 * feathering * p1.normal;
|
||||
|
||||
let pos = p1.pos - dm;
|
||||
out.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: uv_from_pos(pos),
|
||||
color,
|
||||
});
|
||||
|
||||
let pos = p1.pos + dm;
|
||||
out.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: uv_from_pos(pos),
|
||||
color: color_outer,
|
||||
});
|
||||
|
||||
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
||||
out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
|
||||
i0 = i1;
|
||||
}
|
||||
} else {
|
||||
out.reserve_triangles(n as usize);
|
||||
let idx = out.vertices.len() as u32;
|
||||
out.vertices.extend(path.iter().map(|p| Vertex {
|
||||
pos: p.pos,
|
||||
uv: uv_from_pos(p.pos),
|
||||
color,
|
||||
}));
|
||||
for i in 2..n {
|
||||
out.add_triangle(idx, idx + i - 1, idx + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tessellate the given path as a stroke with thickness.
|
||||
fn stroke_path(
|
||||
feathering: f32,
|
||||
@@ -1304,6 +1401,8 @@ impl Tessellator {
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
fill_texture_id,
|
||||
uv,
|
||||
} = *rect;
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
@@ -1345,7 +1444,21 @@ impl Tessellator {
|
||||
path.clear();
|
||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||
path.add_line_loop(&self.scratchpad_points);
|
||||
path.fill(self.feathering, fill, out);
|
||||
|
||||
if uv.is_positive() {
|
||||
// Textured
|
||||
let uv_from_pos = |p: Pos2| {
|
||||
pos2(
|
||||
remap(p.x, rect.x_range(), uv.x_range()),
|
||||
remap(p.y, rect.y_range(), uv.y_range()),
|
||||
)
|
||||
};
|
||||
path.fill_with_uv(self.feathering, fill, fill_texture_id, uv_from_pos, out);
|
||||
} else {
|
||||
// Untextured
|
||||
path.fill(self.feathering, fill, out);
|
||||
}
|
||||
|
||||
path.stroke_closed(self.feathering, stroke, out);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user