Skip to content

Commit 0d634a0

Browse files
authored
Add WindowBuilder::with_outer_position (#1866)
1 parent 86748fb commit 0d634a0

File tree

8 files changed

+108
-16
lines changed

8 files changed

+108
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
1616
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
1717
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
18+
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
1819
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
1920
- On X11, bump `mio` to 0.7.
2021

src/platform_impl/linux/x11/util/hint.rs

+18
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ impl<'a> NormalHints<'a> {
190190
}
191191
}
192192

193+
pub fn get_position(&self) -> Option<(i32, i32)> {
194+
if has_flag(self.size_hints.flags, ffi::PPosition) {
195+
Some((self.size_hints.x as i32, self.size_hints.y as i32))
196+
} else {
197+
None
198+
}
199+
}
200+
201+
pub fn set_position(&mut self, position: Option<(i32, i32)>) {
202+
if let Some((x, y)) = position {
203+
self.size_hints.flags |= ffi::PPosition;
204+
self.size_hints.x = x as c_int;
205+
self.size_hints.y = y as c_int;
206+
} else {
207+
self.size_hints.flags &= !ffi::PPosition;
208+
}
209+
}
210+
193211
// WARNING: This hint is obsolete
194212
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
195213
if let Some((width, height)) = size {

src/platform_impl/linux/x11/util/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub use self::{
2323

2424
use std::{
2525
mem::{self, MaybeUninit},
26+
ops::BitAnd,
2627
os::raw::*,
2728
ptr,
2829
};
@@ -39,6 +40,13 @@ pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
3940
}
4041
}
4142

43+
pub fn has_flag<T>(bitset: T, flag: T) -> bool
44+
where
45+
T: Copy + PartialEq + BitAnd<T, Output = T>,
46+
{
47+
bitset & flag == flag
48+
}
49+
4250
#[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`."]
4351
pub struct Flusher<'a> {
4452
xconn: &'a XConnection,

src/platform_impl/linux/x11/window.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ impl UnownedWindow {
146146
.min_inner_size
147147
.map(|size| size.to_physical::<u32>(scale_factor).into());
148148

149+
let position = window_attrs
150+
.position
151+
.map(|position| position.to_physical::<i32>(scale_factor).into());
152+
149153
let dimensions = {
150154
// x11 only applies constraints when the window is actively resized
151155
// by the user, so we have to manually apply the initial constraints
@@ -211,8 +215,8 @@ impl UnownedWindow {
211215
(xconn.xlib.XCreateWindow)(
212216
xconn.display,
213217
root,
214-
0,
215-
0,
218+
position.map_or(0, |p: PhysicalPosition<i32>| p.x as c_int),
219+
position.map_or(0, |p: PhysicalPosition<i32>| p.y as c_int),
216220
dimensions.0 as c_uint,
217221
dimensions.1 as c_uint,
218222
0,
@@ -344,6 +348,7 @@ impl UnownedWindow {
344348
}
345349

346350
let mut normal_hints = util::NormalHints::new(xconn);
351+
normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y)));
347352
normal_hints.set_size(Some(dimensions));
348353
normal_hints.set_min_size(min_inner_size.map(Into::into));
349354
normal_hints.set_max_size(max_inner_size.map(Into::into));
@@ -439,6 +444,12 @@ impl UnownedWindow {
439444
window
440445
.set_fullscreen_inner(window_attrs.fullscreen.clone())
441446
.map(|flusher| flusher.queue());
447+
448+
if let Some(PhysicalPosition { x, y }) = position {
449+
let shared_state = window.shared_state.get_mut();
450+
451+
shared_state.restore_position = Some((x, y));
452+
}
442453
}
443454
if window_attrs.always_on_top {
444455
window

src/platform_impl/macos/util/mod.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ use std::ops::{BitAnd, Deref};
88
use cocoa::{
99
appkit::{NSApp, NSWindowStyleMask},
1010
base::{id, nil},
11-
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
11+
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSString, NSUInteger},
1212
};
1313
use core_graphics::display::CGDisplay;
1414
use objc::runtime::{Class, Object, Sel, BOOL, YES};
1515

16+
use crate::dpi::LogicalPosition;
1617
use crate::platform_impl::platform::ffi;
1718

1819
// Replace with `!` once stable
@@ -91,6 +92,16 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
9192
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)
9293
}
9394

95+
/// Converts from winit screen-coordinates to macOS screen-coordinates.
96+
/// Winit: top-left is (0, 0) and y increasing downwards
97+
/// macOS: bottom-left is (0, 0) and y increasing upwards
98+
pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
99+
NSPoint::new(
100+
position.x,
101+
CGDisplay::main().pixels_high() as f64 - position.y,
102+
)
103+
}
104+
94105
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
95106
IdRef::new(NSString::alloc(nil).init_str(s))
96107
}

src/platform_impl/macos/window.rs

+15-13
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,17 @@ fn create_window(
166166
}
167167
None => (800.0, 600.0),
168168
};
169-
NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height))
169+
let (left, bottom) = match attrs.position {
170+
Some(position) => {
171+
let logical = util::window_position(position.to_logical(scale_factor));
172+
// macOS wants the position of the bottom left corner,
173+
// but caller is setting the position of top left corner
174+
(logical.x, logical.y - height)
175+
}
176+
// This value is ignored by calling win.center() below
177+
None => (0.0, 0.0),
178+
};
179+
NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height))
170180
}
171181
};
172182

@@ -249,8 +259,9 @@ fn create_window(
249259
if !pl_attrs.has_shadow {
250260
ns_window.setHasShadow_(NO);
251261
}
252-
253-
ns_window.center();
262+
if attrs.position.is_none() {
263+
ns_window.center();
264+
}
254265
ns_window
255266
});
256267
pool.drain();
@@ -496,17 +507,8 @@ impl UnownedWindow {
496507
pub fn set_outer_position(&self, position: Position) {
497508
let scale_factor = self.scale_factor();
498509
let position = position.to_logical(scale_factor);
499-
let dummy = NSRect::new(
500-
NSPoint::new(
501-
position.x,
502-
// While it's true that we're setting the top-left position,
503-
// it still needs to be in a bottom-left coordinate system.
504-
CGDisplay::main().pixels_high() as f64 - position.y,
505-
),
506-
NSSize::new(0f64, 0f64),
507-
);
508510
unsafe {
509-
util::set_frame_top_left_point_async(*self.ns_window, dummy.origin);
511+
util::set_frame_top_left_point_async(*self.ns_window, util::window_position(position));
510512
}
511513
}
512514

src/platform_impl/windows/window.rs

+4
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,10 @@ unsafe fn init<T: 'static>(
832832
force_window_active(win.window.0);
833833
}
834834

835+
if let Some(position) = attributes.position {
836+
win.set_outer_position(position);
837+
}
838+
835839
Ok(win)
836840
}
837841

src/window.rs

+37
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,31 @@ pub struct WindowAttributes {
116116
/// The default is `None`.
117117
pub max_inner_size: Option<Size>,
118118

119+
/// The desired position of the window. If this is `None`, some platform-specific position
120+
/// will be chosen.
121+
///
122+
/// The default is `None`.
123+
///
124+
/// ## Platform-specific
125+
///
126+
/// - **macOS**: The top left corner position of the window content, the window's "inner"
127+
/// position. The window title bar will be placed above it.
128+
/// The window will be positioned such that it fits on screen, maintaining
129+
/// set `inner_size` if any.
130+
/// If you need to precisely position the top left corner of the whole window you have to
131+
/// use [`Window::set_outer_position`] after creating the window.
132+
/// - **Windows**: The top left corner position of the window title bar, the window's "outer"
133+
/// position.
134+
/// There may be a small gap between this position and the window due to the specifics of the
135+
/// Window Manager.
136+
/// - **X11**: The top left corner of the window, the window's "outer" position.
137+
/// - **Others**: Ignored.
138+
///
139+
/// See [`Window::set_outer_position`].
140+
///
141+
/// [`Window::set_outer_position`]: crate::window::Window::set_outer_position
142+
pub position: Option<Position>,
143+
119144
/// Whether the window is resizable or not.
120145
///
121146
/// The default is `true`.
@@ -170,6 +195,7 @@ impl Default for WindowAttributes {
170195
inner_size: None,
171196
min_inner_size: None,
172197
max_inner_size: None,
198+
position: None,
173199
resizable: true,
174200
title: "winit window".to_owned(),
175201
maximized: false,
@@ -223,6 +249,17 @@ impl WindowBuilder {
223249
self
224250
}
225251

252+
/// Sets a desired initial position for the window.
253+
///
254+
/// See [`WindowAttributes::position`] for details.
255+
///
256+
/// [`WindowAttributes::position`]: crate::window::WindowAttributes::position
257+
#[inline]
258+
pub fn with_position<P: Into<Position>>(mut self, position: P) -> Self {
259+
self.window.position = Some(position.into());
260+
self
261+
}
262+
226263
/// Sets whether the window is resizable or not.
227264
///
228265
/// See [`Window::set_resizable`] for details.

0 commit comments

Comments
 (0)