Skip to content

Commit

Permalink
Expose winit's MonitorHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
tychedelia committed Jun 4, 2024
1 parent ab2add6 commit 3e1378d
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 8 deletions.
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
63 changes: 63 additions & 0 deletions crates/bevy_window/src/monitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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 a monitor attached to the system, which can be used to create windows.
///
/// 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,
}
2 changes: 2 additions & 0 deletions crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,8 @@ pub enum MonitorSelection {
Primary,
/// Uses the monitor with the specified index.
Index(usize),
/// Uses a given [`Monitor`] entity.
Entity(Entity),
}

/// Presentation mode for a [`Window`].
Expand Down
79 changes: 74 additions & 5 deletions crates/bevy_winit/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,30 @@ use bevy_ecs::{
removal_detection::RemovedComponents,
system::{NonSendMut, Query, SystemParamItem},
};
use bevy_utils::tracing::{error, info, warn};
use bevy_utils::tracing::{error, info, trace, warn};
use bevy_window::{
ClosingWindow, RawHandleWrapper, Window, WindowClosed, WindowClosing, WindowCreated,
WindowMode, WindowResized,
ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed,
WindowClosing, WindowCreated, WindowMode, WindowResized,
};

use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event_loop::ActiveEventLoop;

use bevy_ecs::query::With;
use bevy_ecs::system::Res;
use bevy_math::{IVec2, UVec2};
#[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, CreateMonitorParams, CreateWindowParams,
WinitWindows,
};

/// Creates new windows on the [`winit`] backend for each entity with a newly-added
Expand All @@ -44,6 +48,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 {
Expand All @@ -64,6 +69,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
&mut adapters,
&mut handlers,
&accessibility_requested,
&monitors,
);

if let Some(theme) = winit_window.theme() {
Expand Down Expand Up @@ -101,6 +107,68 @@ pub fn create_windows<F: QueryFilter + 'static>(
}
}

pub fn create_monitors(
event_loop: &EventLoopWindowTarget<crate::UserEvent>,
(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);
}

monitors.monitors.push((monitor, entity));
seen_monitors.push(true);
}

let mut idx = 0;
monitors.monitors.retain(|(m, entity)| {
if seen_monitors[idx] {
idx += 1;
true
} else {
trace!("Despawning monitor {:?}", m.name());
commands.entity(*entity).despawn();
idx += 1;
false
}
});
}

pub(crate) fn despawn_windows(
closing: Query<Entity, With<ClosingWindow>>,
mut closed: RemovedComponents<Window>,
Expand Down Expand Up @@ -141,6 +209,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 {
Expand Down Expand Up @@ -296,7 +365,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(),
) {
Expand Down
12 changes: 12 additions & 0 deletions crates/bevy_winit/src/winit_monitors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use winit::monitor::MonitorHandle;

use bevy_ecs::entity::Entity;
use bevy_ecs::system::Resource;

/// Stores [`winit`] monitors and their corresponding entities
#[derive(Resource, Debug, Default)]
pub struct WinitMonitors {
/// Stores [`winit`] monitors and their corresponding entities
// we can't use a `HashMap` here because `MonitorHandle` doesn't implement `Hash` :(
pub monitors: Vec<(MonitorHandle, Entity)>,
}
17 changes: 14 additions & 3 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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,
Expand All @@ -39,6 +40,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,
Expand Down Expand Up @@ -79,7 +81,7 @@ impl WinitWindows {
if let Some(position) = winit_window_position(
&window.position,
&window.resolution,
event_loop.available_monitors(),
monitors,
event_loop.primary_monitor(),
None,
) {
Expand Down Expand Up @@ -354,7 +356,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>> {
Expand All @@ -373,7 +375,16 @@ pub fn winit_window_position(
current_monitor
}
Primary => primary_monitor,
Index(n) => available_monitors.nth(*n),
Index(n) => monitors
.monitors
.get(*n)
.as_ref()
.map(|(monitor, _)| monitor.clone()),
Entity(entity) => monitors
.monitors
.iter()
.find(|(_, e)| *e == *entity)
.map(|(m, _)| m.clone()),
};

if let Some(monitor) = maybe_monitor {
Expand Down

0 comments on commit 3e1378d

Please sign in to comment.