Skip to content

Commit ef28bd9

Browse files
authored
Transform Gizmo/UX updates (#247)
* Implement comprehensive Bevy editor features including toolbar, scene tree, and properties pane enhancements * Enhance Bevy toolbar and transform gizmos integration with improved UI elements, keyboard shortcuts, and snapping features * remove readme * Remove IMPLEMENTATION_PLAN.md from .gitignore * refactor: add a simple shared color theme system for usage throughout the editor. this should be easy to bring in line with bevy_feathers * fix: remove broken test * refactor: update doc comment * cargo format result
1 parent 8edc147 commit ef28bd9

File tree

14 files changed

+1185
-114
lines changed

14 files changed

+1185
-114
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ bevy_menu_bar = { path = "bevy_widgets/bevy_menu_bar" }
6363
bevy_scroll_box = { path = "bevy_widgets/bevy_scroll_box" }
6464
bevy_footer_bar = { path = "bevy_widgets/bevy_footer_bar" }
6565
bevy_toolbar = { path = "bevy_widgets/bevy_toolbar" }
66+
bevy_gizmo_indicator = { path = "bevy_widgets/bevy_gizmo_indicator" }
6667
bevy_tooltips = { path = "bevy_widgets/bevy_tooltips" }
6768
bevy_text_editing = { path = "bevy_widgets/bevy_text_editing" }
6869
bevy_field_forms = { path = "bevy_widgets/bevy_field_forms" }

bevy_editor_panes/bevy_properties_pane/src/lib.rs

Lines changed: 105 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use bevy::{
99
scene2::{CommandsSpawnScene, Scene, SceneList, bsn},
1010
};
1111
use bevy_editor_core::{prelude::*, selection::common_conditions::primary_selection_changed};
12+
use bevy_editor_styles::Theme;
1213
use bevy_pane_layout::prelude::*;
1314

1415
/// Plugin for the editor properties pane.
@@ -18,9 +19,9 @@ impl Plugin for PropertiesPanePlugin {
1819
fn build(&self, app: &mut App) {
1920
app.register_pane("Properties", setup_pane).add_systems(
2021
Update,
21-
update_properties_pane.run_if(
22+
(update_properties_pane.run_if(
2223
primary_selection_changed.or(any_match_filter::<Added<PropertiesPaneBody>>),
23-
),
24+
),),
2425
);
2526
}
2627
}
@@ -49,31 +50,46 @@ fn setup_pane(pane: In<PaneStructure>, mut commands: Commands) {
4950
fn update_properties_pane(
5051
pane_bodies: Query<Entity, With<PropertiesPaneBody>>,
5152
selection: Res<EditorSelection>,
53+
theme: Res<Theme>,
5254
world: &World,
5355
mut commands: Commands,
5456
) {
5557
for pane_body in &pane_bodies {
5658
commands.entity(pane_body).despawn_children();
5759
commands
58-
.spawn_scene(properties_pane(&selection, world))
60+
.spawn_scene(properties_pane(&selection, &theme, world))
5961
.insert(ChildOf(pane_body));
6062
}
6163
}
6264

63-
fn properties_pane(selection: &EditorSelection, world: &World) -> impl Scene {
65+
fn properties_pane(selection: &EditorSelection, theme: &Theme, world: &World) -> impl Scene {
6466
match selection.primary() {
65-
Some(selection) => bsn! {Node { flex_direction: FlexDirection::Column } [
66-
{component_list(selection, world)}
67+
Some(selection) => bsn! {
68+
Node {
69+
flex_direction: FlexDirection::Column,
70+
padding: UiRect::all(Val::Px(8.0)),
71+
row_gap: Val::Px(6.0)
72+
} [
73+
{component_list(selection, theme, world)}
6774
]}
6875
.boxed_scene(),
6976
None => bsn! {
70-
(Text("Select an entity to inspect") ThemedText)
77+
Node {
78+
flex_direction: FlexDirection::Column,
79+
justify_content: JustifyContent::Center,
80+
align_items: AlignItems::Center,
81+
padding: UiRect::all(Val::Px(24.0))
82+
} [
83+
Text("Select an entity to inspect")
84+
TextFont::from_font_size(14.0)
85+
TextColor(Color::srgb(0.514, 0.514, 0.522)),
86+
]
7187
}
7288
.boxed_scene(),
7389
}
7490
}
7591

76-
fn component_list(entity: Entity, world: &World) -> impl SceneList {
92+
fn component_list(entity: Entity, theme: &Theme, world: &World) -> impl SceneList {
7793
let type_registry = world.resource::<AppTypeRegistry>().read();
7894
world
7995
.inspect_entity(entity)
@@ -98,72 +114,116 @@ fn component_list(entity: Entity, world: &World) -> impl SceneList {
98114
bsn! {
99115
Node {
100116
flex_direction: FlexDirection::Column,
101-
margin: UiRect::all(Val::Px(4.0)),
102-
} [
117+
margin: UiRect::bottom(Val::Px(6.0)),
118+
border: UiRect::all(Val::Px(1.0)),
119+
padding: UiRect::all(Val::Px(0.0))
120+
}
121+
// CSS: #2A2A2E - Component background
122+
BackgroundColor(Color::srgb(0.165, 0.165, 0.180))
123+
// CSS: #414142 - Border color
124+
BorderColor::all(Color::srgb(0.255, 0.255, 0.259))
125+
BorderRadius::all(Val::Px(5.0))
126+
[
127+
// Component header - CSS styling
103128
Node {
104129
flex_direction: FlexDirection::Row,
130+
justify_content: JustifyContent::SpaceBetween,
105131
align_items: AlignItems::Center,
106-
} [
107-
TextFont::from_font_size(14.0)
108-
Text({format!("{name}")})
109-
TextColor(Color::WHITE)
132+
padding: UiRect::all(Val::Px(8.0)),
133+
height: Val::Px(26.0)
134+
}
135+
// CSS: #36373B - Header background
136+
BackgroundColor(Color::srgb(0.212, 0.216, 0.231))
137+
BorderRadius::top(Val::Px(5.0))
138+
[
139+
Node {
140+
flex_direction: FlexDirection::Row,
141+
align_items: AlignItems::Center,
142+
column_gap: Val::Px(5.0)
143+
} [
144+
Text("▼")
145+
TextFont::from_font_size(12.0)
146+
// CSS: #C4C4C4 - Chevron color
147+
TextColor(Color::srgb(0.769, 0.769, 0.769)),
148+
149+
Text({format!("{name}")})
150+
TextFont::from_font_size(12.0)
151+
// CSS: #DCDCDC - Component name
152+
TextColor(Color::srgb(0.863, 0.863, 0.863)),
153+
],
154+
155+
Text("⋯")
156+
TextFont::from_font_size(12.0)
157+
// CSS: #C4C4C4 - Menu dots
158+
TextColor(Color::srgb(0.769, 0.769, 0.769)),
110159
],
111160
// Component fields
112161
({ match reflect {
113-
Some(reflect) => component(type_info, reflect).boxed_scene(),
162+
Some(reflect) => component(type_info, reflect, theme).boxed_scene(),
114163
None => bsn! {
115164
Node {
116165
flex_direction: FlexDirection::Row,
166+
padding: UiRect::all(Val::Px(8.0))
117167
} [
118-
Text("<unavailable>")
119-
TextFont::from_font_size(10.0)
120-
TextColor(Color::srgb(1.0, 0.0, 0.0))
168+
Text("<reflection unavailable>")
169+
TextFont::from_font_size(11.0)
170+
TextColor(Color::srgb(0.514, 0.514, 0.522)),
121171
]
122172
}.boxed_scene(),
123-
}})
173+
}}),
124174
]
125175
}
126176
})
127177
.collect::<Vec<_>>()
128178
}
129179

130-
fn component(type_info: Option<&TypeInfo>, reflect: &dyn Reflect) -> impl Scene {
180+
fn component(type_info: Option<&TypeInfo>, reflect: &dyn Reflect, theme: &Theme) -> impl Scene {
131181
match type_info {
132-
Some(TypeInfo::Struct(info)) => reflected_struct(info, reflect).boxed_scene(),
133-
Some(TypeInfo::TupleStruct(info)) => reflected_tuple_struct(info).boxed_scene(),
134-
Some(TypeInfo::Enum(info)) => reflected_enum(info).boxed_scene(),
182+
Some(TypeInfo::Struct(info)) => reflected_struct(info, reflect, theme).boxed_scene(),
183+
Some(TypeInfo::TupleStruct(info)) => reflected_tuple_struct(info, theme).boxed_scene(),
184+
Some(TypeInfo::Enum(info)) => reflected_enum(info, theme).boxed_scene(),
135185
_ => bsn! {}.boxed_scene(),
136186
}
137187
}
138-
fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect) -> impl Scene {
188+
fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect, _theme: &Theme) -> impl Scene {
139189
let fields = struct_info
140190
.iter()
141191
.enumerate()
142192
.map(|(i, field)| {
143-
let valuee = reflect
193+
let field_reflect = reflect
144194
.reflect_ref()
145195
.as_struct()
146-
.map(|s| s.field_at(i))
147-
.map(|v| format!("{v:?}"))
148-
.unwrap_or("<unavailable>".to_string());
196+
.ok()
197+
.and_then(|s| s.field_at(i));
149198

150199
let field_name = field.name();
200+
201+
let value_string = field_reflect
202+
.map(|v| format!("{v:?}"))
203+
.unwrap_or_else(|| "<unavailable>".to_string());
204+
151205
bsn! {
152206
Node {
153207
flex_direction: FlexDirection::Row,
154208
margin: UiRect::vertical(Val::Px(2.0)),
155-
} [
156-
(
157-
Text(field_name)
158-
TextFont::from_font_size(12.0)
159-
TextColor(Color::srgb(0.8, 0.8, 0.8))
160-
),
161-
(
162-
// Value (use reflection to get value as string)
163-
Text({valuee.clone()})
164-
TextFont::from_font_size(10.0)
165-
TextColor(Color::WHITE)
166-
),
209+
padding: UiRect::all(Val::Px(5.0)),
210+
justify_content: JustifyContent::SpaceBetween,
211+
align_items: AlignItems::Center,
212+
min_height: Val::Px(22.0)
213+
}
214+
// CSS: #36373B - Field background
215+
BackgroundColor(Color::srgb(0.212, 0.216, 0.231))
216+
BorderRadius::all(Val::Px(3.0))
217+
[
218+
Text(field_name)
219+
TextFont::from_font_size(12.0)
220+
// CSS: #DADADA - Field labels
221+
TextColor(Color::srgb(0.855, 0.855, 0.855)),
222+
223+
Text({value_string.clone()})
224+
TextFont::from_font_size(12.0)
225+
// CSS: #C2C2C2 - Field values
226+
TextColor(Color::srgb(0.761, 0.761, 0.761)),
167227
]
168228
}
169229
})
@@ -172,11 +232,13 @@ fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect) -> impl Sce
172232
bsn! {
173233
Node {
174234
flex_direction: FlexDirection::Column,
235+
padding: UiRect::all(Val::Px(7.0)),
236+
row_gap: Val::Px(4.0)
175237
} [ {fields} ]
176238
}
177239
}
178240

179-
fn reflected_tuple_struct(tuple_struct_info: &TupleStructInfo) -> impl Scene {
241+
fn reflected_tuple_struct(tuple_struct_info: &TupleStructInfo, _theme: &Theme) -> impl Scene {
180242
let fields = tuple_struct_info
181243
.iter()
182244
.map(|_field| {
@@ -189,12 +251,12 @@ fn reflected_tuple_struct(tuple_struct_info: &TupleStructInfo) -> impl Scene {
189251

190252
bsn! {
191253
Node {
192-
flex_direction: FlexDirection::Column,
254+
flex_direction: FlexDirection::Column
193255
} [ {fields} ]
194256
}
195257
}
196258

197-
fn reflected_enum(enum_info: &EnumInfo) -> impl Scene {
259+
fn reflected_enum(enum_info: &EnumInfo, _theme: &Theme) -> impl Scene {
198260
let variants = enum_info
199261
.iter()
200262
.map(|variant| {

bevy_editor_panes/bevy_scene_tree/src/lib.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,19 @@ fn update_scene_tree(
5050
for scene_tree in &scene_trees {
5151
let tree_rows: Template = scene_entities
5252
.iter()
53-
.flat_map(|(entity, name)| scene_tree_row_for_entity(entity, name, &selection))
53+
.flat_map(|(entity, name)| scene_tree_row_for_entity(entity, name, &selection, 0))
5454
.collect();
5555

5656
commands.entity(scene_tree).build_children(tree_rows);
5757
}
5858
}
5959

60-
fn scene_tree_row_for_entity(entity: Entity, name: &Name, selection: &EditorSelection) -> Template {
60+
fn scene_tree_row_for_entity(
61+
entity: Entity,
62+
name: &Name,
63+
selection: &EditorSelection,
64+
level: usize,
65+
) -> Template {
6166
let selection_handler =
6267
move |mut trigger: On<Pointer<Click>>,
6368
keyboard_input: Res<ButtonInput<KeyCode>>,
@@ -75,20 +80,34 @@ fn scene_tree_row_for_entity(entity: Entity, name: &Name, selection: &EditorSele
7580
}
7681
};
7782

83+
let indentation_px = level * 20;
84+
7885
template! {
7986
{entity}: (
8087
Node {
81-
padding: UiRect::all(Val::Px(4.0)),
88+
padding: UiRect::new(Val::Px(4.0 + indentation_px as f32), Val::Px(4.0), Val::Px(2.0), Val::Px(2.0)),
8289
align_items: AlignItems::Center,
83-
..Default::default()
90+
flex_direction: FlexDirection::Row,
91+
..default()
8492
},
8593
BorderRadius::all(Val::Px(4.0)),
86-
BackgroundColor(if selection.contains(entity) { tailwind::NEUTRAL_700.into() } else { Color::NONE }),
94+
BackgroundColor(if selection.contains(entity) { tailwind::BLUE_600.into() } else { Color::NONE }),
8795
) => [
8896
on(selection_handler);
97+
// Indentation spacer
98+
(
99+
Node {
100+
width: Val::Px(16.0),
101+
height: Val::Px(16.0),
102+
margin: UiRect::right(Val::Px(4.0)),
103+
..default()
104+
},
105+
);
106+
// Entity name
89107
(
90108
Text(name.into()),
91-
TextFont::from_font_size(11.0),
109+
TextFont::from_font_size(12.0),
110+
TextColor(if selection.contains(entity) { Color::WHITE } else { tailwind::NEUTRAL_200.into() }),
92111
Pickable::IGNORE,
93112
);
94113
];
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "bevy_gizmo_indicator"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
bevy.workspace = true
8+
bevy_editor_styles = { path = "../../crates/bevy_editor_styles" }
9+
bevy_transform_gizmos = { path = "../../crates/bevy_transform_gizmos" }
10+
11+
[lints]
12+
workspace = true

0 commit comments

Comments
 (0)