CSS-like shadows with offset, spread, and blur (#4232)
This makes `epaint::Shadow` more like CSS's box-shadow, adding `offset` and replacing `extrusion` with `blur` and `spread`. * Closes https://github.com/emilk/egui/pull/3047 The offsets make for nice drop-shadow effects. Old shadows: <img width="1447" alt="old-shadows" src="https://github.com/emilk/egui/assets/1148717/8a30f7b9-fb9d-49ea-9a2f-9367a60c448a"> New shadows: <img width="1447" alt="new-shadows-full" src="https://github.com/emilk/egui/assets/1148717/28cc9c1e-b0de-4c5b-a705-22e52c556584">
This commit is contained in:
parent
1634554032
commit
c530504a04
|
|
@ -423,7 +423,7 @@ impl Prepared {
|
||||||
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
|
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
|
let shadow_radius = ctx.style().visuals.window_shadow.margin().sum().max_elem(); // hacky
|
||||||
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
||||||
|
|
||||||
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max)
|
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max)
|
||||||
|
|
|
||||||
|
|
@ -1071,7 +1071,12 @@ impl Visuals {
|
||||||
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
||||||
|
|
||||||
window_rounding: Rounding::same(6.0),
|
window_rounding: Rounding::same(6.0),
|
||||||
window_shadow: Shadow::big_dark(),
|
window_shadow: Shadow {
|
||||||
|
offset: vec2(10.0, 20.0),
|
||||||
|
blur: 15.0,
|
||||||
|
spread: 0.0,
|
||||||
|
color: Color32::from_black_alpha(96),
|
||||||
|
},
|
||||||
window_fill: Color32::from_gray(27),
|
window_fill: Color32::from_gray(27),
|
||||||
window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
||||||
window_highlight_topmost: true,
|
window_highlight_topmost: true,
|
||||||
|
|
@ -1080,10 +1085,18 @@ impl Visuals {
|
||||||
|
|
||||||
panel_fill: Color32::from_gray(27),
|
panel_fill: Color32::from_gray(27),
|
||||||
|
|
||||||
popup_shadow: Shadow::small_dark(),
|
popup_shadow: Shadow {
|
||||||
|
offset: vec2(6.0, 10.0),
|
||||||
|
blur: 8.0,
|
||||||
|
spread: 0.0,
|
||||||
|
color: Color32::from_black_alpha(96),
|
||||||
|
},
|
||||||
|
|
||||||
resize_corner_size: 12.0,
|
resize_corner_size: 12.0,
|
||||||
|
|
||||||
text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)),
|
text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)),
|
||||||
text_cursor_preview: false,
|
text_cursor_preview: false,
|
||||||
|
|
||||||
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
|
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
|
||||||
button_frame: true,
|
button_frame: true,
|
||||||
collapsing_header_frame: false,
|
collapsing_header_frame: false,
|
||||||
|
|
@ -1115,14 +1128,26 @@ impl Visuals {
|
||||||
warn_fg_color: Color32::from_rgb(255, 100, 0), // slightly orange red. it's difficult to find a warning color that pops on bright background.
|
warn_fg_color: Color32::from_rgb(255, 100, 0), // slightly orange red. it's difficult to find a warning color that pops on bright background.
|
||||||
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
||||||
|
|
||||||
window_shadow: Shadow::big_light(),
|
window_shadow: Shadow {
|
||||||
|
offset: vec2(10.0, 20.0),
|
||||||
|
blur: 15.0,
|
||||||
|
spread: 0.0,
|
||||||
|
color: Color32::from_black_alpha(25),
|
||||||
|
},
|
||||||
window_fill: Color32::from_gray(248),
|
window_fill: Color32::from_gray(248),
|
||||||
window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
|
window_stroke: Stroke::new(1.0, Color32::from_gray(190)),
|
||||||
|
|
||||||
panel_fill: Color32::from_gray(248),
|
panel_fill: Color32::from_gray(248),
|
||||||
|
|
||||||
popup_shadow: Shadow::small_light(),
|
popup_shadow: Shadow {
|
||||||
|
offset: vec2(6.0, 10.0),
|
||||||
|
blur: 8.0,
|
||||||
|
spread: 0.0,
|
||||||
|
color: Color32::from_black_alpha(25),
|
||||||
|
},
|
||||||
|
|
||||||
text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
|
text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)),
|
||||||
|
|
||||||
..Self::dark()
|
..Self::dark()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,15 +126,44 @@ pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &str) {
|
pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &str) {
|
||||||
let epaint::Shadow { extrusion, color } = shadow;
|
let epaint::Shadow {
|
||||||
ui.horizontal(|ui| {
|
offset,
|
||||||
ui.label(text);
|
blur,
|
||||||
ui.add(
|
spread,
|
||||||
DragValue::new(extrusion)
|
color,
|
||||||
.speed(1.0)
|
} = shadow;
|
||||||
.clamp_range(0.0..=100.0),
|
|
||||||
)
|
ui.label(text);
|
||||||
.on_hover_text("Extrusion");
|
ui.indent(text, |ui| {
|
||||||
|
crate::Grid::new("shadow_ui").show(ui, |ui| {
|
||||||
|
ui.add(
|
||||||
|
DragValue::new(&mut offset.x)
|
||||||
|
.speed(1.0)
|
||||||
|
.clamp_range(-100.0..=100.0)
|
||||||
|
.prefix("x: "),
|
||||||
|
);
|
||||||
|
ui.add(
|
||||||
|
DragValue::new(&mut offset.y)
|
||||||
|
.speed(1.0)
|
||||||
|
.clamp_range(-100.0..=100.0)
|
||||||
|
.prefix("y: "),
|
||||||
|
);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.add(
|
||||||
|
DragValue::new(blur)
|
||||||
|
.speed(1.0)
|
||||||
|
.clamp_range(0.0..=100.0)
|
||||||
|
.prefix("Blur:"),
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add(
|
||||||
|
DragValue::new(spread)
|
||||||
|
.speed(1.0)
|
||||||
|
.clamp_range(0.0..=100.0)
|
||||||
|
.prefix("Spread:"),
|
||||||
|
);
|
||||||
|
});
|
||||||
ui.color_edit_button_srgba(color);
|
ui.color_edit_button_srgba(color);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,65 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// The color and fuzziness of a fuzzy shape.
|
/// The color and fuzziness of a fuzzy shape.
|
||||||
|
///
|
||||||
/// Can be used for a rectangular shadow with a soft penumbra.
|
/// Can be used for a rectangular shadow with a soft penumbra.
|
||||||
|
///
|
||||||
|
/// Very similar to a box-shadow in CSS.
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Shadow {
|
pub struct Shadow {
|
||||||
/// The shadow extends this much outside the rect.
|
/// Move the shadow by this much.
|
||||||
/// The size of the fuzzy penumbra.
|
///
|
||||||
pub extrusion: f32,
|
/// For instance, a value of `[1.0, 2.0]` will move the shadow 1 point to the right and 2 points down,
|
||||||
|
/// causing a drop-shadow effet.
|
||||||
|
pub offset: Vec2,
|
||||||
|
|
||||||
|
/// The width of the blur, i.e. the width of the fuzzy penumbra.
|
||||||
|
///
|
||||||
|
/// A value of 0.0 means no blur.
|
||||||
|
pub blur: f32,
|
||||||
|
|
||||||
|
/// Expand the shadow in all directions by this much.
|
||||||
|
pub spread: f32,
|
||||||
|
|
||||||
/// Color of the opaque center of the shadow.
|
/// Color of the opaque center of the shadow.
|
||||||
pub color: Color32,
|
pub color: Color32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shadow {
|
impl Shadow {
|
||||||
|
/// No shadow at all.
|
||||||
pub const NONE: Self = Self {
|
pub const NONE: Self = Self {
|
||||||
extrusion: 0.0,
|
offset: Vec2::ZERO,
|
||||||
|
blur: 0.0,
|
||||||
|
spread: 0.0,
|
||||||
color: Color32::TRANSPARENT,
|
color: Color32::TRANSPARENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const fn new(extrusion: f32, color: Color32) -> Self {
|
|
||||||
Self { extrusion, color }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tooltips, menus, …, for dark mode.
|
|
||||||
pub const fn small_dark() -> Self {
|
|
||||||
Self {
|
|
||||||
extrusion: 16.0,
|
|
||||||
color: Color32::from_black_alpha(96),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tooltips, menus, …, for light mode.
|
|
||||||
pub const fn small_light() -> Self {
|
|
||||||
Self {
|
|
||||||
extrusion: 16.0,
|
|
||||||
color: Color32::from_black_alpha(20),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for egui windows in dark mode.
|
|
||||||
pub const fn big_dark() -> Self {
|
|
||||||
Self {
|
|
||||||
extrusion: 32.0,
|
|
||||||
color: Color32::from_black_alpha(96),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for egui windows in light mode.
|
|
||||||
pub const fn big_light() -> Self {
|
|
||||||
Self {
|
|
||||||
extrusion: 32.0,
|
|
||||||
color: Color32::from_black_alpha(16),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tessellate(&self, rect: Rect, rounding: impl Into<Rounding>) -> Mesh {
|
pub fn tessellate(&self, rect: Rect, rounding: impl Into<Rounding>) -> Mesh {
|
||||||
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling
|
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling
|
||||||
|
|
||||||
let Self { extrusion, color } = *self;
|
|
||||||
|
|
||||||
let rounding: Rounding = rounding.into();
|
|
||||||
let half_ext = 0.5 * extrusion;
|
|
||||||
|
|
||||||
let ext_rounding = Rounding {
|
|
||||||
nw: rounding.nw + half_ext,
|
|
||||||
ne: rounding.ne + half_ext,
|
|
||||||
sw: rounding.sw + half_ext,
|
|
||||||
se: rounding.se + half_ext,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::tessellator::*;
|
use crate::tessellator::*;
|
||||||
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
|
|
||||||
|
let Self {
|
||||||
|
offset,
|
||||||
|
blur,
|
||||||
|
spread,
|
||||||
|
color,
|
||||||
|
} = *self;
|
||||||
|
|
||||||
|
let rect = rect.translate(offset);
|
||||||
|
|
||||||
|
let rounding_expansion = spread.abs() + 0.5 * blur;
|
||||||
|
let rounding = rounding.into() + Rounding::from(rounding_expansion);
|
||||||
|
|
||||||
|
let rect = RectShape::filled(rect.expand(spread), rounding, color);
|
||||||
let pixels_per_point = 1.0; // doesn't matter here
|
let pixels_per_point = 1.0; // doesn't matter here
|
||||||
let font_tex_size = [1; 2]; // unused size we are not tessellating text.
|
let font_tex_size = [1; 2]; // unused since we are not tessellating text.
|
||||||
let mut tessellator = Tessellator::new(
|
let mut tessellator = Tessellator::new(
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
TessellationOptions {
|
TessellationOptions {
|
||||||
feathering: true,
|
feathering: true,
|
||||||
feathering_size_in_pixels: extrusion * pixels_per_point,
|
feathering_size_in_pixels: blur * pixels_per_point,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
font_tex_size,
|
font_tex_size,
|
||||||
|
|
@ -88,4 +69,20 @@ impl Shadow {
|
||||||
tessellator.tessellate_rect(&rect, &mut mesh);
|
tessellator.tessellate_rect(&rect, &mut mesh);
|
||||||
mesh
|
mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How much larger than the parent rect are we in each direction?
|
||||||
|
pub fn margin(&self) -> Margin {
|
||||||
|
let Self {
|
||||||
|
offset,
|
||||||
|
blur,
|
||||||
|
spread,
|
||||||
|
color: _,
|
||||||
|
} = *self;
|
||||||
|
Margin {
|
||||||
|
left: spread + 0.5 * blur - offset.x,
|
||||||
|
right: spread + 0.5 * blur + offset.x,
|
||||||
|
top: spread + 0.5 * blur - offset.y,
|
||||||
|
bottom: spread + 0.5 * blur + offset.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue