diff --git a/CHANGELOG.md b/CHANGELOG.md index d155e71469..7e972bedb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - On macOS, fix objects captured by the event loop closure not being dropped on panic. - On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme. - On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements. +- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11. - Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland. - On X11, bump `mio` to 0.7. diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index 809f3063b0..222d81748e 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -190,6 +190,24 @@ impl<'a> NormalHints<'a> { } } + pub fn get_position(&self) -> Option<(i32, i32)> { + if has_flag(self.size_hints.flags, ffi::PPosition) { + Some((self.size_hints.x as i32, self.size_hints.y as i32)) + } else { + None + } + } + + pub fn set_position(&mut self, position: Option<(i32, i32)>) { + if let Some((x, y)) = position { + self.size_hints.flags |= ffi::PPosition; + self.size_hints.x = x as c_int; + self.size_hints.y = y as c_int; + } else { + self.size_hints.flags &= !ffi::PPosition; + } + } + // WARNING: This hint is obsolete pub fn set_size(&mut self, size: Option<(u32, u32)>) { if let Some((width, height)) = size { diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 4bd1420655..df3d89138a 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -23,6 +23,7 @@ pub use self::{ use std::{ mem::{self, MaybeUninit}, + ops::BitAnd, os::raw::*, ptr, }; @@ -39,6 +40,13 @@ pub fn maybe_change(field: &mut Option, value: T) -> bool { } } +pub fn has_flag(bitset: T, flag: T) -> bool +where + T: Copy + PartialEq + BitAnd, +{ + bitset & flag == flag +} + #[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."] pub struct Flusher<'a> { xconn: &'a XConnection, diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 3f720f0b4a..26d3b5dd1e 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -146,6 +146,10 @@ impl UnownedWindow { .min_inner_size .map(|size| size.to_physical::(scale_factor).into()); + let position = window_attrs + .position + .map(|position| position.to_physical::(scale_factor).into()); + let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints @@ -211,8 +215,8 @@ impl UnownedWindow { (xconn.xlib.XCreateWindow)( xconn.display, root, - 0, - 0, + position.map_or(0, |p: PhysicalPosition| p.x as c_int), + position.map_or(0, |p: PhysicalPosition| p.y as c_int), dimensions.0 as c_uint, dimensions.1 as c_uint, 0, @@ -344,6 +348,7 @@ impl UnownedWindow { } let mut normal_hints = util::NormalHints::new(xconn); + normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y))); normal_hints.set_size(Some(dimensions)); normal_hints.set_min_size(min_inner_size.map(Into::into)); normal_hints.set_max_size(max_inner_size.map(Into::into)); @@ -439,6 +444,12 @@ impl UnownedWindow { window .set_fullscreen_inner(window_attrs.fullscreen.clone()) .map(|flusher| flusher.queue()); + + if let Some(PhysicalPosition { x, y }) = position { + let shared_state = window.shared_state.get_mut(); + + shared_state.restore_position = Some((x, y)); + } } if window_attrs.always_on_top { window diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 39a97c9832..a6187a50ce 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -8,11 +8,12 @@ use std::ops::{BitAnd, Deref}; use cocoa::{ appkit::{NSApp, NSWindowStyleMask}, base::{id, nil}, - foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger}, + foundation::{NSAutoreleasePool, NSPoint, NSRect, NSString, NSUInteger}, }; use core_graphics::display::CGDisplay; use objc::runtime::{Class, Object, Sel, BOOL, YES}; +use crate::dpi::LogicalPosition; use crate::platform_impl::platform::ffi; // Replace with `!` once stable @@ -91,6 +92,16 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) } +/// Converts from winit screen-coordinates to macOS screen-coordinates. +/// Winit: top-left is (0, 0) and y increasing downwards +/// macOS: bottom-left is (0, 0) and y increasing upwards +pub fn window_position(position: LogicalPosition) -> NSPoint { + NSPoint::new( + position.x, + CGDisplay::main().pixels_high() as f64 - position.y, + ) +} + pub unsafe fn ns_string_id_ref(s: &str) -> IdRef { IdRef::new(NSString::alloc(nil).init_str(s)) } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index b57fd5ff3c..4fa85f7de6 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -166,7 +166,17 @@ fn create_window( } None => (800.0, 600.0), }; - NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) + let (left, bottom) = match attrs.position { + Some(position) => { + let logical = util::window_position(position.to_logical(scale_factor)); + // macOS wants the position of the bottom left corner, + // but caller is setting the position of top left corner + (logical.x, logical.y - height) + } + // This value is ignored by calling win.center() below + None => (0.0, 0.0), + }; + NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height)) } }; @@ -249,8 +259,9 @@ fn create_window( if !pl_attrs.has_shadow { ns_window.setHasShadow_(NO); } - - ns_window.center(); + if attrs.position.is_none() { + ns_window.center(); + } ns_window }); pool.drain(); @@ -496,17 +507,8 @@ impl UnownedWindow { pub fn set_outer_position(&self, position: Position) { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); - let dummy = NSRect::new( - NSPoint::new( - position.x, - // While it's true that we're setting the top-left position, - // it still needs to be in a bottom-left coordinate system. - CGDisplay::main().pixels_high() as f64 - position.y, - ), - NSSize::new(0f64, 0f64), - ); unsafe { - util::set_frame_top_left_point_async(*self.ns_window, dummy.origin); + util::set_frame_top_left_point_async(*self.ns_window, util::window_position(position)); } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 775fa7b402..21249593eb 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -832,6 +832,10 @@ unsafe fn init( force_window_active(win.window.0); } + if let Some(position) = attributes.position { + win.set_outer_position(position); + } + Ok(win) } diff --git a/src/window.rs b/src/window.rs index fd7f295c15..079c711240 100644 --- a/src/window.rs +++ b/src/window.rs @@ -116,6 +116,31 @@ pub struct WindowAttributes { /// The default is `None`. pub max_inner_size: Option, + /// The desired position of the window. If this is `None`, some platform-specific position + /// will be chosen. + /// + /// The default is `None`. + /// + /// ## Platform-specific + /// + /// - **macOS**: The top left corner position of the window content, the window's "inner" + /// position. The window title bar will be placed above it. + /// The window will be positioned such that it fits on screen, maintaining + /// set `inner_size` if any. + /// If you need to precisely position the top left corner of the whole window you have to + /// use [`Window::set_outer_position`] after creating the window. + /// - **Windows**: The top left corner position of the window title bar, the window's "outer" + /// position. + /// There may be a small gap between this position and the window due to the specifics of the + /// Window Manager. + /// - **X11**: The top left corner of the window, the window's "outer" position. + /// - **Others**: Ignored. + /// + /// See [`Window::set_outer_position`]. + /// + /// [`Window::set_outer_position`]: crate::window::Window::set_outer_position + pub position: Option, + /// Whether the window is resizable or not. /// /// The default is `true`. @@ -170,6 +195,7 @@ impl Default for WindowAttributes { inner_size: None, min_inner_size: None, max_inner_size: None, + position: None, resizable: true, title: "winit window".to_owned(), maximized: false, @@ -223,6 +249,17 @@ impl WindowBuilder { self } + /// Sets a desired initial position for the window. + /// + /// See [`WindowAttributes::position`] for details. + /// + /// [`WindowAttributes::position`]: crate::window::WindowAttributes::position + #[inline] + pub fn with_position>(mut self, position: P) -> Self { + self.window.position = Some(position.into()); + self + } + /// Sets whether the window is resizable or not. /// /// See [`Window::set_resizable`] for details.