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 b45786d commit ffad6f7
Show file tree
Hide file tree
Showing 7 changed files with 183 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 @@ -15,6 +15,7 @@ use bevy_a11y::Focus;

mod cursor;
mod event;
mod monitor;
mod raw_handle;
mod system;
mod window;
Expand All @@ -23,6 +24,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 @@ -905,6 +905,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
15 changes: 15 additions & 0 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod converters;
mod system;
mod winit_config;
pub mod winit_event;
mod winit_monitors;
mod winit_windows;

use std::sync::mpsc::{sync_channel, SyncSender};
Expand Down Expand Up @@ -66,6 +67,8 @@ use winit::{
use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers};

use crate::converters::convert_winit_theme;
use crate::system::create_monitors;
use crate::winit_monitors::WinitMonitors;

/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
/// (for example lifecycle and input events).
Expand Down Expand Up @@ -131,6 +134,7 @@ impl Plugin for WinitPlugin {

app.init_non_send_resource::<WinitWindows>()
.init_resource::<WinitSettings>()
.init_resource::<WinitMonitors>()
.add_event::<WinitEvent>()
.set_runner(winit_runner)
.add_systems(
Expand Down Expand Up @@ -250,8 +254,12 @@ pub type CreateWindowParams<'w, 's, F = ()> = (
NonSendMut<'w, AccessKitAdapters>,
ResMut<'w, WinitActionHandlers>,
Res<'w, AccessibilityRequested>,
Res<'w, WinitMonitors>,
);

/// The parameters of the [`create_monitors`] system.
pub type CreateMonitorParams<'w, 's> = (Commands<'w, 's>, ResMut<'w, WinitMonitors>);

/// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`].
///
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
Expand Down Expand Up @@ -299,6 +307,7 @@ pub fn winit_runner(mut app: App) -> AppExit {

let mut create_window =
SystemState::<CreateWindowParams<Added<Window>>>::from_world(app.world_mut());
let mut create_monitor = SystemState::<CreateMonitorParams>::from_world(app.world_mut());
let mut winit_events = Vec::default();
// set up the event loop
let event_handler = move |event, event_loop: &EventLoopWindowTarget<UserEvent>| {
Expand All @@ -311,6 +320,7 @@ pub fn winit_runner(mut app: App) -> AppExit {
&mut app,
&mut runner_state,
&mut create_window,
&mut create_monitor,
&mut event_writer_system_state,
&mut focused_windows_state,
&mut redraw_event_reader,
Expand Down Expand Up @@ -339,6 +349,7 @@ fn handle_winit_event(
app: &mut App,
runner_state: &mut WinitAppRunnerState,
create_window: &mut SystemState<CreateWindowParams<Added<Window>>>,
create_monitor: &mut SystemState<CreateMonitorParams>,
event_writer_system_state: &mut SystemState<(
EventWriter<WindowResized>,
NonSend<WinitWindows>,
Expand Down Expand Up @@ -366,6 +377,10 @@ fn handle_winit_event(
runner_state.redraw_requested = true;
}

// create any new monitors identified by winit
create_monitors(event_loop, create_monitor.get_mut(app.world_mut()));
create_monitor.apply(app.world_mut());

// create any new windows
// (even if app did not update, some may have been created by plugin setup)
create_windows(event_loop, create_window.get_mut(app.world_mut()));
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,10 +6,10 @@ 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::{
Expand All @@ -18,15 +18,19 @@ use winit::{
};

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::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 @@ -45,6 +49,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
mut adapters,
mut handlers,
accessibility_requested,
monitors,
): SystemParamItem<CreateWindowParams<F>>,
) {
for (entity, mut window) in &mut created_windows {
Expand All @@ -65,6 +70,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 @@ -98,6 +104,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 @@ -138,6 +206,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 @@ -260,7 +329,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)>,
}
18 changes: 15 additions & 3 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use winit::{
monitor::MonitorHandle,
};

use crate::winit_monitors::WinitMonitors;
use crate::{
accessibility::{prepare_accessibility_for_window, AccessKitAdapters, WinitActionHandlers},
converters::{convert_enabled_buttons, convert_window_level, convert_window_theme},
Expand All @@ -35,6 +36,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: &winit::event_loop::EventLoopWindowTarget<crate::UserEvent>,
Expand All @@ -43,6 +45,7 @@ impl WinitWindows {
adapters: &mut AccessKitAdapters,
handlers: &mut WinitActionHandlers,
accessibility_requested: &AccessibilityRequested,
monitors: &WinitMonitors,
) -> &WindowWrapper<winit::window::Window> {
let mut winit_window_builder = winit::window::WindowBuilder::new();

Expand Down Expand Up @@ -77,7 +80,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 @@ -356,7 +359,7 @@ pub(crate) fn attempt_grab(winit_window: &winit::window::Window, grab_mode: Curs
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 @@ -375,7 +378,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 ffad6f7

Please sign in to comment.