mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
Bring OptionAsAlt
back for macOS
The correct handling of this setting requires to change the events we're getting from the macOS on the fly and call `interpretKeyEvents`, which could affect handling of the next events, meaning that we can't provide them on `KeyEvent`.
This commit is contained in:
parent
b2a46d0439
commit
7094a223af
|
@ -38,7 +38,6 @@ And please only add new entries to the top of this list, right below the `# Unre
|
|||
portable) interpretations of a given key-press.
|
||||
- Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and
|
||||
`KeyCode`.
|
||||
- Remove `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
|
||||
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
|
||||
- On Orbital, fix `ModifiersChanged` not being sent.
|
||||
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
|
||||
|
|
75
examples/window_option_as_alt.rs
Normal file
75
examples/window_option_as_alt.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
#![allow(clippy::single_match)]
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::{OptionAsAlt, WindowExtMacOS};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::{
|
||||
event::ElementState,
|
||||
event::{Event, MouseButton, WindowEvent},
|
||||
event_loop::EventLoop,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "util/fill.rs"]
|
||||
mod fill;
|
||||
|
||||
/// Prints the keyboard events characters received when option_is_alt is true versus false.
|
||||
/// A left mouse click will toggle option_is_alt.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("A fantastic window!")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
window.set_ime_allowed(true);
|
||||
|
||||
let mut option_as_alt = window.option_as_alt();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
control_flow.set_wait();
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
} if window_id == window.id() => control_flow.set_exit(),
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::MouseInput {
|
||||
state: ElementState::Pressed,
|
||||
button: MouseButton::Left,
|
||||
..
|
||||
} => {
|
||||
option_as_alt = match option_as_alt {
|
||||
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
|
||||
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
|
||||
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
|
||||
OptionAsAlt::Both => OptionAsAlt::None,
|
||||
};
|
||||
|
||||
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
|
||||
window.set_option_as_alt(option_as_alt);
|
||||
}
|
||||
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
fill::fill_window(&window);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn main() {
|
||||
println!("This example is only supported on MacOS");
|
||||
}
|
|
@ -56,6 +56,17 @@ pub trait WindowExtMacOS {
|
|||
|
||||
/// Put the window in a state which indicates a file save is required.
|
||||
fn set_document_edited(&self, edited: bool);
|
||||
|
||||
/// Set option as alt behavior as described in [`OptionAsAlt`].
|
||||
///
|
||||
/// This will ignore diacritical marks and accent characters from
|
||||
/// being processed as received characters. Instead, the input
|
||||
/// device's raw character will be placed in event queues with the
|
||||
/// Alt modifier set.
|
||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
|
||||
|
||||
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
|
||||
fn option_as_alt(&self) -> OptionAsAlt;
|
||||
}
|
||||
|
||||
impl WindowExtMacOS for Window {
|
||||
|
@ -98,6 +109,16 @@ impl WindowExtMacOS for Window {
|
|||
fn set_document_edited(&self, edited: bool) {
|
||||
self.window.set_document_edited(edited)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
||||
self.window.set_option_as_alt(option_as_alt)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn option_as_alt(&self) -> OptionAsAlt {
|
||||
self.window.option_as_alt()
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||
|
@ -140,6 +161,10 @@ pub trait WindowBuilderExtMacOS {
|
|||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
||||
/// Window accepts click-through mouse events.
|
||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
||||
/// Set how the <kbd>Option</kbd> keys are interpreted.
|
||||
///
|
||||
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
|
||||
}
|
||||
|
||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||
|
@ -199,6 +224,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
|||
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
|
||||
self.platform_specific.option_as_alt = option_as_alt;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopBuilderExtMacOS {
|
||||
|
@ -309,3 +340,23 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
|||
self.p.hide_other_applications()
|
||||
}
|
||||
}
|
||||
|
||||
/// Option as alt behavior.
|
||||
///
|
||||
/// The default is `None`.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum OptionAsAlt {
|
||||
/// The left `Option` key is treated as `Alt`.
|
||||
OnlyLeft,
|
||||
|
||||
/// The right `Option` key is treated as `Alt`.
|
||||
OnlyRight,
|
||||
|
||||
/// Both `Option` keys are treated as `Alt`.
|
||||
Both,
|
||||
|
||||
/// No special handling is applied for `Option` key.
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
|
|
@ -67,6 +67,35 @@ extern_methods!(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn keyEventWithType(
|
||||
type_: NSEventType,
|
||||
location: NSPoint,
|
||||
modifier_flags: NSEventModifierFlags,
|
||||
timestamp: NSTimeInterval,
|
||||
window_num: NSInteger,
|
||||
context: Option<&NSObject>,
|
||||
characters: &NSString,
|
||||
characters_ignoring_modifiers: &NSString,
|
||||
is_a_repeat: bool,
|
||||
scancode: c_ushort,
|
||||
) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::class(),
|
||||
keyEventWithType: type_,
|
||||
location: location,
|
||||
modifierFlags: modifier_flags,
|
||||
timestamp: timestamp,
|
||||
windowNumber: window_num,
|
||||
context: context,
|
||||
characters: characters,
|
||||
charactersIgnoringModifiers: characters_ignoring_modifiers,
|
||||
isARepeat: is_a_repeat,
|
||||
keyCode: scancode,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(locationInWindow)]
|
||||
pub fn locationInWindow(&self) -> NSPoint;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
|||
TouchPhase, WindowEvent,
|
||||
},
|
||||
keyboard::{Key, KeyCode, KeyLocation, ModifiersState},
|
||||
platform::macos::{OptionAsAlt, WindowExtMacOS},
|
||||
platform::scancode::KeyCodeExtScancode,
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
|
@ -482,6 +483,7 @@ declare_class!(
|
|||
// Get the characters from the event.
|
||||
let old_ime_state = self.state.ime_state;
|
||||
self.state.forward_key_to_app = false;
|
||||
let event = replace_event(event, self.window().option_as_alt());
|
||||
|
||||
// The `interpretKeyEvents` function might call
|
||||
// `setMarkedText`, `insertText`, and `doCommandBySelector`.
|
||||
|
@ -500,7 +502,7 @@ declare_class!(
|
|||
}
|
||||
}
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
self.update_modifiers(&event, false);
|
||||
|
||||
let had_ime_input = match self.state.ime_state {
|
||||
ImeState::Commited => {
|
||||
|
@ -514,7 +516,7 @@ declare_class!(
|
|||
};
|
||||
|
||||
if !had_ime_input || self.state.forward_key_to_app {
|
||||
let key_event = create_key_event(event, true, event.is_a_repeat(), None);
|
||||
let key_event = create_key_event(&event, true, event.is_a_repeat(), None);
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: key_event,
|
||||
|
@ -527,13 +529,14 @@ declare_class!(
|
|||
fn key_up(&mut self, event: &NSEvent) {
|
||||
trace_scope!("keyUp:");
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
let event = replace_event(event, self.window().option_as_alt());
|
||||
self.update_modifiers(&event, false);
|
||||
|
||||
// We want to send keyboard input when we are currently in the ground state.
|
||||
if matches!(self.state.ime_state, ImeState::Ground | ImeState::Disabled) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: create_key_event(event, false, false, None),
|
||||
event: create_key_event(&event, false, false, None),
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
|
@ -1038,3 +1041,38 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
|||
n => MouseButton::Other(n as u16),
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: to get option as alt working we need to rewrite events
|
||||
// we're getting from the operating system, which makes it
|
||||
// impossible to provide such events as extra in `KeyEvent`.
|
||||
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent, Shared> {
|
||||
let ev_mods = event_mods(event).state;
|
||||
let ignore_alt_characters = match option_as_alt {
|
||||
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
|
||||
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
|
||||
OptionAsAlt::Both if ev_mods.alt_key() => true,
|
||||
_ => false,
|
||||
} && !ev_mods.control_key()
|
||||
&& !ev_mods.super_key();
|
||||
|
||||
if ignore_alt_characters {
|
||||
let ns_chars = event
|
||||
.charactersIgnoringModifiers()
|
||||
.expect("expected characters to be non-null");
|
||||
|
||||
NSEvent::keyEventWithType(
|
||||
event.type_(),
|
||||
event.locationInWindow(),
|
||||
event.modifierFlags(),
|
||||
event.timestamp(),
|
||||
event.window_number(),
|
||||
None,
|
||||
&ns_chars,
|
||||
&ns_chars,
|
||||
event.is_a_repeat(),
|
||||
event.key_code(),
|
||||
)
|
||||
} else {
|
||||
event.copy()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
|||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
event::WindowEvent,
|
||||
icon::Icon,
|
||||
platform::macos::WindowExtMacOS,
|
||||
platform::macos::{OptionAsAlt, WindowExtMacOS},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
appkit::NSWindowOrderingMode,
|
||||
|
@ -85,6 +85,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
|||
pub disallow_hidpi: bool,
|
||||
pub has_shadow: bool,
|
||||
pub accepts_first_mouse: bool,
|
||||
pub option_as_alt: OptionAsAlt,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||
|
@ -100,6 +101,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
|||
disallow_hidpi: false,
|
||||
has_shadow: true,
|
||||
accepts_first_mouse: true,
|
||||
option_as_alt: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +162,8 @@ pub struct SharedState {
|
|||
|
||||
/// The current resize incerments for the window content.
|
||||
pub(crate) resize_increments: NSSize,
|
||||
/// The state of the `Option` as `Alt`.
|
||||
pub(crate) option_as_alt: OptionAsAlt,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
|
@ -369,6 +373,8 @@ impl WinitWindow {
|
|||
this.center();
|
||||
}
|
||||
|
||||
this.set_option_as_alt(pl_attrs.option_as_alt);
|
||||
|
||||
Id::into_shared(this)
|
||||
})
|
||||
})
|
||||
|
@ -1376,6 +1382,16 @@ impl WindowExtMacOS for WinitWindow {
|
|||
fn set_document_edited(&self, edited: bool) {
|
||||
self.setDocumentEdited(edited)
|
||||
}
|
||||
|
||||
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
||||
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||
shared_state_lock.option_as_alt = option_as_alt;
|
||||
}
|
||||
|
||||
fn option_as_alt(&self) -> OptionAsAlt {
|
||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||
shared_state_lock.option_as_alt
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_ns_theme() -> Theme {
|
||||
|
|
Loading…
Reference in a new issue