Skip to content

Commit e371407

Browse files
Port Properties Pane to BSN and Feathers (#235)
* Port Properties Pane to BSN and Feathers * Adjust editor core prelude
1 parent ea5cb9a commit e371407

File tree

4 files changed

+165
-114
lines changed

4 files changed

+165
-114
lines changed

bevy_editor_panes/bevy_properties_pane/src/lib.rs

Lines changed: 110 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,86 @@
22
//!
33
//! Data can be viewed and modified in real-time, with changes being reflected in the application.
44
5-
use bevy::{color::palettes::tailwind, prelude::*, reflect::*};
6-
use bevy_editor_core::selection::SelectedEntity;
7-
use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, template};
8-
use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure};
5+
use bevy::{
6+
feathers::theme::ThemedText,
7+
prelude::*,
8+
reflect::*,
9+
scene2::{CommandsSpawnScene, Scene, SceneList, bsn},
10+
};
11+
use bevy_editor_core::prelude::*;
12+
use bevy_pane_layout::prelude::*;
913

1014
/// Plugin for the editor properties pane.
1115
pub struct PropertiesPanePlugin;
1216

1317
impl Plugin for PropertiesPanePlugin {
1418
fn build(&self, app: &mut App) {
15-
app.register_pane("Properties", setup_pane)
16-
.add_systems(PostUpdate, update_properties_pane);
19+
app.register_pane("Properties", setup_pane).add_systems(
20+
Update,
21+
update_properties_pane.run_if(
22+
resource_changed::<SelectedEntity>
23+
.or(any_match_filter::<Added<PropertiesPaneBody>>),
24+
),
25+
);
1726
}
1827
}
1928

2029
/// Root UI node of the properties pane.
21-
#[derive(Component)]
22-
struct PropertiesPaneRoot;
30+
#[derive(Component, Default, Clone)]
31+
struct PropertiesPaneBody;
2332

2433
fn setup_pane(pane: In<PaneStructure>, mut commands: Commands) {
25-
commands.entity(pane.content).insert((
26-
PropertiesPaneRoot,
27-
Node {
28-
flex_direction: FlexDirection::Column,
29-
flex_grow: 1.0,
30-
column_gap: Val::Px(4.0),
31-
padding: UiRect::all(Val::Px(8.0)),
32-
..Default::default()
33-
},
34-
BackgroundColor(tailwind::NEUTRAL_600.into()),
35-
));
34+
// Remove the existing structure
35+
commands.entity(pane.area).despawn();
36+
37+
commands
38+
.spawn_scene(bsn! {
39+
:editor_pane [
40+
:editor_pane_header [
41+
(Text("Properties") ThemedText),
42+
],
43+
:editor_pane_body
44+
PropertiesPaneBody
45+
]
46+
})
47+
.insert(Node::default())
48+
.insert(ChildOf(pane.root));
3649
}
3750

3851
fn update_properties_pane(
39-
panes: Query<Entity, With<PropertiesPaneRoot>>,
52+
pane_bodies: Query<Entity, With<PropertiesPaneBody>>,
4053
selected_entity: Res<SelectedEntity>,
4154
world: &World,
4255
mut commands: Commands,
4356
) {
44-
for pane in &panes {
57+
for pane_body in &pane_bodies {
58+
commands.entity(pane_body).despawn_children();
4559
commands
46-
.entity(pane)
47-
.build_children(properties_pane(&selected_entity, world));
60+
.spawn_scene(properties_pane(&selected_entity, world))
61+
.insert(Node::default())
62+
.insert(ChildOf(pane_body));
4863
}
4964
}
5065

51-
fn properties_pane(selected_entity: &SelectedEntity, world: &World) -> Template {
66+
fn properties_pane(selected_entity: &SelectedEntity, world: &World) -> impl Scene {
5267
match selected_entity.0 {
53-
Some(selected_entity) => component_list(selected_entity, world),
54-
None => template! {
55-
Node {
56-
flex_direction: FlexDirection::Column,
57-
..Default::default()
58-
} => [
59-
(
60-
Text("Select an entity to inspect".into()),
61-
TextFont::from_font_size(14.0),
62-
);
63-
];
64-
65-
},
68+
Some(selected_entity) => bsn! {Node { flex_direction: FlexDirection::Column } [
69+
{component_list(selected_entity, world)}
70+
]}
71+
.boxed_scene(),
72+
None => bsn! {
73+
(Text("Select an entity to inspect") ThemedText)
74+
}
75+
.boxed_scene(),
6676
}
6777
}
6878

69-
fn component_list(entity: Entity, world: &World) -> Template {
79+
fn component_list(entity: Entity, world: &World) -> impl SceneList {
7080
let type_registry = world.resource::<AppTypeRegistry>().read();
7181
world
7282
.inspect_entity(entity)
7383
.unwrap()
74-
.flat_map(|component_info| {
84+
.map(|component_info| {
7585
let type_info = component_info
7686
.type_id()
7787
.and_then(|type_id| type_registry.get_type_info(type_id));
@@ -88,131 +98,120 @@ fn component_list(entity: Entity, world: &World) -> Template {
8898
reflect_component.reflect(entity_ref.unwrap())
8999
});
90100

91-
template! {
101+
bsn! {
92102
Node {
93103
flex_direction: FlexDirection::Column,
94104
margin: UiRect::all(Val::Px(4.0)),
95-
96-
..Default::default()
97-
} => [
98-
// Collapsible header for the component
105+
} [
99106
Node {
100107
flex_direction: FlexDirection::Row,
101108
align_items: AlignItems::Center,
102-
..Default::default()
103-
} => [
104-
(
105-
Text(format!("⯆ {name}")),
106-
TextFont::from_font_size(14.0),
107-
TextColor(Color::WHITE),
108-
);
109-
];
109+
} [
110+
TextFont::from_font_size(14.0)
111+
Text({format!("{name}")})
112+
TextColor(Color::WHITE)
113+
],
110114
// Component fields
111-
@{ match reflect {
112-
Some(reflect) => component(type_info, reflect),
113-
None => template! {
115+
({ match reflect {
116+
Some(reflect) => component(type_info, reflect).boxed_scene(),
117+
None => bsn! {
114118
Node {
115119
flex_direction: FlexDirection::Row,
116-
..Default::default()
117-
} => [
118-
(
119-
Text("<unavailable>".into()),
120-
TextFont::from_font_size(10.0),
121-
TextColor(Color::srgb(1.0, 0.0, 0.0)),
122-
);
123-
];
124-
},
125-
} };
126-
];
120+
} [
121+
Text("<unavailable>")
122+
TextFont::from_font_size(10.0)
123+
TextColor(Color::srgb(1.0, 0.0, 0.0))
124+
]
125+
}.boxed_scene(),
126+
}})
127+
]
127128
}
128129
})
129-
.collect()
130+
.collect::<Vec<_>>()
130131
}
131132

132-
fn component(type_info: Option<&TypeInfo>, reflect: &dyn Reflect) -> Template {
133+
fn component(type_info: Option<&TypeInfo>, reflect: &dyn Reflect) -> impl Scene {
133134
match type_info {
134-
Some(TypeInfo::Struct(struct_info)) => reflected_struct(struct_info, reflect),
135-
Some(TypeInfo::TupleStruct(tuple_struct_info)) => reflected_tuple_struct(tuple_struct_info),
136-
Some(TypeInfo::Enum(enum_info)) => reflected_enum(enum_info),
137-
_ => template! {},
135+
Some(TypeInfo::Struct(info)) => reflected_struct(info, reflect).boxed_scene(),
136+
Some(TypeInfo::TupleStruct(info)) => reflected_tuple_struct(info).boxed_scene(),
137+
Some(TypeInfo::Enum(info)) => reflected_enum(info).boxed_scene(),
138+
_ => bsn! {}.boxed_scene(),
138139
}
139140
}
140-
fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect) -> Template {
141+
fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect) -> impl Scene {
141142
let fields = struct_info
142143
.iter()
143144
.enumerate()
144-
.flat_map(|(i, field)| {
145-
let value = reflect
145+
.map(|(i, field)| {
146+
let valuee = reflect
146147
.reflect_ref()
147148
.as_struct()
148149
.map(|s| s.field_at(i))
149150
.map(|v| format!("{v:?}"))
150151
.unwrap_or("<unavailable>".to_string());
151152

152-
template! {
153+
let field_name = field.name();
154+
bsn! {
153155
Node {
154156
flex_direction: FlexDirection::Row,
155157
margin: UiRect::vertical(Val::Px(2.0)),
156-
..Default::default()
157-
} => [
158+
} [
158159
(
159-
Text(field.name().to_string()),
160-
TextFont::from_font_size(12.0),
161-
TextColor(Color::srgb(0.8, 0.8, 0.8)),
162-
);
160+
Text(field_name)
161+
TextFont::from_font_size(12.0)
162+
TextColor(Color::srgb(0.8, 0.8, 0.8))
163+
),
163164
(
164165
// Value (use reflection to get value as string)
165-
Text(value),
166-
TextFont::from_font_size(10.0),
167-
TextColor(Color::WHITE),
168-
);
169-
];
166+
Text({valuee.clone()})
167+
TextFont::from_font_size(10.0)
168+
TextColor(Color::WHITE)
169+
),
170+
]
170171
}
171172
})
172-
.collect::<Template>();
173+
.collect::<Vec<_>>();
173174

174-
template! {
175+
bsn! {
175176
Node {
176177
flex_direction: FlexDirection::Column,
177-
..Default::default()
178-
} => [ @{ fields }; ];
178+
} [ {fields} ]
179179
}
180180
}
181181

182-
fn reflected_tuple_struct(tuple_struct_info: &TupleStructInfo) -> Template {
182+
fn reflected_tuple_struct(tuple_struct_info: &TupleStructInfo) -> impl Scene {
183183
let fields = tuple_struct_info
184184
.iter()
185-
.flat_map(|_field| {
186-
template! {(
187-
Text("TODO".into()),
188-
TextFont::from_font_size(10.0),
189-
);}
185+
.map(|_field| {
186+
bsn! {
187+
Text("TODO")
188+
TextFont::from_font_size(10.0)
189+
}
190190
})
191-
.collect::<Template>();
191+
.collect::<Vec<_>>();
192192

193-
template! {
193+
bsn! {
194194
Node {
195195
flex_direction: FlexDirection::Column,
196-
..Default::default()
197-
} => [ @{ fields }; ];
196+
} [ {fields} ]
198197
}
199198
}
200199

201-
fn reflected_enum(enum_info: &EnumInfo) -> Template {
200+
fn reflected_enum(enum_info: &EnumInfo) -> impl Scene {
202201
let variants = enum_info
203202
.iter()
204-
.flat_map(|variant| {
205-
template! {(
206-
Text(variant.name().into()),
207-
TextFont::from_font_size(10.0),
208-
);}
203+
.map(|variant| {
204+
let name = variant.name();
205+
bsn! {
206+
Text(name)
207+
TextFont::from_font_size(10.0)
208+
}
209209
})
210-
.collect::<Template>();
210+
.collect::<Vec<_>>();
211211

212-
template! {
212+
bsn! {
213213
Node {
214214
flex_direction: FlexDirection::Column,
215-
..Default::default()
216-
} => [ @{ variants }; ];
215+
} [ {variants} ]
217216
}
218217
}

crates/bevy_editor_core/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ use crate::{actions::ActionsPlugin, selection::SelectionPlugin, utils::CoreUtils
1010

1111
/// Crate prelude.
1212
pub mod prelude {
13-
pub use crate::actions::{ActionAppExt, ActionWorldExt};
13+
pub use crate::{
14+
actions::{ActionAppExt, ActionWorldExt},
15+
selection::SelectedEntity,
16+
utils::IntoBoxedScene,
17+
};
1418
}
1519

1620
/// Core plugin for the editor.

crates/bevy_editor_core/src/utils.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
use std::time::Duration;
44

55
use bevy::{
6-
picking::backend::HitData, platform::collections::HashMap, platform::time::Instant, prelude::*,
6+
picking::backend::HitData,
7+
platform::{collections::HashMap, time::Instant},
8+
prelude::*,
9+
scene2::Scene,
710
};
811

912
/// Editor core utils plugin.
@@ -65,3 +68,39 @@ pub struct DragCancelClick {
6568

6669
#[derive(Resource, Deref, DerefMut, Default)]
6770
struct DragCancelClickState(HashMap<Entity, Instant>);
71+
72+
/// A boxed [`Scene`]. Useful when you might need to pass or store scenes of different types.
73+
pub struct BoxedScene(pub Box<dyn Scene>);
74+
75+
impl BoxedScene {
76+
/// Create a new boxed scene.
77+
pub fn new(scene: impl Scene) -> Self {
78+
Self(Box::new(scene))
79+
}
80+
}
81+
82+
impl Scene for BoxedScene {
83+
fn patch(
84+
&self,
85+
assets: &AssetServer,
86+
patches: &Assets<bevy::scene2::ScenePatch>,
87+
scene: &mut bevy::scene2::ResolvedScene,
88+
) {
89+
self.0.patch(assets, patches, scene);
90+
}
91+
}
92+
93+
/// Convenience trait for boxing scenes.
94+
pub trait IntoBoxedScene: Scene {
95+
/// Box this scene.
96+
fn boxed_scene(self) -> BoxedScene;
97+
}
98+
99+
impl<T> IntoBoxedScene for T
100+
where
101+
T: Scene,
102+
{
103+
fn boxed_scene(self) -> BoxedScene {
104+
BoxedScene::new(self)
105+
}
106+
}

0 commit comments

Comments
 (0)