Skip to content

Commit

Permalink
Support setting position of new window on x11, macos and windows
Browse files Browse the repository at this point in the history
Co-authored-by: osspial@gmail.com
Co-authored-by: murarth@gmail.com
  • Loading branch information
xixixao committed Mar 1, 2021
1 parent 3571dcd commit 58e6f94
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
- 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_outer_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.

# 0.24.0 (2020-12-09)

Expand Down
18 changes: 18 additions & 0 deletions src/platform_impl/linux/x11/util/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions src/platform_impl/linux/x11/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub use self::{

use std::{
mem::{self, MaybeUninit},
ops::BitAnd,
os::raw::*,
ptr,
};
Expand All @@ -39,6 +40,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
}
}

pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
{
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,
Expand Down
15 changes: 13 additions & 2 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ impl UnownedWindow {
.min_inner_size
.map(|size| size.to_physical::<u32>(scale_factor).into());

let position = window_attrs
.outer_position
.map(|position| position.to_physical::<i32>(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
Expand Down Expand Up @@ -211,8 +215,8 @@ impl UnownedWindow {
(xconn.xlib.XCreateWindow)(
xconn.display,
root,
0,
0,
position.map_or(0, |p: PhysicalPosition<i32>| p.x as c_int),
position.map_or(0, |p: PhysicalPosition<i32>| p.y as c_int),
dimensions.0 as c_uint,
dimensions.1 as c_uint,
0,
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand Down
35 changes: 23 additions & 12 deletions src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.outer_position {
Some(position) => {
let logical = 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))
}
};

Expand Down Expand Up @@ -249,8 +259,9 @@ fn create_window(
if !pl_attrs.has_shadow {
ns_window.setHasShadow_(NO);
}

ns_window.center();
if attrs.outer_position.is_none() {
ns_window.center();
}
ns_window
});
pool.drain();
Expand Down Expand Up @@ -496,15 +507,7 @@ 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),
);
let dummy = NSRect::new(window_position(position), NSSize::new(0f64, 0f64));
unsafe {
util::set_frame_top_left_point_async(*self.ns_window, dummy.origin);
}
Expand Down Expand Up @@ -1210,3 +1213,11 @@ unsafe fn set_max_inner_size<V: NSWindow + Copy>(window: V, mut max_size: Logica
window.setFrame_display_(current_rect, NO)
}
}

// Converts from top-left coordinates to bottom-left coordinate system
fn window_position(position: LogicalPosition<f64>) -> NSPoint {
NSPoint::new(
position.x,
CGDisplay::main().pixels_high() as f64 - position.y,
)
}
4 changes: 4 additions & 0 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,10 @@ unsafe fn init<T: 'static>(
force_window_active(win.window.0);
}

if let Some(position) = attributes.outer_position {
win.set_outer_position(position);
}

Ok(win)
}

Expand Down
27 changes: 27 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ pub struct WindowAttributes {
/// The default is `None`.
pub max_inner_size: Option<Size>,

/// 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 window will be positioned such that it fits on screen, maintaining
/// set `inner_size` if any.
///
/// See [`Window::set_outer_position`] for details.
///
/// [`Window::set_outer_position`]: crate::window::Window::set_outer_position
pub outer_position: Option<Position>,

/// Whether the window is resizable or not.
///
/// The default is `true`.
Expand Down Expand Up @@ -170,6 +185,7 @@ impl Default for WindowAttributes {
inner_size: None,
min_inner_size: None,
max_inner_size: None,
outer_position: None,
resizable: true,
title: "winit window".to_owned(),
maximized: false,
Expand Down Expand Up @@ -223,6 +239,17 @@ impl WindowBuilder {
self
}

/// Sets a desired initial position for the window.
///
/// See [`WindowAttributes::outer_position`] for details.
///
/// [`WindowAttributes::outer_position`]: crate::window::WindowAttributes::outer_position
#[inline]
pub fn with_outer_position<P: Into<Position>>(mut self, position: P) -> Self {
self.window.outer_position = Some(position.into());
self
}

/// Sets whether the window is resizable or not.
///
/// See [`Window::set_resizable`] for details.
Expand Down

0 comments on commit 58e6f94

Please sign in to comment.