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/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/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/changelog/unreleased.md b/src/changelog/unreleased.md index 0227a77088..81d485122c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -75,7 +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 X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes. ### Changed @@ -163,6 +163,8 @@ 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. +- Update `smol_str` to version `0.3` ### Removed @@ -209,3 +211,8 @@ 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`. +- 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/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/platform_impl/apple/appkit/event.rs b/src/platform_impl/apple/appkit/event.rs index f71111b67f..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); @@ -377,6 +367,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 +413,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 +446,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 +477,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 +508,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 +554,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 +573,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 +596,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 +629,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)), }) } diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index e8bdffa809..14a0a11939 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -4,30 +4,30 @@ 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; 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; @@ -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: } @@ -495,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, @@ -518,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, }); } @@ -565,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, @@ -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) { @@ -962,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); diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 711535aa72..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. @@ -945,8 +962,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()) } @@ -980,19 +1010,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); 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/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)) + }); + } +} 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] }; } 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)), }) } 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.