diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index ca57cde6fac6b..ac43c01b08180 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -16,8 +16,8 @@ pub use windows::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, ReceivedCharacter, - Window, WindowDescriptor, WindowMoved, Windows, + CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection, + ReceivedCharacter, Window, WindowDescriptor, WindowMoved, WindowPosition, Windows, }; } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 6557d414c590d..928c9ccf80eb5 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -259,6 +259,8 @@ pub enum WindowCommand { SetPosition { position: IVec2, }, + /// Modifies the position of the window to be in the center of the current monitor + Center(MonitorSelection), /// Set the window's [`WindowResizeConstraints`] SetResizeConstraints { resize_constraints: WindowResizeConstraints, @@ -416,6 +418,17 @@ impl Window { .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. #[inline] 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. /// /// 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. pub height: f32, - /// The position on the screen that the window will be centered at. - /// - /// If set to `None`, some platform-specific position will be chosen. - pub position: Option, + /// The position on the screen that the window will be placed at. + pub position: WindowPosition, /// Sets minimum and maximum resize limits. pub resize_constraints: WindowResizeConstraints, /// Overrides the window's ratio of physical pixels to logical pixels. @@ -799,7 +836,7 @@ impl Default for WindowDescriptor { title: "app".to_string(), width: 1280., height: 720., - position: None, + position: WindowPosition::Automatic, resize_constraints: WindowResizeConstraints::default(), scale_factor_override: None, present_mode: PresentMode::Fifo, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 88e3f81126bea..ead83c7eb4fcc 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -158,6 +158,31 @@ fn change_window( 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 } => { let window = winit_windows.get_window(id).unwrap(); let constraints = resize_constraints.check_constraints(); diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index e5565092701b7..91550eeffecf7 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,8 +1,8 @@ use bevy_math::IVec2; -use bevy_utils::HashMap; +use bevy_utils::{tracing::warn, HashMap}; use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; use raw_window_handle::HasRawWindowHandle; -use winit::dpi::LogicalSize; +use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition}; #[derive(Debug, Default)] pub struct WinitWindows { @@ -49,30 +49,61 @@ impl WinitWindows { .. } = window_descriptor; - if let Some(position) = position { - if let Some(sf) = scale_factor_override { - winit_window_builder = winit_window_builder.with_position( - winit::dpi::LogicalPosition::new( - position[0] as f64, - position[1] as f64, - ) - .to_physical::(*sf), - ); - } else { - winit_window_builder = - winit_window_builder.with_position(winit::dpi::LogicalPosition::new( - position[0] as f64, - position[1] as f64, - )); + 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 = scale_factor_override.unwrap_or(1.0); + + // Logical to physical window size + let (width, height): (u32, u32) = LogicalSize::new(*width, *height) + .to_physical::(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::(*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 { - winit_window_builder.with_inner_size( - winit::dpi::LogicalSize::new(*width, *height).to_physical::(*sf), - ) - } else { winit_window_builder - .with_inner_size(winit::dpi::LogicalSize::new(*width, *height)) + .with_inner_size(LogicalSize::new(*width, *height).to_physical::(*sf)) + } else { + winit_window_builder.with_inner_size(LogicalSize::new(*width, *height)) } } .with_resizable(window_descriptor.resizable)