Skip to content

Commit

Permalink
Expose winit's MonitorHandle (#13669)
Browse files Browse the repository at this point in the history
# 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 #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]>
  • Loading branch information
4 people authored Aug 6, 2024
1 parent 897625c commit 3360b45
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 67 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3355,3 +3355,14 @@ panic = "abort"
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
all-features = true
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

[[example]]
name = "monitor_info"
path = "examples/window/monitor_info.rs"
doc-scrape-examples = true

[package.metadata.example.monitor_info]
name = "Monitor info"
description = "Displays information about available monitors (displays)."
category = "Window"
wasm = false
2 changes: 2 additions & 0 deletions crates/bevy_window/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use bevy_a11y::Focus;

mod cursor;
mod event;
mod monitor;
mod raw_handle;
mod system;
mod window;
Expand All @@ -25,6 +26,7 @@ pub use crate::raw_handle::*;

pub use cursor::*;
pub use event::*;
pub use monitor::*;
pub use system::*;
pub use window::*;

Expand Down
69 changes: 69 additions & 0 deletions crates/bevy_window/src/monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use bevy_ecs::component::Component;
use bevy_ecs::prelude::ReflectComponent;
use bevy_math::{IVec2, UVec2};
use bevy_reflect::Reflect;

#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};

/// Represents an available monitor as reported by the user's operating system, which can be used
/// to query information about the display, such as its size, position, and video modes.
///
/// Each monitor corresponds to an entity and can be used to position a monitor using
/// [`crate::window::MonitorSelection::Entity`].
///
/// # Warning
///
/// This component is synchronized with `winit` through `bevy_winit`, but is effectively
/// read-only as `winit` does not support changing monitor properties.
#[derive(Component, Debug, Clone, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Component)]
pub struct Monitor {
/// The name of the monitor
pub name: Option<String>,
/// The height of the monitor in physical pixels
pub physical_height: u32,
/// The width of the monitor in physical pixels
pub physical_width: u32,
/// The position of the monitor in physical pixels
pub physical_position: IVec2,
/// The refresh rate of the monitor in millihertz
pub refresh_rate_millihertz: Option<u32>,
/// The scale factor of the monitor
pub scale_factor: f64,
/// The video modes that the monitor supports
pub video_modes: Vec<VideoMode>,
}

/// A marker component for the primary monitor
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component)]
pub struct PrimaryMonitor;

impl Monitor {
/// Returns the physical size of the monitor in pixels
pub fn physical_size(&self) -> UVec2 {
UVec2::new(self.physical_width, self.physical_height)
}
}

/// Represents a video mode that a monitor supports
#[derive(Debug, Clone, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct VideoMode {
/// The resolution of the video mode
pub physical_size: UVec2,
/// The bit depth of the video mode
pub bit_depth: u16,
/// The refresh rate in millihertz
pub refresh_rate_millihertz: u32,
}
14 changes: 8 additions & 6 deletions crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,8 @@ pub enum MonitorSelection {
Primary,
/// Uses the monitor with the specified index.
Index(usize),
/// Uses a given [`crate::monitor::Monitor`] entity.
Entity(Entity),
}

/// Presentation mode for a [`Window`].
Expand Down Expand Up @@ -1092,7 +1094,7 @@ pub enum WindowMode {
#[default]
Windowed,
/// The window should appear fullscreen by being borderless and using the full
/// size of the screen.
/// size of the screen on the given [`MonitorSelection`].
///
/// When setting this, the window's physical size will be modified to match the size
/// of the current monitor resolution, and the logical size will follow based
Expand All @@ -1102,17 +1104,17 @@ pub enum WindowMode {
/// the window's logical size may be different from its physical size.
/// If you want to avoid that behavior, you can use the [`WindowResolution::set_scale_factor_override`] function
/// or the [`WindowResolution::with_scale_factor_override`] builder method to set the scale factor to 1.0.
BorderlessFullscreen,
/// The window should be in "true"/"legacy" Fullscreen mode.
BorderlessFullscreen(MonitorSelection),
/// The window should be in "true"/"legacy" Fullscreen mode on the given [`MonitorSelection`].
///
/// When setting this, the operating system will be requested to use the
/// **closest** resolution available for the current monitor to match as
/// closely as possible the window's physical size.
/// After that, the window's physical size will be modified to match
/// that monitor resolution, and the logical size will follow based on the
/// scale factor, see [`WindowResolution`].
SizedFullscreen,
/// The window should be in "true"/"legacy" Fullscreen mode.
SizedFullscreen(MonitorSelection),
/// The window should be in "true"/"legacy" Fullscreen mode on the given [`MonitorSelection`].
///
/// When setting this, the operating system will be requested to use the
/// **biggest** resolution available for the current monitor.
Expand All @@ -1124,7 +1126,7 @@ pub enum WindowMode {
/// the window's logical size may be different from its physical size.
/// If you want to avoid that behavior, you can use the [`WindowResolution::set_scale_factor_override`] function
/// or the [`WindowResolution::with_scale_factor_override`] builder method to set the scale factor to 1.0.
Fullscreen,
Fullscreen(MonitorSelection),
}

/// Specifies where a [`Window`] should appear relative to other overlapping windows (on top or under) .
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,24 @@ use bevy_app::{App, Last, Plugin};
use bevy_ecs::prelude::*;
#[allow(deprecated)]
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
pub use system::create_windows;
use system::{changed_windows, despawn_windows};
pub use system::{create_monitors, create_windows};
pub use winit::event_loop::EventLoopProxy;
pub use winit_config::*;
pub use winit_event::*;
pub use winit_windows::*;

use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers};
use crate::state::winit_runner;
use crate::winit_monitors::WinitMonitors;

pub mod accessibility;
mod converters;
mod state;
mod system;
mod winit_config;
pub mod winit_event;
mod winit_monitors;
mod winit_windows;

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

app.init_non_send_resource::<WinitWindows>()
.init_resource::<WinitMonitors>()
.init_resource::<WinitSettings>()
.add_event::<WinitEvent>()
.set_runner(winit_runner::<T>)
Expand Down Expand Up @@ -181,4 +184,8 @@ pub type CreateWindowParams<'w, 's, F = ()> = (
NonSendMut<'w, AccessKitAdapters>,
ResMut<'w, WinitActionRequestHandlers>,
Res<'w, AccessibilityRequested>,
Res<'w, WinitMonitors>,
);

/// The parameters of the [`create_monitors`] system.
pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);
11 changes: 8 additions & 3 deletions crates/bevy_winit/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ use bevy_window::{
use bevy_window::{PrimaryWindow, RawHandleWrapper};

use crate::accessibility::AccessKitAdapters;
use crate::system::CachedWindow;
use crate::system::{create_monitors, CachedWindow};
use crate::{
converters, create_windows, AppSendEvent, CreateWindowParams, EventLoopProxyWrapper,
UpdateMode, WinitEvent, WinitSettings, WinitWindows,
converters, create_windows, AppSendEvent, CreateMonitorParams, CreateWindowParams,
EventLoopProxyWrapper, UpdateMode, WinitEvent, WinitSettings, WinitWindows,
};

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

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

Expand Down Expand Up @@ -475,6 +478,7 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
mut adapters,
mut handlers,
accessibility_requested,
monitors,
) = create_window.get_mut(self.world_mut());

let winit_window = winit_windows.create_window(
Expand All @@ -484,6 +488,7 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
&mut adapters,
&mut handlers,
&accessibility_requested,
&monitors,
);

let wrapper = RawHandleWrapper::new(winit_window).unwrap();
Expand Down
Loading

0 comments on commit 3360b45

Please sign in to comment.