mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-26 03:36:32 +11:00
feat: add macos simple fullscreen (#692)
* feat: add macos simple fullscreen * move impl to WindowExt * feedback: remove warning, unused file and rename param * feedback: combine fullscreen examples into one example * fix: ensure decorations and maximize do not toggle while in fullscreen * fix: prevent warning on non-macos platforms * feedback: make changelog more explicit * fix: prevent unconditional construction of NSRect * fix: don't try to set_simple_fullscreen if already using native fullscreen * fix: ensure set_simple_fullscreen plays nicely with set_fullscreen * fix: do not enter native fullscreen if simple fullscreen is active
This commit is contained in:
parent
4b4c73cee4
commit
bfbcab3a01
5 changed files with 183 additions and 34 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
- On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors.
|
- On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors.
|
||||||
- On X11, fixed panic caused by dropping the window before running the event loop.
|
- On X11, fixed panic caused by dropping the window before running the event loop.
|
||||||
|
- on macOS, added `WindowExt::set_simple_fullscreen` which does not require a separate space
|
||||||
- Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland.
|
- Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland.
|
||||||
- On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code.
|
- On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code.
|
||||||
- On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program.
|
- On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program.
|
||||||
|
|
|
@ -6,35 +6,46 @@ use winit::{ControlFlow, Event, WindowEvent};
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut events_loop = winit::EventsLoop::new();
|
let mut events_loop = winit::EventsLoop::new();
|
||||||
|
|
||||||
// enumerating monitors
|
#[cfg(target_os = "macos")]
|
||||||
let monitor = {
|
let mut macos_use_simple_fullscreen = false;
|
||||||
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
|
||||||
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("Please write the number of the monitor to use: ");
|
let monitor = {
|
||||||
|
// On macOS there are two fullscreen modes "native" and "simple"
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
|
||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
|
|
||||||
let mut num = String::new();
|
let mut num = String::new();
|
||||||
io::stdin().read_line(&mut num).unwrap();
|
io::stdin().read_line(&mut num).unwrap();
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
|
match num {
|
||||||
|
2 => macos_use_simple_fullscreen = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
println!("Using {:?}", monitor.get_name());
|
// Prompt for monitor when using native fullscreen
|
||||||
|
if !macos_use_simple_fullscreen {
|
||||||
|
Some(prompt_for_monitor(&events_loop))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
monitor
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
Some(prompt_for_monitor(&events_loop))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut is_fullscreen = monitor.is_some();
|
||||||
|
let mut is_maximized = false;
|
||||||
|
let mut decorations = true;
|
||||||
|
|
||||||
let window = winit::WindowBuilder::new()
|
let window = winit::WindowBuilder::new()
|
||||||
.with_title("Hello world!")
|
.with_title("Hello world!")
|
||||||
.with_fullscreen(Some(monitor))
|
.with_fullscreen(monitor)
|
||||||
.build(&events_loop)
|
.build(&events_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut is_fullscreen = true;
|
|
||||||
let mut is_maximized = false;
|
|
||||||
let mut decorations = true;
|
|
||||||
|
|
||||||
events_loop.run_forever(|event| {
|
events_loop.run_forever(|event| {
|
||||||
println!("{:?}", event);
|
println!("{:?}", event);
|
||||||
|
|
||||||
|
@ -52,6 +63,18 @@ fn main() {
|
||||||
} => match (virtual_code, state) {
|
} => match (virtual_code, state) {
|
||||||
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
||||||
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
|
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if macos_use_simple_fullscreen {
|
||||||
|
use winit::os::macos::WindowExt;
|
||||||
|
if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) {
|
||||||
|
is_fullscreen = !is_fullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ControlFlow::Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is_fullscreen = !is_fullscreen;
|
is_fullscreen = !is_fullscreen;
|
||||||
if !is_fullscreen {
|
if !is_fullscreen {
|
||||||
window.set_fullscreen(None);
|
window.set_fullscreen(None);
|
||||||
|
@ -77,3 +100,22 @@ fn main() {
|
||||||
ControlFlow::Continue
|
ControlFlow::Continue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enumerate monitors and prompt user to choose one
|
||||||
|
fn prompt_for_monitor(events_loop: &winit::EventsLoop) -> winit::MonitorId {
|
||||||
|
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
||||||
|
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("Please write the number of the monitor to use: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
|
||||||
|
let mut num = String::new();
|
||||||
|
io::stdin().read_line(&mut num).unwrap();
|
||||||
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
|
let monitor = events_loop.get_available_monitors().nth(num).expect("Please enter a valid ID");
|
||||||
|
|
||||||
|
println!("Using {:?}", monitor.get_name());
|
||||||
|
|
||||||
|
monitor
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,15 @@ pub trait WindowExt {
|
||||||
/// - `false`: the dock icon will only bounce once.
|
/// - `false`: the dock icon will only bounce once.
|
||||||
/// - `true`: the dock icon will bounce until the application is focused.
|
/// - `true`: the dock icon will bounce until the application is focused.
|
||||||
fn request_user_attention(&self, is_critical: bool);
|
fn request_user_attention(&self, is_critical: bool);
|
||||||
|
|
||||||
|
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
||||||
|
/// Returns a boolean indicating whether the transition was successful (this
|
||||||
|
/// won't work if the window was already in the native fullscreen).
|
||||||
|
///
|
||||||
|
/// This is how fullscreen used to work on macOS in versions before Lion.
|
||||||
|
/// And allows the user to have a fullscreen window without using another
|
||||||
|
/// space or taking control over the entire monitor.
|
||||||
|
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExt for Window {
|
impl WindowExt for Window {
|
||||||
|
@ -39,6 +48,11 @@ impl WindowExt for Window {
|
||||||
fn request_user_attention(&self, is_critical: bool) {
|
fn request_user_attention(&self, is_critical: bool) {
|
||||||
self.window.request_user_attention(is_critical)
|
self.window.request_user_attention(is_critical)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||||
|
self.window.set_simple_fullscreen(fullscreen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||||
|
|
|
@ -25,6 +25,20 @@ pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
|
||||||
window.makeFirstResponder_(view);
|
window.makeFirstResponder_(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
|
||||||
|
use cocoa::appkit::NSWindow;
|
||||||
|
|
||||||
|
let current_style_mask = window.styleMask();
|
||||||
|
if on {
|
||||||
|
window.setStyleMask_(current_style_mask | mask);
|
||||||
|
} else {
|
||||||
|
window.setStyleMask_(current_style_mask & (!mask));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
||||||
|
window.makeFirstResponder_(view);
|
||||||
|
}
|
||||||
|
|
||||||
pub unsafe fn create_input_context(view: id) -> IdRef {
|
pub unsafe fn create_input_context(view: id) -> IdRef {
|
||||||
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
|
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
|
||||||
let input_context: id = msg_send![input_context, initWithClient:view];
|
let input_context: id = msg_send![input_context, initWithClient:view];
|
||||||
|
|
|
@ -19,6 +19,7 @@ use cocoa::appkit::{
|
||||||
NSWindowButton,
|
NSWindowButton,
|
||||||
NSWindowStyleMask,
|
NSWindowStyleMask,
|
||||||
NSApplicationActivationPolicy,
|
NSApplicationActivationPolicy,
|
||||||
|
NSApplicationPresentationOptions,
|
||||||
};
|
};
|
||||||
use cocoa::base::{id, nil};
|
use cocoa::base::{id, nil};
|
||||||
use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString};
|
use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString};
|
||||||
|
@ -57,7 +58,9 @@ pub struct DelegateState {
|
||||||
|
|
||||||
win_attribs: RefCell<WindowAttributes>,
|
win_attribs: RefCell<WindowAttributes>,
|
||||||
standard_frame: Cell<Option<NSRect>>,
|
standard_frame: Cell<Option<NSRect>>,
|
||||||
|
is_simple_fullscreen: Cell<bool>,
|
||||||
save_style_mask: Cell<Option<NSWindowStyleMask>>,
|
save_style_mask: Cell<Option<NSWindowStyleMask>>,
|
||||||
|
save_presentation_opts: Cell<Option<NSApplicationPresentationOptions>>,
|
||||||
|
|
||||||
// This is set when WindowBuilder::with_fullscreen was set,
|
// This is set when WindowBuilder::with_fullscreen was set,
|
||||||
// see comments of `window_did_fail_to_enter_fullscreen`
|
// see comments of `window_did_fail_to_enter_fullscreen`
|
||||||
|
@ -94,22 +97,30 @@ impl DelegateState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn saved_style_mask(&self, resizable: bool) -> NSWindowStyleMask {
|
||||||
|
let base_mask = self.save_style_mask
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| self.window.styleMask());
|
||||||
|
if resizable {
|
||||||
|
base_mask | NSWindowStyleMask::NSResizableWindowMask
|
||||||
|
} else {
|
||||||
|
base_mask & !NSWindowStyleMask::NSResizableWindowMask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn saved_standard_frame(&self) -> NSRect {
|
||||||
|
self.standard_frame.get().unwrap_or_else(|| NSRect::new(
|
||||||
|
NSPoint::new(50.0, 50.0),
|
||||||
|
NSSize::new(800.0, 600.0),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn restore_state_from_fullscreen(&mut self) {
|
fn restore_state_from_fullscreen(&mut self) {
|
||||||
let maximized = unsafe {
|
let maximized = unsafe {
|
||||||
let mut win_attribs = self.win_attribs.borrow_mut();
|
let mut win_attribs = self.win_attribs.borrow_mut();
|
||||||
win_attribs.fullscreen = None;
|
win_attribs.fullscreen = None;
|
||||||
|
|
||||||
let mask = {
|
let mask = self.saved_style_mask(win_attribs.resizable);
|
||||||
let base_mask = self.save_style_mask
|
|
||||||
.take()
|
|
||||||
.unwrap_or_else(|| self.window.styleMask());
|
|
||||||
if win_attribs.resizable {
|
|
||||||
base_mask | NSWindowStyleMask::NSResizableWindowMask
|
|
||||||
} else {
|
|
||||||
base_mask & !NSWindowStyleMask::NSResizableWindowMask
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
util::set_style_mask(*self.window, *self.view, mask);
|
util::set_style_mask(*self.window, *self.view, mask);
|
||||||
|
|
||||||
win_attribs.maximized
|
win_attribs.maximized
|
||||||
|
@ -151,10 +162,7 @@ impl DelegateState {
|
||||||
let screen = NSScreen::mainScreen(nil);
|
let screen = NSScreen::mainScreen(nil);
|
||||||
NSScreen::visibleFrame(screen)
|
NSScreen::visibleFrame(screen)
|
||||||
} else {
|
} else {
|
||||||
self.standard_frame.get().unwrap_or(NSRect::new(
|
self.saved_standard_frame()
|
||||||
NSPoint::new(50.0, 50.0),
|
|
||||||
NSSize::new(800.0, 600.0),
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.window.setFrame_display_(new_rect, 0);
|
self.window.setFrame_display_(new_rect, 0);
|
||||||
|
@ -600,6 +608,68 @@ impl WindowExt for Window2 {
|
||||||
NSApp().requestUserAttention_(request_type);
|
NSApp().requestUserAttention_(request_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
||||||
|
let state = &self.delegate.state;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let app = NSApp();
|
||||||
|
let win_attribs = state.win_attribs.borrow_mut();
|
||||||
|
let is_native_fullscreen = win_attribs.fullscreen.is_some();
|
||||||
|
let is_simple_fullscreen = state.is_simple_fullscreen.get();
|
||||||
|
|
||||||
|
// Do nothing if native fullscreen is active.
|
||||||
|
if is_native_fullscreen || (fullscreen && is_simple_fullscreen) || (!fullscreen && !is_simple_fullscreen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullscreen {
|
||||||
|
// Remember the original window's settings
|
||||||
|
state.standard_frame.set(Some(NSWindow::frame(*self.window)));
|
||||||
|
state.save_style_mask.set(Some(self.window.styleMask()));
|
||||||
|
state.save_presentation_opts.set(Some(app.presentationOptions_()));
|
||||||
|
|
||||||
|
// Tell our window's state that we're in fullscreen
|
||||||
|
state.is_simple_fullscreen.set(true);
|
||||||
|
|
||||||
|
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
||||||
|
let presentation_options =
|
||||||
|
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock |
|
||||||
|
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
|
||||||
|
app.setPresentationOptions_(presentation_options);
|
||||||
|
|
||||||
|
// Hide the titlebar
|
||||||
|
util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSTitledWindowMask, false);
|
||||||
|
|
||||||
|
// Set the window frame to the screen frame size
|
||||||
|
let screen = self.window.screen();
|
||||||
|
let screen_frame = NSScreen::frame(screen);
|
||||||
|
NSWindow::setFrame_display_(*self.window, screen_frame, YES);
|
||||||
|
|
||||||
|
// Fullscreen windows can't be resized, minimized, or moved
|
||||||
|
util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSMiniaturizableWindowMask, false);
|
||||||
|
util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSResizableWindowMask, false);
|
||||||
|
NSWindow::setMovable_(*self.window, NO);
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let saved_style_mask = state.saved_style_mask(win_attribs.resizable);
|
||||||
|
util::set_style_mask(*self.window, *self.view, saved_style_mask);
|
||||||
|
state.is_simple_fullscreen.set(false);
|
||||||
|
|
||||||
|
if let Some(presentation_opts) = state.save_presentation_opts.get() {
|
||||||
|
app.setPresentationOptions_(presentation_opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = state.saved_standard_frame();
|
||||||
|
NSWindow::setFrame_display_(*self.window, frame, YES);
|
||||||
|
NSWindow::setMovable_(*self.window, YES);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window2 {
|
impl Window2 {
|
||||||
|
@ -675,7 +745,9 @@ impl Window2 {
|
||||||
shared,
|
shared,
|
||||||
win_attribs: RefCell::new(win_attribs.clone()),
|
win_attribs: RefCell::new(win_attribs.clone()),
|
||||||
standard_frame: Cell::new(None),
|
standard_frame: Cell::new(None),
|
||||||
|
is_simple_fullscreen: Cell::new(false),
|
||||||
save_style_mask: Cell::new(None),
|
save_style_mask: Cell::new(None),
|
||||||
|
save_presentation_opts: Cell::new(None),
|
||||||
handle_with_fullscreen: win_attribs.fullscreen.is_some(),
|
handle_with_fullscreen: win_attribs.fullscreen.is_some(),
|
||||||
previous_position: None,
|
previous_position: None,
|
||||||
previous_dpi_factor: dpi_factor,
|
previous_dpi_factor: dpi_factor,
|
||||||
|
@ -1086,6 +1158,12 @@ impl Window2 {
|
||||||
/// in fullscreen mode
|
/// in fullscreen mode
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||||
let state = &self.delegate.state;
|
let state = &self.delegate.state;
|
||||||
|
|
||||||
|
// Do nothing if simple fullscreen is active.
|
||||||
|
if state.is_simple_fullscreen.get() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let current = {
|
let current = {
|
||||||
let win_attribs = state.win_attribs.borrow_mut();
|
let win_attribs = state.win_attribs.borrow_mut();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue