Skip to content

Commit 0606b3d

Browse files
authored
Merge branch 'main' into feat/extend-settings-group
2 parents 3c31d55 + ffa4b0e commit 0606b3d

51 files changed

Lines changed: 1035 additions & 192 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: "`InputFocus` fields are no longer public"
3+
pull_requests: [23723]
4+
---
5+
6+
The `.0` field on `InputFocus` is no longer public.
7+
Use the getter and setters methods instead.
8+
9+
Before:
10+
11+
```rust
12+
let focused_entity = input_focus.0;
13+
input_focus.0 = Some(entity);
14+
input_focus.0 = None;
15+
```
16+
17+
After:
18+
19+
```rust
20+
let focused_entity = input_focus.get();
21+
input_focus.set(entity);
22+
input_focus.clear();
23+
```
24+
25+
Additionally, the core setup of `InputFocus` and related resources now occurs in `InputFocusPlugin`,
26+
rather than `InputDispatchPlugin`.
27+
This is part of `DefaultPlugins`, so most users will not need to make any changes.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: "`Skybox` `image` is now optional"
3+
pull_requests: [23691]
4+
---
5+
6+
The `image` field of the `Skybox` component now has the type `Option<Handle<Image>>` instead of `Handle<Image>`.
7+
A `Skybox` component without an image will not draw anything, just like it was not present.
8+
9+
If you were creating a skybox with an image, wrap the image handle in `Some`:
10+
11+
```rust
12+
// 0.18
13+
Skybox {
14+
image: my_skybox,
15+
brightness: 1000.0,
16+
..default()
17+
}
18+
19+
// 0.19
20+
Skybox {
21+
image: Some(my_skybox),
22+
brightness: 1000.0,
23+
..default()
24+
}
25+
```
26+
27+
If you were previously creating a `Skybox` component with a placeholder image to be changed later, you can now remove the placeholder:
28+
29+
```rust
30+
// 0.18
31+
Skybox {
32+
image: cubemap_image_that_will_not_actually_be_seen,
33+
brightness: 1000.0,
34+
..default()
35+
}
36+
37+
// 0.19
38+
Skybox {
39+
brightness: 1000.0,
40+
..default()
41+
}
42+
```

crates/bevy_camera_controller/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev", default-featu
2222
bevy_time = { path = "../bevy_time", version = "0.19.0-dev", default-features = false }
2323
bevy_transform = { path = "../bevy_transform", version = "0.19.0-dev", default-features = false }
2424
bevy_window = { path = "../bevy_window", version = "0.19.0-dev", default-features = false }
25+
bevy_picking = { path = "../bevy_picking", version = "0.19.0-dev", default-features = false }
2526

2627
[features]
2728
default = ["bevy_reflect"]

crates/bevy_camera_controller/src/pan_camera.rs

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
//! To configure the settings of this controller, modify the fields of the [`PanCamera`] component.
77
88
use bevy_app::{App, Plugin, RunFixedMainLoop, RunFixedMainLoopSystems};
9-
use bevy_camera::Camera;
9+
use bevy_camera::{Camera, RenderTarget};
1010
use bevy_ecs::prelude::*;
1111
use bevy_input::keyboard::KeyCode;
12-
use bevy_input::mouse::{AccumulatedMouseScroll, MouseScrollUnit};
12+
use bevy_input::mouse::{AccumulatedMouseScroll, MouseButton, MouseScrollUnit};
1313
use bevy_input::ButtonInput;
1414
use bevy_math::{Vec2, Vec3};
15+
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer};
1516
use bevy_time::{Real, Time};
17+
use bevy_transform::components::GlobalTransform;
1618
use bevy_transform::prelude::Transform;
17-
19+
use bevy_window::{PrimaryWindow, WindowRef};
1820
use core::{f32::consts::*, fmt};
1921

2022
/// A plugin that enables 2D camera panning and zooming controls.
@@ -28,7 +30,9 @@ impl Plugin for PanCameraPlugin {
2830
app.add_systems(
2931
RunFixedMainLoop,
3032
run_pancamera_controller.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop),
31-
);
33+
)
34+
.add_observer(add_window_observer)
35+
.add_observer(remove_window_observer);
3236
}
3337
}
3438

@@ -68,6 +72,16 @@ pub struct PanCamera {
6872
pub key_rotate_ccw: Option<KeyCode>,
6973
/// [`KeyCode`] for clockwise rotation.
7074
pub key_rotate_cw: Option<KeyCode>,
75+
/// Mouse pan settings.
76+
pub mouse_pan_settings: MousePanSettings,
77+
}
78+
79+
/// Settings for mouse panning for the [`PanCamera`] controller.
80+
pub struct MousePanSettings {
81+
/// Whether the mouse panning is enabled.
82+
pub enabled: bool,
83+
/// The mouse button to use for panning.
84+
pub button: MouseButton,
7185
}
7286

7387
/// Provides the default values for the `PanCamera` controller.
@@ -105,6 +119,10 @@ impl Default for PanCamera {
105119
rotation_speed: PI,
106120
key_rotate_ccw: Some(KeyCode::KeyQ),
107121
key_rotate_cw: Some(KeyCode::KeyE),
122+
mouse_pan_settings: MousePanSettings {
123+
enabled: true,
124+
button: MouseButton::Left,
125+
},
108126
}
109127
}
110128
}
@@ -235,3 +253,83 @@ fn run_pancamera_controller(
235253

236254
transform.scale = Vec3::splat(controller.zoom_factor);
237255
}
256+
257+
/// A component attached to window entities that holds the id of an
258+
/// active `handle_mouse_pan` observer. It is None if there is no
259+
/// such observer.
260+
#[derive(Component)]
261+
struct HandleMousePanObserver(Option<Entity>);
262+
263+
fn add_window_observer(
264+
drag_start: On<Pointer<DragStart>>,
265+
mut commands: Commands,
266+
render_targets: Query<&RenderTarget, With<PanCamera>>,
267+
primary_window: Single<Entity, With<PrimaryWindow>>,
268+
) {
269+
for render_target in render_targets.iter() {
270+
if let RenderTarget::Window(window) = render_target {
271+
let entity = match window {
272+
WindowRef::Primary => primary_window.entity(),
273+
WindowRef::Entity(entity) => *entity,
274+
};
275+
if entity == drag_start.entity {
276+
let observer_id = commands
277+
.spawn(Observer::new(handle_mouse_pan).with_entity(entity))
278+
.id();
279+
commands
280+
.entity(entity)
281+
.insert(HandleMousePanObserver(Some(observer_id)));
282+
}
283+
}
284+
}
285+
}
286+
287+
fn remove_window_observer(
288+
drag_end: On<Pointer<DragEnd>>,
289+
mut commands: Commands,
290+
render_targets: Query<&RenderTarget, With<PanCamera>>,
291+
mut handle_mouse_pan_observer: Query<&mut HandleMousePanObserver>,
292+
primary_window: Single<Entity, With<PrimaryWindow>>,
293+
) {
294+
for render_target in render_targets.iter() {
295+
if let RenderTarget::Window(window) = render_target {
296+
let entity = match window {
297+
WindowRef::Primary => primary_window.entity(),
298+
WindowRef::Entity(entity) => *entity,
299+
};
300+
if entity == drag_end.entity
301+
&& let Ok(mut observer) = handle_mouse_pan_observer.get_mut(entity)
302+
&& let Some(observer_entity) = observer.0.take()
303+
{
304+
commands.entity(observer_entity).despawn();
305+
}
306+
}
307+
}
308+
}
309+
310+
fn handle_mouse_pan(
311+
drag: On<Pointer<Drag>>,
312+
mut pan_cameras: Query<(&Camera, &GlobalTransform, &mut Transform, &PanCamera)>,
313+
) {
314+
for (camera, global_transform, mut transform, pan_camera_controller) in pan_cameras.iter_mut() {
315+
if !pan_camera_controller.enabled || !pan_camera_controller.mouse_pan_settings.enabled {
316+
return;
317+
}
318+
319+
let Ok(camera_screen_position) =
320+
camera.world_to_viewport(global_transform, transform.translation)
321+
else {
322+
continue;
323+
};
324+
325+
let offset_camera_screen_position = camera_screen_position + drag.delta * -1.; // inverted feels more natural
326+
327+
let Ok(new_camera_position) =
328+
camera.viewport_to_world_2d(global_transform, offset_camera_screen_position)
329+
else {
330+
continue;
331+
};
332+
333+
transform.translation = new_camera_position.extend(transform.translation.z);
334+
}
335+
}

crates/bevy_core_pipeline/src/skybox/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,18 @@ fn prepare_skybox_bind_groups(
227227
views: Query<(Entity, &Skybox, &DynamicUniformIndex<SkyboxUniforms>)>,
228228
) {
229229
for (entity, skybox, skybox_uniform_index) in &views {
230-
if let (Some(skybox), Some(view_uniforms), Some(skybox_uniforms)) = (
231-
images.get(&skybox.image),
230+
if let (Some(image_handle), Some(view_uniforms), Some(skybox_uniforms)) = (
231+
&skybox.image,
232232
view_uniforms.uniforms.binding(),
233233
skybox_uniforms.binding(),
234-
) {
234+
) && let Some(image) = images.get(image_handle)
235+
{
235236
let bind_group = render_device.create_bind_group(
236237
"skybox_bind_group",
237238
&pipeline_cache.get_bind_group_layout(&pipeline.bind_group_layout),
238239
&BindGroupEntries::sequential((
239-
&skybox.texture_view,
240-
&skybox.sampler,
240+
&image.texture_view,
241+
&image.sampler,
241242
view_uniforms,
242243
skybox_uniforms,
243244
)),
@@ -246,6 +247,8 @@ fn prepare_skybox_bind_groups(
246247
commands
247248
.entity(entity)
248249
.insert(SkyboxBindGroup((bind_group, skybox_uniform_index.index())));
250+
} else {
251+
commands.entity(entity).remove::<SkyboxBindGroup>();
249252
}
250253
}
251254
}

crates/bevy_ecs/src/event/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,22 @@ pub trait Event: Send + Sync + Sized + 'static {
272272
/// });
273273
/// ```
274274
///
275+
/// ## Best practices for event propagation
276+
///
277+
/// Propagation is useful for events that should be handled by multiple entities in a hierarchy, such as UI events.
278+
/// In these cases, it is common for the event to be triggered on a "leaf" entity, and then propagate up to "root" entities.
279+
/// In this pattern, it is generally recommended to trigger the event on the most specific entity possible (the leaf), and then use propagation to have it handled by more general entities (the roots).
280+
///
281+
/// Once an event is handled by a given entity, you should stop propagation.
282+
/// This ensures that only a single "behavior" resolves per event sent,
283+
/// avoiding unexpected behavior from entities higher up the hierarchy.
284+
///
285+
/// This advice has one notable wrinkle:
286+
/// if an entity is "disabled" (e.g. if a UI node is grayed out),
287+
/// the event should still be considered "handled" by that entity,
288+
/// even though the observer logic should not be run.
289+
/// This ensures consistent behavior regardless of the enabled/disabled state of entities.
290+
///
275291
/// ## Naming and Usage Conventions
276292
///
277293
/// In most cases, it is recommended to use a named struct field for the "event target" entity, and to use

0 commit comments

Comments
 (0)