Add option to center a window (#4999)

# Objective
- Fixes #4993 

## Solution

- ~~Add `centered` property to `WindowDescriptor`~~
- Add `WindowPosition` enum
- `WindowDescriptor.position` is now `WindowPosition` instead of `Option<Vec2>`
- Add `center_window` function to `Window`

## Migration Guide
- If using `WindowDescriptor`, replace `position: None` with `position: WindowPosition::Default` and `position: Some(vec2)`  with `WindowPosition::At(vec2)`.

I'm not sure if this is the best approach, so feel free to give any feedback.
Also I'm not sure how `Option`s should be handled in `bevy_winit/src/lib.rs:161`.

Also, on window creation we can't (or at least I couldn't) get `outer_size`, so this doesn't include decorations in calculations.
This commit is contained in:
LoipesMas 2022-07-04 13:04:14 +00:00
parent c0f807ce38
commit 49da4e741d
4 changed files with 122 additions and 29 deletions

View file

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

View file

@ -259,6 +259,8 @@ pub enum WindowCommand {
SetPosition { SetPosition {
position: IVec2, position: IVec2,
}, },
/// Modifies the position of the window to be in the center of the current monitor
Center(MonitorSelection),
/// Set the window's [`WindowResizeConstraints`] /// Set the window's [`WindowResizeConstraints`]
SetResizeConstraints { SetResizeConstraints {
resize_constraints: WindowResizeConstraints, resize_constraints: WindowResizeConstraints,
@ -416,6 +418,17 @@ impl Window {
.push(WindowCommand::SetPosition { position }); .push(WindowCommand::SetPosition { position });
} }
/// Modifies the position of the window to be in the center of the current monitor
///
/// # Platform-specific
/// - iOS: Can only be called on the main thread.
/// - Web / Android / Wayland: Unsupported.
#[inline]
pub fn center_window(&mut self, monitor_selection: MonitorSelection) {
self.command_queue
.push(WindowCommand::Center(monitor_selection));
}
/// Modifies the minimum and maximum window bounds for resizing in logical pixels. /// Modifies the minimum and maximum window bounds for resizing in logical pixels.
#[inline] #[inline]
pub fn set_resize_constraints(&mut self, resize_constraints: WindowResizeConstraints) { pub fn set_resize_constraints(&mut self, resize_constraints: WindowResizeConstraints) {
@ -714,6 +727,32 @@ impl Window {
} }
} }
/// Defines where window should be placed at on creation.
#[derive(Debug, Clone, Copy)]
pub enum WindowPosition {
/// Position will be set by the window manager
Automatic,
/// Window will be centered on the selected monitor
///
/// Note that this does not account for window decorations.
Centered(MonitorSelection),
/// The window's top-left corner will be placed at the specified position (in pixels)
///
/// (0,0) represents top-left corner of screen space.
At(Vec2),
}
/// Defines which monitor to use.
#[derive(Debug, Clone, Copy)]
pub enum MonitorSelection {
/// Uses current monitor of the window.
Current,
/// Uses primary monitor of the system.
Primary,
/// Uses monitor with the specified index.
Number(usize),
}
/// Describes the information needed for creating a window. /// Describes the information needed for creating a window.
/// ///
/// This should be set up before adding the [`WindowPlugin`](crate::WindowPlugin). /// This should be set up before adding the [`WindowPlugin`](crate::WindowPlugin).
@ -732,10 +771,8 @@ 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 centered at. /// The position on the screen that the window will be placed at.
/// pub position: WindowPosition,
/// If set to `None`, some platform-specific position will be chosen.
pub position: Option<Vec2>,
/// 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.
@ -799,7 +836,7 @@ impl Default for WindowDescriptor {
title: "app".to_string(), title: "app".to_string(),
width: 1280., width: 1280.,
height: 720., height: 720.,
position: None, position: WindowPosition::Automatic,
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

@ -162,6 +162,31 @@ fn change_window(
y: position[1], y: position[1],
}); });
} }
bevy_window::WindowCommand::Center(monitor_selection) => {
let window = winit_windows.get_window(id).unwrap();
use bevy_window::MonitorSelection::*;
let maybe_monitor = match monitor_selection {
Current => window.current_monitor(),
Primary => window.primary_monitor(),
Number(n) => window.available_monitors().nth(n),
};
if let Some(monitor) = maybe_monitor {
let screen_size = monitor.size();
let window_size = window.outer_size();
window.set_outer_position(PhysicalPosition {
x: screen_size.width.saturating_sub(window_size.width) as f64 / 2.
+ monitor.position().x as f64,
y: screen_size.height.saturating_sub(window_size.height) as f64 / 2.
+ monitor.position().y as f64,
});
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
}
}
bevy_window::WindowCommand::SetResizeConstraints { resize_constraints } => { bevy_window::WindowCommand::SetResizeConstraints { resize_constraints } => {
let window = winit_windows.get_window(id).unwrap(); let window = winit_windows.get_window(id).unwrap();
let constraints = resize_constraints.check_constraints(); let constraints = resize_constraints.check_constraints();

View file

@ -1,8 +1,8 @@
use bevy_math::IVec2; use bevy_math::IVec2;
use bevy_utils::HashMap; use bevy_utils::{tracing::warn, HashMap};
use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode};
use raw_window_handle::HasRawWindowHandle; use raw_window_handle::HasRawWindowHandle;
use winit::dpi::LogicalSize; use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct WinitWindows { pub struct WinitWindows {
@ -49,30 +49,61 @@ impl WinitWindows {
.. ..
} = window_descriptor; } = window_descriptor;
if let Some(position) = position { use bevy_window::WindowPosition::*;
if let Some(sf) = scale_factor_override { match position {
winit_window_builder = winit_window_builder.with_position( Automatic => { /* Window manager will handle position */ }
winit::dpi::LogicalPosition::new( Centered(monitor_selection) => {
position[0] as f64, use bevy_window::MonitorSelection::*;
position[1] as f64, let maybe_monitor = match monitor_selection {
) Current => {
.to_physical::<f64>(*sf), warn!("Can't select current monitor on window creation!");
); None
} else { }
winit_window_builder = Primary => event_loop.primary_monitor(),
winit_window_builder.with_position(winit::dpi::LogicalPosition::new( Number(n) => event_loop.available_monitors().nth(*n),
position[0] as f64, };
position[1] as f64,
)); if let Some(monitor) = maybe_monitor {
let screen_size = monitor.size();
let scale_factor = scale_factor_override.unwrap_or(1.0);
// 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.with_inner_size(
winit::dpi::LogicalSize::new(*width, *height).to_physical::<f64>(*sf),
)
} else {
winit_window_builder winit_window_builder
.with_inner_size(winit::dpi::LogicalSize::new(*width, *height)) .with_inner_size(LogicalSize::new(*width, *height).to_physical::<f64>(*sf))
} else {
winit_window_builder.with_inner_size(LogicalSize::new(*width, *height))
} }
} }
.with_resizable(window_descriptor.resizable) .with_resizable(window_descriptor.resizable)