mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-24 06:11:30 +11:00
Implement set_maximized, get_current_monitor, set_fullscreen and set_decorations for MacOS (#465)
* Added helper function for make monitor from display. * Implement get_current_monitor for macos * Implemented with_fullscreen and set_fullscreen for macos * Implemented set_decorations for macos * Implement set_maximized and with_maximized for macos * Changed fullscreen example fullscreen keypress from F11 to F * Update CHANGELOG.md * Add and fixed some comments * Reformat and add more comments * Better handling window and maximized state * Reformat and typo fix
This commit is contained in:
parent
8fd49a4dbe
commit
0474dc9861
|
@ -1,5 +1,6 @@
|
|||
# Unreleased
|
||||
|
||||
- Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for MacOS.
|
||||
- Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for Windows.
|
||||
- On Windows, `WindowBuilder::with_dimensions` no longer changing monitor display resolution.
|
||||
- Overhauled X11 window geometry calculations. `get_position` and `set_position` are more universally accurate across different window managers, and `get_outer_size` actually works now.
|
||||
|
|
|
@ -51,7 +51,7 @@ fn main() {
|
|||
..
|
||||
} => match (virtual_code, state) {
|
||||
(winit::VirtualKeyCode::Escape, _) => return ControlFlow::Break,
|
||||
(winit::VirtualKeyCode::F11, winit::ElementState::Pressed) => {
|
||||
(winit::VirtualKeyCode::F, winit::ElementState::Pressed) => {
|
||||
is_fullscreen = !is_fullscreen;
|
||||
if !is_fullscreen {
|
||||
window.set_fullscreen(None);
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::collections::VecDeque;
|
|||
use super::EventsLoop;
|
||||
use super::window::IdRef;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct MonitorId(CGDirectDisplayID);
|
||||
|
||||
impl EventsLoop {
|
||||
|
@ -25,6 +25,11 @@ impl EventsLoop {
|
|||
let id = MonitorId(CGDisplay::main().id);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorId {
|
||||
let id = MonitorId(id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
|
|
|
@ -11,9 +11,10 @@ use objc::runtime::{Class, Object, Sel, BOOL, YES, NO};
|
|||
use objc::declare::ClassDecl;
|
||||
|
||||
use cocoa;
|
||||
use cocoa::appkit::{self, NSApplication, NSColor, NSScreen, NSView, NSWindow, NSWindowButton,
|
||||
NSWindowStyleMask};
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString};
|
||||
use cocoa::appkit::{self, NSApplication, NSColor, NSView, NSWindow, NSWindowStyleMask, NSWindowButton};
|
||||
use cocoa::foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString};
|
||||
|
||||
use core_graphics::display::CGDisplay;
|
||||
|
||||
|
@ -21,8 +22,9 @@ use std;
|
|||
use std::ops::Deref;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::Weak;
|
||||
use std::cell::{Cell,RefCell};
|
||||
|
||||
use super::events_loop::Shared;
|
||||
use super::events_loop::{EventsLoop, Shared};
|
||||
|
||||
use window::MonitorId as RootMonitorId;
|
||||
|
||||
|
@ -33,6 +35,98 @@ struct DelegateState {
|
|||
view: IdRef,
|
||||
window: IdRef,
|
||||
shared: Weak<Shared>,
|
||||
|
||||
win_attribs: RefCell<WindowAttributes>,
|
||||
standard_frame: Cell<Option<NSRect>>,
|
||||
save_style_mask: Cell<Option<NSWindowStyleMask>>,
|
||||
|
||||
// This is set when WindowBuilder::with_fullscreen was set,
|
||||
// see comments of `window_did_fail_to_enter_fullscreen`
|
||||
handle_with_fullscreen: bool,
|
||||
}
|
||||
|
||||
impl DelegateState {
|
||||
fn is_zoomed(&self) -> bool {
|
||||
unsafe {
|
||||
// Because isZoomed do not work in Borderless mode, we set it
|
||||
// resizable temporality
|
||||
let curr_mask = self.window.styleMask();
|
||||
|
||||
if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) {
|
||||
self.window
|
||||
.setStyleMask_(NSWindowStyleMask::NSResizableWindowMask);
|
||||
}
|
||||
|
||||
let is_zoomed: BOOL = msg_send![*self.window, isZoomed];
|
||||
|
||||
// Roll back temp styles
|
||||
if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) {
|
||||
self.window.setStyleMask_(curr_mask);
|
||||
}
|
||||
|
||||
is_zoomed != 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 save_style_opt = self.save_style_mask.take();
|
||||
|
||||
if let Some(save_style) = save_style_opt {
|
||||
self.window.setStyleMask_(save_style);
|
||||
}
|
||||
|
||||
win_attribs.maximized
|
||||
};
|
||||
|
||||
self.perform_maximized(maximized);
|
||||
}
|
||||
|
||||
fn perform_maximized(&self, maximized: bool) {
|
||||
let is_zoomed = self.is_zoomed();
|
||||
|
||||
if is_zoomed == maximized {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the standard frame sized if it is not zoomed
|
||||
if !is_zoomed {
|
||||
unsafe {
|
||||
self.standard_frame.set(Some(NSWindow::frame(*self.window)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut win_attribs = self.win_attribs.borrow_mut();
|
||||
win_attribs.maximized = maximized;
|
||||
|
||||
if win_attribs.fullscreen.is_some() {
|
||||
// Handle it in window_did_exit_fullscreen
|
||||
return;
|
||||
} else if win_attribs.decorations {
|
||||
// Just use the native zoom if not borderless
|
||||
unsafe {
|
||||
self.window.zoom_(nil);
|
||||
}
|
||||
} else {
|
||||
// if it is borderless, we set the frame directly
|
||||
unsafe {
|
||||
let new_rect = if maximized {
|
||||
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.window.setFrame_display_(new_rect, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowDelegate {
|
||||
|
@ -199,6 +293,71 @@ impl WindowDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
extern fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id){
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
state.win_attribs.borrow_mut().fullscreen = Some(get_current_monitor());
|
||||
|
||||
state.handle_with_fullscreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
extern fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
let is_zoomed = state.is_zoomed();
|
||||
|
||||
state.win_attribs.borrow_mut().maximized = is_zoomed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
extern fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id){
|
||||
let state = unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
&mut *(state as *mut DelegateState)
|
||||
};
|
||||
|
||||
state.restore_state_from_fullscreen();
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
///
|
||||
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
||||
/// terminal), it creates a new virtual destkop and a transition animation.
|
||||
/// This animation takes one second and cannot be disable without
|
||||
/// elevated privileges. In this animation time, all toggleFullscreen events
|
||||
/// will be failed. In this implementation, we will try again by using
|
||||
/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen.
|
||||
/// It should be fine as we only do this at initialzation (i.e with_fullscreen
|
||||
/// was set).
|
||||
///
|
||||
/// From Apple doc:
|
||||
/// In some cases, the transition to enter full-screen mode can fail,
|
||||
/// due to being in the midst of handling some other animation or user gesture.
|
||||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
extern fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let state: *mut c_void = *this.get_ivar("winitState");
|
||||
let state = &mut *(state as *mut DelegateState);
|
||||
|
||||
if state.handle_with_fullscreen {
|
||||
let _: () = msg_send![*state.window,
|
||||
performSelector:sel!(toggleFullScreen:)
|
||||
withObject:nil
|
||||
afterDelay: 0.5
|
||||
];
|
||||
} else {
|
||||
state.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: std::sync::Once = std::sync::ONCE_INIT;
|
||||
|
||||
|
@ -233,6 +392,16 @@ impl WindowDelegate {
|
|||
decl.add_method(sel!(draggingExited:),
|
||||
dragging_exited as extern fn(&Object, Sel, id));
|
||||
|
||||
// callbacks for fullscreen events
|
||||
decl.add_method(sel!(windowDidEnterFullScreen:),
|
||||
window_did_enter_fullscreen as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(windowWillEnterFullScreen:),
|
||||
window_will_enter_fullscreen as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(windowDidExitFullScreen:),
|
||||
window_did_exit_fullscreen as extern fn(&Object, Sel, id));
|
||||
decl.add_method(sel!(windowDidFailToEnterFullScreen:),
|
||||
window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id));
|
||||
|
||||
// Store internal state as user data
|
||||
decl.add_ivar::<*mut c_void>("winitState");
|
||||
|
||||
|
@ -288,6 +457,20 @@ pub struct Window2 {
|
|||
unsafe impl Send for Window2 {}
|
||||
unsafe impl Sync for Window2 {}
|
||||
|
||||
/// Helpper funciton to convert NSScreen::mainScreen to MonitorId
|
||||
unsafe fn get_current_monitor() -> RootMonitorId {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
let desc = NSScreen::deviceDescription(screen);
|
||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||
|
||||
let value = NSDictionary::valueForKey_(desc, *key);
|
||||
let display_id = msg_send![value, unsignedIntegerValue];
|
||||
|
||||
RootMonitorId {
|
||||
inner: EventsLoop::make_monitor_from_display(display_id),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window2 {
|
||||
fn drop(&mut self) {
|
||||
// Remove this window from the `EventLoop`s list of windows.
|
||||
|
@ -319,11 +502,11 @@ impl WindowExt for Window2 {
|
|||
}
|
||||
|
||||
impl Window2 {
|
||||
pub fn new(shared: Weak<Shared>,
|
||||
pub fn new(
|
||||
shared: Weak<Shared>,
|
||||
win_attribs: &WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window2, CreationError>
|
||||
{
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window2, CreationError> {
|
||||
unsafe {
|
||||
if !msg_send![cocoa::base::class("NSThread"), isMainThread] {
|
||||
panic!("Windows can only be created on the main thread on macOS");
|
||||
|
@ -352,11 +535,6 @@ impl Window2 {
|
|||
}
|
||||
|
||||
app.activateIgnoringOtherApps_(YES);
|
||||
if win_attribs.visible {
|
||||
window.makeKeyAndOrderFront_(nil);
|
||||
} else {
|
||||
window.makeKeyWindow();
|
||||
}
|
||||
|
||||
if let Some((width, height)) = win_attribs.min_dimensions {
|
||||
nswindow_set_min_dimensions(window.0, width.into(), height.into());
|
||||
|
@ -375,8 +553,13 @@ impl Window2 {
|
|||
let ds = DelegateState {
|
||||
view: view.clone(),
|
||||
window: window.clone(),
|
||||
win_attribs: RefCell::new(win_attribs.clone()),
|
||||
standard_frame: Cell::new(None),
|
||||
save_style_mask: Cell::new(None),
|
||||
handle_with_fullscreen: win_attribs.fullscreen.is_some(),
|
||||
shared: shared,
|
||||
};
|
||||
ds.win_attribs.borrow_mut().fullscreen = None;
|
||||
|
||||
let window = Window2 {
|
||||
view: view,
|
||||
|
@ -384,6 +567,30 @@ impl Window2 {
|
|||
delegate: WindowDelegate::new(ds),
|
||||
};
|
||||
|
||||
// Set fullscreen mode after we setup everything
|
||||
if let Some(ref monitor) = win_attribs.fullscreen {
|
||||
unsafe {
|
||||
if monitor.inner != get_current_monitor().inner {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
window.set_fullscreen(Some(monitor.clone()));
|
||||
}
|
||||
|
||||
// Make key have to be after set fullscreen
|
||||
// to prevent normal size window brefly appears
|
||||
unsafe {
|
||||
if win_attribs.visible {
|
||||
window.window.makeKeyAndOrderFront_(nil);
|
||||
} else {
|
||||
window.window.makeKeyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
if win_attribs.maximized {
|
||||
window.delegate.state.perform_maximized(win_attribs.maximized);
|
||||
}
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
|
@ -424,19 +631,11 @@ impl Window2 {
|
|||
}
|
||||
};
|
||||
|
||||
let masks = if screen.is_some() {
|
||||
// Fullscreen window
|
||||
NSWindowStyleMask::NSBorderlessWindowMask |
|
||||
NSWindowStyleMask::NSResizableWindowMask |
|
||||
NSWindowStyleMask::NSTitledWindowMask
|
||||
} else if !attrs.decorations {
|
||||
// Window2 without a titlebar
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
} else if pl_attrs.titlebar_hidden {
|
||||
let masks = if pl_attrs.titlebar_hidden {
|
||||
NSWindowStyleMask::NSBorderlessWindowMask |
|
||||
NSWindowStyleMask::NSResizableWindowMask
|
||||
} else if !pl_attrs.titlebar_transparent {
|
||||
// Window2 with a titlebar
|
||||
} else if pl_attrs.titlebar_transparent {
|
||||
// Window2 with a transparent titlebar and regular content view
|
||||
NSWindowStyleMask::NSClosableWindowMask |
|
||||
NSWindowStyleMask::NSMiniaturizableWindowMask |
|
||||
NSWindowStyleMask::NSResizableWindowMask |
|
||||
|
@ -449,11 +648,16 @@ impl Window2 {
|
|||
NSWindowStyleMask::NSTitledWindowMask |
|
||||
NSWindowStyleMask::NSFullSizeContentViewWindowMask
|
||||
} else {
|
||||
// Window2 with a transparent titlebar and regular content view
|
||||
if !attrs.decorations && !screen.is_some() {
|
||||
// Window2 without a titlebar
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
} else {
|
||||
// Window2 with a titlebar
|
||||
NSWindowStyleMask::NSClosableWindowMask |
|
||||
NSWindowStyleMask::NSMiniaturizableWindowMask |
|
||||
NSWindowStyleMask::NSResizableWindowMask |
|
||||
NSWindowStyleMask::NSTitledWindowMask
|
||||
}
|
||||
};
|
||||
|
||||
let winit_window = Class::get("WinitWindow").unwrap_or_else(|| {
|
||||
|
@ -504,12 +708,7 @@ impl Window2 {
|
|||
window.setTitlebarAppearsTransparent_(YES);
|
||||
}
|
||||
|
||||
if screen.is_some() {
|
||||
window.setLevel_(appkit::NSMainMenuWindowLevel as i64 + 1);
|
||||
}
|
||||
else {
|
||||
window.center();
|
||||
}
|
||||
window
|
||||
})
|
||||
}
|
||||
|
@ -517,7 +716,7 @@ impl Window2 {
|
|||
|
||||
fn create_view(window: id) -> Option<IdRef> {
|
||||
unsafe {
|
||||
let view = IdRef::new(NSView::alloc(nil).init());
|
||||
let view = IdRef::new(NSView::init(NSView::alloc(nil)));
|
||||
view.non_nil().map(|view| {
|
||||
view.setWantsBestResolutionOpenGLSurface_(YES);
|
||||
window.setContentView_(*view);
|
||||
|
@ -719,23 +918,91 @@ impl Window2 {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, _maximized: bool) {
|
||||
unimplemented!()
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
self.delegate.state.perform_maximized(maximized)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorId>) {
|
||||
unimplemented!()
|
||||
/// TODO: Right now set_fullscreen do not work on switching monitors
|
||||
/// in fullscreen mode
|
||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
|
||||
let state = &self.delegate.state;
|
||||
let current = {
|
||||
let win_attribs = state.win_attribs.borrow_mut();
|
||||
|
||||
let current = win_attribs.fullscreen.clone();
|
||||
match (¤t, monitor) {
|
||||
(&None, None) => {
|
||||
return;
|
||||
}
|
||||
(&Some(ref a), Some(ref b)) if a.inner != b.inner => {
|
||||
unimplemented!();
|
||||
}
|
||||
(&Some(_), Some(_)) => {
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
current
|
||||
};
|
||||
|
||||
unsafe {
|
||||
// Because toggleFullScreen will not work if the StyleMask is none,
|
||||
// We set a normal style to it temporary.
|
||||
// It will clean up at window_did_exit_fullscreen.
|
||||
if current.is_none() {
|
||||
let curr_mask = state.window.styleMask();
|
||||
|
||||
if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) {
|
||||
state.window.setStyleMask_(
|
||||
NSWindowStyleMask::NSTitledWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask,
|
||||
);
|
||||
state.save_style_mask.set(Some(curr_mask));
|
||||
}
|
||||
}
|
||||
|
||||
self.window.toggleFullScreen_(nil);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, _decorations: bool) {
|
||||
unimplemented!()
|
||||
pub fn set_decorations(&self, decorations: bool) {
|
||||
let state = &self.delegate.state;
|
||||
let mut win_attribs = state.win_attribs.borrow_mut();
|
||||
|
||||
if win_attribs.decorations == decorations {
|
||||
return;
|
||||
}
|
||||
|
||||
win_attribs.decorations = decorations;
|
||||
|
||||
// Skip modifiy if we are in fullscreen mode,
|
||||
// window_did_exit_fullscreen will handle it
|
||||
if win_attribs.fullscreen.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let new_mask = if decorations {
|
||||
NSWindowStyleMask::NSClosableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask
|
||||
| NSWindowStyleMask::NSResizableWindowMask
|
||||
| NSWindowStyleMask::NSTitledWindowMask
|
||||
} else {
|
||||
NSWindowStyleMask::NSBorderlessWindowMask
|
||||
};
|
||||
|
||||
state.window.setStyleMask_(new_mask);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_monitor(&self) -> RootMonitorId {
|
||||
unimplemented!()
|
||||
unsafe {
|
||||
self::get_current_monitor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue