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

Multiple box shadow support #16502

Merged
merged 10 commits into from
Dec 1, 2024
105 changes: 56 additions & 49 deletions crates/bevy_ui/src/render/box_shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ pub fn extract_shadows(
};

// Skip invisible images
if !view_visibility.get() || box_shadow.color.is_fully_transparent() || uinode.is_empty() {
if !view_visibility.get() || box_shadow.is_empty() || uinode.is_empty() {
continue;
}

Expand All @@ -278,57 +278,64 @@ pub fn extract_shadows(

let scale_factor = uinode.inverse_scale_factor.recip();

let resolve_val = |val, base, scale_factor| match val {
Val::Auto => 0.,
Val::Px(px) => px * scale_factor,
Val::Percent(percent) => percent / 100. * base,
Val::Vw(percent) => percent / 100. * ui_physical_viewport_size.x,
Val::Vh(percent) => percent / 100. * ui_physical_viewport_size.y,
Val::VMin(percent) => percent / 100. * ui_physical_viewport_size.min_element(),
Val::VMax(percent) => percent / 100. * ui_physical_viewport_size.max_element(),
};

let spread_x = resolve_val(box_shadow.spread_radius, uinode.size().x, scale_factor);
let spread_ratio = (spread_x + uinode.size().x) / uinode.size().x;

let spread = vec2(spread_x, uinode.size().y * spread_ratio - uinode.size().y);

let blur_radius = resolve_val(box_shadow.blur_radius, uinode.size().x, scale_factor);
let offset = vec2(
resolve_val(box_shadow.x_offset, uinode.size().x, scale_factor),
resolve_val(box_shadow.y_offset, uinode.size().y, scale_factor),
);

let shadow_size = uinode.size() + spread;
if shadow_size.cmple(Vec2::ZERO).any() {
continue;
}
for drop_shadow in box_shadow.iter() {
if drop_shadow.color.is_fully_transparent() {
continue;
}

let radius = ResolvedBorderRadius {
top_left: uinode.border_radius.top_left * spread_ratio,
top_right: uinode.border_radius.top_right * spread_ratio,
bottom_left: uinode.border_radius.bottom_left * spread_ratio,
bottom_right: uinode.border_radius.bottom_right * spread_ratio,
};
let resolve_val = |val, base, scale_factor| match val {
Val::Auto => 0.,
Val::Px(px) => px * scale_factor,
Val::Percent(percent) => percent / 100. * base,
Val::Vw(percent) => percent / 100. * ui_physical_viewport_size.x,
Val::Vh(percent) => percent / 100. * ui_physical_viewport_size.y,
Val::VMin(percent) => percent / 100. * ui_physical_viewport_size.min_element(),
Val::VMax(percent) => percent / 100. * ui_physical_viewport_size.max_element(),
};

let spread_x = resolve_val(drop_shadow.spread_radius, uinode.size().x, scale_factor);
let spread_ratio = (spread_x + uinode.size().x) / uinode.size().x;

let spread = vec2(spread_x, uinode.size().y * spread_ratio - uinode.size().y);

let blur_radius = resolve_val(drop_shadow.blur_radius, uinode.size().x, scale_factor);
let offset = vec2(
resolve_val(drop_shadow.x_offset, uinode.size().x, scale_factor),
resolve_val(drop_shadow.y_offset, uinode.size().y, scale_factor),
);

let shadow_size = uinode.size() + spread;
if shadow_size.cmple(Vec2::ZERO).any() {
continue;
}

extracted_box_shadows.box_shadows.insert(
commands.spawn(TemporaryRenderEntity).id(),
ExtractedBoxShadow {
stack_index: uinode.stack_index,
transform: transform.compute_matrix() * Mat4::from_translation(offset.extend(0.)),
color: box_shadow.color.into(),
rect: Rect {
min: Vec2::ZERO,
max: shadow_size + 6. * blur_radius,
let radius = ResolvedBorderRadius {
top_left: uinode.border_radius.top_left * spread_ratio,
top_right: uinode.border_radius.top_right * spread_ratio,
bottom_left: uinode.border_radius.bottom_left * spread_ratio,
bottom_right: uinode.border_radius.bottom_right * spread_ratio,
};

extracted_box_shadows.box_shadows.insert(
commands.spawn(TemporaryRenderEntity).id(),
ExtractedBoxShadow {
stack_index: uinode.stack_index,
transform: transform.compute_matrix()
* Mat4::from_translation(offset.extend(0.)),
color: drop_shadow.color.into(),
rect: Rect {
min: Vec2::ZERO,
max: shadow_size + 6. * blur_radius,
},
clip: clip.map(|clip| clip.clip),
camera_entity,
radius,
blur_radius,
size: shadow_size,
main_entity: entity.into(),
},
clip: clip.map(|clip| clip.clip),
camera_entity,
radius,
blur_radius,
size: shadow_size,
main_entity: entity.into(),
},
);
);
}
}
}

Expand Down
44 changes: 41 additions & 3 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{FocusPolicy, UiRect, Val};
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles};
use bevy_reflect::prelude::*;
Expand Down Expand Up @@ -2416,14 +2417,51 @@ impl ResolvedBorderRadius {
};
}

#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
#[derive(Component, Clone, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]
#[reflect(Component, PartialEq, Default)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BoxShadow {
/// List of shadows to draw for a [`Node`].
///
/// Draw order is determined implicitly from the vector of [`ShadowStyle`]s, back-to-front.
pub struct BoxShadow(pub Vec<ShadowStyle>);

impl BoxShadow {
/// A single drop shadow
pub fn new(
color: Color,
x_offset: Val,
y_offset: Val,
spread_radius: Val,
blur_radius: Val,
) -> Self {
Self(vec![ShadowStyle {
color,
x_offset,
y_offset,
spread_radius,
blur_radius,
}])
}
}

impl From<ShadowStyle> for BoxShadow {
fn from(value: ShadowStyle) -> Self {
Self(vec![value])
}
}

#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct ShadowStyle {
/// The shadow's color
pub color: Color,
/// Horizontal offset
Expand All @@ -2439,7 +2477,7 @@ pub struct BoxShadow {
pub blur_radius: Val,
}

impl Default for BoxShadow {
impl Default for ShadowStyle {
fn default() -> Self {
Self {
color: Color::BLACK,
Expand Down
16 changes: 8 additions & 8 deletions examples/testbed/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
});

let shadow = BoxShadow {
let shadow_style = ShadowStyle {
color: Color::BLACK.with_alpha(0.5),
blur_radius: Val::Px(2.),
x_offset: Val::Px(10.),
Expand Down Expand Up @@ -218,7 +218,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
BackgroundColor(Color::srgb(1.0, 0.0, 0.)),
shadow,
BoxShadow::from(shadow_style),
))
.with_children(|parent| {
parent.spawn((
Expand All @@ -232,7 +232,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
BackgroundColor(Color::srgb(1.0, 0.3, 0.3)),
shadow,
BoxShadow::from(shadow_style),
));
parent.spawn((
Node {
Expand All @@ -244,7 +244,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
BackgroundColor(Color::srgb(1.0, 0.5, 0.5)),
shadow,
BoxShadow::from(shadow_style),
));
parent.spawn((
Node {
Expand All @@ -256,7 +256,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
BackgroundColor(Color::srgb(0.0, 0.7, 0.7)),
shadow,
BoxShadow::from(shadow_style),
));
// alpha test
parent.spawn((
Expand All @@ -269,10 +269,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
BackgroundColor(Color::srgba(1.0, 0.9, 0.9, 0.4)),
BoxShadow {
BoxShadow::from(ShadowStyle {
color: Color::BLACK.with_alpha(0.3),
..shadow
},
..shadow_style
}),
));
});
});
Expand Down
61 changes: 54 additions & 7 deletions examples/ui/box_shadow.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
//! This example shows how to create a node with a shadow

use argh::FromArgs;
use bevy::color::palettes::css::BLUE;
use bevy::color::palettes::css::DEEP_SKY_BLUE;
use bevy::color::palettes::css::GREEN;
use bevy::color::palettes::css::LIGHT_SKY_BLUE;
use bevy::color::palettes::css::RED;
use bevy::color::palettes::css::YELLOW;
use bevy::prelude::*;
use bevy::winit::WinitSettings;

Expand Down Expand Up @@ -189,6 +193,49 @@ fn setup(mut commands: Commands) {
border_radius,
));
}

// Demonstrate multiple shadows on one node
commands.spawn((
Node {
width: Val::Px(40.),
height: Val::Px(40.),
border: UiRect::all(Val::Px(4.)),
..default()
},
BorderColor(LIGHT_SKY_BLUE.into()),
BorderRadius::all(Val::Px(20.)),
BackgroundColor(DEEP_SKY_BLUE.into()),
BoxShadow(vec![
ShadowStyle {
color: RED.with_alpha(0.7).into(),
x_offset: Val::Px(-20.),
y_offset: Val::Px(-5.),
spread_radius: Val::Percent(10.),
blur_radius: Val::Px(3.),
},
ShadowStyle {
color: BLUE.with_alpha(0.7).into(),
x_offset: Val::Px(-5.),
y_offset: Val::Px(-20.),
spread_radius: Val::Percent(10.),
blur_radius: Val::Px(3.),
},
ShadowStyle {
color: YELLOW.with_alpha(0.7).into(),
x_offset: Val::Px(20.),
y_offset: Val::Px(5.),
spread_radius: Val::Percent(10.),
blur_radius: Val::Px(3.),
},
ShadowStyle {
color: GREEN.with_alpha(0.7).into(),
x_offset: Val::Px(5.),
y_offset: Val::Px(20.),
spread_radius: Val::Percent(10.),
blur_radius: Val::Px(3.),
},
]),
));
});
}

Expand All @@ -209,12 +256,12 @@ fn box_shadow_node_bundle(
BorderColor(LIGHT_SKY_BLUE.into()),
border_radius,
BackgroundColor(DEEP_SKY_BLUE.into()),
BoxShadow {
color: Color::BLACK.with_alpha(0.8),
x_offset: Val::Percent(offset.x),
y_offset: Val::Percent(offset.y),
spread_radius: Val::Percent(spread),
blur_radius: Val::Px(blur),
},
BoxShadow::new(
Color::BLACK.with_alpha(0.8),
Val::Percent(offset.x),
Val::Percent(offset.y),
Val::Percent(spread),
Val::Px(blur),
),
)
}