diff --git a/examples/application.rs b/examples/application.rs index aa869384a5..196663d028 100644 --- a/examples/application.rs +++ b/examples/application.rs @@ -22,7 +22,7 @@ use winit::error::RequestError; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::icon::{Icon, RgbaIcon}; -use winit::keyboard::{Key, ModifiersState}; +use winit::keyboard::Key; use winit::monitor::Fullscreen; #[cfg(macos_platform)] use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS}; @@ -36,6 +36,7 @@ use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb}; use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11}; use winit::window::{CursorGrabMode, ResizeDirection, Theme, Window, WindowAttributes, WindowId}; use winit_core::application::macos::ApplicationHandlerExtMacOS; +use winit_core::keyboard::Modifiers; #[path = "util/tracing.rs"] mod tracing; @@ -358,14 +359,14 @@ impl Application { } /// Process the key binding. - fn process_key_binding(key: &str, mods: &ModifiersState) -> Option { + fn process_key_binding(key: &str, mods: &Modifiers) -> Option { KEY_BINDINGS .iter() .find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action)) } /// Process mouse binding. - fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option { + fn process_mouse_binding(button: MouseButton, mods: &Modifiers) -> Option { MOUSE_BINDINGS .iter() .find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action)) @@ -447,7 +448,7 @@ impl ApplicationHandler for Application { self.windows.remove(&window_id); }, WindowEvent::ModifiersChanged(modifiers) => { - window.modifiers = modifiers.state(); + window.modifiers = modifiers; info!("Modifiers changed to {:?}", window.modifiers); }, WindowEvent::MouseWheel { delta, .. } => match delta { @@ -621,7 +622,7 @@ struct WindowState { /// Cursor position over the window. cursor_position: Option>, /// Window modifiers state. - modifiers: ModifiersState, + modifiers: Modifiers, /// Occlusion state of the window. occluded: bool, /// Current cursor grab mode. @@ -1002,17 +1003,19 @@ impl WindowState { struct Binding { trigger: T, - mods: ModifiersState, + mods: Modifiers, action: Action, } impl Binding { - const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self { + const fn new(trigger: T, mods: Modifiers, action: Action) -> Self { Self { trigger, mods, action } } - fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool { - &self.trigger == trigger && &self.mods == mods + fn is_triggered_by(&self, trigger: &T, mods: &Modifiers) -> bool { + // only consider common shortcut-related mods Shift, Alt, Meta, Control (side-agnostic) + // ignore mismatch in app's Shift+F and actual Shift+NumLock+F + &self.trigger == trigger && self.mods.shortcut_match(mods) } } @@ -1141,12 +1144,12 @@ fn load_icon(bytes: &[u8]) -> Icon { RgbaIcon::new(icon_rgba, icon_width, icon_height).expect("Failed to open icon").into() } -fn modifiers_to_string(mods: ModifiersState) -> String { +fn modifiers_to_string(mods: Modifiers) -> String { let mut mods_line = String::new(); // Always add + since it's printed as a part of the bindings. for (modifier, desc) in [ ( - ModifiersState::META, + Modifiers::META, if cfg!(target_os = "windows") { "Win+" } else if cfg!(target_vendor = "apple") { @@ -1155,9 +1158,9 @@ fn modifiers_to_string(mods: ModifiersState) -> String { "Super+" }, ), - (ModifiersState::ALT, "Alt+"), - (ModifiersState::CONTROL, "Ctrl+"), - (ModifiersState::SHIFT, "Shift+"), + (Modifiers::ALT, "Alt+"), + (Modifiers::CONTROL, "Ctrl+"), + (Modifiers::SHIFT, "Shift+"), ] { if !mods.contains(modifier) { continue; @@ -1272,54 +1275,46 @@ const CURSORS: &[CursorIcon] = &[ ]; const KEY_BINDINGS: &[Binding<&'static str>] = &[ - Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow), - Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp), - Binding::new("F", ModifiersState::SHIFT, Action::ToggleAnimatedFillColor), - Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen), + Binding::new("Q", Modifiers::CONTROL, Action::CloseWindow), + Binding::new("H", Modifiers::CONTROL, Action::PrintHelp), + Binding::new("F", Modifiers::SHIFT, Action::ToggleAnimatedFillColor), + Binding::new("F", Modifiers::CONTROL, Action::ToggleFullscreen), #[cfg(macos_platform)] - Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen), - Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations), - Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput), - Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab), - Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements), - Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), - Binding::new("R", ModifiersState::ALT, Action::RequestResize), - Binding::new("R", ModifiersState::SHIFT, Action::ToggleContinuousRedraw), + Binding::new("F", Modifiers::ALT, Action::ToggleSimpleFullscreen), + Binding::new("D", Modifiers::CONTROL, Action::ToggleDecorations), + Binding::new("I", Modifiers::CONTROL, Action::ToggleImeInput), + Binding::new("L", Modifiers::CONTROL, Action::CycleCursorGrab), + Binding::new("P", Modifiers::CONTROL, Action::ToggleResizeIncrements), + Binding::new("R", Modifiers::CONTROL, Action::ToggleResizable), + Binding::new("R", Modifiers::ALT, Action::RequestResize), + Binding::new("R", Modifiers::SHIFT, Action::ToggleContinuousRedraw), // M. - Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors), - Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize), - Binding::new("M", ModifiersState::ALT, Action::Minimize), + Binding::new("M", Modifiers::CONTROL.union(Modifiers::ALT), Action::DumpMonitors), + Binding::new("M", Modifiers::CONTROL, Action::ToggleMaximize), + Binding::new("M", Modifiers::ALT, Action::Minimize), // N. - Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow), + Binding::new("N", Modifiers::CONTROL, Action::CreateNewWindow), // C. - Binding::new("C", ModifiersState::CONTROL, Action::NextCursor), - Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor), + Binding::new("C", Modifiers::CONTROL, Action::NextCursor), + Binding::new("C", Modifiers::ALT, Action::NextCustomCursor), #[cfg(web_platform)] - Binding::new( - "C", - ModifiersState::CONTROL.union(ModifiersState::SHIFT), - Action::UrlCustomCursor, - ), + Binding::new("C", Modifiers::CONTROL.union(Modifiers::SHIFT), Action::UrlCustomCursor), #[cfg(web_platform)] - Binding::new( - "C", - ModifiersState::ALT.union(ModifiersState::SHIFT), - Action::AnimationCustomCursor, - ), - Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility), + Binding::new("C", Modifiers::ALT.union(Modifiers::SHIFT), Action::AnimationCustomCursor), + Binding::new("Z", Modifiers::CONTROL, Action::ToggleCursorVisibility), // K. - Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)), - Binding::new("K", ModifiersState::META, Action::SetTheme(Some(Theme::Light))), - Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))), + Binding::new("K", Modifiers::empty(), Action::SetTheme(None)), + Binding::new("K", Modifiers::META, Action::SetTheme(Some(Theme::Light))), + Binding::new("K", Modifiers::CONTROL, Action::SetTheme(Some(Theme::Dark))), #[cfg(macos_platform)] - Binding::new("T", ModifiersState::META, Action::CreateNewTab), + Binding::new("T", Modifiers::META, Action::CreateNewTab), #[cfg(macos_platform)] - Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt), - Binding::new("S", ModifiersState::CONTROL, Action::Message), + Binding::new("O", Modifiers::CONTROL, Action::CycleOptionAsAlt), + Binding::new("S", Modifiers::CONTROL, Action::Message), ]; const MOUSE_BINDINGS: &[Binding] = &[ - Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow), - Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow), - Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu), + Binding::new(MouseButton::Left, Modifiers::ALT, Action::DragResizeWindow), + Binding::new(MouseButton::Left, Modifiers::CONTROL, Action::DragWindow), + Binding::new(MouseButton::Right, Modifiers::CONTROL, Action::ShowWindowMenu), ]; diff --git a/examples/kbd_ev_print.rs b/examples/kbd_ev_print.rs new file mode 100644 index 0000000000..ce20283b61 --- /dev/null +++ b/examples/kbd_ev_print.rs @@ -0,0 +1,170 @@ +//! Simple winit window example that prints keyboard events: +//! [KeyboardInput](https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.KeyboardInput) +//! [ModifiersChanged](https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.ModifiersChanged).) + +use std::error::Error; + +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +#[cfg(web_platform)] +use winit::platform::web::WindowAttributesWeb; +use winit::window::{Window, WindowAttributes, WindowId}; + +#[path = "util/fill.rs"] +mod fill; +#[path = "util/tracing.rs"] +mod tracing; + +#[derive(Default, Debug)] +struct App { + window: Option>, +} + +// https://docs.rs/winit/latest/winit/event/struct.Modifiers.html +// pub struct KeyEvent +// physical_key: PhysicalKey, enum PhysicalKey +// Code ( KeyCode) +// ๏ฟฝUnidentified(NativeKeyCode) +// logical_key: Key, enum Key +// Named(NamedKey) +// Character(Str) +// ๏ฟฝUnidentified(NativeKey) +// ๐Ÿ•ฑDead(Option) +// text : Option +// location: KeyLocation, enum KeyLocation Standard,Left,Right,Numpad +// state : ElementState, pressed/released +//๐Ÿ”repeat : bool +use winit::event::{ElementState, KeyEvent}; +use winit::keyboard::{Key, KeyLocation, PhysicalKey}; +pub fn ev_key_s(key: &KeyEvent) -> String { + let mut s = String::new(); + match &key.state { + ElementState::Pressed => s.push('โ†“'), + ElementState::Released => s.push('โ†‘'), + } + if key.repeat { + s.push('๐Ÿ”') + } else { + s.push('โ€ƒ') + }; //๐œฑฃโš› + s.push(' '); + match &key.physical_key { + PhysicalKey::Code(key_code) => s.push_str(&format!("{:?}", key_code)), + PhysicalKey::Unidentified(key_code_native) => { + s.push_str(&format!("๏ฟฝ{:?}", key_code_native)) + }, + }; + s.push(' '); + match &key.logical_key { + Key::Named(key_named) => s.push_str(&format!("{:?}", key_named)), + Key::Character(key_char) => s.push_str(&format!("{}", key_char)), + Key::Unidentified(key_native) => s.push_str(&format!("๏ฟฝ{:?}", key_native)), + Key::Dead(maybe_char) => s.push_str(&format!("๐Ÿ•ฑ{:?}", maybe_char)), + }; + s.push_str(" "); + if let Some(txt) = &key.text { + s.push_str(&format!("{}", txt)); + } else { + s.push(' '); + } + s.push(' '); + if let Some(txt) = &key.text_with_all_modifiers { + s.push_str(&format!("{}", txt)); + } else { + s.push(' '); + } + s.push(' '); + match &key.key_without_modifiers { + Key::Named(key_named) => s.push_str(&format!("{:?}", key_named)), + Key::Character(key_char) => s.push_str(&format!("{}", key_char)), + Key::Unidentified(key_native) => s.push_str(&format!("๏ฟฝ{:?}", key_native)), + Key::Dead(maybe_char) => s.push_str(&format!("๐Ÿ•ฑ{:?}", maybe_char)), + }; + s.push_str(" "); + match &key.location { + KeyLocation::Standard => s.push('โ‰'), + KeyLocation::Left => s.push('โ†'), + KeyLocation::Right => s.push('โ†’'), + KeyLocation::Numpad => s.push('๐Ÿ”ข'), + } + s +} + +impl ApplicationHandler for App { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + #[cfg(not(web_platform))] + let window_attributes = WindowAttributes::default(); + #[cfg(web_platform)] + let window_attributes = WindowAttributes::default() + .with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true))); + self.window = match event_loop.create_window(window_attributes) { + Ok(window) => Some(window), + Err(err) => { + eprintln!("error creating window: {err}"); + event_loop.exit(); + return; + }, + } + } + + fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { + match event { + WindowEvent::ModifiersChanged(mods) => { + println!("ฮ” {mods:#}\tmodifier state"); + }, + WindowEvent::KeyboardInput { event, is_synthetic, .. } => { + let is_synthetic_s = if is_synthetic { "โš—" } else { " " }; + let key_event_s = ev_key_s(&event); + println!("๐Ÿ–ฎ {}{}", is_synthetic_s, key_event_s); + }, + WindowEvent::CloseRequested => { + event_loop.exit(); + }, + WindowEvent::SurfaceResized(_) => { + self.window.as_ref().expect("resize event without a window").request_redraw(); + }, + WindowEvent::RedrawRequested => { + // Redraw the application. + // + // It's preferable for applications that do not render continuously to render in + // this event rather than in AboutToWait, since rendering in here allows + // the program to gracefully handle redraws requested by the OS. + + let window = self.window.as_ref().expect("redraw request without a window"); + + // Notify that you're about to draw. + window.pre_present_notify(); + + // Draw. + fill::fill_window(window.as_ref()); + + // For contiguous redraw loop you can request a redraw from here. + // window.request_redraw(); + }, + _ => (), + } + } +} + +fn main() -> Result<(), Box> { + #[cfg(web_platform)] + console_error_panic_hook::set_once(); + + tracing::init(); + + let event_loop = EventLoop::new()?; + + println!( + "ฮ” is ModifiersChanged event, showing (line #1) side-agnostic modifier state as well as \ + (#2) side-aware one.\n โ‡ง Shift โŽˆ Control โ—† Meta โއ Alt โއGr AltGraph โ‡ช CapsLock โ‡ญ \ + NumLock โ‡ณ๐Ÿ”’ ScrollLock\n ฦ’ Fn ฦ’๐Ÿ”’ FnLock ใ‚ซใƒŠ๐Ÿ”’ KanaLock โ€น๐Ÿ‘ Loya ๐Ÿ‘โ€บ Roya ๐Ÿ”ฃ \ + Symbol ๐Ÿ”ฃ๐Ÿ”’ SymbolLock\n๐Ÿ–ฎ is KeyboardInput: โš— synthetic, โ†“โ†‘ pressed/unknown, ๐Ÿ” \ + repeat\n phys logic txt +mod โˆ’mod location" + ); + + // For alternative loop run options see `pump_events` and `run_on_demand` examples. + event_loop.run_app(App::default())?; + + Ok(()) +} diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 67ea934912..f1111c31bc 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -81,6 +81,10 @@ changelog entry. - `keyboard::ModifiersKey` to track which modifier is exactly pressed. - `ActivationToken::as_raw` to get a ref to raw token. - Each platform now has corresponding `WindowAttributes` struct instead of trait extension. +- Added support for using AltGr, CapsLock,NumLock, ScrollLock, Fn, FnLock, KanaLock, Loya, Roya, Symbol, SymbolLock as separate modifiers. +- On Windows, update side-aware `event::Modifiers` information on state change. +- On Windows, added AltGr as a separate modifier (though currently AltGr+LCtrl can't be differentiated from just AltGr). +- On Windows, added CapsLock,NumLock, ScrollLock, KanaLock, Loya, Roya as separate modifiers. ### Changed @@ -201,6 +205,8 @@ changelog entry. - Move `IconExtWindows` into `WinIcon`. - Move `EventLoopExtPumpEvents` and `PumpStatus` from platform module to `winit::event_loop::pump_events`. - Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`. +- Replaced `winit::keyboard::ModifiersState` with the new `winit_core::keyboard::Modifiers`. +- Implement `Display` for `winit_core::keyboard::Modifiers` to print โ€นโ‡งโއ modifier combos, including an `:#` alternate notation to preserve modifier symbol positioning (for vertically-aligned formatting). ### Removed @@ -240,6 +246,7 @@ changelog entry. - Remove `NamedKey::Space`, match on `Key::Character(" ")` instead. - Remove `PartialEq` impl for `WindowAttributes`. - `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead. +- Remove `winit::keyboard::ModifiersKeyState`, replaced with the new `winit_core::keyboard::Modifiers` containing the same information. ### Fixed diff --git a/src/platform_impl/apple/appkit/event.rs b/src/platform_impl/apple/appkit/event.rs index 3713397ba4..276fb48515 100644 --- a/src/platform_impl/apple/appkit/event.rs +++ b/src/platform_impl/apple/appkit/event.rs @@ -6,10 +6,9 @@ use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType}; use objc2_core_foundation::{CFData, CFRetained}; use objc2_foundation::NSPoint; use smol_str::SmolStr; -use winit_core::event::{ElementState, KeyEvent, Modifiers}; +use winit_core::event::{ElementState, KeyEvent}; use winit_core::keyboard::{ - Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, - PhysicalKey, + Key, KeyCode, KeyLocation, Modifiers, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }; use super::ffi; @@ -311,26 +310,25 @@ pub(super) fn ralt_pressed(event: &NSEvent) -> bool { pub(super) fn event_mods(event: &NSEvent) -> Modifiers { let flags = unsafe { event.modifierFlags() }; - let mut state = ModifiersState::empty(); - let mut pressed_mods = ModifiersKeys::empty(); + let mut mods = Modifiers::empty(); - state.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::Shift)); - pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK)); - pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); + mods.set(Modifiers::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK)); + mods.set(Modifiers::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); + mods.set(Modifiers::SHIFT, flags.contains(NSEventModifierFlags::Shift)); - state.set(ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::Control)); - pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK)); - pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); + mods.set(Modifiers::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK)); + mods.set(Modifiers::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); + mods.set(Modifiers::CONTROL, flags.contains(NSEventModifierFlags::Control)); - state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option)); - pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK)); - pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK)); + mods.set(Modifiers::LALT, flags.contains(NX_DEVICELALTKEYMASK)); + mods.set(Modifiers::RALT, flags.contains(NX_DEVICERALTKEYMASK)); + mods.set(Modifiers::ALT, flags.contains(NSEventModifierFlags::Option)); - state.set(ModifiersState::META, flags.contains(NSEventModifierFlags::Command)); - pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK)); - pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK)); + mods.set(Modifiers::LMETA, flags.contains(NX_DEVICELCMDKEYMASK)); + mods.set(Modifiers::RMETA, flags.contains(NX_DEVICERCMDKEYMASK)); + mods.set(Modifiers::META, flags.contains(NSEventModifierFlags::Command)); - Modifiers::new(state, pressed_mods) + mods } pub(super) fn dummy_event() -> Option> { diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index 8103770642..b0e06c59b7 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -17,10 +17,10 @@ use objc2_foundation::{ NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; use winit_core::event::{ - DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, - PointerKind, PointerSource, TouchPhase, WindowEvent, + DeviceEvent, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, PointerKind, + PointerSource, TouchPhase, WindowEvent, }; -use winit_core::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey}; +use winit_core::keyboard::{Key, KeyCode, KeyLocation, Modifiers, NamedKey}; use super::app_state::AppState; use super::cursor::{default_cursor, invisible_cursor}; @@ -77,12 +77,34 @@ impl ModLocationMask { } } -fn key_to_modifier(key: &Key) -> Option { +fn key_to_modifier(key: &Key) -> Option { match key { - Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT), - Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL), - Key::Named(NamedKey::Meta) => Some(ModifiersState::META), - Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT), + // side-agnostic mods are stored on the left side + // side-agnostic storage is a convenience dupe + Key::Named(NamedKey::Alt) => { + let mut mods = Modifiers::empty(); + mods.set(Modifiers::LALT, true); + mods.set(Modifiers::ALT, true); + Some(mods) + }, + Key::Named(NamedKey::Control) => { + let mut mods = Modifiers::empty(); + mods.set(Modifiers::LCONTROL, true); + mods.set(Modifiers::CONTROL, true); + Some(mods) + }, + Key::Named(NamedKey::Meta) => { + let mut mods = Modifiers::empty(); + mods.set(Modifiers::LMETA, true); + mods.set(Modifiers::META, true); + Some(mods) + }, + Key::Named(NamedKey::Shift) => { + let mut mods = Modifiers::empty(); + mods.set(Modifiers::LSHIFT, true); + mods.set(Modifiers::SHIFT, true); + Some(mods) + }, _ => None, } } @@ -886,8 +908,8 @@ impl WinitView { /// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary. pub(super) fn reset_modifiers(&self) { - if !self.ivars().modifiers.get().state().is_empty() { - self.ivars().modifiers.set(Modifiers::default()); + if !self.ivars().modifiers.get().is_empty() { + self.ivars().modifiers.set(Modifiers::empty()); self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get())); } } @@ -950,7 +972,7 @@ impl WinitView { let phys_mod = phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty()); - let is_active = current_modifiers.state().contains(event_modifier); + let is_active = current_modifiers.contains(event_modifier); let mut events = VecDeque::with_capacity(2); // There is no API for getting whether the button was pressed or released @@ -1096,14 +1118,14 @@ fn mouse_button(event: &NSEvent) -> MouseButton { // 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) -> Retained { - let ev_mods = event_mods(event).state(); + let ev_mods = event_mods(event); let ignore_alt_characters = match option_as_alt { OptionAsAlt::OnlyLeft if lalt_pressed(event) => true, OptionAsAlt::OnlyRight if ralt_pressed(event) => true, - OptionAsAlt::Both if ev_mods.alt_key() => true, + OptionAsAlt::Both if ev_mods.alt_state() => true, _ => false, - } && !ev_mods.control_key() - && !ev_mods.meta_key(); + } && !ev_mods.control_state() + && !ev_mods.meta_state(); if ignore_alt_characters { let ns_chars = unsafe { diff --git a/src/platform_impl/linux/common/xkb/state.rs b/src/platform_impl/linux/common/xkb/state.rs index 4228ed6449..ce06e46d76 100644 --- a/src/platform_impl/linux/common/xkb/state.rs +++ b/src/platform_impl/linux/common/xkb/state.rs @@ -178,13 +178,19 @@ pub struct ModifiersState { pub num_lock: bool, } -impl From for winit_core::keyboard::ModifiersState { - fn from(mods: ModifiersState) -> winit_core::keyboard::ModifiersState { - let mut to_mods = winit_core::keyboard::ModifiersState::empty(); - to_mods.set(winit_core::keyboard::ModifiersState::SHIFT, mods.shift); - to_mods.set(winit_core::keyboard::ModifiersState::CONTROL, mods.ctrl); - to_mods.set(winit_core::keyboard::ModifiersState::ALT, mods.alt); - to_mods.set(winit_core::keyboard::ModifiersState::META, mods.logo); +impl From for winit_core::keyboard::Modifiers { + fn from(mods: ModifiersState) -> winit_core::keyboard::Modifiers { + let mut to_mods = winit_core::keyboard::Modifiers::empty(); + // side-agnostic mods are stored on the left side + to_mods.set(winit_core::keyboard::Modifiers::LSHIFT, mods.shift); + to_mods.set(winit_core::keyboard::Modifiers::LCONTROL, mods.ctrl); + to_mods.set(winit_core::keyboard::Modifiers::LALT, mods.alt); + to_mods.set(winit_core::keyboard::Modifiers::LMETA, mods.logo); + // side-agnostic storage is a convenience dupe + to_mods.set(winit_core::keyboard::Modifiers::SHIFT, mods.shift); + to_mods.set(winit_core::keyboard::Modifiers::CONTROL, mods.ctrl); + to_mods.set(winit_core::keyboard::Modifiers::ALT, mods.alt); + to_mods.set(winit_core::keyboard::Modifiers::META, mods.logo); to_mods } } diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index eb259e5b77..66c91705a9 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -12,7 +12,7 @@ use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum}; use tracing::warn; use winit_core::event::{ElementState, WindowEvent}; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use crate::platform_impl::common::xkb::Context; use crate::platform_impl::wayland::event_loop::sink::EventSink; @@ -89,7 +89,7 @@ impl Dispatch for WinitState { // HACK: this is just for GNOME not fixing their ordering issue of modifiers. if std::mem::take(&mut seat_state.modifiers_pending) { state.events_sink.push_window_event( - WindowEvent::ModifiersChanged(seat_state.modifiers.into()), + WindowEvent::ModifiersChanged(seat_state.modifiers), window_id, ); } @@ -122,7 +122,7 @@ impl Dispatch for WinitState { if !focused { // Notify that no modifiers are being pressed. state.events_sink.push_window_event( - WindowEvent::ModifiersChanged(ModifiersState::empty().into()), + WindowEvent::ModifiersChanged(Modifiers::empty()), window_id, ); @@ -245,7 +245,7 @@ impl Dispatch for WinitState { }; state.events_sink.push_window_event( - WindowEvent::ModifiersChanged(seat_state.modifiers.into()), + WindowEvent::ModifiersChanged(seat_state.modifiers), window_id, ); }, diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 4cf6a3b503..7892cab165 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -13,7 +13,7 @@ use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use tracing::warn; use winit_core::event::WindowEvent; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use crate::platform_impl::wayland::state::WinitState; @@ -53,7 +53,7 @@ pub struct WinitSeatState { keyboard_state: Option, /// The current modifiers state on the seat. - modifiers: ModifiersState, + modifiers: Modifiers, /// Whether we have pending modifiers. modifiers_pending: bool, diff --git a/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs b/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs index 605baa6ddc..56f0a35a26 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs @@ -12,8 +12,8 @@ use sctk::reexports::protocols::wp::relative_pointer::zv1::{ use sctk::globals::GlobalData; -use winit_core::event::DeviceEvent; use crate::platform_impl::wayland::state::WinitState; +use winit_core::event::DeviceEvent; /// Wrapper around the relative pointer. #[derive(Debug)] diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 43fa60513c..212118f798 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -11,7 +11,7 @@ use winit_core::event::{ MouseScrollDelta, PointerKind, PointerSource, RawKeyEvent, SurfaceSizeWriter, TouchPhase, WindowEvent, }; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use x11_dl::xinput2::{ self, XIDeviceEvent, XIEnterEvent, XIFocusInEvent, XIFocusOutEvent, XIHierarchyEvent, XILeaveEvent, XIModifierState, XIRawEvent, @@ -67,7 +67,7 @@ pub struct EventProcessor { // Currently focused window belonging to this process pub active_window: Option, /// Latest modifiers we've sent for the user to trigger change in event. - pub modifiers: Cell, + pub modifiers: Cell, // Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking // since they are not unique and could be duplicated in case of sequence of key events is // delivered at near the same time. @@ -954,9 +954,9 @@ impl EventProcessor { let mask = self.xkb_mod_mask_from_core(state); xkb_state.update_modifiers(mask, 0, 0, 0, 0, Self::core_keyboard_group(state)); - let mods: ModifiersState = xkb_state.modifiers().into(); + let mods: Modifiers = xkb_state.modifiers().into(); - let event = WindowEvent::ModifiersChanged(mods.into()); + let event = WindowEvent::ModifiersChanged(mods); app.window_event(&self.target, window_id, event); } @@ -1662,14 +1662,14 @@ impl EventProcessor { fn send_modifiers( &self, window_id: winit_core::window::WindowId, - modifiers: ModifiersState, + modifiers: Modifiers, force: bool, app: &mut dyn ApplicationHandler, ) { // NOTE: Always update the modifiers to account for case when they've changed // and forced was `true`. if self.modifiers.replace(modifiers) != modifiers || force { - let event = WindowEvent::ModifiersChanged(self.modifiers.get().into()); + let event = WindowEvent::ModifiersChanged(self.modifiers.get()); app.window_event(&self.target, window_id, event); } } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 619c264467..d323bfbb6a 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -13,7 +13,7 @@ use winit_core::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopProxy as RootEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use winit_core::monitor::MonitorHandle as CoremMonitorHandle; use winit_core::window::{Theme, WindowId}; @@ -27,14 +27,14 @@ use crate::platform_impl::web::event_loop::proxy::EventLoopProxy; use crate::platform_impl::Window; #[derive(Default, Debug)] -struct ModifiersShared(Rc>); +struct ModifiersShared(Rc>); impl ModifiersShared { - fn set(&self, new: ModifiersState) { + fn set(&self, new: Modifiers) { self.0.set(new) } - fn get(&self) -> ModifiersState { + fn get(&self) -> Modifiers { self.0.get() } } @@ -81,10 +81,10 @@ impl ActiveEventLoop { has_focus.set(false); let clear_modifiers = (!modifiers.get().is_empty()).then(|| { - modifiers.set(ModifiersState::empty()); + modifiers.set(Modifiers::empty()); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()), + event: WindowEvent::ModifiersChanged(Modifiers::empty()), } }); @@ -130,7 +130,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -165,7 +165,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -203,7 +203,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -229,7 +229,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -255,9 +255,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged( - active_modifiers.into(), - ), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -285,7 +283,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -312,7 +310,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -340,7 +338,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); @@ -365,7 +363,7 @@ impl ActiveEventLoop { modifiers.set(active_modifiers); Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(active_modifiers.into()), + event: WindowEvent::ModifiersChanged(active_modifiers), } }); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index b2933d6b39..3dea54d909 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -16,7 +16,7 @@ use winit_core::event::{ ButtonSource, DeviceId, ElementState, MouseScrollDelta, PointerKind, PointerSource, SurfaceSizeWriter, WindowEvent, }; -use winit_core::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey}; +use winit_core::keyboard::{Key, KeyLocation, Modifiers, PhysicalKey}; use winit_core::monitor::Fullscreen; use winit_core::window::{WindowAttributes, WindowId}; @@ -287,7 +287,7 @@ impl Canvas { pub fn on_keyboard_release(&self, mut handler: F) where - F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), + F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, Modifiers), { let prevent_default = Rc::clone(&self.prevent_default); self.handlers.borrow_mut().on_keyboard_release = @@ -310,7 +310,7 @@ impl Canvas { pub fn on_keyboard_press(&self, mut handler: F) where - F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), + F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, Modifiers), { let prevent_default = Rc::clone(&self.prevent_default); self.handlers.borrow_mut().on_keyboard_press = @@ -333,32 +333,28 @@ impl Canvas { pub fn on_pointer_leave(&self, handler: F) where - F: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, PointerKind), + F: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, PointerKind), { self.handlers.borrow_mut().pointer_handler.on_pointer_leave(&self.common, handler) } pub fn on_pointer_enter(&self, handler: F) where - F: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, PointerKind), + F: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, PointerKind), { self.handlers.borrow_mut().pointer_handler.on_pointer_enter(&self.common, handler) } pub fn on_pointer_release(&self, handler: C) where - C: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, ButtonSource), + C: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, ButtonSource), { self.handlers.borrow_mut().pointer_handler.on_pointer_release(&self.common, handler) } pub fn on_pointer_press(&self, handler: C) where - C: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, ButtonSource), + C: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, ButtonSource), { self.handlers.borrow_mut().pointer_handler.on_pointer_press( &self.common, @@ -372,13 +368,11 @@ impl Canvas { C: 'static + FnMut( Option, - &mut dyn Iterator< - Item = (ModifiersState, bool, PhysicalPosition, PointerSource), - >, + &mut dyn Iterator, PointerSource)>, ), B: 'static + FnMut( - ModifiersState, + Modifiers, Option, bool, PhysicalPosition, @@ -396,7 +390,7 @@ impl Canvas { pub fn on_mouse_wheel(&self, mut handler: F) where - F: 'static + FnMut(MouseScrollDelta, ModifiersState), + F: 'static + FnMut(MouseScrollDelta, Modifiers), { let window = self.common.window.clone(); let prevent_default = Rc::clone(&self.prevent_default); diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 448c259d6e..34e6b82723 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -6,7 +6,7 @@ use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{KeyboardEvent, MouseEvent, Navigator, PointerEvent, WheelEvent}; use winit_core::event::{FingerId, MouseButton, MouseScrollDelta, PointerKind}; -use winit_core::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; +use winit_core::keyboard::{Key, KeyLocation, Modifiers, NamedKey, PhysicalKey}; use super::Engine; use crate::platform_impl::web::keyboard::FromAttributeValue; @@ -201,39 +201,51 @@ pub fn key_location(event: &KeyboardEvent) -> KeyLocation { } } -pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { - let mut state = ModifiersState::empty(); +pub fn keyboard_modifiers(event: &KeyboardEvent) -> Modifiers { + let mut state = Modifiers::empty(); + // side-agnostic mods are stored on the left side + // side-agnostic storage is a convenience dupe if event.shift_key() { - state |= ModifiersState::SHIFT; + state |= Modifiers::LSHIFT; + state |= Modifiers::SHIFT; } if event.ctrl_key() { - state |= ModifiersState::CONTROL; + state |= Modifiers::LCONTROL; + state |= Modifiers::CONTROL; } if event.alt_key() { - state |= ModifiersState::ALT; + state |= Modifiers::LALT; + state |= Modifiers::ALT; } if event.meta_key() { - state |= ModifiersState::META; + state |= Modifiers::LMETA; + state |= Modifiers::META; } state } -pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { - let mut state = ModifiersState::empty(); +pub fn mouse_modifiers(event: &MouseEvent) -> Modifiers { + let mut state = Modifiers::empty(); + // side-agnostic mods are stored on the left side + // side-agnostic storage is a convenience dupe if event.shift_key() { - state |= ModifiersState::SHIFT; + state |= Modifiers::LSHIFT; + state |= Modifiers::SHIFT; } if event.ctrl_key() { - state |= ModifiersState::CONTROL; + state |= Modifiers::LCONTROL; + state |= Modifiers::CONTROL; } if event.alt_key() { - state |= ModifiersState::ALT; + state |= Modifiers::LALT; + state |= Modifiers::ALT; } if event.meta_key() { - state |= ModifiersState::META; + state |= Modifiers::LMETA; + state |= Modifiers::META; } state diff --git a/src/platform_impl/web/web_sys/pointer.rs b/src/platform_impl/web/web_sys/pointer.rs index 6d5fa9f844..2de0392b5f 100644 --- a/src/platform_impl/web/web_sys/pointer.rs +++ b/src/platform_impl/web/web_sys/pointer.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use dpi::PhysicalPosition; use web_sys::PointerEvent; use winit_core::event::{ButtonSource, DeviceId, ElementState, Force, PointerKind, PointerSource}; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use super::canvas::Common; use super::event; @@ -36,8 +36,7 @@ impl PointerHandler { pub fn on_pointer_leave(&mut self, canvas_common: &Common, mut handler: F) where - F: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, PointerKind), + F: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, PointerKind), { let window = canvas_common.window.clone(); self.on_cursor_leave = @@ -54,8 +53,7 @@ impl PointerHandler { pub fn on_pointer_enter(&mut self, canvas_common: &Common, mut handler: F) where - F: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, PointerKind), + F: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, PointerKind), { let window = canvas_common.window.clone(); self.on_cursor_enter = @@ -72,8 +70,7 @@ impl PointerHandler { pub fn on_pointer_release(&mut self, canvas_common: &Common, mut handler: C) where - C: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, ButtonSource), + C: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, ButtonSource), { let window = canvas_common.window.clone(); self.on_pointer_release = @@ -109,8 +106,7 @@ impl PointerHandler { mut handler: C, prevent_default: Rc>, ) where - C: 'static - + FnMut(ModifiersState, Option, bool, PhysicalPosition, ButtonSource), + C: 'static + FnMut(Modifiers, Option, bool, PhysicalPosition, ButtonSource), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw().clone(); @@ -166,13 +162,11 @@ impl PointerHandler { C: 'static + FnMut( Option, - &mut dyn Iterator< - Item = (ModifiersState, bool, PhysicalPosition, PointerSource), - >, + &mut dyn Iterator, PointerSource)>, ), B: 'static + FnMut( - ModifiersState, + Modifiers, Option, bool, PhysicalPosition, diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 6d0648174c..50e6028918 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -72,7 +72,7 @@ use winit_core::event_loop::{ EventLoopProxy as RootEventLoopProxy, EventLoopProxyProvider, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use winit_core::window::{Theme, Window as CoreWindow, WindowAttributes, WindowId}; @@ -934,17 +934,17 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { let modifiers = { let mut layouts = LAYOUT_CACHE.lock().unwrap(); - layouts.get_agnostic_mods() + layouts.get_mods() }; let mut window_state = userdata.window_state.lock().unwrap(); - if window_state.modifiers_state != modifiers { - window_state.modifiers_state = modifiers; + if window_state.modifiers != modifiers { + window_state.modifiers = modifiers; // Drop lock drop(window_state); - userdata.send_window_event(window, ModifiersChanged(modifiers.into())); + userdata.send_window_event(window, ModifiersChanged(modifiers)); } } @@ -959,8 +959,8 @@ unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { use winit_core::event::WindowEvent::{Focused, ModifiersChanged}; - userdata.window_state_lock().modifiers_state = ModifiersState::empty(); - userdata.send_window_event(window, ModifiersChanged(ModifiersState::empty().into())); + userdata.window_state_lock().modifiers = Modifiers::empty(); + userdata.send_window_event(window, ModifiersChanged(Modifiers::empty())); userdata.send_window_event(window, Focused(false)); } @@ -1040,6 +1040,9 @@ unsafe fn public_window_callback_inner( let mut result = ProcResult::DefWindowProc(wparam); // Send new modifiers before sending key events. + // NOTE: Some modifier key presses are not reported as KeyDown/Up events when the same + // alternative side modifier is being held, e.g., in a sequence of โ†“LShift โ†“RShift โ†‘RShift the + // last event is not reported. let mods_changed_callback = || match msg { WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => { update_modifiers(window, userdata); diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs index a31362f386..92635b613f 100644 --- a/src/platform_impl/windows/keyboard_layout.rs +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -40,7 +40,7 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM, }; -use winit_core::keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey}; +use winit_core::keyboard::{Key, KeyCode, Modifiers, NamedKey, NativeKey, PhysicalKey}; use crate::platform_impl::{loword, primarylangid, scancode_to_physicalkey}; @@ -50,6 +50,11 @@ pub(crate) static LAYOUT_CACHE: LazyLock> = fn key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { (GetKeyState(vkey as i32) & (1 << 15)) == (1 << 15) } } +fn key_toggled(vkey: VIRTUAL_KEY) -> bool { + // learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeystate + // If the low-order bit is 1, the key is toggled. GetKeyState is SHORT = 16bit + unsafe { (GetKeyState(vkey as i32) & (1 << 0)) == (1 << 0) } +} const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ VK_NUMPAD0, @@ -191,8 +196,9 @@ pub(crate) struct Layout { /// off. The keys of this map are identical to the keys of `numlock_on_keys`. pub numlock_off_keys: HashMap, + // TODO: update since Modifiers can now express caps lock /// Maps a modifier state to group of key strings - /// We're not using `ModifiersState` here because that object cannot express caps lock, + /// We're not using `Modifiers` here because that object cannot express caps lock, /// but we need to handle caps lock too. /// /// This map shouldn't need to exist. @@ -271,14 +277,43 @@ impl LayoutCache { } } - pub fn get_agnostic_mods(&mut self) -> ModifiersState { + pub fn get_mods(&mut self) -> Modifiers { let (_, layout) = self.get_current_layout(); - let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); - mods.set(ModifiersState::CONTROL, key_pressed(VK_CONTROL) && !filter_out_altgr); - mods.set(ModifiersState::ALT, key_pressed(VK_MENU) && !filter_out_altgr); - mods.set(ModifiersState::META, key_pressed(VK_LWIN) || key_pressed(VK_RWIN)); + let mut mods = Modifiers::empty(); + + mods.set(Modifiers::LSHIFT, key_pressed(VK_LSHIFT)); + mods.set(Modifiers::RSHIFT, key_pressed(VK_RSHIFT)); + mods.set(Modifiers::SHIFT, mods.shift_state()); + + mods.set(Modifiers::LALT, key_pressed(VK_LMENU)); + let is_ralt = key_pressed(VK_RMENU); + let is_altgr = layout.has_alt_graph && is_ralt; + mods.set(Modifiers::RALT, is_ralt && !is_altgr); + mods.set(Modifiers::RALT_GRAPH, is_altgr); + mods.set(Modifiers::ALT, mods.alt_state()); + mods.set(Modifiers::ALT_GRAPH, mods.alt_graph_state()); + + // On Windows AltGr = RAlt + LCtrl, and OS sends artificial LCtrl key event, which needs to + // be filtered out without touching "real" LCtrl events to allow separate bindings of + // LCtrl+AltGr+X and AltGr+X. TODO: this is likely only possible by tracking the + // physical LCtrl state via raw keyboard events as the message loop isn't capable of + // excluding artificial LCtrl events? + mods.set(Modifiers::RCONTROL, key_pressed(VK_RCONTROL)); + mods.set(Modifiers::LCONTROL, key_pressed(VK_LCONTROL) && !is_altgr); + mods.set(Modifiers::CONTROL, mods.control_state()); + + mods.set(Modifiers::LMETA, key_pressed(VK_LWIN)); + mods.set(Modifiers::RMETA, key_pressed(VK_RWIN)); + mods.set(Modifiers::META, mods.meta_state()); + + mods.set(Modifiers::CAPS_LOCK, key_toggled(VK_CAPITAL)); + mods.set(Modifiers::NUM_LOCK, key_toggled(VK_NUMLOCK)); + mods.set(Modifiers::SCROLL_LOCK, key_toggled(VK_SCROLL)); + + mods.set(Modifiers::KANA_LOCK, key_toggled(VK_KANA)); + mods.set(Modifiers::LOYA, key_pressed(VK_OEM_FJ_LOYA)); + mods.set(Modifiers::ROYA, key_pressed(VK_OEM_FJ_ROYA)); + mods } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index a0ca3a539e..4d028f669d 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -17,7 +17,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE, }; use winit_core::icon::Icon; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::Modifiers; use winit_core::monitor::Fullscreen; use winit_core::window::{Theme, WindowAttributes}; @@ -40,7 +40,7 @@ pub(crate) struct WindowState { pub saved_window: Option, pub scale_factor: f64, - pub modifiers_state: ModifiersState, + pub modifiers: Modifiers, pub fullscreen: Option, pub current_theme: Theme, pub preferred_theme: Option, @@ -171,7 +171,7 @@ impl WindowState { saved_window: None, scale_factor, - modifiers_state: ModifiersState::default(), + modifiers: Modifiers::default(), fullscreen: None, current_theme, preferred_theme, diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index 18e796cd31..414f911fc7 100644 --- a/tests/serde_objects.rs +++ b/tests/serde_objects.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use winit::cursor::CursorIcon; use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}; -use winit::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; +use winit::keyboard::{Key, KeyCode, KeyLocation, Modifiers, NamedKey, PhysicalKey}; #[allow(dead_code)] fn needs_serde>() {} @@ -25,7 +25,7 @@ fn events_serde() { needs_serde::(); needs_serde::(); needs_serde::(); - needs_serde::(); + needs_serde::(); } #[test] diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index 416723749c..aceedccc79 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -13,7 +13,7 @@ use web_time::Instant; use crate::error::RequestError; use crate::event_loop::AsyncRequestSerial; -use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}; +use crate::keyboard::{self, Modifiers}; #[cfg(doc)] use crate::window::Window; use crate::window::{ActivationToken, Theme}; @@ -802,84 +802,6 @@ pub struct KeyEvent { pub key_without_modifiers: keyboard::Key, } -/// Describes keyboard modifiers event. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Modifiers { - pub(crate) state: ModifiersState, - - // NOTE: Currently active modifiers keys (logically, but not necessarily physically, pressed). - // - // The field providing a metadata, it shouldn't be used as a source of truth. - pub(crate) pressed_mods: ModifiersKeys, -} - -impl Modifiers { - /// Create a new modifiers from state and pressed mods. - pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self { - Self { state, pressed_mods } - } - - /// The logical state of the modifiers. - pub fn state(&self) -> ModifiersState { - self.state - } - - /// The logical state of the left shift key. - pub fn lshift_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::LSHIFT) - } - - /// The logical state of the right shift key. - pub fn rshift_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::RSHIFT) - } - - /// The logical state of the left alt key. - pub fn lalt_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::LALT) - } - - /// The logical state of the right alt key. - pub fn ralt_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::RALT) - } - - /// The logical state of the left control key. - pub fn lcontrol_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::LCONTROL) - } - - /// The logical state of the right control key. - pub fn rcontrol_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::RCONTROL) - } - - /// The logical state of the left super key. - pub fn lsuper_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::LMETA) - } - - /// The logical state of the right super key. - pub fn rsuper_state(&self) -> ModifiersKeyState { - self.mod_state(ModifiersKeys::RMETA) - } - - fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState { - if self.pressed_mods.contains(modifier) { - ModifiersKeyState::Pressed - } else { - ModifiersKeyState::Unknown - } - } -} - -impl From for Modifiers { - fn from(value: ModifiersState) -> Self { - Self { state: value, pressed_mods: Default::default() } - } -} - /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// /// This is also called a "composition event". @@ -1121,6 +1043,7 @@ mod tests { use dpi::PhysicalPosition; use crate::event; + use crate::keyboard::Modifiers; macro_rules! foreach_event { ($closure:expr) => {{ @@ -1152,7 +1075,7 @@ mod tests { position: (0, 0).into(), source: PointerSource::Mouse, }); - with_window_event(ModifiersChanged(event::Modifiers::default())); + with_window_event(ModifiersChanged(Modifiers::default())); with_window_event(PointerEntered { device_id: None, primary: true, diff --git a/winit-core/src/keyboard.rs b/winit-core/src/keyboard.rs index 156a32d4ad..fac6892fdd 100644 --- a/winit-core/src/keyboard.rs +++ b/winit-core/src/keyboard.rs @@ -1687,113 +1687,523 @@ pub enum KeyLocation { } bitflags! { - /// Represents the current logical state of the keyboard modifiers + /// Represents the current _logical_ state of keyboard modifiers. /// /// Each flag represents a modifier and is set if this modifier is active. /// - /// Note that the modifier key can be physically released with the modifier - /// still being marked as active, as in the case of sticky modifiers. - /// See [`ModifiersKeyState`] for more details on what "sticky" means. + /// NOTE: the _physical_ state of a modifier key doesn't necessarily match its _logical_ state, for example, LShift can be physically released while `LSHIFT` is active, as in the case of sticky modifiers (see below on what "sticky" means). + /// Even the sides can mismatch, for example, RShift on macOS can be used to activate a sticky Shift state, but logically the OS will report that `LSHIFT` is active. Wayland/X11 can have similar issues. + /// + /// NOTE: while the modifier can only be in a binary active/inactive state, it might be helpful to + /// note the context re. how its state changes by physical key events. + /// + /// `โ†“` / `โ†‘` denote physical press/release[^1]: + /// + /// | Type | Activated | Deactivated | Comment | + /// | ----------------- | :-----------------: | :---------: | ------- | + /// | __Regular__ | `โ†“` | `โ†‘` | Active while being held | + /// | __Sticky__ | `โ†“` | `โ†“` unless lock is enabled
`โ†“`/`โ†‘`[^2] __non__-sticky key | Temporarily "stuck"; other `Sticky` keys have no effect | + /// | __Sticky Locked__ | `โ†“`
if `Sticky` | `โ†“` | Similar to `Toggle`, but deactivating `โ†“` turns on `Regular` effect | + /// | __Toggle__ | `โ†“` | `โ†“` | `โ†‘` from the activating `โ†“` has no effect | + /// + /// `Sticky` effect avoids the need to press and hold multiple modifiers for a single shortcut and + /// is usually a platform-wide option that affects modifiers _commonly_ used in shortcuts: + /// Shift, Control, Alt, Meta. + /// + /// `Toggle` type is typically a property of a modifier, for example, Caps Lock. + /// + /// These active states are __not__ differentiated here. + /// + /// [^1]: For virtual/on-screen keyboards physical press/release can be a mouse click or a finger tap or a voice command. + /// [^2]: platform-dependent + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - pub struct ModifiersState: u32 { - /// The "shift" key. - const SHIFT = 0b100; - /// The "control" key. - const CONTROL = 0b100 << 3; - /// The "alt" key. - const ALT = 0b100 << 6; - /// This is the "windows" key on PC and "command" key on Mac. - const META = 0b100 << 9; + pub struct Modifiers: u32 { + /// The "Right Shift" modifier. + const RSHIFT = 0b_1 << 0; + /// The "Left Shift" modifier. + const LSHIFT = 0b10; + /// The "Right Control" modifier. + const RCONTROL = 0b_1 << 2; + /// The "Left Control" modifier. + const LCONTROL = 0b10 << 2; + /// The "Right Alt" modifier. + const RALT = 0b_1 << 4; + /// The "Left Alt" modifier. + const LALT = 0b10 << 4; + /// The "Right Windows" modifier on PC and "Right Command" modifier on Mac. + const RMETA = 0b_1 << 6; + /// The "Left Windows" modifier on PC and "Left Command" modifier on Mac. + const LMETA = 0b10 << 6; + /// The "Right AltGraph" modifier, typically used to insert symbols. + const RALT_GRAPH = 0b_1 << 8; + /// The "Left AltGraph" modifier, typically used to insert symbols. + const LALT_GRAPH = 0b10 << 8; + /// The "Right Control" modifier on PC/Linux and "Right Command" modifier on Mac. + #[cfg(target_vendor = "apple")] + const RPRIMARY = Self::RMETA.bits(); + #[cfg(not(target_vendor = "apple"))] + const RPRIMARY = Self::RCONTROL.bits(); + /// The "Left Control" modifier on PC/Linux and "Left Command" modifier on Mac. + #[cfg(target_vendor = "apple")] + const LPRIMARY = Self::LMETA.bits(); + #[cfg(not(target_vendor = "apple"))] + const LPRIMARY = Self::LCONTROL.bits(); + + /// The "Caps Lock" modifier. + const CAPS_LOCK = 0b_1 << 10; + /// The "Num Lock" modifier. + const NUM_LOCK = 0b_1 << 11; + /// The "Scroll Lock" modifier. + const SCROLL_LOCK = 0b_1 << 12; + + /// The "Function" switch modifier. Often handled directly in the keyboard hardware and does not generate key events. + /// - **macOS**: Generates `ModifiersChanged` events on Apple hardware. + const FN = 0b_1 << 13; + /// The "Function-Lock" modifier. + const FN_LOCK = 0b_1 << 14; + /// The "Kana Mode" ("Kana Lock") modifier, typically used to enter hiragana mode (typically from romaji mode). + const KANA_LOCK = 0b_1 << 15; + /// The "Right OYAYUBI" modifier (OEM-specific). + const ROYA = 0b_1 << 16; + /// The "Left OYAYUBI" modifier (OEM-specific). + const LOYA = 0b10 << 16; + /// The "Symbol" modifier modifier used on some virtual keyboards. + const SYMBOL = 0b_1 << 18; + /// The "Symbol" Lock modifier. + const SYMBOL_LOCK = 0b_1 << 19; + #[deprecated = "use LMETA instead"] + const LSUPER = Self::LMETA.bits(); + #[deprecated = "use RMETA instead"] + const RSUPER = Self::RMETA.bits(); + + // TODO: worth storing composite state to make user binding API a bit easier? + /// Either Left or Right "Shift" modifier. + const SHIFT = 0b_1 << 20; + /// Either Left or Right "Control" modifier. + const CONTROL = 0b_1 << 21; + /// Either Left or Right "Alt" modifier. + const ALT = 0b_1 << 22; + /// Either Left or Right "Windows" modifier on PC and "Command" modifier on Mac. + const META = 0b_1 << 23; + /// Either Left or Right "AltGraph" modifier. + const ALT_GRAPH = 0b_1 << 24; + /// Either Left or Right "Control" modifier on PC/Linux and "Command" modifier on Mac. + #[cfg(target_vendor = "apple")] + const PRIMARY = Self::META.bits(); + #[cfg(not(target_vendor = "apple"))] + const PRIMARY = Self::CONTROL.bits(); #[deprecated = "use META instead"] const SUPER = Self::META.bits(); + + /// Union of all modifiers commonly used in shortcuts (side-agnostic): Shift, Control, Alt, Meta + /// Useful to match app shortuct's modifiers against, ignoring the state of CapsLock etc. + const SHORTCUT = Self::SHIFT.bits() | Self::CONTROL.bits() | Self::META.bits() | Self::ALT.bits(); + + /// Same as [`SHORTCUT`], but side-aware, combines both left and right mods. + const SHORTCUTLR = Self::LSHIFT.bits() | Self::LCONTROL.bits() | Self::LMETA.bits() | Self::LALT.bits() + | Self::RSHIFT.bits() | Self::RCONTROL.bits() | Self::RMETA.bits() | Self::RALT.bits(); } } -impl ModifiersState { - /// Returns whether the shift modifier is active. - pub fn shift_key(&self) -> bool { - self.intersects(Self::SHIFT) +impl Modifiers { + /// Returns `true` if either Left or Right Shift modifier is active. + pub fn shift_state(&self) -> bool { + self.contains(Self::LSHIFT) || self.contains(Self::RSHIFT) + } + /// Returns `true` if the Left Shift modifier is active. + pub fn lshift_state(&self) -> bool { + self.contains(Self::LSHIFT) + } + /// Returns `true` if the Right Shift modifier is active. + pub fn rshift_state(&self) -> bool { + self.contains(Self::RSHIFT) + } + + /// Returns `true` if either Left or Right Control modifier is active. + pub fn control_state(&self) -> bool { + self.contains(Self::LCONTROL) || self.contains(Self::RCONTROL) + } + /// Returns `true` if the Left Control modifier is active. + pub fn lcontrol_state(&self) -> bool { + self.contains(Self::LCONTROL) + } + /// Returns `true` if the Right Control modifier is active. + pub fn rcontrol_state(&self) -> bool { + self.contains(Self::RCONTROL) + } + + /// Returns `true` if either Left or Right Alt modifier is active. + pub fn alt_state(&self) -> bool { + self.contains(Self::LALT) || self.contains(Self::RALT) + } + /// Returns `true` if the Left Alt modifier is active. + pub fn lalt_state(&self) -> bool { + self.contains(Self::LALT) + } + /// Returns `true` if the Right Alt modifier is active. + pub fn ralt_state(&self) -> bool { + self.contains(Self::RALT) } - /// Returns whether the control modifier is active. - pub fn control_key(&self) -> bool { - self.intersects(Self::CONTROL) + /// Returns `true` if either Left or Right Meta modifier is active. + pub fn meta_state(&self) -> bool { + self.contains(Self::LMETA) || self.contains(Self::RMETA) + } + /// Returns `true` if the Left Meta modifier is active. + pub fn lmeta_state(&self) -> bool { + self.contains(Self::LMETA) + } + /// Returns `true` if the Right Meta modifier is active. + pub fn rmeta_state(&self) -> bool { + self.contains(Self::RMETA) } - /// Returns whether the alt modifier is active. - pub fn alt_key(&self) -> bool { - self.intersects(Self::ALT) + /// Returns `true` if either Left or Right AltGraph modifier is active. + pub fn alt_graph_state(&self) -> bool { + self.contains(Self::LALT_GRAPH) || self.contains(Self::RALT_GRAPH) + } + /// Returns `true` if the Left AltGraph modifier is active. + pub fn lalt_graph_state(&self) -> bool { + self.contains(Self::LALT_GRAPH) + } + /// Returns `true` if the Right AltGraph modifier is active. + pub fn ralt_graph_state(&self) -> bool { + self.contains(Self::RALT_GRAPH) } - /// Returns whether the meta modifier is active. - pub fn meta_key(&self) -> bool { - self.intersects(Self::META) + /// Returns `true` if the CapsLock modifier is active. + pub fn caps_lock_state(&self) -> bool { + self.contains(Self::CAPS_LOCK) } -} -/// The logical state of the particular modifiers key. -/// -/// NOTE: while the modifier can only be in a binary active/inactive state, it might be helpful to -/// note the context re. how its state changes by physical key events. -/// -/// `โ†“` / `โ†‘` denote physical press/release[^1]: -/// -/// | Type | Activated | Deactivated | Comment | -/// | ----------------- | :-----------------: | :---------: | ------- | -/// | __Regular__ | `โ†“` | `โ†‘` | Active while being held | -/// | __Sticky__ | `โ†“` | `โ†“` unless lock is enabled
`โ†“`/`โ†‘`[^2] __non__-sticky key | Temporarily "stuck"; other `Sticky` keys have no effect | -/// | __Sticky Locked__ | `โ†“`
if `Sticky` | `โ†“` | Similar to `Toggle`, but deactivating `โ†“` turns on `Regular` effect | -/// | __Toggle__ | `โ†“` | `โ†“` | `โ†‘` from the activating `โ†“` has no effect | -/// -/// `Sticky` effect avoids the need to press and hold multiple modifiers for a single shortcut and -/// is usually a platform-wide option that affects modifiers _commonly_ used in shortcuts: -/// Shift, Control, Alt, Meta. -/// -/// `Toggle` type is typically a property of a modifier, for example, Caps Lock. -/// -/// These active states are __not__ differentiated here. -/// -/// [^1]: For virtual/on-screen keyboards physical press/release can be a mouse click or a finger tap or a voice command. -/// [^2]: platform-dependent -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum ModifiersKeyState { - /// The particular modifier is active or logically, but not necessarily physically, pressed. - Pressed, - /// The state of the key is unknown. - /// - /// Can include cases when the key is active or logically pressed, for example, when a sticky - /// **Shift** is active, the OS might not preserve information that it was activated by - /// RightShift, so the state of [`ModifiersKeys::RSHIFT`] will be unknown while the state - /// of [`ModifiersState::SHIFT`] will be active. - #[default] - Unknown, + /// Returns `true` if the NumLock modifier is active. + pub fn num_lock_state(&self) -> bool { + self.contains(Self::NUM_LOCK) + } + + /// Returns `true` if the ScrollLock modifier is active. + pub fn scroll_lock_state(&self) -> bool { + self.contains(Self::SCROLL_LOCK) + } + + /// Returns `true` if the Fn modifier is active. + pub fn fn_state(&self) -> bool { + self.contains(Self::FN) + } + + /// Returns `true` if the FnLock modifier is active. + pub fn fn_lock_state(&self) -> bool { + self.contains(Self::FN_LOCK) + } + + /// Returns `true` if the KanaLock modifier is active. + pub fn kana_lock_state(&self) -> bool { + self.contains(Self::KANA_LOCK) + } + + /// Returns `true` if either Loya or Roya modifier is active (provided for completeness, there + /// is no "Oya" state similar to "Shift"). + pub fn oya_state(&self) -> bool { + self.contains(Self::LOYA) || self.contains(Self::ROYA) + } + /// Returns `true` if the Loya modifier is active. + pub fn loya_state(&self) -> bool { + self.contains(Self::LOYA) + } + /// Returns `true` if the Roya modifier is active. + pub fn roya_state(&self) -> bool { + self.contains(Self::ROYA) + } + + /// Returns `true` if the Symbol modifier is active. + pub fn symbol_state(&self) -> bool { + self.contains(Self::SYMBOL) + } + + /// Returns `true` if the SymbolLock modifier is active. + pub fn symbol_lock_state(&self) -> bool { + self.contains(Self::SYMBOL_LOCK) + } + + /// Returns `true` if either Left or Right Primary modifier is active. + pub fn primary_state(&self) -> bool { + self.contains(Self::LPRIMARY) || self.contains(Self::RPRIMARY) + } + /// Returns `true` if the Left Primary modifier is active. + pub fn lprimary_state(&self) -> bool { + self.contains(Self::LPRIMARY) + } + /// Returns `true` if the Right Primary modifier is active. + pub fn rprimary_state(&self) -> bool { + self.contains(Self::RPRIMARY) + } + + /// Leave bitflags only for modifiers commonly used in shortcuts (side-agnostic): + /// Shift, Control, Alt, Meta + pub fn shortcut_mask(&self) -> Self { + *self & Self::SHORTCUT + } + /// Leave bitflags only for modifiers commonly used in shortcuts (side-aware): left or right + /// Shift, Control, Alt, Meta + pub fn shortcutlr_mask(&self) -> Self { + *self & Self::SHORTCUTLR + } + /// Returns `true` if app-defined shortcut matches the current modifier state, but only + /// considering the latter's modifiers commonly used in shortcuts (side-agnostic): + /// Shift, Control, Alt, Meta + pub fn shortcut_match(&self, current_mods: &Self) -> bool { + *self == (*current_mods & Self::SHORTCUT) + } + /// Returns `true` if app-defined shortcut matches the current modifier state, but only + /// considering the latter's modifiers commonly used in shortcuts (side-aware): left or right + /// Shift, Control, Alt, Meta + pub fn shortcutlr_match(&self, current_mods: &Self) -> bool { + *self == (*current_mods & Self::SHORTCUTLR) + } } -// NOTE: the exact modifier key is not used to represent modifiers state in the -// first place due to a fact that modifiers state could be changed without any -// key being pressed and on some platforms like Wayland/X11 which key resulted -// in modifiers change is hidden, also, not that it really matters. -// -// The reason this API is even exposed is mostly to provide a way for users -// to treat modifiers differently based on their position, which is required -// on macOS due to their AltGr/Option situation. -bitflags! { - #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - pub struct ModifiersKeys: u8 { - const LSHIFT = 0b0000_0001; - const RSHIFT = 0b0000_0010; - const LCONTROL = 0b0000_0100; - const RCONTROL = 0b0000_1000; - const LALT = 0b0001_0000; - const RALT = 0b0010_0000; - const LMETA = 0b0100_0000; - const RMETA = 0b1000_0000; - #[deprecated = "use LMETA instead"] - const LSUPER = Self::LMETA.bits(); - #[deprecated = "use RMETA instead"] - const RSUPER = Self::RMETA.bits(); +const SYM_CONTROL: &str = "โŽˆ"; //โ€นโŒƒโ€บ looks too similar +const SYM_SHIFT: &str = "โ‡ง"; +#[cfg(target_os = "windows")] +const SYM_ALT: &str = "โއ"; +#[cfg(target_vendor = "apple")] +const SYM_ALT: &str = "โŒฅ"; +#[cfg(all(not(target_os = "windows"), not(target_vendor = "apple")))] +const SYM_ALT: &str = "โއ"; +#[cfg(target_os = "windows")] +const SYM_META: &str = "โ–"; +#[cfg(target_vendor = "apple")] +const SYM_META: &str = "โŒ˜"; +#[cfg(all(not(target_os = "windows"), not(target_vendor = "apple")))] +const SYM_META: &str = "โ—†"; + +use std::fmt::{self, Display, Formatter}; +impl Display for Modifiers { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if f.alternate() { + // {:#} "Fixed position" string output so that, e.g., + // โ‡ง is always the 2nd symbol as " โ‡ง" or "โ€นโ‡ง" + if self.lshift_state() { + write!(f, "โ€น{SYM_SHIFT}")?; + if self.rshift_state() { + write!(f, "โ€บ")?; + } else { + write!(f, "โ€ˆ")?; + }; + } else if self.rshift_state() { + write!(f, "โ€ˆ{SYM_SHIFT}โ€บ")?; + } else if self.shift_state() { + write!(f, "โ€ˆ{SYM_SHIFT}โ€ˆ")?; + } else { + write!(f, "โ€ˆ โ€ˆ")?; + } + if self.lcontrol_state() { + write!(f, "โ€น{SYM_CONTROL}")?; + if self.rcontrol_state() { + write!(f, "โ€บ")?; + } else { + write!(f, "โ€ˆ")?; + }; + } else if self.rcontrol_state() { + write!(f, "โ€ˆ{SYM_CONTROL}โ€บ")?; + } else if self.control_state() { + write!(f, "โ€ˆ{SYM_CONTROL}โ€ˆ")?; + } else { + write!(f, "โ€ˆ โ€ˆ")?; + } + if self.lmeta_state() { + write!(f, "โ€น{SYM_META}")?; + if self.rmeta_state() { + write!(f, "โ€บ")?; + } else { + write!(f, "โ€ˆ")?; + }; + } else if self.rmeta_state() { + write!(f, "โ€ˆ{SYM_META}โ€บ")?; + } else if self.meta_state() { + write!(f, "โ€ˆ{SYM_META}โ€ˆ")?; + } else { + write!(f, "โ€ˆ โ€ˆ")?; + } + if self.lalt_state() { + write!(f, "โ€น{SYM_ALT}")?; + if self.ralt_state() { + write!(f, "โ€บ")?; + } else { + write!(f, "โ€ˆ")?; + }; + } else if self.ralt_state() { + write!(f, "โ€ˆ{SYM_ALT}โ€บ")?; + } else if self.alt_state() { + write!(f, "โ€ˆ{SYM_ALT}โ€ˆ")?; + } else { + write!(f, "โ€ˆ โ€ˆ")?; + } + if self.lalt_graph_state() { + write!(f, "โ€น{SYM_ALT}Gr")?; + if self.ralt_graph_state() { + write!(f, "โ€บ")?; + } else { + write!(f, "โ€ˆ")?; + }; + } else if self.ralt_graph_state() { + write!(f, "โ€ˆ{SYM_ALT}Grโ€บ")?; + } else if self.alt_graph_state() { + write!(f, "โ€ˆ{SYM_ALT}Grโ€ˆ")?; + } else { + write!(f, "โ€ˆ โ€ˆ")?; + } + + if self.caps_lock_state() { + write!(f, "โ‡ช")?; + } else { + write!(f, " ")?; + } + if self.num_lock_state() { + write!(f, "โ‡ญ")?; + } else { + write!(f, " ")?; + } + if self.scroll_lock_state() { + write!(f, "โ‡ณ๐Ÿ”’")?; + } else { + write!(f, " ")?; + } + + if self.fn_state() { + write!(f, "ฦ’")?; + } else { + write!(f, " ")?; + } + if self.fn_lock_state() { + write!(f, "ฦ’๐Ÿ”’")?; + } else { + write!(f, " ")?; + } + if self.kana_lock_state() { + write!(f, "ใ‚ซใƒŠ๐Ÿ”’")?; + } else { + write!(f, " ")?; + } + + if self.loya_state() { + write!(f, "โ€น๐Ÿ‘")?; + if self.roya_state() { + write!(f, "โ€บ")?; + } else { + write!(f, "โ€ˆ")?; + }; + } else if self.roya_state() { + write!(f, "โ€ˆ๐Ÿ‘โ€บ")?; + } else if self.oya_state() { + write!(f, "โ€ˆ๐Ÿ‘โ€ˆ")?; + } else { + write!(f, "โ€ˆ โ€ˆ")?; + } + + if self.symbol_state() { + write!(f, "๐Ÿ”ฃ")?; + } else { + write!(f, "โ€ƒ")?; + } + if self.symbol_lock_state() { + write!(f, "๐Ÿ”ฃ๐Ÿ”’")?; + } else { + write!(f, "โ€ƒ ")?; + } + } else { + // {} "Flexible position" string output, no extra space separators + if self.lshift_state() { + write!(f, "โ€น{SYM_SHIFT}")?; + if self.rshift_state() { + write!(f, "โ€บ")?; + }; + } else if self.rshift_state() { + write!(f, "{SYM_SHIFT}โ€บ")?; + } else if self.shift_state() { + write!(f, "{SYM_SHIFT}")?; + } + if self.lcontrol_state() { + write!(f, "โ€น{SYM_CONTROL}")?; + if self.rcontrol_state() { + write!(f, "โ€บ")?; + }; + } else if self.rcontrol_state() { + write!(f, "{SYM_CONTROL}โ€บ")?; + } else if self.control_state() { + write!(f, "{SYM_CONTROL}")?; + } + if self.lmeta_state() { + write!(f, "โ€น{SYM_META}")?; + if self.rmeta_state() { + write!(f, "โ€บ")?; + }; + } else if self.rmeta_state() { + write!(f, "{SYM_META}โ€บ")?; + } else if self.meta_state() { + write!(f, "{SYM_META}")?; + } + if self.lalt_state() { + write!(f, "โ€น{SYM_ALT}")?; + if self.ralt_state() { + write!(f, "โ€บ")?; + }; + } else if self.ralt_state() { + write!(f, "{SYM_ALT}โ€บ")?; + } else if self.alt_state() { + write!(f, "{SYM_ALT}")?; + } + if self.lalt_graph_state() { + write!(f, "โ€น{SYM_ALT}Gr")?; + if self.ralt_graph_state() { + write!(f, "โ€บ")?; + }; + } else if self.ralt_graph_state() { + write!(f, "{SYM_ALT}Grโ€บ")?; + } else if self.alt_graph_state() { + write!(f, "{SYM_ALT}Gr")?; + } + + if self.caps_lock_state() { + write!(f, "โ‡ช")?; + } + if self.num_lock_state() { + write!(f, "โ‡ญ")?; + } + if self.scroll_lock_state() { + write!(f, "โ‡ณ๐Ÿ”’")?; + } + + if self.fn_state() { + write!(f, "ฦ’")?; + } + if self.fn_lock_state() { + write!(f, "ฦ’๐Ÿ”’")?; + } + if self.kana_lock_state() { + write!(f, "ใ‚ซใƒŠ๐Ÿ”’")?; + } + + if self.loya_state() { + write!(f, "โ€น๐Ÿ‘")?; + if self.roya_state() { + write!(f, "โ€บ")?; + }; + } else if self.roya_state() { + write!(f, "๐Ÿ‘โ€บ")?; + } else if self.oya_state() { + write!(f, "๐Ÿ‘")?; + } + + if self.symbol_state() { + write!(f, "๐Ÿ”ฃ")?; + } + if self.symbol_lock_state() { + write!(f, "๐Ÿ”ฃ๐Ÿ”’")?; + } + } + Ok(()) } } diff --git a/winit-orbital/src/event_loop.rs b/winit-orbital/src/event_loop.rs index 595f4cdf31..da3d5da784 100644 --- a/winit-orbital/src/event_loop.rs +++ b/winit-orbital/src/event_loop.rs @@ -13,15 +13,14 @@ use smol_str::SmolStr; use winit_core::application::ApplicationHandler; use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; -use winit_core::event::{self, Ime, Modifiers, StartCause}; +use winit_core::event::{self, Ime, StartCause}; use winit_core::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; use winit_core::keyboard::{ - Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, - PhysicalKey, + Key, KeyCode, KeyLocation, Modifiers, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }; use winit_core::window::{Theme, Window as CoreWindow, WindowId}; @@ -230,44 +229,24 @@ impl EventState { } fn modifiers(&self) -> Modifiers { - let mut state = ModifiersState::empty(); - let mut pressed_mods = ModifiersKeys::empty(); + let mut mods = Modifiers::empty(); - if self.keyboard.intersects(KeyboardModifierState::LSHIFT | KeyboardModifierState::RSHIFT) { - state |= ModifiersState::SHIFT; - } - - pressed_mods - .set(ModifiersKeys::LSHIFT, self.keyboard.contains(KeyboardModifierState::LSHIFT)); - pressed_mods - .set(ModifiersKeys::RSHIFT, self.keyboard.contains(KeyboardModifierState::RSHIFT)); - - if self.keyboard.intersects(KeyboardModifierState::LCTRL | KeyboardModifierState::RCTRL) { - state |= ModifiersState::CONTROL; - } + mods.set(Modifiers::LSHIFT, self.keyboard.contains(KeyboardModifierState::LSHIFT)); + mods.set(Modifiers::RSHIFT, self.keyboard.contains(KeyboardModifierState::RSHIFT)); + mods.set(Modifiers::SHIFT, mods.shift_state()); - pressed_mods - .set(ModifiersKeys::LCONTROL, self.keyboard.contains(KeyboardModifierState::LCTRL)); - pressed_mods - .set(ModifiersKeys::RCONTROL, self.keyboard.contains(KeyboardModifierState::RCTRL)); + mods.set(Modifiers::LCONTROL, self.keyboard.contains(KeyboardModifierState::LCTRL)); + mods.set(Modifiers::RCONTROL, self.keyboard.contains(KeyboardModifierState::RCTRL)); + mods.set(Modifiers::CONTROL, mods.control_state()); - if self.keyboard.intersects(KeyboardModifierState::LALT | KeyboardModifierState::RALT) { - state |= ModifiersState::ALT; - } - - pressed_mods.set(ModifiersKeys::LALT, self.keyboard.contains(KeyboardModifierState::LALT)); - pressed_mods.set(ModifiersKeys::RALT, self.keyboard.contains(KeyboardModifierState::RALT)); - - if self.keyboard.intersects(KeyboardModifierState::LMETA | KeyboardModifierState::RMETA) { - state |= ModifiersState::META - } + mods.set(Modifiers::LALT, self.keyboard.contains(KeyboardModifierState::LALT)); + mods.set(Modifiers::RALT, self.keyboard.contains(KeyboardModifierState::RALT)); - pressed_mods - .set(ModifiersKeys::LMETA, self.keyboard.contains(KeyboardModifierState::LMETA)); - pressed_mods - .set(ModifiersKeys::RMETA, self.keyboard.contains(KeyboardModifierState::RMETA)); + mods.set(Modifiers::LMETA, self.keyboard.contains(KeyboardModifierState::LMETA)); + mods.set(Modifiers::RMETA, self.keyboard.contains(KeyboardModifierState::RMETA)); + mods.set(Modifiers::META, mods.meta_state()); - Modifiers::new(state, pressed_mods) + mods } }