Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 13 commits into from
Nov 29, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
68 changes: 68 additions & 0 deletions examples/window_buttons.rs
Original file line number Diff line number Diff line change
@@ -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(),
_ => (),
}
});
}
8 changes: 7 additions & 1 deletion src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<event::VirtualKeyCode> {
Expand Down Expand Up @@ -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) {}
Expand Down
13 changes: 12 additions & 1 deletion src/platform_impl/ios/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down Expand Up @@ -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];
Expand Down
15 changes: 14 additions & 1 deletion src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down
12 changes: 11 additions & 1 deletion src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -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 _
Expand Down
12 changes: 11 additions & 1 deletion src/platform_impl/macos/appkit/control.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
use objc2::{extern_class, extern_methods, ClassType};

use super::{NSResponder, NSView};

Expand All @@ -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;
}
);
6 changes: 6 additions & 0 deletions src/platform_impl/macos/appkit/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
63 changes: 62 additions & 1 deletion src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{
Fullscreen, OsError,
},
window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes,
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons,
WindowId as RootWindowId, WindowLevel,
},
};
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
12 changes: 10 additions & 2 deletions src/platform_impl/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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()
Expand Down
Loading