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

CSS-like shadows with offset, spread, and blur #4232

Merged
merged 4 commits into from
Mar 26, 2024
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
2 changes: 1 addition & 1 deletion crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ impl Prepared {
.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 = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max)
Expand Down
33 changes: 29 additions & 4 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,12 @@ impl Visuals {
error_fg_color: Color32::from_rgb(255, 0, 0), // red

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_stroke: Stroke::new(1.0, Color32::from_gray(60)),
window_highlight_topmost: true,
Expand All @@ -1080,10 +1085,18 @@ impl Visuals {

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,

text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)),
text_cursor_preview: false,

clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
button_frame: true,
collapsing_header_frame: false,
Expand Down Expand Up @@ -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.
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_stroke: Stroke::new(1.0, Color32::from_gray(190)),

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)),

..Self::dark()
}
}
Expand Down
47 changes: 38 additions & 9 deletions crates/egui/src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
let epaint::Shadow { extrusion, color } = shadow;
ui.horizontal(|ui| {
ui.label(text);
ui.add(
DragValue::new(extrusion)
.speed(1.0)
.clamp_range(0.0..=100.0),
)
.on_hover_text("Extrusion");
let epaint::Shadow {
offset,
blur,
spread,
color,
} = shadow;

ui.label(text);
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);
});
}
Expand Down
103 changes: 50 additions & 53 deletions crates/epaint/src/shadow.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,65 @@
use super::*;

/// The color and fuzziness of a fuzzy shape.
///
/// 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)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Shadow {
/// The shadow extends this much outside the rect.
/// The size of the fuzzy penumbra.
pub extrusion: f32,
/// Move the shadow by this much.
///
/// 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.
pub color: Color32,
}

impl Shadow {
/// No shadow at all.
pub const NONE: Self = Self {
extrusion: 0.0,
offset: Vec2::ZERO,
blur: 0.0,
spread: 0.0,
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 {
// tessellator.clip_rect = clip_rect; // TODO(emilk): culling

let Self { extrusion, color } = *self;
use crate::tessellator::*;

let rounding: Rounding = rounding.into();
let half_ext = 0.5 * extrusion;
let Self {
offset,
blur,
spread,
color,
} = *self;

let ext_rounding = Rounding {
nw: rounding.nw + half_ext,
ne: rounding.ne + half_ext,
sw: rounding.sw + half_ext,
se: rounding.se + half_ext,
};
let rect = rect.translate(offset);

use crate::tessellator::*;
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
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 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(
pixels_per_point,
TessellationOptions {
feathering: true,
feathering_size_in_pixels: extrusion * pixels_per_point,
feathering_size_in_pixels: blur * pixels_per_point,
..Default::default()
},
font_tex_size,
Expand All @@ -88,4 +69,20 @@ impl Shadow {
tessellator.tessellate_rect(&rect, &mut 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