From e517e468f8709a8e27e5f453ba4f128b07b6389e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 2 Sep 2022 19:38:32 +0200 Subject: [PATCH] Fix `declare_class!` indentation (#2461) * Fix NSWindow delegate indentation * Fix NSView delegate indentation --- src/platform_impl/macos/view.rs | 1685 ++++++++++---------- src/platform_impl/macos/window_delegate.rs | 669 ++++---- 2 files changed, 1178 insertions(+), 1176 deletions(-) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index afef01df..dc40d2be 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -289,7 +289,7 @@ fn mouse_motion(this: &Object, event: id) { } } -declare_class! { +declare_class!( #[derive(Debug)] #[allow(non_snake_case)] struct WinitView { @@ -303,879 +303,880 @@ declare_class! { } unsafe impl WinitView { -#[sel(dealloc)] -fn dealloc(&mut self) { - unsafe { - let marked_text: id = *self.ivar("markedText"); - let _: () = msg_send![marked_text, release]; - let state: *mut c_void = *self.ivar("winitState"); - drop(Box::from_raw(state as *mut ViewState)); - } -} - -#[sel(initWithWinit:)] -fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> { - let this: Option<&mut Self> = unsafe { msg_send![self, init] }; - this.map(|this| unsafe { - (*this).set_ivar("winitState", state); - let marked_text = - ::init(NSMutableAttributedString::alloc(nil)); - (*this).set_ivar("markedText", marked_text); - let _: () = msg_send![&mut *this, setPostsFrameChangedNotifications: true]; - - let notification_center: &Object = - msg_send![class!(NSNotificationCenter), defaultCenter]; - // About frame change - let frame_did_change_notification_name = - IdRef::new(NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification")); - let _: () = msg_send![ - notification_center, - addObserver: &*this - selector: sel!(frameDidChange:) - name: *frame_did_change_notification_name - object: &*this - ]; - - let winit_state = &mut *(state as *mut ViewState); - winit_state.input_source = this.current_input_source(); - this - }) -} - } - - unsafe impl WinitView { -#[sel(viewDidMoveToWindow)] -fn view_did_move_to_window(&self) { - trace_scope!("viewDidMoveToWindow"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(tracking_rect) = state.tracking_rect.take() { - let _: () = msg_send![self, removeTrackingRect: tracking_rect]; - } - - let rect: NSRect = msg_send![self, visibleRect]; - let tracking_rect: NSInteger = msg_send![ - self, - addTrackingRect: rect, - owner: self, - userData: ptr::null_mut::(), - assumeInside: false, - ]; - state.tracking_rect = Some(tracking_rect); - } -} - -#[sel(frameDidChange:)] -fn frame_did_change(&self, _event: id) { - trace_scope!("frameDidChange:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(tracking_rect) = state.tracking_rect.take() { - let _: () = msg_send![self, removeTrackingRect: tracking_rect]; - } - - let rect: NSRect = msg_send![self, visibleRect]; - let tracking_rect: NSInteger = msg_send![ - self, - addTrackingRect: rect, - owner: self, - userData: ptr::null_mut::(), - assumeInside: false, - ]; - state.tracking_rect = Some(tracking_rect); - - // 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::(state.get_scale_factor()); - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Resized(size), - })); - } -} - -#[sel(drawRect:)] -fn draw_rect(&self, rect: NSRect) { - trace_scope!("drawRect:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - AppState::handle_redraw(WindowId(get_window_id(state.ns_window))); - - let _: () = msg_send![super(self), drawRect: rect]; - } -} - -#[sel(acceptsFirstResponder)] -fn accepts_first_responder(&self) -> bool { - trace_scope!("acceptsFirstResponder"); - true -} - -// 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` -#[sel(touchBar)] -fn touch_bar(&self) -> bool { - trace_scope!("touchBar"); - false -} - -#[sel(resetCursorRects)] -fn reset_cursor_rects(&self) { - trace_scope!("resetCursorRects"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let bounds: NSRect = msg_send![self, bounds]; - let cursor_state = state.cursor_state.lock().unwrap(); - let cursor = if cursor_state.visible { - cursor_state.cursor.load() - } else { - util::invisible_cursor() - }; - let _: () = msg_send![self, - addCursorRect:bounds - cursor:cursor - ]; - } -} - } - - unsafe impl Protocol for WinitView { - -#[sel(hasMarkedText)] -fn has_marked_text(&self) -> bool { - trace_scope!("hasMarkedText"); - unsafe { - let marked_text: id = *self.ivar("markedText"); - marked_text.length() > 0 - } -} - -#[sel(markedRange)] -fn marked_range(&self) -> NSRange { - trace_scope!("markedRange"); - unsafe { - let marked_text: id = *self.ivar("markedText"); - let length = marked_text.length(); - if length > 0 { - NSRange::new(0, length) - } else { - util::EMPTY_RANGE - } - } -} - -#[sel(selectedRange)] -fn selected_range(&self) -> NSRange { - trace_scope!("selectedRange"); - util::EMPTY_RANGE -} - -#[sel(setMarkedText:selectedRange:replacementRange:)] -fn set_marked_text( - &mut self, - string: id, - _selected_range: NSRange, - _replacement_range: NSRange, -) { - trace_scope!("setMarkedText:selectedRange:replacementRange:"); - unsafe { - // Get pre-edit text - let marked_text_ref: &mut id = self.ivar_mut("markedText"); - - // Update markedText - let _: () = msg_send![*marked_text_ref, release]; - let marked_text = NSMutableAttributedString::alloc(nil); - let has_attr = msg_send![string, isKindOfClass: class!(NSAttributedString)]; - if has_attr { - marked_text.initWithAttributedString(string); - } else { - marked_text.initWithString(string); - }; - *marked_text_ref = marked_text; - - // Update ViewState with new marked text - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let preedit_string = id_to_string_lossy(string); - - // Notify IME is active if application still doesn't know it. - if state.ime_state == ImeState::Disabled { - state.input_source = self.current_input_source(); - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Enabled), - })); - } - - // Don't update state to preedit when we've just commited a string, since the following - // preedit string will be None anyway. - if state.ime_state != ImeState::Commited { - state.ime_state = ImeState::Preedit; - } - - // 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 - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)), - })); - } -} - -#[sel(unmarkText)] -fn unmark_text(&self) { - trace_scope!("unmarkText"); - unsafe { - let marked_text: id = *self.ivar("markedText"); - let mutable_string = marked_text.mutableString(); - let s: id = msg_send![class!(NSString), new]; - let _: () = msg_send![mutable_string, setString: s]; - let _: () = msg_send![s, release]; - let input_context: &Object = msg_send![self, inputContext]; - let _: () = msg_send![input_context, discardMarkedText]; - - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - })); - if state.is_ime_enabled() { - // Leave the Preedit state - state.ime_state = ImeState::Enabled; - } else { - warn!("Expected to have IME enabled when receiving unmarkText"); - } - } -} - -#[sel(validAttributesForMarkedText)] -fn valid_attributes_for_marked_text(&self) -> id { - trace_scope!("validAttributesForMarkedText"); - unsafe { msg_send![class!(NSArray), array] } -} - -#[sel(attributedSubstringForProposedRange:actualRange:)] -fn attributed_substring_for_proposed_range( - &self, - _range: NSRange, - _actual_range: *mut c_void, // *mut NSRange -) -> id { - trace_scope!("attributedSubstringForProposedRange:actualRange:"); - nil -} - -#[sel(characterIndexForPoint:)] -fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { - trace_scope!("characterIndexForPoint:"); - 0 -} - -#[sel(firstRectForCharacterRange:actualRange:)] -fn first_rect_for_character_range( - &self, - _range: NSRange, - _actual_range: *mut c_void, // *mut NSRange -) -> NSRect { - trace_scope!("firstRectForCharacterRange:actualRange:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let content_rect = - NSWindow::contentRectForFrameRect_(state.ns_window, NSWindow::frame(state.ns_window)); - let base_x = content_rect.origin.x as f64; - let base_y = (content_rect.origin.y + content_rect.size.height) as f64; - let x = base_x + state.ime_position.x; - let y = base_y - state.ime_position.y; - // This is not ideal: We _should_ return a different position based on - // the currently selected character (which varies depending on the type - // and size of the character), but in the current `winit` API there is - // no way to express this. Same goes for the `NSSize`. - NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) - } -} - -#[sel(insertText:replacementRange:)] -fn insert_text(&self, string: id, _replacement_range: NSRange) { - trace_scope!("insertText:replacementRange:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let string = id_to_string_lossy(string); - - let is_control = string.chars().next().map_or(false, |c| c.is_control()); - - // We don't need this now, but it's here if that changes. - //let event: id = msg_send![NSApp(), currentEvent]; - - if state.is_ime_enabled() && !is_control { - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Commit(string)), - })); - state.ime_state = ImeState::Commited; - } - } -} - -#[sel(doCommandBySelector:)] -fn do_command_by_selector(&self, _command: Sel) { - trace_scope!("doCommandBySelector:"); - // 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 = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - // 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. - if state.ime_state == ImeState::Commited { - return; - } - - state.forward_key_to_app = true; - - let has_marked_text = msg_send![self, hasMarkedText]; - if has_marked_text && state.ime_state == ImeState::Preedit { - // Leave preedit so that we also report the keyup for this key - state.ime_state = ImeState::Enabled; - } - } -} - } - - unsafe impl WinitView { - -#[sel(keyDown:)] -fn key_down(&self, event: id) { - trace_scope!("keyDown:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let window_id = WindowId(get_window_id(state.ns_window)); - - let input_source = self.current_input_source(); - if state.input_source != input_source && state.is_ime_enabled() { - state.ime_state = ImeState::Disabled; - state.input_source = input_source; - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::Ime(Ime::Disabled), - })); - } - let was_in_preedit = state.ime_state == ImeState::Preedit; - - let characters = get_characters(event, false); - state.forward_key_to_app = false; - - // 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) - let mut text_commited = false; - if state.ime_allowed { - let events_for_nsview: id = msg_send![class!(NSArray), arrayWithObject: event]; - let _: () = msg_send![self, interpretKeyEvents: events_for_nsview]; - - // Using a compiler fence because `interpretKeyEvents` might call - // into functions that modify the `ViewState`, but the compiler - // doesn't know this. Without the fence, the compiler may think that - // some of the reads (eg `state.ime_state`) that happen after this - // point are not needed. - compiler_fence(Ordering::SeqCst); - - // If the text was commited we must treat the next keyboard event as IME related. - if state.ime_state == ImeState::Commited { - state.ime_state = ImeState::Enabled; - text_commited = true; + #[sel(dealloc)] + fn dealloc(&mut self) { + unsafe { + let marked_text: id = *self.ivar("markedText"); + let _: () = msg_send![marked_text, release]; + let state: *mut c_void = *self.ivar("winitState"); + drop(Box::from_raw(state as *mut ViewState)); } } - let now_in_preedit = state.ime_state == ImeState::Preedit; + #[sel(initWithWinit:)] + fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> { + let this: Option<&mut Self> = unsafe { msg_send![self, init] }; + this.map(|this| unsafe { + (*this).set_ivar("winitState", state); + let marked_text = + ::init(NSMutableAttributedString::alloc(nil)); + (*this).set_ivar("markedText", marked_text); + let _: () = msg_send![&mut *this, setPostsFrameChangedNotifications: true]; - let scancode = get_scancode(event) as u32; - let virtual_keycode = retrieve_keycode(event); + let notification_center: &Object = + msg_send![class!(NSNotificationCenter), defaultCenter]; + // About frame change + let frame_did_change_notification_name = + IdRef::new(NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification")); + let _: () = msg_send![ + notification_center, + addObserver: &*this + selector: sel!(frameDidChange:) + name: *frame_did_change_notification_name + object: &*this + ]; - update_potentially_stale_modifiers(state, event); + let winit_state = &mut *(state as *mut ViewState); + winit_state.input_source = this.current_input_source(); + this + }) + } + } - let ime_related = was_in_preedit || now_in_preedit || text_commited; + unsafe impl WinitView { + #[sel(viewDidMoveToWindow)] + fn view_did_move_to_window(&self) { + trace_scope!("viewDidMoveToWindow"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); - if !ime_related || state.forward_key_to_app || !state.ime_allowed { - #[allow(deprecated)] - let window_event = Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode, - virtual_keycode, - modifiers: event_mods(event), - }, - is_synthetic: false, - }, - }; + if let Some(tracking_rect) = state.tracking_rect.take() { + let _: () = msg_send![self, removeTrackingRect: tracking_rect]; + } - AppState::queue_event(EventWrapper::StaticEvent(window_event)); + let rect: NSRect = msg_send![self, visibleRect]; + let tracking_rect: NSInteger = msg_send![ + self, + addTrackingRect: rect, + owner: self, + userData: ptr::null_mut::(), + assumeInside: false, + ]; + state.tracking_rect = Some(tracking_rect); + } + } - for character in characters.chars().filter(|c| !is_corporate_character(*c)) { + #[sel(frameDidChange:)] + fn frame_did_change(&self, _event: id) { + trace_scope!("frameDidChange:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + if let Some(tracking_rect) = state.tracking_rect.take() { + let _: () = msg_send![self, removeTrackingRect: tracking_rect]; + } + + let rect: NSRect = msg_send![self, visibleRect]; + let tracking_rect: NSInteger = msg_send![ + self, + addTrackingRect: rect, + owner: self, + userData: ptr::null_mut::(), + assumeInside: false, + ]; + state.tracking_rect = Some(tracking_rect); + + // 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::(state.get_scale_factor()); AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event: WindowEvent::ReceivedCharacter(character), + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::Resized(size), })); } } + + #[sel(drawRect:)] + fn draw_rect(&self, rect: NSRect) { + trace_scope!("drawRect:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + AppState::handle_redraw(WindowId(get_window_id(state.ns_window))); + + let _: () = msg_send![super(self), drawRect: rect]; + } + } + + #[sel(acceptsFirstResponder)] + fn accepts_first_responder(&self) -> bool { + trace_scope!("acceptsFirstResponder"); + true + } + + // 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` + #[sel(touchBar)] + fn touch_bar(&self) -> bool { + trace_scope!("touchBar"); + false + } + + #[sel(resetCursorRects)] + fn reset_cursor_rects(&self) { + trace_scope!("resetCursorRects"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let bounds: NSRect = msg_send![self, bounds]; + let cursor_state = state.cursor_state.lock().unwrap(); + let cursor = if cursor_state.visible { + cursor_state.cursor.load() + } else { + util::invisible_cursor() + }; + let _: () = msg_send![self, + addCursorRect:bounds + cursor:cursor + ]; + } + } } -} -#[sel(keyUp:)] -fn key_up(&self, event: id) { - trace_scope!("keyUp:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); + unsafe impl Protocol for WinitView { + #[sel(hasMarkedText)] + fn has_marked_text(&self) -> bool { + trace_scope!("hasMarkedText"); + unsafe { + let marked_text: id = *self.ivar("markedText"); + marked_text.length() > 0 + } + } - let scancode = get_scancode(event) as u32; - let virtual_keycode = retrieve_keycode(event); + #[sel(markedRange)] + fn marked_range(&self) -> NSRange { + trace_scope!("markedRange"); + unsafe { + let marked_text: id = *self.ivar("markedText"); + let length = marked_text.length(); + if length > 0 { + NSRange::new(0, length) + } else { + util::EMPTY_RANGE + } + } + } - update_potentially_stale_modifiers(state, event); + #[sel(selectedRange)] + fn selected_range(&self) -> NSRange { + trace_scope!("selectedRange"); + util::EMPTY_RANGE + } - // We want to send keyboard input when we are not currently in preedit - if state.ime_state != ImeState::Preedit { - #[allow(deprecated)] - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::KeyboardInput { + #[sel(setMarkedText:selectedRange:replacementRange:)] + fn set_marked_text( + &mut self, + string: id, + _selected_range: NSRange, + _replacement_range: NSRange, + ) { + trace_scope!("setMarkedText:selectedRange:replacementRange:"); + unsafe { + // Get pre-edit text + let marked_text_ref: &mut id = self.ivar_mut("markedText"); + + // Update markedText + let _: () = msg_send![*marked_text_ref, release]; + let marked_text = NSMutableAttributedString::alloc(nil); + let has_attr = msg_send![string, isKindOfClass: class!(NSAttributedString)]; + if has_attr { + marked_text.initWithAttributedString(string); + } else { + marked_text.initWithString(string); + }; + *marked_text_ref = marked_text; + + // Update ViewState with new marked text + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let preedit_string = id_to_string_lossy(string); + + // Notify IME is active if application still doesn't know it. + if state.ime_state == ImeState::Disabled { + state.input_source = self.current_input_source(); + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::Ime(Ime::Enabled), + })); + } + + // Don't update state to preedit when we've just commited a string, since the following + // preedit string will be None anyway. + if state.ime_state != ImeState::Commited { + state.ime_state = ImeState::Preedit; + } + + // 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 + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)), + })); + } + } + + #[sel(unmarkText)] + fn unmark_text(&self) { + trace_scope!("unmarkText"); + unsafe { + let marked_text: id = *self.ivar("markedText"); + let mutable_string = marked_text.mutableString(); + let s: id = msg_send![class!(NSString), new]; + let _: () = msg_send![mutable_string, setString: s]; + let _: () = msg_send![s, release]; + let input_context: &Object = msg_send![self, inputContext]; + let _: () = msg_send![input_context, discardMarkedText]; + + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), + })); + if state.is_ime_enabled() { + // Leave the Preedit state + state.ime_state = ImeState::Enabled; + } else { + warn!("Expected to have IME enabled when receiving unmarkText"); + } + } + } + + #[sel(validAttributesForMarkedText)] + fn valid_attributes_for_marked_text(&self) -> id { + trace_scope!("validAttributesForMarkedText"); + unsafe { msg_send![class!(NSArray), array] } + } + + #[sel(attributedSubstringForProposedRange:actualRange:)] + fn attributed_substring_for_proposed_range( + &self, + _range: NSRange, + _actual_range: *mut c_void, // *mut NSRange + ) -> id { + trace_scope!("attributedSubstringForProposedRange:actualRange:"); + nil + } + + #[sel(characterIndexForPoint:)] + fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { + trace_scope!("characterIndexForPoint:"); + 0 + } + + #[sel(firstRectForCharacterRange:actualRange:)] + fn first_rect_for_character_range( + &self, + _range: NSRange, + _actual_range: *mut c_void, // *mut NSRange + ) -> NSRect { + trace_scope!("firstRectForCharacterRange:actualRange:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let content_rect = NSWindow::contentRectForFrameRect_( + state.ns_window, + NSWindow::frame(state.ns_window), + ); + let base_x = content_rect.origin.x as f64; + let base_y = (content_rect.origin.y + content_rect.size.height) as f64; + let x = base_x + state.ime_position.x; + let y = base_y - state.ime_position.y; + // This is not ideal: We _should_ return a different position based on + // the currently selected character (which varies depending on the type + // and size of the character), but in the current `winit` API there is + // no way to express this. Same goes for the `NSSize`. + NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) + } + } + + #[sel(insertText:replacementRange:)] + fn insert_text(&self, string: id, _replacement_range: NSRange) { + trace_scope!("insertText:replacementRange:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let string = id_to_string_lossy(string); + + let is_control = string.chars().next().map_or(false, |c| c.is_control()); + + // We don't need this now, but it's here if that changes. + //let event: id = msg_send![NSApp(), currentEvent]; + + if state.is_ime_enabled() && !is_control { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::Ime(Ime::Commit(string)), + })); + state.ime_state = ImeState::Commited; + } + } + } + + #[sel(doCommandBySelector:)] + fn do_command_by_selector(&self, _command: Sel) { + trace_scope!("doCommandBySelector:"); + // 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 = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + // 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. + if state.ime_state == ImeState::Commited { + return; + } + + state.forward_key_to_app = true; + + let has_marked_text = msg_send![self, hasMarkedText]; + if has_marked_text && state.ime_state == ImeState::Preedit { + // Leave preedit so that we also report the keyup for this key + state.ime_state = ImeState::Enabled; + } + } + } + } + + unsafe impl WinitView { + #[sel(keyDown:)] + fn key_down(&self, event: id) { + trace_scope!("keyDown:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let window_id = WindowId(get_window_id(state.ns_window)); + + let input_source = self.current_input_source(); + if state.input_source != input_source && state.is_ime_enabled() { + state.ime_state = ImeState::Disabled; + state.input_source = input_source; + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::Ime(Ime::Disabled), + })); + } + let was_in_preedit = state.ime_state == ImeState::Preedit; + + let characters = get_characters(event, false); + state.forward_key_to_app = false; + + // 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) + let mut text_commited = false; + if state.ime_allowed { + let events_for_nsview: id = msg_send![class!(NSArray), arrayWithObject: event]; + let _: () = msg_send![self, interpretKeyEvents: events_for_nsview]; + + // Using a compiler fence because `interpretKeyEvents` might call + // into functions that modify the `ViewState`, but the compiler + // doesn't know this. Without the fence, the compiler may think that + // some of the reads (eg `state.ime_state`) that happen after this + // point are not needed. + compiler_fence(Ordering::SeqCst); + + // If the text was commited we must treat the next keyboard event as IME related. + if state.ime_state == ImeState::Commited { + state.ime_state = ImeState::Enabled; + text_commited = true; + } + } + + let now_in_preedit = state.ime_state == ImeState::Preedit; + + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); + + update_potentially_stale_modifiers(state, event); + + let ime_related = was_in_preedit || now_in_preedit || text_commited; + + if !ime_related || state.forward_key_to_app || !state.ime_allowed { + #[allow(deprecated)] + let window_event = Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode, + virtual_keycode, + modifiers: event_mods(event), + }, + is_synthetic: false, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + + for character in characters.chars().filter(|c| !is_corporate_character(*c)) { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ReceivedCharacter(character), + })); + } + } + } + } + + #[sel(keyUp:)] + fn key_up(&self, event: id) { + trace_scope!("keyUp:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); + + update_potentially_stale_modifiers(state, event); + + // We want to send keyboard input when we are not currently in preedit + if state.ime_state != ImeState::Preedit { + #[allow(deprecated)] + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Released, + scancode, + virtual_keycode, + modifiers: event_mods(event), + }, + is_synthetic: false, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + } + } + + #[sel(flagsChanged:)] + fn flags_changed(&self, event: id) { + trace_scope!("flagsChanged:"); + unsafe { + let state_ptr: *mut c_void = *self.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(), + ) { + state.modifiers.toggle(ModifiersState::SHIFT); + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSControlKeyMask, + state.modifiers.ctrl(), + ) { + state.modifiers.toggle(ModifiersState::CTRL); + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSCommandKeyMask, + state.modifiers.logo(), + ) { + state.modifiers.toggle(ModifiersState::LOGO); + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSAlternateKeyMask, + state.modifiers.alt(), + ) { + state.modifiers.toggle(ModifiersState::ALT); + events.push_back(window_event); + } + + let window_id = WindowId(get_window_id(state.ns_window)); + + for event in events { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event, + })); + } + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(state.modifiers), + })); + } + } + + #[sel(insertTab:)] + fn insert_tab(&self, _sender: id) { + trace_scope!("insertTab:"); + unsafe { + let window: id = msg_send![self, window]; + let first_responder: id = msg_send![window, firstResponder]; + let self_ptr = self as *const _ as *mut _; + if first_responder == self_ptr { + let _: () = msg_send![window, selectNextKeyView: self]; + } + } + } + + #[sel(insertBackTab:)] + fn insert_back_tab(&self, _sender: id) { + trace_scope!("insertBackTab:"); + unsafe { + let window: id = msg_send![self, window]; + let first_responder: id = msg_send![window, firstResponder]; + let self_ptr = self as *const _ as *mut _; + if first_responder == self_ptr { + let _: () = msg_send![window, selectPreviousKeyView: self]; + } + } + } + + // Allows us to receive Cmd-. (the shortcut for closing a dialog) + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 + #[sel(cancelOperation:)] + fn cancel_operation(&self, _sender: id) { + trace_scope!("cancelOperation:"); + unsafe { + let state_ptr: *mut c_void = *self.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]; + + update_potentially_stale_modifiers(state, event); + + #[allow(deprecated)] + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode: scancode as _, + virtual_keycode, + modifiers: event_mods(event), + }, + is_synthetic: false, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + } + + #[sel(mouseDown:)] + fn mouse_down(&self, event: id) { + trace_scope!("mouseDown:"); + mouse_motion(self, event); + mouse_click(self, event, MouseButton::Left, ElementState::Pressed); + } + + #[sel(mouseUp:)] + fn mouse_up(&self, event: id) { + trace_scope!("mouseUp:"); + mouse_motion(self, event); + mouse_click(self, event, MouseButton::Left, ElementState::Released); + } + + #[sel(rightMouseDown:)] + fn right_mouse_down(&self, event: id) { + trace_scope!("rightMouseDown:"); + mouse_motion(self, event); + mouse_click(self, event, MouseButton::Right, ElementState::Pressed); + } + + #[sel(rightMouseUp:)] + fn right_mouse_up(&self, event: id) { + trace_scope!("rightMouseUp:"); + mouse_motion(self, event); + mouse_click(self, event, MouseButton::Right, ElementState::Released); + } + + #[sel(otherMouseDown:)] + fn other_mouse_down(&self, event: id) { + trace_scope!("otherMouseDown:"); + mouse_motion(self, event); + mouse_click(self, event, MouseButton::Middle, ElementState::Pressed); + } + + #[sel(otherMouseUp:)] + fn other_mouse_up(&self, event: id) { + trace_scope!("otherMouseUp:"); + mouse_motion(self, event); + mouse_click(self, event, MouseButton::Middle, ElementState::Released); + } + + // No tracing on these because that would be overly verbose + + #[sel(mouseMoved:)] + fn mouse_moved(&self, event: id) { + mouse_motion(self, event); + } + + #[sel(mouseDragged:)] + fn mouse_dragged(&self, event: id) { + mouse_motion(self, event); + } + + #[sel(rightMouseDragged:)] + fn right_mouse_dragged(&self, event: id) { + mouse_motion(self, event); + } + + #[sel(otherMouseDragged:)] + fn other_mouse_dragged(&self, event: id) { + mouse_motion(self, event); + } + + #[sel(mouseEntered:)] + fn mouse_entered(&self, _event: id) { + trace_scope!("mouseEntered:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let enter_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::CursorEntered { + device_id: DEVICE_ID, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(enter_event)); + } + } + + #[sel(mouseExited:)] + fn mouse_exited(&self, _event: id) { + trace_scope!("mouseExited:"); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::CursorLeft { + device_id: DEVICE_ID, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + } + + #[sel(scrollWheel:)] + fn scroll_wheel(&self, event: id) { + trace_scope!("scrollWheel:"); + + mouse_motion(self, event); + + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let delta = { + let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); + if Bool::from_raw(event.hasPreciseScrollingDeltas()).as_bool() { + let delta = + LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); + MouseScrollDelta::PixelDelta(delta) + } else { + MouseScrollDelta::LineDelta(x as f32, y as f32) + } + }; + + // 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. + let phase = + match event.momentumPhase() { + NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { + TouchPhase::Started + } + NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { + TouchPhase::Ended + } + _ => match event.phase() { + NSEventPhase::NSEventPhaseMayBegin + | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseEnded + | NSEventPhase::NSEventPhaseCancelled => TouchPhase::Ended, + _ => TouchPhase::Moved, + }, + }; + + let device_event = Event::DeviceEvent { device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Released, - scancode, - virtual_keycode, + event: DeviceEvent::MouseWheel { delta }, + }; + + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + update_potentially_stale_modifiers(state, event); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta, + phase, modifiers: event_mods(event), }, - is_synthetic: false, - }, - }; + }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - } -} - -#[sel(flagsChanged:)] -fn flags_changed(&self, event: id) { - trace_scope!("flagsChanged:"); - unsafe { - let state_ptr: *mut c_void = *self.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(), - ) { - state.modifiers.toggle(ModifiersState::SHIFT); - events.push_back(window_event); - } - - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSControlKeyMask, - state.modifiers.ctrl(), - ) { - state.modifiers.toggle(ModifiersState::CTRL); - events.push_back(window_event); - } - - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSCommandKeyMask, - state.modifiers.logo(), - ) { - state.modifiers.toggle(ModifiersState::LOGO); - events.push_back(window_event); - } - - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSAlternateKeyMask, - state.modifiers.alt(), - ) { - state.modifiers.toggle(ModifiersState::ALT); - events.push_back(window_event); - } - - let window_id = WindowId(get_window_id(state.ns_window)); - - for event in events { - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event, - })); - } - - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(state.modifiers), - })); - } -} - -#[sel(insertTab:)] -fn insert_tab(&self, _sender: id) { - trace_scope!("insertTab:"); - unsafe { - let window: id = msg_send![self, window]; - let first_responder: id = msg_send![window, firstResponder]; - let self_ptr = self as *const _ as *mut _; - if first_responder == self_ptr { - let _: () = msg_send![window, selectNextKeyView: self]; - } - } -} - -#[sel(insertBackTab:)] -fn insert_back_tab(&self, _sender: id) { - trace_scope!("insertBackTab:"); - unsafe { - let window: id = msg_send![self, window]; - let first_responder: id = msg_send![window, firstResponder]; - let self_ptr = self as *const _ as *mut _; - if first_responder == self_ptr { - let _: () = msg_send![window, selectPreviousKeyView: self]; - } - } -} - -// Allows us to receive Cmd-. (the shortcut for closing a dialog) -// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 -#[sel(cancelOperation:)] -fn cancel_operation(&self, _sender: id) { - trace_scope!("cancelOperation:"); - unsafe { - let state_ptr: *mut c_void = *self.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]; - - update_potentially_stale_modifiers(state, event); - - #[allow(deprecated)] - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode: scancode as _, - virtual_keycode, - modifiers: event_mods(event), - }, - is_synthetic: false, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -#[sel(mouseDown:)] -fn mouse_down(&self, event: id) { - trace_scope!("mouseDown:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Left, ElementState::Pressed); -} - -#[sel(mouseUp:)] -fn mouse_up(&self, event: id) { - trace_scope!("mouseUp:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Left, ElementState::Released); -} - -#[sel(rightMouseDown:)] -fn right_mouse_down(&self, event: id) { - trace_scope!("rightMouseDown:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Right, ElementState::Pressed); -} - -#[sel(rightMouseUp:)] -fn right_mouse_up(&self, event: id) { - trace_scope!("rightMouseUp:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Right, ElementState::Released); -} - -#[sel(otherMouseDown:)] -fn other_mouse_down(&self, event: id) { - trace_scope!("otherMouseDown:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Middle, ElementState::Pressed); -} - -#[sel(otherMouseUp:)] -fn other_mouse_up(&self, event: id) { - trace_scope!("otherMouseUp:"); - mouse_motion(self, event); - mouse_click(self, event, MouseButton::Middle, ElementState::Released); -} - -// No tracing on these because that would be overly verbose - -#[sel(mouseMoved:)] -fn mouse_moved(&self, event: id) { - mouse_motion(self, event); -} - -#[sel(mouseDragged:)] -fn mouse_dragged(&self, event: id) { - mouse_motion(self, event); -} - -#[sel(rightMouseDragged:)] -fn right_mouse_dragged(&self, event: id) { - mouse_motion(self, event); -} - -#[sel(otherMouseDragged:)] -fn other_mouse_dragged(&self, event: id) { - mouse_motion(self, event); -} - -#[sel(mouseEntered:)] -fn mouse_entered(&self, _event: id) { - trace_scope!("mouseEntered:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let enter_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::CursorEntered { - device_id: DEVICE_ID, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(enter_event)); - } -} - -#[sel(mouseExited:)] -fn mouse_exited(&self, _event: id) { - trace_scope!("mouseExited:"); - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::CursorLeft { - device_id: DEVICE_ID, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -#[sel(scrollWheel:)] -fn scroll_wheel(&self, event: id) { - trace_scope!("scrollWheel:"); - - mouse_motion(self, event); - - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = { - let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); - if Bool::from_raw(event.hasPreciseScrollingDeltas()).as_bool() { - let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); - MouseScrollDelta::PixelDelta(delta) - } else { - MouseScrollDelta::LineDelta(x as f32, y as f32) + AppState::queue_event(EventWrapper::StaticEvent(device_event)); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } - }; + } - // 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. - let phase = match event.momentumPhase() { - NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { - TouchPhase::Started + #[sel(magnifyWithEvent:)] + fn magnify_with_event(&self, event: id) { + trace_scope!("magnifyWithEvent:"); + + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let delta = event.magnification(); + let phase = match event.phase() { + NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, + NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => return, + }; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::TouchpadMagnify { + device_id: DEVICE_ID, + delta, + phase, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } - NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { - TouchPhase::Ended + } + + #[sel(rotateWithEvent:)] + fn rotate_with_event(&self, event: id) { + trace_scope!("rotateWithEvent:"); + + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let delta = event.rotation(); + let phase = match event.phase() { + NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, + NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => return, + }; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::TouchpadRotate { + device_id: DEVICE_ID, + delta, + phase, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } - _ => match event.phase() { - NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { - TouchPhase::Started - } - NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { - TouchPhase::Ended - } - _ => TouchPhase::Moved, - }, - }; + } - let device_event = Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseWheel { delta }, - }; + #[sel(pressureChangeWithEvent:)] + fn pressure_change_with_event(&self, event: id) { + trace_scope!("pressureChangeWithEvent:"); - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); + mouse_motion(self, event); - update_potentially_stale_modifiers(state, event); + unsafe { + let state_ptr: *mut c_void = *self.ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta, - phase, - modifiers: event_mods(event), - }, - }; + let pressure = event.pressure(); + let stage = event.stage(); - AppState::queue_event(EventWrapper::StaticEvent(device_event)); - AppState::queue_event(EventWrapper::StaticEvent(window_event)); + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::TouchpadPressure { + device_id: DEVICE_ID, + pressure, + stage: stage as i64, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + } + + // 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 + #[sel(_wantsKeyDownForEvent:)] + fn wants_key_down_for_event(&self, _event: id) -> bool { + trace_scope!("_wantsKeyDownForEvent:"); + true + } + + #[sel(acceptsFirstMouse:)] + fn accepts_first_mouse(&self, _event: id) -> bool { + trace_scope!("acceptsFirstMouse:"); + true + } } -} - -#[sel(magnifyWithEvent:)] -fn magnify_with_event(&self, event: id) { - trace_scope!("magnifyWithEvent:"); - - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = event.magnification(); - let phase = match event.phase() { - NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, - NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => return, - }; - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::TouchpadMagnify { - device_id: DEVICE_ID, - delta, - phase, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -#[sel(rotateWithEvent:)] -fn rotate_with_event(&self, event: id) { - trace_scope!("rotateWithEvent:"); - - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = event.rotation(); - let phase = match event.phase() { - NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, - NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => return, - }; - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::TouchpadRotate { - device_id: DEVICE_ID, - delta, - phase, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -#[sel(pressureChangeWithEvent:)] -fn pressure_change_with_event(&self, event: id) { - trace_scope!("pressureChangeWithEvent:"); - - mouse_motion(self, event); - - unsafe { - let state_ptr: *mut c_void = *self.ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - - let pressure = event.pressure(); - let stage = event.stage(); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::TouchpadPressure { - device_id: DEVICE_ID, - pressure, - stage: stage as i64, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -// 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 -#[sel(_wantsKeyDownForEvent:)] -fn wants_key_down_for_event(&self, _event: id) -> bool { - trace_scope!("_wantsKeyDownForEvent:"); - true -} - -#[sel(acceptsFirstMouse:)] -fn accepts_first_mouse(&self, _event: id) -> bool { - trace_scope!("acceptsFirstMouse:"); - true -} - } -} +); impl WinitView { fn current_input_source(&self) -> String { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 076b27a5..73cf41c7 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -128,7 +128,7 @@ pub fn new_delegate(window: &Arc, initial_fullscreen: bool) -> Id } } -declare_class! { +declare_class!( #[derive(Debug)] struct WinitWindowDelegate { state: *mut c_void, @@ -139,363 +139,364 @@ declare_class! { } unsafe impl WinitWindowDelegate { -#[sel(dealloc)] -fn dealloc(&mut self) { - self.with_state(|state| unsafe { - drop(Box::from_raw(state as *mut WindowDelegateState)); - }); -} + #[sel(dealloc)] + fn dealloc(&mut self) { + self.with_state(|state| unsafe { + drop(Box::from_raw(state as *mut WindowDelegateState)); + }); + } -#[sel(initWithWinit:)] -fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> { - let this: Option<&mut Self> = unsafe { msg_send![self, init] }; - this.map(|this| { - *this.state = state; - this.with_state(|state| { - let _: () = unsafe { msg_send![*state.ns_window, setDelegate: &*this] }; - }); - this - }) -} + #[sel(initWithWinit:)] + fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> { + let this: Option<&mut Self> = unsafe { msg_send![self, init] }; + this.map(|this| { + *this.state = state; + this.with_state(|state| { + let _: () = unsafe { msg_send![*state.ns_window, setDelegate: &*this] }; + }); + this + }) + } } // NSWindowDelegate + NSDraggingDestination protocols unsafe impl WinitWindowDelegate { -#[sel(windowShouldClose:)] -fn window_should_close(&self, _: id) -> bool { - trace_scope!("windowShouldClose:"); - self.with_state(|state| state.emit_event(WindowEvent::CloseRequested)); - false -} - -#[sel(windowWillClose:)] -fn window_will_close(&self, _: id) { - trace_scope!("windowWillClose:"); - self.with_state(|state| unsafe { - // `setDelegate:` retains the previous value and then autoreleases it - autoreleasepool(|_| { - // Since El Capitan, we need to be careful that delegate methods can't - // be called after the window closes. - let _: () = msg_send![*state.ns_window, setDelegate: nil]; - }); - state.emit_event(WindowEvent::Destroyed); - }); -} - -#[sel(windowDidResize:)] -fn window_did_resize(&self, _: id) { - trace_scope!("windowDidResize:"); - self.with_state(|state| { - // NOTE: WindowEvent::Resized is reported in frameDidChange. - state.emit_move_event(); - }); -} - -// This won't be triggered if the move was part of a resize. -#[sel(windowDidMove:)] -fn window_did_move(&self, _: id) { - trace_scope!("windowDidMove:"); - self.with_state(|state| { - state.emit_move_event(); - }); -} - -#[sel(windowDidChangeBackingProperties:)] -fn window_did_change_backing_properties(&self, _: id) { - trace_scope!("windowDidChangeBackingProperties:"); - self.with_state(|state| { - state.emit_static_scale_factor_changed_event(); - }); -} - -#[sel(windowDidBecomeKey:)] -fn window_did_become_key(&self, _: id) { - trace_scope!("windowDidBecomeKey:"); - self.with_state(|state| { - // TODO: center the cursor if the window had mouse grab when it - // lost focus - state.emit_event(WindowEvent::Focused(true)); - }); -} - -#[sel(windowDidResignKey:)] -fn window_did_resign_key(&self, _: id) { - trace_scope!("windowDidResignKey:"); - self.with_state(|state| { - // It happens rather often, e.g. when the user is Cmd+Tabbing, that the - // NSWindowDelegate will receive a didResignKey event despite no event - // being received when the modifiers are released. This is because - // flagsChanged events are received by the NSView instead of the - // NSWindowDelegate, and as a result a tracked modifiers state can quite - // easily fall out of synchrony with reality. This requires us to emit - // a synthetic ModifiersChanged event when we lose focus. - // - // Here we (very unsafely) acquire the winitState (a ViewState) from the - // Object referenced by state.ns_view (an IdRef, which is dereferenced - // to an id) - let view_state: &mut ViewState = unsafe { - let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref"); - let state_ptr: *mut c_void = *ns_view.ivar("winitState"); - &mut *(state_ptr as *mut ViewState) - }; - - // Both update the state and emit a ModifiersChanged event. - if !view_state.modifiers.is_empty() { - view_state.modifiers = ModifiersState::empty(); - state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); + #[sel(windowShouldClose:)] + fn window_should_close(&self, _: id) -> bool { + trace_scope!("windowShouldClose:"); + self.with_state(|state| state.emit_event(WindowEvent::CloseRequested)); + false } - state.emit_event(WindowEvent::Focused(false)); - }); -} - -/// Invoked when the dragged image enters destination bounds or frame -#[sel(draggingEntered:)] -fn dragging_entered(&self, sender: id) -> bool { - trace_scope!("draggingEntered:"); - - use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration}; - use std::path::PathBuf; - - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - for file in unsafe { filenames.iter() } { - use cocoa::foundation::NSString; - use std::ffi::CStr; - - unsafe { - let f = NSString::UTF8String(file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); - - self.with_state(|state| { - state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); + #[sel(windowWillClose:)] + fn window_will_close(&self, _: id) { + trace_scope!("windowWillClose:"); + self.with_state(|state| unsafe { + // `setDelegate:` retains the previous value and then autoreleases it + autoreleasepool(|_| { + // Since El Capitan, we need to be careful that delegate methods can't + // be called after the window closes. + let _: () = msg_send![*state.ns_window, setDelegate: nil]; + }); + state.emit_event(WindowEvent::Destroyed); }); } - } - - true -} - -/// Invoked when the image is released -#[sel(prepareForDragOperation:)] -fn prepare_for_drag_operation(&self, _: id) -> bool { - trace_scope!("prepareForDragOperation:"); - true -} - -/// Invoked after the released image has been removed from the screen -#[sel(performDragOperation:)] -fn perform_drag_operation(&self, sender: id) -> bool { - trace_scope!("performDragOperation:"); - - use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration}; - use std::path::PathBuf; - - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - for file in unsafe { filenames.iter() } { - use cocoa::foundation::NSString; - use std::ffi::CStr; - - unsafe { - let f = NSString::UTF8String(file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + #[sel(windowDidResize:)] + fn window_did_resize(&self, _: id) { + trace_scope!("windowDidResize:"); self.with_state(|state| { - state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); + // NOTE: WindowEvent::Resized is reported in frameDidChange. + state.emit_move_event(); }); } - } - true -} + // This won't be triggered if the move was part of a resize. + #[sel(windowDidMove:)] + fn window_did_move(&self, _: id) { + trace_scope!("windowDidMove:"); + self.with_state(|state| { + state.emit_move_event(); + }); + } -/// Invoked when the dragging operation is complete -#[sel(concludeDragOperation:)] -fn conclude_drag_operation(&self, _: id) { - trace_scope!("concludeDragOperation:"); -} + #[sel(windowDidChangeBackingProperties:)] + fn window_did_change_backing_properties(&self, _: id) { + trace_scope!("windowDidChangeBackingProperties:"); + self.with_state(|state| { + state.emit_static_scale_factor_changed_event(); + }); + } -/// Invoked when the dragging operation is cancelled -#[sel(draggingExited:)] -fn dragging_exited(&self, _: id) { - trace_scope!("draggingExited:"); - self.with_state(|state| { - state.emit_event(WindowEvent::HoveredFileCancelled) - }); -} + #[sel(windowDidBecomeKey:)] + fn window_did_become_key(&self, _: id) { + trace_scope!("windowDidBecomeKey:"); + self.with_state(|state| { + // TODO: center the cursor if the window had mouse grab when it + // lost focus + state.emit_event(WindowEvent::Focused(true)); + }); + } -/// Invoked when before enter fullscreen -#[sel(windowWillEnterFullscreen:)] -fn window_will_enter_fullscreen(&self, _: id) { - trace_scope!("windowWillEnterFullscreen:"); + #[sel(windowDidResignKey:)] + fn window_did_resign_key(&self, _: id) { + trace_scope!("windowDidResignKey:"); + self.with_state(|state| { + // It happens rather often, e.g. when the user is Cmd+Tabbing, that the + // NSWindowDelegate will receive a didResignKey event despite no event + // being received when the modifiers are released. This is because + // flagsChanged events are received by the NSView instead of the + // NSWindowDelegate, and as a result a tracked modifiers state can quite + // easily fall out of synchrony with reality. This requires us to emit + // a synthetic ModifiersChanged event when we lose focus. + // + // Here we (very unsafely) acquire the winitState (a ViewState) from the + // Object referenced by state.ns_view (an IdRef, which is dereferenced + // to an id) + let view_state: &mut ViewState = unsafe { + let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref"); + let state_ptr: *mut c_void = *ns_view.ivar("winitState"); + &mut *(state_ptr as *mut ViewState) + }; - self.with_state(|state| { - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_will_enter_fullscreen"); - shared_state.maximized = window.is_zoomed(); - let fullscreen = shared_state.fullscreen.as_ref(); - match fullscreen { - // Exclusive mode sets the state in `set_fullscreen` as the user - // can't enter exclusive mode by other means (like the - // fullscreen button on the window decorations) - Some(Fullscreen::Exclusive(_)) => (), - // `window_will_enter_fullscreen` was triggered and we're already - // in fullscreen, so we must've reached here by `set_fullscreen` - // as it updates the state - Some(Fullscreen::Borderless(_)) => (), - // Otherwise, we must've reached fullscreen by the user clicking - // on the green fullscreen button. Update state! - None => { - let current_monitor = Some(window.current_monitor_inner()); - shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) + // Both update the state and emit a ModifiersChanged event. + if !view_state.modifiers.is_empty() { + view_state.modifiers = ModifiersState::empty(); + state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); + } + + state.emit_event(WindowEvent::Focused(false)); + }); + } + + /// Invoked when the dragged image enters destination bounds or frame + #[sel(draggingEntered:)] + fn dragging_entered(&self, sender: id) -> bool { + trace_scope!("draggingEntered:"); + + use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration}; + use std::path::PathBuf; + + let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = + unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; + + for file in unsafe { filenames.iter() } { + use cocoa::foundation::NSString; + use std::ffi::CStr; + + unsafe { + let f = NSString::UTF8String(file); + let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + + self.with_state(|state| { + state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); + }); } } - shared_state.in_fullscreen_transition = true; - }) - }); -} -/// Invoked when before exit fullscreen -#[sel(windowWillExitFullScreen:)] -fn window_will_exit_fullscreen(&self, _: id) { - trace_scope!("windowWillExitFullScreen:"); - - self.with_state(|state| { - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_will_exit_fullscreen"); - shared_state.in_fullscreen_transition = true; - }); - }); -} - -#[sel(window:willUseFullScreenPresentationOptions:)] -fn window_will_use_fullscreen_presentation_options( - &self, - _: id, - proposed_options: NSUInteger, -) -> NSUInteger { - trace_scope!("window:willUseFullScreenPresentationOptions:"); - // Generally, games will want to disable the menu bar and the dock. Ideally, - // this would be configurable by the user. Unfortunately because of our - // `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is - // placed on top of the menu bar in exclusive fullscreen mode. This looks - // broken so we always disable the menu bar in exclusive fullscreen. We may - // still want to make this configurable for borderless fullscreen. Right now - // we don't, for consistency. If we do, it should be documented that the - // user-provided options are ignored in exclusive fullscreen. - let mut options: NSUInteger = proposed_options; - self.with_state(|state| { - state.with_window(|window| { - let shared_state = - window.lock_shared_state("window_will_use_fullscreen_presentation_options"); - if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { - options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen - | NSApplicationPresentationOptions::NSApplicationPresentationHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar) - .bits() as NSUInteger; - } - }) - }); - - options -} - -/// Invoked when entered fullscreen -#[sel(windowDidEnterFullscreen:)] -fn window_did_enter_fullscreen(&self, _: id) { - trace_scope!("windowDidEnterFullscreen:"); - self.with_state(|state| { - state.initial_fullscreen = false; - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_did_enter_fullscreen"); - shared_state.in_fullscreen_transition = false; - let target_fullscreen = shared_state.target_fullscreen.take(); - drop(shared_state); - if let Some(target_fullscreen) = target_fullscreen { - window.set_fullscreen(target_fullscreen); - } - }); - }); -} - -/// Invoked when exited fullscreen -#[sel(windowDidExitFullscreen:)] -fn window_did_exit_fullscreen(&self, _: id) { - trace_scope!("windowDidExitFullscreen:"); - - self.with_state(|state| { - state.with_window(|window| { - window.restore_state_from_fullscreen(); - let mut shared_state = window.lock_shared_state("window_did_exit_fullscreen"); - shared_state.in_fullscreen_transition = false; - let target_fullscreen = shared_state.target_fullscreen.take(); - drop(shared_state); - if let Some(target_fullscreen) = target_fullscreen { - window.set_fullscreen(target_fullscreen); - } - }) - }); -} - -/// Invoked when fail to enter fullscreen -/// -/// When this window launch from a fullscreen app (e.g. launch from VS Code -/// terminal), it creates a new virtual destkop and a transition animation. -/// This animation takes one second and cannot be disable without -/// elevated privileges. In this animation time, all toggleFullscreen events -/// will be failed. In this implementation, we will try again by using -/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. -/// It should be fine as we only do this at initialzation (i.e with_fullscreen -/// was set). -/// -/// From Apple doc: -/// In some cases, the transition to enter full-screen mode can fail, -/// due to being in the midst of handling some other animation or user gesture. -/// This method indicates that there was an error, and you should clean up any -/// work you may have done to prepare to enter full-screen mode. -#[sel(windowDidFailToEnterFullscreen:)] -fn window_did_fail_to_enter_fullscreen(&self, _: id) { - trace_scope!("windowDidFailToEnterFullscreen:"); - self.with_state(|state| { - state.with_window(|window| { - let mut shared_state = window.lock_shared_state("window_did_fail_to_enter_fullscreen"); - shared_state.in_fullscreen_transition = false; - shared_state.target_fullscreen = None; - }); - if state.initial_fullscreen { - unsafe { - let _: () = msg_send![*state.ns_window, - performSelector:sel!(toggleFullScreen:) - withObject:nil - afterDelay: 0.5 - ]; - }; - } else { - state.with_window(|window| window.restore_state_from_fullscreen()); + true } - }); -} -// Invoked when the occlusion state of the window changes -#[sel(windowDidChangeOcclusionState:)] -fn window_did_change_occlusion_state(&self, _: id) { - trace_scope!("windowDidChangeOcclusionState:"); - unsafe { - self.with_state(|state| { - state.emit_event(WindowEvent::Occluded( - !state - .ns_window - .occlusionState() - .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), - )) - }); + /// Invoked when the image is released + #[sel(prepareForDragOperation:)] + fn prepare_for_drag_operation(&self, _: id) -> bool { + trace_scope!("prepareForDragOperation:"); + true + } + + /// Invoked after the released image has been removed from the screen + #[sel(performDragOperation:)] + fn perform_drag_operation(&self, sender: id) -> bool { + trace_scope!("performDragOperation:"); + + use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration}; + use std::path::PathBuf; + + let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = + unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; + + for file in unsafe { filenames.iter() } { + use cocoa::foundation::NSString; + use std::ffi::CStr; + + unsafe { + let f = NSString::UTF8String(file); + let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + + self.with_state(|state| { + state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); + }); + } + } + + true + } + + /// Invoked when the dragging operation is complete + #[sel(concludeDragOperation:)] + fn conclude_drag_operation(&self, _: id) { + trace_scope!("concludeDragOperation:"); + } + + /// Invoked when the dragging operation is cancelled + #[sel(draggingExited:)] + fn dragging_exited(&self, _: id) { + trace_scope!("draggingExited:"); + self.with_state(|state| state.emit_event(WindowEvent::HoveredFileCancelled)); + } + + /// Invoked when before enter fullscreen + #[sel(windowWillEnterFullscreen:)] + fn window_will_enter_fullscreen(&self, _: id) { + trace_scope!("windowWillEnterFullscreen:"); + + self.with_state(|state| { + state.with_window(|window| { + let mut shared_state = window.lock_shared_state("window_will_enter_fullscreen"); + shared_state.maximized = window.is_zoomed(); + let fullscreen = shared_state.fullscreen.as_ref(); + match fullscreen { + // Exclusive mode sets the state in `set_fullscreen` as the user + // can't enter exclusive mode by other means (like the + // fullscreen button on the window decorations) + Some(Fullscreen::Exclusive(_)) => (), + // `window_will_enter_fullscreen` was triggered and we're already + // in fullscreen, so we must've reached here by `set_fullscreen` + // as it updates the state + Some(Fullscreen::Borderless(_)) => (), + // Otherwise, we must've reached fullscreen by the user clicking + // on the green fullscreen button. Update state! + None => { + let current_monitor = Some(window.current_monitor_inner()); + shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) + } + } + shared_state.in_fullscreen_transition = true; + }) + }); + } + + /// Invoked when before exit fullscreen + #[sel(windowWillExitFullScreen:)] + fn window_will_exit_fullscreen(&self, _: id) { + trace_scope!("windowWillExitFullScreen:"); + + self.with_state(|state| { + state.with_window(|window| { + let mut shared_state = window.lock_shared_state("window_will_exit_fullscreen"); + shared_state.in_fullscreen_transition = true; + }); + }); + } + + #[sel(window:willUseFullScreenPresentationOptions:)] + fn window_will_use_fullscreen_presentation_options( + &self, + _: id, + proposed_options: NSUInteger, + ) -> NSUInteger { + trace_scope!("window:willUseFullScreenPresentationOptions:"); + // Generally, games will want to disable the menu bar and the dock. Ideally, + // this would be configurable by the user. Unfortunately because of our + // `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is + // placed on top of the menu bar in exclusive fullscreen mode. This looks + // broken so we always disable the menu bar in exclusive fullscreen. We may + // still want to make this configurable for borderless fullscreen. Right now + // we don't, for consistency. If we do, it should be documented that the + // user-provided options are ignored in exclusive fullscreen. + let mut options: NSUInteger = proposed_options; + self.with_state(|state| { + state.with_window(|window| { + let shared_state = + window.lock_shared_state("window_will_use_fullscreen_presentation_options"); + if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { + options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar) + .bits() as NSUInteger; + } + }) + }); + + options + } + + /// Invoked when entered fullscreen + #[sel(windowDidEnterFullscreen:)] + fn window_did_enter_fullscreen(&self, _: id) { + trace_scope!("windowDidEnterFullscreen:"); + self.with_state(|state| { + state.initial_fullscreen = false; + state.with_window(|window| { + let mut shared_state = window.lock_shared_state("window_did_enter_fullscreen"); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + window.set_fullscreen(target_fullscreen); + } + }); + }); + } + + /// Invoked when exited fullscreen + #[sel(windowDidExitFullscreen:)] + fn window_did_exit_fullscreen(&self, _: id) { + trace_scope!("windowDidExitFullscreen:"); + + self.with_state(|state| { + state.with_window(|window| { + window.restore_state_from_fullscreen(); + let mut shared_state = window.lock_shared_state("window_did_exit_fullscreen"); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + window.set_fullscreen(target_fullscreen); + } + }) + }); + } + + /// Invoked when fail to enter fullscreen + /// + /// When this window launch from a fullscreen app (e.g. launch from VS Code + /// terminal), it creates a new virtual destkop and a transition animation. + /// This animation takes one second and cannot be disable without + /// elevated privileges. In this animation time, all toggleFullscreen events + /// will be failed. In this implementation, we will try again by using + /// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. + /// It should be fine as we only do this at initialzation (i.e with_fullscreen + /// was set). + /// + /// From Apple doc: + /// In some cases, the transition to enter full-screen mode can fail, + /// due to being in the midst of handling some other animation or user gesture. + /// This method indicates that there was an error, and you should clean up any + /// work you may have done to prepare to enter full-screen mode. + #[sel(windowDidFailToEnterFullscreen:)] + fn window_did_fail_to_enter_fullscreen(&self, _: id) { + trace_scope!("windowDidFailToEnterFullscreen:"); + self.with_state(|state| { + state.with_window(|window| { + let mut shared_state = + window.lock_shared_state("window_did_fail_to_enter_fullscreen"); + shared_state.in_fullscreen_transition = false; + shared_state.target_fullscreen = None; + }); + if state.initial_fullscreen { + unsafe { + let _: () = msg_send![*state.ns_window, + performSelector:sel!(toggleFullScreen:) + withObject:nil + afterDelay: 0.5 + ]; + }; + } else { + state.with_window(|window| window.restore_state_from_fullscreen()); + } + }); + } + + // Invoked when the occlusion state of the window changes + #[sel(windowDidChangeOcclusionState:)] + fn window_did_change_occlusion_state(&self, _: id) { + trace_scope!("windowDidChangeOcclusionState:"); + unsafe { + self.with_state(|state| { + state.emit_event(WindowEvent::Occluded( + !state + .ns_window + .occlusionState() + .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), + )) + }); + } + } } -} - } -} +); impl WinitWindowDelegate { // This function is definitely unsafe (&self -> &mut state), but labeling that