2022-12-22 21:35:33 +02:00
|
|
|
#![allow(clippy::unnecessary_cast)]
|
2024-01-14 05:19:23 +01:00
|
|
|
use std::cell::{Cell, RefCell};
|
|
|
|
|
use std::collections::VecDeque;
|
2024-06-06 12:32:02 +02:00
|
|
|
use std::sync::{Arc, Mutex};
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
use core_graphics::display::{CGDisplay, CGPoint};
|
|
|
|
|
use monitor::VideoModeHandle;
|
2024-05-27 14:49:22 +02:00
|
|
|
use objc2::rc::{autoreleasepool, Retained};
|
2023-12-23 23:07:55 +01:00
|
|
|
use objc2::runtime::{AnyObject, ProtocolObject};
|
2024-05-06 17:09:38 +02:00
|
|
|
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
2024-04-18 17:34:19 +02:00
|
|
|
use objc2_app_kit::{
|
|
|
|
|
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
|
2024-04-27 15:41:14 +02:00
|
|
|
NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination,
|
2024-04-18 17:34:19 +02:00
|
|
|
NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView,
|
|
|
|
|
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
|
|
|
|
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
|
|
|
|
NSWindowTabbingMode, NSWindowTitleVisibility,
|
|
|
|
|
};
|
|
|
|
|
use objc2_foundation::{
|
2024-05-06 17:09:38 +02:00
|
|
|
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDistributedNotificationCenter,
|
|
|
|
|
NSObject, NSObjectNSDelayedPerforming, NSObjectNSThreadPerformAdditions, NSObjectProtocol,
|
|
|
|
|
NSPoint, NSRect, NSSize, NSString,
|
2024-04-18 17:34:19 +02:00
|
|
|
};
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2024-06-06 14:39:31 +02:00
|
|
|
use super::app_state::ApplicationDelegate;
|
2024-01-14 05:19:23 +01:00
|
|
|
use super::cursor::cursor_from_icon;
|
|
|
|
|
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
2024-06-06 12:32:02 +02:00
|
|
|
use super::observer::RunLoop;
|
2024-01-14 05:19:23 +01:00
|
|
|
use super::view::WinitView;
|
|
|
|
|
use super::window::WinitWindow;
|
|
|
|
|
use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId};
|
|
|
|
|
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
|
|
|
|
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
2024-06-06 12:32:02 +02:00
|
|
|
use crate::event::{InnerSizeWriter, WindowEvent};
|
2024-01-14 05:19:23 +01:00
|
|
|
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
|
|
|
|
|
use crate::window::{
|
|
|
|
|
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
|
|
|
|
WindowAttributes, WindowButtons, WindowLevel,
|
2019-05-01 17:03:30 -06:00
|
|
|
};
|
|
|
|
|
|
2024-01-17 23:37:28 +01:00
|
|
|
#[derive(Clone, Debug)]
|
2024-01-31 17:29:59 +04:00
|
|
|
pub struct PlatformSpecificWindowAttributes {
|
2024-01-14 05:19:23 +01:00
|
|
|
pub movable_by_window_background: bool,
|
|
|
|
|
pub titlebar_transparent: bool,
|
|
|
|
|
pub title_hidden: bool,
|
|
|
|
|
pub titlebar_hidden: bool,
|
|
|
|
|
pub titlebar_buttons_hidden: bool,
|
|
|
|
|
pub fullsize_content_view: bool,
|
|
|
|
|
pub disallow_hidpi: bool,
|
|
|
|
|
pub has_shadow: bool,
|
|
|
|
|
pub accepts_first_mouse: bool,
|
|
|
|
|
pub tabbing_identifier: Option<String>,
|
|
|
|
|
pub option_as_alt: OptionAsAlt,
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
impl Default for PlatformSpecificWindowAttributes {
|
2024-01-14 05:19:23 +01:00
|
|
|
#[inline]
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
movable_by_window_background: false,
|
|
|
|
|
titlebar_transparent: false,
|
|
|
|
|
title_hidden: false,
|
|
|
|
|
titlebar_hidden: false,
|
|
|
|
|
titlebar_buttons_hidden: false,
|
|
|
|
|
fullsize_content_view: false,
|
|
|
|
|
disallow_hidpi: false,
|
|
|
|
|
has_shadow: true,
|
|
|
|
|
accepts_first_mouse: true,
|
|
|
|
|
tabbing_identifier: None,
|
|
|
|
|
option_as_alt: Default::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-08 22:36:42 +03:00
|
|
|
#[derive(Debug)]
|
2023-12-23 18:04:24 +01:00
|
|
|
pub(crate) struct State {
|
2024-05-06 16:29:07 +02:00
|
|
|
/// Strong reference to the global application state.
|
2024-05-27 14:49:22 +02:00
|
|
|
app_delegate: Retained<ApplicationDelegate>,
|
2024-05-06 16:29:07 +02:00
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
window: Retained<WinitWindow>,
|
2023-12-23 18:04:24 +01:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
current_theme: Cell<Option<Theme>>,
|
2023-07-08 22:36:42 +03:00
|
|
|
|
|
|
|
|
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
2023-12-24 10:12:09 +01:00
|
|
|
//
|
|
|
|
|
// This is expressed in native screen coordinates.
|
|
|
|
|
previous_position: Cell<Option<NSPoint>>,
|
2023-07-08 22:36:42 +03:00
|
|
|
|
|
|
|
|
// Used to prevent redundant events.
|
|
|
|
|
previous_scale_factor: Cell<f64>,
|
2024-01-14 05:19:23 +01:00
|
|
|
|
|
|
|
|
/// The current resize increments for the window content.
|
|
|
|
|
resize_increments: Cell<NSSize>,
|
|
|
|
|
/// Whether the window is showing decorations.
|
|
|
|
|
decorations: Cell<bool>,
|
|
|
|
|
resizable: Cell<bool>,
|
|
|
|
|
maximized: Cell<bool>,
|
|
|
|
|
|
|
|
|
|
/// Presentation options saved before entering `set_simple_fullscreen`, and
|
|
|
|
|
/// restored upon exiting it. Also used when transitioning from Borderless to
|
|
|
|
|
/// Exclusive fullscreen in `set_fullscreen` because we need to disable the menu
|
|
|
|
|
/// bar in exclusive fullscreen but want to restore the original options when
|
|
|
|
|
/// transitioning back to borderless fullscreen.
|
|
|
|
|
save_presentation_opts: Cell<Option<NSApplicationPresentationOptions>>,
|
2024-01-31 17:29:59 +04:00
|
|
|
// This is set when WindowAttributes::with_fullscreen was set,
|
2024-01-14 05:19:23 +01:00
|
|
|
// see comments of `window_did_fail_to_enter_fullscreen`
|
|
|
|
|
initial_fullscreen: Cell<bool>,
|
|
|
|
|
/// This field tracks the current fullscreen state of the window
|
|
|
|
|
/// (as seen by `WindowDelegate`).
|
|
|
|
|
fullscreen: RefCell<Option<Fullscreen>>,
|
|
|
|
|
// If it is attempted to toggle fullscreen when in_fullscreen_transition is true,
|
|
|
|
|
// Set target_fullscreen and do after fullscreen transition is end.
|
|
|
|
|
target_fullscreen: RefCell<Option<Option<Fullscreen>>>,
|
|
|
|
|
// This is true between windowWillEnterFullScreen and windowDidEnterFullScreen
|
|
|
|
|
// or windowWillExitFullScreen and windowDidExitFullScreen.
|
|
|
|
|
// We must not toggle fullscreen when this is true.
|
|
|
|
|
in_fullscreen_transition: Cell<bool>,
|
|
|
|
|
standard_frame: Cell<Option<NSRect>>,
|
|
|
|
|
is_simple_fullscreen: Cell<bool>,
|
|
|
|
|
saved_style: Cell<Option<NSWindowStyleMask>>,
|
2023-07-08 22:36:42 +03:00
|
|
|
}
|
|
|
|
|
|
2022-09-08 16:45:29 +02:00
|
|
|
declare_class!(
|
2024-01-14 05:19:23 +01:00
|
|
|
pub(crate) struct WindowDelegate;
|
2023-07-29 00:33:03 +02:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
unsafe impl ClassType for WindowDelegate {
|
2022-09-02 18:46:18 +02:00
|
|
|
type Super = NSObject;
|
2023-12-23 23:07:55 +01:00
|
|
|
type Mutability = mutability::MainThreadOnly;
|
2023-07-29 00:33:03 +02:00
|
|
|
const NAME: &'static str = "WinitWindowDelegate";
|
2022-09-02 18:46:18 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
impl DeclaredClass for WindowDelegate {
|
2023-12-23 18:04:24 +01:00
|
|
|
type Ivars = State;
|
2022-09-02 18:46:18 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
unsafe impl NSObjectProtocol for WindowDelegate {}
|
2023-12-23 23:07:55 +01:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
unsafe impl NSWindowDelegate for WindowDelegate {
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowShouldClose:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowShouldClose:");
|
2023-01-22 23:29:38 +01:00
|
|
|
self.queue_event(WindowEvent::CloseRequested);
|
2022-09-02 19:38:32 +02:00
|
|
|
false
|
|
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowWillClose:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_will_close(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowWillClose:");
|
2022-09-08 16:45:29 +02:00
|
|
|
// `setDelegate:` retains the previous value and then autoreleases it
|
|
|
|
|
autoreleasepool(|_| {
|
|
|
|
|
// Since El Capitan, we need to be careful that delegate methods can't
|
|
|
|
|
// be called after the window closes.
|
2024-01-14 05:19:23 +01:00
|
|
|
self.window().setDelegate(None);
|
2022-09-02 19:38:32 +02:00
|
|
|
});
|
2023-01-22 23:29:38 +01:00
|
|
|
self.queue_event(WindowEvent::Destroyed);
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidResize:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_resize(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowDidResize:");
|
2022-09-08 16:45:29 +02:00
|
|
|
// NOTE: WindowEvent::Resized is reported in frameDidChange.
|
|
|
|
|
self.emit_move_event();
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowWillStartLiveResize:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
|
2023-02-15 03:32:55 +03:00
|
|
|
trace_scope!("windowWillStartLiveResize:");
|
|
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
let increments = self.ivars().resize_increments.get();
|
|
|
|
|
self.set_resize_increments_inner(increments);
|
2023-02-15 03:32:55 +03:00
|
|
|
}
|
|
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidEndLiveResize:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
|
2023-02-15 03:32:55 +03:00
|
|
|
trace_scope!("windowDidEndLiveResize:");
|
2024-01-14 05:19:23 +01:00
|
|
|
self.set_resize_increments_inner(NSSize::new(1., 1.));
|
2023-02-15 03:32:55 +03:00
|
|
|
}
|
|
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
// This won't be triggered if the move was part of a resize.
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidMove:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_move(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowDidMove:");
|
2022-09-08 16:45:29 +02:00
|
|
|
self.emit_move_event();
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidChangeBackingProperties:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowDidChangeBackingProperties:");
|
2024-06-06 12:32:02 +02:00
|
|
|
let scale_factor = self.scale_factor();
|
|
|
|
|
if scale_factor == self.ivars().previous_scale_factor.get() {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
self.ivars().previous_scale_factor.set(scale_factor);
|
|
|
|
|
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let this = self.retain();
|
|
|
|
|
RunLoop::main(mtm).queue_closure(move || {
|
|
|
|
|
this.handle_scale_factor_changed(scale_factor);
|
|
|
|
|
});
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidBecomeKey:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_become_key(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowDidBecomeKey:");
|
2022-09-08 16:45:29 +02:00
|
|
|
// TODO: center the cursor if the window had mouse grab when it
|
|
|
|
|
// lost focus
|
2023-01-22 23:29:38 +01:00
|
|
|
self.queue_event(WindowEvent::Focused(true));
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidResignKey:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowDidResignKey:");
|
2022-09-08 16:45:29 +02:00
|
|
|
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
|
|
|
|
// NSWindowDelegate will receive a didResignKey event despite no event
|
|
|
|
|
// being received when the modifiers are released. This is because
|
|
|
|
|
// flagsChanged events are received by the NSView instead of the
|
|
|
|
|
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
|
|
|
|
// easily fall out of synchrony with reality. This requires us to emit
|
|
|
|
|
// a synthetic ModifiersChanged event when we lose focus.
|
2024-01-14 05:19:23 +01:00
|
|
|
self.view().reset_modifiers();
|
Move `ModifiersChanged` variant to `WindowEvent` (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`
* macos: Fix flags_changed for ModifiersChanged variant move
I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Fire a ModifiersChanged event on window_did_resign_key
From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent. It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Fix build
I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS
Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.
It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.
[1]: f79f21641a31da3e4039d41be89047cdcc6028f7
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Add a hook to update stale modifiers
Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags. These are internally stored as a bitmask in
the latter and an enum in the former.
We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`. That's what the hook does.
This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Only call event_mods once when determining whether to update state
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* flags_changed: Memoize window_id collection
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_did_resign_key: Remove synthetic ModifiersChanged event
We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Add a call to update_potentially_stale_modifiers
Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave. Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* key_up: Add a call to update_potentially_stale_modifiers
We also want to make sure modifiers state is synchronized here, too.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Remove update_potentially_stale_modifiers invocation
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Retry CI
* ViewState: Promote visibility of modifiers to the macos impl
This is so that we can interact with the ViewState directly from the
WindowDelegate.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_delegate: Synthetically set modifiers state to empty on resignKey
This logic is implemented similarly on other platforms, so we wish to
regain parity here. Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.
This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Check for modifiers change in window events
* Fix modifier changed on macOS
Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.
The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.
This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.
* Fix unused variable warning
* Move changelog entry into `Unreleased` section
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
|
|
|
|
2023-01-22 23:29:38 +01:00
|
|
|
self.queue_event(WindowEvent::Focused(false));
|
Move `ModifiersChanged` variant to `WindowEvent` (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`
* macos: Fix flags_changed for ModifiersChanged variant move
I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Fire a ModifiersChanged event on window_did_resign_key
From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent. It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Fix build
I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS
Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.
It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.
[1]: f79f21641a31da3e4039d41be89047cdcc6028f7
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Add a hook to update stale modifiers
Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags. These are internally stored as a bitmask in
the latter and an enum in the former.
We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`. That's what the hook does.
This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Only call event_mods once when determining whether to update state
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* flags_changed: Memoize window_id collection
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_did_resign_key: Remove synthetic ModifiersChanged event
We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Add a call to update_potentially_stale_modifiers
Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave. Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* key_up: Add a call to update_potentially_stale_modifiers
We also want to make sure modifiers state is synchronized here, too.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Remove update_potentially_stale_modifiers invocation
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Retry CI
* ViewState: Promote visibility of modifiers to the macos impl
This is so that we can interact with the ViewState directly from the
WindowDelegate.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_delegate: Synthetically set modifiers state to empty on resignKey
This logic is implemented similarly on other platforms, so we wish to
regain parity here. Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.
This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Check for modifiers change in window events
* Fix modifier changed on macOS
Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.
The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.
This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.
* Fix unused variable warning
* Move changelog entry into `Unreleased` section
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
|
|
|
}
|
|
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
/// Invoked when before enter fullscreen
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowWillEnterFullScreen:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
2022-11-03 22:33:38 +01:00
|
|
|
trace_scope!("windowWillEnterFullScreen:");
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
self.ivars().maximized.set(self.is_zoomed());
|
|
|
|
|
let mut fullscreen = self.ivars().fullscreen.borrow_mut();
|
2024-01-14 04:44:10 +01:00
|
|
|
match &*fullscreen {
|
2022-09-08 16:45:29 +02:00
|
|
|
// Exclusive mode sets the state in `set_fullscreen` as the user
|
|
|
|
|
// can't enter exclusive mode by other means (like the
|
|
|
|
|
// fullscreen button on the window decorations)
|
|
|
|
|
Some(Fullscreen::Exclusive(_)) => (),
|
|
|
|
|
// `window_will_enter_fullscreen` was triggered and we're already
|
|
|
|
|
// in fullscreen, so we must've reached here by `set_fullscreen`
|
|
|
|
|
// as it updates the state
|
|
|
|
|
Some(Fullscreen::Borderless(_)) => (),
|
|
|
|
|
// Otherwise, we must've reached fullscreen by the user clicking
|
|
|
|
|
// on the green fullscreen button. Update state!
|
|
|
|
|
None => {
|
2024-01-14 05:19:23 +01:00
|
|
|
let current_monitor = self.current_monitor_inner();
|
2024-01-14 04:44:10 +01:00
|
|
|
*fullscreen = Some(Fullscreen::Borderless(current_monitor));
|
2024-05-27 14:49:22 +02:00
|
|
|
},
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
2024-01-14 05:19:23 +01:00
|
|
|
self.ivars().in_fullscreen_transition.set(true);
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
/// Invoked when before exit fullscreen
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowWillExitFullScreen:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowWillExitFullScreen:");
|
2020-06-09 23:46:33 +02:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
self.ivars().in_fullscreen_transition.set(true);
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-12-25 03:56:56 +09:00
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(window:willUseFullScreenPresentationOptions:)]
|
2022-09-02 19:38:32 +02:00
|
|
|
fn window_will_use_fullscreen_presentation_options(
|
|
|
|
|
&self,
|
2023-08-02 16:30:41 +02:00
|
|
|
_: Option<&AnyObject>,
|
2022-09-08 16:45:29 +02:00
|
|
|
proposed_options: NSApplicationPresentationOptions,
|
|
|
|
|
) -> NSApplicationPresentationOptions {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
|
|
|
|
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
|
|
|
|
// this would be configurable by the user. Unfortunately because of our
|
|
|
|
|
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
|
|
|
|
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
|
|
|
|
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
|
|
|
|
// still want to make this configurable for borderless fullscreen. Right now
|
|
|
|
|
// we don't, for consistency. If we do, it should be documented that the
|
|
|
|
|
// user-provided options are ignored in exclusive fullscreen.
|
2022-09-08 16:45:29 +02:00
|
|
|
let mut options = proposed_options;
|
2024-01-14 05:19:23 +01:00
|
|
|
let fullscreen = self.ivars().fullscreen.borrow();
|
2024-01-14 04:44:10 +01:00
|
|
|
if let Some(Fullscreen::Exclusive(_)) = &*fullscreen {
|
2024-05-27 14:49:22 +02:00
|
|
|
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
Show the menu bar in borderless fullscreen on macOS (#2053)
* In MacOS, only disable menu bar in exclusive fullscreen
* Save and restore fullscreen options in set_fullscreen
* Don't always cache presentation options when entering exclusive fullscreen
This commit caches presentation options when entering exclusive fullscreen
only if we're coming from borderless fullscreen.
Then, when transitioning from exclusive -> borderless, if no cached presentation
options are present, then the default borderless options are applied.
This fixes the menu bar being unavailable when taking the following path:
[not fullscreen] -> [exclusive fullscreen] -> [borderless fullscreen].
Without this commit, the presentation options from [not fullscreen] were being
cached and then applied to [borderless fullscreen].
* Restore the window level when switching to exclusive fullscreen
The hack of using `CGShieldingWindowLevel() + 1` in borderless fullscreen needs
to be undone when switching from [borderless] -> [exclusive] fullscreen,
otherwise there are menu bar glitches when following a path through
[borderless] -> [exclusive] -> [borderless] modes.
Now, this might appear to conflict with the 'always on top' feature which uses
the 'floating window' level, but this feature appears to be broken anyway when
entering and exiting fullscreen with an always-on-top window. So, rather than
introducing logic to attempt to restore to the 'floating' level here, I think
it's better to do the simple thing for now and then introduce logic for
always-on-top windows when fixing the overall fullscreen behaviour.
* Update the changelog
Co-authored-by: Ehden Sinai <ehdens@gmail.com>
Co-authored-by: Francesca Lovebloom <francesca@brainiumstudios.com>
2021-11-19 22:05:36 +01:00
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
options
|
|
|
|
|
}
|
2019-07-29 21:16:14 +03:00
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
/// Invoked when entered fullscreen
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidEnterFullScreen:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
2022-11-03 22:33:38 +01:00
|
|
|
trace_scope!("windowDidEnterFullScreen:");
|
2023-12-23 18:04:24 +01:00
|
|
|
self.ivars().initial_fullscreen.set(false);
|
2024-01-14 05:19:23 +01:00
|
|
|
self.ivars().in_fullscreen_transition.set(false);
|
|
|
|
|
if let Some(target_fullscreen) = self.ivars().target_fullscreen.take() {
|
|
|
|
|
self.set_fullscreen(target_fullscreen);
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
/// Invoked when exited fullscreen
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidExitFullScreen:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
|
2022-11-03 22:33:38 +01:00
|
|
|
trace_scope!("windowDidExitFullScreen:");
|
2019-05-01 17:03:30 -06:00
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
self.restore_state_from_fullscreen();
|
|
|
|
|
self.ivars().in_fullscreen_transition.set(false);
|
|
|
|
|
if let Some(target_fullscreen) = self.ivars().target_fullscreen.take() {
|
|
|
|
|
self.set_fullscreen(target_fullscreen);
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
2019-05-01 17:03:30 -06:00
|
|
|
}
|
2022-07-06 18:46:25 +00:00
|
|
|
|
2022-09-02 19:38:32 +02:00
|
|
|
/// Invoked when fail to enter fullscreen
|
|
|
|
|
///
|
|
|
|
|
/// When this window launch from a fullscreen app (e.g. launch from VS Code
|
2024-02-19 11:58:44 +07:00
|
|
|
/// terminal), it creates a new virtual desktop and a transition animation.
|
2022-09-02 19:38:32 +02:00
|
|
|
/// 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.
|
2024-02-19 11:58:44 +07:00
|
|
|
/// It should be fine as we only do this at initialization (i.e with_fullscreen
|
2022-09-02 19:38:32 +02:00
|
|
|
/// 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.
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidFailToEnterFullScreen:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
|
2022-11-03 22:33:38 +01:00
|
|
|
trace_scope!("windowDidFailToEnterFullScreen:");
|
2024-01-14 05:19:23 +01:00
|
|
|
self.ivars().in_fullscreen_transition.set(false);
|
|
|
|
|
self.ivars().target_fullscreen.replace(None);
|
2023-12-23 18:04:24 +01:00
|
|
|
if self.ivars().initial_fullscreen.get() {
|
2022-09-08 16:45:29 +02:00
|
|
|
unsafe {
|
2024-05-06 17:09:38 +02:00
|
|
|
self.window().performSelector_withObject_afterDelay(
|
|
|
|
|
sel!(toggleFullScreen:),
|
|
|
|
|
None,
|
|
|
|
|
0.5,
|
|
|
|
|
)
|
2022-09-08 16:45:29 +02:00
|
|
|
};
|
|
|
|
|
} else {
|
2024-01-14 05:19:23 +01:00
|
|
|
self.restore_state_from_fullscreen();
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Invoked when the occlusion state of the window changes
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(windowDidChangeOcclusionState:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
2022-09-02 19:38:32 +02:00
|
|
|
trace_scope!("windowDidChangeOcclusionState:");
|
2024-05-27 14:49:22 +02:00
|
|
|
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
|
2023-12-23 23:07:55 +01:00
|
|
|
self.queue_event(WindowEvent::Occluded(!visible));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[method(windowDidChangeScreen:)]
|
|
|
|
|
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
|
|
|
|
|
trace_scope!("windowDidChangeScreen:");
|
2024-01-14 05:19:23 +01:00
|
|
|
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
|
2023-12-23 23:07:55 +01:00
|
|
|
if is_simple_fullscreen {
|
2024-01-14 05:19:23 +01:00
|
|
|
if let Some(screen) = self.window().screen() {
|
|
|
|
|
self.window().setFrame_display(screen.frame(), true);
|
2023-12-23 23:07:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
unsafe impl NSDraggingDestination for WindowDelegate {
|
2023-12-23 23:07:55 +01:00
|
|
|
/// Invoked when the dragged image enters destination bounds or frame
|
|
|
|
|
#[method(draggingEntered:)]
|
|
|
|
|
fn dragging_entered(&self, sender: &NSObject) -> bool {
|
|
|
|
|
trace_scope!("draggingEntered:");
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
|
|
|
|
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
|
|
|
|
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
2023-12-23 23:07:55 +01:00
|
|
|
|
|
|
|
|
filenames.into_iter().for_each(|file| {
|
|
|
|
|
let path = PathBuf::from(file.to_string());
|
|
|
|
|
self.queue_event(WindowEvent::HoveredFile(path));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Invoked when the image is released
|
|
|
|
|
#[method(prepareForDragOperation:)]
|
|
|
|
|
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
|
|
|
|
|
trace_scope!("prepareForDragOperation:");
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Invoked after the released image has been removed from the screen
|
|
|
|
|
#[method(performDragOperation:)]
|
|
|
|
|
fn perform_drag_operation(&self, sender: &NSObject) -> bool {
|
|
|
|
|
trace_scope!("performDragOperation:");
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
|
|
|
|
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
|
|
|
|
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
2023-12-23 23:07:55 +01:00
|
|
|
|
|
|
|
|
filenames.into_iter().for_each(|file| {
|
|
|
|
|
let path = PathBuf::from(file.to_string());
|
|
|
|
|
self.queue_event(WindowEvent::DroppedFile(path));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
2022-09-02 19:38:32 +02:00
|
|
|
}
|
2022-10-19 03:34:36 +09:00
|
|
|
|
2023-12-23 23:07:55 +01:00
|
|
|
/// Invoked when the dragging operation is complete
|
|
|
|
|
#[method(concludeDragOperation:)]
|
|
|
|
|
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
|
|
|
|
|
trace_scope!("concludeDragOperation:");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Invoked when the dragging operation is cancelled
|
|
|
|
|
#[method(draggingExited:)]
|
|
|
|
|
fn dragging_exited(&self, _sender: Option<&NSObject>) {
|
|
|
|
|
trace_scope!("draggingExited:");
|
|
|
|
|
self.queue_event(WindowEvent::HoveredFileCancelled);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
unsafe impl WindowDelegate {
|
2022-10-19 03:34:36 +09:00
|
|
|
// Observe theme change
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(effectiveAppearanceDidChange:)]
|
2024-05-06 17:09:38 +02:00
|
|
|
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
|
2024-01-14 05:19:23 +01:00
|
|
|
trace_scope!("effectiveAppearanceDidChange:");
|
2022-10-19 03:34:36 +09:00
|
|
|
unsafe {
|
2024-05-06 17:09:38 +02:00
|
|
|
self.performSelectorOnMainThread_withObject_waitUntilDone(
|
|
|
|
|
sel!(effectiveAppearanceDidChangedOnMainThread:),
|
|
|
|
|
sender,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
};
|
2022-10-19 03:34:36 +09:00
|
|
|
}
|
|
|
|
|
|
2023-07-29 00:33:03 +02:00
|
|
|
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
2023-08-02 16:30:41 +02:00
|
|
|
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
2023-12-23 23:07:55 +01:00
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let theme = get_ns_theme(mtm);
|
2024-01-14 05:19:23 +01:00
|
|
|
let old_theme = self.ivars().current_theme.replace(Some(theme));
|
2024-01-14 04:44:10 +01:00
|
|
|
if old_theme != Some(theme) {
|
2023-01-22 23:29:38 +01:00
|
|
|
self.queue_event(WindowEvent::ThemeChanged(theme));
|
2022-10-19 03:34:36 +09:00
|
|
|
}
|
|
|
|
|
}
|
2022-09-02 18:46:18 +02:00
|
|
|
}
|
2022-09-02 19:38:32 +02:00
|
|
|
);
|
2022-09-02 18:46:18 +02:00
|
|
|
|
2024-05-06 16:29:07 +02:00
|
|
|
fn new_window(
|
|
|
|
|
app_delegate: &ApplicationDelegate,
|
|
|
|
|
attrs: &WindowAttributes,
|
|
|
|
|
mtm: MainThreadMarker,
|
2024-05-27 14:49:22 +02:00
|
|
|
) -> Option<Retained<WinitWindow>> {
|
2024-01-14 05:19:23 +01:00
|
|
|
autoreleasepool(|_| {
|
|
|
|
|
let screen = match attrs.fullscreen.clone().map(Into::into) {
|
|
|
|
|
Some(Fullscreen::Borderless(Some(monitor)))
|
|
|
|
|
| Some(Fullscreen::Exclusive(VideoModeHandle { monitor, .. })) => {
|
|
|
|
|
monitor.ns_screen(mtm).or_else(|| NSScreen::mainScreen(mtm))
|
|
|
|
|
},
|
|
|
|
|
Some(Fullscreen::Borderless(None)) => NSScreen::mainScreen(mtm),
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
|
|
|
|
let frame = match &screen {
|
|
|
|
|
Some(screen) => screen.frame(),
|
|
|
|
|
None => {
|
|
|
|
|
let scale_factor = NSScreen::mainScreen(mtm)
|
|
|
|
|
.map(|screen| screen.backingScaleFactor() as f64)
|
|
|
|
|
.unwrap_or(1.0);
|
|
|
|
|
let size = match attrs.inner_size {
|
|
|
|
|
Some(size) => {
|
|
|
|
|
let size = size.to_logical(scale_factor);
|
|
|
|
|
NSSize::new(size.width, size.height)
|
|
|
|
|
},
|
|
|
|
|
None => NSSize::new(800.0, 600.0),
|
|
|
|
|
};
|
|
|
|
|
let position = match attrs.position {
|
|
|
|
|
Some(position) => {
|
|
|
|
|
let position = position.to_logical(scale_factor);
|
|
|
|
|
flip_window_screen_coordinates(NSRect::new(
|
|
|
|
|
NSPoint::new(position.x, position.y),
|
|
|
|
|
size,
|
|
|
|
|
))
|
|
|
|
|
},
|
|
|
|
|
// This value is ignored by calling win.center() below
|
|
|
|
|
None => NSPoint::new(0.0, 0.0),
|
|
|
|
|
};
|
|
|
|
|
NSRect::new(position, size)
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-17 23:37:28 +01:00
|
|
|
let mut masks = if (!attrs.decorations && screen.is_none())
|
|
|
|
|
|| attrs.platform_specific.titlebar_hidden
|
|
|
|
|
{
|
2024-01-14 05:19:23 +01:00
|
|
|
// Resizable without a titlebar or borders
|
|
|
|
|
// if decorations is set to false, ignore pl_attrs
|
|
|
|
|
//
|
|
|
|
|
// if the titlebar is hidden, ignore other pl_attrs
|
2024-05-27 14:49:22 +02:00
|
|
|
NSWindowStyleMask::Borderless
|
|
|
|
|
| NSWindowStyleMask::Resizable
|
|
|
|
|
| NSWindowStyleMask::Miniaturizable
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
|
|
|
|
// default case, resizable window with titlebar and titlebar buttons
|
2024-05-27 14:49:22 +02:00
|
|
|
NSWindowStyleMask::Closable
|
|
|
|
|
| NSWindowStyleMask::Miniaturizable
|
|
|
|
|
| NSWindowStyleMask::Resizable
|
|
|
|
|
| NSWindowStyleMask::Titled
|
2024-01-14 05:19:23 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !attrs.resizable {
|
2024-05-27 14:49:22 +02:00
|
|
|
masks &= !NSWindowStyleMask::Resizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) {
|
2024-05-27 14:49:22 +02:00
|
|
|
masks &= !NSWindowStyleMask::Miniaturizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) {
|
2024-05-27 14:49:22 +02:00
|
|
|
masks &= !NSWindowStyleMask::Closable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-17 23:37:28 +01:00
|
|
|
if attrs.platform_specific.fullsize_content_view {
|
2024-05-27 14:49:22 +02:00
|
|
|
masks |= NSWindowStyleMask::FullSizeContentView;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
let window: Option<Retained<WinitWindow>> = unsafe {
|
2024-01-14 05:19:23 +01:00
|
|
|
msg_send_id![
|
|
|
|
|
super(mtm.alloc().set_ivars(())),
|
|
|
|
|
initWithContentRect: frame,
|
2024-05-27 14:49:22 +02:00
|
|
|
styleMask: masks,
|
2024-04-18 17:34:19 +02:00
|
|
|
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
2024-01-14 05:19:23 +01:00
|
|
|
defer: false,
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
let window = window?;
|
|
|
|
|
|
|
|
|
|
// It is very important for correct memory management that we
|
|
|
|
|
// disable the extra release that would otherwise happen when
|
|
|
|
|
// calling `close` on the window.
|
|
|
|
|
unsafe { window.setReleasedWhenClosed(false) };
|
|
|
|
|
|
|
|
|
|
window.setTitle(&NSString::from_str(&attrs.title));
|
|
|
|
|
window.setAcceptsMouseMovedEvents(true);
|
|
|
|
|
|
2024-01-17 23:37:28 +01:00
|
|
|
if let Some(identifier) = &attrs.platform_specific.tabbing_identifier {
|
2024-01-14 05:19:23 +01:00
|
|
|
window.setTabbingIdentifier(&NSString::from_str(identifier));
|
2024-04-18 17:34:19 +02:00
|
|
|
window.setTabbingMode(NSWindowTabbingMode::Preferred);
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if attrs.content_protected {
|
2024-04-18 17:34:19 +02:00
|
|
|
window.setSharingType(NSWindowSharingType::NSWindowSharingNone);
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-17 23:37:28 +01:00
|
|
|
if attrs.platform_specific.titlebar_transparent {
|
2024-01-14 05:19:23 +01:00
|
|
|
window.setTitlebarAppearsTransparent(true);
|
|
|
|
|
}
|
2024-01-17 23:37:28 +01:00
|
|
|
if attrs.platform_specific.title_hidden {
|
2024-04-18 17:34:19 +02:00
|
|
|
window.setTitleVisibility(NSWindowTitleVisibility::NSWindowTitleHidden);
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
2024-01-17 23:37:28 +01:00
|
|
|
if attrs.platform_specific.titlebar_buttons_hidden {
|
2024-01-14 05:19:23 +01:00
|
|
|
for titlebar_button in &[
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
|
NSWindowFullScreenButton,
|
2024-04-18 17:34:19 +02:00
|
|
|
NSWindowButton::NSWindowMiniaturizeButton,
|
|
|
|
|
NSWindowButton::NSWindowCloseButton,
|
|
|
|
|
NSWindowButton::NSWindowZoomButton,
|
2024-01-14 05:19:23 +01:00
|
|
|
] {
|
|
|
|
|
if let Some(button) = window.standardWindowButton(*titlebar_button) {
|
|
|
|
|
button.setHidden(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-17 23:37:28 +01:00
|
|
|
if attrs.platform_specific.movable_by_window_background {
|
2024-01-14 05:19:23 +01:00
|
|
|
window.setMovableByWindowBackground(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
|
2024-04-18 17:34:19 +02:00
|
|
|
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) {
|
2024-01-14 05:19:23 +01:00
|
|
|
button.setEnabled(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 23:37:28 +01:00
|
|
|
if !attrs.platform_specific.has_shadow {
|
2024-01-14 05:19:23 +01:00
|
|
|
window.setHasShadow(false);
|
|
|
|
|
}
|
|
|
|
|
if attrs.position.is_none() {
|
|
|
|
|
window.center();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let view = WinitView::new(
|
2024-05-06 16:29:07 +02:00
|
|
|
app_delegate,
|
2024-01-14 05:19:23 +01:00
|
|
|
&window,
|
2024-01-17 23:37:28 +01:00
|
|
|
attrs.platform_specific.accepts_first_mouse,
|
|
|
|
|
attrs.platform_specific.option_as_alt,
|
2024-01-14 05:19:23 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until
|
|
|
|
|
// macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid
|
|
|
|
|
// always the default system value in favour of the user's code
|
|
|
|
|
#[allow(deprecated)]
|
2024-01-17 23:37:28 +01:00
|
|
|
view.setWantsBestResolutionOpenGLSurface(!attrs.platform_specific.disallow_hidpi);
|
2024-01-14 05:19:23 +01:00
|
|
|
|
|
|
|
|
// On Mojave, views automatically become layer-backed shortly after being added to
|
|
|
|
|
// a window. Changing the layer-backedness of a view breaks the association between
|
|
|
|
|
// the view and its associated OpenGL context. To work around this, on Mojave we
|
|
|
|
|
// explicitly make the view layer-backed up front so that AppKit doesn't do it
|
|
|
|
|
// itself and break the association with its context.
|
|
|
|
|
if unsafe { NSAppKitVersionNumber }.floor() > NSAppKitVersionNumber10_12 {
|
|
|
|
|
view.setWantsLayer(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configure the new view as the "key view" for the window
|
|
|
|
|
window.setContentView(Some(&view));
|
|
|
|
|
window.setInitialFirstResponder(Some(&view));
|
|
|
|
|
|
|
|
|
|
if attrs.transparent {
|
|
|
|
|
window.setOpaque(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// register for drag and drop operations.
|
|
|
|
|
window
|
|
|
|
|
.registerForDraggedTypes(&NSArray::from_id_slice(&[
|
|
|
|
|
unsafe { NSFilenamesPboardType }.copy()
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
Some(window)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WindowDelegate {
|
2024-05-06 16:29:07 +02:00
|
|
|
pub(super) fn new(
|
|
|
|
|
app_delegate: &ApplicationDelegate,
|
|
|
|
|
attrs: WindowAttributes,
|
|
|
|
|
mtm: MainThreadMarker,
|
2024-05-27 14:49:22 +02:00
|
|
|
) -> Result<Retained<Self>, RootOsError> {
|
2024-05-06 16:29:07 +02:00
|
|
|
let window = new_window(app_delegate, &attrs, mtm)
|
2024-01-14 05:19:23 +01:00
|
|
|
.ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?;
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "rwh_06")]
|
|
|
|
|
match attrs.parent_window.map(|handle| handle.0) {
|
|
|
|
|
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
|
|
|
|
|
// SAFETY: Caller ensures the pointer is valid or NULL
|
|
|
|
|
// Unwrap is fine, since the pointer comes from `NonNull`.
|
2024-05-27 14:49:22 +02:00
|
|
|
let parent_view: Retained<NSView> =
|
|
|
|
|
unsafe { Retained::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
|
2024-01-14 05:19:23 +01:00
|
|
|
let parent = parent_view.window().ok_or_else(|| {
|
|
|
|
|
os_error!(OsError::CreationError("parent view should be installed in a window"))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// SAFETY: We know that there are no parent -> child -> parent cycles since the only
|
|
|
|
|
// place in `winit` where we allow making a window a child window is
|
|
|
|
|
// right here, just after it's been created.
|
2024-04-18 17:34:19 +02:00
|
|
|
unsafe {
|
|
|
|
|
parent.addChildWindow_ordered(&window, NSWindowOrderingMode::NSWindowAbove)
|
|
|
|
|
};
|
2024-01-14 05:19:23 +01:00
|
|
|
},
|
|
|
|
|
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
|
|
|
|
|
None => (),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let resize_increments =
|
|
|
|
|
match attrs.resize_increments.map(|i| i.to_logical(window.backingScaleFactor() as _)) {
|
|
|
|
|
Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => {
|
|
|
|
|
NSSize::new(width, height)
|
|
|
|
|
},
|
|
|
|
|
_ => NSSize::new(1., 1.),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let scale_factor = window.backingScaleFactor() as _;
|
|
|
|
|
|
|
|
|
|
let current_theme = match attrs.preferred_theme {
|
|
|
|
|
Some(theme) => Some(theme),
|
|
|
|
|
None => Some(get_ns_theme(mtm)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let delegate = mtm.alloc().set_ivars(State {
|
2024-05-06 16:29:07 +02:00
|
|
|
app_delegate: app_delegate.retain(),
|
2023-12-23 18:04:24 +01:00
|
|
|
window: window.retain(),
|
2024-01-14 05:19:23 +01:00
|
|
|
current_theme: Cell::new(current_theme),
|
2023-12-23 18:04:24 +01:00
|
|
|
previous_position: Cell::new(None),
|
|
|
|
|
previous_scale_factor: Cell::new(scale_factor),
|
2024-01-14 05:19:23 +01:00
|
|
|
resize_increments: Cell::new(resize_increments),
|
|
|
|
|
decorations: Cell::new(attrs.decorations),
|
|
|
|
|
resizable: Cell::new(attrs.resizable),
|
|
|
|
|
maximized: Cell::new(attrs.maximized),
|
|
|
|
|
save_presentation_opts: Cell::new(None),
|
|
|
|
|
initial_fullscreen: Cell::new(attrs.fullscreen.is_some()),
|
|
|
|
|
fullscreen: RefCell::new(None),
|
|
|
|
|
target_fullscreen: RefCell::new(None),
|
|
|
|
|
in_fullscreen_transition: Cell::new(false),
|
|
|
|
|
standard_frame: Cell::new(None),
|
|
|
|
|
is_simple_fullscreen: Cell::new(false),
|
|
|
|
|
saved_style: Cell::new(None),
|
2023-12-23 18:04:24 +01:00
|
|
|
});
|
2024-05-27 14:49:22 +02:00
|
|
|
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
|
2023-12-23 18:04:24 +01:00
|
|
|
|
|
|
|
|
if scale_factor != 1.0 {
|
2024-06-06 12:32:02 +02:00
|
|
|
let delegate = delegate.clone();
|
|
|
|
|
RunLoop::main(mtm).queue_closure(move || {
|
|
|
|
|
delegate.handle_scale_factor_changed(scale_factor);
|
|
|
|
|
});
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
2024-01-14 05:19:23 +01:00
|
|
|
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
2023-12-23 18:04:24 +01:00
|
|
|
|
|
|
|
|
// Enable theme change event
|
2024-05-06 17:09:38 +02:00
|
|
|
let notification_center = unsafe { NSDistributedNotificationCenter::defaultCenter() };
|
|
|
|
|
unsafe {
|
|
|
|
|
notification_center.addObserver_selector_name_object(
|
|
|
|
|
&delegate,
|
|
|
|
|
sel!(effectiveAppearanceDidChange:),
|
|
|
|
|
Some(ns_string!("AppleInterfaceThemeChangedNotification")),
|
|
|
|
|
None,
|
|
|
|
|
)
|
2023-12-23 18:04:24 +01:00
|
|
|
};
|
|
|
|
|
|
2024-01-14 05:19:23 +01:00
|
|
|
if attrs.blur {
|
|
|
|
|
delegate.set_blur(attrs.blur);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(dim) = attrs.min_inner_size {
|
|
|
|
|
delegate.set_min_inner_size(Some(dim));
|
|
|
|
|
}
|
|
|
|
|
if let Some(dim) = attrs.max_inner_size {
|
|
|
|
|
delegate.set_max_inner_size(Some(dim));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delegate.set_window_level(attrs.window_level);
|
|
|
|
|
|
|
|
|
|
delegate.set_cursor(attrs.cursor);
|
|
|
|
|
|
|
|
|
|
// XXX Send `Focused(false)` right after creating the window delegate, so we won't
|
|
|
|
|
// obscure the real focused events on the startup.
|
|
|
|
|
delegate.queue_event(WindowEvent::Focused(false));
|
|
|
|
|
|
|
|
|
|
// Set fullscreen mode after we setup everything
|
|
|
|
|
delegate.set_fullscreen(attrs.fullscreen.map(Into::into));
|
|
|
|
|
|
|
|
|
|
// Setting the window as key has to happen *after* we set the fullscreen
|
|
|
|
|
// state, since otherwise we'll briefly see the window at normal size
|
|
|
|
|
// before it transitions.
|
|
|
|
|
if attrs.visible {
|
|
|
|
|
if attrs.active {
|
|
|
|
|
// Tightly linked with `app_state::window_activation_hack`
|
|
|
|
|
window.makeKeyAndOrderFront(None);
|
|
|
|
|
} else {
|
|
|
|
|
window.orderFront(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if attrs.maximized {
|
|
|
|
|
delegate.set_maximized(attrs.maximized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(delegate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[track_caller]
|
2024-05-27 14:49:22 +02:00
|
|
|
pub(super) fn view(&self) -> Retained<WinitView> {
|
2024-01-14 05:19:23 +01:00
|
|
|
// SAFETY: The view inside WinitWindow is always `WinitView`
|
2024-05-27 14:49:22 +02:00
|
|
|
unsafe { Retained::cast(self.window().contentView().unwrap()) }
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[track_caller]
|
|
|
|
|
pub(super) fn window(&self) -> &WinitWindow {
|
|
|
|
|
&self.ivars().window
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[track_caller]
|
|
|
|
|
pub(crate) fn id(&self) -> WindowId {
|
|
|
|
|
self.window().id()
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-31 00:39:01 +04:00
|
|
|
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
2024-06-06 12:32:02 +02:00
|
|
|
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-06 12:32:02 +02:00
|
|
|
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
|
|
|
|
|
let app_delegate = &self.ivars().app_delegate;
|
|
|
|
|
let window = self.window();
|
2022-09-08 16:45:29 +02:00
|
|
|
|
2024-06-06 12:32:02 +02:00
|
|
|
let content_size = window.contentRectForFrameRect(window.frame()).size;
|
2023-12-24 10:12:09 +01:00
|
|
|
let content_size = LogicalSize::new(content_size.width, content_size.height);
|
2024-01-14 03:37:53 +01:00
|
|
|
|
2024-06-06 12:32:02 +02:00
|
|
|
let suggested_size = content_size.to_physical(scale_factor);
|
|
|
|
|
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
|
|
|
|
app_delegate.handle_window_event(window.id(), WindowEvent::ScaleFactorChanged {
|
2022-09-08 16:45:29 +02:00
|
|
|
scale_factor,
|
2024-06-06 12:32:02 +02:00
|
|
|
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
|
|
|
|
});
|
|
|
|
|
let physical_size = *new_inner_size.lock().unwrap();
|
|
|
|
|
drop(new_inner_size);
|
|
|
|
|
|
|
|
|
|
if physical_size != suggested_size {
|
|
|
|
|
let logical_size = physical_size.to_logical(scale_factor);
|
|
|
|
|
let size = NSSize::new(logical_size.width, logical_size.height);
|
|
|
|
|
window.setContentSize(size);
|
|
|
|
|
}
|
|
|
|
|
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-08 22:36:42 +03:00
|
|
|
fn emit_move_event(&self) {
|
2024-01-14 05:19:23 +01:00
|
|
|
let frame = self.window().frame();
|
2023-12-24 10:12:09 +01:00
|
|
|
if self.ivars().previous_position.get() == Some(frame.origin) {
|
|
|
|
|
return;
|
2022-09-08 16:45:29 +02:00
|
|
|
}
|
2023-12-24 10:12:09 +01:00
|
|
|
self.ivars().previous_position.set(Some(frame.origin));
|
2022-09-08 16:45:29 +02:00
|
|
|
|
2023-12-24 10:12:09 +01:00
|
|
|
let position = flip_window_screen_coordinates(frame);
|
|
|
|
|
let position =
|
2024-01-14 05:19:23 +01:00
|
|
|
LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor());
|
2023-12-24 10:12:09 +01:00
|
|
|
self.queue_event(WindowEvent::Moved(position));
|
2022-09-02 18:46:18 +02:00
|
|
|
}
|
2024-01-14 05:19:23 +01:00
|
|
|
|
|
|
|
|
fn set_style_mask(&self, mask: NSWindowStyleMask) {
|
|
|
|
|
self.window().setStyleMask(mask);
|
|
|
|
|
// If we don't do this, key handling will break
|
|
|
|
|
// (at least until the window is clicked again/etc.)
|
|
|
|
|
let _ = self.window().makeFirstResponder(Some(&self.view()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_title(&self, title: &str) {
|
|
|
|
|
self.window().setTitle(&NSString::from_str(title))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_transparent(&self, transparent: bool) {
|
|
|
|
|
self.window().setOpaque(!transparent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_blur(&self, blur: bool) {
|
|
|
|
|
// NOTE: in general we want to specify the blur radius, but the choice of 80
|
|
|
|
|
// should be a reasonable default.
|
|
|
|
|
let radius = if blur { 80 } else { 0 };
|
|
|
|
|
let window_number = unsafe { self.window().windowNumber() };
|
|
|
|
|
unsafe {
|
|
|
|
|
ffi::CGSSetWindowBackgroundBlurRadius(
|
|
|
|
|
ffi::CGSMainConnectionID(),
|
|
|
|
|
window_number,
|
|
|
|
|
radius,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_visible(&self, visible: bool) {
|
|
|
|
|
match visible {
|
|
|
|
|
true => self.window().makeKeyAndOrderFront(None),
|
|
|
|
|
false => self.window().orderOut(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_visible(&self) -> Option<bool> {
|
|
|
|
|
Some(self.window().isVisible())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn request_redraw(&self) {
|
2024-05-06 16:29:07 +02:00
|
|
|
self.ivars().app_delegate.queue_redraw(self.window().id());
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn pre_present_notify(&self) {}
|
|
|
|
|
|
|
|
|
|
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
|
|
|
let position = flip_window_screen_coordinates(self.window().frame());
|
|
|
|
|
Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
|
|
|
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
|
|
|
|
|
let position = flip_window_screen_coordinates(content_rect);
|
|
|
|
|
Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_outer_position(&self, position: Position) {
|
|
|
|
|
let position = position.to_logical(self.scale_factor());
|
|
|
|
|
let point = flip_window_screen_coordinates(NSRect::new(
|
|
|
|
|
NSPoint::new(position.x, position.y),
|
|
|
|
|
self.window().frame().size,
|
|
|
|
|
));
|
|
|
|
|
unsafe { self.window().setFrameOrigin(point) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
|
|
|
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
|
|
|
|
|
let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height);
|
|
|
|
|
logical.to_physical(self.scale_factor())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
|
|
|
let frame = self.window().frame();
|
|
|
|
|
let logical = LogicalSize::new(frame.size.width, frame.size.height);
|
|
|
|
|
logical.to_physical(self.scale_factor())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
|
|
|
|
|
let scale_factor = self.scale_factor();
|
|
|
|
|
let size = size.to_logical(scale_factor);
|
|
|
|
|
self.window().setContentSize(NSSize::new(size.width, size.height));
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
|
|
|
|
let dimensions =
|
|
|
|
|
dimensions.unwrap_or(Size::Logical(LogicalSize { width: 0.0, height: 0.0 }));
|
|
|
|
|
let min_size = dimensions.to_logical::<CGFloat>(self.scale_factor());
|
|
|
|
|
|
|
|
|
|
let min_size = NSSize::new(min_size.width, min_size.height);
|
|
|
|
|
unsafe { self.window().setContentMinSize(min_size) };
|
|
|
|
|
|
|
|
|
|
// If necessary, resize the window to match constraint
|
|
|
|
|
let mut current_size = self.window().contentRectForFrameRect(self.window().frame()).size;
|
|
|
|
|
if current_size.width < min_size.width {
|
|
|
|
|
current_size.width = min_size.width;
|
|
|
|
|
}
|
|
|
|
|
if current_size.height < min_size.height {
|
|
|
|
|
current_size.height = min_size.height;
|
|
|
|
|
}
|
|
|
|
|
self.window().setContentSize(current_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
|
|
|
|
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
|
2024-06-15 15:26:26 +03:00
|
|
|
width: f32::MAX as f64,
|
|
|
|
|
height: f32::MAX as f64,
|
2024-01-14 05:19:23 +01:00
|
|
|
}));
|
|
|
|
|
let scale_factor = self.scale_factor();
|
|
|
|
|
let max_size = dimensions.to_logical::<CGFloat>(scale_factor);
|
|
|
|
|
|
|
|
|
|
let max_size = NSSize::new(max_size.width, max_size.height);
|
|
|
|
|
unsafe { self.window().setContentMaxSize(max_size) };
|
|
|
|
|
|
|
|
|
|
// If necessary, resize the window to match constraint
|
|
|
|
|
let mut current_size = self.window().contentRectForFrameRect(self.window().frame()).size;
|
|
|
|
|
if max_size.width < current_size.width {
|
|
|
|
|
current_size.width = max_size.width;
|
|
|
|
|
}
|
|
|
|
|
if max_size.height < current_size.height {
|
|
|
|
|
current_size.height = max_size.height;
|
|
|
|
|
}
|
|
|
|
|
self.window().setContentSize(current_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
|
|
|
|
let increments = self.ivars().resize_increments.get();
|
|
|
|
|
let (w, h) = (increments.width, increments.height);
|
|
|
|
|
if w > 1.0 || h > 1.0 {
|
|
|
|
|
Some(LogicalSize::new(w, h).to_physical(self.scale_factor()))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_resize_increments(&self, increments: Option<Size>) {
|
|
|
|
|
// XXX the resize increments are only used during live resizes.
|
|
|
|
|
self.ivars().resize_increments.set(
|
|
|
|
|
increments
|
|
|
|
|
.map(|increments| {
|
|
|
|
|
let logical = increments.to_logical::<f64>(self.scale_factor());
|
|
|
|
|
NSSize::new(logical.width.max(1.0), logical.height.max(1.0))
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_else(|| NSSize::new(1.0, 1.0)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn set_resize_increments_inner(&self, size: NSSize) {
|
|
|
|
|
// It was concluded (#2411) that there is never a use-case for
|
|
|
|
|
// "outer" resize increments, hence we set "inner" ones here.
|
|
|
|
|
// ("outer" in macOS being just resizeIncrements, and "inner" - contentResizeIncrements)
|
|
|
|
|
// This is consistent with X11 size hints behavior
|
|
|
|
|
self.window().setContentResizeIncrements(size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_resizable(&self, resizable: bool) {
|
|
|
|
|
self.ivars().resizable.set(resizable);
|
|
|
|
|
let fullscreen = self.ivars().fullscreen.borrow().is_some();
|
|
|
|
|
if !fullscreen {
|
2024-05-27 14:49:22 +02:00
|
|
|
let mut mask = self.window().styleMask();
|
2024-01-14 05:19:23 +01:00
|
|
|
if resizable {
|
2024-05-27 14:49:22 +02:00
|
|
|
mask |= NSWindowStyleMask::Resizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-05-27 14:49:22 +02:00
|
|
|
mask &= !NSWindowStyleMask::Resizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
2024-05-27 14:49:22 +02:00
|
|
|
self.set_style_mask(mask);
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
// Otherwise, we don't change the mask until we exit fullscreen.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_resizable(&self) -> bool {
|
|
|
|
|
self.window().isResizable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
2024-05-27 14:49:22 +02:00
|
|
|
let mut mask = self.window().styleMask();
|
2024-01-14 05:19:23 +01:00
|
|
|
|
|
|
|
|
if buttons.contains(WindowButtons::CLOSE) {
|
2024-05-27 14:49:22 +02:00
|
|
|
mask |= NSWindowStyleMask::Closable;
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-05-27 14:49:22 +02:00
|
|
|
mask &= !NSWindowStyleMask::Closable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if buttons.contains(WindowButtons::MINIMIZE) {
|
2024-05-27 14:49:22 +02:00
|
|
|
mask |= NSWindowStyleMask::Miniaturizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-05-27 14:49:22 +02:00
|
|
|
mask &= !NSWindowStyleMask::Miniaturizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This must happen before the button's "enabled" status has been set,
|
|
|
|
|
// hence we do it synchronously.
|
2024-05-27 14:49:22 +02:00
|
|
|
self.set_style_mask(mask);
|
2024-01-14 05:19:23 +01:00
|
|
|
|
|
|
|
|
// 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`).
|
2024-04-18 17:34:19 +02:00
|
|
|
if let Some(button) = self.window().standardWindowButton(NSWindowButton::NSWindowZoomButton)
|
|
|
|
|
{
|
2024-01-14 05:19:23 +01:00
|
|
|
button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn enabled_buttons(&self) -> WindowButtons {
|
|
|
|
|
let mut buttons = WindowButtons::empty();
|
|
|
|
|
if self.window().isMiniaturizable() {
|
|
|
|
|
buttons |= WindowButtons::MINIMIZE;
|
|
|
|
|
}
|
|
|
|
|
if self
|
|
|
|
|
.window()
|
2024-04-18 17:34:19 +02:00
|
|
|
.standardWindowButton(NSWindowButton::NSWindowZoomButton)
|
2024-01-14 05:19:23 +01:00
|
|
|
.map(|b| b.isEnabled())
|
|
|
|
|
.unwrap_or(true)
|
|
|
|
|
{
|
|
|
|
|
buttons |= WindowButtons::MAXIMIZE;
|
|
|
|
|
}
|
|
|
|
|
if self.window().hasCloseBox() {
|
|
|
|
|
buttons |= WindowButtons::CLOSE;
|
|
|
|
|
}
|
|
|
|
|
buttons
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_cursor(&self, cursor: Cursor) {
|
|
|
|
|
let view = self.view();
|
|
|
|
|
|
|
|
|
|
let cursor = match cursor {
|
|
|
|
|
Cursor::Icon(icon) => cursor_from_icon(icon),
|
|
|
|
|
Cursor::Custom(cursor) => cursor.inner.0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if view.cursor_icon() == cursor {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view.set_cursor_icon(cursor);
|
|
|
|
|
self.window().invalidateCursorRectsForView(&view);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
|
|
|
|
let associate_mouse_cursor = match mode {
|
|
|
|
|
CursorGrabMode::Locked => false,
|
|
|
|
|
CursorGrabMode::None => true,
|
|
|
|
|
CursorGrabMode::Confined => {
|
|
|
|
|
return Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
|
|
|
|
|
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
|
|
|
|
|
.map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status))))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_cursor_visible(&self, visible: bool) {
|
|
|
|
|
let view = self.view();
|
|
|
|
|
let state_changed = view.set_cursor_visible(visible);
|
|
|
|
|
if state_changed {
|
|
|
|
|
self.window().invalidateCursorRectsForView(&view);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn scale_factor(&self) -> f64 {
|
|
|
|
|
self.window().backingScaleFactor() as _
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> {
|
|
|
|
|
let physical_window_position = self.inner_position().unwrap();
|
|
|
|
|
let scale_factor = self.scale_factor();
|
|
|
|
|
let window_position = physical_window_position.to_logical::<CGFloat>(scale_factor);
|
|
|
|
|
let logical_cursor_position = cursor_position.to_logical::<CGFloat>(scale_factor);
|
|
|
|
|
let point = CGPoint {
|
|
|
|
|
x: logical_cursor_position.x + window_position.x,
|
|
|
|
|
y: logical_cursor_position.y + window_position.y,
|
|
|
|
|
};
|
|
|
|
|
CGDisplay::warp_mouse_cursor_position(point)
|
|
|
|
|
.map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?;
|
|
|
|
|
CGDisplay::associate_mouse_and_mouse_cursor_position(true)
|
|
|
|
|
.map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let event = NSApplication::sharedApplication(mtm).currentEvent().unwrap();
|
|
|
|
|
self.window().performWindowDragWithEvent(&event);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
|
|
|
|
|
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn show_window_menu(&self, _position: Position) {}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
|
|
|
|
self.window().setIgnoresMouseEvents(!hittest);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn is_zoomed(&self) -> bool {
|
|
|
|
|
// because `isZoomed` doesn't work if the window's borderless,
|
2024-02-19 11:58:44 +07:00
|
|
|
// we make it resizable temporarily.
|
2024-01-14 05:19:23 +01:00
|
|
|
let curr_mask = self.window().styleMask();
|
|
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable;
|
|
|
|
|
let needs_temp_mask = !curr_mask.contains(required);
|
2024-01-14 05:19:23 +01:00
|
|
|
if needs_temp_mask {
|
|
|
|
|
self.set_style_mask(required);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let is_zoomed = self.window().isZoomed();
|
|
|
|
|
|
|
|
|
|
// Roll back temp styles
|
|
|
|
|
if needs_temp_mask {
|
|
|
|
|
self.set_style_mask(curr_mask);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_zoomed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn saved_style(&self) -> NSWindowStyleMask {
|
|
|
|
|
let base_mask =
|
2024-05-27 14:49:22 +02:00
|
|
|
self.ivars().saved_style.take().unwrap_or_else(|| self.window().styleMask());
|
|
|
|
|
if self.ivars().resizable.get() {
|
|
|
|
|
base_mask | NSWindowStyleMask::Resizable
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-05-27 14:49:22 +02:00
|
|
|
base_mask & !NSWindowStyleMask::Resizable
|
|
|
|
|
}
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// This is called when the window is exiting fullscreen, whether by the
|
|
|
|
|
/// user clicking on the green fullscreen button or programmatically by
|
|
|
|
|
/// `toggleFullScreen:`
|
|
|
|
|
pub(crate) fn restore_state_from_fullscreen(&self) {
|
|
|
|
|
self.ivars().fullscreen.replace(None);
|
|
|
|
|
|
|
|
|
|
let maximized = self.ivars().maximized.get();
|
|
|
|
|
let mask = self.saved_style();
|
|
|
|
|
|
|
|
|
|
self.set_style_mask(mask);
|
|
|
|
|
self.set_maximized(maximized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_minimized(&self, minimized: bool) {
|
|
|
|
|
let is_minimized = self.window().isMiniaturized();
|
|
|
|
|
if is_minimized == minimized {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if minimized {
|
|
|
|
|
self.window().miniaturize(Some(self));
|
|
|
|
|
} else {
|
|
|
|
|
unsafe { self.window().deminiaturize(Some(self)) };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_minimized(&self) -> Option<bool> {
|
|
|
|
|
Some(self.window().isMiniaturized())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_maximized(&self, maximized: bool) {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let is_zoomed = self.is_zoomed();
|
|
|
|
|
if is_zoomed == maximized {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Save the standard frame sized if it is not zoomed
|
|
|
|
|
if !is_zoomed {
|
|
|
|
|
self.ivars().standard_frame.set(Some(self.window().frame()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.ivars().maximized.set(maximized);
|
|
|
|
|
|
|
|
|
|
if self.ivars().fullscreen.borrow().is_some() {
|
|
|
|
|
// Handle it in window_did_exit_fullscreen
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
if self.window().styleMask().contains(NSWindowStyleMask::Resizable) {
|
2024-01-14 05:19:23 +01:00
|
|
|
// Just use the native zoom if resizable
|
|
|
|
|
self.window().zoom(None);
|
|
|
|
|
} else {
|
|
|
|
|
// if it's not resizable, we set the frame directly
|
|
|
|
|
let new_rect = if maximized {
|
|
|
|
|
let screen = NSScreen::mainScreen(mtm).expect("no screen found");
|
|
|
|
|
screen.visibleFrame()
|
|
|
|
|
} else {
|
|
|
|
|
self.ivars().standard_frame.get().unwrap_or(DEFAULT_STANDARD_FRAME)
|
|
|
|
|
};
|
|
|
|
|
self.window().setFrame_display(new_rect, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
|
|
|
|
self.ivars().fullscreen.borrow().clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_maximized(&self) -> bool {
|
|
|
|
|
self.is_zoomed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let app = NSApplication::sharedApplication(mtm);
|
|
|
|
|
|
|
|
|
|
if self.ivars().is_simple_fullscreen.get() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if self.ivars().in_fullscreen_transition.get() {
|
|
|
|
|
// We can't set fullscreen here.
|
|
|
|
|
// Set fullscreen after transition.
|
|
|
|
|
self.ivars().target_fullscreen.replace(Some(fullscreen));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let old_fullscreen = self.ivars().fullscreen.borrow().clone();
|
|
|
|
|
if fullscreen == old_fullscreen {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the fullscreen is on a different monitor, we must move the window
|
|
|
|
|
// to that monitor before we toggle fullscreen (as `toggleFullScreen`
|
|
|
|
|
// does not take a screen parameter, but uses the current screen)
|
|
|
|
|
if let Some(ref fullscreen) = fullscreen {
|
|
|
|
|
let new_screen = match fullscreen {
|
|
|
|
|
Fullscreen::Borderless(Some(monitor)) => monitor.clone(),
|
|
|
|
|
Fullscreen::Borderless(None) => {
|
|
|
|
|
if let Some(monitor) = self.current_monitor_inner() {
|
|
|
|
|
monitor
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Fullscreen::Exclusive(video_mode) => video_mode.monitor(),
|
|
|
|
|
}
|
|
|
|
|
.ns_screen(mtm)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let old_screen = self.window().screen().unwrap();
|
|
|
|
|
if old_screen != new_screen {
|
|
|
|
|
unsafe { self.window().setFrameOrigin(new_screen.frame().origin) };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
|
|
|
|
|
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
|
|
|
|
|
// same thing as we're doing here (captures the display, sets the
|
|
|
|
|
// video mode, and hides the menu bar and dock), with the exception
|
|
|
|
|
// of that I couldn't figure out how to set the display mode with
|
|
|
|
|
// it. I think `enterFullScreenMode:withOptions:` is still using the
|
|
|
|
|
// older display mode API where display modes were of the type
|
|
|
|
|
// `CFDictionary`, but this has changed, so we can't obtain the
|
|
|
|
|
// correct parameter for this any longer. Apple's code samples for
|
|
|
|
|
// this function seem to just pass in "YES" for the display mode
|
|
|
|
|
// parameter, which is not consistent with the docs saying that it
|
|
|
|
|
// takes a `NSDictionary`..
|
|
|
|
|
|
|
|
|
|
let display_id = video_mode.monitor().native_identifier();
|
|
|
|
|
|
|
|
|
|
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
|
|
|
|
|
|
|
|
|
|
if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) {
|
|
|
|
|
self.ivars().save_presentation_opts.replace(Some(app.presentationOptions()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
// Fade to black (and wait for the fade to complete) to hide the
|
|
|
|
|
// flicker from capturing the display and switching display mode
|
|
|
|
|
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
|
|
|
|
|
== ffi::kCGErrorSuccess
|
|
|
|
|
{
|
|
|
|
|
ffi::CGDisplayFade(
|
|
|
|
|
fade_token,
|
|
|
|
|
0.3,
|
|
|
|
|
ffi::kCGDisplayBlendNormal,
|
|
|
|
|
ffi::kCGDisplayBlendSolidColor,
|
|
|
|
|
0.0,
|
|
|
|
|
0.0,
|
|
|
|
|
0.0,
|
|
|
|
|
ffi::TRUE,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
let result = ffi::CGDisplaySetDisplayMode(
|
|
|
|
|
display_id,
|
|
|
|
|
video_mode.native_mode.0,
|
|
|
|
|
std::ptr::null(),
|
|
|
|
|
);
|
|
|
|
|
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
|
|
|
|
|
|
|
|
|
|
// After the display has been configured, fade back in
|
|
|
|
|
// asynchronously
|
|
|
|
|
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
|
|
|
|
|
ffi::CGDisplayFade(
|
|
|
|
|
fade_token,
|
|
|
|
|
0.6,
|
|
|
|
|
ffi::kCGDisplayBlendSolidColor,
|
|
|
|
|
ffi::kCGDisplayBlendNormal,
|
|
|
|
|
0.0,
|
|
|
|
|
0.0,
|
|
|
|
|
0.0,
|
|
|
|
|
ffi::FALSE,
|
|
|
|
|
);
|
|
|
|
|
ffi::CGReleaseDisplayFadeReservation(fade_token);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.ivars().fullscreen.replace(fullscreen.clone());
|
|
|
|
|
|
|
|
|
|
fn toggle_fullscreen(window: &WinitWindow) {
|
|
|
|
|
// Window level must be restored from `CGShieldingWindowLevel()
|
|
|
|
|
// + 1` back to normal in order for `toggleFullScreen` to do
|
|
|
|
|
// anything
|
|
|
|
|
window.setLevel(ffi::kCGNormalWindowLevel as NSWindowLevel);
|
|
|
|
|
window.toggleFullScreen(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match (old_fullscreen, fullscreen) {
|
|
|
|
|
(None, Some(_)) => {
|
|
|
|
|
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
|
|
|
|
// set a normal style temporarily. The previous state will be
|
|
|
|
|
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
|
|
|
|
let curr_mask = self.window().styleMask();
|
2024-05-27 14:49:22 +02:00
|
|
|
let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable;
|
|
|
|
|
if !curr_mask.contains(required) {
|
2024-01-14 05:19:23 +01:00
|
|
|
self.set_style_mask(required);
|
|
|
|
|
self.ivars().saved_style.set(Some(curr_mask));
|
|
|
|
|
}
|
|
|
|
|
toggle_fullscreen(self.window());
|
|
|
|
|
},
|
|
|
|
|
(Some(Fullscreen::Borderless(_)), None) => {
|
|
|
|
|
// State is restored by `window_did_exit_fullscreen`
|
|
|
|
|
toggle_fullscreen(self.window());
|
|
|
|
|
},
|
|
|
|
|
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
|
|
|
|
|
unsafe {
|
|
|
|
|
ffi::CGRestorePermanentDisplayConfiguration();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
|
|
|
|
|
ffi::kCGErrorSuccess
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
toggle_fullscreen(self.window());
|
|
|
|
|
},
|
|
|
|
|
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
|
|
|
|
|
// If we're already in fullscreen mode, calling
|
|
|
|
|
// `CGDisplayCapture` will place the shielding window on top of
|
|
|
|
|
// our window, which results in a black display and is not what
|
|
|
|
|
// we want. So, we must place our window on top of the shielding
|
|
|
|
|
// window. Unfortunately, this also makes our window be on top
|
|
|
|
|
// of the menu bar, and this looks broken, so we must make sure
|
|
|
|
|
// that the menu bar is disabled. This is done in the window
|
|
|
|
|
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
|
|
|
|
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
|
|
|
|
|
|
2024-05-27 14:49:22 +02:00
|
|
|
let presentation_options =
|
|
|
|
|
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
2024-01-14 05:19:23 +01:00
|
|
|
app.setPresentationOptions(presentation_options);
|
|
|
|
|
|
|
|
|
|
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
|
|
|
|
|
self.window().setLevel(window_level);
|
|
|
|
|
},
|
|
|
|
|
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
|
|
|
|
|
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
|
2024-05-27 14:49:22 +02:00
|
|
|
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
2024-01-14 05:19:23 +01:00
|
|
|
);
|
|
|
|
|
app.setPresentationOptions(presentation_options);
|
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
ffi::CGRestorePermanentDisplayConfiguration();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
|
|
|
|
|
ffi::kCGErrorSuccess
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Restore the normal window level following the Borderless fullscreen
|
|
|
|
|
// `CGShieldingWindowLevel() + 1` hack.
|
|
|
|
|
self.window().setLevel(ffi::kCGNormalWindowLevel as NSWindowLevel);
|
|
|
|
|
},
|
|
|
|
|
_ => {},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_decorations(&self, decorations: bool) {
|
|
|
|
|
if decorations == self.ivars().decorations.get() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.ivars().decorations.set(decorations);
|
|
|
|
|
|
|
|
|
|
let fullscreen = self.ivars().fullscreen.borrow().is_some();
|
|
|
|
|
let resizable = self.ivars().resizable.get();
|
|
|
|
|
|
|
|
|
|
// If we're in fullscreen mode, we wait to apply decoration changes
|
|
|
|
|
// until we're in `window_did_exit_fullscreen`.
|
|
|
|
|
if fullscreen {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let new_mask = {
|
|
|
|
|
let mut new_mask = if decorations {
|
2024-05-27 14:49:22 +02:00
|
|
|
NSWindowStyleMask::Closable
|
|
|
|
|
| NSWindowStyleMask::Miniaturizable
|
|
|
|
|
| NSWindowStyleMask::Resizable
|
|
|
|
|
| NSWindowStyleMask::Titled
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-05-27 14:49:22 +02:00
|
|
|
NSWindowStyleMask::Borderless | NSWindowStyleMask::Resizable
|
2024-01-14 05:19:23 +01:00
|
|
|
};
|
|
|
|
|
if !resizable {
|
2024-05-27 14:49:22 +02:00
|
|
|
new_mask &= !NSWindowStyleMask::Resizable;
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
new_mask
|
|
|
|
|
};
|
2024-05-27 14:49:22 +02:00
|
|
|
self.set_style_mask(new_mask);
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_decorated(&self) -> bool {
|
|
|
|
|
self.ivars().decorations.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_window_level(&self, level: WindowLevel) {
|
|
|
|
|
let level = match level {
|
|
|
|
|
WindowLevel::AlwaysOnTop => ffi::kCGFloatingWindowLevel as NSWindowLevel,
|
|
|
|
|
WindowLevel::AlwaysOnBottom => (ffi::kCGNormalWindowLevel - 1) as NSWindowLevel,
|
|
|
|
|
WindowLevel::Normal => ffi::kCGNormalWindowLevel as NSWindowLevel,
|
|
|
|
|
};
|
|
|
|
|
self.window().setLevel(level);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_window_icon(&self, _icon: Option<Icon>) {
|
|
|
|
|
// macOS doesn't have window icons. Though, there is
|
|
|
|
|
// `setRepresentedFilename`, but that's semantically distinct and should
|
|
|
|
|
// only be used when the window is in some way representing a specific
|
|
|
|
|
// file/directory. For instance, Terminal.app uses this for the CWD.
|
|
|
|
|
// Anyway, that should eventually be implemented as
|
2024-01-31 17:29:59 +04:00
|
|
|
// `WindowAttributesExt::with_represented_file` or something, and doesn't
|
2024-01-14 05:19:23 +01:00
|
|
|
// have anything to do with `set_window_icon`.
|
|
|
|
|
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
|
|
|
|
|
let scale_factor = self.scale_factor();
|
|
|
|
|
let logical_spot = spot.to_logical(scale_factor);
|
|
|
|
|
let logical_spot = NSPoint::new(logical_spot.x, logical_spot.y);
|
|
|
|
|
|
|
|
|
|
let size = size.to_logical(scale_factor);
|
|
|
|
|
let size = NSSize::new(size.width, size.height);
|
|
|
|
|
|
|
|
|
|
self.view().set_ime_cursor_area(logical_spot, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_ime_allowed(&self, allowed: bool) {
|
|
|
|
|
self.view().set_ime_allowed(allowed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn focus_window(&self) {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let is_minimized = self.window().isMiniaturized();
|
|
|
|
|
let is_visible = self.window().isVisible();
|
|
|
|
|
|
|
|
|
|
if !is_minimized && is_visible {
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
|
NSApplication::sharedApplication(mtm).activateIgnoringOtherApps(true);
|
|
|
|
|
self.window().makeKeyAndOrderFront(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
let ns_request_type = request_type.map(|ty| match ty {
|
2024-04-18 17:34:19 +02:00
|
|
|
UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest,
|
|
|
|
|
UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest,
|
2024-01-14 05:19:23 +01:00
|
|
|
});
|
|
|
|
|
if let Some(ty) = ns_request_type {
|
|
|
|
|
NSApplication::sharedApplication(mtm).requestUserAttention(ty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
// Allow directly accessing the current monitor internally without unwrapping.
|
|
|
|
|
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
|
|
|
|
|
let display_id = get_display_id(&*self.window().screen()?);
|
|
|
|
|
Some(MonitorHandle::new(display_id))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
|
|
|
|
self.current_monitor_inner()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
|
|
|
monitor::available_monitors()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
|
|
|
|
let monitor = monitor::primary_monitor();
|
|
|
|
|
Some(monitor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "rwh_04")]
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
|
|
|
|
let mut window_handle = rwh_04::AppKitHandle::empty();
|
|
|
|
|
window_handle.ns_window = self.window() as *const WinitWindow as *mut _;
|
2024-05-27 14:49:22 +02:00
|
|
|
window_handle.ns_view = Retained::as_ptr(&self.contentView().unwrap()) as *mut _;
|
2024-01-14 05:19:23 +01:00
|
|
|
rwh_04::RawWindowHandle::AppKit(window_handle)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "rwh_05")]
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
|
|
|
|
let mut window_handle = rwh_05::AppKitWindowHandle::empty();
|
|
|
|
|
window_handle.ns_window = self.window() as *const WinitWindow as *mut _;
|
2024-05-27 14:49:22 +02:00
|
|
|
window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _;
|
2024-01-14 05:19:23 +01:00
|
|
|
rwh_05::RawWindowHandle::AppKit(window_handle)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "rwh_05")]
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
|
|
|
|
|
rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "rwh_06")]
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
|
|
|
|
let window_handle = rwh_06::AppKitWindowHandle::new({
|
2024-05-27 14:49:22 +02:00
|
|
|
let ptr = Retained::as_ptr(&self.view()) as *mut _;
|
|
|
|
|
std::ptr::NonNull::new(ptr).expect("Retained<T> should never be null")
|
2024-01-14 05:19:23 +01:00
|
|
|
});
|
|
|
|
|
rwh_06::RawWindowHandle::AppKit(window_handle)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) {
|
|
|
|
|
let current_style_mask = self.window().styleMask();
|
|
|
|
|
if on {
|
2024-05-27 14:49:22 +02:00
|
|
|
self.set_style_mask(current_style_mask | mask);
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-05-27 14:49:22 +02:00
|
|
|
self.set_style_mask(current_style_mask & !mask);
|
2024-01-14 05:19:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn theme(&self) -> Option<Theme> {
|
|
|
|
|
self.ivars().current_theme.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn has_focus(&self) -> bool {
|
|
|
|
|
self.window().isKeyWindow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_theme(&self, theme: Option<Theme>) {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
set_ns_theme(theme, mtm);
|
|
|
|
|
self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_content_protected(&self, protected: bool) {
|
|
|
|
|
self.window().setSharingType(if protected {
|
2024-04-18 17:34:19 +02:00
|
|
|
NSWindowSharingType::NSWindowSharingNone
|
2024-01-14 05:19:23 +01:00
|
|
|
} else {
|
2024-04-18 17:34:19 +02:00
|
|
|
NSWindowSharingType::NSWindowSharingReadOnly
|
2024-01-14 05:19:23 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn title(&self) -> String {
|
|
|
|
|
self.window().title().to_string()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reset_dead_keys(&self) {
|
|
|
|
|
// (Artur) I couldn't find a way to implement this.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WindowExtMacOS for WindowDelegate {
|
|
|
|
|
#[inline]
|
|
|
|
|
fn simple_fullscreen(&self) -> bool {
|
|
|
|
|
self.ivars().is_simple_fullscreen.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
|
|
|
|
let mtm = MainThreadMarker::from(self);
|
|
|
|
|
|
|
|
|
|
let app = NSApplication::sharedApplication(mtm);
|
|
|
|
|
let is_native_fullscreen = self.ivars().fullscreen.borrow().is_some();
|
|
|
|
|
let is_simple_fullscreen = self.ivars().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
|
|
|
|
|
// Exclude title bar
|
|
|
|
|
self.ivars()
|
|
|
|
|
.standard_frame
|
|
|
|
|
.set(Some(self.window().contentRectForFrameRect(self.window().frame())));
|
|
|
|
|
self.ivars().saved_style.set(Some(self.window().styleMask()));
|
|
|
|
|
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
|
|
|
|
|
|
|
|
|
|
// Tell our window's state that we're in fullscreen
|
|
|
|
|
self.ivars().is_simple_fullscreen.set(true);
|
|
|
|
|
|
|
|
|
|
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
2024-05-27 14:49:22 +02:00
|
|
|
let presentation_options =
|
|
|
|
|
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
|
|
|
|
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
|
2024-01-14 05:19:23 +01:00
|
|
|
app.setPresentationOptions(presentation_options);
|
|
|
|
|
|
|
|
|
|
// Hide the titlebar
|
2024-04-18 17:34:19 +02:00
|
|
|
self.toggle_style_mask(NSWindowStyleMask::Titled, false);
|
2024-01-14 05:19:23 +01:00
|
|
|
|
|
|
|
|
// Set the window frame to the screen frame size
|
|
|
|
|
let screen = self.window().screen().expect("expected screen to be available");
|
|
|
|
|
self.window().setFrame_display(screen.frame(), true);
|
|
|
|
|
|
|
|
|
|
// Fullscreen windows can't be resized, minimized, or moved
|
2024-04-18 17:34:19 +02:00
|
|
|
self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false);
|
|
|
|
|
self.toggle_style_mask(NSWindowStyleMask::Resizable, false);
|
2024-01-14 05:19:23 +01:00
|
|
|
self.window().setMovable(false);
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
let new_mask = self.saved_style();
|
|
|
|
|
self.set_style_mask(new_mask);
|
|
|
|
|
self.ivars().is_simple_fullscreen.set(false);
|
|
|
|
|
|
|
|
|
|
let save_presentation_opts = self.ivars().save_presentation_opts.get();
|
|
|
|
|
let frame = self.ivars().standard_frame.get().unwrap_or(DEFAULT_STANDARD_FRAME);
|
|
|
|
|
|
|
|
|
|
if let Some(presentation_opts) = save_presentation_opts {
|
|
|
|
|
app.setPresentationOptions(presentation_opts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.window().setFrame_display(frame, true);
|
|
|
|
|
self.window().setMovable(true);
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn has_shadow(&self) -> bool {
|
|
|
|
|
self.window().hasShadow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn set_has_shadow(&self, has_shadow: bool) {
|
|
|
|
|
self.window().setHasShadow(has_shadow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn set_tabbing_identifier(&self, identifier: &str) {
|
|
|
|
|
self.window().setTabbingIdentifier(&NSString::from_str(identifier))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn tabbing_identifier(&self) -> String {
|
|
|
|
|
self.window().tabbingIdentifier().to_string()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn select_next_tab(&self) {
|
|
|
|
|
self.window().selectNextTab(None)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn select_previous_tab(&self) {
|
|
|
|
|
unsafe { self.window().selectPreviousTab(None) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn select_tab_at_index(&self, index: usize) {
|
|
|
|
|
if let Some(group) = self.window().tabGroup() {
|
|
|
|
|
if let Some(windows) = unsafe { self.window().tabbedWindows() } {
|
|
|
|
|
if index < windows.len() {
|
|
|
|
|
group.setSelectedWindow(Some(&windows[index]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn num_tabs(&self) -> usize {
|
|
|
|
|
unsafe { self.window().tabbedWindows() }.map(|windows| windows.len()).unwrap_or(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_document_edited(&self) -> bool {
|
|
|
|
|
self.window().isDocumentEdited()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_document_edited(&self, edited: bool) {
|
|
|
|
|
self.window().setDocumentEdited(edited)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
|
|
|
|
self.view().set_option_as_alt(option_as_alt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn option_as_alt(&self) -> OptionAsAlt {
|
|
|
|
|
self.view().option_as_alt()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DEFAULT_STANDARD_FRAME: NSRect =
|
|
|
|
|
NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0));
|
|
|
|
|
|
|
|
|
|
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
|
|
|
|
|
let app = NSApplication::sharedApplication(mtm);
|
2024-05-06 17:09:38 +02:00
|
|
|
if !app.respondsToSelector(sel!(effectiveAppearance)) {
|
2024-01-14 05:19:23 +01:00
|
|
|
return Theme::Light;
|
|
|
|
|
}
|
|
|
|
|
let appearance = app.effectiveAppearance();
|
|
|
|
|
let name = appearance
|
|
|
|
|
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
|
|
|
|
NSString::from_str("NSAppearanceNameAqua"),
|
|
|
|
|
NSString::from_str("NSAppearanceNameDarkAqua"),
|
|
|
|
|
]))
|
|
|
|
|
.unwrap();
|
|
|
|
|
match &*name.to_string() {
|
|
|
|
|
"NSAppearanceNameDarkAqua" => Theme::Dark,
|
|
|
|
|
_ => Theme::Light,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
|
|
|
|
|
let app = NSApplication::sharedApplication(mtm);
|
2024-05-06 17:09:38 +02:00
|
|
|
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
2024-01-14 05:19:23 +01:00
|
|
|
let appearance = theme.map(|t| {
|
|
|
|
|
let name = match t {
|
|
|
|
|
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
|
|
|
|
|
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
|
|
|
|
|
};
|
|
|
|
|
NSAppearance::appearanceNamed(&name).unwrap()
|
|
|
|
|
});
|
|
|
|
|
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
|
|
|
|
|
}
|
2022-07-06 18:46:25 +00:00
|
|
|
}
|