2022-12-22 21:35:33 +02:00
#![ allow(clippy::unnecessary_cast) ]
2023-07-08 22:36:42 +03:00
use std ::cell ::{ Cell , RefCell } ;
use std ::collections ::{ HashMap , VecDeque } ;
2023-12-23 20:58:38 +01:00
use std ::ptr ;
2019-05-01 17:03:30 -06:00
2023-12-23 20:58:38 +01:00
use icrate ::AppKit ::{
2023-12-23 23:07:55 +01:00
NSApplication , NSCursor , NSEvent , NSEventPhaseBegan , NSEventPhaseCancelled ,
NSEventPhaseChanged , NSEventPhaseEnded , NSEventPhaseMayBegin , NSResponder , NSTextInputClient ,
NSTrackingRectTag , NSView ,
2023-12-23 20:58:38 +01:00
} ;
2023-07-29 00:33:03 +02:00
use icrate ::Foundation ::{
2023-12-23 20:58:38 +01:00
MainThreadMarker , NSArray , NSAttributedString , NSAttributedStringKey , NSCopying ,
NSMutableAttributedString , NSObject , NSObjectProtocol , NSPoint , NSRange , NSRect , NSSize ,
NSString , NSUInteger ,
2019-06-21 11:33:15 -04:00
} ;
2023-07-29 00:33:03 +02:00
use objc2 ::rc ::{ Id , WeakId } ;
2023-08-02 16:30:41 +02:00
use objc2 ::runtime ::{ AnyObject , Sel } ;
2023-12-23 18:04:24 +01:00
use objc2 ::{
class , declare_class , msg_send , msg_send_id , mutability , sel , ClassType , DeclaredClass ,
} ;
2019-05-01 17:03:30 -06:00
2023-12-23 20:58:38 +01:00
use super ::cursor ::{ default_cursor , invisible_cursor } ;
2023-12-23 23:07:55 +01:00
use super ::event ::{ code_to_key , code_to_location } ;
2023-12-23 20:58:38 +01:00
use super ::event ::{ lalt_pressed , ralt_pressed } ;
2019-06-18 02:27:00 +08:00
use crate ::{
2022-05-23 13:53:07 -06:00
dpi ::{ LogicalPosition , LogicalSize } ,
2019-05-01 17:03:30 -06:00
event ::{
2023-05-28 20:02:59 +02:00
DeviceEvent , ElementState , Event , Ime , Modifiers , MouseButton , MouseScrollDelta ,
TouchPhase , WindowEvent ,
2019-06-21 11:33:15 -04:00
} ,
2023-10-19 15:27:49 +01:00
keyboard ::{ Key , KeyCode , KeyLocation , ModifiersState , NamedKey , PhysicalKey } ,
2023-06-20 19:07:49 +00:00
platform ::macos ::{ OptionAsAlt , WindowExtMacOS } ,
2023-10-19 15:27:49 +01:00
platform ::scancode ::PhysicalKeyExtScancode ,
2019-06-21 11:33:15 -04:00
platform_impl ::platform ::{
app_state ::AppState ,
2023-08-27 17:04:39 +02:00
event ::{ create_key_event , event_mods } ,
2022-09-08 16:45:29 +02:00
util ,
window ::WinitWindow ,
2019-06-21 11:33:15 -04:00
DEVICE_ID ,
2019-05-01 17:03:30 -06:00
} ,
window ::WindowId ,
} ;
2022-09-08 16:45:29 +02:00
#[ derive(Debug) ]
2023-07-08 22:36:42 +03:00
struct CursorState {
visible : bool ,
2023-07-29 00:33:03 +02:00
cursor : Id < NSCursor > ,
2020-01-03 09:34:14 +09:00
}
impl Default for CursorState {
fn default ( ) -> Self {
Self {
visible : true ,
2023-12-23 20:58:38 +01:00
cursor : default_cursor ( ) ,
2020-01-03 09:34:14 +09:00
}
}
}
2023-07-08 22:36:42 +03:00
#[ derive(Debug, Eq, PartialEq, Clone, Copy, Default) ]
2022-05-07 05:29:25 +03:00
enum ImeState {
2023-07-08 22:36:42 +03:00
#[ default ]
2022-07-21 22:23:22 +03:00
/// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user.
2022-05-07 05:29:25 +03:00
Disabled ,
2022-07-21 22:23:22 +03:00
2023-05-02 20:09:50 +03:00
/// The ground state of enabled IME input. It means that both Preedit and regular keyboard
/// input could be start from it.
Ground ,
2022-07-21 22:23:22 +03:00
/// The IME is in preedit.
2022-05-07 05:29:25 +03:00
Preedit ,
2022-07-21 22:23:22 +03:00
/// The text was just commited, so the next input from the keyboard must be ignored.
Commited ,
2022-05-07 05:29:25 +03:00
}
2023-05-28 20:02:59 +02:00
bitflags! {
2023-06-02 15:44:36 +01:00
#[ derive(Debug, Clone, Copy, PartialEq) ]
2023-05-28 20:02:59 +02:00
struct ModLocationMask : u8 {
2023-11-17 15:56:03 +04:00
const LEFT = 0b0001 ;
const RIGHT = 0b0010 ;
2023-05-28 20:02:59 +02:00
}
}
impl ModLocationMask {
fn from_location ( loc : KeyLocation ) -> ModLocationMask {
match loc {
KeyLocation ::Left = > ModLocationMask ::LEFT ,
KeyLocation ::Right = > ModLocationMask ::RIGHT ,
_ = > unreachable! ( ) ,
}
}
}
2023-11-17 15:56:03 +04:00
fn key_to_modifier ( key : & Key ) -> Option < ModifiersState > {
2023-05-28 20:02:59 +02:00
match key {
2023-11-17 15:56:03 +04:00
Key ::Named ( NamedKey ::Alt ) = > Some ( ModifiersState ::ALT ) ,
Key ::Named ( NamedKey ::Control ) = > Some ( ModifiersState ::CONTROL ) ,
Key ::Named ( NamedKey ::Super ) = > Some ( ModifiersState ::SUPER ) ,
Key ::Named ( NamedKey ::Shift ) = > Some ( ModifiersState ::SHIFT ) ,
_ = > None ,
2023-05-28 20:02:59 +02:00
}
}
fn get_right_modifier_code ( key : & Key ) -> KeyCode {
match key {
2023-10-19 15:27:49 +01:00
Key ::Named ( NamedKey ::Alt ) = > KeyCode ::AltRight ,
Key ::Named ( NamedKey ::Control ) = > KeyCode ::ControlRight ,
Key ::Named ( NamedKey ::Shift ) = > KeyCode ::ShiftRight ,
Key ::Named ( NamedKey ::Super ) = > KeyCode ::SuperRight ,
2023-05-28 20:02:59 +02:00
_ = > unreachable! ( ) ,
}
}
fn get_left_modifier_code ( key : & Key ) -> KeyCode {
match key {
2023-10-19 15:27:49 +01:00
Key ::Named ( NamedKey ::Alt ) = > KeyCode ::AltLeft ,
Key ::Named ( NamedKey ::Control ) = > KeyCode ::ControlLeft ,
Key ::Named ( NamedKey ::Shift ) = > KeyCode ::ShiftLeft ,
Key ::Named ( NamedKey ::Super ) = > KeyCode ::SuperLeft ,
2023-05-28 20:02:59 +02:00
_ = > unreachable! ( ) ,
}
}
2023-07-08 22:36:42 +03:00
#[ derive(Debug, Default) ]
2023-07-29 00:33:03 +02:00
pub struct ViewState {
2023-07-08 22:36:42 +03:00
cursor_state : RefCell < CursorState > ,
ime_position : Cell < LogicalPosition < f64 > > ,
ime_size : Cell < LogicalSize < f64 > > ,
modifiers : Cell < Modifiers > ,
phys_modifiers : RefCell < HashMap < Key , ModLocationMask > > ,
tracking_rect : Cell < Option < NSTrackingRectTag > > ,
ime_state : Cell < ImeState > ,
input_source : RefCell < String > ,
2022-05-07 05:29:25 +03:00
/// True iff the application wants IME events.
///
/// Can be set using `set_ime_allowed`
2023-07-08 22:36:42 +03:00
ime_allowed : Cell < bool > ,
2022-05-07 05:29:25 +03:00
/// True if the current key event should be forwarded
/// to the application, even during IME
2023-07-08 22:36:42 +03:00
forward_key_to_app : Cell < bool > ,
2023-07-29 00:33:03 +02:00
marked_text : RefCell < Id < NSMutableAttributedString > > ,
2023-07-08 22:36:42 +03:00
accepts_first_mouse : bool ,
2023-12-23 18:04:24 +01:00
// Weak reference because the window keeps a strong reference to the view
_ns_window : WeakId < WinitWindow > ,
2018-05-17 21:28:30 -04:00
}
2022-09-02 19:38:32 +02:00
declare_class! (
2023-12-23 18:04:24 +01:00
pub ( super ) struct WinitView ;
2023-07-29 00:33:03 +02:00
2022-09-02 18:46:18 +02:00
unsafe impl ClassType for WinitView {
#[ inherits(NSResponder, NSObject) ]
2022-09-08 16:45:29 +02:00
type Super = NSView ;
2023-12-23 20:58:38 +01:00
type Mutability = mutability ::MainThreadOnly ;
2023-07-29 00:33:03 +02:00
const NAME : & 'static str = " WinitView " ;
2022-09-02 18:46:18 +02:00
}
2023-12-23 18:04:24 +01:00
impl DeclaredClass for WinitView {
type Ivars = ViewState ;
2022-09-02 18:46:18 +02:00
}
unsafe impl WinitView {
2023-07-29 00:33:03 +02:00
#[ method(viewDidMoveToWindow) ]
2023-07-08 22:36:42 +03:00
fn view_did_move_to_window ( & self ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " viewDidMoveToWindow " ) ;
2023-12-23 18:04:24 +01:00
if let Some ( tracking_rect ) = self . ivars ( ) . tracking_rect . take ( ) {
2022-09-08 16:45:29 +02:00
self . removeTrackingRect ( tracking_rect ) ;
2022-09-02 19:38:32 +02:00
}
2022-09-08 16:45:29 +02:00
2023-08-24 22:52:11 +02:00
let rect = self . frame ( ) ;
2023-12-23 20:58:38 +01:00
let tracking_rect = unsafe { self . addTrackingRect_owner_userData_assumeInside ( rect , self , ptr ::null_mut ( ) , false ) } ;
assert_ne! ( tracking_rect , 0 , " failed adding tracking rect " ) ;
2023-12-23 18:04:24 +01:00
self . ivars ( ) . tracking_rect . set ( Some ( tracking_rect ) ) ;
2019-12-31 05:32:37 +09:00
}
2023-07-29 00:33:03 +02:00
#[ method(frameDidChange:) ]
2023-07-08 22:36:42 +03:00
fn frame_did_change ( & self , _event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " frameDidChange: " ) ;
2023-12-23 18:04:24 +01:00
if let Some ( tracking_rect ) = self . ivars ( ) . tracking_rect . take ( ) {
2022-09-08 16:45:29 +02:00
self . removeTrackingRect ( tracking_rect ) ;
2022-09-02 19:38:32 +02:00
}
2022-09-08 16:45:29 +02:00
2023-08-24 22:52:11 +02:00
let rect = self . frame ( ) ;
2023-12-23 20:58:38 +01:00
let tracking_rect = unsafe { self . addTrackingRect_owner_userData_assumeInside ( rect , self , ptr ::null_mut ( ) , false ) } ;
assert_ne! ( tracking_rect , 0 , " failed adding tracking rect " ) ;
2023-12-23 18:04:24 +01:00
self . ivars ( ) . tracking_rect . set ( Some ( tracking_rect ) ) ;
2022-09-08 16:45:29 +02:00
// Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
let logical_size = LogicalSize ::new ( rect . size . width as f64 , rect . size . height as f64 ) ;
let size = logical_size . to_physical ::< u32 > ( self . scale_factor ( ) ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::Resized ( size ) ) ;
2019-12-31 05:32:37 +09:00
}
2023-07-29 00:33:03 +02:00
#[ method(drawRect:) ]
2023-07-08 22:36:42 +03:00
fn draw_rect ( & self , rect : NSRect ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " drawRect: " ) ;
2019-12-31 05:32:37 +09:00
2023-06-09 21:20:27 +08:00
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
2023-12-23 18:04:24 +01:00
if let Some ( window ) = self . ivars ( ) . _ns_window . load ( ) {
2023-06-09 21:20:27 +08:00
AppState ::handle_redraw ( WindowId ( window . id ( ) ) ) ;
}
2018-12-27 15:16:58 -05:00
2022-09-08 16:45:29 +02:00
#[ allow(clippy::let_unit_value) ]
unsafe {
2022-09-02 19:38:32 +02:00
let _ : ( ) = msg_send! [ super ( self ) , drawRect : rect ] ;
}
}
2019-05-01 17:03:30 -06:00
2023-07-29 00:33:03 +02:00
#[ method(acceptsFirstResponder) ]
2022-09-02 19:38:32 +02:00
fn accepts_first_responder ( & self ) -> bool {
trace_scope! ( " acceptsFirstResponder " ) ;
true
}
2019-05-01 17:03:30 -06:00
2022-09-02 19:38:32 +02:00
// This is necessary to prevent a beefy terminal error on MacBook Pros:
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
// TODO: Add an API extension for using `NSTouchBar`
2023-07-29 00:33:03 +02:00
#[ method_id(touchBar) ]
fn touch_bar ( & self ) -> Option < Id < NSObject > > {
2022-09-02 19:38:32 +02:00
trace_scope! ( " touchBar " ) ;
2023-07-29 00:33:03 +02:00
None
2022-09-02 19:38:32 +02:00
}
2018-12-28 15:29:29 -05:00
2023-07-29 00:33:03 +02:00
#[ method(resetCursorRects) ]
2022-09-02 19:38:32 +02:00
fn reset_cursor_rects ( & self ) {
trace_scope! ( " resetCursorRects " ) ;
2022-09-02 21:02:40 +02:00
let bounds = self . bounds ( ) ;
2023-12-23 18:04:24 +01:00
let cursor_state = self . ivars ( ) . cursor_state . borrow ( ) ;
2022-09-08 16:45:29 +02:00
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
2022-09-02 21:02:40 +02:00
if cursor_state . visible {
2023-12-23 20:58:38 +01:00
self . addCursorRect_cursor ( bounds , & cursor_state . cursor ) ;
2022-09-02 21:02:40 +02:00
} else {
2023-12-23 20:58:38 +01:00
self . addCursorRect_cursor ( bounds , & invisible_cursor ( ) ) ;
2022-09-02 19:38:32 +02:00
}
}
2022-09-02 18:46:18 +02:00
}
2018-12-28 15:29:29 -05:00
2023-07-29 00:33:03 +02:00
unsafe impl NSTextInputClient for WinitView {
#[ method(hasMarkedText) ]
2022-09-02 19:38:32 +02:00
fn has_marked_text ( & self ) -> bool {
trace_scope! ( " hasMarkedText " ) ;
2023-12-23 18:04:24 +01:00
self . ivars ( ) . marked_text . borrow ( ) . length ( ) > 0
2022-09-02 19:38:32 +02:00
}
2022-09-02 18:46:18 +02:00
2023-07-29 00:33:03 +02:00
#[ method(markedRange) ]
2022-09-02 19:38:32 +02:00
fn marked_range ( & self ) -> NSRange {
trace_scope! ( " markedRange " ) ;
2023-12-23 18:04:24 +01:00
let length = self . ivars ( ) . marked_text . borrow ( ) . length ( ) ;
2022-09-08 16:45:29 +02:00
if length > 0 {
NSRange ::new ( 0 , length )
} else {
util ::EMPTY_RANGE
2022-09-02 19:38:32 +02:00
}
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method(selectedRange) ]
2022-09-02 19:38:32 +02:00
fn selected_range ( & self ) -> NSRange {
trace_scope! ( " selectedRange " ) ;
2018-05-17 21:28:30 -04:00
util ::EMPTY_RANGE
}
2023-07-29 00:33:03 +02:00
#[ method(setMarkedText:selectedRange:replacementRange:) ]
2022-09-02 19:38:32 +02:00
fn set_marked_text (
2023-07-08 22:36:42 +03:00
& self ,
2022-09-08 16:45:29 +02:00
string : & NSObject ,
2022-09-02 19:38:32 +02:00
_selected_range : NSRange ,
_replacement_range : NSRange ,
) {
trace_scope! ( " setMarkedText:selectedRange:replacementRange: " ) ;
2018-05-17 21:28:30 -04:00
2022-09-08 16:45:29 +02:00
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let ( marked_text , preedit_string ) = if string . is_kind_of ::< NSAttributedString > ( ) {
let string : * const NSObject = string ;
let string : * const NSAttributedString = string . cast ( ) ;
let string = unsafe { & * string } ;
(
NSMutableAttributedString ::from_attributed_nsstring ( string ) ,
string . string ( ) . to_string ( ) ,
)
} else {
let string : * const NSObject = string ;
let string : * const NSString = string . cast ( ) ;
let string = unsafe { & * string } ;
(
NSMutableAttributedString ::from_nsstring ( string ) ,
string . to_string ( ) ,
)
} ;
2022-05-07 05:29:25 +03:00
2023-05-02 20:09:50 +03:00
// Update marked text.
2023-12-23 18:04:24 +01:00
* self . ivars ( ) . marked_text . borrow_mut ( ) = marked_text ;
2022-05-07 05:29:25 +03:00
2022-09-08 16:45:29 +02:00
// Notify IME is active if application still doesn't know it.
2023-12-23 18:04:24 +01:00
if self . ivars ( ) . ime_state . get ( ) = = ImeState ::Disabled {
* self . ivars ( ) . input_source . borrow_mut ( ) = self . current_input_source ( ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::Ime ( Ime ::Enabled ) ) ;
2022-09-02 19:38:32 +02:00
}
2022-09-08 16:45:29 +02:00
2023-12-23 20:58:38 +01:00
if unsafe { self . hasMarkedText ( ) } {
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . set ( ImeState ::Preedit ) ;
2023-05-02 20:09:50 +03:00
} else {
// In case the preedit was cleared, set IME into the Ground state.
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . set ( ImeState ::Ground ) ;
2022-09-08 16:45:29 +02:00
}
// Empty string basically means that there's no preedit, so indicate that by sending
// `None` cursor range.
let cursor_range = if preedit_string . is_empty ( ) {
None
} else {
Some ( ( preedit_string . len ( ) , preedit_string . len ( ) ) )
} ;
// Send WindowEvent for updating marked text
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::Ime ( Ime ::Preedit ( preedit_string , cursor_range ) ) ) ;
2022-07-21 22:23:22 +03:00
}
2023-07-29 00:33:03 +02:00
#[ method(unmarkText) ]
2023-07-08 22:36:42 +03:00
fn unmark_text ( & self ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " unmarkText " ) ;
2023-12-23 18:04:24 +01:00
* self . ivars ( ) . marked_text . borrow_mut ( ) = NSMutableAttributedString ::new ( ) ;
2022-09-08 16:45:29 +02:00
let input_context = self . inputContext ( ) . expect ( " input context " ) ;
input_context . discardMarkedText ( ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::Ime ( Ime ::Preedit ( String ::new ( ) , None ) ) ) ;
2022-09-08 16:45:29 +02:00
if self . is_ime_enabled ( ) {
2023-12-23 18:04:24 +01:00
// Leave the Preedit self.ivars()
self . ivars ( ) . ime_state . set ( ImeState ::Ground ) ;
2022-09-08 16:45:29 +02:00
} else {
warn! ( " Expected to have IME enabled when receiving unmarkText " ) ;
2022-09-02 19:38:32 +02:00
}
2022-05-07 05:29:25 +03:00
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method_id(validAttributesForMarkedText) ]
fn valid_attributes_for_marked_text ( & self ) -> Id < NSArray < NSAttributedStringKey > > {
2022-09-02 19:38:32 +02:00
trace_scope! ( " validAttributesForMarkedText " ) ;
2023-07-29 00:33:03 +02:00
NSArray ::new ( )
2022-09-02 19:38:32 +02:00
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method_id(attributedSubstringForProposedRange:actualRange:) ]
2022-09-02 19:38:32 +02:00
fn attributed_substring_for_proposed_range (
& self ,
_range : NSRange ,
2023-07-29 00:33:03 +02:00
_actual_range : * mut NSRange ,
) -> Option < Id < NSAttributedString > > {
2022-09-02 19:38:32 +02:00
trace_scope! ( " attributedSubstringForProposedRange:actualRange: " ) ;
2023-07-29 00:33:03 +02:00
None
2022-09-02 19:38:32 +02:00
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method(characterIndexForPoint:) ]
2022-09-02 19:38:32 +02:00
fn character_index_for_point ( & self , _point : NSPoint ) -> NSUInteger {
trace_scope! ( " characterIndexForPoint: " ) ;
0
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method(firstRectForCharacterRange:actualRange:) ]
2022-09-02 19:38:32 +02:00
fn first_rect_for_character_range (
& self ,
_range : NSRange ,
2023-07-29 00:33:03 +02:00
_actual_range : * mut NSRange ,
2022-09-02 19:38:32 +02:00
) -> NSRect {
trace_scope! ( " firstRectForCharacterRange:actualRange: " ) ;
2022-09-08 16:45:29 +02:00
let window = self . window ( ) ;
let content_rect = window . contentRectForFrameRect ( window . frame ( ) ) ;
let base_x = content_rect . origin . x as f64 ;
let base_y = ( content_rect . origin . y + content_rect . size . height ) as f64 ;
2023-12-23 18:04:24 +01:00
let x = base_x + self . ivars ( ) . ime_position . get ( ) . x ;
let y = base_y - self . ivars ( ) . ime_position . get ( ) . y ;
let LogicalSize { width , height } = self . ivars ( ) . ime_size . get ( ) ;
2023-06-22 19:12:14 +00:00
NSRect ::new ( NSPoint ::new ( x as _ , y as _ ) , NSSize ::new ( width , height ) )
2022-09-02 19:38:32 +02:00
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method(insertText:replacementRange:) ]
2023-07-08 22:36:42 +03:00
fn insert_text ( & self , string : & NSObject , _replacement_range : NSRange ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " insertText:replacementRange: " ) ;
2018-05-17 21:28:30 -04:00
2022-09-08 16:45:29 +02:00
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let string = if string . is_kind_of ::< NSAttributedString > ( ) {
let string : * const NSObject = string ;
let string : * const NSAttributedString = string . cast ( ) ;
unsafe { & * string } . string ( ) . to_string ( )
} else {
let string : * const NSObject = string ;
let string : * const NSString = string . cast ( ) ;
unsafe { & * string } . to_string ( )
} ;
2018-05-17 21:28:30 -04:00
2022-09-08 16:45:29 +02:00
let is_control = string . chars ( ) . next ( ) . map_or ( false , | c | c . is_control ( ) ) ;
2018-05-17 21:28:30 -04:00
2023-02-01 18:08:25 +03:00
// Commit only if we have marked text.
2023-12-23 20:58:38 +01:00
if unsafe { self . hasMarkedText ( ) } & & self . is_ime_enabled ( ) & & ! is_control {
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::Ime ( Ime ::Preedit ( String ::new ( ) , None ) ) ) ;
self . queue_event ( WindowEvent ::Ime ( Ime ::Commit ( string ) ) ) ;
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . set ( ImeState ::Commited ) ;
2022-09-02 19:38:32 +02:00
}
2018-05-17 21:28:30 -04:00
}
2022-09-08 16:45:29 +02:00
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
2023-07-29 00:33:03 +02:00
#[ method(doCommandBySelector:) ]
2023-07-08 22:36:42 +03:00
fn do_command_by_selector ( & self , _command : Sel ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " doCommandBySelector: " ) ;
2022-09-08 16:45:29 +02:00
// We shouldn't forward any character from just commited text, since we'll end up sending
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
// which is not desired given it was used to confirm IME input.
2023-12-23 18:04:24 +01:00
if self . ivars ( ) . ime_state . get ( ) = = ImeState ::Commited {
2022-09-08 16:45:29 +02:00
return ;
}
2022-07-21 22:23:22 +03:00
2023-12-23 18:04:24 +01:00
self . ivars ( ) . forward_key_to_app . set ( true ) ;
2018-05-17 21:28:30 -04:00
2023-12-23 20:58:38 +01:00
if unsafe { self . hasMarkedText ( ) } & & self . ivars ( ) . ime_state . get ( ) = = ImeState ::Preedit {
2023-05-02 20:09:50 +03:00
// Leave preedit so that we also report the key-up for this key.
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . set ( ImeState ::Ground ) ;
2022-09-02 19:38:32 +02:00
}
2022-05-07 05:29:25 +03:00
}
2018-05-17 21:28:30 -04:00
}
2019-02-24 06:41:55 +10:00
2022-09-02 18:46:18 +02:00
unsafe impl WinitView {
2023-07-29 00:33:03 +02:00
#[ method(keyDown:) ]
2023-07-08 22:36:42 +03:00
fn key_down ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " keyDown: " ) ;
2023-07-08 22:36:42 +03:00
{
2023-12-23 18:04:24 +01:00
let mut prev_input_source = self . ivars ( ) . input_source . borrow_mut ( ) ;
2023-07-08 22:36:42 +03:00
let current_input_source = self . current_input_source ( ) ;
if * prev_input_source ! = current_input_source & & self . is_ime_enabled ( ) {
* prev_input_source = current_input_source ;
drop ( prev_input_source ) ;
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . set ( ImeState ::Disabled ) ;
2023-07-08 22:36:42 +03:00
self . queue_event ( WindowEvent ::Ime ( Ime ::Disabled ) ) ;
}
2022-09-08 16:45:29 +02:00
}
2023-01-31 01:35:49 -08:00
// Get the characters from the event.
2023-12-23 18:04:24 +01:00
let old_ime_state = self . ivars ( ) . ime_state . get ( ) ;
self . ivars ( ) . forward_key_to_app . set ( false ) ;
2023-06-20 19:07:49 +00:00
let event = replace_event ( event , self . window ( ) . option_as_alt ( ) ) ;
2022-09-08 16:45:29 +02:00
// The `interpretKeyEvents` function might call
// `setMarkedText`, `insertText`, and `doCommandBySelector`.
// It's important that we call this before queuing the KeyboardInput, because
// we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application)
2023-12-23 18:04:24 +01:00
if self . ivars ( ) . ime_allowed . get ( ) {
2023-07-29 00:33:03 +02:00
let events_for_nsview = NSArray ::from_slice ( & [ & * event ] ) ;
2022-09-08 16:45:29 +02:00
unsafe { self . interpretKeyEvents ( & events_for_nsview ) } ;
// If the text was commited we must treat the next keyboard event as IME related.
2023-12-23 18:04:24 +01:00
if self . ivars ( ) . ime_state . get ( ) = = ImeState ::Commited {
2023-02-01 18:08:25 +03:00
// Remove any marked text, so normal input can continue.
2023-12-23 18:04:24 +01:00
* self . ivars ( ) . marked_text . borrow_mut ( ) = NSMutableAttributedString ::new ( ) ;
2022-09-02 19:38:32 +02:00
}
2022-09-08 16:45:29 +02:00
}
Move `ModifiersChanged` variant to `WindowEvent` (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`
* macos: Fix flags_changed for ModifiersChanged variant move
I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Fire a ModifiersChanged event on window_did_resign_key
From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent. It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Fix build
I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS
Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.
It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.
[1]: f79f21641a31da3e4039d41be89047cdcc6028f7
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Add a hook to update stale modifiers
Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags. These are internally stored as a bitmask in
the latter and an enum in the former.
We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`. That's what the hook does.
This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Only call event_mods once when determining whether to update state
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* flags_changed: Memoize window_id collection
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_did_resign_key: Remove synthetic ModifiersChanged event
We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Add a call to update_potentially_stale_modifiers
Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave. Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* key_up: Add a call to update_potentially_stale_modifiers
We also want to make sure modifiers state is synchronized here, too.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Remove update_potentially_stale_modifiers invocation
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Retry CI
* ViewState: Promote visibility of modifiers to the macos impl
This is so that we can interact with the ViewState directly from the
WindowDelegate.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_delegate: Synthetically set modifiers state to empty on resignKey
This logic is implemented similarly on other platforms, so we wish to
regain parity here. Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.
This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Check for modifiers change in window events
* Fix modifier changed on macOS
Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.
The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.
This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.
* Fix unused variable warning
* Move changelog entry into `Unreleased` section
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
2023-06-20 19:07:49 +00:00
self . update_modifiers ( & event , false ) ;
2022-09-02 19:38:32 +02:00
2023-12-23 18:04:24 +01:00
let had_ime_input = match self . ivars ( ) . ime_state . get ( ) {
2023-05-02 20:09:50 +03:00
ImeState ::Commited = > {
// Allow normal input after the commit.
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . set ( ImeState ::Ground ) ;
2023-05-02 20:09:50 +03:00
true
}
ImeState ::Preedit = > true ,
2023-05-13 03:02:05 +03:00
// `key_down` could result in preedit clear, so compare old and current state.
2023-12-23 18:04:24 +01:00
_ = > old_ime_state ! = self . ivars ( ) . ime_state . get ( ) ,
2023-05-02 20:09:50 +03:00
} ;
2022-09-02 19:38:32 +02:00
2023-12-23 18:04:24 +01:00
if ! had_ime_input | | self . ivars ( ) . forward_key_to_app . get ( ) {
2023-12-23 20:58:38 +01:00
let key_event = create_key_event ( & event , true , unsafe { event . isARepeat ( ) } , None ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
2023-05-28 20:02:59 +02:00
event : key_event ,
2023-01-22 23:29:38 +01:00
is_synthetic : false ,
} ) ;
2022-09-02 19:38:32 +02:00
}
2022-05-07 05:29:25 +03:00
}
2022-09-02 19:38:32 +02:00
2023-07-29 00:33:03 +02:00
#[ method(keyUp:) ]
2023-07-08 22:36:42 +03:00
fn key_up ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " keyUp: " ) ;
2022-09-08 16:45:29 +02:00
2023-06-20 19:07:49 +00:00
let event = replace_event ( event , self . window ( ) . option_as_alt ( ) ) ;
self . update_modifiers ( & event , false ) ;
2022-09-08 16:45:29 +02:00
2023-05-02 20:09:50 +03:00
// We want to send keyboard input when we are currently in the ground state.
2023-07-08 22:36:42 +03:00
if matches! (
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_state . get ( ) ,
2023-07-08 22:36:42 +03:00
ImeState ::Ground | ImeState ::Disabled
) {
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
2023-06-20 19:07:49 +00:00
event : create_key_event ( & event , false , false , None ) ,
2023-01-22 23:29:38 +01:00
is_synthetic : false ,
} ) ;
2022-07-21 22:23:22 +03:00
}
2022-05-07 05:29:25 +03:00
}
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method(flagsChanged:) ]
fn flags_changed ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " flagsChanged: " ) ;
2023-07-29 00:33:03 +02:00
self . update_modifiers ( event , true ) ;
2018-05-17 21:28:30 -04:00
}
2023-07-29 00:33:03 +02:00
#[ method(insertTab:) ]
2023-08-02 16:30:41 +02:00
fn insert_tab ( & self , _sender : Option < & AnyObject > ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " insertTab: " ) ;
2022-09-08 16:45:29 +02:00
let window = self . window ( ) ;
if let Some ( first_responder ) = window . firstResponder ( ) {
if * first_responder = = * * * self {
window . selectNextKeyView ( Some ( self ) )
2022-09-02 19:38:32 +02:00
}
}
}
2018-09-12 20:04:16 +03:00
2023-07-29 00:33:03 +02:00
#[ method(insertBackTab:) ]
2023-08-02 16:30:41 +02:00
fn insert_back_tab ( & self , _sender : Option < & AnyObject > ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " insertBackTab: " ) ;
2022-09-08 16:45:29 +02:00
let window = self . window ( ) ;
if let Some ( first_responder ) = window . firstResponder ( ) {
if * first_responder = = * * * self {
window . selectPreviousKeyView ( Some ( self ) )
2022-09-02 19:38:32 +02:00
}
}
}
Move `ModifiersChanged` variant to `WindowEvent` (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`
* macos: Fix flags_changed for ModifiersChanged variant move
I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Fire a ModifiersChanged event on window_did_resign_key
From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent. It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Fix build
I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS
Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.
It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.
[1]: f79f21641a31da3e4039d41be89047cdcc6028f7
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Add a hook to update stale modifiers
Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags. These are internally stored as a bitmask in
the latter and an enum in the former.
We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`. That's what the hook does.
This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Only call event_mods once when determining whether to update state
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* flags_changed: Memoize window_id collection
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_did_resign_key: Remove synthetic ModifiersChanged event
We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Add a call to update_potentially_stale_modifiers
Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave. Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* key_up: Add a call to update_potentially_stale_modifiers
We also want to make sure modifiers state is synchronized here, too.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Remove update_potentially_stale_modifiers invocation
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Retry CI
* ViewState: Promote visibility of modifiers to the macos impl
This is so that we can interact with the ViewState directly from the
WindowDelegate.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_delegate: Synthetically set modifiers state to empty on resignKey
This logic is implemented similarly on other platforms, so we wish to
regain parity here. Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.
This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Check for modifiers change in window events
* Fix modifier changed on macOS
Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.
The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.
This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.
* Fix unused variable warning
* Move changelog entry into `Unreleased` section
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
2022-09-02 19:38:32 +02:00
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
2023-07-29 00:33:03 +02:00
#[ method(cancelOperation:) ]
2023-08-02 16:30:41 +02:00
fn cancel_operation ( & self , _sender : Option < & AnyObject > ) {
2023-12-23 23:07:55 +01:00
let mtm = MainThreadMarker ::from ( self ) ;
2022-09-02 19:38:32 +02:00
trace_scope! ( " cancelOperation: " ) ;
2023-12-23 23:07:55 +01:00
let event = NSApplication ::sharedApplication ( mtm )
2022-09-08 16:45:29 +02:00
. currentEvent ( )
. expect ( " could not find current event " ) ;
2022-09-02 19:38:32 +02:00
2023-05-28 20:02:59 +02:00
self . update_modifiers ( & event , false ) ;
2023-12-23 20:58:38 +01:00
let event = create_key_event ( & event , true , unsafe { event . isARepeat ( ) } , None ) ;
2022-09-02 19:38:32 +02:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
2023-05-28 20:02:59 +02:00
event ,
2023-01-22 23:29:38 +01:00
is_synthetic : false ,
} ) ;
2022-05-07 05:29:25 +03:00
}
2019-05-01 17:03:30 -06:00
2023-07-29 00:33:03 +02:00
#[ method(mouseDown:) ]
2023-07-08 22:36:42 +03:00
fn mouse_down ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " mouseDown: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2023-01-18 04:32:34 +01:00
self . mouse_click ( event , ElementState ::Pressed ) ;
2019-05-01 17:03:30 -06:00
}
2023-07-29 00:33:03 +02:00
#[ method(mouseUp:) ]
2023-07-08 22:36:42 +03:00
fn mouse_up ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " mouseUp: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2023-01-18 04:32:34 +01:00
self . mouse_click ( event , ElementState ::Released ) ;
2019-05-01 17:03:30 -06:00
}
2023-07-29 00:33:03 +02:00
#[ method(rightMouseDown:) ]
2023-07-08 22:36:42 +03:00
fn right_mouse_down ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " rightMouseDown: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2023-01-18 04:32:34 +01:00
self . mouse_click ( event , ElementState ::Pressed ) ;
2019-05-01 17:03:30 -06:00
}
2023-07-29 00:33:03 +02:00
#[ method(rightMouseUp:) ]
2023-07-08 22:36:42 +03:00
fn right_mouse_up ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " rightMouseUp: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2023-01-18 04:32:34 +01:00
self . mouse_click ( event , ElementState ::Released ) ;
2019-05-01 17:03:30 -06:00
}
2023-07-29 00:33:03 +02:00
#[ method(otherMouseDown:) ]
2023-07-08 22:36:42 +03:00
fn other_mouse_down ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " otherMouseDown: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2023-01-18 04:32:34 +01:00
self . mouse_click ( event , ElementState ::Pressed ) ;
2022-09-02 19:38:32 +02:00
}
Move `ModifiersChanged` variant to `WindowEvent` (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`
* macos: Fix flags_changed for ModifiersChanged variant move
I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Fire a ModifiersChanged event on window_did_resign_key
From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent. It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Fix build
I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS
Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.
It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.
[1]: f79f21641a31da3e4039d41be89047cdcc6028f7
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Add a hook to update stale modifiers
Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags. These are internally stored as a bitmask in
the latter and an enum in the former.
We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`. That's what the hook does.
This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Only call event_mods once when determining whether to update state
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* flags_changed: Memoize window_id collection
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_did_resign_key: Remove synthetic ModifiersChanged event
We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Add a call to update_potentially_stale_modifiers
Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave. Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* key_up: Add a call to update_potentially_stale_modifiers
We also want to make sure modifiers state is synchronized here, too.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Remove update_potentially_stale_modifiers invocation
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Retry CI
* ViewState: Promote visibility of modifiers to the macos impl
This is so that we can interact with the ViewState directly from the
WindowDelegate.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_delegate: Synthetically set modifiers state to empty on resignKey
This logic is implemented similarly on other platforms, so we wish to
regain parity here. Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.
This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Check for modifiers change in window events
* Fix modifier changed on macOS
Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.
The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.
This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.
* Fix unused variable warning
* Move changelog entry into `Unreleased` section
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
2023-07-29 00:33:03 +02:00
#[ method(otherMouseUp:) ]
2023-07-08 22:36:42 +03:00
fn other_mouse_up ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " otherMouseUp: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2023-01-18 04:32:34 +01:00
self . mouse_click ( event , ElementState ::Released ) ;
2018-05-17 21:28:30 -04:00
}
2019-12-12 22:48:32 +01:00
2022-09-02 19:38:32 +02:00
// No tracing on these because that would be overly verbose
2018-05-17 21:28:30 -04:00
2023-07-29 00:33:03 +02:00
#[ method(mouseMoved:) ]
2023-07-08 22:36:42 +03:00
fn mouse_moved ( & self , event : & NSEvent ) {
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2018-05-17 21:28:30 -04:00
}
2023-07-29 00:33:03 +02:00
#[ method(mouseDragged:) ]
2023-07-08 22:36:42 +03:00
fn mouse_dragged ( & self , event : & NSEvent ) {
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2018-05-17 21:28:30 -04:00
}
2018-06-11 11:16:39 -04:00
2023-07-29 00:33:03 +02:00
#[ method(rightMouseDragged:) ]
2023-07-08 22:36:42 +03:00
fn right_mouse_dragged ( & self , event : & NSEvent ) {
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2022-09-02 19:38:32 +02:00
}
2020-04-26 20:42:45 +00:00
2023-07-29 00:33:03 +02:00
#[ method(otherMouseDragged:) ]
2023-07-08 22:36:42 +03:00
fn other_mouse_dragged ( & self , event : & NSEvent ) {
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2022-09-02 19:38:32 +02:00
}
2020-07-26 22:16:21 +00:00
2023-07-29 00:33:03 +02:00
#[ method(mouseEntered:) ]
2022-09-08 16:45:29 +02:00
fn mouse_entered ( & self , _event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " mouseEntered: " ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::CursorEntered {
device_id : DEVICE_ID ,
} ) ;
2022-09-02 19:38:32 +02:00
}
2019-05-01 17:03:30 -06:00
2023-07-29 00:33:03 +02:00
#[ method(mouseExited:) ]
2022-09-08 16:45:29 +02:00
fn mouse_exited ( & self , _event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " mouseExited: " ) ;
2019-05-01 17:03:30 -06:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::CursorLeft {
device_id : DEVICE_ID ,
} ) ;
2022-09-02 19:38:32 +02:00
}
2022-08-16 18:20:06 +03:00
2023-07-29 00:33:03 +02:00
#[ method(scrollWheel:) ]
2023-07-08 22:36:42 +03:00
fn scroll_wheel ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " scrollWheel: " ) ;
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2022-09-02 19:38:32 +02:00
2022-09-08 16:45:29 +02:00
let delta = {
2023-12-23 20:58:38 +01:00
let ( x , y ) = unsafe { ( event . scrollingDeltaX ( ) , event . scrollingDeltaY ( ) ) } ;
if unsafe { event . hasPreciseScrollingDeltas ( ) } {
2022-09-08 16:45:29 +02:00
let delta = LogicalPosition ::new ( x , y ) . to_physical ( self . scale_factor ( ) ) ;
MouseScrollDelta ::PixelDelta ( delta )
} else {
MouseScrollDelta ::LineDelta ( x as f32 , y as f32 )
}
} ;
2022-09-02 19:38:32 +02:00
2022-09-08 16:45:29 +02:00
// The "momentum phase," if any, has higher priority than touch phase (the two should
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
// report the touch phase.
2023-12-23 20:58:38 +01:00
#[ allow(non_upper_case_globals) ]
let phase = match unsafe { event . momentumPhase ( ) } {
NSEventPhaseMayBegin | NSEventPhaseBegan = > {
2022-09-08 16:45:29 +02:00
TouchPhase ::Started
}
2023-12-23 20:58:38 +01:00
NSEventPhaseEnded | NSEventPhaseCancelled = > {
2022-09-08 16:45:29 +02:00
TouchPhase ::Ended
}
2023-12-23 20:58:38 +01:00
_ = > match unsafe { event . phase ( ) } {
NSEventPhaseMayBegin | NSEventPhaseBegan = > {
2022-09-08 16:45:29 +02:00
TouchPhase ::Started
}
2023-12-23 20:58:38 +01:00
NSEventPhaseEnded | NSEventPhaseCancelled = > {
2022-09-08 16:45:29 +02:00
TouchPhase ::Ended
}
_ = > TouchPhase ::Moved ,
} ,
} ;
2022-08-16 18:20:06 +03:00
2023-05-28 20:02:59 +02:00
self . update_modifiers ( event , false ) ;
2022-08-16 18:20:06 +03:00
2023-01-22 23:29:38 +01:00
self . queue_device_event ( DeviceEvent ::MouseWheel { delta } ) ;
self . queue_event ( WindowEvent ::MouseWheel {
device_id : DEVICE_ID ,
delta ,
phase ,
} ) ;
2022-09-02 19:38:32 +02:00
}
2022-08-16 18:20:06 +03:00
2023-07-29 00:33:03 +02:00
#[ method(magnifyWithEvent:) ]
2022-09-08 16:45:29 +02:00
fn magnify_with_event ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " magnifyWithEvent: " ) ;
2023-12-23 20:58:38 +01:00
#[ allow(non_upper_case_globals) ]
let phase = match unsafe { event . phase ( ) } {
NSEventPhaseBegan = > TouchPhase ::Started ,
NSEventPhaseChanged = > TouchPhase ::Moved ,
NSEventPhaseCancelled = > TouchPhase ::Cancelled ,
NSEventPhaseEnded = > TouchPhase ::Ended ,
2022-09-08 16:45:29 +02:00
_ = > return ,
} ;
2022-09-02 19:38:32 +02:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::TouchpadMagnify {
device_id : DEVICE_ID ,
2023-12-23 20:58:38 +01:00
delta : unsafe { event . magnification ( ) } ,
2023-01-22 23:29:38 +01:00
phase ,
} ) ;
2022-09-02 19:38:32 +02:00
}
2022-08-16 18:20:06 +03:00
2023-07-29 00:33:03 +02:00
#[ method(smartMagnifyWithEvent:) ]
2023-01-21 17:35:07 +01:00
fn smart_magnify_with_event ( & self , _event : & NSEvent ) {
trace_scope! ( " smartMagnifyWithEvent: " ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::SmartMagnify {
device_id : DEVICE_ID ,
} ) ;
2023-01-21 17:35:07 +01:00
}
2023-07-29 00:33:03 +02:00
#[ method(rotateWithEvent:) ]
2022-09-08 16:45:29 +02:00
fn rotate_with_event ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " rotateWithEvent: " ) ;
2023-12-23 20:58:38 +01:00
#[ allow(non_upper_case_globals) ]
let phase = match unsafe { event . phase ( ) } {
NSEventPhaseBegan = > TouchPhase ::Started ,
NSEventPhaseChanged = > TouchPhase ::Moved ,
NSEventPhaseCancelled = > TouchPhase ::Cancelled ,
NSEventPhaseEnded = > TouchPhase ::Ended ,
2022-09-08 16:45:29 +02:00
_ = > return ,
} ;
2022-09-02 19:38:32 +02:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::TouchpadRotate {
device_id : DEVICE_ID ,
2023-12-23 20:58:38 +01:00
delta : unsafe { event . rotation ( ) } ,
2023-01-22 23:29:38 +01:00
phase ,
} ) ;
2022-09-02 19:38:32 +02:00
}
2022-08-16 18:20:06 +03:00
2023-07-29 00:33:03 +02:00
#[ method(pressureChangeWithEvent:) ]
2023-07-08 22:36:42 +03:00
fn pressure_change_with_event ( & self , event : & NSEvent ) {
2022-09-02 19:38:32 +02:00
trace_scope! ( " pressureChangeWithEvent: " ) ;
2020-04-26 20:42:45 +00:00
2022-09-08 16:45:29 +02:00
self . mouse_motion ( event ) ;
2019-05-01 17:03:30 -06:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::TouchpadPressure {
device_id : DEVICE_ID ,
2023-12-23 20:58:38 +01:00
pressure : unsafe { event . pressure ( ) } ,
stage : unsafe { event . stage ( ) } as i64 ,
2023-01-22 23:29:38 +01:00
} ) ;
2022-09-02 19:38:32 +02:00
}
2019-05-01 17:03:30 -06:00
2022-09-02 19:38:32 +02:00
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs.
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
2023-07-29 00:33:03 +02:00
#[ method(_wantsKeyDownForEvent:) ]
2022-09-08 16:45:29 +02:00
fn wants_key_down_for_event ( & self , _event : & NSEvent ) -> bool {
2022-09-02 19:38:32 +02:00
trace_scope! ( " _wantsKeyDownForEvent: " ) ;
true
}
2021-04-30 17:30:09 +08:00
2023-07-29 00:33:03 +02:00
#[ method(acceptsFirstMouse:) ]
2022-09-08 16:45:29 +02:00
fn accepts_first_mouse ( & self , _event : & NSEvent ) -> bool {
2022-09-02 19:38:32 +02:00
trace_scope! ( " acceptsFirstMouse: " ) ;
2023-12-23 18:04:24 +01:00
self . ivars ( ) . accepts_first_mouse
2022-09-02 19:38:32 +02:00
}
2022-09-02 18:46:18 +02:00
}
2022-09-02 19:38:32 +02:00
) ;
2022-09-02 18:46:18 +02:00
impl WinitView {
2023-07-29 00:33:03 +02:00
pub ( super ) fn new ( window : & WinitWindow , accepts_first_mouse : bool ) -> Id < Self > {
2023-12-23 20:58:38 +01:00
let mtm = MainThreadMarker ::from ( window ) ;
let this = mtm . alloc ( ) . set_ivars ( ViewState {
2023-12-23 18:04:24 +01:00
accepts_first_mouse ,
_ns_window : WeakId ::new ( & window . retain ( ) ) ,
.. Default ::default ( )
} ) ;
let this : Id < Self > = unsafe { msg_send_id! [ super ( this ) , init ] } ;
this . setPostsFrameChangedNotifications ( true ) ;
let notification_center : & AnyObject =
unsafe { msg_send! [ class! ( NSNotificationCenter ) , defaultCenter ] } ;
// About frame change
let frame_did_change_notification_name =
NSString ::from_str ( " NSViewFrameDidChangeNotification " ) ;
#[ allow(clippy::let_unit_value) ]
2022-09-13 21:11:18 +02:00
unsafe {
2023-12-23 18:04:24 +01:00
let _ : ( ) = msg_send! [
notification_center ,
addObserver : & * this ,
selector : sel ! ( frameDidChange :) ,
name : & * frame_did_change_notification_name ,
object : & * this ,
] ;
2022-09-13 21:11:18 +02:00
}
2023-12-23 18:04:24 +01:00
* this . ivars ( ) . input_source . borrow_mut ( ) = this . current_input_source ( ) ;
this
2022-09-08 16:45:29 +02:00
}
2023-07-29 00:33:03 +02:00
fn window ( & self ) -> Id < WinitWindow > {
2022-09-08 16:45:29 +02:00
// 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] }
2023-12-23 18:04:24 +01:00
self . ivars ( )
. _ns_window
. load ( )
. expect ( " view to have a window " )
2022-09-08 16:45:29 +02:00
}
fn window_id ( & self ) -> WindowId {
2023-03-14 13:27:41 +03:00
WindowId ( self . window ( ) . id ( ) )
2022-09-08 16:45:29 +02:00
}
2023-07-31 00:39:01 +04:00
fn queue_event ( & self , event : WindowEvent ) {
2023-01-22 23:29:38 +01:00
let event = Event ::WindowEvent {
window_id : self . window_id ( ) ,
event ,
} ;
2023-08-27 17:04:39 +02:00
AppState ::queue_event ( event ) ;
2023-01-22 23:29:38 +01:00
}
fn queue_device_event ( & self , event : DeviceEvent ) {
let event = Event ::DeviceEvent {
device_id : DEVICE_ID ,
event ,
} ;
2023-08-27 17:04:39 +02:00
AppState ::queue_event ( event ) ;
2023-01-22 23:29:38 +01:00
}
2022-09-08 16:45:29 +02:00
fn scale_factor ( & self ) -> f64 {
self . window ( ) . backingScaleFactor ( ) as f64
}
fn is_ime_enabled ( & self ) -> bool {
2023-12-23 18:04:24 +01:00
! matches! ( self . ivars ( ) . ime_state . get ( ) , ImeState ::Disabled )
2022-09-08 16:45:29 +02:00
}
2022-09-02 18:46:18 +02:00
fn current_input_source ( & self ) -> String {
2022-09-08 16:45:29 +02:00
self . inputContext ( )
. expect ( " input context " )
. selectedKeyboardInputSource ( )
. map ( | input_source | input_source . to_string ( ) )
2023-10-08 01:21:46 +02:00
. unwrap_or_default ( )
2022-09-08 16:45:29 +02:00
}
2023-07-29 00:33:03 +02:00
pub ( super ) fn set_cursor_icon ( & self , icon : Id < NSCursor > ) {
2023-12-23 18:04:24 +01:00
let mut cursor_state = self . ivars ( ) . cursor_state . borrow_mut ( ) ;
2023-07-08 22:36:42 +03:00
cursor_state . cursor = icon ;
}
/// Set whether the cursor should be visible or not.
///
/// Returns whether the state changed.
pub ( super ) fn set_cursor_visible ( & self , visible : bool ) -> bool {
2023-12-23 18:04:24 +01:00
let mut cursor_state = self . ivars ( ) . cursor_state . borrow_mut ( ) ;
2023-07-08 22:36:42 +03:00
if visible ! = cursor_state . visible {
cursor_state . visible = visible ;
true
} else {
false
}
}
pub ( super ) fn set_ime_allowed ( & self , ime_allowed : bool ) {
2023-12-23 18:04:24 +01:00
if self . ivars ( ) . ime_allowed . get ( ) = = ime_allowed {
2022-09-08 16:45:29 +02:00
return ;
}
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_allowed . set ( ime_allowed ) ;
if self . ivars ( ) . ime_allowed . get ( ) {
2022-09-08 16:45:29 +02:00
return ;
}
// Clear markedText
2023-12-23 18:04:24 +01:00
* self . ivars ( ) . marked_text . borrow_mut ( ) = NSMutableAttributedString ::new ( ) ;
2022-09-08 16:45:29 +02:00
2023-12-23 18:04:24 +01:00
if self . ivars ( ) . ime_state . get ( ) ! = ImeState ::Disabled {
self . ivars ( ) . ime_state . set ( ImeState ::Disabled ) ;
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::Ime ( Ime ::Disabled ) ) ;
2022-09-08 16:45:29 +02:00
}
}
2023-06-22 19:12:14 +00:00
pub ( super ) fn set_ime_cursor_area (
2023-07-08 22:36:42 +03:00
& self ,
2023-06-22 19:12:14 +00:00
position : LogicalPosition < f64 > ,
size : LogicalSize < f64 > ,
) {
2023-12-23 18:04:24 +01:00
self . ivars ( ) . ime_position . set ( position ) ;
self . ivars ( ) . ime_size . set ( size ) ;
2022-09-08 16:45:29 +02:00
let input_context = self . inputContext ( ) . expect ( " input context " ) ;
input_context . invalidateCharacterCoordinates ( ) ;
}
2023-07-08 22:36:42 +03:00
/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
pub ( super ) fn reset_modifiers ( & self ) {
2023-12-23 18:04:24 +01:00
if ! self . ivars ( ) . modifiers . get ( ) . state ( ) . is_empty ( ) {
self . ivars ( ) . modifiers . set ( Modifiers ::default ( ) ) ;
self . queue_event ( WindowEvent ::ModifiersChanged ( self . ivars ( ) . modifiers . get ( ) ) ) ;
2023-07-08 22:36:42 +03:00
}
}
/// Update modifiers if `event` has something different
fn update_modifiers ( & self , ns_event : & NSEvent , is_flags_changed_event : bool ) {
2023-05-28 20:02:59 +02:00
use ElementState ::{ Pressed , Released } ;
let current_modifiers = event_mods ( ns_event ) ;
2023-12-23 18:04:24 +01:00
let prev_modifiers = self . ivars ( ) . modifiers . get ( ) ;
self . ivars ( ) . modifiers . set ( current_modifiers ) ;
2023-05-28 20:02:59 +02:00
// This function was called form the flagsChanged event, which is triggered
// when the user presses/releases a modifier even if the same kind of modifier
2023-10-17 05:59:48 +04:00
// has already been pressed.
//
// When flags changed event has key code of zero it means that event doesn't carry any key
// event, thus we can't generate regular presses based on that. The `ModifiersChanged`
// later will work though, since the flags are attached to the event and contain valid
// information.
2023-11-17 15:56:03 +04:00
' send_event : {
2023-12-23 20:58:38 +01:00
if is_flags_changed_event & & unsafe { ns_event . keyCode ( ) } ! = 0 {
let scancode = unsafe { ns_event . keyCode ( ) } ;
2023-11-17 15:56:03 +04:00
let physical_key = PhysicalKey ::from_scancode ( 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 ) ;
// Ignore processing of unkown modifiers because we can't determine whether
// it was pressed or release reliably.
let Some ( event_modifier ) = key_to_modifier ( & key ) else {
break 'send_event ;
} ;
event . physical_key = physical_key ;
event . logical_key = key . clone ( ) ;
event . location = code_to_location ( physical_key ) ;
let location_mask = ModLocationMask ::from_location ( event . location ) ;
2023-12-23 18:04:24 +01:00
let mut phys_mod_state = self . ivars ( ) . phys_modifiers . borrow_mut ( ) ;
2023-11-17 15:56:03 +04:00
let phys_mod = phys_mod_state
. entry ( key )
. or_insert ( ModLocationMask ::empty ( ) ) ;
let is_active = current_modifiers . state ( ) . contains ( event_modifier ) ;
let mut events = VecDeque ::with_capacity ( 2 ) ;
// There is no API for getting whether the button was pressed or released
// during this event. For this reason we have to do a bit of magic below
// to come up with a good guess whether this key was pressed or released.
// (This is not trivial because there are multiple buttons that may affect
// the same modifier)
if ! is_active {
event . state = Released ;
if phys_mod . contains ( ModLocationMask ::LEFT ) {
let mut event = event . clone ( ) ;
event . location = KeyLocation ::Left ;
event . physical_key = get_left_modifier_code ( & event . logical_key ) . into ( ) ;
events . push_back ( WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
event ,
is_synthetic : false ,
} ) ;
}
if phys_mod . contains ( ModLocationMask ::RIGHT ) {
event . location = KeyLocation ::Right ;
event . physical_key = get_right_modifier_code ( & event . logical_key ) . into ( ) ;
events . push_back ( WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
event ,
is_synthetic : false ,
} ) ;
}
* phys_mod = ModLocationMask ::empty ( ) ;
} else {
if * phys_mod = = location_mask {
// Here we hit a contradiction:
// The modifier state was "changed" to active,
// yet the only pressed modifier key was the one that we
// just got a change event for.
// This seemingly means that the only pressed modifier is now released,
// but at the same time the modifier became active.
//
// But this scenario is possible if we released modifiers
// while the application was not in focus. (Because we don't
// get informed of modifier key events while the application
// is not focused)
// In this case we prioritize the information
// about the current modifier state which means
// that the button was pressed.
event . state = Pressed ;
} else {
phys_mod . toggle ( location_mask ) ;
let is_pressed = phys_mod . contains ( location_mask ) ;
event . state = if is_pressed { Pressed } else { Released } ;
}
2023-05-28 20:02:59 +02:00
events . push_back ( WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
event ,
is_synthetic : false ,
} ) ;
}
2023-11-17 15:56:03 +04:00
drop ( phys_mod_state ) ;
2022-09-08 16:45:29 +02:00
2023-11-17 15:56:03 +04:00
for event in events {
self . queue_event ( event ) ;
}
2023-05-28 20:02:59 +02:00
}
}
if prev_modifiers = = current_modifiers {
return ;
2022-09-08 16:45:29 +02:00
}
2023-05-28 20:02:59 +02:00
2023-12-23 18:04:24 +01:00
self . queue_event ( WindowEvent ::ModifiersChanged ( self . ivars ( ) . modifiers . get ( ) ) ) ;
2022-09-08 16:45:29 +02:00
}
2023-07-08 22:36:42 +03:00
fn mouse_click ( & self , event : & NSEvent , button_state : ElementState ) {
2023-01-18 04:32:34 +01:00
let button = mouse_button ( event ) ;
2023-05-28 20:02:59 +02:00
self . update_modifiers ( event , false ) ;
2022-09-08 16:45:29 +02:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::MouseInput {
device_id : DEVICE_ID ,
state : button_state ,
button ,
} ) ;
2022-09-08 16:45:29 +02:00
}
2023-07-08 22:36:42 +03:00
fn mouse_motion ( & self , event : & NSEvent ) {
2023-12-23 20:58:38 +01:00
let window_point = unsafe { event . locationInWindow ( ) } ;
2022-09-08 16:45:29 +02:00
let view_point = self . convertPoint_fromView ( window_point , None ) ;
let view_rect = self . frame ( ) ;
if view_point . x . is_sign_negative ( )
| | view_point . y . is_sign_negative ( )
| | view_point . x > view_rect . size . width
| | view_point . y > view_rect . size . height
{
2023-12-23 20:58:38 +01:00
let mouse_buttons_down = unsafe { NSEvent ::pressedMouseButtons ( ) } ;
2022-09-08 16:45:29 +02:00
if mouse_buttons_down = = 0 {
// Point is outside of the client area (view) and no buttons are pressed
return ;
}
}
let x = view_point . x as f64 ;
let y = view_rect . size . height as f64 - view_point . y as f64 ;
let logical_position = LogicalPosition ::new ( x , y ) ;
2023-05-28 20:02:59 +02:00
self . update_modifiers ( event , false ) ;
2022-09-08 16:45:29 +02:00
2023-01-22 23:29:38 +01:00
self . queue_event ( WindowEvent ::CursorMoved {
device_id : DEVICE_ID ,
position : logical_position . to_physical ( self . scale_factor ( ) ) ,
} ) ;
2022-09-02 18:46:18 +02:00
}
2021-04-30 17:30:09 +08:00
}
2023-01-18 04:32:34 +01:00
/// Get the mouse button from the NSEvent.
fn mouse_button ( event : & NSEvent ) -> MouseButton {
// The buttonNumber property only makes sense for the mouse events:
// NSLeftMouse.../NSRightMouse.../NSOtherMouse...
// For the other events, it's always set to 0.
2023-06-16 11:51:09 +03:00
// MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons,
// but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications.
2023-12-23 20:58:38 +01:00
match unsafe { event . buttonNumber ( ) } {
2023-01-18 04:32:34 +01:00
0 = > MouseButton ::Left ,
1 = > MouseButton ::Right ,
2 = > MouseButton ::Middle ,
2023-06-16 11:51:09 +03:00
3 = > MouseButton ::Back ,
4 = > MouseButton ::Forward ,
2023-01-18 04:32:34 +01:00
n = > MouseButton ::Other ( n as u16 ) ,
}
}
2023-06-20 19:07:49 +00:00
// 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`.
2023-07-29 00:33:03 +02:00
fn replace_event ( event : & NSEvent , option_as_alt : OptionAsAlt ) -> Id < NSEvent > {
2023-06-20 19:07:49 +00:00
let ev_mods = event_mods ( event ) . state ;
let ignore_alt_characters = match option_as_alt {
2023-12-23 20:58:38 +01:00
OptionAsAlt ::OnlyLeft if lalt_pressed ( event ) = > true ,
OptionAsAlt ::OnlyRight if ralt_pressed ( event ) = > true ,
2023-06-20 19:07:49 +00:00
OptionAsAlt ::Both if ev_mods . alt_key ( ) = > true ,
_ = > false ,
} & & ! ev_mods . control_key ( )
& & ! ev_mods . super_key ( ) ;
if ignore_alt_characters {
2023-12-23 20:58:38 +01:00
let ns_chars = unsafe {
event
. charactersIgnoringModifiers ( )
. expect ( " expected characters to be non-null " )
} ;
unsafe {
NSEvent ::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode (
event . r#type ( ) ,
event . locationInWindow ( ) ,
event . modifierFlags ( ) ,
event . timestamp ( ) ,
event . windowNumber ( ) ,
None ,
& ns_chars ,
& ns_chars ,
event . isARepeat ( ) ,
event . keyCode ( ) ,
)
. unwrap ( )
}
2023-06-20 19:07:49 +00:00
} else {
event . copy ( )
}
}