Skip to content

Commit 3360b45

Browse files
tychedeliaPascal Hertleifalice-i-cecilekillercup
authored
Expose winit's MonitorHandle (bevyengine#13669)
# Objective Adds a new `Monitor` component representing a winit `MonitorHandle` that can be used to spawn new windows and check for system monitor information. Closes bevyengine#12955. ## Solution For every winit event, check available monitors and spawn them into the world as components. ## Testing TODO: - [x] Test plugging in and unplugging monitor during app runtime - [x] Test spawning a window on a second monitor by entity id - [ ] Since this touches winit, test all platforms --- ## Changelog - Adds a new `Monitor` component that can be queried for information about available system monitors. ## Migration Guide - `WindowMode` variants now take a `MonitorSelection`, which can be set to `MonitorSelection::Primary` to mirror the old behavior. --------- Co-authored-by: Pascal Hertleif <[email protected]> Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Pascal Hertleif <[email protected]>
1 parent 897625c commit 3360b45

File tree

12 files changed

+428
-67
lines changed

12 files changed

+428
-67
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3355,3 +3355,14 @@ panic = "abort"
33553355
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
33563356
all-features = true
33573357
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
3358+
3359+
[[example]]
3360+
name = "monitor_info"
3361+
path = "examples/window/monitor_info.rs"
3362+
doc-scrape-examples = true
3363+
3364+
[package.metadata.example.monitor_info]
3365+
name = "Monitor info"
3366+
description = "Displays information about available monitors (displays)."
3367+
category = "Window"
3368+
wasm = false

crates/bevy_window/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use bevy_a11y::Focus;
1717

1818
mod cursor;
1919
mod event;
20+
mod monitor;
2021
mod raw_handle;
2122
mod system;
2223
mod window;
@@ -25,6 +26,7 @@ pub use crate::raw_handle::*;
2526

2627
pub use cursor::*;
2728
pub use event::*;
29+
pub use monitor::*;
2830
pub use system::*;
2931
pub use window::*;
3032

crates/bevy_window/src/monitor.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use bevy_ecs::component::Component;
2+
use bevy_ecs::prelude::ReflectComponent;
3+
use bevy_math::{IVec2, UVec2};
4+
use bevy_reflect::Reflect;
5+
6+
#[cfg(feature = "serialize")]
7+
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
8+
9+
/// Represents an available monitor as reported by the user's operating system, which can be used
10+
/// to query information about the display, such as its size, position, and video modes.
11+
///
12+
/// Each monitor corresponds to an entity and can be used to position a monitor using
13+
/// [`crate::window::MonitorSelection::Entity`].
14+
///
15+
/// # Warning
16+
///
17+
/// This component is synchronized with `winit` through `bevy_winit`, but is effectively
18+
/// read-only as `winit` does not support changing monitor properties.
19+
#[derive(Component, Debug, Clone, Reflect)]
20+
#[cfg_attr(
21+
feature = "serialize",
22+
derive(serde::Serialize, serde::Deserialize),
23+
reflect(Serialize, Deserialize)
24+
)]
25+
#[reflect(Component)]
26+
pub struct Monitor {
27+
/// The name of the monitor
28+
pub name: Option<String>,
29+
/// The height of the monitor in physical pixels
30+
pub physical_height: u32,
31+
/// The width of the monitor in physical pixels
32+
pub physical_width: u32,
33+
/// The position of the monitor in physical pixels
34+
pub physical_position: IVec2,
35+
/// The refresh rate of the monitor in millihertz
36+
pub refresh_rate_millihertz: Option<u32>,
37+
/// The scale factor of the monitor
38+
pub scale_factor: f64,
39+
/// The video modes that the monitor supports
40+
pub video_modes: Vec<VideoMode>,
41+
}
42+
43+
/// A marker component for the primary monitor
44+
#[derive(Component, Debug, Clone, Reflect)]
45+
#[reflect(Component)]
46+
pub struct PrimaryMonitor;
47+
48+
impl Monitor {
49+
/// Returns the physical size of the monitor in pixels
50+
pub fn physical_size(&self) -> UVec2 {
51+
UVec2::new(self.physical_width, self.physical_height)
52+
}
53+
}
54+
55+
/// Represents a video mode that a monitor supports
56+
#[derive(Debug, Clone, Reflect)]
57+
#[cfg_attr(
58+
feature = "serialize",
59+
derive(serde::Serialize, serde::Deserialize),
60+
reflect(Serialize, Deserialize)
61+
)]
62+
pub struct VideoMode {
63+
/// The resolution of the video mode
64+
pub physical_size: UVec2,
65+
/// The bit depth of the video mode
66+
pub bit_depth: u16,
67+
/// The refresh rate in millihertz
68+
pub refresh_rate_millihertz: u32,
69+
}

crates/bevy_window/src/window.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,8 @@ pub enum MonitorSelection {
945945
Primary,
946946
/// Uses the monitor with the specified index.
947947
Index(usize),
948+
/// Uses a given [`crate::monitor::Monitor`] entity.
949+
Entity(Entity),
948950
}
949951

950952
/// Presentation mode for a [`Window`].
@@ -1092,7 +1094,7 @@ pub enum WindowMode {
10921094
#[default]
10931095
Windowed,
10941096
/// The window should appear fullscreen by being borderless and using the full
1095-
/// size of the screen.
1097+
/// size of the screen on the given [`MonitorSelection`].
10961098
///
10971099
/// When setting this, the window's physical size will be modified to match the size
10981100
/// of the current monitor resolution, and the logical size will follow based
@@ -1102,17 +1104,17 @@ pub enum WindowMode {
11021104
/// the window's logical size may be different from its physical size.
11031105
/// If you want to avoid that behavior, you can use the [`WindowResolution::set_scale_factor_override`] function
11041106
/// or the [`WindowResolution::with_scale_factor_override`] builder method to set the scale factor to 1.0.
1105-
BorderlessFullscreen,
1106-
/// The window should be in "true"/"legacy" Fullscreen mode.
1107+
BorderlessFullscreen(MonitorSelection),
1108+
/// The window should be in "true"/"legacy" Fullscreen mode on the given [`MonitorSelection`].
11071109
///
11081110
/// When setting this, the operating system will be requested to use the
11091111
/// **closest** resolution available for the current monitor to match as
11101112
/// closely as possible the window's physical size.
11111113
/// After that, the window's physical size will be modified to match
11121114
/// that monitor resolution, and the logical size will follow based on the
11131115
/// scale factor, see [`WindowResolution`].
1114-
SizedFullscreen,
1115-
/// The window should be in "true"/"legacy" Fullscreen mode.
1116+
SizedFullscreen(MonitorSelection),
1117+
/// The window should be in "true"/"legacy" Fullscreen mode on the given [`MonitorSelection`].
11161118
///
11171119
/// When setting this, the operating system will be requested to use the
11181120
/// **biggest** resolution available for the current monitor.
@@ -1124,7 +1126,7 @@ pub enum WindowMode {
11241126
/// the window's logical size may be different from its physical size.
11251127
/// If you want to avoid that behavior, you can use the [`WindowResolution::set_scale_factor_override`] function
11261128
/// or the [`WindowResolution::with_scale_factor_override`] builder method to set the scale factor to 1.0.
1127-
Fullscreen,
1129+
Fullscreen(MonitorSelection),
11281130
}
11291131

11301132
/// Specifies where a [`Window`] should appear relative to other overlapping windows (on top or under) .

crates/bevy_winit/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,24 @@ use bevy_app::{App, Last, Plugin};
2424
use bevy_ecs::prelude::*;
2525
#[allow(deprecated)]
2626
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
27-
pub use system::create_windows;
2827
use system::{changed_windows, despawn_windows};
28+
pub use system::{create_monitors, create_windows};
2929
pub use winit::event_loop::EventLoopProxy;
3030
pub use winit_config::*;
3131
pub use winit_event::*;
3232
pub use winit_windows::*;
3333

3434
use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers};
3535
use crate::state::winit_runner;
36+
use crate::winit_monitors::WinitMonitors;
3637

3738
pub mod accessibility;
3839
mod converters;
3940
mod state;
4041
mod system;
4142
mod winit_config;
4243
pub mod winit_event;
44+
mod winit_monitors;
4345
mod winit_windows;
4446

4547
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
@@ -113,6 +115,7 @@ impl<T: Event> Plugin for WinitPlugin<T> {
113115
}
114116

115117
app.init_non_send_resource::<WinitWindows>()
118+
.init_resource::<WinitMonitors>()
116119
.init_resource::<WinitSettings>()
117120
.add_event::<WinitEvent>()
118121
.set_runner(winit_runner::<T>)
@@ -181,4 +184,8 @@ pub type CreateWindowParams<'w, 's, F = ()> = (
181184
NonSendMut<'w, AccessKitAdapters>,
182185
ResMut<'w, WinitActionRequestHandlers>,
183186
Res<'w, AccessibilityRequested>,
187+
Res<'w, WinitMonitors>,
184188
);
189+
190+
/// The parameters of the [`create_monitors`] system.
191+
pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);

crates/bevy_winit/src/state.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ use bevy_window::{
3535
use bevy_window::{PrimaryWindow, RawHandleWrapper};
3636

3737
use crate::accessibility::AccessKitAdapters;
38-
use crate::system::CachedWindow;
38+
use crate::system::{create_monitors, CachedWindow};
3939
use crate::{
40-
converters, create_windows, AppSendEvent, CreateWindowParams, EventLoopProxyWrapper,
41-
UpdateMode, WinitEvent, WinitSettings, WinitWindows,
40+
converters, create_windows, AppSendEvent, CreateMonitorParams, CreateWindowParams,
41+
EventLoopProxyWrapper, UpdateMode, WinitEvent, WinitSettings, WinitWindows,
4242
};
4343

4444
/// Persistent state that is used to run the [`App`] according to the current
@@ -401,10 +401,13 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
401401
}
402402

403403
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
404+
let mut create_monitor = SystemState::<CreateMonitorParams>::from_world(self.world_mut());
404405
// create any new windows
405406
// (even if app did not update, some may have been created by plugin setup)
406407
let mut create_window =
407408
SystemState::<CreateWindowParams<Added<Window>>>::from_world(self.world_mut());
409+
create_monitors(event_loop, create_monitor.get_mut(self.world_mut()));
410+
create_monitor.apply(self.world_mut());
408411
create_windows(event_loop, create_window.get_mut(self.world_mut()));
409412
create_window.apply(self.world_mut());
410413

@@ -475,6 +478,7 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
475478
mut adapters,
476479
mut handlers,
477480
accessibility_requested,
481+
monitors,
478482
) = create_window.get_mut(self.world_mut());
479483

480484
let winit_window = winit_windows.create_window(
@@ -484,6 +488,7 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
484488
&mut adapters,
485489
&mut handlers,
486490
&accessibility_requested,
491+
&monitors,
487492
);
488493

489494
let wrapper = RawHandleWrapper::new(winit_window).unwrap();

0 commit comments

Comments
 (0)