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:
parent
28e34c2e1b
commit
94688a62f0
14 changed files with 343 additions and 36 deletions
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue