diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 8c97ace0b934b..2498d66d73a5f 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use super::{WindowDescriptor, WindowId}; -use bevy_math::Vec2; +use bevy_math::{IVec2, Vec2}; /// A window event that is sent whenever a window has been resized. #[derive(Debug, Clone)] @@ -89,3 +89,10 @@ pub enum FileDragAndDrop { HoveredFileCancelled { id: WindowId }, } + +/// An event that is sent when a window is repositioned in physical pixels. +#[derive(Debug, Clone)] +pub struct WindowMoved { + pub id: WindowId, + pub position: IVec2, +} diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 55c46bef8fd6f..d01b5f751e640 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -12,7 +12,7 @@ pub use windows::*; pub mod prelude { pub use crate::{ CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ReceivedCharacter, Window, - WindowDescriptor, Windows, + WindowDescriptor, WindowMoved, Windows, }; } @@ -47,6 +47,7 @@ impl Plugin for WindowPlugin { .add_event::() .add_event::() .add_event::() + .add_event::() .init_resource::(); if self.add_primary_window { diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 0bcb3c6f73068..fc9d800cdaeff 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,4 +1,4 @@ -use bevy_math::Vec2; +use bevy_math::{IVec2, Vec2}; use bevy_utils::Uuid; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -54,6 +54,7 @@ pub struct Window { requested_height: f32, physical_width: u32, physical_height: u32, + position: Option, scale_factor_override: Option, backend_scale_factor: f64, title: String, @@ -106,6 +107,12 @@ pub enum WindowCommand { SetMaximized { maximized: bool, }, + SetMinimized { + minimized: bool, + }, + SetPosition { + position: IVec2, + }, } /// Defines the way a window is displayed @@ -127,11 +134,13 @@ impl Window { physical_width: u32, physical_height: u32, scale_factor: f64, + position: Option, ) -> Self { Window { id, requested_width: window_descriptor.width, requested_height: window_descriptor.height, + position, physical_width, physical_height, scale_factor_override: window_descriptor.scale_factor_override, @@ -199,12 +208,46 @@ impl Window { self.physical_height } + /// The window's client position in physical pixels. + #[inline] + pub fn position(&self) -> Option { + self.position + } + #[inline] pub fn set_maximized(&mut self, maximized: bool) { self.command_queue .push(WindowCommand::SetMaximized { maximized }); } + /// Sets the window to minimized or back. + /// + /// # Platform-specific + /// - iOS / Android / Web: Unsupported. + /// - Wayland: Un-minimize is unsupported. + #[inline] + pub fn set_minimized(&mut self, minimized: bool) { + self.command_queue + .push(WindowCommand::SetMinimized { minimized }); + } + + /// Modifies the position of the window in physical pixels. + /// + /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen. 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 + /// + /// - iOS: Can only be called on the main thread. Sets the top left coordinates of the window in the screen space coordinate system. + /// - Web: Sets the top-left coordinates relative to the viewport. + /// - Android / Wayland: Unsupported. + #[inline] + pub fn set_position(&mut self, position: IVec2) { + self.command_queue + .push(WindowCommand::SetPosition { position }) + } + /// Request the OS to resize the window such the the client area matches the /// specified width and height. #[allow(clippy::float_cmp)] @@ -250,6 +293,12 @@ impl Window { self.physical_height = physical_height; } + #[allow(missing_docs)] + #[inline] + pub fn update_actual_position_from_backend(&mut self, position: IVec2) { + self.position = Some(position); + } + /// The ratio of physical pixels to logical pixels /// /// `physical_pixels = logical_pixels * scale_factor` diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index f41b1186b6a91..0f5d082a3739a 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -12,14 +12,15 @@ pub use winit_windows::*; use bevy_app::{prelude::*, AppExit, ManualEventReader}; use bevy_ecs::{IntoSystem, Resources, World}; -use bevy_math::Vec2; +use bevy_math::{ivec2, Vec2}; use bevy_utils::tracing::{error, trace, warn}; use bevy_window::{ CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ReceivedCharacter, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowFocused, - WindowResized, WindowScaleFactorChanged, Windows, + WindowMoved, WindowResized, WindowScaleFactorChanged, Windows, }; use winit::{ + dpi::PhysicalPosition, event::{self, DeviceEvent, Event, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, }; @@ -127,6 +128,17 @@ fn change_window(_: &mut World, resources: &mut Resources) { let window = winit_windows.get_window(id).unwrap(); window.set_maximized(maximized) } + bevy_window::WindowCommand::SetMinimized { minimized } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_minimized(minimized) + } + bevy_window::WindowCommand::SetPosition { position } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_outer_position(PhysicalPosition { + x: position[0], + y: position[1], + }); + } } } } @@ -423,6 +435,15 @@ pub fn winit_runner_with(mut app: App, mut event_loop: EventLoop<()>) { app.resources.get_mut::>().unwrap(); events.send(FileDragAndDrop::HoveredFileCancelled { id: window_id }); } + WindowEvent::Moved(position) => { + let position = ivec2(position.x, position.y); + window.update_actual_position_from_backend(position); + let mut events = app.resources.get_mut::>().unwrap(); + events.send(WindowMoved { + id: window_id, + position, + }); + } _ => {} } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index d32b4d2e41233..736b98e762305 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,3 +1,4 @@ +use bevy_math::IVec2; use bevy_utils::HashMap; use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; @@ -110,16 +111,20 @@ impl WinitWindows { } } + let position = winit_window + .outer_position() + .ok() + .map(|position| IVec2::new(position.x, position.y)); let inner_size = winit_window.inner_size(); let scale_factor = winit_window.scale_factor(); self.windows.insert(winit_window.id(), winit_window); - Window::new( window_id, &window_descriptor, inner_size.width, inner_size.height, scale_factor, + position, ) }