Add `RectShape::blur_width` to implement shadows (#4267)
This is mostly a refactor, but has some performance benefits: * We (re)use the same tessellator as for everything else, leading to less allocations * We cull shapes before rendering them Adding `RectShape::blur_width` means it can also be used for other effects, such as glow.
This commit is contained in:
parent
73dbfd689b
commit
a541e021aa
|
|
@ -291,9 +291,8 @@ impl Frame {
|
|||
if shadow == Default::default() {
|
||||
frame_shape
|
||||
} else {
|
||||
let shadow = shadow.tessellate(outer_rect, rounding);
|
||||
let shadow = Shape::Mesh(shadow);
|
||||
Shape::Vec(vec![shadow, frame_shape])
|
||||
let shadow = shadow.as_shape(outer_rect, rounding);
|
||||
Shape::Vec(vec![Shape::from(shadow), frame_shape])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -762,6 +762,7 @@ pub fn paint_texture_at(
|
|||
rounding: options.rounding,
|
||||
fill: options.tint,
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: texture.id,
|
||||
uv: options.uv,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -739,14 +739,8 @@ impl<'a> Slider<'a> {
|
|||
};
|
||||
let v = v + Vec2::splat(visuals.expansion);
|
||||
let rect = Rect::from_center_size(center, 2.0 * v);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.fg_stroke,
|
||||
rect,
|
||||
rounding: visuals.rounding,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
});
|
||||
ui.painter()
|
||||
.rect(rect, visuals.rounding, visuals.bg_fill, visuals.fg_stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use emath::NumExt as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The color and fuzziness of a fuzzy shape.
|
||||
|
|
@ -37,11 +35,10 @@ impl Shadow {
|
|||
color: Color32::TRANSPARENT,
|
||||
};
|
||||
|
||||
pub fn tessellate(&self, rect: Rect, rounding: impl Into<Rounding>) -> Mesh {
|
||||
/// The argument is the rectangle of the shadow caster.
|
||||
pub fn as_shape(&self, rect: Rect, rounding: impl Into<Rounding>) -> RectShape {
|
||||
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling
|
||||
|
||||
use crate::tessellator::*;
|
||||
|
||||
let Self {
|
||||
offset,
|
||||
blur,
|
||||
|
|
@ -50,40 +47,9 @@ impl Shadow {
|
|||
} = *self;
|
||||
|
||||
let rect = rect.translate(offset).expand(spread);
|
||||
let rounding = rounding.into() + Rounding::same(spread.abs());
|
||||
|
||||
// We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering.
|
||||
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
|
||||
// The tessellator can't handle blurring/feathering larger than the smallest side of the rect.
|
||||
// Thats because the tessellator approximate very thin rectangles as line segments,
|
||||
// and these line segments don't have rounded corners.
|
||||
// When the feathering is small (the size of a pixel), this is usually fine,
|
||||
// but here we have a huge feathering to simulate blur,
|
||||
// so we need to avoid this optimization in the tessellator,
|
||||
// which is also why we add this rather big epsilon:
|
||||
let eps = 0.1;
|
||||
let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0);
|
||||
|
||||
// TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator
|
||||
|
||||
let rounding_expansion = spread.abs() + 0.5 * blur;
|
||||
let rounding = rounding.into() + Rounding::same(rounding_expansion);
|
||||
|
||||
let rect = RectShape::filled(rect, rounding, color);
|
||||
let pixels_per_point = 1.0; // doesn't matter here
|
||||
let font_tex_size = [1; 2]; // unused since we are not tessellating text.
|
||||
let mut tessellator = Tessellator::new(
|
||||
pixels_per_point,
|
||||
TessellationOptions {
|
||||
feathering: true,
|
||||
feathering_size_in_pixels: blur * pixels_per_point,
|
||||
..Default::default()
|
||||
},
|
||||
font_tex_size,
|
||||
vec![],
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
tessellator.tessellate_rect(&rect, &mut mesh);
|
||||
mesh
|
||||
RectShape::filled(rect, rounding, color).with_blur_width(blur)
|
||||
}
|
||||
|
||||
/// How much larger than the parent rect are we in each direction?
|
||||
|
|
|
|||
|
|
@ -668,6 +668,14 @@ pub struct RectShape {
|
|||
/// The thickness and color of the outline.
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// If larger than zero, the edges of the rectangle
|
||||
/// (for both fill and stroke) will be blurred.
|
||||
///
|
||||
/// This can be used to produce shadows and glow effects.
|
||||
///
|
||||
/// 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?
|
||||
///
|
||||
/// The texture is multiplied with [`Self::fill`].
|
||||
|
|
@ -695,6 +703,7 @@ impl RectShape {
|
|||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: stroke.into(),
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
|
|
@ -711,6 +720,7 @@ impl RectShape {
|
|||
rounding: rounding.into(),
|
||||
fill: fill_color.into(),
|
||||
stroke: Default::default(),
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
|
|
@ -723,18 +733,32 @@ impl RectShape {
|
|||
rounding: rounding.into(),
|
||||
fill: Default::default(),
|
||||
stroke: stroke.into(),
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// If larger than zero, the edges of the rectangle
|
||||
/// (for both fill and stroke) will be blurred.
|
||||
///
|
||||
/// This can be used to produce shadows and glow effects.
|
||||
///
|
||||
/// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space.
|
||||
#[inline]
|
||||
pub fn with_blur_width(mut self, blur_width: f32) -> Self {
|
||||
self.blur_width = blur_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke width)
|
||||
#[inline]
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
|
||||
Rect::NOTHING
|
||||
} else {
|
||||
self.rect.expand(self.stroke.width / 2.0)
|
||||
self.rect
|
||||
.expand((self.stroke.width + self.blur_width) / 2.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) {
|
|||
rounding: _,
|
||||
fill,
|
||||
stroke,
|
||||
blur_width: _,
|
||||
fill_texture_id: _,
|
||||
uv: _,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1503,9 +1503,10 @@ impl Tessellator {
|
|||
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||
let RectShape {
|
||||
mut rect,
|
||||
rounding,
|
||||
mut rounding,
|
||||
fill,
|
||||
stroke,
|
||||
mut blur_width,
|
||||
fill_texture_id,
|
||||
uv,
|
||||
} = *rect;
|
||||
|
|
@ -1524,6 +1525,29 @@ impl Tessellator {
|
|||
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||
|
||||
let old_feathering = self.feathering;
|
||||
|
||||
if old_feathering < blur_width {
|
||||
// We accomplish the blur by using a larger-than-normal feathering.
|
||||
// Feathering is usually used to make the edges of a shape softer for anti-aliasing.
|
||||
|
||||
// The tessellator can't handle blurring/feathering larger than the smallest side of the rect.
|
||||
// Thats because the tessellator approximate very thin rectangles as line segments,
|
||||
// and these line segments don't have rounded corners.
|
||||
// When the feathering is small (the size of a pixel), this is usually fine,
|
||||
// but here we have a huge feathering to simulate blur,
|
||||
// so we need to avoid this optimization in the tessellator,
|
||||
// which is also why we add this rather big epsilon:
|
||||
let eps = 0.1;
|
||||
blur_width = blur_width
|
||||
.at_most(rect.size().min_elem() - eps)
|
||||
.at_least(0.0);
|
||||
|
||||
rounding += Rounding::same(0.5 * blur_width);
|
||||
|
||||
self.feathering = self.feathering.max(blur_width);
|
||||
}
|
||||
|
||||
if rect.width() < self.feathering {
|
||||
// Very thin - approximate by a vertical line-segment:
|
||||
let line = [rect.center_top(), rect.center_bottom()];
|
||||
|
|
@ -1566,6 +1590,8 @@ impl Tessellator {
|
|||
|
||||
path.stroke_closed(self.feathering, stroke, out);
|
||||
}
|
||||
|
||||
self.feathering = old_feathering; // restore
|
||||
}
|
||||
|
||||
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
||||
|
|
|
|||
Loading…
Reference in New Issue