Add option to make window "always on top" (#528)

* macOS: always_on_top

* Windows: always_on_top

* X11: always_on_top

* Stub set_always_on_top on other platforms
This commit is contained in:
Francesca Frangipane 2018-05-20 10:24:05 -04:00 committed by GitHub
parent f6d26df64d
commit f51f7c0ca8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 161 additions and 4 deletions

View file

@ -20,6 +20,7 @@
- On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the window. - On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the window.
- On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first. - On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first.
- Implemented `MouseCursor::NoneCursor` on Windows. - Implemented `MouseCursor::NoneCursor` on Windows.
- Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11.
# Version 0.14.0 (2018-05-09) # Version 0.14.0 (2018-05-09)

View file

@ -441,6 +441,11 @@ pub struct WindowAttributes {
/// The default is `true`. /// The default is `true`.
pub decorations: bool, pub decorations: bool,
/// Whether the window should always be on top of other windows.
///
/// The default is `false`.
pub always_on_top: bool,
/// The window icon. /// The window icon.
/// ///
/// The default is `None`. /// The default is `None`.
@ -464,6 +469,7 @@ impl Default for WindowAttributes {
visible: true, visible: true,
transparent: false, transparent: false,
decorations: true, decorations: true,
always_on_top: false,
window_icon: None, window_icon: None,
multitouch: false, multitouch: false,
} }

View file

@ -323,6 +323,11 @@ impl Window {
// N/A // N/A
} }
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline] #[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) { pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A // N/A

View file

@ -543,6 +543,11 @@ impl Window {
// N/A // N/A
} }
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline] #[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) { pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A // N/A

View file

@ -370,6 +370,11 @@ impl Window {
// N/A // N/A
} }
#[inline]
pub fn set_always_on_top(&self, _always_on_top: bool) {
// N/A
}
#[inline] #[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) { pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// N/A // N/A

View file

@ -302,6 +302,14 @@ impl Window {
} }
} }
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
match self {
&Window::X(ref w) => w.set_always_on_top(always_on_top),
&Window::Wayland(_) => (),
}
}
#[inline] #[inline]
pub fn set_window_icon(&self, window_icon: Option<Icon>) { pub fn set_window_icon(&self, window_icon: Option<Icon>) {
match self { match self {

View file

@ -299,8 +299,15 @@ impl Window2 {
}.queue(); }.queue();
// These properties must be set after mapping // These properties must be set after mapping
window.set_maximized_inner(window_attrs.maximized).queue(); if window_attrs.maximized {
window.set_fullscreen_inner(window_attrs.fullscreen.clone()).queue(); window.set_maximized_inner(window_attrs.maximized).queue();
}
if window_attrs.fullscreen.is_some() {
window.set_fullscreen_inner(window_attrs.fullscreen.clone()).queue();
}
if window_attrs.always_on_top {
window.set_always_on_top_inner(window_attrs.always_on_top).queue();
}
if window_attrs.visible { if window_attrs.visible {
unsafe { unsafe {
@ -498,6 +505,27 @@ impl Window2 {
self.invalidate_cached_frame_extents(); self.invalidate_cached_frame_extents();
} }
fn set_always_on_top_inner(&self, always_on_top: bool) -> util::Flusher {
let xconn = &self.x.display;
let above_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_ABOVE\0") }
.expect("Failed to call XInternAtom (_NET_WM_STATE_ABOVE)");
Window2::set_netwm(
xconn,
self.x.window,
self.x.root,
(above_atom as c_long, 0, 0, 0),
always_on_top.into(),
)
}
pub fn set_always_on_top(&self, always_on_top: bool) {
self.set_always_on_top_inner(always_on_top)
.flush()
.expect("Failed to set always-on-top state");
}
fn set_icon_inner(&self, icon: Icon) -> util::Flusher { fn set_icon_inner(&self, icon: Icon) -> util::Flusher {
let xconn = &self.x.display; let xconn = &self.x.display;

View file

@ -1,6 +1,6 @@
// TODO: Upstream these // TODO: Upstream these
#![allow(non_snake_case, non_upper_case_globals)] #![allow(dead_code, non_snake_case, non_upper_case_globals)]
use cocoa::base::{class, id}; use cocoa::base::{class, id};
use cocoa::foundation::{NSInteger, NSUInteger}; use cocoa::foundation::{NSInteger, NSUInteger};
@ -72,3 +72,36 @@ impl NSMutableAttributedString for id {
msg_send![self, length] msg_send![self, length]
} }
} }
pub const kCGBaseWindowLevelKey: NSInteger = 0;
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
pub const kCGNormalWindowLevelKey: NSInteger = 4;
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
pub const kCGDockWindowLevelKey: NSInteger = 7;
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
pub const kCGStatusWindowLevelKey: NSInteger = 9;
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
pub const kCGHelpWindowLevelKey: NSInteger = 16;
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
pub const kCGCursorWindowLevelKey: NSInteger = 19;
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
pub enum NSWindowLevel {
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
}

View file

@ -25,6 +25,7 @@ use std::sync::Weak;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use super::events_loop::{EventsLoop, Shared}; use super::events_loop::{EventsLoop, Shared};
use platform::platform::ffi;
use platform::platform::util; use platform::platform::util;
use platform::platform::view::{new_view, set_ime_spot}; use platform::platform::view::{new_view, set_ime_spot};
@ -775,6 +776,10 @@ impl Window2 {
window.setMovableByWindowBackground_(YES); window.setMovableByWindowBackground_(YES);
} }
if attrs.always_on_top {
let _: () = msg_send![*window, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel];
}
if let Some((x, y)) = pl_attrs.resize_increments { if let Some((x, y)) = pl_attrs.resize_increments {
if x >= 1 && y >= 1 { if x >= 1 && y >= 1 {
let size = NSSize::new(x as _, y as _); let size = NSSize::new(x as _, y as _);
@ -1066,6 +1071,18 @@ impl Window2 {
} }
} }
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
unsafe {
let level = if always_on_top {
ffi::NSWindowLevel::NSFloatingWindowLevel
} else {
ffi::NSWindowLevel::NSNormalWindowLevel
};
let _: () = msg_send![*self.window, setLevel:level];
}
}
#[inline] #[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) { pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's // macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's

View file

@ -650,6 +650,38 @@ impl Window {
} }
} }
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
if let Ok(mut window_state) = self.window_state.lock() {
if window_state.attributes.always_on_top == always_on_top {
return;
}
let window = self.window.clone();
self.events_loop_proxy.execute_in_thread(move |_| {
let insert_after = if always_on_top {
winuser::HWND_TOPMOST
} else {
winuser::HWND_NOTOPMOST
};
unsafe {
winuser::SetWindowPos(
window.0,
insert_after,
0,
0,
0,
0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
winuser::UpdateWindow(window.0);
}
});
window_state.attributes.always_on_top = always_on_top;
}
}
#[inline] #[inline]
pub fn get_current_monitor(&self) -> RootMonitorId { pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId { RootMonitorId {
@ -765,7 +797,7 @@ unsafe fn init(
}; };
// computing the style and extended style of the window // computing the style and extended style of the window
let (ex_style, style) = if !window.decorations { let (mut ex_style, style) = if !window.decorations {
(winuser::WS_EX_APPWINDOW, (winuser::WS_EX_APPWINDOW,
//winapi::WS_POPUP is incompatible with winapi::WS_CHILD //winapi::WS_POPUP is incompatible with winapi::WS_CHILD
if pl_attribs.parent.is_some() { if pl_attribs.parent.is_some() {
@ -780,6 +812,10 @@ unsafe fn init(
winuser::WS_OVERLAPPEDWINDOW | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN) winuser::WS_OVERLAPPEDWINDOW | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN)
}; };
if window.always_on_top {
ex_style |= winuser::WS_EX_TOPMOST;
}
// adjusting the window coordinates using the style // adjusting the window coordinates using the style
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style); winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);

View file

@ -92,6 +92,13 @@ impl WindowBuilder {
self self
} }
/// Sets whether or not the window will always be on top of other windows.
#[inline]
pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder {
self.window.always_on_top = always_on_top;
self
}
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar. /// corner of the titlebar.
/// ///
@ -363,6 +370,12 @@ impl Window {
self.window.set_decorations(decorations) self.window.set_decorations(decorations)
} }
/// Change whether or not the window will always be on top of other windows.
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
self.window.set_always_on_top(always_on_top)
}
/// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left /// Sets the window icon. On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar. /// corner of the titlebar.
/// ///