macOS: Implement NSTextInputClient (#518)

Fixes #263
This commit is contained in:
Francesca Frangipane 2018-05-17 21:28:30 -04:00 committed by GitHub
parent 8440091a4e
commit dec728cfa2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 648 additions and 147 deletions

View file

@ -13,6 +13,9 @@
- On macOS, `Window::get_current_monitor` now returns accurate values. - On macOS, `Window::get_current_monitor` now returns accurate values.
- Added `WindowBuilderExt::with_resize_increments` to macOS. - Added `WindowBuilderExt::with_resize_increments` to macOS.
- **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`. - **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`.
- macOS keyboard handling has been overhauled, allowing for the use of dead keys, IME, etc. Right modifier keys are also no longer reported as being left.
- Added the `Window::set_ime_spot(x: i32, y: i32)` method, which is implemented on X11 and macOS.
- **Breaking**: `os::unix::WindowExt::send_xim_spot(x: i16, y: i16)` no longer exists. Switch to the new `Window::set_ime_spot(x: i32, y: i32)`, which has equivalent functionality.
# Version 0.14.0 (2018-05-09) # Version 0.14.0 (2018-05-09)

View file

@ -80,7 +80,6 @@
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window. //! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the window.
//! //!
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "windows"))]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate libc; extern crate libc;

View file

@ -94,8 +94,6 @@ pub trait WindowExt {
fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>; fn get_xlib_xconnection(&self) -> Option<Arc<XConnection>>;
fn send_xim_spot(&self, x: i16, y: i16);
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`. /// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
/// ///
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
@ -165,12 +163,6 @@ impl WindowExt for Window {
} }
} }
fn send_xim_spot(&self, x: i16, y: i16) {
if let LinuxWindow::X(ref w) = self.window {
w.send_xim_spot(x, y);
}
}
#[inline] #[inline]
fn get_wayland_surface(&self) -> Option<*mut raw::c_void> { fn get_wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window { match self.window {

View file

@ -328,6 +328,11 @@ impl Window {
// N/A // N/A
} }
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
// N/A
}
#[inline] #[inline]
pub fn get_current_monitor(&self) -> RootMonitorId { pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId{inner: MonitorId} RootMonitorId{inner: MonitorId}

View file

@ -548,6 +548,11 @@ impl Window {
// N/A // N/A
} }
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
// N/A
}
#[inline] #[inline]
pub fn get_current_monitor(&self) -> ::MonitorId { pub fn get_current_monitor(&self) -> ::MonitorId {
::MonitorId{inner: MonitorId} ::MonitorId{inner: MonitorId}

View file

@ -375,6 +375,11 @@ impl Window {
// N/A // N/A
} }
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
// N/A
}
#[inline] #[inline]
pub fn get_current_monitor(&self) -> RootMonitorId { pub fn get_current_monitor(&self) -> RootMonitorId {
RootMonitorId{inner: MonitorId} RootMonitorId{inner: MonitorId}

View file

@ -310,6 +310,14 @@ impl Window {
} }
} }
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
match self {
&Window::X(ref w) => w.send_xim_spot(x as i16, y as i16),
&Window::Wayland(_) => (),
}
}
#[inline] #[inline]
pub fn get_current_monitor(&self) -> RootMonitorId { pub fn get_current_monitor(&self) -> RootMonitorId {
match self { match self {

View file

@ -6,6 +6,7 @@ use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak}; use std::sync::{Arc, Mutex, Weak};
use super::window::Window2; use super::window::Window2;
use std; use std;
use std::os::raw::*;
use super::DeviceId; use super::DeviceId;
@ -315,127 +316,50 @@ impl EventsLoop {
}); });
match event_type { match event_type {
appkit::NSKeyDown => {
let mut events = std::collections::VecDeque::new();
let received_c_str = foundation::NSString::UTF8String(ns_event.characters());
let received_str = std::ffi::CStr::from_ptr(received_c_str);
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
let state = ElementState::Pressed;
let code = NSEvent::keyCode(ns_event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: vkey,
modifiers: event_mods(ns_event),
},
};
for received_char in std::str::from_utf8(received_str.to_bytes()).unwrap().chars() {
let window_event = WindowEvent::ReceivedCharacter(received_char);
events.push_back(into_event(window_event));
}
self.shared.pending_events.lock().unwrap().extend(events.into_iter());
Some(into_event(window_event))
},
appkit::NSKeyUp => {
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
let state = ElementState::Released;
let code = NSEvent::keyCode(ns_event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: vkey,
modifiers: event_mods(ns_event),
},
};
Some(into_event(window_event))
},
appkit::NSFlagsChanged => { appkit::NSFlagsChanged => {
unsafe fn modifier_event(event: cocoa::base::id,
keymask: NSEventModifierFlags,
key: events::VirtualKeyCode,
key_pressed: bool) -> Option<WindowEvent>
{
if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) {
let state = ElementState::Pressed;
let code = NSEvent::keyCode(event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: Some(key),
modifiers: event_mods(event),
},
};
Some(window_event)
} else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) {
let state = ElementState::Released;
let code = NSEvent::keyCode(event) as u32;
let window_event = WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: state,
scancode: code,
virtual_keycode: Some(key),
modifiers: event_mods(event),
},
};
Some(window_event)
} else {
None
}
}
let mut events = std::collections::VecDeque::new(); let mut events = std::collections::VecDeque::new();
if let Some(window_event) = modifier_event(ns_event,
if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSShiftKeyMask, NSEventModifierFlags::NSShiftKeyMask,
events::VirtualKeyCode::LShift, self.modifiers.shift_pressed,
self.modifiers.shift_pressed) ) {
{
self.modifiers.shift_pressed = !self.modifiers.shift_pressed; self.modifiers.shift_pressed = !self.modifiers.shift_pressed;
events.push_back(into_event(window_event)); events.push_back(into_event(window_event));
} }
if let Some(window_event) = modifier_event(ns_event, if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSControlKeyMask, NSEventModifierFlags::NSControlKeyMask,
events::VirtualKeyCode::LControl, self.modifiers.ctrl_pressed,
self.modifiers.ctrl_pressed) ) {
{
self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed; self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed;
events.push_back(into_event(window_event)); events.push_back(into_event(window_event));
} }
if let Some(window_event) = modifier_event(ns_event, if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSCommandKeyMask, NSEventModifierFlags::NSCommandKeyMask,
events::VirtualKeyCode::LWin, self.modifiers.win_pressed,
self.modifiers.win_pressed) ) {
{
self.modifiers.win_pressed = !self.modifiers.win_pressed; self.modifiers.win_pressed = !self.modifiers.win_pressed;
events.push_back(into_event(window_event)); events.push_back(into_event(window_event));
} }
if let Some(window_event) = modifier_event(ns_event, if let Some(window_event) = modifier_event(
ns_event,
NSEventModifierFlags::NSAlternateKeyMask, NSEventModifierFlags::NSAlternateKeyMask,
events::VirtualKeyCode::LAlt, self.modifiers.alt_pressed,
self.modifiers.alt_pressed) ) {
{
self.modifiers.alt_pressed = !self.modifiers.alt_pressed; self.modifiers.alt_pressed = !self.modifiers.alt_pressed;
events.push_back(into_event(window_event)); events.push_back(into_event(window_event));
} }
let event = events.pop_front(); let event = events.pop_front();
self.shared.pending_events.lock().unwrap().extend(events.into_iter()); self.shared.pending_events
.lock()
.unwrap()
.extend(events.into_iter());
event event
}, },
@ -618,8 +542,7 @@ impl Proxy {
} }
} }
pub fn to_virtual_key_code(code: c_ushort) -> Option<events::VirtualKeyCode> {
fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
Some(match code { Some(match code {
0x00 => events::VirtualKeyCode::A, 0x00 => events::VirtualKeyCode::A,
0x01 => events::VirtualKeyCode::S, 0x01 => events::VirtualKeyCode::S,
@ -755,7 +678,7 @@ fn to_virtual_key_code(code: u16) -> Option<events::VirtualKeyCode> {
}) })
} }
fn event_mods(event: cocoa::base::id) -> ModifiersState { pub fn event_mods(event: cocoa::base::id) -> ModifiersState {
let flags = unsafe { let flags = unsafe {
NSEvent::modifierFlags(event) NSEvent::modifierFlags(event)
}; };
@ -767,5 +690,30 @@ fn event_mods(event: cocoa::base::id) -> ModifiersState {
} }
} }
unsafe fn modifier_event(
ns_event: cocoa::base::id,
keymask: NSEventModifierFlags,
key_pressed: bool,
) -> Option<WindowEvent> {
if !key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|| key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) {
let state = ElementState::Released;
let keycode = NSEvent::keyCode(ns_event);
let scancode = keycode as u32;
let virtual_keycode = to_virtual_key_code(keycode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode,
virtual_keycode,
modifiers: event_mods(ns_event),
},
})
} else {
None
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs. // Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);

74
src/platform/macos/ffi.rs Normal file
View file

@ -0,0 +1,74 @@
// TODO: Upstream these
#![allow(non_snake_case, non_upper_case_globals)]
use cocoa::base::{class, id};
use cocoa::foundation::{NSInteger, NSUInteger};
use objc;
pub const NSNotFound: NSInteger = NSInteger::max_value();
#[repr(C)]
pub struct NSRange {
pub location: NSUInteger,
pub length: NSUInteger,
}
impl NSRange {
#[inline]
pub fn new(location: NSUInteger, length: NSUInteger) -> NSRange {
NSRange { location, length }
}
}
unsafe impl objc::Encode for NSRange {
fn encode() -> objc::Encoding {
let encoding = format!(
// TODO: Verify that this is correct
"{{NSRange={}{}}}",
NSUInteger::encode().as_str(),
NSUInteger::encode().as_str(),
);
unsafe { objc::Encoding::from_str(&encoding) }
}
}
pub trait NSMutableAttributedString: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class("NSMutableAttributedString"), alloc]
}
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
unsafe fn initWithString(self, string: id) -> id;
unsafe fn initWithAttributedString(self, string: id) -> id;
unsafe fn string(self) -> id; // *mut NSString
unsafe fn mutableString(self) -> id; // *mut NSMutableString
unsafe fn length(self) -> NSUInteger;
}
impl NSMutableAttributedString for id {
unsafe fn init(self) -> id {
msg_send![self, init]
}
unsafe fn initWithString(self, string: id) -> id {
msg_send![self, initWithString:string]
}
unsafe fn initWithAttributedString(self, string: id) -> id {
msg_send![self, initWithAttributedString:string]
}
unsafe fn string(self) -> id {
msg_send![self, string]
}
unsafe fn mutableString(self) -> id {
msg_send![self, mutableString]
}
unsafe fn length(self) -> NSUInteger {
msg_send![self, length]
}
}

View file

@ -38,5 +38,8 @@ impl Window {
} }
mod events_loop; mod events_loop;
mod ffi;
mod monitor; mod monitor;
mod util;
mod view;
mod window; mod window;

View file

@ -0,0 +1,38 @@
use cocoa::appkit::NSWindowStyleMask;
use cocoa::base::{class, id, nil};
use cocoa::foundation::{NSRect, NSUInteger};
use core_graphics::display::CGDisplay;
use platform::platform::ffi;
use platform::platform::window::IdRef;
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
location: ffi::NSNotFound as NSUInteger,
length: 0,
};
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
pub fn bottom_left_to_top_left(rect: NSRect) -> i32 {
(CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)) as _
}
pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) {
use cocoa::appkit::NSWindow;
window.setStyleMask_(mask);
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}
pub unsafe fn create_input_context(view: id) -> IdRef {
let input_context: id = msg_send![class("NSTextInputContext"), alloc];
let input_context: id = msg_send![input_context, initWithClient:view];
IdRef::new(input_context)
}
#[allow(dead_code)]
pub unsafe fn open_emoji_picker() {
let app: id = msg_send![class("NSApplication"), sharedApplication];
let _: () = msg_send![app, orderFrontCharacterPalette:nil];
}

411
src/platform/macos/view.rs Normal file
View file

@ -0,0 +1,411 @@
// This is a pretty close port of the implementation in GLFW:
// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m
use std::{slice, str};
use std::boxed::Box;
use std::collections::VecDeque;
use std::os::raw::*;
use std::sync::Weak;
use cocoa::base::{class, id, nil};
use cocoa::appkit::NSWindow;
use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Protocol, Sel, BOOL};
use {ElementState, Event, KeyboardInput, WindowEvent, WindowId};
use platform::platform::events_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code};
use platform::platform::util;
use platform::platform::ffi::*;
use platform::platform::window::{get_window_id, IdRef};
struct ViewState {
window: id,
shared: Weak<Shared>,
ime_spot: Option<(i32, i32)>,
raw_characters: Option<String>,
}
pub fn new_view(window: id, shared: Weak<Shared>) -> IdRef {
let state = ViewState { window, shared, ime_spot: None, raw_characters: None };
unsafe {
// This is free'd in `dealloc`
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
let view: id = msg_send![VIEW_CLASS.0, alloc];
IdRef::new(msg_send![view, initWithWinit:state_ptr])
}
}
pub fn set_ime_spot(view: id, input_context: id, x: i32, y: i32) {
unsafe {
let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let base_x = content_rect.origin.x as i32;
let base_y = (content_rect.origin.y + content_rect.size.height) as i32;
state.ime_spot = Some((base_x + x, base_y - y));
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
}
struct ViewClass(*const Class);
unsafe impl Send for ViewClass {}
unsafe impl Sync for ViewClass {}
lazy_static! {
static ref VIEW_CLASS: ViewClass = unsafe {
let superclass = Class::get("NSView").unwrap();
let mut decl = ClassDecl::new("WinitView", superclass).unwrap();
decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel));
decl.add_method(
sel!(initWithWinit:),
init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id,
);
decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL);
decl.add_method(
sel!(markedRange),
marked_range as extern fn(&Object, Sel) -> NSRange,
);
decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange);
decl.add_method(
sel!(setMarkedText:selectedRange:replacementRange:),
set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange),
);
decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel));
decl.add_method(
sel!(validAttributesForMarkedText),
valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id,
);
decl.add_method(
sel!(attributedSubstringForProposedRange:actualRange:),
attributed_substring_for_proposed_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> id,
);
decl.add_method(
sel!(insertText:replacementRange:),
insert_text as extern fn(&Object, Sel, id, NSRange),
);
decl.add_method(
sel!(characterIndexForPoint:),
character_index_for_point as extern fn(&Object, Sel, NSPoint) -> NSUInteger,
);
decl.add_method(
sel!(firstRectForCharacterRange:actualRange:),
first_rect_for_character_range
as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect,
);
decl.add_method(
sel!(doCommandBySelector:),
do_command_by_selector as extern fn(&Object, Sel, Sel),
);
decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id));
decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id));
decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id));
decl.add_ivar::<*mut c_void>("winitState");
decl.add_ivar::<id>("markedText");
let protocol = Protocol::get("NSTextInputClient").unwrap();
decl.add_protocol(&protocol);
ViewClass(decl.register())
};
}
extern fn dealloc(this: &Object, _sel: Sel) {
unsafe {
let state: *mut c_void = *this.get_ivar("winitState");
let marked_text: id = *this.get_ivar("markedText");
let _: () = msg_send![marked_text, release];
Box::from_raw(state as *mut ViewState);
}
}
extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
unsafe {
let this: id = msg_send![this, init];
if this != nil {
(*this).set_ivar("winitState", state);
let marked_text = <id as NSMutableAttributedString>::init(
NSMutableAttributedString::alloc(nil),
);
(*this).set_ivar("markedText", marked_text);
}
this
}
}
extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL {
unsafe {
let marked_text: id = *this.get_ivar("markedText");
(marked_text.length() > 0) as i8
}
}
extern fn marked_range(this: &Object, _sel: Sel) -> NSRange {
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let length = marked_text.length();
if length > 0 {
NSRange::new(0, length - 1)
} else {
util::EMPTY_RANGE
}
}
}
extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange {
util::EMPTY_RANGE
}
extern fn set_marked_text(
this: &mut Object,
_sel: Sel,
string: id,
_selected_range: NSRange,
_replacement_range: NSRange,
) {
unsafe {
let marked_text_ref: &mut id = this.get_mut_ivar("markedText");
let _: () = msg_send![(*marked_text_ref), release];
let marked_text = NSMutableAttributedString::alloc(nil);
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;
}
}
extern fn unmark_text(this: &Object, _sel: Sel) {
unsafe {
let marked_text: id = *this.get_ivar("markedText");
let mutable_string = marked_text.mutableString();
let _: () = msg_send![mutable_string, setString:""];
let input_context: id = msg_send![this, inputContext];
let _: () = msg_send![input_context, discardMarkedText];
}
}
extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id {
unsafe { msg_send![class("NSArray"), array] }
}
extern fn attributed_substring_for_proposed_range(
_this: &Object,
_sel: Sel,
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> id {
nil
}
extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger {
0
}
extern fn first_rect_for_character_range(
this: &Object,
_sel: Sel,
_range: NSRange,
_actual_range: *mut c_void, // *mut NSRange
) -> NSRect {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let (x, y) = state.ime_spot.unwrap_or_else(|| {
let content_rect = NSWindow::contentRectForFrameRect_(
state.window,
NSWindow::frame(state.window),
);
let x = content_rect.origin.x;
let y = util::bottom_left_to_top_left(content_rect);
(x as i32, y as i32)
});
NSRect::new(
NSPoint::new(x as _, y as _),
NSSize::new(0.0, 0.0),
)
}
}
extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let has_attr = msg_send![string, isKindOfClass:class("NSAttributedString")];
let characters = if has_attr {
// This is a *mut NSAttributedString
msg_send![string, string]
} else {
// This is already a *mut NSString
string
};
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
// We don't need this now, but it's here if that changes.
//let event: id = msg_send![class("NSApp"), currentEvent];
let mut events = VecDeque::with_capacity(characters.len());
for character in string.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter(character),
});
}
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
}
}
extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character
// happens, i.e. newlines, tabs, and Ctrl+C.
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let shared = if let Some(shared) = state.shared.upgrade() {
shared
} else {
return;
};
let mut events = VecDeque::with_capacity(1);
if command == sel!(insertNewline:) {
// The `else` condition would emit the same character, but I'm keeping this here both...
// 1) as a reminder for how `doCommandBySelector` works
// 2) to make the newline character explicit (...not that it matters)
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter('\n'),
});
} else {
let raw_characters = state.raw_characters.take();
if let Some(raw_characters) = raw_characters {
for character in raw_characters.chars() {
events.push_back(Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::ReceivedCharacter(character),
});
}
}
};
shared.pending_events
.lock()
.unwrap()
.append(&mut events);
}
}
extern fn key_down(this: &Object, _sel: Sel, event: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(keycode);
let scancode = keycode as u32;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Pressed,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
state.raw_characters = {
let characters: id = msg_send![event, characters];
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);
Some(string.to_owned())
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
let array: id = msg_send![class("NSArray"), arrayWithObject:event];
let (): _ = msg_send![this, interpretKeyEvents:array];
}
}
extern fn key_up(this: &Object, _sel: Sel, event: id) {
unsafe {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_key_code(keycode);
let scancode = keycode as u32;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Released,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
},
};
if let Some(shared) = state.shared.upgrade() {
shared.pending_events
.lock()
.unwrap()
.push_back(window_event);
}
}
}
extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) {
unsafe {
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
let this_ptr = this as *const _ as *mut _;
if first_responder == this_ptr {
let (): _ = msg_send![window, selectNextKeyView:this];
}
}
}
extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) {
unsafe {
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
let this_ptr = this as *const _ as *mut _;
if first_responder == this_ptr {
let (): _ = msg_send![window, selectPreviousKeyView:this];
}
}
}

View file

@ -25,6 +25,8 @@ use std::sync::Weak;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use super::events_loop::{EventsLoop, Shared}; use super::events_loop::{EventsLoop, Shared};
use platform::platform::util;
use platform::platform::view::{new_view, set_ime_spot};
use window::MonitorId as RootMonitorId; use window::MonitorId as RootMonitorId;
@ -56,15 +58,14 @@ impl DelegateState {
let curr_mask = self.window.styleMask(); let curr_mask = self.window.styleMask();
if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) { if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) {
self.window util::set_style_mask(*self.window, *self.view, NSWindowStyleMask::NSResizableWindowMask);
.setStyleMask_(NSWindowStyleMask::NSResizableWindowMask);
} }
let is_zoomed: BOOL = msg_send![*self.window, isZoomed]; let is_zoomed: BOOL = msg_send![*self.window, isZoomed];
// Roll back temp styles // Roll back temp styles
if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) { if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) {
self.window.setStyleMask_(curr_mask); util::set_style_mask(*self.window, *self.view, curr_mask);
} }
is_zoomed != 0 is_zoomed != 0
@ -79,7 +80,7 @@ impl DelegateState {
let save_style_opt = self.save_style_mask.take(); let save_style_opt = self.save_style_mask.take();
if let Some(save_style) = save_style_opt { if let Some(save_style) = save_style_opt {
self.window.setStyleMask_(save_style); util::set_style_mask(*self.window, *self.view, save_style);
} }
win_attribs.maximized win_attribs.maximized
@ -167,7 +168,7 @@ impl WindowDelegate {
unsafe fn emit_move_event(state: &mut DelegateState) { unsafe fn emit_move_event(state: &mut DelegateState) {
let frame_rect = NSWindow::frame(*state.window); let frame_rect = NSWindow::frame(*state.window);
let x = frame_rect.origin.x as _; let x = frame_rect.origin.x as _;
let y = Window2::bottom_left_to_top_left(frame_rect); let y = util::bottom_left_to_top_left(frame_rect);
let moved = state.previous_position != Some((x, y)); let moved = state.previous_position != Some((x, y));
if moved { if moved {
state.previous_position = Some((x, y)); state.previous_position = Some((x, y));
@ -495,6 +496,7 @@ pub struct Window2 {
pub view: IdRef, pub view: IdRef,
pub window: IdRef, pub window: IdRef,
pub delegate: WindowDelegate, pub delegate: WindowDelegate,
pub input_context: IdRef,
} }
unsafe impl Send for Window2 {} unsafe impl Send for Window2 {}
@ -582,7 +584,7 @@ impl Window2 {
return Err(OsError(format!("Couldn't create NSWindow"))); return Err(OsError(format!("Couldn't create NSWindow")));
}, },
}; };
let view = match Window2::create_view(*window) { let view = match Window2::create_view(*window, Weak::clone(&shared)) {
Some(view) => view, Some(view) => view,
None => { None => {
let _: () = unsafe { msg_send![autoreleasepool, drain] }; let _: () = unsafe { msg_send![autoreleasepool, drain] };
@ -590,6 +592,8 @@ impl Window2 {
}, },
}; };
let input_context = unsafe { util::create_input_context(*view) };
unsafe { unsafe {
if win_attribs.transparent { if win_attribs.transparent {
(*window as id).setOpaque_(NO); (*window as id).setOpaque_(NO);
@ -628,6 +632,7 @@ impl Window2 {
view: view, view: view,
window: window, window: window,
delegate: WindowDelegate::new(ds), delegate: WindowDelegate::new(ds),
input_context,
}; };
// Set fullscreen mode after we setup everything // Set fullscreen mode after we setup everything
@ -681,13 +686,10 @@ impl Window2 {
static INIT: std::sync::Once = std::sync::ONCE_INIT; static INIT: std::sync::Once = std::sync::ONCE_INIT;
INIT.call_once(|| unsafe { INIT.call_once(|| unsafe {
extern fn on_key_down(_this: &Object, _sel: Sel, _id: id) {}
let window_superclass = Class::get("NSWindow").unwrap(); let window_superclass = Class::get("NSWindow").unwrap();
let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap();
decl.add_method(sel!(canBecomeMainWindow), yes as extern fn(&Object, Sel) -> BOOL); decl.add_method(sel!(canBecomeMainWindow), yes as extern fn(&Object, Sel) -> BOOL);
decl.add_method(sel!(canBecomeKeyWindow), yes as extern fn(&Object, Sel) -> BOOL); decl.add_method(sel!(canBecomeKeyWindow), yes as extern fn(&Object, Sel) -> BOOL);
decl.add_method(sel!(keyDown:), on_key_down as extern fn(&Object, Sel, id));
WINDOW2_CLASS = decl.register(); WINDOW2_CLASS = decl.register();
}); });
@ -788,12 +790,13 @@ impl Window2 {
} }
} }
fn create_view(window: id) -> Option<IdRef> { fn create_view(window: id, shared: Weak<Shared>) -> Option<IdRef> {
unsafe { unsafe {
let view = IdRef::new(NSView::init(NSView::alloc(nil))); let view = new_view(window, shared);
view.non_nil().map(|view| { view.non_nil().map(|view| {
view.setWantsBestResolutionOpenGLSurface_(YES); view.setWantsBestResolutionOpenGLSurface_(YES);
window.setContentView_(*view); window.setContentView_(*view);
window.makeFirstResponder_(*view);
view view
}) })
} }
@ -816,18 +819,11 @@ impl Window2 {
unsafe { NSWindow::orderOut_(*self.window, nil); } unsafe { NSWindow::orderOut_(*self.window, nil); }
} }
// For consistency with other platforms, this will...
// 1. translate the bottom-left window corner into the top-left window corner
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
fn bottom_left_to_top_left(rect: NSRect) -> i32 {
(CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height)) as _
}
pub fn get_position(&self) -> Option<(i32, i32)> { pub fn get_position(&self) -> Option<(i32, i32)> {
let frame_rect = unsafe { NSWindow::frame(*self.window) }; let frame_rect = unsafe { NSWindow::frame(*self.window) };
Some(( Some((
frame_rect.origin.x as i32, frame_rect.origin.x as i32,
Self::bottom_left_to_top_left(frame_rect), util::bottom_left_to_top_left(frame_rect),
)) ))
} }
@ -840,7 +836,7 @@ impl Window2 {
}; };
Some(( Some((
content_rect.origin.x as i32, content_rect.origin.x as i32,
Self::bottom_left_to_top_left(content_rect), util::bottom_left_to_top_left(content_rect),
)) ))
} }
@ -1029,10 +1025,9 @@ impl Window2 {
let curr_mask = state.window.styleMask(); let curr_mask = state.window.styleMask();
if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) { if !curr_mask.contains(NSWindowStyleMask::NSTitledWindowMask) {
state.window.setStyleMask_( let mask = NSWindowStyleMask::NSTitledWindowMask
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
| NSWindowStyleMask::NSResizableWindowMask, util::set_style_mask(*self.window, *self.view, mask);
);
state.save_style_mask.set(Some(curr_mask)); state.save_style_mask.set(Some(curr_mask));
} }
} }
@ -1067,21 +1062,26 @@ impl Window2 {
} else { } else {
NSWindowStyleMask::NSBorderlessWindowMask NSWindowStyleMask::NSBorderlessWindowMask
}; };
util::set_style_mask(*state.window, *state.view, new_mask);
state.window.setStyleMask_(new_mask);
} }
} }
#[inline] #[inline]
pub fn set_window_icon(&self, _icon: Option<::Icon>) { pub fn set_window_icon(&self, _icon: Option<::Icon>) {
// macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's // macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's
// semantically distinct and should only be used when the window is in some representing a // semantically distinct and should only be used when the window is in some way
// specific file/directory. For instance, Terminal.app uses this for the CWD. Anyway, that // representing a specific file/directory. For instance, Terminal.app uses this for the
// should eventually be implemented as `WindowBuilderExt::with_represented_file` or // CWD. Anyway, that should eventually be implemented as
// something, and doesn't have anything to do with this. // `WindowBuilderExt::with_represented_file` or something, and doesn't have anything to do
// with `set_window_icon`.
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html
} }
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
set_ime_spot(*self.view, *self.input_context, x, y);
}
#[inline] #[inline]
pub fn get_current_monitor(&self) -> RootMonitorId { pub fn get_current_monitor(&self) -> RootMonitorId {
unsafe { unsafe {
@ -1169,8 +1169,7 @@ impl Drop for IdRef {
fn drop(&mut self) { fn drop(&mut self) {
if self.0 != nil { if self.0 != nil {
unsafe { unsafe {
let autoreleasepool = let autoreleasepool = NSAutoreleasePool::new(nil);
NSAutoreleasePool::new(nil);
let _ : () = msg_send![self.0, release]; let _ : () = msg_send![self.0, release];
let _ : () = msg_send![autoreleasepool, release]; let _ : () = msg_send![autoreleasepool, release];
}; };

View file

@ -643,6 +643,11 @@ impl Window {
} }
self.taskbar_icon.replace(taskbar_icon); self.taskbar_icon.replace(taskbar_icon);
} }
#[inline]
pub fn set_ime_spot(&self, _x: i32, _y: i32) {
unimplemented!();
}
} }
impl Drop for Window { impl Drop for Window {

View file

@ -376,6 +376,12 @@ impl Window {
self.window.set_window_icon(window_icon) self.window.set_window_icon(window_icon)
} }
//// Sets location of IME candidate box in client area coordinates relative to the top left.
#[inline]
pub fn set_ime_spot(&self, x: i32, y: i32) {
self.window.set_ime_spot(x, y)
}
/// Returns the monitor on which the window currently resides /// Returns the monitor on which the window currently resides
pub fn get_current_monitor(&self) -> MonitorId { pub fn get_current_monitor(&self) -> MonitorId {
self.window.get_current_monitor() self.window.get_current_monitor()