#![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::ptr;
use objc2::rc::{Retained, WeakId};
use objc2::runtime::{AnyObject, Sel};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
};
use objc2_foundation::{
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject,
NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_delegate::ApplicationDelegate;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed,
ralt_pressed, scancode_to_physicalkey,
};
use super::window::WinitWindow;
use super::window_delegate::WindowDelegate;
use super::DEVICE_ID;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
#[derive(Debug)]
struct CursorState {
visible: bool,
cursor: Retained<NSCursor>,
}
impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
cursor: default_cursor(),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
enum ImeState {
#[default]
Disabled,
Ground,
Preedit,
Committed,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq)]
struct ModLocationMask: u8 {
const LEFT = 0b0001;
const RIGHT = 0b0010;
}
}
impl ModLocationMask {
fn from_location(loc: KeyLocation) -> ModLocationMask {
match loc {
KeyLocation::Left => ModLocationMask::LEFT,
KeyLocation::Right => ModLocationMask::RIGHT,
_ => unreachable!(),
}
}
}
fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
match key {
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
_ => None,
}
}
fn get_right_modifier_code(key: &Key) -> KeyCode {
match key {
Key::Named(NamedKey::Alt) => KeyCode::AltRight,
Key::Named(NamedKey::Control) => KeyCode::ControlRight,
Key::Named(NamedKey::Shift) => KeyCode::ShiftRight,
Key::Named(NamedKey::Super) => KeyCode::SuperRight,
_ => unreachable!(),
}
}
fn get_left_modifier_code(key: &Key) -> KeyCode {
match key {
Key::Named(NamedKey::Alt) => KeyCode::AltLeft,
Key::Named(NamedKey::Control) => KeyCode::ControlLeft,
Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft,
Key::Named(NamedKey::Super) => KeyCode::SuperLeft,
_ => unreachable!(),
}
}
#[derive(Debug)]
pub struct ViewState {
app_delegate: Retained<ApplicationDelegate>,
cursor_state: RefCell<CursorState>,
ime_position: Cell<NSPoint>,
ime_size: Cell<NSSize>,
modifiers: Cell<Modifiers>,
phys_modifiers: RefCell<HashMap<Key, ModLocationMask>>,
tracking_rect: Cell<Option<NSTrackingRectTag>>,
ime_state: Cell<ImeState>,
input_source: RefCell<String>,
ime_allowed: Cell<bool>,
forward_key_to_app: Cell<bool>,
in_key_event: Cell<bool>,
marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool,
_ns_window: WeakId<WinitWindow>,
option_as_alt: Cell<OptionAsAlt>,
}
declare_class!(
pub(super) struct WinitView;
unsafe impl ClassType for WinitView {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
impl DeclaredClass for WinitView {
type Ivars = ViewState;
}
unsafe impl WinitView {
#[method(isFlipped)]
fn is_flipped(&self) -> bool {
true
}
#[method(viewDidMoveToWindow)]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
}
#[method(frameDidChange:)]
fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::Resized(size));
}
#[method(drawRect:)]
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");
if let Some(window) = self.ivars()._ns_window.load() {
self.ivars().app_delegate.handle_redraw(window.id());
}
}
#[method(acceptsFirstResponder)]
fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder");
true
}
#[method_id(touchBar)]
fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar");
None
}
#[method(resetCursorRects)]
fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects");
let bounds = self.bounds();
let cursor_state = self.ivars().cursor_state.borrow();
if cursor_state.visible {
self.addCursorRect_cursor(bounds, &cursor_state.cursor);
} else {
self.addCursorRect_cursor(bounds, &invisible_cursor());
}
}
}
unsafe impl NSTextInputClient for WinitView {
#[method(hasMarkedText)]
fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0
}
#[method(markedRange)]
fn marked_range(&self) -> NSRange {
trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length();
if length > 0 {
NSRange::new(0, length)
} else {
NSRange::new(NSNotFound as NSUInteger, 0)
}
}
#[method(selectedRange)]
fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange");
NSRange::new(NSNotFound as NSUInteger, 0)
}
#[method(setMarkedText:selectedRange:replacementRange:)]
fn set_marked_text(
&self,
string: &NSObject,
selected_range: NSRange,
_replacement_range: NSRange,
) {
trace_scope!("setMarkedText:selectedRange:replacementRange:");
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_attributed_nsstring(string),
string.string(),
)
} else {
let string: *const NSObject = string;
let string: *const NSString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
)
};
*self.ivars().marked_text.borrow_mut() = marked_text;
if self.ivars().ime_state.get() == ImeState::Disabled {
*self.ivars().input_source.borrow_mut() = self.current_input_source();
self.queue_event(WindowEvent::Ime(Ime::Enabled));
}
if unsafe { self.hasMarkedText() } {
self.ivars().ime_state.set(ImeState::Preedit);
} else {
self.ivars().ime_state.set(ImeState::Ground);
}
let cursor_range = if string.is_empty() {
None
} else {
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) };
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8))
};
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[method(unmarkText)]
fn unmark_text(&self) {
trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
if let Some(input_context) = self.inputContext() {
input_context.discardMarkedText();
}
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
if self.is_ime_enabled() {
self.ivars().ime_state.set(ImeState::Ground);
} else {
tracing::warn!("Expected to have IME enabled when receiving unmarkText");
}
}
#[method_id(validAttributesForMarkedText)]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText");
NSArray::new()
}
#[method_id(attributedSubstringForProposedRange:actualRange:)]
fn attributed_substring_for_proposed_range(
&self,
_range: NSRange,
_actual_range: *mut NSRange,
) -> Option<Retained<NSAttributedString>> {
trace_scope!("attributedSubstringForProposedRange:actualRange:");
None
}
#[method(characterIndexForPoint:)]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:");
0
}
#[method(firstRectForCharacterRange:actualRange:)]
fn first_rect_for_character_range(
&self,
_range: NSRange,
_actual_range: *mut NSRange,
) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:");
let position = self.ivars().ime_position.get();
let size = self.ivars().ime_size.get();
if position.x.is_nan() || position.y.is_nan() || size.width.is_nan() || size.height.is_nan() {
tracing::warn!("Invalid IME coordinates in firstRectForCharacterRange");
let default_rect = NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(1.0, 1.0));
return self.window()
.convertRectToScreen(self.convertRect_toView(default_rect, None));
}
let rect = NSRect::new(position, size);
self.window()
.convertRectToScreen(self.convertRect_toView(rect, None))
}
#[method(insertText:replacementRange:)]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
trace_scope!("insertText:replacementRange:");
let string = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
unsafe { &*string }.string().to_string()
} else {
let string: *const NSObject = string;
let string: *const NSString = string.cast();
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().is_some_and(|c| c.is_control());
let has_marked_text = unsafe { self.hasMarkedText() };
let is_in_key_event = self.ivars().in_key_event.get();
if self.is_ime_enabled() {
if has_marked_text && !is_control {
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.ivars().ime_state.set(ImeState::Committed);
} else if !is_control && !string.is_empty() && !is_in_key_event {
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.ivars().ime_state.set(ImeState::Committed);
}
} else if !is_control && !string.is_empty() && !is_in_key_event {
self.queue_event(WindowEvent::Ime(Ime::Enabled));
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.ivars().ime_state.set(ImeState::Committed);
}
}
#[method(doCommandBySelector:)]
fn do_command_by_selector(&self, _command: Sel) {
trace_scope!("doCommandBySelector:");
if self.ivars().ime_state.get() == ImeState::Committed {
return;
}
self.ivars().forward_key_to_app.set(true);
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit
{
self.ivars().ime_state.set(ImeState::Ground);
}
}
}
unsafe impl WinitView {
#[method(keyDown:)]
fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:");
self.mark_input_received();
self.ivars().in_key_event.set(true);
{
let mut prev_input_source = self.ivars().input_source.borrow_mut();
let current_input_source = self.current_input_source();
if *prev_input_source != current_input_source && self.is_ime_enabled() {
*prev_input_source = current_input_source;
drop(prev_input_source);
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
}
let old_ime_state = self.ivars().ime_state.get();
self.ivars().forward_key_to_app.set(false);
let event = replace_event(event, self.option_as_alt());
if self.ivars().ime_allowed.get() {
let events_for_nsview = NSArray::from_slice(&[&*event]);
unsafe { self.interpretKeyEvents(&events_for_nsview) };
if self.ivars().ime_state.get() == ImeState::Committed {
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
}
}
self.update_modifiers(&event, false);
let had_ime_input = match self.ivars().ime_state.get() {
ImeState::Committed => {
self.ivars().ime_state.set(ImeState::Ground);
true
}
ImeState::Preedit => true,
_ => old_ime_state != self.ivars().ime_state.get(),
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: key_event,
is_synthetic: false,
});
}
self.ivars().in_key_event.set(false);
}
#[method(keyUp:)]
fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:");
self.ivars().in_key_event.set(true);
let event = replace_event(event, self.option_as_alt());
self.update_modifiers(&event, false);
if matches!(
self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: create_key_event(&event, false, false, None),
is_synthetic: false,
});
}
self.ivars().in_key_event.set(false);
}
#[method(flagsChanged:)]
fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:");
self.update_modifiers(event, true);
}
#[method(insertTab:)]
fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:");
let window = self.window();
if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self {
window.selectNextKeyView(Some(self))
}
}
}
#[method(insertBackTab:)]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:");
let window = self.window();
if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self {
window.selectPreviousKeyView(Some(self))
}
}
}
#[method(cancelOperation:)]
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:");
let event = NSApplication::sharedApplication(mtm)
.currentEvent()
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
#[method(mouseDown:)]
fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:");
self.mark_input_received();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[method(mouseUp:)]
fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[method(rightMouseDown:)]
fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:");
self.mark_input_received();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[method(rightMouseUp:)]
fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[method(otherMouseDown:)]
fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:");
self.mark_input_received();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[method(otherMouseUp:)]
fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[method(mouseMoved:)]
fn mouse_moved(&self, event: &NSEvent) {
self.mark_input_received();
self.mouse_motion(event);
}
#[method(mouseDragged:)]
fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(rightMouseDragged:)]
fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(otherMouseDragged:)]
fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[method(mouseEntered:)]
fn mouse_entered(&self, _event: &NSEvent) {
trace_scope!("mouseEntered:");
self.queue_event(WindowEvent::CursorEntered {
device_id: DEVICE_ID,
});
}
#[method(mouseExited:)]
fn mouse_exited(&self, _event: &NSEvent) {
trace_scope!("mouseExited:");
self.queue_event(WindowEvent::CursorLeft {
device_id: DEVICE_ID,
});
}
#[method(scrollWheel:)]
fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:");
self.mark_input_received();
self.mouse_motion(event);
let delta = {
let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) };
if unsafe { event.hasPreciseScrollingDeltas() } {
let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor());
MouseScrollDelta::PixelDelta(delta)
} else {
MouseScrollDelta::LineDelta(x as f32, y as f32)
}
};
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended,
_ => match unsafe { event.phase() } {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Ended | NSEventPhase::Cancelled => TouchPhase::Ended,
_ => TouchPhase::Moved,
},
};
self.update_modifiers(event, false);
self.queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
phase,
});
}
#[method(magnifyWithEvent:)]
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
self.mouse_motion(event);
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } {
NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Changed => TouchPhase::Moved,
NSEventPhase::Cancelled => TouchPhase::Cancelled,
NSEventPhase::Ended => TouchPhase::Ended,
_ => return,
};
self.queue_event(WindowEvent::PinchGesture {
device_id: DEVICE_ID,
delta: unsafe { event.magnification() },
phase,
});
}
#[method(smartMagnifyWithEvent:)]
fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture {
device_id: DEVICE_ID,
});
}
#[method(rotateWithEvent:)]
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
self.mouse_motion(event);
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } {
NSEventPhase::Began => TouchPhase::Started,
NSEventPhase::Changed => TouchPhase::Moved,
NSEventPhase::Cancelled => TouchPhase::Cancelled,
NSEventPhase::Ended => TouchPhase::Ended,
_ => return,
};
self.queue_event(WindowEvent::RotationGesture {
device_id: DEVICE_ID,
delta: unsafe { event.rotation() },
phase,
});
}
#[method(pressureChangeWithEvent:)]
fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:");
self.queue_event(WindowEvent::TouchpadPressure {
device_id: DEVICE_ID,
pressure: unsafe { event.pressure() },
stage: unsafe { event.stage() } as i64,
});
}
#[method(_wantsKeyDownForEvent:)]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:");
true
}
#[method(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse
}
}
);
impl WinitView {
pub(super) fn new(
app_delegate: &ApplicationDelegate,
window: &WinitWindow,
accepts_first_mouse: bool,
option_as_alt: OptionAsAlt,
) -> Retained<Self> {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState {
app_delegate: app_delegate.retain(),
cursor_state: Default::default(),
ime_position: Default::default(),
ime_size: Default::default(),
modifiers: Default::default(),
phys_modifiers: Default::default(),
tracking_rect: Default::default(),
ime_state: Default::default(),
input_source: Default::default(),
ime_allowed: Default::default(),
forward_key_to_app: Default::default(),
in_key_event: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
this.setPostsFrameChangedNotifications(true);
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
unsafe {
notification_center.addObserver_selector_name_object(
&this,
sel!(frameDidChange:),
Some(NSViewFrameDidChangeNotification),
Some(&this),
)
}
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Retained<WinitWindow> {
self.ivars()
._ns_window
.load()
.expect("view to have a window")
}
fn queue_event(&self, event: WindowEvent) {
self.ivars()
.app_delegate
.queue_window_event(self.window().id(), event);
}
fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().app_delegate.queue_device_event(event);
}
#[inline]
fn mark_input_received(&self) {
unsafe {
if let Some(delegate) = self.window().delegate() {
let delegate_ptr = Retained::as_ptr(&delegate) as *const WindowDelegate;
(*delegate_ptr).mark_input_received();
}
}
}
fn scale_factor(&self) -> f64 {
self.window().backingScaleFactor() as f64
}
fn is_ime_enabled(&self) -> bool {
!matches!(self.ivars().ime_state.get(), ImeState::Disabled)
}
fn current_input_source(&self) -> String {
self.inputContext()
.expect("input context")
.selectedKeyboardInputSource()
.map(|input_source| input_source.to_string())
.unwrap_or_default()
}
pub(super) fn cursor_icon(&self) -> Retained<NSCursor> {
self.ivars().cursor_state.borrow().cursor.clone()
}
pub(super) fn set_cursor_icon(&self, icon: Retained<NSCursor>) {
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
cursor_state.cursor = icon;
}
pub(super) fn set_cursor_visible(&self, visible: bool) -> bool {
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
if visible != cursor_state.visible {
cursor_state.visible = visible;
true
} else {
false
}
}
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
if self.ivars().ime_allowed.get() == ime_allowed {
return;
}
self.ivars().ime_allowed.set(ime_allowed);
if self.ivars().ime_allowed.get() {
return;
}
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
if self.ivars().ime_state.get() != ImeState::Disabled {
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
}
pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) {
if position.x.is_nan()
|| position.y.is_nan()
|| position.x < 0.0
|| position.y < 0.0
{
tracing::warn!("Invalid IME cursor position: {:?}", position);
return;
}
if size.width.is_nan()
|| size.height.is_nan()
|| size.width <= 0.0
|| size.height <= 0.0
{
tracing::warn!("Invalid IME cursor size: {:?}", size);
return;
}
let current_position = self.ivars().ime_position.get();
let current_size = self.ivars().ime_size.get();
if (current_position.x - position.x).abs() < 1.0
&& (current_position.y - position.y).abs() < 1.0
&& (current_size.width - size.width).abs() < 1.0
&& (current_size.height - size.height).abs() < 1.0
{
return; }
self.ivars().ime_position.set(position);
self.ivars().ime_size.set(size);
if let Some(input_context) = self.inputContext() {
if self.is_ime_enabled() {
input_context.invalidateCharacterCoordinates();
}
}
}
pub(super) fn reset_modifiers(&self) {
if !self.ivars().modifiers.get().state().is_empty() {
self.ivars().modifiers.set(Modifiers::default());
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
}
}
pub(super) fn set_option_as_alt(&self, value: OptionAsAlt) {
self.ivars().option_as_alt.set(value)
}
pub(super) fn option_as_alt(&self) -> OptionAsAlt {
self.ivars().option_as_alt.get()
}
fn update_modifiers(&self, ns_event: &NSEvent, is_flags_changed_event: bool) {
use ElementState::{Pressed, Released};
let current_modifiers = event_mods(ns_event);
let prev_modifiers = self.ivars().modifiers.get();
self.ivars().modifiers.set(current_modifiers);
'send_event: {
if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32);
let mut event =
create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
let Some(event_modifier) = key_to_modifier(&key) else {
break 'send_event;
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod = phys_mod_state
.entry(key)
.or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
if !is_active {
event.state = Released;
if phys_mod.contains(ModLocationMask::LEFT) {
let mut event = event.clone();
event.location = KeyLocation::Left;
event.physical_key =
get_left_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
if phys_mod.contains(ModLocationMask::RIGHT) {
event.location = KeyLocation::Right;
event.physical_key =
get_right_modifier_code(&event.logical_key).into();
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
*phys_mod = ModLocationMask::empty();
} else {
if *phys_mod == location_mask {
event.state = Pressed;
} else {
phys_mod.toggle(location_mask);
let is_pressed = phys_mod.contains(location_mask);
event.state = if is_pressed { Pressed } else { Released };
}
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
drop(phys_mod_state);
for event in events {
self.queue_event(event);
}
}
}
if prev_modifiers == current_modifiers {
return;
}
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
}
fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
let button = mouse_button(event);
self.update_modifiers(event, false);
self.queue_event(WindowEvent::MouseInput {
device_id: DEVICE_ID,
state: button_state,
button,
});
}
fn mouse_motion(&self, event: &NSEvent) {
let window_point = unsafe { event.locationInWindow() };
let view_point = self.convertPoint_fromView(window_point, None);
let frame = self.frame();
if view_point.x.is_sign_negative()
|| view_point.y.is_sign_negative()
|| view_point.x > frame.size.width
|| view_point.y > frame.size.height
{
let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() };
if mouse_buttons_down == 0 {
return;
}
}
let view_point = LogicalPosition::new(view_point.x, view_point.y);
self.update_modifiers(event, false);
self.queue_event(WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: view_point.to_physical(self.scale_factor()),
});
}
}
fn mouse_button(event: &NSEvent) -> MouseButton {
match unsafe { event.buttonNumber() } {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
3 => MouseButton::Back,
4 => MouseButton::Forward,
n => MouseButton::Other(n as u16),
}
}
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEvent> {
let ev_mods = event_mods(event).state;
let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
OptionAsAlt::Both if ev_mods.alt_key() => true,
_ => false,
} && !ev_mods.control_key()
&& !ev_mods.super_key();
if ignore_alt_characters {
let ns_chars = unsafe {
event
.charactersIgnoringModifiers()
.expect("expected characters to be non-null")
};
unsafe {
NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
event.r#type(),
event.locationInWindow(),
event.modifierFlags(),
event.timestamp(),
event.windowNumber(),
None,
&ns_chars,
&ns_chars,
event.isARepeat(),
event.keyCode(),
)
.unwrap()
}
} else {
event.copy()
}
}
pub(crate) unsafe fn get_window_delegate(
view_ptr: *mut std::ffi::c_void,
) -> Option<Retained<WindowDelegate>> {
if view_ptr.is_null() {
return None;
}
unsafe {
let view = view_ptr as *const WinitView;
let view = &*view;
if let Some(window) = view.ivars()._ns_window.load() {
if let Some(delegate) = window.delegate() {
let delegate_ptr = Retained::as_ptr(&delegate) as *mut WindowDelegate;
Retained::retain(delegate_ptr)
} else {
None
}
} else {
None
}
}
}