mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +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
|
@ -2,6 +2,7 @@
|
|||
|
||||
- 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 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.
|
||||
- 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.
|
||||
|
|
|
@ -6,35 +6,46 @@ use winit::{ControlFlow, Event, WindowEvent};
|
|||
fn main() {
|
||||
let mut events_loop = winit::EventsLoop::new();
|
||||
|
||||
// enumerating monitors
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut macos_use_simple_fullscreen = false;
|
||||
|
||||
let monitor = {
|
||||
for (num, monitor) in events_loop.get_available_monitors().enumerate() {
|
||||
println!("Monitor #{}: {:?}", num, monitor.get_name());
|
||||
// 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();
|
||||
|
||||
let mut num = String::new();
|
||||
io::stdin().read_line(&mut num).unwrap();
|
||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||
match num {
|
||||
2 => macos_use_simple_fullscreen = true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Prompt for monitor when using native fullscreen
|
||||
if !macos_use_simple_fullscreen {
|
||||
Some(prompt_for_monitor(&events_loop))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
#[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()
|
||||
.with_title("Hello world!")
|
||||
.with_fullscreen(Some(monitor))
|
||||
.with_fullscreen(monitor)
|
||||
.build(&events_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut is_fullscreen = true;
|
||||
let mut is_maximized = false;
|
||||
let mut decorations = true;
|
||||
|
||||
events_loop.run_forever(|event| {
|
||||
println!("{:?}", event);
|
||||
|
||||
|
@ -52,6 +63,18 @@ fn main() {
|
|||
} => match (virtual_code, state) {
|
||||
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
||||
(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;
|
||||
if !is_fullscreen {
|
||||
window.set_fullscreen(None);
|
||||
|
@ -77,3 +100,22 @@ fn main() {
|
|||
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.
|
||||
/// - `true`: the dock icon will bounce until the application is focused.
|
||||
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 {
|
||||
|
@ -39,6 +48,11 @@ impl WindowExt for Window {
|
|||
fn request_user_attention(&self, is_critical: bool) {
|
||||
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`.
|
||||
|
|
|
@ -25,6 +25,20 @@ pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
|
|||
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 {
|
||||
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
|
||||
let input_context: id = msg_send![input_context, initWithClient:view];
|
||||
|
|
|
@ -19,6 +19,7 @@ use cocoa::appkit::{
|
|||
NSWindowButton,
|
||||
NSWindowStyleMask,
|
||||
NSApplicationActivationPolicy,
|
||||
NSApplicationPresentationOptions,
|
||||
};
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString};
|
||||
|
@ -57,7 +58,9 @@ pub struct DelegateState {
|
|||
|
||||
win_attribs: RefCell<WindowAttributes>,
|
||||
standard_frame: Cell<Option<NSRect>>,
|
||||
is_simple_fullscreen: Cell<bool>,
|
||||
save_style_mask: Cell<Option<NSWindowStyleMask>>,
|
||||
save_presentation_opts: Cell<Option<NSApplicationPresentationOptions>>,
|
||||
|
||||
// This is set when WindowBuilder::with_fullscreen was set,
|
||||
// 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) {
|
||||
let maximized = unsafe {
|
||||
let mut win_attribs = self.win_attribs.borrow_mut();
|
||||
win_attribs.fullscreen = None;
|
||||
|
||||
let mask = {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
let mask = self.saved_style_mask(win_attribs.resizable);
|
||||
util::set_style_mask(*self.window, *self.view, mask);
|
||||
|
||||
win_attribs.maximized
|
||||
|
@ -151,10 +162,7 @@ impl DelegateState {
|
|||
let screen = NSScreen::mainScreen(nil);
|
||||
NSScreen::visibleFrame(screen)
|
||||
} else {
|
||||
self.standard_frame.get().unwrap_or(NSRect::new(
|
||||
NSPoint::new(50.0, 50.0),
|
||||
NSSize::new(800.0, 600.0),
|
||||
))
|
||||
self.saved_standard_frame()
|
||||
};
|
||||
|
||||
self.window.setFrame_display_(new_rect, 0);
|
||||
|
@ -600,6 +608,68 @@ impl WindowExt for Window2 {
|
|||
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 {
|
||||
|
@ -675,7 +745,9 @@ impl Window2 {
|
|||
shared,
|
||||
win_attribs: RefCell::new(win_attribs.clone()),
|
||||
standard_frame: Cell::new(None),
|
||||
is_simple_fullscreen: Cell::new(false),
|
||||
save_style_mask: Cell::new(None),
|
||||
save_presentation_opts: Cell::new(None),
|
||||
handle_with_fullscreen: win_attribs.fullscreen.is_some(),
|
||||
previous_position: None,
|
||||
previous_dpi_factor: dpi_factor,
|
||||
|
@ -1086,6 +1158,12 @@ impl Window2 {
|
|||
/// in fullscreen mode
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
let state = &self.delegate.state;
|
||||
|
||||
// Do nothing if simple fullscreen is active.
|
||||
if state.is_simple_fullscreen.get() {
|
||||
return
|
||||
}
|
||||
|
||||
let current = {
|
||||
let win_attribs = state.win_attribs.borrow_mut();
|
||||
|
||||
|
|
Loading…
Reference in a new issue