From 164bf85b5b64c702db662b5ce15463a4a6f12276 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 11:05:22 +0100 Subject: [PATCH 01/10] Windows: Add scancode conversions from Chromium sources (#4020) --- src/changelog/unreleased.md | 1 + src/platform_impl/windows/keyboard.rs | 28 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 0227a77088..648001623b 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -76,6 +76,7 @@ changelog entry. - Added `Window::surface_position`, which is the position of the surface inside the window. - Added `Window::safe_area`, which describes the area of the surface that is unobstructed. - On X11 and Wayland, improved scancode conversions for more obscure key codes. +- On Windows, improved scancode conversions for more obscure key codes. ### Changed diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs index d360a1d363..d4e7cc3407 100644 --- a/src/platform_impl/windows/keyboard.rs +++ b/src/platform_impl/windows/keyboard.rs @@ -1079,6 +1079,20 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option KeyCode::AudioVolumeDown => Some(0xe02e), KeyCode::AudioVolumeMute => Some(0xe020), KeyCode::AudioVolumeUp => Some(0xe030), + + // Extra from Chromium sources: + // https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc + KeyCode::Lang4 => Some(0x0077), + KeyCode::Lang3 => Some(0x0078), + KeyCode::Undo => Some(0xe008), + KeyCode::Paste => Some(0xe00a), + KeyCode::Cut => Some(0xe017), + KeyCode::Copy => Some(0xe018), + KeyCode::Eject => Some(0xe02c), + KeyCode::Help => Some(0xe03b), + KeyCode::Sleep => Some(0xe05f), + KeyCode::WakeUp => Some(0xe063), + _ => None, } } @@ -1238,6 +1252,20 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { 0xe02e => KeyCode::AudioVolumeDown, 0xe020 => KeyCode::AudioVolumeMute, 0xe030 => KeyCode::AudioVolumeUp, + + // Extra from Chromium sources: + // https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc + 0x0077 => KeyCode::Lang4, + 0x0078 => KeyCode::Lang3, + 0xe008 => KeyCode::Undo, + 0xe00a => KeyCode::Paste, + 0xe017 => KeyCode::Cut, + 0xe018 => KeyCode::Copy, + 0xe02c => KeyCode::Eject, + 0xe03b => KeyCode::Help, + 0xe05f => KeyCode::Sleep, + 0xe063 => KeyCode::WakeUp, + _ => return PhysicalKey::Unidentified(NativeKeyCode::Windows(scancode as u16)), }) } From 132fbe14d5e638469248b4dc26528ea45751a51f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 17:19:45 +0100 Subject: [PATCH 02/10] macOS: Fix safe area on macOS 10.14 and below (#4028) --- .../apple/appkit/window_delegate.rs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 711535aa72..a43952d0a4 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -980,19 +980,24 @@ impl WindowDelegate { // we've set it up with `additionalSafeAreaInsets`. unsafe { self.view().safeAreaInsets() } } else { - let content_rect = self.window().contentRectForFrameRect(self.window().frame()); - // Includes NSWindowStyleMask::FullSizeContentView - // Convert from window coordinates to view coordinates - let safe_rect = unsafe { - self.view().convertRect_fromView(self.window().contentLayoutRect(), None) + // If `safeAreaInsets` is not available, we'll have to do the calculation ourselves. + + let window_rect = unsafe { + self.window().convertRectFromScreen( + self.window().contentRectForFrameRect(self.window().frame()), + ) }; + // This includes NSWindowStyleMask::FullSizeContentView. + let layout_rect = unsafe { self.window().contentLayoutRect() }; + + // Calculate the insets from window coordinates in AppKit's coordinate system. NSEdgeInsets { - top: safe_rect.origin.y - content_rect.origin.y, - left: safe_rect.origin.x - content_rect.origin.x, - bottom: (content_rect.size.height + content_rect.origin.x) - - (safe_rect.size.height + safe_rect.origin.x), - right: (content_rect.size.width + content_rect.origin.y) - - (safe_rect.size.width + safe_rect.origin.y), + top: (window_rect.size.height + window_rect.origin.y) + - (layout_rect.size.height + layout_rect.origin.y), + left: layout_rect.origin.x - window_rect.origin.x, + bottom: layout_rect.origin.y - window_rect.origin.y, + right: (window_rect.size.width + window_rect.origin.x) + - (layout_rect.size.width + layout_rect.origin.x), } }; let insets = LogicalInsets::new(insets.top, insets.left, insets.bottom, insets.right); From ca46e29203fc45ae5bd871af1d82346ef0b595a1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 17:31:32 +0100 Subject: [PATCH 03/10] macOS: Fix surface position (#4027) --- .../apple/appkit/window_delegate.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index a43952d0a4..92878ce980 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -945,8 +945,21 @@ impl WindowDelegate { } pub fn surface_position(&self) -> PhysicalPosition { - let content_rect = self.window().contentRectForFrameRect(self.window().frame()); - let logical = LogicalPosition::new(content_rect.origin.x, content_rect.origin.y); + // The calculation here is a bit awkward because we've gotta reconcile the + // different origins (Winit prefers top-left vs. NSWindow's bottom-left), + // and I couldn't find a built-in way to do so. + + // The position of the window and the view, both in Winit screen coordinates. + let window_position = flip_window_screen_coordinates(self.window().frame()); + let view_position = flip_window_screen_coordinates( + self.window().contentRectForFrameRect(self.window().frame()), + ); + + // And use that to convert the view position to window coordinates. + let surface_position = + NSPoint::new(view_position.x - window_position.x, view_position.y - window_position.y); + + let logical = LogicalPosition::new(surface_position.x, surface_position.y); logical.to_physical(self.scale_factor()) } From edca3ebc41f28c3645dfc27059825ba83dffb18c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 18:17:57 +0100 Subject: [PATCH 04/10] macOS: Align scancode conversions with Chromium and Firefox (#4019) Also fix missing codes in physicalkey_to_scancode - This had become out of sync with scancode_to_physicalkey. --- src/changelog/unreleased.md | 5 +- src/platform_impl/apple/appkit/event.rs | 65 ++++++++++++++++--------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 648001623b..47651d4242 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -75,8 +75,7 @@ changelog entry. variables to test the respective modifiers of window creation. - Added `Window::surface_position`, which is the position of the surface inside the window. - Added `Window::safe_area`, which describes the area of the surface that is unobstructed. -- On X11 and Wayland, improved scancode conversions for more obscure key codes. -- On Windows, improved scancode conversions for more obscure key codes. +- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes. ### Changed @@ -210,3 +209,5 @@ changelog entry. - On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. - On X11, fix XInput handling that prevented a new window from getting the focus in some cases. - On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface. +- On macOS, fixed the scancode conversion for audio volume keys. +- On macOS, fixed the scancode conversion for `IntlBackslash`. diff --git a/src/platform_impl/apple/appkit/event.rs b/src/platform_impl/apple/appkit/event.rs index f71111b67f..aabbeb051b 100644 --- a/src/platform_impl/apple/appkit/event.rs +++ b/src/platform_impl/apple/appkit/event.rs @@ -377,6 +377,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option KeyCode::KeyX => Some(0x07), KeyCode::KeyC => Some(0x08), KeyCode::KeyV => Some(0x09), + KeyCode::IntlBackslash => Some(0x0a), KeyCode::KeyB => Some(0x0b), KeyCode::KeyQ => Some(0x0c), KeyCode::KeyW => Some(0x0d), @@ -422,18 +423,21 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option KeyCode::SuperRight => Some(0x36), KeyCode::SuperLeft => Some(0x37), KeyCode::ShiftLeft => Some(0x38), + KeyCode::CapsLock => Some(0x39), KeyCode::AltLeft => Some(0x3a), KeyCode::ControlLeft => Some(0x3b), KeyCode::ShiftRight => Some(0x3c), KeyCode::AltRight => Some(0x3d), KeyCode::ControlRight => Some(0x3e), + KeyCode::Fn => Some(0x3f), KeyCode::F17 => Some(0x40), KeyCode::NumpadDecimal => Some(0x41), KeyCode::NumpadMultiply => Some(0x43), KeyCode::NumpadAdd => Some(0x45), KeyCode::NumLock => Some(0x47), - KeyCode::AudioVolumeUp => Some(0x49), - KeyCode::AudioVolumeDown => Some(0x4a), + KeyCode::AudioVolumeUp => Some(0x48), + KeyCode::AudioVolumeDown => Some(0x49), + KeyCode::AudioVolumeMute => Some(0x4a), KeyCode::NumpadDivide => Some(0x4b), KeyCode::NumpadEnter => Some(0x4c), KeyCode::NumpadSubtract => Some(0x4e), @@ -452,17 +456,22 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option KeyCode::Numpad8 => Some(0x5b), KeyCode::Numpad9 => Some(0x5c), KeyCode::IntlYen => Some(0x5d), + KeyCode::IntlRo => Some(0x5e), + KeyCode::NumpadComma => Some(0x5f), KeyCode::F5 => Some(0x60), KeyCode::F6 => Some(0x61), KeyCode::F7 => Some(0x62), KeyCode::F3 => Some(0x63), KeyCode::F8 => Some(0x64), KeyCode::F9 => Some(0x65), + KeyCode::Lang2 => Some(0x66), KeyCode::F11 => Some(0x67), + KeyCode::Lang1 => Some(0x68), KeyCode::F13 => Some(0x69), KeyCode::F16 => Some(0x6a), KeyCode::F14 => Some(0x6b), KeyCode::F10 => Some(0x6d), + KeyCode::ContextMenu => Some(0x6e), KeyCode::F12 => Some(0x6f), KeyCode::F15 => Some(0x71), KeyCode::Insert => Some(0x72), @@ -478,11 +487,26 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option KeyCode::ArrowRight => Some(0x7c), KeyCode::ArrowDown => Some(0x7d), KeyCode::ArrowUp => Some(0x7e), + KeyCode::Power => Some(0x7f), _ => None, } } pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { + // Follows what Chromium and Firefox do: + // https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc + // https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h + // + // See also: + // Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h + // + // Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values: + // + // > the system handles some function keys at a lower level and your app never sees them. + // > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and + // > Function key found on many Macs. + // + // So the handling of some of these is mostly for show. PhysicalKey::Code(match scancode { 0x00 => KeyCode::KeyA, 0x01 => KeyCode::KeyS, @@ -494,7 +518,11 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { 0x07 => KeyCode::KeyX, 0x08 => KeyCode::KeyC, 0x09 => KeyCode::KeyV, - // 0x0a => World 1, + // This key is typically located near LeftShift key, roughly the same location as backquote + // (`) on Windows' US layout. + // + // The keycap varies on international keyboards. + 0x0a => KeyCode::IntlBackslash, 0x0b => KeyCode::KeyB, 0x0c => KeyCode::KeyQ, 0x0d => KeyCode::KeyW, @@ -536,7 +564,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { 0x31 => KeyCode::Space, 0x32 => KeyCode::Backquote, 0x33 => KeyCode::Backspace, - // 0x34 => unknown, + // 0x34 => unknown, // kVK_Powerbook_KeypadEnter 0x35 => KeyCode::Escape, 0x36 => KeyCode::SuperRight, 0x37 => KeyCode::SuperLeft, @@ -555,15 +583,10 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { // 0x44 => unknown, 0x45 => KeyCode::NumpadAdd, // 0x46 => unknown, - 0x47 => KeyCode::NumLock, - // 0x48 => KeyCode::NumpadClear, - - // TODO: (Artur) for me, kVK_VolumeUp is 0x48 - // macOS 10.11 - // /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/ - // Versions/A/Headers/Events.h - 0x49 => KeyCode::AudioVolumeUp, - 0x4a => KeyCode::AudioVolumeDown, + 0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear + 0x48 => KeyCode::AudioVolumeUp, + 0x49 => KeyCode::AudioVolumeDown, + 0x4a => KeyCode::AudioVolumeMute, 0x4b => KeyCode::NumpadDivide, 0x4c => KeyCode::NumpadEnter, // 0x4d => unknown, @@ -583,23 +606,23 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { 0x5b => KeyCode::Numpad8, 0x5c => KeyCode::Numpad9, 0x5d => KeyCode::IntlYen, - // 0x5e => JIS Ro, - // 0x5f => unknown, + 0x5e => KeyCode::IntlRo, + 0x5f => KeyCode::NumpadComma, 0x60 => KeyCode::F5, 0x61 => KeyCode::F6, 0x62 => KeyCode::F7, 0x63 => KeyCode::F3, 0x64 => KeyCode::F8, 0x65 => KeyCode::F9, - // 0x66 => JIS Eisuu (macOS), + 0x66 => KeyCode::Lang2, 0x67 => KeyCode::F11, - // 0x68 => JIS Kanna (macOS), + 0x68 => KeyCode::Lang1, 0x69 => KeyCode::F13, 0x6a => KeyCode::F16, 0x6b => KeyCode::F14, // 0x6c => unknown, 0x6d => KeyCode::F10, - // 0x6e => unknown, + 0x6e => KeyCode::ContextMenu, 0x6f => KeyCode::F12, // 0x70 => unknown, 0x71 => KeyCode::F15, @@ -616,11 +639,7 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { 0x7c => KeyCode::ArrowRight, 0x7d => KeyCode::ArrowDown, 0x7e => KeyCode::ArrowUp, - // 0x7f => unknown, - - // 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as - // backquote (`) on Windows' US layout. - 0xa => KeyCode::Backquote, + 0x7f => KeyCode::Power, // On 10.7 and 10.8 only _ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)), }) } From 3657506f6edfbcbc82f349e2f29bbd57b521939f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 18:35:04 +0100 Subject: [PATCH 05/10] macOS: Avoid redundant initial resize event (#3913) The `NSViewFrameDidChangeNotification` that we listen to is emitted when `-[NSWindow setContentView]` is called, since that sets the frame of the view as well. So now we register the notification later, so that it's not triggered at window creation. This behaviour is well described in the documentation: https://developer.apple.com/documentation/appkit/nsview/postsframechangednotifications?language=objc --- src/changelog/unreleased.md | 1 + src/platform_impl/apple/appkit/view.rs | 54 +++++++------------ .../apple/appkit/window_delegate.rs | 31 ++++++++--- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 47651d4242..776912b13d 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -211,3 +211,4 @@ changelog entry. - On iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface. - On macOS, fixed the scancode conversion for audio volume keys. - On macOS, fixed the scancode conversion for `IntlBackslash`. +- On macOS, fixed redundant `SurfaceResized` event at window creation. diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index e8bdffa809..94994b025f 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -4,17 +4,17 @@ use std::collections::{HashMap, VecDeque}; use std::ptr; use std::rc::Rc; -use objc2::rc::{Retained, WeakId}; +use objc2::rc::Retained; use objc2::runtime::{AnyObject, Sel}; -use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; +use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; use objc2_app_kit::{ NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, - NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification, + NSTrackingRectTag, NSView, }; use objc2_foundation::{ MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, - NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol, - NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, + NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, + NSSize, NSString, NSUInteger, }; use super::app_state::AppState; @@ -134,9 +134,6 @@ pub struct ViewState { marked_text: RefCell>, accepts_first_mouse: bool, - // Weak reference because the window keeps a strong reference to the view - _ns_window: WeakId, - /// The state of the `Option` as `Alt`. option_as_alt: Cell, } @@ -177,9 +174,10 @@ declare_class!( self.ivars().tracking_rect.set(Some(tracking_rect)); } - #[method(frameDidChange:)] - fn frame_did_change(&self, _event: &NSEvent) { - trace_scope!("frameDidChange:"); + // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. + #[method(viewFrameDidChangeNotification:)] + fn frame_did_change(&self, _notification: Option<&AnyObject>) { + trace_scope!("NSViewFrameDidChangeNotification"); if let Some(tracking_rect) = self.ivars().tracking_rect.take() { self.removeTrackingRect(tracking_rect); } @@ -203,10 +201,7 @@ declare_class!( fn draw_rect(&self, _rect: NSRect) { trace_scope!("drawRect:"); - // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. - if let Some(window) = self.ivars()._ns_window.load() { - self.ivars().app_state.handle_redraw(window.id()); - } + self.ivars().app_state.handle_redraw(self.window().id()); // This is a direct subclass of NSView, no need to call superclass' drawRect: } @@ -806,11 +801,10 @@ declare_class!( impl WinitView { pub(super) fn new( app_state: &Rc, - window: &WinitWindow, accepts_first_mouse: bool, option_as_alt: OptionAsAlt, + mtm: MainThreadMarker, ) -> Retained { - let mtm = MainThreadMarker::from(window); let this = mtm.alloc().set_ivars(ViewState { app_state: Rc::clone(app_state), cursor_state: Default::default(), @@ -825,34 +819,24 @@ impl WinitView { forward_key_to_app: Default::default(), marked_text: Default::default(), accepts_first_mouse, - _ns_window: WeakId::new(&window.retain()), option_as_alt: Cell::new(option_as_alt), }); let this: Retained = unsafe { msg_send_id![super(this), init] }; - this.setPostsFrameChangedNotifications(true); - let notification_center = unsafe { NSNotificationCenter::defaultCenter() }; - unsafe { - notification_center.addObserver_selector_name_object( - &this, - sel!(frameDidChange:), - Some(NSViewFrameDidChangeNotification), - Some(&this), - ) - } - *this.ivars().input_source.borrow_mut() = this.current_input_source(); this } fn window(&self) -> Retained { - // TODO: Simply use `window` property on `NSView`. - // That only returns a window _after_ the view has been attached though! - // (which is incompatible with `frameDidChange:`) - // - // unsafe { msg_send_id![self, window] } - self.ivars()._ns_window.load().expect("view to have a window") + let window = (**self).window().expect("view must be installed in a window"); + + if !window.isKindOfClass(WinitWindow::class()) { + unreachable!("view installed in non-WinitWindow"); + } + + // SAFETY: Just checked that the window is `WinitWindow` + unsafe { Retained::cast(window) } } fn queue_event(&self, event: WindowEvent) { diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 92878ce980..6c2f434180 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -15,15 +15,15 @@ use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, - NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate, - NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, - NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, - NSWindowToolbarStyle, + NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, + NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, + NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, + NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle, }; use objc2_foundation::{ ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, - NSKeyValueObservingOptions, NSObject, NSObjectNSDelayedPerforming, + NSKeyValueObservingOptions, NSNotificationCenter, NSObject, NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSRect, NSSize, NSString, }; use tracing::{trace, warn}; @@ -170,7 +170,7 @@ declare_class!( #[method(windowDidResize:)] fn window_did_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResize:"); - // NOTE: WindowEvent::SurfaceResized is reported in frameDidChange. + // NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification. self.emit_move_event(); } @@ -658,9 +658,9 @@ fn new_window( let view = WinitView::new( app_state, - &window, attrs.platform_specific.accepts_first_mouse, attrs.platform_specific.option_as_alt, + mtm, ); // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until @@ -682,6 +682,23 @@ fn new_window( window.setContentView(Some(&view)); window.setInitialFirstResponder(Some(&view)); + // Configure the view to send notifications whenever its frame rectangle changes. + // + // We explicitly do this _after_ setting the view as the content view of the window, to + // avoid a resize event when creating the window. + view.setPostsFrameChangedNotifications(true); + // `setPostsFrameChangedNotifications` posts the notification immediately, so register the + // observer _after_, again so that the event isn't triggered initially. + let notification_center = unsafe { NSNotificationCenter::defaultCenter() }; + unsafe { + notification_center.addObserver_selector_name_object( + &view, + sel!(viewFrameDidChangeNotification:), + Some(NSViewFrameDidChangeNotification), + Some(&view), + ) + } + if attrs.transparent { window.setOpaque(false); // See `set_transparent` for details on why we do this. From f314cd2b9a8bba793af1a58286294f00d2bcc900 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 18:48:23 +0100 Subject: [PATCH 06/10] macOS: Fix crash when pressing Caps Lock (#4024) Events emitted by `flagsChanged:` cannot access `charactersIgnoringModifiers`. We were previously doing this because we were trying to re-use the `create_key_event` function, but that is unsuited for this purpose, so I have separated the `flagsChanged:` logic out from it. --- src/changelog/unreleased.md | 1 + src/platform_impl/apple/appkit/event.rs | 26 +++++---------- src/platform_impl/apple/appkit/view.rs | 44 ++++++++++++++++--------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 776912b13d..3034746ca3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -212,3 +212,4 @@ changelog entry. - On macOS, fixed the scancode conversion for audio volume keys. - On macOS, fixed the scancode conversion for `IntlBackslash`. - On macOS, fixed redundant `SurfaceResized` event at window creation. +- On macOS, fix crash when pressing Caps Lock in certain configurations. diff --git a/src/platform_impl/apple/appkit/event.rs b/src/platform_impl/apple/appkit/event.rs index aabbeb051b..f562aceb84 100644 --- a/src/platform_impl/apple/appkit/event.rs +++ b/src/platform_impl/apple/appkit/event.rs @@ -92,17 +92,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { /// Create `KeyEvent` for the given `NSEvent`. /// /// This function shouldn't be called when the IME input is in process. -pub(crate) fn create_key_event( - ns_event: &NSEvent, - is_press: bool, - is_repeat: bool, - key_override: Option, -) -> KeyEvent { +pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent { use ElementState::{Pressed, Released}; let state = if is_press { Pressed } else { Released }; let scancode = unsafe { ns_event.keyCode() }; - let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32)); + let mut physical_key = scancode_to_physicalkey(scancode as u32); // NOTE: The logical key should heed both SHIFT and ALT if possible. // For instance: @@ -111,20 +106,15 @@ pub(crate) fn create_key_event( // * Pressing CTRL SHIFT A: logical key should also be "A" // This is not easy to tease out of `NSEvent`, but we do our best. - let text_with_all_modifiers: Option = if key_override.is_some() { + let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); + let text_with_all_modifiers = if characters.is_empty() { None } else { - let characters = - unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); - if characters.is_empty() { - None - } else { - if matches!(physical_key, PhysicalKey::Unidentified(_)) { - // The key may be one of the funky function keys - physical_key = extra_function_key_to_code(scancode, &characters); - } - Some(SmolStr::new(characters)) + if matches!(physical_key, PhysicalKey::Unidentified(_)) { + // The key may be one of the funky function keys + physical_key = extra_function_key_to_code(scancode, &characters); } + Some(SmolStr::new(characters)) }; let key_from_code = code_to_key(physical_key, scancode); diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index 94994b025f..14a0a11939 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -21,13 +21,13 @@ use super::app_state::AppState; use super::cursor::{default_cursor, invisible_cursor}; use super::event::{ code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, - scancode_to_physicalkey, + scancode_to_physicalkey, KeyEventExtra, }; use super::window::WinitWindow; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::event::{ - DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, PointerKind, - PointerSource, TouchPhase, WindowEvent, + DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, + PointerKind, PointerSource, TouchPhase, WindowEvent, }; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey}; use crate::platform::macos::OptionAsAlt; @@ -490,7 +490,7 @@ declare_class!( }; if !had_ime_input || self.ivars().forward_key_to_app.get() { - let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None); + let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }); self.queue_event(WindowEvent::KeyboardInput { device_id: None, event: key_event, @@ -513,7 +513,7 @@ declare_class!( ) { self.queue_event(WindowEvent::KeyboardInput { device_id: None, - event: create_key_event(&event, false, false, None), + event: create_key_event(&event, false, false), is_synthetic: false, }); } @@ -560,7 +560,7 @@ declare_class!( .expect("could not find current event"); self.update_modifiers(&event, false); - let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None); + let event = create_key_event(&event, true, unsafe { event.isARepeat() }); self.queue_event(WindowEvent::KeyboardInput { device_id: None, @@ -946,22 +946,36 @@ impl WinitView { let scancode = unsafe { ns_event.keyCode() }; let physical_key = scancode_to_physicalkey(scancode as u32); - // We'll correct the `is_press` later. - let mut event = create_key_event(ns_event, false, false, Some(physical_key)); - - let key = code_to_key(physical_key, scancode); + let logical_key = code_to_key(physical_key, scancode); // Ignore processing of unknown modifiers because we can't determine whether // it was pressed or release reliably. - let Some(event_modifier) = key_to_modifier(&key) else { + // + // Furthermore, sometimes normal keys are reported inside flagsChanged:, such as + // when holding Caps Lock while pressing another key, see: + // https://github.com/alacritty/alacritty/issues/8268 + let Some(event_modifier) = key_to_modifier(&logical_key) else { break 'send_event; }; - event.physical_key = physical_key; - event.logical_key = key.clone(); - event.location = code_to_location(physical_key); + + let mut event = KeyEvent { + location: code_to_location(physical_key), + logical_key: logical_key.clone(), + physical_key, + repeat: false, + // We'll correct this later. + state: Pressed, + text: None, + platform_specific: KeyEventExtra { + text_with_all_modifiers: None, + key_without_modifiers: logical_key.clone(), + }, + }; + let location_mask = ModLocationMask::from_location(event.location); let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut(); - let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty()); + let phys_mod = + phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty()); let is_active = current_modifiers.state().contains(event_modifier); let mut events = VecDeque::with_capacity(2); From 4a8b659228e3d512d664d6e400058125064d6489 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 19:02:53 +0100 Subject: [PATCH 07/10] Fix MonitorHandle PartialEq and Hash on iOS (#4013) --- src/changelog/unreleased.md | 1 + src/platform_impl/apple/uikit/monitor.rs | 39 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 3034746ca3..9eefbbd3d6 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -213,3 +213,4 @@ changelog entry. - On macOS, fixed the scancode conversion for `IntlBackslash`. - On macOS, fixed redundant `SurfaceResized` event at window creation. - On macOS, fix crash when pressing Caps Lock in certain configurations. +- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations. diff --git a/src/platform_impl/apple/uikit/monitor.rs b/src/platform_impl/apple/uikit/monitor.rs index b87d5a26ce..7973ed5669 100644 --- a/src/platform_impl/apple/uikit/monitor.rs +++ b/src/platform_impl/apple/uikit/monitor.rs @@ -101,13 +101,20 @@ impl Clone for MonitorHandle { impl hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { - (self as *const Self).hash(state); + // SAFETY: Only getting the pointer. + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Retained::as_ptr(self.ui_screen.get(mtm)).hash(state); } } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { - ptr::eq(self, other) + // SAFETY: Only getting the pointer. + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + ptr::eq( + Retained::as_ptr(self.ui_screen.get(mtm)), + Retained::as_ptr(other.ui_screen.get(mtm)), + ) } } @@ -121,8 +128,10 @@ impl PartialOrd for MonitorHandle { impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // SAFETY: Only getting the pointer. // TODO: Make a better ordering - (self as *const Self).cmp(&(other as *const Self)) + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm))) } } @@ -240,3 +249,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque { #[allow(deprecated)] UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect() } + +#[cfg(test)] +mod tests { + use objc2_foundation::NSSet; + + use super::*; + + // Test that UIScreen pointer comparisons are correct. + #[test] + #[allow(deprecated)] + fn screen_comparisons() { + // Test code, doesn't matter that it's not thread safe + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + + assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm))); + + let main = UIScreen::mainScreen(mtm); + assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main))); + + assert!(unsafe { + NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm)) + }); + } +} From 4d2a0dd2b3b3e06e98a43cd254846b68d7fc0672 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 19:53:29 +0100 Subject: [PATCH 08/10] iOS: Never queue application-level events (#3905) Events like `resumed`, `new_events`, `about_to_wait`, and so on will never happen as a result of programmer action, so we'll never need to queue those. This allows us to remove the need for the old `Event` struct in the iOS backend. Furthermore, we can now remove `InUserCallback`, since that state is already stored inside `EventHandler`. I've tried to otherwise keep the semantics as close to the original by calling `handle_nonuser_events(mtm, [])`, which flushes pending events. --- src/changelog/unreleased.md | 1 + src/platform_impl/apple/event_handler.rs | 1 - src/platform_impl/apple/uikit/app_state.rs | 326 ++++++-------------- src/platform_impl/apple/uikit/event_loop.rs | 18 +- src/platform_impl/apple/uikit/view.rs | 121 ++++---- src/platform_impl/apple/uikit/window.rs | 10 +- 6 files changed, 165 insertions(+), 312 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 9eefbbd3d6..23654f4b97 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -163,6 +163,7 @@ changelog entry. - On X11, use bottom-right corner for IME hotspot in `Window::set_ime_cursor_area`. - On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation. - On macOS, no longer emit `Focused` upon window creation. +- On iOS, emit more events immediately, instead of queuing them. ### Removed diff --git a/src/platform_impl/apple/event_handler.rs b/src/platform_impl/apple/event_handler.rs index 37765d760c..a68d4bb8e6 100644 --- a/src/platform_impl/apple/event_handler.rs +++ b/src/platform_impl/apple/event_handler.rs @@ -106,7 +106,6 @@ impl EventHandler { self.inner.try_borrow().is_err() } - #[cfg(target_os = "macos")] pub(crate) fn ready(&self) -> bool { matches!(self.inner.try_borrow().as_deref(), Ok(Some(_))) } diff --git a/src/platform_impl/apple/uikit/app_state.rs b/src/platform_impl/apple/uikit/app_state.rs index c90629e235..420504bf85 100644 --- a/src/platform_impl/apple/uikit/app_state.rs +++ b/src/platform_impl/apple/uikit/app_state.rs @@ -27,8 +27,9 @@ use super::window::WinitUIWindow; use super::{ActiveEventLoop, EventLoopProxy}; use crate::application::ApplicationHandler; use crate::dpi::PhysicalSize; -use crate::event::{Event, StartCause, SurfaceSizeWriter, WindowEvent}; +use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent}; use crate::event_loop::ControlFlow; +use crate::window::WindowId; macro_rules! bug { ($($msg:tt)*) => { @@ -67,25 +68,9 @@ fn get_handler(mtm: MainThreadMarker) -> &'static EventHandler { GLOBAL.get(mtm).get_or_init(EventHandler::new) } -fn handle_event(mtm: MainThreadMarker, event: Event) { - let event_loop = &ActiveEventLoop { mtm }; - get_handler(mtm).handle(|app| match event { - Event::NewEvents(cause) => app.new_events(event_loop, cause), - Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event), - Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event), - Event::UserWakeUp => app.proxy_wake_up(event_loop), - Event::Suspended => app.suspended(event_loop), - Event::Resumed => app.resumed(event_loop), - Event::CreateSurfaces => app.can_create_surfaces(event_loop), - Event::AboutToWait => app.about_to_wait(event_loop), - Event::LoopExiting => app.exiting(event_loop), - Event::MemoryWarning => app.memory_warning(event_loop), - }) -} - #[derive(Debug)] pub(crate) enum EventWrapper { - StaticEvent(Event), + Window { window_id: WindowId, event: WindowEvent }, ScaleFactorChanged(ScaleFactorChanged), } @@ -96,14 +81,9 @@ pub struct ScaleFactorChanged { pub(super) scale_factor: f64, } -enum UserCallbackTransitionResult<'a> { - Success { active_control_flow: ControlFlow, processing_redraws: bool }, - ReentrancyPrevented { queued_events: &'a mut Vec }, -} - -impl Event { +impl EventWrapper { fn is_redraw(&self) -> bool { - matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. }) + matches!(self, Self::Window { event: WindowEvent::RedrawRequested, .. }) } } @@ -112,18 +92,12 @@ impl Event { #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] enum AppStateImpl { Initial { - queued_events: Vec, queued_gpu_redraws: HashSet>, }, ProcessingEvents { queued_gpu_redraws: HashSet>, active_control_flow: ControlFlow, }, - // special state to deal with reentrancy and prevent mutable aliasing. - InUserCallback { - queued_events: Vec, - queued_gpu_redraws: HashSet>, - }, ProcessingRedraws { active_control_flow: ControlFlow, }, @@ -140,6 +114,7 @@ pub(crate) struct AppState { control_flow: ControlFlow, waker: EventLoopWaker, event_loop_proxy: Arc, + queued_events: Vec, } impl AppState { @@ -158,13 +133,11 @@ impl AppState { fn init_guard(guard: &mut RefMut<'static, Option>) { let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() }); **guard = Some(AppState { - app_state: Some(AppStateImpl::Initial { - queued_events: Vec::new(), - queued_gpu_redraws: HashSet::new(), - }), + app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }), control_flow: ControlFlow::default(), waker, event_loop_proxy: Arc::new(EventLoopProxy::new()), + queued_events: Vec::new(), }); } init_guard(&mut guard); @@ -217,48 +190,34 @@ impl AppState { matches!(self.state(), AppStateImpl::Terminated) } - fn did_finish_launching_transition(&mut self) -> Vec { - let (events, queued_gpu_redraws) = match self.take_state() { - AppStateImpl::Initial { queued_events, queued_gpu_redraws } => { - (queued_events, queued_gpu_redraws) - }, + fn did_finish_launching_transition(&mut self) { + let queued_gpu_redraws = match self.take_state() { + AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws, s => bug!("unexpected state {:?}", s), }; self.set_state(AppStateImpl::ProcessingEvents { active_control_flow: self.control_flow, queued_gpu_redraws, }); - events } - fn wakeup_transition(&mut self) -> Option { + fn wakeup_transition(&mut self) -> Option { // before `AppState::did_finish_launching` is called, pretend there is no running // event loop. if !self.has_launched() || self.has_terminated() { return None; } - let event = match (self.control_flow, self.take_state()) { - (ControlFlow::Poll, AppStateImpl::PollFinished) => { - EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)) - }, + let start_cause = match (self.control_flow, self.take_state()) { + (ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll, (ControlFlow::Wait, AppStateImpl::Waiting { start }) => { - EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: None, - })) + StartCause::WaitCancelled { start, requested_resume: None } }, (ControlFlow::WaitUntil(requested_resume), AppStateImpl::Waiting { start }) => { if Instant::now() >= requested_resume { - EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume, - })) + StartCause::ResumeTimeReached { start, requested_resume } } else { - EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(requested_resume), - })) + StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) } } }, s => bug!("`EventHandler` unexpectedly woke up {:?}", s), @@ -268,55 +227,7 @@ impl AppState { queued_gpu_redraws: Default::default(), active_control_flow: self.control_flow, }); - Some(event) - } - - fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { - // If we're not able to process an event due to recursion or `Init` not having been sent out - // yet, then queue the events up. - match self.state_mut() { - &mut AppStateImpl::Initial { ref mut queued_events, .. } - | &mut AppStateImpl::InUserCallback { ref mut queued_events, .. } => { - // A lifetime cast: early returns are not currently handled well with NLL, but - // polonius handles them well. This transmute is a safe workaround. - return unsafe { - mem::transmute::< - UserCallbackTransitionResult<'_>, - UserCallbackTransitionResult<'_>, - >(UserCallbackTransitionResult::ReentrancyPrevented { - queued_events, - }) - }; - }, - - &mut AppStateImpl::ProcessingEvents { .. } - | &mut AppStateImpl::ProcessingRedraws { .. } => {}, - - s @ &mut AppStateImpl::PollFinished { .. } - | s @ &mut AppStateImpl::Waiting { .. } - | s @ &mut AppStateImpl::Terminated => { - bug!("unexpected attempted to process an event {:?}", s) - }, - } - - let (queued_gpu_redraws, active_control_flow, processing_redraws) = match self.take_state() - { - AppStateImpl::Initial { .. } | AppStateImpl::InUserCallback { .. } => unreachable!(), - AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => { - (queued_gpu_redraws, active_control_flow, false) - }, - AppStateImpl::ProcessingRedraws { active_control_flow } => { - (Default::default(), active_control_flow, true) - }, - AppStateImpl::PollFinished { .. } - | AppStateImpl::Waiting { .. } - | AppStateImpl::Terminated => unreachable!(), - }; - self.set_state(AppStateImpl::InUserCallback { - queued_events: Vec::new(), - queued_gpu_redraws, - }); - UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } + Some(start_cause) } fn main_events_cleared_transition(&mut self) -> HashSet> { @@ -372,7 +283,7 @@ impl AppState { fn terminated_transition(&mut self) { match self.replace_state(AppStateImpl::Terminated) { AppStateImpl::ProcessingEvents { .. } => {}, - s => bug!("`LoopExiting` happened while not processing events {:?}", s), + s => bug!("terminated while not processing events {:?}", s), } } @@ -393,8 +304,7 @@ pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained { + | &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => { let _ = queued_gpu_redraws.insert(window); }, s @ &mut AppStateImpl::ProcessingRedraws { .. } @@ -418,27 +328,24 @@ pub fn did_finish_launching(mtm: MainThreadMarker) { // have to drop RefMut because the window setup code below can trigger new events drop(this); - let events = AppState::get_mut(mtm).did_finish_launching_transition(); + AppState::get_mut(mtm).did_finish_launching_transition(); - let events = [ - EventWrapper::StaticEvent(Event::NewEvents(StartCause::Init)), - EventWrapper::StaticEvent(Event::CreateSurfaces), - ] - .into_iter() - .chain(events); - handle_nonuser_events(mtm, events); + get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init)); + get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm })); + handle_nonuser_events(mtm, []); } // AppState::did_finish_launching handles the special transition `Init` pub fn handle_wakeup_transition(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); - let wakeup_event = match this.wakeup_transition() { + let cause = match this.wakeup_transition() { None => return, - Some(wakeup_event) => wakeup_event, + Some(cause) => cause, }; drop(this); - handle_nonuser_event(mtm, wakeup_event) + get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause)); + handle_nonuser_events(mtm, []); } pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { @@ -454,132 +361,75 @@ pub(crate) fn handle_nonuser_events>( return; } - let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() { - UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { - queued_events.extend(events); - return; - }, - UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => { - (active_control_flow, processing_redraws) - }, - }; + if !get_handler(mtm).ready() { + // Prevent re-entrancy; queue the events up for once we're done handling the event instead. + this.queued_events.extend(events); + return; + } + + let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }); drop(this); - for wrapper in events { - match wrapper { - EventWrapper::StaticEvent(event) => { - if !processing_redraws && event.is_redraw() { - tracing::info!("processing `RedrawRequested` during the main event loop"); - } else if processing_redraws && !event.is_redraw() { - tracing::warn!( - "processing non `RedrawRequested` event after the main event loop: {:#?}", - event - ); - } - handle_event(mtm, event) - }, - EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event), + for event in events { + if !processing_redraws && event.is_redraw() { + tracing::info!("processing `RedrawRequested` during the main event loop"); + } else if processing_redraws && !event.is_redraw() { + tracing::warn!( + "processing non `RedrawRequested` event after the main event loop: {:#?}", + event + ); } + handle_wrapped_event(mtm, event) } loop { let mut this = AppState::get_mut(mtm); - let queued_events = match this.state_mut() { - &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => { - mem::take(queued_events) - }, - s => bug!("unexpected state {:?}", s), - }; + let queued_events = mem::take(&mut this.queued_events); if queued_events.is_empty() { - let queued_gpu_redraws = match this.take_state() { - AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => { - queued_gpu_redraws - }, - _ => unreachable!(), - }; - this.app_state = Some(if processing_redraws { - bug_assert!( - queued_gpu_redraws.is_empty(), - "redraw queued while processing redraws" - ); - AppStateImpl::ProcessingRedraws { active_control_flow } - } else { - AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } - }); break; } drop(this); - for wrapper in queued_events { - match wrapper { - EventWrapper::StaticEvent(event) => { - if !processing_redraws && event.is_redraw() { - tracing::info!("processing `RedrawRequested` during the main event loop"); - } else if processing_redraws && !event.is_redraw() { - tracing::warn!( - "processing non-`RedrawRequested` event after the main event loop: \ - {:#?}", - event - ); - } - handle_event(mtm, event) - }, - EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event), + for event in queued_events { + if !processing_redraws && event.is_redraw() { + tracing::info!("processing `RedrawRequested` during the main event loop"); + } else if processing_redraws && !event.is_redraw() { + tracing::warn!( + "processing non-`RedrawRequested` event after the main event loop: {:#?}", + event + ); } + handle_wrapped_event(mtm, event); } } } fn handle_user_events(mtm: MainThreadMarker) { - let mut this = AppState::get_mut(mtm); - let (active_control_flow, processing_redraws) = match this.try_user_callback_transition() { - UserCallbackTransitionResult::ReentrancyPrevented { .. } => { - bug!("unexpected attempted to process an event") - }, - UserCallbackTransitionResult::Success { active_control_flow, processing_redraws } => { - (active_control_flow, processing_redraws) - }, - }; - if processing_redraws { + let this = AppState::get_mut(mtm); + if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) { bug!("user events attempted to be sent out while `ProcessingRedraws`"); } let event_loop_proxy = this.event_loop_proxy().clone(); drop(this); if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) { - handle_event(mtm, Event::UserWakeUp); + get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm })); } loop { let mut this = AppState::get_mut(mtm); - let queued_events = match this.state_mut() { - &mut AppStateImpl::InUserCallback { ref mut queued_events, queued_gpu_redraws: _ } => { - mem::take(queued_events) - }, - s => bug!("unexpected state {:?}", s), - }; + let queued_events = mem::take(&mut this.queued_events); if queued_events.is_empty() { - let queued_gpu_redraws = match this.take_state() { - AppStateImpl::InUserCallback { queued_events: _, queued_gpu_redraws } => { - queued_gpu_redraws - }, - _ => unreachable!(), - }; - this.app_state = - Some(AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow }); break; } drop(this); - for wrapper in queued_events { - match wrapper { - EventWrapper::StaticEvent(event) => handle_event(mtm, event), - EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event), - } + for event in queued_events { + handle_wrapped_event(mtm, event); } if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) { - handle_event(mtm, Event::UserWakeUp); + get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm })); } } } @@ -597,10 +447,10 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; - events.push(EventWrapper::StaticEvent(Event::WindowEvent { + events.push(EventWrapper::Window { window_id: window.id(), event: WindowEvent::Occluded(occluded), - })); + }); } } handle_nonuser_events(mtm, events); @@ -623,23 +473,37 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) { let redraw_events: Vec = this .main_events_cleared_transition() .into_iter() - .map(|window| { - EventWrapper::StaticEvent(Event::WindowEvent { - window_id: window.id(), - event: WindowEvent::RedrawRequested, - }) + .map(|window| EventWrapper::Window { + window_id: window.id(), + event: WindowEvent::RedrawRequested, }) .collect(); drop(this); handle_nonuser_events(mtm, redraw_events); - handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait)); + get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm })); + handle_nonuser_events(mtm, []); } pub fn handle_events_cleared(mtm: MainThreadMarker) { AppState::get_mut(mtm).events_cleared_transition(); } +pub(crate) fn handle_resumed(mtm: MainThreadMarker) { + get_handler(mtm).handle(|app| app.resumed(&ActiveEventLoop { mtm })); + handle_nonuser_events(mtm, []); +} + +pub(crate) fn handle_suspended(mtm: MainThreadMarker) { + get_handler(mtm).handle(|app| app.suspended(&ActiveEventLoop { mtm })); + handle_nonuser_events(mtm, []); +} + +pub(crate) fn handle_memory_warning(mtm: MainThreadMarker) { + get_handler(mtm).handle(|app| app.memory_warning(&ActiveEventLoop { mtm })); + handle_nonuser_events(mtm, []); +} + pub(crate) fn terminated(application: &UIApplication) { let mtm = MainThreadMarker::from(application); @@ -653,10 +517,10 @@ pub(crate) fn terminated(application: &UIApplication) { let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; - events.push(EventWrapper::StaticEvent(Event::WindowEvent { + events.push(EventWrapper::Window { window_id: window.id(), event: WindowEvent::Destroyed, - })); + }); } } handle_nonuser_events(mtm, events); @@ -665,20 +529,26 @@ pub(crate) fn terminated(application: &UIApplication) { this.terminated_transition(); drop(this); - handle_event(mtm, Event::LoopExiting) + get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm })); +} + +fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) { + match event { + EventWrapper::Window { window_id, event } => get_handler(mtm) + .handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)), + EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event), + } } fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) { let ScaleFactorChanged { suggested_size, scale_factor, window } = event; let new_surface_size = Arc::new(Mutex::new(suggested_size)); - let event = Event::WindowEvent { - window_id: window.id(), - event: WindowEvent::ScaleFactorChanged { + get_handler(mtm).handle(|app| { + app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)), - }, - }; - handle_event(mtm, event); + }); + }); let (view, screen_frame) = get_view_and_screen_frame(&window); let physical_size = *new_surface_size.lock().unwrap(); drop(new_surface_size); diff --git a/src/platform_impl/apple/uikit/event_loop.rs b/src/platform_impl/apple/uikit/event_loop.rs index 4f93a61d90..ba7377ce66 100644 --- a/src/platform_impl/apple/uikit/event_loop.rs +++ b/src/platform_impl/apple/uikit/event_loop.rs @@ -23,11 +23,10 @@ use objc2_ui_kit::{ use rwh_06::HasDisplayHandle; use super::super::notification_center::create_observer; -use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper}; +use super::app_state::{send_occluded_event_for_all_windows, AppState}; use super::{app_state, monitor, MonitorHandle}; use crate::application::ApplicationHandler; use crate::error::{EventLoopError, NotSupportedError, RequestError}; -use crate::event::Event; use crate::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, @@ -174,17 +173,13 @@ impl EventLoop { ¢er, // `applicationDidBecomeActive:` unsafe { UIApplicationDidBecomeActiveNotification }, - move |_| { - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)); - }, + move |_| app_state::handle_resumed(mtm), ); let _will_resign_active_observer = create_observer( ¢er, // `applicationWillResignActive:` unsafe { UIApplicationWillResignActiveNotification }, - move |_| { - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)); - }, + move |_| app_state::handle_suspended(mtm), ); let _will_enter_foreground_observer = create_observer( ¢er, @@ -231,12 +226,7 @@ impl EventLoop { ¢er, // `applicationDidReceiveMemoryWarning:` unsafe { UIApplicationDidReceiveMemoryWarningNotification }, - move |_| { - app_state::handle_nonuser_event( - mtm, - EventWrapper::StaticEvent(Event::MemoryWarning), - ); - }, + move |_| app_state::handle_memory_warning(mtm), ); Ok(EventLoop { diff --git a/src/platform_impl/apple/uikit/view.rs b/src/platform_impl/apple/uikit/view.rs index 64ee71570a..9330a9cc5c 100644 --- a/src/platform_impl/apple/uikit/view.rs +++ b/src/platform_impl/apple/uikit/view.rs @@ -17,8 +17,8 @@ use super::app_state::{self, EventWrapper}; use super::window::WinitUIWindow; use crate::dpi::PhysicalPosition; use crate::event::{ - ButtonSource, ElementState, Event, FingerId, Force, KeyEvent, PointerKind, PointerSource, - TouchPhase, WindowEvent, + ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase, + WindowEvent, }; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey}; use crate::platform_impl::KeyEventExtra; @@ -60,10 +60,10 @@ declare_class!( let window = self.window().unwrap(); app_state::handle_nonuser_event( mtm, - EventWrapper::StaticEvent(Event::WindowEvent { + EventWrapper::Window { window_id: window.id(), event: WindowEvent::RedrawRequested, - }), + }, ); let _: () = unsafe { msg_send![super(self), drawRect: rect] }; } @@ -84,10 +84,10 @@ declare_class!( let window = self.window().unwrap(); app_state::handle_nonuser_event( mtm, - EventWrapper::StaticEvent(Event::WindowEvent { + EventWrapper::Window { window_id: window.id(), event: WindowEvent::SurfaceResized(size), - }), + }, ); } @@ -131,12 +131,11 @@ declare_class!( suggested_size: size.to_physical(scale_factor), }, )) - .chain(std::iter::once(EventWrapper::StaticEvent( - Event::WindowEvent { + .chain(std::iter::once(EventWrapper::Window { window_id, event: WindowEvent::SurfaceResized(size.to_physical(scale_factor)), }, - ))), + )), ); } @@ -192,14 +191,14 @@ declare_class!( state => panic!("unexpected recognizer state: {state:?}"), }; - let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { + let gesture_event = EventWrapper::Window { window_id: window.id(), event: WindowEvent::PinchGesture { device_id: None, delta: delta as f64, phase, }, - }); + }; let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); @@ -210,12 +209,12 @@ declare_class!( let window = self.window().unwrap(); if recognizer.state() == UIGestureRecognizerState::Ended { - let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { + let gesture_event = EventWrapper::Window { window_id: window.id(), event: WindowEvent::DoubleTapGesture { device_id: None, }, - }); + }; let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); @@ -252,14 +251,14 @@ declare_class!( }; // Make delta negative to match macos, convert to degrees - let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { + let gesture_event = EventWrapper::Window { window_id: window.id(), event: WindowEvent::RotationGesture { device_id: None, delta: -delta.to_degrees() as _, phase, }, - }); + }; let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); @@ -303,14 +302,14 @@ declare_class!( }; - let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { + let gesture_event = EventWrapper::Window { window_id: window.id(), event: WindowEvent::PanGesture { device_id: None, delta: PhysicalPosition::new(dx as _, dy as _), phase, }, - }); + }; let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); @@ -538,7 +537,7 @@ impl WinitView { } }; - touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { + touch_events.push(EventWrapper::Window { window_id, event: WindowEvent::PointerEntered { device_id: None, @@ -550,8 +549,8 @@ impl WinitView { PointerKind::Touch(finger_id) }, }, - })); - touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { + }); + touch_events.push(EventWrapper::Window { window_id, event: WindowEvent::PointerButton { device_id: None, @@ -564,7 +563,7 @@ impl WinitView { ButtonSource::Touch { finger_id, force } }, }, - })); + }); }, UITouchPhase::Moved => { let (primary, source) = if let UITouchType::Pencil = touch_type { @@ -576,7 +575,7 @@ impl WinitView { }) }; - touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { + touch_events.push(EventWrapper::Window { window_id, event: WindowEvent::PointerMoved { device_id: None, @@ -584,7 +583,7 @@ impl WinitView { position, source, }, - })); + }); }, // 2 is UITouchPhase::Stationary and is not expected here UITouchPhase::Ended | UITouchPhase::Cancelled => { @@ -600,7 +599,7 @@ impl WinitView { }; if let UITouchPhase::Ended = phase { - touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { + touch_events.push(EventWrapper::Window { window_id, event: WindowEvent::PointerButton { device_id: None, @@ -613,10 +612,10 @@ impl WinitView { ButtonSource::Touch { finger_id, force } }, }, - })); + }); } - touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { + touch_events.push(EventWrapper::Window { window_id, event: WindowEvent::PointerLeft { device_id: None, @@ -628,7 +627,7 @@ impl WinitView { PointerKind::Touch(finger_id) }, }, - })); + }); }, _ => panic!("unexpected touch phase: {phase:?}"), } @@ -647,29 +646,25 @@ impl WinitView { text.to_string().chars().flat_map(|c| { let text = smol_str::SmolStr::from_iter([c]); // Emit both press and release events - [ElementState::Pressed, ElementState::Released].map(|state| { - EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - event: KeyEvent { - text: if state == ElementState::Pressed { - Some(text.clone()) - } else { - None - }, - state, - location: KeyLocation::Standard, - repeat: false, - logical_key: Key::Character(text.clone()), - physical_key: PhysicalKey::Unidentified( - NativeKeyCode::Unidentified, - ), - platform_specific: KeyEventExtra {}, + [ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window { + window_id, + event: WindowEvent::KeyboardInput { + device_id: None, + event: KeyEvent { + text: if state == ElementState::Pressed { + Some(text.clone()) + } else { + None }, - is_synthetic: false, - device_id: None, + state, + location: KeyLocation::Standard, + repeat: false, + logical_key: Key::Character(text.clone()), + physical_key: PhysicalKey::Unidentified(NativeKeyCode::Unidentified), + platform_specific: KeyEventExtra {}, }, - }) + is_synthetic: false, + }, }) }), ); @@ -681,23 +676,21 @@ impl WinitView { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events( mtm, - [ElementState::Pressed, ElementState::Released].map(|state| { - EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id: None, - event: KeyEvent { - state, - logical_key: Key::Named(NamedKey::Backspace), - physical_key: PhysicalKey::Code(KeyCode::Backspace), - platform_specific: KeyEventExtra {}, - repeat: false, - location: KeyLocation::Standard, - text: None, - }, - is_synthetic: false, + [ElementState::Pressed, ElementState::Released].map(|state| EventWrapper::Window { + window_id, + event: WindowEvent::KeyboardInput { + device_id: None, + event: KeyEvent { + state, + logical_key: Key::Named(NamedKey::Backspace), + physical_key: PhysicalKey::Code(KeyCode::Backspace), + platform_specific: KeyEventExtra {}, + repeat: false, + location: KeyLocation::Standard, + text: None, }, - }) + is_synthetic: false, + }, }), ); } diff --git a/src/platform_impl/apple/uikit/window.rs b/src/platform_impl/apple/uikit/window.rs index 1764e00b8f..fcbe2cf3ba 100644 --- a/src/platform_impl/apple/uikit/window.rs +++ b/src/platform_impl/apple/uikit/window.rs @@ -23,7 +23,7 @@ use crate::dpi::{ Position, Size, }; use crate::error::{NotSupportedError, RequestError}; -use crate::event::{Event, WindowEvent}; +use crate::event::WindowEvent; use crate::icon::Icon; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}; @@ -51,10 +51,10 @@ declare_class!( let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event( mtm, - EventWrapper::StaticEvent(Event::WindowEvent { + EventWrapper::Window { window_id: self.id(), event: WindowEvent::Focused(true), - }), + }, ); let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; } @@ -64,10 +64,10 @@ declare_class!( let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event( mtm, - EventWrapper::StaticEvent(Event::WindowEvent { + EventWrapper::Window { window_id: self.id(), event: WindowEvent::Focused(false), - }), + }, ); let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; } From 35379f305a0b1c6caf8fe9a04fc26e1055d41fd3 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 21:24:57 +0100 Subject: [PATCH 09/10] Remove feature description in `FEATURES.md` (#3479) It easily becomes out of date. --- .github/PULL_REQUEST_TEMPLATE.md | 1 - FEATURES.md | 198 ------------------------------- src/event.rs | 34 ++++-- src/monitor.rs | 20 ++-- src/window.rs | 7 +- 5 files changed, 40 insertions(+), 220 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b219f393f4..4f7bbec39f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,4 +2,3 @@ - [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users - [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior - [ ] Created or updated an example program if it would help users understand this functionality -- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented diff --git a/FEATURES.md b/FEATURES.md index 6f5aff3f4e..25055f540f 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -47,201 +47,3 @@ through the implementation work necessary to function on all platforms. When one gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. If that gets accepted, the platform-specific functions get deprecated and become permanently exposed through the core, cross-platform API. - -# Features - -## Extending this section - -If your PR makes notable changes to Winit's features, please update this section as follows: - -- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core - feature, add a row to the feature matrix and describe what platforms the feature has been implemented on. - -- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the - API rework on all relevant platforms, please move it to the `Completed API Reworks` table. - -- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*, - or mark it as *mostly completed* and link to an issue describing the problems with the implementation. - -## Core - -### Windowing -- **Window initialization**: Winit allows the creation of a window -- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context -- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan -- **Window decorations**: The windows created by winit are properly decorated, and the decorations can - be deactivated -- **Window decorations toggle**: Decorations can be turned on or off after window creation -- **Window resizing**: The windows created by winit can be resized and generate the appropriate events - when they are. The application can precisely control its window size if desired. -- **Window resize increments**: When the window gets resized, the application can choose to snap the window's - size to specific values. -- **Window transparency**: Winit allows the creation of windows with a transparent background. -- **Window maximization**: The windows created by winit can be maximized upon creation. -- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after - creation. -- **Window minimization**: The windows created by winit can be minimized after creation. -- **Fullscreen**: The windows created by winit can be put into fullscreen mode. -- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after - creation. -- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor - for fullscreen windows and, if applicable, captures the monitor for exclusive - use by this application. -- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. -- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent - windows can be disabled in favor of popup windows. This feature also guarantees that popup windows - get drawn above their owner. - - -### System Information -- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary. -- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth). - -### Input Handling -- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events. -- **Mouse set location**: Forcibly changing the location of the pointer. -- **Cursor locking**: Locking the cursor inside the window so it cannot move. -- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them. -- **Cursor icon**: Changing the cursor icon or hiding the cursor. -- **Cursor image**: Changing the cursor to your own image. -- **Cursor hittest**: Handle or ignore mouse events for a window. -- **Touch events**: Single-touch events. -- **Touch pressure**: Touch events contain information about the amount of force being applied. -- **Multitouch**: Multi-touch events, including cancellation of a gesture. -- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and - translating keypresses into UTF-8 characters, handling dead keys and IMEs. -- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled. -- **Raw Device Events**: Capturing input from input devices without any OS filtering. -- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks. -- **Device movement events**: Capturing input from the device gyroscope and accelerometer. - -## Platform -### Windows -* Setting the name of the internal window class -* Setting the taskbar icon -* Setting the parent window -* Setting a menu bar -* `WS_EX_NOREDIRECTIONBITMAP` support -* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme -* Changing a system-drawn backdrop -* Setting the window border color -* Setting the title bar background color -* Setting the title color -* Setting the corner rounding preference - -### macOS -* Window activation policy -* Window movable by background -* Transparent titlebar -* Hidden titlebar -* Hidden titlebar buttons -* Full-size content view -* Accepts first mouse -* Set a preferred theme and get current theme. - -### Unix -* Window urgency -* X11 Window Class -* X11 Override Redirect Flag -* GTK Theme Variant -* Base window size -* Setting the X11 parent window - -### iOS -* Get the `UIScreen` object pointer -* Setting the `UIView` hidpi factor -* Valid orientations -* Home indicator visibility -* Status bar visibility and style -* Deferring system gestures -* Getting the preferred video mode - -### Web -* Get if the systems preferred color scheme is "dark" - -## Compatibility Matrix - -Legend: - -- ✔️: Works as intended -- ▢: Mostly works, but some bugs are known -- ❌: Missing feature or large bugs making it unusable -- **N/A**: Not applicable for this platform -- ❓: Unknown status - -### Windowing -|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS| -|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | -|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ | -|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ | -|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** | -|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ | -|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | -|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ | -|Window resize increments |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** | -|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ | -|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ | -|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | -|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | -|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | -|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** | -|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** | -|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** | -|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ | -|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** | - -### System information -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| -|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ | -|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ | -|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ | - -### Input handling -|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| -|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | -|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ | -|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** | -|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ | -|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ | -|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** | -|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** | -|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ | -|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** | -|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** | -|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** | -|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ | -|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** | -|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** | -|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** | -|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** | -|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** | -|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** | - -### Pending API Reworks -Changes in the API that have been agreed upon but aren't implemented across all platforms. - -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| -|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | -|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | -|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ | -|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ | - -### Completed API Reworks -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| -|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | - -[#165]: https://github.com/rust-windowing/winit/issues/165 -[#219]: https://github.com/rust-windowing/winit/issues/219 -[#242]: https://github.com/rust-windowing/winit/issues/242 -[#306]: https://github.com/rust-windowing/winit/issues/306 -[#315]: https://github.com/rust-windowing/winit/issues/315 -[#319]: https://github.com/rust-windowing/winit/issues/319 -[#33]: https://github.com/rust-windowing/winit/issues/33 -[#459]: https://github.com/rust-windowing/winit/issues/459 -[#5]: https://github.com/rust-windowing/winit/issues/5 -[#63]: https://github.com/rust-windowing/winit/issues/63 -[#720]: https://github.com/rust-windowing/winit/issues/720 -[#721]: https://github.com/rust-windowing/winit/issues/721 -[#750]: https://github.com/rust-windowing/winit/issues/750 -[#753]: https://github.com/rust-windowing/winit/issues/753 -[#804]: https://github.com/rust-windowing/winit/issues/804 diff --git a/src/event.rs b/src/event.rs index 5a89923ec3..e7af7d65cd 100644 --- a/src/event.rs +++ b/src/event.rs @@ -175,18 +175,23 @@ pub enum WindowEvent { /// The window has been destroyed. Destroyed, - /// A file has been dropped into the window. - /// - /// When the user drops multiple files at once, this event will be emitted for each file - /// separately. - DroppedFile(PathBuf), - /// A file is being hovered over the window. /// /// When the user hovers multiple files at once, this event will be emitted for each file /// separately. HoveredFile(PathBuf), + /// A file has been dropped into the window. + /// + /// When the user drops multiple files at once, this event will be emitted for each file + /// separately. + /// + /// The support for this is known to be incomplete, see [#720] for more + /// information. + /// + /// [#720]: https://github.com/rust-windowing/winit/issues/720 + DroppedFile(PathBuf), + /// A file was hovered, but has exited the window. /// /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were @@ -207,6 +212,7 @@ pub enum WindowEvent { /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key /// events which are not marked as `is_synthetic`. + /// - **iOS:** Unsupported. KeyboardInput { device_id: Option, event: KeyEvent, @@ -410,10 +416,18 @@ pub enum WindowEvent { /// Touchpad pressure event. /// - /// At the moment, only supported on Apple forcetouch-capable macbooks. - /// The parameters are: pressure level (value between 0 and 1 representing how hard the - /// touchpad is being pressed) and stage (integer representing the click level). - TouchpadPressure { device_id: Option, pressure: f32, stage: i64 }, + /// ## Platform-specific + /// + /// - **macOS**: Only supported on Apple forcetouch-capable macbooks. + /// - **Android / iOS / Wayland / X11 / Windows / Orbital / Web:** Unsupported. + TouchpadPressure { + device_id: Option, + /// Value between 0 and 1 representing how hard the touchpad is being + /// pressed. + pressure: f32, + /// Represents the click level. + stage: i64, + }, /// The window's scale factor has changed. /// diff --git a/src/monitor.rs b/src/monitor.rs index aeb64b4a01..7cc7cb8fb3 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,18 +1,12 @@ //! Types useful for interacting with a user's monitors. -//! -//! If you want to get basic information about a monitor, you can use the -//! [`MonitorHandle`] type. This is retrieved from one of the following -//! methods, which return an iterator of [`MonitorHandle`]: -//! - [`ActiveEventLoop::available_monitors`][crate::event_loop::ActiveEventLoop::available_monitors]. -//! - [`Window::available_monitors`][crate::window::Window::available_monitors]. use std::num::{NonZeroU16, NonZeroU32}; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::platform_impl; -/// Describes a fullscreen video mode of a monitor. +/// A handle to a fullscreen video mode of a specific monitor. /// -/// Can be acquired with [`MonitorHandle::video_modes`]. +/// This can be acquired with [`MonitorHandle::video_modes`]. #[derive(Clone, PartialEq, Eq, Hash)] pub struct VideoModeHandle { pub(crate) video_mode: platform_impl::VideoModeHandle, @@ -92,7 +86,15 @@ impl std::fmt::Display for VideoModeHandle { /// Handle to a monitor. /// -/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. +/// Allows you to retrieve basic information and metadata about a monitor. +/// +/// Can be used in [`Window`] creation to place the window on a specific +/// monitor. +/// +/// This can be retrieved from one of the following methods, which return an +/// iterator of [`MonitorHandle`]s: +/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors). +/// - [`Window::available_monitors`](crate::window::Window::available_monitors). /// /// ## Platform-specific /// diff --git a/src/window.rs b/src/window.rs index cd16d3878a..ff698af2c3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -909,7 +909,7 @@ pub trait Window: AsAny + Send + Sync { /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. fn enabled_buttons(&self) -> WindowButtons; - /// Sets the window to minimized or back + /// Minimize the window, or put it back from the minimized state. /// /// ## Platform-specific /// @@ -945,7 +945,7 @@ pub trait Window: AsAny + Send + Sync { /// - **iOS / Android / Web:** Unsupported. fn is_maximized(&self) -> bool; - /// Sets the window to fullscreen or back. + /// Set the window's fullscreen state. /// /// ## Platform-specific /// @@ -1433,6 +1433,9 @@ impl From for CursorIcon { /// Fullscreen modes. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Fullscreen { + /// This changes the video mode of the monitor for fullscreen windows and, + /// if applicable, captures the monitor for exclusive use by this + /// application. Exclusive(VideoModeHandle), /// Providing `None` to `Borderless` will fullscreen on the current monitor. From 171d53c04238e851a8f687bca027584fa7464bda Mon Sep 17 00:00:00 2001 From: Benjamin Brienen Date: Tue, 3 Dec 2024 23:07:19 +0100 Subject: [PATCH 10/10] Update `smol_str` (#3991) --- Cargo.toml | 2 +- src/changelog/unreleased.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bbd6f8d1a1..37d69a837f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ cursor-icon = "1.1.0" dpi = { version = "0.1.1", path = "dpi" } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] } serde = { workspace = true, optional = true } -smol_str = "0.2.0" +smol_str = "0.3" tracing = { version = "0.1.40", default-features = false } [dev-dependencies] diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 23654f4b97..81d485122c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -164,6 +164,7 @@ changelog entry. - On macOS and iOS, no longer emit `ScaleFactorChanged` upon window creation. - On macOS, no longer emit `Focused` upon window creation. - On iOS, emit more events immediately, instead of queuing them. +- Update `smol_str` to version `0.3` ### Removed