2019-05-02 09:03:30 +10:00
use std ::{
boxed ::Box , collections ::VecDeque , os ::raw ::* , slice , str ,
sync ::{ Arc , Mutex , Weak } ,
} ;
use cocoa ::{
appkit ::{ NSApp , NSEvent , NSEventModifierFlags , NSEventPhase , NSView , NSWindow } ,
base ::{ id , nil } , foundation ::{ NSPoint , NSRect , NSSize , NSString , NSUInteger } ,
} ;
use objc ::{ declare ::ClassDecl , runtime ::{ BOOL , Class , NO , Object , Protocol , Sel , YES } } ;
use {
event ::{
DeviceEvent , ElementState , Event , KeyboardInput , MouseButton ,
MouseScrollDelta , TouchPhase , VirtualKeyCode , WindowEvent ,
} ,
window ::WindowId ,
} ;
use platform_impl ::platform ::{
app_state ::AppState , DEVICE_ID ,
event ::{ check_function_keys , event_mods , modifier_event , char_to_keycode , get_scancode , scancode_to_keycode } ,
util ::{ self , IdRef } , ffi ::* , window ::get_window_id ,
} ;
#[ derive(Default) ]
struct Modifiers {
shift_pressed : bool ,
ctrl_pressed : bool ,
win_pressed : bool ,
alt_pressed : bool ,
}
2018-05-18 11:28:30 +10:00
struct ViewState {
2019-06-11 09:09:38 +10:00
ns_window : id ,
2019-05-02 09:03:30 +10:00
pub cursor : Arc < Mutex < util ::Cursor > > ,
2018-06-15 09:42:18 +10:00
ime_spot : Option < ( f64 , f64 ) > ,
2018-05-18 11:28:30 +10:00
raw_characters : Option < String > ,
2018-10-18 13:03:26 +11:00
is_key_down : bool ,
2019-05-02 09:03:30 +10:00
modifiers : Modifiers ,
2018-05-18 11:28:30 +10:00
}
2019-06-11 09:09:38 +10:00
pub fn new_view ( ns_window : id ) -> ( IdRef , Weak < Mutex < util ::Cursor > > ) {
2018-12-29 07:29:29 +11:00
let cursor = Default ::default ( ) ;
let cursor_access = Arc ::downgrade ( & cursor ) ;
2018-05-22 23:05:33 +10:00
let state = ViewState {
2019-06-11 09:09:38 +10:00
ns_window ,
2018-12-29 07:29:29 +11:00
cursor ,
2018-05-22 23:05:33 +10:00
ime_spot : None ,
raw_characters : None ,
2018-10-18 13:03:26 +11:00
is_key_down : false ,
2019-05-02 09:03:30 +10:00
modifiers : Default ::default ( ) ,
2018-05-22 23:05:33 +10:00
} ;
2018-05-18 11:28:30 +10:00
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box ::into_raw ( Box ::new ( state ) ) as * mut c_void ;
2019-06-11 09:09:38 +10:00
let ns_view : id = msg_send! [ VIEW_CLASS . 0 , alloc ] ;
( IdRef ::new ( msg_send! [ ns_view , initWithWinit :state_ptr ] ) , cursor_access )
2018-05-18 11:28:30 +10:00
}
}
2019-06-11 09:09:38 +10:00
pub unsafe fn set_ime_position ( ns_view : id , input_context : id , x : f64 , y : f64 ) {
let state_ptr : * mut c_void = * ( * ns_view ) . get_mut_ivar ( " winitState " ) ;
2019-05-02 09:03:30 +10:00
let state = & mut * ( state_ptr as * mut ViewState ) ;
let content_rect = NSWindow ::contentRectForFrameRect_ (
2019-06-11 09:09:38 +10:00
state . ns_window ,
NSWindow ::frame ( state . ns_window ) ,
2019-05-02 09:03:30 +10:00
) ;
let base_x = content_rect . origin . x as f64 ;
let base_y = ( content_rect . origin . y + content_rect . size . height ) as f64 ;
state . ime_spot = Some ( ( base_x + x , base_y - y ) ) ;
let _ : ( ) = msg_send! [ input_context , invalidateCharacterCoordinates ] ;
2018-05-18 11:28:30 +10:00
}
struct ViewClass ( * const Class ) ;
unsafe impl Send for ViewClass { }
unsafe impl Sync for ViewClass { }
lazy_static! {
static ref VIEW_CLASS : ViewClass = unsafe {
2018-07-20 02:02:33 +10:00
let superclass = class! ( NSView ) ;
2018-05-18 11:28:30 +10:00
let mut decl = ClassDecl ::new ( " WinitView " , superclass ) . unwrap ( ) ;
2019-05-02 09:03:30 +10:00
decl . add_method (
sel! ( dealloc ) ,
dealloc as extern fn ( & Object , Sel ) ,
) ;
2018-05-18 11:28:30 +10:00
decl . add_method (
sel! ( initWithWinit :) ,
init_with_winit as extern fn ( & Object , Sel , * mut c_void ) -> id ,
) ;
2019-05-02 09:03:30 +10:00
decl . add_method (
sel! ( viewDidMoveToWindow ) ,
view_did_move_to_window as extern fn ( & Object , Sel ) ,
) ;
2018-12-28 07:16:58 +11:00
decl . add_method (
sel! ( drawRect :) ,
2019-05-02 09:03:30 +10:00
draw_rect as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( acceptsFirstResponder ) ,
accepts_first_responder as extern fn ( & Object , Sel ) -> BOOL ,
) ;
decl . add_method (
sel! ( touchBar ) ,
touch_bar as extern fn ( & Object , Sel ) -> BOOL ,
2018-12-28 07:16:58 +11:00
) ;
2018-12-29 07:29:29 +11:00
decl . add_method (
sel! ( resetCursorRects ) ,
reset_cursor_rects as extern fn ( & Object , Sel ) ,
) ;
2019-05-02 09:03:30 +10:00
decl . add_method (
sel! ( hasMarkedText ) ,
has_marked_text as extern fn ( & Object , Sel ) -> BOOL ,
) ;
2018-05-18 11:28:30 +10:00
decl . add_method (
sel! ( markedRange ) ,
marked_range as extern fn ( & Object , Sel ) -> NSRange ,
) ;
2019-05-02 09:03:30 +10:00
decl . add_method (
sel! ( selectedRange ) ,
selected_range as extern fn ( & Object , Sel ) -> NSRange ,
) ;
2018-05-18 11:28:30 +10:00
decl . add_method (
sel! ( setMarkedText :selectedRange :replacementRange :) ,
set_marked_text as extern fn ( & mut Object , Sel , id , NSRange , NSRange ) ,
) ;
2019-05-02 09:03:30 +10:00
decl . add_method (
sel! ( unmarkText ) ,
unmark_text as extern fn ( & Object , Sel ) ,
) ;
2018-05-18 11:28:30 +10:00
decl . add_method (
sel! ( validAttributesForMarkedText ) ,
valid_attributes_for_marked_text as extern fn ( & Object , Sel ) -> id ,
) ;
decl . add_method (
sel! ( attributedSubstringForProposedRange :actualRange :) ,
2019-05-02 09:03:30 +10:00
attributed_substring_for_proposed_range as extern fn ( & Object , Sel , NSRange , * mut c_void ) -> id ,
2018-05-18 11:28:30 +10:00
) ;
decl . add_method (
sel! ( insertText :replacementRange :) ,
insert_text as extern fn ( & Object , Sel , id , NSRange ) ,
) ;
decl . add_method (
sel! ( characterIndexForPoint :) ,
character_index_for_point as extern fn ( & Object , Sel , NSPoint ) -> NSUInteger ,
) ;
decl . add_method (
sel! ( firstRectForCharacterRange :actualRange :) ,
2019-05-02 09:03:30 +10:00
first_rect_for_character_range as extern fn ( & Object , Sel , NSRange , * mut c_void ) -> NSRect ,
2018-05-18 11:28:30 +10:00
) ;
decl . add_method (
sel! ( doCommandBySelector :) ,
do_command_by_selector as extern fn ( & Object , Sel , Sel ) ,
) ;
2019-05-02 09:03:30 +10:00
decl . add_method (
sel! ( keyDown :) ,
key_down as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( keyUp :) ,
key_up as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( flagsChanged :) ,
flags_changed as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( insertTab :) ,
insert_tab as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( insertBackTab :) ,
insert_back_tab as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( mouseDown :) ,
mouse_down as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( mouseUp :) ,
mouse_up as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( rightMouseDown :) ,
right_mouse_down as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( rightMouseUp :) ,
right_mouse_up as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( otherMouseDown :) ,
other_mouse_down as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( otherMouseUp :) ,
other_mouse_up as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( mouseMoved :) ,
mouse_moved as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( mouseDragged :) ,
mouse_dragged as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( rightMouseDragged :) ,
right_mouse_dragged as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( otherMouseDragged :) ,
other_mouse_dragged as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( mouseEntered :) ,
mouse_entered as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( mouseExited :) ,
mouse_exited as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( scrollWheel :) ,
scroll_wheel as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( pressureChangeWithEvent :) ,
pressure_change_with_event as extern fn ( & Object , Sel , id ) ,
) ;
decl . add_method (
sel! ( _wantsKeyDownForEvent :) ,
wants_key_down_for_event as extern fn ( & Object , Sel , id ) -> BOOL ,
) ;
decl . add_method (
sel! ( cancelOperation :) ,
cancel_operation as extern fn ( & Object , Sel , id ) ,
) ;
2018-05-18 11:28:30 +10:00
decl . add_ivar ::< * mut c_void > ( " winitState " ) ;
decl . add_ivar ::< id > ( " markedText " ) ;
let protocol = Protocol ::get ( " NSTextInputClient " ) . unwrap ( ) ;
decl . add_protocol ( & protocol ) ;
ViewClass ( decl . register ( ) )
} ;
}
extern fn dealloc ( this : & Object , _sel : Sel ) {
unsafe {
let state : * mut c_void = * this . get_ivar ( " winitState " ) ;
let marked_text : id = * this . get_ivar ( " markedText " ) ;
let _ : ( ) = msg_send! [ marked_text , release ] ;
Box ::from_raw ( state as * mut ViewState ) ;
}
}
extern fn init_with_winit ( this : & Object , _sel : Sel , state : * mut c_void ) -> id {
unsafe {
let this : id = msg_send! [ this , init ] ;
if this ! = nil {
( * this ) . set_ivar ( " winitState " , state ) ;
let marked_text = < id as NSMutableAttributedString > ::init (
NSMutableAttributedString ::alloc ( nil ) ,
) ;
( * this ) . set_ivar ( " markedText " , marked_text ) ;
}
this
}
}
2019-05-02 09:03:30 +10:00
extern fn view_did_move_to_window ( this : & Object , _sel : Sel ) {
trace! ( " Triggered `viewDidMoveToWindow` " ) ;
unsafe {
let rect : NSRect = msg_send! [ this , visibleRect ] ;
let _ : ( ) = msg_send! [ this ,
addTrackingRect :rect
owner :this
userData :nil
assumeInside :NO
] ;
}
trace! ( " Completed `viewDidMoveToWindow` " ) ;
}
extern fn draw_rect ( this : & Object , _sel : Sel , rect : id ) {
2018-12-28 07:16:58 +11:00
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
2019-06-11 09:09:38 +10:00
AppState ::queue_redraw ( WindowId ( get_window_id ( state . ns_window ) ) ) ;
2018-12-28 07:16:58 +11:00
let superclass = util ::superclass ( this ) ;
let ( ) = msg_send! [ super ( this , superclass ) , drawRect :rect ] ;
}
}
2019-05-02 09:03:30 +10:00
extern fn accepts_first_responder ( _this : & Object , _sel : Sel ) -> BOOL {
YES
}
// 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`
extern fn touch_bar ( _this : & Object , _sel : Sel ) -> BOOL {
NO
}
2018-12-29 07:29:29 +11:00
extern fn reset_cursor_rects ( this : & Object , _sel : Sel ) {
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let bounds : NSRect = msg_send! [ this , bounds ] ;
let cursor = state . cursor . lock ( ) . unwrap ( ) . load ( ) ;
let _ : ( ) = msg_send! [ this ,
addCursorRect :bounds
cursor :cursor
] ;
}
}
2019-05-02 09:03:30 +10:00
2018-05-18 11:28:30 +10:00
extern fn has_marked_text ( this : & Object , _sel : Sel ) -> BOOL {
unsafe {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `hasMarkedText` " ) ;
2018-05-18 11:28:30 +10:00
let marked_text : id = * this . get_ivar ( " markedText " ) ;
2019-05-02 09:03:30 +10:00
trace! ( " Completed `hasMarkedText` " ) ;
2018-05-18 11:28:30 +10:00
( marked_text . length ( ) > 0 ) as i8
}
}
extern fn marked_range ( this : & Object , _sel : Sel ) -> NSRange {
unsafe {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `markedRange` " ) ;
2018-05-18 11:28:30 +10:00
let marked_text : id = * this . get_ivar ( " markedText " ) ;
let length = marked_text . length ( ) ;
2019-05-02 09:03:30 +10:00
trace! ( " Completed `markedRange` " ) ;
2018-05-18 11:28:30 +10:00
if length > 0 {
NSRange ::new ( 0 , length - 1 )
} else {
util ::EMPTY_RANGE
}
}
}
extern fn selected_range ( _this : & Object , _sel : Sel ) -> NSRange {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `selectedRange` " ) ;
trace! ( " Completed `selectedRange` " ) ;
2018-05-18 11:28:30 +10:00
util ::EMPTY_RANGE
}
extern fn set_marked_text (
this : & mut Object ,
_sel : Sel ,
string : id ,
_selected_range : NSRange ,
_replacement_range : NSRange ,
) {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `setMarkedText` " ) ;
2018-05-18 11:28:30 +10:00
unsafe {
let marked_text_ref : & mut id = this . get_mut_ivar ( " markedText " ) ;
let _ : ( ) = msg_send! [ ( * marked_text_ref ) , release ] ;
let marked_text = NSMutableAttributedString ::alloc ( nil ) ;
2018-07-20 02:02:33 +10:00
let has_attr = msg_send! [ string , isKindOfClass :class ! ( NSAttributedString ) ] ;
2018-05-18 11:28:30 +10:00
if has_attr {
marked_text . initWithAttributedString ( string ) ;
} else {
marked_text . initWithString ( string ) ;
} ;
* marked_text_ref = marked_text ;
}
2019-05-02 09:03:30 +10:00
trace! ( " Completed `setMarkedText` " ) ;
2018-05-18 11:28:30 +10:00
}
extern fn unmark_text ( this : & Object , _sel : Sel ) {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `unmarkText` " ) ;
2018-05-18 11:28:30 +10:00
unsafe {
let marked_text : id = * this . get_ivar ( " markedText " ) ;
let mutable_string = marked_text . mutableString ( ) ;
let _ : ( ) = msg_send! [ mutable_string , setString :" " ] ;
let input_context : id = msg_send! [ this , inputContext ] ;
let _ : ( ) = msg_send! [ input_context , discardMarkedText ] ;
}
2019-05-02 09:03:30 +10:00
trace! ( " Completed `unmarkText` " ) ;
2018-05-18 11:28:30 +10:00
}
extern fn valid_attributes_for_marked_text ( _this : & Object , _sel : Sel ) -> id {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `validAttributesForMarkedText` " ) ;
trace! ( " Completed `validAttributesForMarkedText` " ) ;
2018-07-20 02:02:33 +10:00
unsafe { msg_send! [ class! ( NSArray ) , array ] }
2018-05-18 11:28:30 +10:00
}
extern fn attributed_substring_for_proposed_range (
_this : & Object ,
_sel : Sel ,
_range : NSRange ,
_actual_range : * mut c_void , // *mut NSRange
) -> id {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `attributedSubstringForProposedRange` " ) ;
trace! ( " Completed `attributedSubstringForProposedRange` " ) ;
2018-05-18 11:28:30 +10:00
nil
}
extern fn character_index_for_point ( _this : & Object , _sel : Sel , _point : NSPoint ) -> NSUInteger {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `characterIndexForPoint` " ) ;
trace! ( " Completed `characterIndexForPoint` " ) ;
2018-05-18 11:28:30 +10:00
0
}
extern fn first_rect_for_character_range (
this : & Object ,
_sel : Sel ,
_range : NSRange ,
_actual_range : * mut c_void , // *mut NSRange
) -> NSRect {
unsafe {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `firstRectForCharacterRange` " ) ;
2018-05-18 11:28:30 +10:00
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let ( x , y ) = state . ime_spot . unwrap_or_else ( | | {
let content_rect = NSWindow ::contentRectForFrameRect_ (
2019-06-11 09:09:38 +10:00
state . ns_window ,
NSWindow ::frame ( state . ns_window ) ,
2018-05-18 11:28:30 +10:00
) ;
let x = content_rect . origin . x ;
let y = util ::bottom_left_to_top_left ( content_rect ) ;
2018-06-15 09:42:18 +10:00
( x , y )
2018-05-18 11:28:30 +10:00
} ) ;
2019-05-02 09:03:30 +10:00
trace! ( " Completed `firstRectForCharacterRange` " ) ;
2018-05-18 11:28:30 +10:00
NSRect ::new (
NSPoint ::new ( x as _ , y as _ ) ,
NSSize ::new ( 0.0 , 0.0 ) ,
)
}
}
extern fn insert_text ( this : & Object , _sel : Sel , string : id , _replacement_range : NSRange ) {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `insertText` " ) ;
2018-05-18 11:28:30 +10:00
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
2018-07-20 02:02:33 +10:00
let has_attr = msg_send! [ string , isKindOfClass :class ! ( NSAttributedString ) ] ;
2018-05-18 11:28:30 +10:00
let characters = if has_attr {
// This is a *mut NSAttributedString
msg_send! [ string , string ]
} else {
// This is already a *mut NSString
string
} ;
let slice = slice ::from_raw_parts (
characters . UTF8String ( ) as * const c_uchar ,
characters . len ( ) ,
) ;
let string = str ::from_utf8_unchecked ( slice ) ;
2018-10-18 13:03:26 +11:00
state . is_key_down = true ;
2018-05-18 11:28:30 +10:00
// We don't need this now, but it's here if that changes.
2019-05-02 09:03:30 +10:00
//let event: id = msg_send![NSApp(), currentEvent];
2018-05-18 11:28:30 +10:00
let mut events = VecDeque ::with_capacity ( characters . len ( ) ) ;
for character in string . chars ( ) {
events . push_back ( Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2018-05-18 11:28:30 +10:00
event : WindowEvent ::ReceivedCharacter ( character ) ,
} ) ;
}
2019-05-02 09:03:30 +10:00
AppState ::queue_events ( events ) ;
2018-05-18 11:28:30 +10:00
}
2019-05-02 09:03:30 +10:00
trace! ( " Completed `insertText` " ) ;
2018-05-18 11:28:30 +10:00
}
extern fn do_command_by_selector ( this : & Object , _sel : Sel , command : Sel ) {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `doCommandBySelector` " ) ;
2018-05-18 11:28:30 +10: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.
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let mut events = VecDeque ::with_capacity ( 1 ) ;
if command = = sel! ( insertNewline :) {
// The `else` condition would emit the same character, but I'm keeping this here both...
// 1) as a reminder for how `doCommandBySelector` works
2018-05-22 23:05:33 +10:00
// 2) to make our use of carriage return explicit
2018-05-18 11:28:30 +10:00
events . push_back ( Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2018-05-22 23:05:33 +10:00
event : WindowEvent ::ReceivedCharacter ( '\r' ) ,
2018-05-18 11:28:30 +10:00
} ) ;
} else {
let raw_characters = state . raw_characters . take ( ) ;
if let Some ( raw_characters ) = raw_characters {
for character in raw_characters . chars ( ) {
events . push_back ( Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2018-05-18 11:28:30 +10:00
event : WindowEvent ::ReceivedCharacter ( character ) ,
} ) ;
}
}
} ;
2019-05-02 09:03:30 +10:00
AppState ::queue_events ( events ) ;
2018-05-18 11:28:30 +10:00
}
2019-05-02 09:03:30 +10:00
trace! ( " Completed `doCommandBySelector` " ) ;
2018-05-18 11:28:30 +10:00
}
2019-02-24 07:41:55 +11:00
fn get_characters ( event : id , ignore_modifiers : bool ) -> String {
2018-09-13 03:04:16 +10:00
unsafe {
2019-02-24 07:41:55 +11:00
let characters : id = if ignore_modifiers {
msg_send! [ event , charactersIgnoringModifiers ]
} else {
msg_send! [ event , characters ]
} ;
assert_ne! ( characters , nil ) ;
2018-09-13 03:04:16 +10:00
let slice = slice ::from_raw_parts (
characters . UTF8String ( ) as * const c_uchar ,
characters . len ( ) ,
) ;
2019-02-24 07:41:55 +11:00
2018-09-13 03:04:16 +10:00
let string = str ::from_utf8_unchecked ( slice ) ;
2019-02-24 07:41:55 +11:00
string . to_owned ( )
}
}
// Retrieves a layout-independent keycode given an event.
2019-05-02 09:03:30 +10:00
fn retrieve_keycode ( event : id ) -> Option < VirtualKeyCode > {
2019-02-24 07:41:55 +11:00
#[ inline ]
2019-05-02 09:03:30 +10:00
fn get_code ( ev : id , raw : bool ) -> Option < VirtualKeyCode > {
2019-02-24 07:41:55 +11:00
let characters = get_characters ( ev , raw ) ;
characters . chars ( ) . next ( ) . map_or ( None , | c | char_to_keycode ( c ) )
2018-09-13 03:04:16 +10:00
}
2019-02-24 07:41:55 +11:00
// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
// If we don't get a match, then we fall back to unmodified characters.
let code = get_code ( event , false )
. or_else ( | | {
get_code ( event , true )
} ) ;
// We've checked all layout related keys, so fall through to scancode.
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
//
// We're additionally checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
code . or_else ( | | {
let scancode = get_scancode ( event ) ;
scancode_to_keycode ( scancode )
. or_else ( | | {
check_function_keys ( & get_characters ( event , true ) )
} )
} )
2018-09-13 03:04:16 +10:00
}
2018-05-18 11:28:30 +10:00
extern fn key_down ( this : & Object , _sel : Sel , event : id ) {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `keyDown` " ) ;
2018-05-18 11:28:30 +10:00
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
2019-06-11 09:09:38 +10:00
let window_id = WindowId ( get_window_id ( state . ns_window ) ) ;
2019-02-24 07:41:55 +11:00
let characters = get_characters ( event , false ) ;
2018-05-18 11:28:30 +10:00
2019-02-24 07:41:55 +11:00
state . raw_characters = Some ( characters . clone ( ) ) ;
2018-09-13 03:04:16 +10:00
2019-02-24 07:41:55 +11:00
let scancode = get_scancode ( event ) as u32 ;
let virtual_keycode = retrieve_keycode ( event ) ;
2019-05-02 09:03:30 +10:00
2018-05-22 23:05:33 +10:00
let is_repeat = msg_send! [ event , isARepeat ] ;
2018-05-18 11:28:30 +10:00
let window_event = Event ::WindowEvent {
2018-05-22 23:05:33 +10:00
window_id ,
2018-05-18 11:28:30 +10:00
event : WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
input : KeyboardInput {
state : ElementState ::Pressed ,
scancode ,
virtual_keycode ,
modifiers : event_mods ( event ) ,
} ,
} ,
} ;
2019-05-02 09:03:30 +10:00
let pass_along = {
AppState ::queue_event ( window_event ) ;
2018-05-22 23:05:33 +10:00
// Emit `ReceivedCharacter` for key repeats
2019-05-02 09:03:30 +10:00
if is_repeat & & state . is_key_down {
2019-02-24 07:41:55 +11:00
for character in characters . chars ( ) {
2019-05-02 09:03:30 +10:00
AppState ::queue_event ( Event ::WindowEvent {
2018-05-22 23:05:33 +10:00
window_id ,
event : WindowEvent ::ReceivedCharacter ( character ) ,
2019-05-02 09:03:30 +10:00
} ) ;
2018-05-22 23:05:33 +10:00
}
2019-05-02 09:03:30 +10:00
false
2018-06-18 05:08:26 +10:00
} else {
2019-05-02 09:03:30 +10:00
true
2018-05-22 23:05:33 +10:00
}
2019-05-02 09:03:30 +10:00
} ;
if pass_along {
// Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do...
// So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some
// keys to generate twice as many characters.
let array : id = msg_send! [ class! ( NSArray ) , arrayWithObject :event ] ;
let _ : ( ) = msg_send! [ this , interpretKeyEvents :array ] ;
2018-05-18 11:28:30 +10:00
}
}
2019-05-02 09:03:30 +10:00
trace! ( " Completed `keyDown` " ) ;
2018-05-18 11:28:30 +10:00
}
extern fn key_up ( this : & Object , _sel : Sel , event : id ) {
2019-05-02 09:03:30 +10:00
trace! ( " Triggered `keyUp` " ) ;
2018-05-18 11:28:30 +10:00
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
2018-10-18 13:03:26 +11:00
state . is_key_down = false ;
2018-05-22 23:05:33 +10:00
2019-02-24 07:41:55 +11:00
let scancode = get_scancode ( event ) as u32 ;
let virtual_keycode = retrieve_keycode ( event ) ;
2018-09-13 03:04:16 +10:00
2018-05-18 11:28:30 +10:00
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2018-05-18 11:28:30 +10:00
event : WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
input : KeyboardInput {
state : ElementState ::Released ,
scancode ,
virtual_keycode ,
modifiers : event_mods ( event ) ,
} ,
} ,
} ;
2019-05-02 09:03:30 +10:00
AppState ::queue_event ( window_event ) ;
}
trace! ( " Completed `keyUp` " ) ;
}
extern fn flags_changed ( this : & Object , _sel : Sel , event : id ) {
trace! ( " Triggered `flagsChanged` " ) ;
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let mut events = VecDeque ::with_capacity ( 4 ) ;
if let Some ( window_event ) = modifier_event (
event ,
NSEventModifierFlags ::NSShiftKeyMask ,
state . modifiers . shift_pressed ,
) {
state . modifiers . shift_pressed = ! state . modifiers . shift_pressed ;
events . push_back ( window_event ) ;
}
if let Some ( window_event ) = modifier_event (
event ,
NSEventModifierFlags ::NSControlKeyMask ,
state . modifiers . ctrl_pressed ,
) {
state . modifiers . ctrl_pressed = ! state . modifiers . ctrl_pressed ;
events . push_back ( window_event ) ;
}
if let Some ( window_event ) = modifier_event (
event ,
NSEventModifierFlags ::NSCommandKeyMask ,
state . modifiers . win_pressed ,
) {
state . modifiers . win_pressed = ! state . modifiers . win_pressed ;
events . push_back ( window_event ) ;
}
if let Some ( window_event ) = modifier_event (
event ,
NSEventModifierFlags ::NSAlternateKeyMask ,
state . modifiers . alt_pressed ,
) {
state . modifiers . alt_pressed = ! state . modifiers . alt_pressed ;
events . push_back ( window_event ) ;
}
for event in events {
AppState ::queue_event ( Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event ,
} ) ;
2018-05-18 11:28:30 +10:00
}
}
2019-05-02 09:03:30 +10:00
trace! ( " Completed `flagsChanged` " ) ;
2018-05-18 11:28:30 +10:00
}
extern fn insert_tab ( this : & Object , _sel : Sel , _sender : id ) {
unsafe {
let window : id = msg_send! [ this , window ] ;
let first_responder : id = msg_send! [ window , firstResponder ] ;
let this_ptr = this as * const _ as * mut _ ;
if first_responder = = this_ptr {
let ( ) : _ = msg_send! [ window , selectNextKeyView :this ] ;
}
}
}
extern fn insert_back_tab ( this : & Object , _sel : Sel , _sender : id ) {
unsafe {
let window : id = msg_send! [ this , window ] ;
let first_responder : id = msg_send! [ window , firstResponder ] ;
let this_ptr = this as * const _ as * mut _ ;
if first_responder = = this_ptr {
let ( ) : _ = msg_send! [ window , selectPreviousKeyView :this ] ;
}
}
}
2018-06-12 01:16:39 +10:00
2019-05-02 09:03:30 +10:00
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
extern fn cancel_operation ( this : & Object , _sel : Sel , _sender : id ) {
trace! ( " Triggered `cancelOperation` " ) ;
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let scancode = 0x2f ;
let virtual_keycode = scancode_to_keycode ( scancode ) ;
debug_assert_eq! ( virtual_keycode , Some ( VirtualKeyCode ::Period ) ) ;
let event : id = msg_send! [ NSApp ( ) , currentEvent ] ;
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event : WindowEvent ::KeyboardInput {
device_id : DEVICE_ID ,
input : KeyboardInput {
state : ElementState ::Pressed ,
scancode : scancode as _ ,
virtual_keycode ,
modifiers : event_mods ( event ) ,
} ,
} ,
} ;
AppState ::queue_event ( window_event ) ;
}
trace! ( " Completed `cancelOperation` " ) ;
}
2018-06-12 01:16:39 +10:00
fn mouse_click ( this : & Object , event : id , button : MouseButton , button_state : ElementState ) {
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2018-06-12 01:16:39 +10:00
event : WindowEvent ::MouseInput {
device_id : DEVICE_ID ,
state : button_state ,
button ,
modifiers : event_mods ( event ) ,
} ,
} ;
2019-05-02 09:03:30 +10:00
AppState ::queue_event ( window_event ) ;
2018-06-12 01:16:39 +10:00
}
}
extern fn mouse_down ( this : & Object , _sel : Sel , event : id ) {
mouse_click ( this , event , MouseButton ::Left , ElementState ::Pressed ) ;
}
extern fn mouse_up ( this : & Object , _sel : Sel , event : id ) {
mouse_click ( this , event , MouseButton ::Left , ElementState ::Released ) ;
}
extern fn right_mouse_down ( this : & Object , _sel : Sel , event : id ) {
mouse_click ( this , event , MouseButton ::Right , ElementState ::Pressed ) ;
}
extern fn right_mouse_up ( this : & Object , _sel : Sel , event : id ) {
mouse_click ( this , event , MouseButton ::Right , ElementState ::Released ) ;
}
extern fn other_mouse_down ( this : & Object , _sel : Sel , event : id ) {
mouse_click ( this , event , MouseButton ::Middle , ElementState ::Pressed ) ;
}
extern fn other_mouse_up ( this : & Object , _sel : Sel , event : id ) {
mouse_click ( this , event , MouseButton ::Middle , ElementState ::Released ) ;
}
fn mouse_motion ( this : & Object , event : id ) {
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
// We have to do this to have access to the `NSView` trait...
let view : id = this as * const _ as * mut _ ;
let window_point = event . locationInWindow ( ) ;
let view_point = view . convertPoint_fromView_ ( window_point , nil ) ;
let view_rect = NSView ::frame ( view ) ;
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 {
// Point is outside of the client area (view)
return ;
}
2018-06-15 09:42:18 +10:00
let x = view_point . x as f64 ;
let y = view_rect . size . height as f64 - view_point . y as f64 ;
2018-06-12 01:16:39 +10:00
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2018-06-12 01:16:39 +10:00
event : WindowEvent ::CursorMoved {
device_id : DEVICE_ID ,
2018-06-15 09:42:18 +10:00
position : ( x , y ) . into ( ) ,
2018-06-12 01:16:39 +10:00
modifiers : event_mods ( event ) ,
} ,
} ;
2019-05-02 09:03:30 +10:00
AppState ::queue_event ( window_event ) ;
2018-06-12 01:16:39 +10:00
}
}
extern fn mouse_moved ( this : & Object , _sel : Sel , event : id ) {
mouse_motion ( this , event ) ;
}
extern fn mouse_dragged ( this : & Object , _sel : Sel , event : id ) {
mouse_motion ( this , event ) ;
}
extern fn right_mouse_dragged ( this : & Object , _sel : Sel , event : id ) {
mouse_motion ( this , event ) ;
}
extern fn other_mouse_dragged ( this : & Object , _sel : Sel , event : id ) {
mouse_motion ( this , event ) ;
}
2018-08-16 09:42:57 +10:00
2019-05-02 09:03:30 +10:00
extern fn mouse_entered ( this : & Object , _sel : Sel , event : id ) {
trace! ( " Triggered `mouseEntered` " ) ;
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let enter_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event : WindowEvent ::CursorEntered { device_id : DEVICE_ID } ,
} ;
let move_event = {
let window_point = event . locationInWindow ( ) ;
let view_point : NSPoint = msg_send! [ this ,
convertPoint :window_point
fromView :nil // convert from window coordinates
] ;
let view_rect : NSRect = msg_send! [ this , frame ] ;
let x = view_point . x as f64 ;
let y = ( view_rect . size . height - view_point . y ) as f64 ;
Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event : WindowEvent ::CursorMoved {
device_id : DEVICE_ID ,
position : ( x , y ) . into ( ) ,
modifiers : event_mods ( event ) ,
}
}
} ;
AppState ::queue_event ( enter_event ) ;
AppState ::queue_event ( move_event ) ;
}
trace! ( " Completed `mouseEntered` " ) ;
}
extern fn mouse_exited ( this : & Object , _sel : Sel , _event : id ) {
trace! ( " Triggered `mouseExited` " ) ;
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event : WindowEvent ::CursorLeft { device_id : DEVICE_ID } ,
} ;
AppState ::queue_event ( window_event ) ;
}
trace! ( " Completed `mouseExited` " ) ;
}
extern fn scroll_wheel ( this : & Object , _sel : Sel , event : id ) {
trace! ( " Triggered `scrollWheel` " ) ;
unsafe {
let delta = {
let ( x , y ) = ( event . scrollingDeltaX ( ) , event . scrollingDeltaY ( ) ) ;
if event . hasPreciseScrollingDeltas ( ) = = YES {
MouseScrollDelta ::PixelDelta ( ( x as f64 , y as f64 ) . into ( ) )
} else {
MouseScrollDelta ::LineDelta ( x as f32 , y as f32 )
}
} ;
let phase = match event . phase ( ) {
NSEventPhase ::NSEventPhaseMayBegin | NSEventPhase ::NSEventPhaseBegan = > TouchPhase ::Started ,
NSEventPhase ::NSEventPhaseEnded = > TouchPhase ::Ended ,
_ = > TouchPhase ::Moved ,
} ;
let device_event = Event ::DeviceEvent {
device_id : DEVICE_ID ,
event : DeviceEvent ::MouseWheel { delta } ,
} ;
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event : WindowEvent ::MouseWheel {
device_id : DEVICE_ID ,
delta ,
phase ,
modifiers : event_mods ( event ) ,
} ,
} ;
AppState ::queue_event ( device_event ) ;
AppState ::queue_event ( window_event ) ;
}
trace! ( " Completed `scrollWheel` " ) ;
}
extern fn pressure_change_with_event ( this : & Object , _sel : Sel , event : id ) {
trace! ( " Triggered `pressureChangeWithEvent` " ) ;
unsafe {
let state_ptr : * mut c_void = * this . get_ivar ( " winitState " ) ;
let state = & mut * ( state_ptr as * mut ViewState ) ;
let pressure = event . pressure ( ) ;
let stage = event . stage ( ) ;
let window_event = Event ::WindowEvent {
2019-06-11 09:09:38 +10:00
window_id : WindowId ( get_window_id ( state . ns_window ) ) ,
2019-05-02 09:03:30 +10:00
event : WindowEvent ::TouchpadPressure {
device_id : DEVICE_ID ,
pressure ,
stage ,
} ,
} ;
AppState ::queue_event ( window_event ) ;
}
trace! ( " Completed `pressureChangeWithEvent` " ) ;
}
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs.
2018-08-16 09:42:57 +10:00
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
2019-05-02 09:03:30 +10:00
extern fn wants_key_down_for_event ( _this : & Object , _sel : Sel , _event : id ) -> BOOL {
2018-08-16 09:42:57 +10:00
YES
}