Skip to content

Commit 1f835c1

Browse files
committed
Add support to bevy_window for closing windows
Make the rest of bevy work with multiple windows Remove exit_on_esc Add suggested docs
1 parent d8974e7 commit 1f835c1

File tree

16 files changed

+148
-51
lines changed

16 files changed

+148
-51
lines changed

crates/bevy_input/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pub mod gamepad;
33
mod input;
44
pub mod keyboard;
55
pub mod mouse;
6-
pub mod system;
76
pub mod touch;
87

98
pub use axis::*;

crates/bevy_input/src/system.rs

-20
This file was deleted.

crates/bevy_pbr/src/light.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -371,9 +371,13 @@ pub fn add_clusters(
371371

372372
pub fn update_clusters(windows: Res<Windows>, mut views: Query<(&Camera, &mut Clusters)>) {
373373
for (camera, mut clusters) in views.iter_mut() {
374+
// If the window doesn't exist, we won't render anything to it, so don't need to calculate the clusters for it
375+
let window = match windows.get(camera.window) {
376+
Some(window) => window,
377+
_ => continue,
378+
};
374379
let is_orthographic = camera.projection_matrix.w_axis.w == 1.0;
375380
let inverse_projection = camera.projection_matrix.inverse();
376-
let window = windows.get(camera.window).unwrap();
377381
let screen_size_u32 = UVec2::new(window.physical_width(), window.physical_height());
378382
// Don't update clusters if screen size is 0.
379383
if screen_size_u32.x == 0 || screen_size_u32.y == 0 {

crates/bevy_render/src/view/window.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
use bevy_app::{App, Plugin};
88
use bevy_ecs::prelude::*;
99
use bevy_utils::{tracing::debug, HashMap, HashSet};
10-
use bevy_window::{PresentMode, RawWindowHandleWrapper, WindowId, Windows};
10+
use bevy_window::{PresentMode, RawWindowHandleWrapper, WindowClosed, WindowId, Windows};
1111
use std::ops::{Deref, DerefMut};
1212
use wgpu::TextureFormat;
1313

@@ -67,7 +67,11 @@ impl DerefMut for ExtractedWindows {
6767
}
6868
}
6969

70-
fn extract_windows(mut render_world: ResMut<RenderWorld>, windows: Res<Windows>) {
70+
fn extract_windows(
71+
mut render_world: ResMut<RenderWorld>,
72+
mut closed: EventReader<WindowClosed>,
73+
windows: Res<Windows>,
74+
) {
7175
let mut extracted_windows = render_world.get_resource_mut::<ExtractedWindows>().unwrap();
7276
for window in windows.iter() {
7377
let (new_width, new_height) = (
@@ -105,6 +109,9 @@ fn extract_windows(mut render_world: ResMut<RenderWorld>, windows: Res<Windows>)
105109
extracted_window.physical_height = new_height;
106110
}
107111
}
112+
for closed_window in closed.iter() {
113+
extracted_windows.remove(&closed_window.id);
114+
}
108115
}
109116

110117
#[derive(Default)]

crates/bevy_window/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ keywords = ["bevy"]
1010

1111
[dependencies]
1212
# bevy
13+
bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" }
14+
# Used for close_on_esc
15+
bevy_input = { path = "../bevy_input", version = "0.6.0" }
1316
bevy_app = { path = "../bevy_app", version = "0.6.0" }
1417
bevy_math = { path = "../bevy_math", version = "0.6.0" }
1518
bevy_utils = { path = "../bevy_utils", version = "0.6.0" }

crates/bevy_window/src/event.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@ pub struct CreateWindow {
2020
pub descriptor: WindowDescriptor,
2121
}
2222

23-
/// An event that indicates a window should be closed.
24-
#[derive(Debug, Clone)]
25-
pub struct CloseWindow {
26-
pub id: WindowId,
27-
}
28-
2923
/// An event that is sent whenever a new window is created.
3024
#[derive(Debug, Clone)]
3125
pub struct WindowCreated {
@@ -34,11 +28,26 @@ pub struct WindowCreated {
3428

3529
/// An event that is sent whenever a close was requested for a window. For example: when the "close"
3630
/// button is pressed on a window.
31+
///
32+
/// By default, these events are handled by closing the corresponding [`crate::Window`].
33+
/// To disable this behaviour, set `close_when_requested` on the [`crate::WindowPlugin`] to `false`
3734
#[derive(Debug, Clone)]
3835
pub struct WindowCloseRequested {
3936
pub id: WindowId,
4037
}
4138

39+
/// An event that is sent whenever a window is closed.
40+
/// This will only be sent in response to the [`Window::close`] method.
41+
///
42+
/// By default, when no windows are open, the app will close.
43+
/// To disable this behaviour, set `exit_on_all_closed` on the [`crate::WindowPlugin`] to `false`
44+
///
45+
/// [`Window::close`]: crate::Window::close
46+
#[derive(Debug, Clone)]
47+
pub struct WindowClosed {
48+
pub id: WindowId,
49+
}
50+
4251
#[derive(Debug, Clone)]
4352
pub struct CursorMoved {
4453
pub id: WindowId,

crates/bevy_window/src/lib.rs

+19-5
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,25 @@ use bevy_app::{prelude::*, Events};
2424

2525
pub struct WindowPlugin {
2626
pub add_primary_window: bool,
27-
pub exit_on_close: bool,
27+
/// Whether to close the app when there are no open windows.
28+
/// If disabling this, consider ensuring that you send a [`bevy_app::AppExit`] event yourself
29+
/// when the app should exit; otherwise you will create headless processes, which would be
30+
/// surprising for your users.
31+
///
32+
/// This setting controls whether this plugin adds [`exit_on_all_closed`]
33+
pub exit_on_all_closed: bool,
34+
/// Whether to close windows when they are requested to be closed (i.e. when the close button is pressed)
35+
///
36+
/// This setting controls whether this plugin adds [`close_when_requested`]
37+
pub close_when_requested: bool,
2838
}
2939

3040
impl Default for WindowPlugin {
3141
fn default() -> Self {
3242
WindowPlugin {
3343
add_primary_window: true,
34-
exit_on_close: true,
44+
exit_on_all_closed: true,
45+
close_when_requested: true,
3546
}
3647
}
3748
}
@@ -41,8 +52,8 @@ impl Plugin for WindowPlugin {
4152
app.add_event::<WindowResized>()
4253
.add_event::<CreateWindow>()
4354
.add_event::<WindowCreated>()
55+
.add_event::<WindowClosed>()
4456
.add_event::<WindowCloseRequested>()
45-
.add_event::<CloseWindow>()
4657
.add_event::<CursorMoved>()
4758
.add_event::<CursorEntered>()
4859
.add_event::<CursorLeft>()
@@ -70,8 +81,11 @@ impl Plugin for WindowPlugin {
7081
});
7182
}
7283

73-
if self.exit_on_close {
74-
app.add_system(exit_on_window_close_system);
84+
if self.exit_on_all_closed {
85+
app.add_system(exit_on_all_closed);
86+
}
87+
if self.close_when_requested {
88+
app.add_system(close_when_requested);
7589
}
7690
}
7791
}

crates/bevy_window/src/system.rs

+50-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,55 @@
1-
use crate::WindowCloseRequested;
1+
use crate::{Window, WindowCloseRequested, WindowFocused, WindowId, Windows};
22
use bevy_app::{AppExit, EventReader, EventWriter};
3+
use bevy_ecs::prelude::*;
4+
use bevy_input::{keyboard::KeyCode, Input};
35

4-
pub fn exit_on_window_close_system(
5-
mut app_exit_events: EventWriter<AppExit>,
6-
mut window_close_requested_events: EventReader<WindowCloseRequested>,
7-
) {
8-
if window_close_requested_events.iter().next().is_some() {
6+
/// Whether to exit the application when there are no open windows.
7+
///
8+
/// By default, this system is added by the [`crate::WindowPlugin`].
9+
/// To disable this behaviour, set `close_when_requested` (on the [`crate::WindowPlugin`]) to `false`.
10+
/// Please ensure that you read the caveats documented on that field.
11+
12+
pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Res<Windows>) {
13+
if windows.iter().count() == 0 {
914
app_exit_events.send(AppExit);
1015
}
1116
}
17+
18+
/// Whether to close windows when they are requested to be closed (i.e. when the close button is pressed).
19+
/// Not adding this system (without replacement) will lead to the close button having no effect.
20+
///
21+
/// By default, this system is added by the [`crate::WindowPlugin`].
22+
/// To disable this behaviour, set `close_when_requested` (on the [`crate::WindowPlugin`]) to `false`
23+
pub fn close_when_requested(
24+
mut windows: ResMut<Windows>,
25+
mut closed: EventReader<WindowCloseRequested>,
26+
) {
27+
for event in closed.iter() {
28+
windows.get_mut(event.id).map(Window::close);
29+
}
30+
}
31+
32+
// TODO: Consider using the kbd tag here for escape: <kbd>esc</kbd>
33+
// Currently, it isn't rendered by vscode's hover markdown provider (and the contents are lost)
34+
/// Close the focused window whenever the escape key is pressed
35+
///
36+
/// This is useful for examples
37+
pub fn close_on_esc(
38+
mut focused: Local<Option<WindowId>>,
39+
mut focused_events: EventReader<WindowFocused>,
40+
mut windows: ResMut<Windows>,
41+
input: Res<Input<KeyCode>>,
42+
) {
43+
// TODO: Track this in e.g. a resource to ensure consistent behaviour across similar systems
44+
for event in focused_events.iter() {
45+
*focused = event.focused.then(|| event.id);
46+
}
47+
48+
if let Some(focused) = &*focused {
49+
if input.just_pressed(KeyCode::Escape) {
50+
if let Some(window) = windows.get_mut(*focused) {
51+
window.close();
52+
}
53+
}
54+
}
55+
}

crates/bevy_window/src/window.rs

+5
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ pub enum WindowCommand {
219219
SetResizeConstraints {
220220
resize_constraints: WindowResizeConstraints,
221221
},
222+
Close,
222223
}
223224

224225
/// Defines the way a window is displayed
@@ -571,6 +572,10 @@ impl Window {
571572
});
572573
}
573574

575+
pub fn close(&mut self) {
576+
self.command_queue.push(WindowCommand::Close);
577+
}
578+
574579
#[inline]
575580
pub fn drain_commands(&mut self) -> impl Iterator<Item = WindowCommand> + '_ {
576581
self.command_queue.drain(..)

crates/bevy_window/src/windows.rs

+4
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ impl Windows {
3434
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Window> {
3535
self.windows.values_mut()
3636
}
37+
38+
pub fn remove(&mut self, id: WindowId) -> Option<Window> {
39+
self.windows.remove(&id)
40+
}
3741
}

crates/bevy_winit/src/lib.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ pub use winit_windows::*;
1313
use bevy_app::{App, AppExit, CoreStage, Events, ManualEventReader, Plugin};
1414
use bevy_ecs::{system::IntoExclusiveSystem, world::World};
1515
use bevy_math::{ivec2, DVec2, Vec2};
16-
use bevy_utils::tracing::{error, trace, warn};
16+
use bevy_utils::tracing::{error, info, trace, warn};
1717
use bevy_window::{
1818
CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ReceivedCharacter,
19-
WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowFocused,
20-
WindowMoved, WindowResized, WindowScaleFactorChanged, Windows,
19+
WindowBackendScaleFactorChanged, WindowCloseRequested, WindowClosed, WindowCreated,
20+
WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged, Windows,
2121
};
2222
use winit::{
2323
dpi::PhysicalPosition,
@@ -43,8 +43,9 @@ impl Plugin for WinitPlugin {
4343

4444
fn change_window(world: &mut World) {
4545
let world = world.cell();
46-
let winit_windows = world.get_resource::<WinitWindows>().unwrap();
46+
let mut winit_windows = world.get_resource_mut::<WinitWindows>().unwrap();
4747
let mut windows = world.get_resource_mut::<Windows>().unwrap();
48+
let mut removed_windows = Vec::new();
4849

4950
for bevy_window in windows.iter_mut() {
5051
let id = bevy_window.id();
@@ -160,9 +161,26 @@ fn change_window(world: &mut World) {
160161
window.set_max_inner_size(Some(max_inner_size));
161162
}
162163
}
164+
bevy_window::WindowCommand::Close => {
165+
let window = winit_windows.remove_window(id);
166+
// Close the window
167+
drop(window);
168+
// Since we borrow `windows` here to iterate through them, we can't mutate it here.
169+
// Add it to the queue to solve this
170+
removed_windows.push(id);
171+
// No need to run any further commands - this drops the rest of the commands, although the `bevy_window::Window` will be dropped later anyway
172+
break;
173+
}
163174
}
164175
}
165176
}
177+
if !removed_windows.is_empty() {
178+
let mut events = world.get_resource_mut::<Events<WindowClosed>>().unwrap();
179+
for id in removed_windows {
180+
windows.remove(id);
181+
events.send(WindowClosed { id });
182+
}
183+
}
166184
}
167185

168186
fn run<F>(event_loop: EventLoop<()>, event_handler: F) -> !
@@ -280,7 +298,8 @@ pub fn winit_runner_with(mut app: App) {
280298
let window = if let Some(window) = windows.get_mut(window_id) {
281299
window
282300
} else {
283-
warn!("Skipped event for unknown Window Id {:?}", winit_window_id);
301+
// If we're here, this window was previously opened
302+
info!("Skipped event for closed window: {:?}", window_id);
284303
return;
285304
};
286305

crates/bevy_winit/src/winit_windows.rs

+6
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ impl WinitWindows {
184184
pub fn get_window_id(&self, id: winit::window::WindowId) -> Option<WindowId> {
185185
self.winit_to_window_id.get(&id).cloned()
186186
}
187+
188+
pub fn remove_window(&mut self, id: WindowId) -> Option<winit::window::Window> {
189+
let winit_id = self.window_id_to_winit.remove(&id)?;
190+
// Don't remove from winit_to_window_id, to track that we used to know about this winit window
191+
self.windows.remove(&winit_id)
192+
}
187193
}
188194

189195
pub fn get_fitting_videomode(

examples/2d/rotation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ fn main() {
1818
.with_system(snap_to_player_system)
1919
.with_system(rotate_to_player_system),
2020
)
21-
.add_system(bevy::input::system::exit_on_esc_system)
21+
.add_system(bevy::window::close_on_esc)
2222
.run();
2323
}
2424

examples/game/alien_cake_addict.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bevy::{
22
core::FixedTimestep, ecs::schedule::SystemSet, prelude::*, render::camera::CameraPlugin,
3+
window::close_on_esc,
34
};
45
use rand::Rng;
56

@@ -33,7 +34,7 @@ fn main() {
3334
.with_run_criteria(FixedTimestep::step(5.0))
3435
.with_system(spawn_bonus),
3536
)
36-
.add_system(bevy::input::system::exit_on_esc_system)
37+
.add_system(close_on_esc)
3738
.run();
3839
}
3940

examples/game/breakout.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use bevy::{
22
core::FixedTimestep,
33
prelude::*,
44
sprite::collide_aabb::{collide, Collision},
5+
window::close_on_esc,
56
};
67

78
/// An implementation of the classic game "Breakout"
@@ -20,7 +21,7 @@ fn main() {
2021
.with_system(ball_movement_system),
2122
)
2223
.add_system(scoreboard_system)
23-
.add_system(bevy::input::system::exit_on_esc_system)
24+
.add_system(close_on_esc)
2425
.run();
2526
}
2627

0 commit comments

Comments
 (0)