mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
Expose winit's MonitorHandle
(#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 #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 <pascal@technocreatives.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Pascal Hertleif <killercup@gmail.com>
This commit is contained in:
parent
897625c899
commit
3360b45153
12 changed files with 426 additions and 65 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -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
|
||||
|
|
|
@ -17,6 +17,7 @@ use bevy_a11y::Focus;
|
|||
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod monitor;
|
||||
mod raw_handle;
|
||||
mod system;
|
||||
mod window;
|
||||
|
@ -25,6 +26,7 @@ pub use crate::raw_handle::*;
|
|||
|
||||
pub use cursor::*;
|
||||
pub use event::*;
|
||||
pub use monitor::*;
|
||||
pub use system::*;
|
||||
pub use window::*;
|
||||
|
||||
|
|
69
crates/bevy_window/src/monitor.rs
Normal file
69
crates/bevy_window/src/monitor.rs
Normal file
|
@ -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,
|
||||
}
|
|
@ -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`].
|
||||
|
@ -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
|
||||
|
@ -1102,8 +1104,8 @@ 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
|
||||
|
@ -1111,8 +1113,8 @@ pub enum WindowMode {
|
|||
/// 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.
|
||||
|
@ -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) .
|
||||
|
|
|
@ -24,8 +24,8 @@ 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::*;
|
||||
|
@ -33,6 +33,7 @@ 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;
|
||||
|
@ -40,6 +41,7 @@ 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
|
||||
|
@ -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>)
|
||||
|
@ -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>);
|
||||
|
|
|
@ -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
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
|
|
@ -8,8 +8,8 @@ use bevy_ecs::{
|
|||
};
|
||||
use bevy_utils::tracing::{error, info, warn};
|
||||
use bevy_window::{
|
||||
ClosingWindow, RawHandleWrapper, Window, WindowClosed, WindowClosing, WindowCreated,
|
||||
WindowMode, WindowResized, WindowWrapper,
|
||||
ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed,
|
||||
WindowClosing, WindowCreated, WindowMode, WindowResized, WindowWrapper,
|
||||
};
|
||||
|
||||
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
|
||||
|
@ -18,18 +18,22 @@ use winit::event_loop::ActiveEventLoop;
|
|||
use bevy_app::AppExit;
|
||||
use bevy_ecs::prelude::EventReader;
|
||||
use bevy_ecs::query::With;
|
||||
use bevy_ecs::system::Res;
|
||||
use bevy_math::{IVec2, UVec2};
|
||||
#[cfg(target_os = "ios")]
|
||||
use winit::platform::ios::WindowExtIOS;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
use crate::state::react_to_resize;
|
||||
use crate::winit_monitors::WinitMonitors;
|
||||
use crate::{
|
||||
converters::{
|
||||
self, convert_enabled_buttons, convert_window_level, convert_window_theme,
|
||||
convert_winit_theme,
|
||||
},
|
||||
get_best_videomode, get_fitting_videomode, CreateWindowParams, WinitWindows,
|
||||
get_best_videomode, get_fitting_videomode, select_monitor, CreateMonitorParams,
|
||||
CreateWindowParams, WinitWindows,
|
||||
};
|
||||
|
||||
/// Creates new windows on the [`winit`] backend for each entity with a newly-added
|
||||
|
@ -48,6 +52,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
|||
mut adapters,
|
||||
mut handlers,
|
||||
accessibility_requested,
|
||||
monitors,
|
||||
): SystemParamItem<CreateWindowParams<F>>,
|
||||
) {
|
||||
for (entity, mut window, handle_holder) in &mut created_windows {
|
||||
|
@ -68,6 +73,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
|||
&mut adapters,
|
||||
&mut handlers,
|
||||
&accessibility_requested,
|
||||
&monitors,
|
||||
);
|
||||
|
||||
if let Some(theme) = winit_window.theme() {
|
||||
|
@ -118,6 +124,69 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Synchronize available monitors as reported by [`winit`] with [`Monitor`] entities in the world.
|
||||
pub fn create_monitors(
|
||||
event_loop: &ActiveEventLoop,
|
||||
(mut commands, mut monitors): SystemParamItem<CreateMonitorParams>,
|
||||
) {
|
||||
let primary_monitor = event_loop.primary_monitor();
|
||||
let mut seen_monitors = vec![false; monitors.monitors.len()];
|
||||
|
||||
'outer: for monitor in event_loop.available_monitors() {
|
||||
for (idx, (m, _)) in monitors.monitors.iter().enumerate() {
|
||||
if &monitor == m {
|
||||
seen_monitors[idx] = true;
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
let size = monitor.size();
|
||||
let position = monitor.position();
|
||||
|
||||
let entity = commands
|
||||
.spawn(Monitor {
|
||||
name: monitor.name(),
|
||||
physical_height: size.height,
|
||||
physical_width: size.width,
|
||||
physical_position: IVec2::new(position.x, position.y),
|
||||
refresh_rate_millihertz: monitor.refresh_rate_millihertz(),
|
||||
scale_factor: monitor.scale_factor(),
|
||||
video_modes: monitor
|
||||
.video_modes()
|
||||
.map(|v| {
|
||||
let size = v.size();
|
||||
VideoMode {
|
||||
physical_size: UVec2::new(size.width, size.height),
|
||||
bit_depth: v.bit_depth(),
|
||||
refresh_rate_millihertz: v.refresh_rate_millihertz(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.id();
|
||||
|
||||
if primary_monitor.as_ref() == Some(&monitor) {
|
||||
commands.entity(entity).insert(PrimaryMonitor);
|
||||
}
|
||||
|
||||
seen_monitors.push(true);
|
||||
monitors.monitors.push((monitor, entity));
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
monitors.monitors.retain(|(_m, entity)| {
|
||||
if seen_monitors[idx] {
|
||||
idx += 1;
|
||||
true
|
||||
} else {
|
||||
info!("Monitor removed {:?}", entity);
|
||||
commands.entity(*entity).despawn();
|
||||
idx += 1;
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn despawn_windows(
|
||||
closing: Query<Entity, With<ClosingWindow>>,
|
||||
|
@ -178,6 +247,7 @@ pub struct CachedWindow {
|
|||
pub(crate) fn changed_windows(
|
||||
mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed<Window>>,
|
||||
winit_windows: NonSendMut<WinitWindows>,
|
||||
monitors: Res<WinitMonitors>,
|
||||
mut window_resized: EventWriter<WindowResized>,
|
||||
) {
|
||||
for (entity, mut window, mut cache) in &mut changed_windows {
|
||||
|
@ -191,26 +261,44 @@ pub(crate) fn changed_windows(
|
|||
|
||||
if window.mode != cache.window.mode {
|
||||
let new_mode = match window.mode {
|
||||
WindowMode::BorderlessFullscreen => {
|
||||
Some(Some(winit::window::Fullscreen::Borderless(None)))
|
||||
WindowMode::BorderlessFullscreen(monitor_selection) => {
|
||||
Some(Some(winit::window::Fullscreen::Borderless(select_monitor(
|
||||
&monitors,
|
||||
winit_window.primary_monitor(),
|
||||
winit_window.current_monitor(),
|
||||
&monitor_selection,
|
||||
))))
|
||||
}
|
||||
mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => {
|
||||
if let Some(current_monitor) = winit_window.current_monitor() {
|
||||
let videomode = match mode {
|
||||
WindowMode::Fullscreen => get_best_videomode(¤t_monitor),
|
||||
WindowMode::SizedFullscreen => get_fitting_videomode(
|
||||
¤t_monitor,
|
||||
window.width() as u32,
|
||||
window.height() as u32,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
mode @ (WindowMode::Fullscreen(_) | WindowMode::SizedFullscreen(_)) => {
|
||||
let videomode = match mode {
|
||||
WindowMode::Fullscreen(monitor_selection) => get_best_videomode(
|
||||
&select_monitor(
|
||||
&monitors,
|
||||
winit_window.primary_monitor(),
|
||||
winit_window.current_monitor(),
|
||||
&monitor_selection,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Could not find monitor for {:?}", monitor_selection)
|
||||
}),
|
||||
),
|
||||
WindowMode::SizedFullscreen(monitor_selection) => get_fitting_videomode(
|
||||
&select_monitor(
|
||||
&monitors,
|
||||
winit_window.primary_monitor(),
|
||||
winit_window.current_monitor(),
|
||||
&monitor_selection,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Could not find monitor for {:?}", monitor_selection)
|
||||
}),
|
||||
window.width() as u32,
|
||||
window.height() as u32,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Some(Some(winit::window::Fullscreen::Exclusive(videomode)))
|
||||
} else {
|
||||
warn!("Could not determine current monitor, ignoring exclusive fullscreen request for window {:?}", window.title);
|
||||
None
|
||||
}
|
||||
Some(Some(winit::window::Fullscreen::Exclusive(videomode)))
|
||||
}
|
||||
WindowMode::Windowed => Some(None),
|
||||
};
|
||||
|
@ -336,7 +424,7 @@ pub(crate) fn changed_windows(
|
|||
if let Some(position) = crate::winit_window_position(
|
||||
&window.position,
|
||||
&window.resolution,
|
||||
winit_window.available_monitors(),
|
||||
&monitors,
|
||||
winit_window.primary_monitor(),
|
||||
winit_window.current_monitor(),
|
||||
) {
|
||||
|
|
35
crates/bevy_winit/src/winit_monitors.rs
Normal file
35
crates/bevy_winit/src/winit_monitors.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use winit::monitor::MonitorHandle;
|
||||
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::system::Resource;
|
||||
|
||||
/// Stores [`winit`] monitors and their corresponding entities
|
||||
///
|
||||
/// # Known Issues
|
||||
///
|
||||
/// On some platforms, physically disconnecting a monitor might result in a
|
||||
/// panic in [`winit`]'s loop. This will lead to a crash in the bevy app. See
|
||||
/// [13669] for investigations and discussions.
|
||||
///
|
||||
/// [13669]: https://github.com/bevyengine/bevy/pull/13669
|
||||
#[derive(Resource, Debug, Default)]
|
||||
pub struct WinitMonitors {
|
||||
/// Stores [`winit`] monitors and their corresponding entities
|
||||
// We can't use a `BtreeMap` here because clippy complains about using `MonitorHandle` as a key
|
||||
// on some platforms. Using a `Vec` is fine because we don't expect to have a large number of
|
||||
// monitors and avoids having to audit the code for `MonitorHandle` equality.
|
||||
pub(crate) monitors: Vec<(MonitorHandle, Entity)>,
|
||||
}
|
||||
|
||||
impl WinitMonitors {
|
||||
pub fn nth(&self, n: usize) -> Option<MonitorHandle> {
|
||||
self.monitors.get(n).map(|(monitor, _)| monitor.clone())
|
||||
}
|
||||
|
||||
pub fn find_entity(&self, entity: Entity) -> Option<MonitorHandle> {
|
||||
self.monitors
|
||||
.iter()
|
||||
.find(|(_, e)| *e == entity)
|
||||
.map(|(monitor, _)| monitor.clone())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,8 @@ use bevy_ecs::entity::Entity;
|
|||
use bevy_ecs::entity::EntityHashMap;
|
||||
use bevy_utils::{tracing::warn, HashMap};
|
||||
use bevy_window::{
|
||||
CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution, WindowWrapper,
|
||||
CursorGrabMode, MonitorSelection, Window, WindowMode, WindowPosition, WindowResolution,
|
||||
WindowWrapper,
|
||||
};
|
||||
|
||||
use winit::{
|
||||
|
@ -14,6 +15,7 @@ use winit::{
|
|||
window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, Window as WinitWindow, WindowId},
|
||||
};
|
||||
|
||||
use crate::winit_monitors::WinitMonitors;
|
||||
use crate::{
|
||||
accessibility::{
|
||||
prepare_accessibility_for_window, AccessKitAdapters, WinitActionRequestHandlers,
|
||||
|
@ -39,6 +41,7 @@ pub struct WinitWindows {
|
|||
|
||||
impl WinitWindows {
|
||||
/// Creates a `winit` window and associates it with our entity.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_window(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
|
@ -47,6 +50,7 @@ impl WinitWindows {
|
|||
adapters: &mut AccessKitAdapters,
|
||||
handlers: &mut WinitActionRequestHandlers,
|
||||
accessibility_requested: &AccessibilityRequested,
|
||||
monitors: &WinitMonitors,
|
||||
) -> &WindowWrapper<WinitWindow> {
|
||||
let mut winit_window_attributes = WinitWindow::default_attributes();
|
||||
|
||||
|
@ -55,31 +59,49 @@ impl WinitWindows {
|
|||
winit_window_attributes = winit_window_attributes.with_visible(false);
|
||||
|
||||
winit_window_attributes = match window.mode {
|
||||
WindowMode::BorderlessFullscreen => winit_window_attributes
|
||||
.with_fullscreen(Some(Fullscreen::Borderless(event_loop.primary_monitor()))),
|
||||
mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => {
|
||||
if let Some(primary_monitor) = event_loop.primary_monitor() {
|
||||
let videomode = match mode {
|
||||
WindowMode::Fullscreen => get_best_videomode(&primary_monitor),
|
||||
WindowMode::SizedFullscreen => get_fitting_videomode(
|
||||
&primary_monitor,
|
||||
window.width() as u32,
|
||||
window.height() as u32,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
WindowMode::BorderlessFullscreen(monitor_selection) => winit_window_attributes
|
||||
.with_fullscreen(Some(Fullscreen::Borderless(select_monitor(
|
||||
monitors,
|
||||
event_loop.primary_monitor(),
|
||||
None,
|
||||
&monitor_selection,
|
||||
)))),
|
||||
mode @ (WindowMode::Fullscreen(_) | WindowMode::SizedFullscreen(_)) => {
|
||||
let videomode = match mode {
|
||||
WindowMode::Fullscreen(monitor_selection) => get_best_videomode(
|
||||
&select_monitor(
|
||||
monitors,
|
||||
event_loop.primary_monitor(),
|
||||
None,
|
||||
&monitor_selection,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Could not find monitor for {:?}", monitor_selection)
|
||||
}),
|
||||
),
|
||||
WindowMode::SizedFullscreen(monitor_selection) => get_fitting_videomode(
|
||||
&select_monitor(
|
||||
monitors,
|
||||
event_loop.primary_monitor(),
|
||||
None,
|
||||
&monitor_selection,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Could not find monitor for {:?}", monitor_selection)
|
||||
}),
|
||||
window.width() as u32,
|
||||
window.height() as u32,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
winit_window_attributes.with_fullscreen(Some(Fullscreen::Exclusive(videomode)))
|
||||
} else {
|
||||
warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title);
|
||||
winit_window_attributes
|
||||
}
|
||||
winit_window_attributes.with_fullscreen(Some(Fullscreen::Exclusive(videomode)))
|
||||
}
|
||||
WindowMode::Windowed => {
|
||||
if let Some(position) = winit_window_position(
|
||||
&window.position,
|
||||
&window.resolution,
|
||||
event_loop.available_monitors(),
|
||||
monitors,
|
||||
event_loop.primary_monitor(),
|
||||
None,
|
||||
) {
|
||||
|
@ -354,7 +376,7 @@ pub(crate) fn attempt_grab(winit_window: &WinitWindow, grab_mode: CursorGrabMode
|
|||
pub fn winit_window_position(
|
||||
position: &WindowPosition,
|
||||
resolution: &WindowResolution,
|
||||
mut available_monitors: impl Iterator<Item = MonitorHandle>,
|
||||
monitors: &WinitMonitors,
|
||||
primary_monitor: Option<MonitorHandle>,
|
||||
current_monitor: Option<MonitorHandle>,
|
||||
) -> Option<PhysicalPosition<i32>> {
|
||||
|
@ -364,17 +386,12 @@ pub fn winit_window_position(
|
|||
None
|
||||
}
|
||||
WindowPosition::Centered(monitor_selection) => {
|
||||
use bevy_window::MonitorSelection::*;
|
||||
let maybe_monitor = match monitor_selection {
|
||||
Current => {
|
||||
if current_monitor.is_none() {
|
||||
warn!("Can't select current monitor on window creation or cannot find current monitor!");
|
||||
}
|
||||
current_monitor
|
||||
}
|
||||
Primary => primary_monitor,
|
||||
Index(n) => available_monitors.nth(*n),
|
||||
};
|
||||
let maybe_monitor = select_monitor(
|
||||
monitors,
|
||||
primary_monitor,
|
||||
current_monitor,
|
||||
monitor_selection,
|
||||
);
|
||||
|
||||
if let Some(monitor) = maybe_monitor {
|
||||
let screen_size = monitor.size();
|
||||
|
@ -410,3 +427,25 @@ pub fn winit_window_position(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects a monitor based on the given [`MonitorSelection`].
|
||||
pub fn select_monitor(
|
||||
monitors: &WinitMonitors,
|
||||
primary_monitor: Option<MonitorHandle>,
|
||||
current_monitor: Option<MonitorHandle>,
|
||||
monitor_selection: &MonitorSelection,
|
||||
) -> Option<MonitorHandle> {
|
||||
use bevy_window::MonitorSelection::*;
|
||||
|
||||
match monitor_selection {
|
||||
Current => {
|
||||
if current_monitor.is_none() {
|
||||
warn!("Can't select current monitor on window creation or cannot find current monitor!");
|
||||
}
|
||||
current_monitor
|
||||
}
|
||||
Primary => primary_monitor,
|
||||
Index(n) => monitors.nth(*n),
|
||||
Entity(entity) => monitors.find_entity(*entity),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -489,6 +489,7 @@ Example | Description
|
|||
[Clear Color](../examples/window/clear_color.rs) | Creates a solid color window
|
||||
[Custom User Event](../examples/window/custom_user_event.rs) | Handles custom user events within the event loop
|
||||
[Low Power](../examples/window/low_power.rs) | Demonstrates settings to reduce power use for bevy applications
|
||||
[Monitor info](../examples/window/monitor_info.rs) | Displays information about available monitors (displays).
|
||||
[Multiple Windows](../examples/window/multiple_windows.rs) | Demonstrates creating multiple windows, and rendering to them
|
||||
[Scale Factor Override](../examples/window/scale_factor_override.rs) | Illustrates how to customize the default window settings
|
||||
[Screenshot](../examples/window/screenshot.rs) | Shows how to save screenshots to disk
|
||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
|||
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resizable: false,
|
||||
mode: WindowMode::BorderlessFullscreen,
|
||||
mode: WindowMode::BorderlessFullscreen(MonitorSelection::Primary),
|
||||
// on iOS, gestures must be enabled.
|
||||
// This doesn't work on Android
|
||||
recognize_rotation_gesture: true,
|
||||
|
|
102
examples/window/monitor_info.rs
Normal file
102
examples/window/monitor_info.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
//! Displays information about available monitors (displays).
|
||||
|
||||
use bevy::render::camera::RenderTarget;
|
||||
use bevy::window::{ExitCondition, WindowMode, WindowRef};
|
||||
use bevy::{prelude::*, window::Monitor};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: None,
|
||||
exit_condition: ExitCondition::DontExit,
|
||||
..default()
|
||||
}))
|
||||
.add_systems(Update, (update, close_on_esc))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct MonitorRef(Entity);
|
||||
|
||||
fn update(
|
||||
mut commands: Commands,
|
||||
monitors_added: Query<(Entity, &Monitor), Added<Monitor>>,
|
||||
mut monitors_removed: RemovedComponents<Monitor>,
|
||||
monitor_refs: Query<(Entity, &MonitorRef)>,
|
||||
) {
|
||||
for (entity, monitor) in monitors_added.iter() {
|
||||
// Spawn a new window on each monitor
|
||||
let name = monitor.name.clone().unwrap_or_else(|| "<no name>".into());
|
||||
let size = format!("{}x{}px", monitor.physical_height, monitor.physical_width);
|
||||
let hz = monitor
|
||||
.refresh_rate_millihertz
|
||||
.map(|x| format!("{}Hz", x as f32 / 1000.0))
|
||||
.unwrap_or_else(|| "<unknown>".into());
|
||||
let position = format!(
|
||||
"x={} y={}",
|
||||
monitor.physical_position.x, monitor.physical_position.y
|
||||
);
|
||||
let scale = format!("{:.2}", monitor.scale_factor);
|
||||
|
||||
let window = commands
|
||||
.spawn((
|
||||
Window {
|
||||
title: name.clone(),
|
||||
mode: WindowMode::Fullscreen(MonitorSelection::Entity(entity)),
|
||||
position: WindowPosition::Centered(MonitorSelection::Entity(entity)),
|
||||
..default()
|
||||
},
|
||||
MonitorRef(entity),
|
||||
))
|
||||
.id();
|
||||
|
||||
let camera = commands
|
||||
.spawn(Camera2dBundle {
|
||||
camera: Camera {
|
||||
target: RenderTarget::Window(WindowRef::Entity(window)),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.id();
|
||||
|
||||
let info_text = format!(
|
||||
"Monitor: {name}\nSize: {size}\nRefresh rate: {hz}\nPosition: {position}\nScale: {scale}\n\n",
|
||||
);
|
||||
commands.spawn((
|
||||
TextBundle::from_section(info_text, default()).with_style(Style {
|
||||
position_type: PositionType::Relative,
|
||||
height: Val::Percent(100.0),
|
||||
width: Val::Percent(100.0),
|
||||
..default()
|
||||
}),
|
||||
TargetCamera(camera),
|
||||
MonitorRef(entity),
|
||||
));
|
||||
}
|
||||
|
||||
// Remove windows for removed monitors
|
||||
for monitor_entity in monitors_removed.read() {
|
||||
for (ref_entity, monitor_ref) in monitor_refs.iter() {
|
||||
if monitor_ref.0 == monitor_entity {
|
||||
commands.entity(ref_entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close_on_esc(
|
||||
mut commands: Commands,
|
||||
focused_windows: Query<(Entity, &Window)>,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
for (window, focus) in focused_windows.iter() {
|
||||
if !focus.focused {
|
||||
continue;
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::Escape) {
|
||||
commands.entity(window).despawn();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue