On Windows and macOS, add API to enable/disable window controls (#2537)

* On Windows and macOS, add API to enable/disable window controls

* fix build

* missing import

* use `WindowButtons` flags

* rename to `[set_]enabled_buttons`

* add example, fix windows impl for minimize

* macOS: Fix button enabling close/minimize while disabling maximized

* Update src/platform_impl/windows/window.rs

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>

* compose the flags on a sep line, use `bool::then`

Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
This commit is contained in:
Amr Bashir 2022-11-29 12:03:51 +02:00 committed by GitHub
parent 28e34c2e1b
commit 94688a62f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 343 additions and 36 deletions

View file

@ -71,7 +71,10 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
Fullscreen, Parent, PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowLevel},
window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons,
WindowLevel,
},
};
/// The Win32 implementation of the main `Window` object.
@ -263,6 +266,44 @@ impl Window {
window_state.window_flags.contains(WindowFlags::RESIZABLE)
}
#[inline]
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let _ = &window;
WindowState::set_window_flags(window_state.lock().unwrap(), window.0, |f| {
f.set(
WindowFlags::MINIMIZABLE,
buttons.contains(WindowButtons::MINIMIZE),
);
f.set(
WindowFlags::MAXIMIZABLE,
buttons.contains(WindowButtons::MAXIMIZE),
);
f.set(
WindowFlags::CLOSABLE,
buttons.contains(WindowButtons::CLOSE),
)
});
});
}
pub fn enabled_buttons(&self) -> WindowButtons {
let mut buttons = WindowButtons::empty();
let window_state = self.window_state_lock();
if window_state.window_flags.contains(WindowFlags::MINIMIZABLE) {
buttons |= WindowButtons::MINIMIZE;
}
if window_state.window_flags.contains(WindowFlags::MAXIMIZABLE) {
buttons |= WindowButtons::MAXIMIZE;
}
if window_state.window_flags.contains(WindowFlags::CLOSABLE) {
buttons |= WindowButtons::CLOSE;
}
buttons
}
/// Returns the `hwnd` of this window.
#[inline]
pub fn hwnd(&self) -> HWND {
@ -943,6 +984,8 @@ impl<'a, T: 'static> InitData<'a, T> {
// attribute is correctly applied.
win.set_visible(attributes.visible);
win.set_enabled_buttons(attributes.enabled_buttons);
if attributes.fullscreen.is_some() {
win.set_fullscreen(attributes.fullscreen);
force_window_active(win.window.0);
@ -1012,6 +1055,9 @@ where
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
// Will be changed later using `window.set_enabled_buttons` but we need to set a default here
// so the diffing later can work.
window_flags.set(WindowFlags::CLOSABLE, true);
let parent = match pl_attribs.parent {
Parent::ChildOf(parent) => {

View file

@ -11,8 +11,9 @@ use windows_sys::Win32::{
Foundation::{HWND, RECT},
Graphics::Gdi::InvalidateRgn,
UI::WindowsAndMessaging::{
AdjustWindowRectEx, GetMenu, GetWindowLongW, SendMessageW, SetWindowLongW, SetWindowPos,
ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_BOTTOM, HWND_NOTOPMOST, HWND_TOPMOST,
AdjustWindowRectEx, EnableMenuItem, GetMenu, GetSystemMenu, GetWindowLongW, SendMessageW,
SetWindowLongW, SetWindowPos, ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_BOTTOM,
HWND_NOTOPMOST, HWND_TOPMOST, MF_BYCOMMAND, MF_DISABLED, MF_ENABLED, SC_CLOSE,
SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION,
SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW,
WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD,
@ -77,38 +78,41 @@ bitflags! {
bitflags! {
pub struct WindowFlags: u32 {
const RESIZABLE = 1 << 0;
const VISIBLE = 1 << 1;
const ON_TASKBAR = 1 << 2;
const ALWAYS_ON_TOP = 1 << 3;
const ALWAYS_ON_BOTTOM = 1 << 4;
const NO_BACK_BUFFER = 1 << 5;
const TRANSPARENT = 1 << 6;
const CHILD = 1 << 7;
const MAXIMIZED = 1 << 8;
const POPUP = 1 << 9;
const MINIMIZABLE = 1 << 1;
const MAXIMIZABLE = 1 << 2;
const CLOSABLE = 1 << 3;
const VISIBLE = 1 << 4;
const ON_TASKBAR = 1 << 5;
const ALWAYS_ON_TOP = 1 << 6;
const ALWAYS_ON_BOTTOM = 1 << 7;
const NO_BACK_BUFFER = 1 << 8;
const TRANSPARENT = 1 << 9;
const CHILD = 1 << 10;
const MAXIMIZED = 1 << 11;
const POPUP = 1 << 12;
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
/// included here to make masking easier.
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 10;
const MARKER_BORDERLESS_FULLSCREEN = 1 << 11;
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 13;
const MARKER_BORDERLESS_FULLSCREEN = 1 << 14;
/// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`.
/// In most cases, it's okay to let those parameters change the state. However, when we're
/// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to
/// effect our stored state, because the purpose of `apply_diff` is to update the actual
/// window's state to match our stored state. This controls whether to accept those changes.
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 12;
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 15;
const MARKER_IN_SIZE_MOVE = 1 << 13;
const MARKER_IN_SIZE_MOVE = 1 << 16;
const MINIMIZED = 1 << 14;
const MINIMIZED = 1 << 17;
const IGNORE_CURSOR_EVENT = 1 << 15;
const IGNORE_CURSOR_EVENT = 1 << 18;
/// Fully decorated window (incl. caption, border and drop shadow).
const MARKER_DECORATIONS = 1 << 16;
const MARKER_DECORATIONS = 1 << 19;
/// Drop shadow for undecorated windows.
const MARKER_UNDECORATED_SHADOW = 1 << 17;
const MARKER_UNDECORATED_SHADOW = 1 << 20;
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
}
@ -237,16 +241,17 @@ impl WindowFlags {
pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) {
// Required styles to properly support common window functionality like aero snap.
let mut style = WS_CAPTION
| WS_MINIMIZEBOX
| WS_BORDER
| WS_CLIPSIBLINGS
| WS_CLIPCHILDREN
| WS_SYSMENU;
let mut style = WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES;
if self.contains(WindowFlags::RESIZABLE) {
style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
style |= WS_SIZEBOX;
}
if self.contains(WindowFlags::MAXIMIZABLE) {
style |= WS_MAXIMIZEBOX;
}
if self.contains(WindowFlags::MINIMIZABLE) {
style |= WS_MINIMIZEBOX;
}
if self.contains(WindowFlags::VISIBLE) {
style |= WS_VISIBLE;
@ -350,6 +355,18 @@ impl WindowFlags {
}
}
if diff.contains(WindowFlags::CLOSABLE) || new.contains(WindowFlags::CLOSABLE) {
let flags = MF_BYCOMMAND
| new
.contains(WindowFlags::CLOSABLE)
.then(|| MF_ENABLED)
.unwrap_or(MF_DISABLED);
unsafe {
EnableMenuItem(GetSystemMenu(window, 0), SC_CLOSE, flags);
}
}
if !new.contains(WindowFlags::VISIBLE) {
unsafe {
ShowWindow(window, SW_HIDE);