Support monitor selection for all window modes. (#5878)

# Objective
Support monitor selection for all window modes.
Fixes #5875.

## Changelog

* Moved `MonitorSelection` out of `WindowPosition::Centered`, into `WindowDescriptor`.
* `WindowPosition::At` is now relative to the monitor instead of being in 'desktop space'.
* Renamed `MonitorSelection::Number` to `MonitorSelection::Index` for clarity.
* Added `WindowMode` to the prelude.
* `Window::set_position` is now relative to a monitor and takes a `MonitorSelection` as argument.

## Migration Guide

`MonitorSelection` was moved out of `WindowPosition::Centered`, into `WindowDescriptor`.
`MonitorSelection::Number` was renamed to `MonitorSelection::Index`.
```rust
// Before
.insert_resource(WindowDescriptor {
    position: WindowPosition::Centered(MonitorSelection::Number(1)),
    ..default()
})
// After
.insert_resource(WindowDescriptor {
    monitor: MonitorSelection::Index(1),
    position: WindowPosition::Centered,
    ..default()
})
```
`Window::set_position` now takes a `MonitorSelection` as argument.
```rust
window.set_position(MonitorSelection::Current, position);
```

Co-authored-by: devil-ira <justthecooldude@gmail.com>
This commit is contained in:
ira 2022-09-06 14:45:44 +00:00
parent 092bb71bcf
commit 28c16b9713
4 changed files with 141 additions and 107 deletions

View file

@ -17,7 +17,8 @@ pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection, CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection,
ReceivedCharacter, Window, WindowDescriptor, WindowMoved, WindowPosition, Windows, ReceivedCharacter, Window, WindowDescriptor, WindowMode, WindowMoved, WindowPosition,
Windows,
}; };
} }

View file

@ -272,11 +272,12 @@ pub enum WindowCommand {
SetMinimized { SetMinimized {
minimized: bool, minimized: bool,
}, },
/// Set the window's position on the screen. /// Set the window's position on the selected monitor.
SetPosition { SetPosition {
monitor_selection: MonitorSelection,
position: IVec2, position: IVec2,
}, },
/// Modifies the position of the window to be in the center of the current monitor /// Sets the position of the window to be in the center of the selected monitor.
Center(MonitorSelection), Center(MonitorSelection),
/// Set the window's [`WindowResizeConstraints`] /// Set the window's [`WindowResizeConstraints`]
SetResizeConstraints { SetResizeConstraints {
@ -416,12 +417,9 @@ impl Window {
.push(WindowCommand::SetMinimized { minimized }); .push(WindowCommand::SetMinimized { minimized });
} }
/// Modifies the position of the window in physical pixels. /// Sets the `position` of the window on the selected `monitor` in physical pixels.
/// ///
/// Note that the top-left hand corner of the desktop is not necessarily the same as the screen. /// This automatically un-maximizes the window if it's maximized.
/// If the user uses a desktop with multiple monitors, the top-left hand corner of the
/// desktop is the top-left hand corner of the monitor at the top-left of the desktop. This
/// automatically un-maximizes the window if it's maximized.
/// ///
/// # Platform-specific /// # Platform-specific
/// ///
@ -430,9 +428,11 @@ impl Window {
/// - Web: Sets the top-left coordinates relative to the viewport. /// - Web: Sets the top-left coordinates relative to the viewport.
/// - Android / Wayland: Unsupported. /// - Android / Wayland: Unsupported.
#[inline] #[inline]
pub fn set_position(&mut self, position: IVec2) { pub fn set_position(&mut self, monitor: MonitorSelection, position: IVec2) {
self.command_queue self.command_queue.push(WindowCommand::SetPosition {
.push(WindowCommand::SetPosition { position }); monitor_selection: monitor,
position,
});
} }
/// Modifies the position of the window to be in the center of the current monitor /// Modifies the position of the window to be in the center of the current monitor
@ -747,15 +747,17 @@ impl Window {
/// Defines where window should be placed at on creation. /// Defines where window should be placed at on creation.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum WindowPosition { pub enum WindowPosition {
/// Position will be set by the window manager /// The position will be set by the window manager.
Automatic, Automatic,
/// Window will be centered on the selected monitor /// Center the window on the monitor.
/// ///
/// Note that this does not account for window decorations. /// The monitor to center the window on can be selected with the `monitor` field in `WindowDescriptor`.
Centered(MonitorSelection), Centered,
/// The window's top-left corner will be placed at the specified position (in pixels) /// The window's top-left corner will be placed at the specified position in pixels.
/// ///
/// (0,0) represents top-left corner of screen space. /// (0,0) represents top-left corner of the monitor.
///
/// The monitor to position the window on can be selected with the `monitor` field in `WindowDescriptor`.
At(Vec2), At(Vec2),
} }
@ -763,11 +765,13 @@ pub enum WindowPosition {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MonitorSelection { pub enum MonitorSelection {
/// Uses current monitor of the window. /// Uses current monitor of the window.
///
/// Will fall back to the system default if the window has not yet been created.
Current, Current,
/// Uses primary monitor of the system. /// Uses primary monitor of the system.
Primary, Primary,
/// Uses monitor with the specified index. /// Uses monitor with the specified index.
Number(usize), Index(usize),
} }
/// Describes the information needed for creating a window. /// Describes the information needed for creating a window.
@ -789,7 +793,15 @@ pub struct WindowDescriptor {
/// May vary from the physical height due to different pixel density on different monitors. /// May vary from the physical height due to different pixel density on different monitors.
pub height: f32, pub height: f32,
/// The position on the screen that the window will be placed at. /// The position on the screen that the window will be placed at.
///
/// The monitor to place the window on can be selected with the `monitor` field.
///
/// Ignored if `mode` is set to something other than [`WindowMode::Windowed`]
///
/// `WindowPosition::Automatic` will be overridden with `WindowPosition::At(Vec2::ZERO)` if a specific monitor is selected.
pub position: WindowPosition, pub position: WindowPosition,
/// The monitor to place the window on.
pub monitor: MonitorSelection,
/// Sets minimum and maximum resize limits. /// Sets minimum and maximum resize limits.
pub resize_constraints: WindowResizeConstraints, pub resize_constraints: WindowResizeConstraints,
/// Overrides the window's ratio of physical pixels to logical pixels. /// Overrides the window's ratio of physical pixels to logical pixels.
@ -819,6 +831,8 @@ pub struct WindowDescriptor {
/// Sets whether the window locks the cursor inside its borders when the window has focus. /// Sets whether the window locks the cursor inside its borders when the window has focus.
pub cursor_locked: bool, pub cursor_locked: bool,
/// Sets the [`WindowMode`](crate::WindowMode). /// Sets the [`WindowMode`](crate::WindowMode).
///
/// The monitor to go fullscreen on can be selected with the `monitor` field.
pub mode: WindowMode, pub mode: WindowMode,
/// Sets whether the background of the window should be transparent. /// Sets whether the background of the window should be transparent.
/// ///
@ -854,6 +868,7 @@ impl Default for WindowDescriptor {
width: 1280., width: 1280.,
height: 720., height: 720.,
position: WindowPosition::Automatic, position: WindowPosition::Automatic,
monitor: MonitorSelection::Current,
resize_constraints: WindowResizeConstraints::default(), resize_constraints: WindowResizeConstraints::default(),
scale_factor_override: None, scale_factor_override: None,
present_mode: PresentMode::Fifo, present_mode: PresentMode::Fifo,

View file

@ -31,7 +31,7 @@ use bevy_window::{
}; };
use winit::{ use winit::{
dpi::{LogicalSize, PhysicalPosition}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition},
event::{self, DeviceEvent, Event, StartCause, WindowEvent}, event::{self, DeviceEvent, Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
}; };
@ -149,7 +149,7 @@ fn change_window(
let window = winit_windows.get_window(id).unwrap(); let window = winit_windows.get_window(id).unwrap();
let inner_size = window.inner_size().to_logical::<f32>(window.scale_factor()); let inner_size = window.inner_size().to_logical::<f32>(window.scale_factor());
window window
.set_cursor_position(winit::dpi::LogicalPosition::new( .set_cursor_position(LogicalPosition::new(
position.x, position.x,
inner_size.height - position.y, inner_size.height - position.y,
)) ))
@ -163,12 +163,26 @@ fn change_window(
let window = winit_windows.get_window(id).unwrap(); let window = winit_windows.get_window(id).unwrap();
window.set_minimized(minimized); window.set_minimized(minimized);
} }
bevy_window::WindowCommand::SetPosition { position } => { bevy_window::WindowCommand::SetPosition {
monitor_selection,
position,
} => {
let window = winit_windows.get_window(id).unwrap(); let window = winit_windows.get_window(id).unwrap();
window.set_outer_position(PhysicalPosition {
x: position[0], use bevy_window::MonitorSelection::*;
y: position[1], let maybe_monitor = match monitor_selection {
}); Current => window.current_monitor(),
Primary => window.primary_monitor(),
Index(i) => window.available_monitors().nth(i),
};
if let Some(monitor) = maybe_monitor {
let monitor_position = DVec2::from(<(_, _)>::from(monitor.position()));
let position = monitor_position + position.as_dvec2();
window.set_outer_position(LogicalPosition::new(position.x, position.y));
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
}
} }
bevy_window::WindowCommand::Center(monitor_selection) => { bevy_window::WindowCommand::Center(monitor_selection) => {
let window = winit_windows.get_window(id).unwrap(); let window = winit_windows.get_window(id).unwrap();
@ -177,19 +191,20 @@ fn change_window(
let maybe_monitor = match monitor_selection { let maybe_monitor = match monitor_selection {
Current => window.current_monitor(), Current => window.current_monitor(),
Primary => window.primary_monitor(), Primary => window.primary_monitor(),
Number(n) => window.available_monitors().nth(n), Index(i) => window.available_monitors().nth(i),
}; };
if let Some(monitor) = maybe_monitor { if let Some(monitor) = maybe_monitor {
let screen_size = monitor.size(); let monitor_size = monitor.size();
let monitor_position = monitor.position().cast::<f64>();
let window_size = window.outer_size(); let window_size = window.outer_size();
window.set_outer_position(PhysicalPosition { window.set_outer_position(PhysicalPosition {
x: screen_size.width.saturating_sub(window_size.width) as f64 / 2. x: monitor_size.width.saturating_sub(window_size.width) as f64 / 2.
+ monitor.position().x as f64, + monitor_position.x,
y: screen_size.height.saturating_sub(window_size.height) as f64 / 2. y: monitor_size.height.saturating_sub(window_size.height) as f64 / 2.
+ monitor.position().y as f64, + monitor_position.y,
}); });
} else { } else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}"); warn!("Couldn't get monitor selected with: {monitor_selection:?}");
@ -578,12 +593,12 @@ pub fn winit_runner_with(mut app: App) {
} }
} }
event::Event::DeviceEvent { event::Event::DeviceEvent {
event: DeviceEvent::MouseMotion { delta }, event: DeviceEvent::MouseMotion { delta: (x, y) },
.. ..
} => { } => {
let mut mouse_motion_events = app.world.resource_mut::<Events<MouseMotion>>(); let mut mouse_motion_events = app.world.resource_mut::<Events<MouseMotion>>();
mouse_motion_events.send(MouseMotion { mouse_motion_events.send(MouseMotion {
delta: Vec2::new(delta.0 as f32, delta.1 as f32), delta: DVec2 { x, y }.as_vec2(),
}); });
} }
event::Event::Suspended => { event::Event::Suspended => {

View file

@ -1,8 +1,11 @@
use bevy_math::IVec2; use bevy_math::{DVec2, IVec2};
use bevy_utils::{tracing::warn, HashMap}; use bevy_utils::HashMap;
use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; use bevy_window::{MonitorSelection, Window, WindowDescriptor, WindowId, WindowMode};
use raw_window_handle::HasRawWindowHandle; use raw_window_handle::HasRawWindowHandle;
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition}; use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
window::Fullscreen,
};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct WinitWindows { pub struct WinitWindows {
@ -24,86 +27,43 @@ impl WinitWindows {
) -> Window { ) -> Window {
let mut winit_window_builder = winit::window::WindowBuilder::new(); let mut winit_window_builder = winit::window::WindowBuilder::new();
let &WindowDescriptor {
width,
height,
position,
monitor,
scale_factor_override,
..
} = window_descriptor;
let logical_size = LogicalSize::new(width, height);
let monitor = match monitor {
MonitorSelection::Current => None,
MonitorSelection::Primary => event_loop.primary_monitor(),
MonitorSelection::Index(i) => event_loop.available_monitors().nth(i),
};
let selected_or_primary_monitor = monitor.clone().or_else(|| event_loop.primary_monitor());
winit_window_builder = match window_descriptor.mode { winit_window_builder = match window_descriptor.mode {
WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some( WindowMode::BorderlessFullscreen => winit_window_builder
winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), .with_fullscreen(Some(Fullscreen::Borderless(selected_or_primary_monitor))),
WindowMode::Fullscreen => winit_window_builder.with_fullscreen(Some(
Fullscreen::Exclusive(get_best_videomode(&selected_or_primary_monitor.unwrap())),
)), )),
WindowMode::Fullscreen => {
winit_window_builder.with_fullscreen(Some(winit::window::Fullscreen::Exclusive(
get_best_videomode(&event_loop.primary_monitor().unwrap()),
)))
}
WindowMode::SizedFullscreen => winit_window_builder.with_fullscreen(Some( WindowMode::SizedFullscreen => winit_window_builder.with_fullscreen(Some(
winit::window::Fullscreen::Exclusive(get_fitting_videomode( Fullscreen::Exclusive(get_fitting_videomode(
&event_loop.primary_monitor().unwrap(), &selected_or_primary_monitor.unwrap(),
window_descriptor.width as u32, window_descriptor.width as u32,
window_descriptor.height as u32, window_descriptor.height as u32,
)), )),
)), )),
_ => { _ => {
let WindowDescriptor {
width,
height,
position,
scale_factor_override,
..
} = window_descriptor;
use bevy_window::WindowPosition::*;
match position {
Automatic => { /* Window manager will handle position */ }
Centered(monitor_selection) => {
use bevy_window::MonitorSelection::*;
let maybe_monitor = match monitor_selection {
Current => {
warn!("Can't select current monitor on window creation!");
None
}
Primary => event_loop.primary_monitor(),
Number(n) => event_loop.available_monitors().nth(*n),
};
if let Some(monitor) = maybe_monitor {
let screen_size = monitor.size();
let scale_factor = monitor.scale_factor();
// Logical to physical window size
let (width, height): (u32, u32) = LogicalSize::new(*width, *height)
.to_physical::<u32>(scale_factor)
.into();
let position = PhysicalPosition {
x: screen_size.width.saturating_sub(width) as f64 / 2.
+ monitor.position().x as f64,
y: screen_size.height.saturating_sub(height) as f64 / 2.
+ monitor.position().y as f64,
};
winit_window_builder = winit_window_builder.with_position(position);
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
}
}
At(position) => {
if let Some(sf) = scale_factor_override {
winit_window_builder = winit_window_builder.with_position(
LogicalPosition::new(position[0] as f64, position[1] as f64)
.to_physical::<f64>(*sf),
);
} else {
winit_window_builder = winit_window_builder.with_position(
LogicalPosition::new(position[0] as f64, position[1] as f64),
);
}
}
}
if let Some(sf) = scale_factor_override { if let Some(sf) = scale_factor_override {
winit_window_builder winit_window_builder.with_inner_size(logical_size.to_physical::<f64>(sf))
.with_inner_size(LogicalSize::new(*width, *height).to_physical::<f64>(*sf))
} else { } else {
winit_window_builder.with_inner_size(LogicalSize::new(*width, *height)) winit_window_builder.with_inner_size(logical_size)
} }
} }
.with_resizable(window_descriptor.resizable) .with_resizable(window_descriptor.resizable)
@ -155,6 +115,49 @@ impl WinitWindows {
let winit_window = winit_window_builder.build(event_loop).unwrap(); let winit_window = winit_window_builder.build(event_loop).unwrap();
if window_descriptor.mode == WindowMode::Windowed {
use bevy_window::WindowPosition::*;
match position {
Automatic => {
if let Some(monitor) = monitor {
winit_window.set_outer_position(monitor.position());
}
}
Centered => {
if let Some(monitor) = monitor.or_else(|| winit_window.current_monitor()) {
let monitor_position = monitor.position().cast::<f64>();
let size = monitor.size();
// Logical to physical window size
let PhysicalSize::<u32> { width, height } =
logical_size.to_physical(monitor.scale_factor());
let position = PhysicalPosition {
x: size.width.saturating_sub(width) as f64 / 2. + monitor_position.x,
y: size.height.saturating_sub(height) as f64 / 2. + monitor_position.y,
};
winit_window.set_outer_position(position);
}
}
At(position) => {
if let Some(monitor) = monitor.or_else(|| winit_window.current_monitor()) {
let monitor_position = DVec2::from(<(_, _)>::from(monitor.position()));
let position = monitor_position + position.as_dvec2();
if let Some(sf) = scale_factor_override {
winit_window.set_outer_position(
LogicalPosition::new(position.x, position.y).to_physical::<f64>(sf),
);
} else {
winit_window
.set_outer_position(LogicalPosition::new(position.x, position.y));
}
}
}
}
}
if window_descriptor.cursor_locked { if window_descriptor.cursor_locked {
match winit_window.set_cursor_grab(true) { match winit_window.set_cursor_grab(true) {
Ok(_) | Err(winit::error::ExternalError::NotSupported(_)) => {} Ok(_) | Err(winit::error::ExternalError::NotSupported(_)) => {}