Skip to content

Commit 5d5e3f9

Browse files
robojebcartZeophlite
authored
Implement Scene for Option<P> where P: Scene (#24536)
This allows easier conditional behavior without having to resort to `Box<dyn Scene>` with an empty `bsn!{}` in the negative case. # Objective While designing custom widgets and extra scene tooling I have found several cases where I would like to optionally include a set of components. Right now (as best I can tell) this requires constructing a `Box<dyn Scene>` which either has the components, or is an empty scene (`bsn!{}`). Every time I try to do this, I always first end up with a type error that the `if` and `else` branches don't have matching types. This is fixable, but prone to mistakes. Allowing `Option<P: Scene>` is much simpler, and doesn't require allocations. ## Solution Allow `Option<P: Scene>` to be a `Scene` which optionally resolves the contained scene. ## Testing I have been using a new-typed version of this implementation (`ConditionalScene<P>(Option<P>)`) in my own personal widget library to get around Orphan rules. I added a simple `#[test]` to the `bevy_scene` test suite. --- ## Showcase I put together an example widget for a "PullQuote" which can optionally use the inherited font from the parent, or use a larger font by default. <details> <summary>Click to view showcase</summary> ```rust use bevy::{ feathers::{ FeathersPlugins, constants::{fonts, size}, dark_theme::create_dark_theme, font_styles::InheritableFont, theme::{ThemeBorderColor, ThemedText, UiTheme}, tokens, }, prelude::*, }; fn main() { App::new() .add_plugins((DefaultPlugins, FeathersPlugins)) .insert_resource(UiTheme(create_dark_theme())) .add_systems(Startup, scene.spawn()) .run(); } fn scene() -> impl SceneList { bsn_list!(Camera2d, ui()) } #[derive(SceneComponent, Default, Clone)] #[scene(PullQuoteProps)] pub struct PullQuote; pub struct PullQuoteProps { /// Set a specific font for the Pull Quote body elements using `ThemedText`, otherwise inherit the font from the outer context pub use_quote_font: bool, } impl Default for PullQuoteProps { fn default() -> Self { Self { use_quote_font: true, } } } impl PullQuote { fn scene(props: PullQuoteProps) -> impl Scene { let quote_font = props.use_quote_font.then(|| { bsn! { InheritableFont { font: fonts::REGULAR, font_size: px(24), weight: FontWeight::NORMAL, } } }); bsn! { Node { border: px(1), padding: UiRect::horizontal(px(4)), } ThemeBorderColor(tokens::SUBPANE_HEADER_BORDER) {quote_font} ThemedText // Propagate text theme even if quote font isn't used } } } fn ui() -> impl Scene { bsn! { Node { flex_direction: FlexDirection::Column, padding: px(8), row_gap: px(2), } InheritableFont { font: fonts::REGULAR, font_size: size::MEDIUM_FONT, weight: FontWeight::NORMAL, } Children [ Text("Inherited Font") ThemedText, @PullQuote Children [ Text("A Big Quote!") ThemedText ], @PullQuote { @use_quote_font: false } Children [ Text("A Quote using the inherited font!") ThemedText ] ] } } ``` </details> --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com> Co-authored-by: Daniel Skates <zeophlite@gmail.com>
1 parent 79d3afe commit 5d5e3f9

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

crates/bevy_scene/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,4 +2202,25 @@ mod tests {
22022202
.unwrap();
22032203
assert_eq!(entity.get::<Foo>().unwrap().value, vec![10usize]);
22042204
}
2205+
2206+
#[test]
2207+
fn scene_with_optional_components() {
2208+
let mut app = test_app();
2209+
let world = app.world_mut();
2210+
2211+
#[derive(Component, Default, Clone)]
2212+
struct Foo;
2213+
2214+
let optional_component = Some(bsn! {
2215+
Foo
2216+
});
2217+
2218+
let entity = world
2219+
.spawn_scene(bsn! {
2220+
#MaybeFoo
2221+
{optional_component}
2222+
})
2223+
.unwrap();
2224+
assert!(entity.get::<Foo>().is_some());
2225+
}
22052226
}

crates/bevy_scene/src/scene.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,21 @@ macro_rules! scene_impl {
211211

212212
all_tuples!(scene_impl, 0, 12, P);
213213

214+
impl<P> Scene for Option<P>
215+
where
216+
P: Scene,
217+
{
218+
#[inline]
219+
fn resolve(
220+
self,
221+
context: &mut ResolveContext,
222+
scene: &mut ResolvedScene,
223+
) -> Result<(), ResolveSceneError> {
224+
self.map(|optional_scene| optional_scene.resolve(context, scene))
225+
.unwrap_or(Ok(()))
226+
}
227+
}
228+
214229
/// A [`Scene`] that patches a [`Template`] of type `T` with a given function `F`.
215230
///
216231
/// Functionally, a [`TemplatePatch`] scene will initialize a [`Default`] value of the patched

0 commit comments

Comments
 (0)