From 94688a62f06cb84a9d8d13fd4b20d4211f22ce92 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 29 Nov 2022 12:03:51 +0200 Subject: [PATCH] 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 * compose the flags on a sep line, use `bool::then` Co-authored-by: Mads Marquart Co-authored-by: Kirill Chibisov --- CHANGELOG.md | 1 + examples/window_buttons.rs | 68 ++++++++++++++++++ src/platform_impl/android/mod.rs | 8 ++- src/platform_impl/ios/window.rs | 13 +++- src/platform_impl/linux/mod.rs | 15 +++- src/platform_impl/linux/wayland/window/mod.rs | 12 +++- src/platform_impl/linux/x11/window.rs | 11 ++- src/platform_impl/macos/appkit/control.rs | 12 +++- src/platform_impl/macos/appkit/window.rs | 6 ++ src/platform_impl/macos/window.rs | 63 ++++++++++++++++- src/platform_impl/web/window.rs | 12 +++- src/platform_impl/windows/window.rs | 48 ++++++++++++- src/platform_impl/windows/window_state.rs | 69 ++++++++++++------- src/window.rs | 41 +++++++++++ 14 files changed, 343 insertions(+), 36 deletions(-) create mode 100644 examples/window_buttons.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b142e2a7..2bf23311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc). - On Windows, macOS, X11 and Wayland, add `Window::set_theme`. - **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`. - On Windows, revert window background to an empty brush to avoid white flashes when changing scaling. diff --git a/examples/window_buttons.rs b/examples/window_buttons.rs new file mode 100644 index 00000000..5d41144d --- /dev/null +++ b/examples/window_buttons.rs @@ -0,0 +1,68 @@ +#![allow(clippy::single_match)] + +// This example is used by developers to test various window functions. + +use simple_logger::SimpleLogger; +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{DeviceEventFilter, EventLoop}, + window::{WindowBuilder, WindowButtons}, +}; + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .with_inner_size(LogicalSize::new(300.0, 300.0)) + .build(&event_loop) + .unwrap(); + + eprintln!("Window Button keys:"); + eprintln!(" (F) Toggle close button"); + eprintln!(" (G) Toggle maximize button"); + eprintln!(" (H) Toggle minimize button"); + + event_loop.set_device_event_filter(DeviceEventFilter::Never); + + event_loop.run(move |event, _, control_flow| { + control_flow.set_wait(); + + match event { + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } => match key { + VirtualKeyCode::F => { + let buttons = window.enabled_buttons(); + window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE); + } + VirtualKeyCode::G => { + let buttons = window.enabled_buttons(); + window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE); + } + VirtualKeyCode::H => { + let buttons = window.enabled_buttons(); + window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE); + } + _ => (), + }, + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window_id == window.id() => control_flow.set_exit(), + _ => (), + } + }); +} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 18f7c249..68e4cf66 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -24,7 +24,7 @@ use crate::{ error, event::{self, StartCause, VirtualKeyCode}, event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, - window::{self, CursorGrabMode, Theme, WindowLevel}, + window::{self, CursorGrabMode, Theme, WindowButtons, WindowLevel}, }; fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option { @@ -959,6 +959,12 @@ impl Window { false } + pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} + + pub fn enabled_buttons(&self) -> WindowButtons { + WindowButtons::all() + } + pub fn set_minimized(&self, _minimized: bool) {} pub fn set_maximized(&self, _maximized: bool) {} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 7413888b..e3f33908 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -23,7 +23,7 @@ use crate::{ monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle, }, window::{ - CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }, }; @@ -172,6 +172,17 @@ impl Inner { false } + #[inline] + pub fn set_enabled_buttons(&self, _buttons: WindowButtons) { + warn!("`Window::set_enabled_buttons` is ignored on iOS"); + } + + #[inline] + pub fn enabled_buttons(&self) -> WindowButtons { + warn!("`Window::enabled_buttons` is ignored on iOS"); + WindowButtons::all() + } + pub fn scale_factor(&self) -> f64 { unsafe { let hidpi: CGFloat = msg_send![self.view, contentScaleFactor]; diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 32b661d9..0c69df88 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -39,7 +39,10 @@ use crate::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, icon::Icon, - window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowLevel}, + window::{ + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, + WindowLevel, + }, }; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; @@ -401,6 +404,16 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.is_resizable()) } + #[inline] + pub fn set_enabled_buttons(&self, buttons: WindowButtons) { + x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons)) + } + + #[inline] + pub fn enabled_buttons(&self) -> WindowButtons { + x11_or_wayland!(match self; Window(w) => w.enabled_buttons()) + } + #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor)) diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index cfc0a8a4..7b64948b 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -18,7 +18,9 @@ use crate::platform_impl::{ Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; -use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes}; +use crate::window::{ + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, +}; use super::env::WindowingFeatures; use super::event_loop::WinitState; @@ -421,6 +423,14 @@ impl Window { self.resizeable.load(Ordering::Relaxed) } + #[inline] + pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} + + #[inline] + pub fn enabled_buttons(&self) -> WindowButtons { + WindowButtons::all() + } + #[inline] pub fn scale_factor(&self) -> u32 { // The scale factor from `get_surface_scale_factor` is always greater than zero, so diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 835379f3..29bf0b7a 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -21,7 +21,8 @@ use crate::{ PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, window::{ - CursorGrabMode, CursorIcon, Icon, Theme, UserAttentionType, WindowAttributes, WindowLevel, + CursorGrabMode, CursorIcon, Icon, Theme, UserAttentionType, WindowAttributes, + WindowButtons, WindowLevel, }, }; @@ -1280,6 +1281,14 @@ impl UnownedWindow { self.shared_state_lock().is_resizable } + #[inline] + pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} + + #[inline] + pub fn enabled_buttons(&self) -> WindowButtons { + WindowButtons::all() + } + #[inline] pub fn xlib_display(&self) -> *mut c_void { self.xconn.display as _ diff --git a/src/platform_impl/macos/appkit/control.rs b/src/platform_impl/macos/appkit/control.rs index 2dd22902..1ca52195 100644 --- a/src/platform_impl/macos/appkit/control.rs +++ b/src/platform_impl/macos/appkit/control.rs @@ -1,5 +1,5 @@ use objc2::foundation::NSObject; -use objc2::{extern_class, ClassType}; +use objc2::{extern_class, extern_methods, ClassType}; use super::{NSResponder, NSView}; @@ -12,3 +12,13 @@ extern_class!( type Super = NSView; } ); + +extern_methods!( + unsafe impl NSControl { + #[sel(setEnabled:)] + pub fn setEnabled(&self, enabled: bool); + + #[sel(isEnabled)] + pub fn isEnabled(&self) -> bool; + } +); diff --git a/src/platform_impl/macos/appkit/window.rs b/src/platform_impl/macos/appkit/window.rs index 7f859746..af53fd21 100644 --- a/src/platform_impl/macos/appkit/window.rs +++ b/src/platform_impl/macos/appkit/window.rs @@ -180,6 +180,12 @@ extern_methods!( #[sel(isResizable)] pub fn isResizable(&self) -> bool; + #[sel(isMiniaturizable)] + pub fn isMiniaturizable(&self) -> bool; + + #[sel(hasCloseBox)] + pub fn hasCloseBox(&self) -> bool; + #[sel(isMiniaturized)] pub fn isMiniaturized(&self) -> bool; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index e4c2f5da..94b98473 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -29,7 +29,7 @@ use crate::{ Fullscreen, OsError, }, window::{ - CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }, }; @@ -269,6 +269,14 @@ impl WinitWindow { masks &= !NSWindowStyleMask::NSResizableWindowMask; } + if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) { + masks &= !NSWindowStyleMask::NSMiniaturizableWindowMask; + } + + if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) { + masks &= !NSWindowStyleMask::NSClosableWindowMask; + } + if pl_attrs.fullsize_content_view { masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; } @@ -333,6 +341,12 @@ impl WinitWindow { this.setMovableByWindowBackground(true); } + if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { + if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) { + button.setEnabled(false); + } + } + if let Some(increments) = attrs.resize_increments { let increments = increments.to_logical(this.scale_factor()); let (w, h) = (increments.width, increments.height); @@ -624,6 +638,53 @@ impl WinitWindow { self.isResizable() } + #[inline] + pub fn set_enabled_buttons(&self, buttons: WindowButtons) { + let mut mask = self.styleMask(); + + if buttons.contains(WindowButtons::CLOSE) { + mask |= NSWindowStyleMask::NSClosableWindowMask; + } else { + mask &= !NSWindowStyleMask::NSClosableWindowMask; + } + + if buttons.contains(WindowButtons::MINIMIZE) { + mask |= NSWindowStyleMask::NSMiniaturizableWindowMask; + } else { + mask &= !NSWindowStyleMask::NSMiniaturizableWindowMask; + } + + // This must happen before the button's "enabled" status has been set, + // hence we do it synchronously. + self.set_style_mask_sync(mask); + + // We edit the button directly instead of using `NSResizableWindowMask`, + // since that mask also affect the resizability of the window (which is + // controllable by other means in `winit`). + if let Some(button) = self.standardWindowButton(NSWindowButton::Zoom) { + button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE)); + } + } + + #[inline] + pub fn enabled_buttons(&self) -> WindowButtons { + let mut buttons = WindowButtons::empty(); + if self.isMiniaturizable() { + buttons |= WindowButtons::MINIMIZE; + } + if self + .standardWindowButton(NSWindowButton::Zoom) + .map(|b| b.isEnabled()) + .unwrap_or(true) + { + buttons |= WindowButtons::MAXIMIZE; + } + if self.hasCloseBox() { + buttons |= WindowButtons::CLOSE; + } + buttons + } + pub fn set_cursor_icon(&self, icon: CursorIcon) { let view = self.view(); let mut cursor_state = view.state.cursor_state.lock().unwrap(); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 2ec46331..7f39f608 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -3,8 +3,8 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::event; use crate::icon::Icon; use crate::window::{ - CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowId as RootWI, - WindowLevel, + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, + WindowId as RootWI, WindowLevel, }; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; @@ -172,6 +172,14 @@ impl Window { true } + #[inline] + pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} + + #[inline] + pub fn enabled_buttons(&self) -> WindowButtons { + WindowButtons::all() + } + #[inline] pub fn scale_factor(&self) -> f64 { super::backend::scale_factor() diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index ceb6c34d..0706fc83 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -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) => { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index f36ed935..c5a092ac 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -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); diff --git a/src/window.rs b/src/window.rs index e00a73ac..bac0c34d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -126,6 +126,7 @@ pub(crate) struct WindowAttributes { pub max_inner_size: Option, pub position: Option, pub resizable: bool, + pub enabled_buttons: WindowButtons, pub title: String, pub fullscreen: Option, pub maximized: bool, @@ -148,6 +149,7 @@ impl Default for WindowAttributes { max_inner_size: None, position: None, resizable: true, + enabled_buttons: WindowButtons::all(), title: "winit window".to_owned(), maximized: false, fullscreen: None, @@ -244,6 +246,17 @@ impl WindowBuilder { self } + /// Sets the enabled window buttons. + /// + /// The default is [`WindowButtons::all`] + /// + /// See [`Window::set_enabled_buttons`] for details. + #[inline] + pub fn with_enabled_buttons(mut self, buttons: WindowButtons) -> Self { + self.window.enabled_buttons = buttons; + self + } + /// Sets the initial title of the window in the title bar. /// /// The default is `"winit window"`. @@ -755,6 +768,26 @@ impl Window { self.window.is_resizable() } + /// Sets the enabled window buttons. + /// + /// ## Platform-specific + /// + /// - **Wayland / X11:** Not implemented. + /// - **Web / iOS / Android:** Unsupported. + pub fn set_enabled_buttons(&self, buttons: WindowButtons) { + self.window.set_enabled_buttons(buttons) + } + + /// Gets the enabled window buttons. + /// + /// ## Platform-specific + /// + /// - **Wayland / X11:** Not implemented. Always returns [`WindowButtons::all`]. + /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. + pub fn enabled_buttons(&self) -> WindowButtons { + self.window.enabled_buttons() + } + /// Sets the window to minimized or back /// /// ## Platform-specific @@ -1441,6 +1474,14 @@ impl Default for UserAttentionType { } } +bitflags! { + pub struct WindowButtons: u32 { + const CLOSE = 1 << 0; + const MINIMIZE = 1 << 1; + const MAXIMIZE = 1 << 2; + } +} + /// A window level groups windows with respect to their z-position. /// /// The relative ordering between windows in different window levels is fixed.