WIP
This commit is contained in:
parent
7757fbe241
commit
2ea0b89e7c
3 changed files with 303 additions and 31 deletions
|
@ -26,6 +26,7 @@ use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
@ -88,6 +89,23 @@ pub trait Dispatcher: Send + Sync {
|
||||||
fn run_on_main_thread(&self, task: Runnable);
|
fn run_on_main_thread(&self, task: Runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait InputHandler {
|
||||||
|
fn select(&mut self, range: Range<usize>);
|
||||||
|
fn selected_range(&self) -> Option<Range<usize>>;
|
||||||
|
fn set_composition(
|
||||||
|
&mut self,
|
||||||
|
marked_text: &str,
|
||||||
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
replacement_range: Option<Range<usize>>,
|
||||||
|
);
|
||||||
|
fn commit(&mut self, text: &str, replacement_range: Option<Range<usize>>);
|
||||||
|
fn cancel_composition(&mut self);
|
||||||
|
fn finish_composition(&mut self);
|
||||||
|
fn unmark(&mut self);
|
||||||
|
fn marked_range(&self) -> Option<Range<usize>>;
|
||||||
|
fn text_for_range(&self, range: Range<usize>) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Window: WindowContext {
|
pub trait Window: WindowContext {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
|
fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
|
||||||
|
@ -95,6 +113,7 @@ pub trait Window: WindowContext {
|
||||||
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
|
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
|
||||||
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
|
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
|
||||||
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
||||||
|
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
|
||||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||||
fn activate(&self);
|
fn activate(&self);
|
||||||
fn set_title(&mut self, title: &str);
|
fn set_title(&mut self, title: &str);
|
||||||
|
|
|
@ -7,7 +7,8 @@ use crate::{
|
||||||
},
|
},
|
||||||
keymap::Keystroke,
|
keymap::Keystroke,
|
||||||
platform::{self, Event, WindowBounds, WindowContext},
|
platform::{self, Event, WindowBounds, WindowContext},
|
||||||
KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, Scene,
|
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent,
|
||||||
|
Scene,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -16,7 +17,9 @@ use cocoa::{
|
||||||
NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
|
NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
|
||||||
},
|
},
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
|
foundation::{
|
||||||
|
NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||||
|
},
|
||||||
quartzcore::AutoresizingMask,
|
quartzcore::AutoresizingMask,
|
||||||
};
|
};
|
||||||
use core_graphics::display::CGRect;
|
use core_graphics::display::CGRect;
|
||||||
|
@ -34,9 +37,13 @@ use smol::Timer;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
cmp,
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
ffi::c_void,
|
ffi::{c_void, CStr},
|
||||||
mem, ptr,
|
mem,
|
||||||
|
ops::Range,
|
||||||
|
os::raw::c_char,
|
||||||
|
ptr,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
|
@ -48,12 +55,44 @@ static mut WINDOW_CLASS: *const Class = ptr::null();
|
||||||
static mut VIEW_CLASS: *const Class = ptr::null();
|
static mut VIEW_CLASS: *const Class = ptr::null();
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
struct NSRange {
|
struct NSRange {
|
||||||
pub location: NSUInteger,
|
pub location: NSUInteger,
|
||||||
pub length: NSUInteger,
|
pub length: NSUInteger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NSRange {
|
||||||
|
fn invalid() -> Self {
|
||||||
|
Self {
|
||||||
|
location: NSNotFound as NSUInteger,
|
||||||
|
length: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
self.location != NSNotFound as NSUInteger
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_range(&self) -> Option<Range<usize>> {
|
||||||
|
if self.is_valid() {
|
||||||
|
let start = self.location as usize;
|
||||||
|
let end = start + self.length as usize;
|
||||||
|
Some(start..end)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Range<usize>> for NSRange {
|
||||||
|
fn from(range: Range<usize>) -> Self {
|
||||||
|
NSRange {
|
||||||
|
location: range.start as NSUInteger,
|
||||||
|
length: range.len() as NSUInteger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl objc::Encode for NSRange {
|
unsafe impl objc::Encode for NSRange {
|
||||||
fn encode() -> objc::Encoding {
|
fn encode() -> objc::Encoding {
|
||||||
let encoding = format!(
|
let encoding = format!(
|
||||||
|
@ -189,6 +228,10 @@ unsafe fn build_classes() {
|
||||||
sel!(hasMarkedText),
|
sel!(hasMarkedText),
|
||||||
has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
|
has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
|
||||||
);
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(markedRange),
|
||||||
|
marked_range as extern "C" fn(&Object, Sel) -> NSRange,
|
||||||
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(selectedRange),
|
sel!(selectedRange),
|
||||||
selected_range as extern "C" fn(&Object, Sel) -> NSRange,
|
selected_range as extern "C" fn(&Object, Sel) -> NSRange,
|
||||||
|
@ -205,10 +248,11 @@ unsafe fn build_classes() {
|
||||||
sel!(setMarkedText:selectedRange:replacementRange:),
|
sel!(setMarkedText:selectedRange:replacementRange:),
|
||||||
set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
|
set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
|
||||||
);
|
);
|
||||||
|
decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(attributedSubstringForProposedRange:actualRange:),
|
sel!(attributedSubstringForProposedRange:actualRange:),
|
||||||
attributed_substring_for_proposed_range
|
attributed_substring_for_proposed_range
|
||||||
as extern "C" fn(&Object, Sel, NSRange, id) -> id,
|
as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
|
||||||
);
|
);
|
||||||
|
|
||||||
decl.register()
|
decl.register()
|
||||||
|
@ -225,6 +269,8 @@ struct WindowState {
|
||||||
resize_callback: Option<Box<dyn FnMut()>>,
|
resize_callback: Option<Box<dyn FnMut()>>,
|
||||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||||
close_callback: Option<Box<dyn FnOnce()>>,
|
close_callback: Option<Box<dyn FnOnce()>>,
|
||||||
|
input_handler: Option<Box<dyn InputHandler>>,
|
||||||
|
pending_key_event: Option<PendingKeyEvent>,
|
||||||
synthetic_drag_counter: usize,
|
synthetic_drag_counter: usize,
|
||||||
executor: Rc<executor::Foreground>,
|
executor: Rc<executor::Foreground>,
|
||||||
scene_to_render: Option<Scene>,
|
scene_to_render: Option<Scene>,
|
||||||
|
@ -236,6 +282,13 @@ struct WindowState {
|
||||||
previous_modifiers_changed_event: Option<Event>,
|
previous_modifiers_changed_event: Option<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
struct PendingKeyEvent {
|
||||||
|
set_marked_text: Option<(String, Option<Range<usize>>, Option<Range<usize>>)>,
|
||||||
|
unmark_text: bool,
|
||||||
|
insert_text: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn open(
|
pub fn open(
|
||||||
id: usize,
|
id: usize,
|
||||||
|
@ -311,6 +364,8 @@ impl Window {
|
||||||
should_close_callback: None,
|
should_close_callback: None,
|
||||||
close_callback: None,
|
close_callback: None,
|
||||||
activate_callback: None,
|
activate_callback: None,
|
||||||
|
input_handler: None,
|
||||||
|
pending_key_event: None,
|
||||||
synthetic_drag_counter: 0,
|
synthetic_drag_counter: 0,
|
||||||
executor,
|
executor,
|
||||||
scene_to_render: Default::default(),
|
scene_to_render: Default::default(),
|
||||||
|
@ -419,6 +474,10 @@ impl platform::Window for Window {
|
||||||
self.0.as_ref().borrow_mut().activate_callback = Some(callback);
|
self.0.as_ref().borrow_mut().activate_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
|
||||||
|
self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
|
||||||
|
}
|
||||||
|
|
||||||
fn prompt(
|
fn prompt(
|
||||||
&self,
|
&self,
|
||||||
level: platform::PromptLevel,
|
level: platform::PromptLevel,
|
||||||
|
@ -636,38 +695,103 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
|
extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
|
||||||
|
let had_marked_text = with_input_handler(this, |input_handler| input_handler.marked_range())
|
||||||
|
.flatten()
|
||||||
|
.is_some();
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let window_state = unsafe { get_window_state(this) };
|
||||||
|
|
||||||
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
||||||
|
|
||||||
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
|
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
match &event {
|
let mut event = match event {
|
||||||
Event::KeyDown(KeyDownEvent {
|
Event::KeyDown(event) => {
|
||||||
keystroke,
|
let keydown = (event.keystroke.clone(), event.input.clone());
|
||||||
input,
|
|
||||||
is_held,
|
|
||||||
}) => {
|
|
||||||
let keydown = (keystroke.clone(), input.clone());
|
|
||||||
// Ignore events from held-down keys after some of the initially-pressed keys
|
// Ignore events from held-down keys after some of the initially-pressed keys
|
||||||
// were released.
|
// were released.
|
||||||
if *is_held {
|
if event.is_held {
|
||||||
if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
|
if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window_state_borrow.last_fresh_keydown = Some(keydown);
|
window_state_borrow.last_fresh_keydown = Some(keydown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event
|
||||||
}
|
}
|
||||||
_ => return NO,
|
_ => return NO,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: handle "live conversion"
|
||||||
|
window_state_borrow.pending_key_event = Some(Default::default());
|
||||||
|
drop(window_state_borrow);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Since Mac Eisu Kana keys cannot be handled by interpretKeyEvents to enable/
|
||||||
|
// disable an IME, we need to pass the event to processInputKeyBindings.
|
||||||
|
// processInputKeyBindings is available at least on 10.11-11.0.
|
||||||
|
// if (keyCode == kVK_JIS_Eisu || keyCode == kVK_JIS_Kana) {
|
||||||
|
// if ([NSTextInputContext
|
||||||
|
// respondsToSelector:@selector(processInputKeyBindings:)]) {
|
||||||
|
// [NSTextInputContext performSelector:@selector(processInputKeyBindings:)
|
||||||
|
// withObject:theEvent];
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
unsafe {
|
||||||
|
let input_context: id = msg_send![this, inputContext];
|
||||||
|
let _: BOOL = msg_send![input_context, handleEvent: native_event];
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
let pending_event = window_state.borrow_mut().pending_key_event.take().unwrap();
|
||||||
|
let mut inserted_text = false;
|
||||||
|
let has_marked_text = pending_event.set_marked_text.is_some();
|
||||||
|
if let Some(text) = pending_event.insert_text.as_ref() {
|
||||||
|
if !text.is_empty() && (had_marked_text || has_marked_text || text.len() > 1) {
|
||||||
|
with_input_handler(this, |input_handler| input_handler.commit(&text, None));
|
||||||
|
inserted_text = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
with_input_handler(this, |input_handler| {
|
||||||
drop(window_state_borrow);
|
if let Some((text, new_selected_range, replacement_range)) =
|
||||||
let handled = callback(event);
|
pending_event.set_marked_text
|
||||||
window_state.borrow_mut().event_callback = Some(callback);
|
{
|
||||||
handled as BOOL
|
input_handler.set_composition(&text, new_selected_range, replacement_range)
|
||||||
|
} else if had_marked_text && !inserted_text {
|
||||||
|
if pending_event.unmark_text {
|
||||||
|
input_handler.finish_composition();
|
||||||
|
} else {
|
||||||
|
input_handler.cancel_composition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if has_marked_text {
|
||||||
|
YES
|
||||||
} else {
|
} else {
|
||||||
NO
|
let mut handled = false;
|
||||||
|
let mut window_state_borrow = window_state.borrow_mut();
|
||||||
|
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
||||||
|
drop(window_state_borrow);
|
||||||
|
|
||||||
|
if inserted_text {
|
||||||
|
handled = true;
|
||||||
|
} else if let Some(text) = pending_event.insert_text {
|
||||||
|
if text.len() == 1 {
|
||||||
|
event.keystroke.key = text;
|
||||||
|
handled = callback(Event::KeyDown(event));
|
||||||
|
} else if event.keystroke.cmd || event.keystroke.ctrl {
|
||||||
|
handled = callback(Event::KeyDown(event));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handled = callback(Event::KeyDown(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
window_state.borrow_mut().event_callback = Some(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
handled as BOOL
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NO
|
NO
|
||||||
|
@ -926,27 +1050,138 @@ extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
|
||||||
unsafe { msg_send![class!(NSArray), array] }
|
unsafe { msg_send![class!(NSArray), array] }
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn has_marked_text(_: &Object, _: Sel) -> BOOL {
|
extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
|
||||||
false as BOOL
|
with_input_handler(this, |input_handler| input_handler.marked_range())
|
||||||
|
.flatten()
|
||||||
|
.is_some() as BOOL
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn selected_range(_: &Object, _: Sel) -> NSRange {
|
extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
|
||||||
NSRange {
|
with_input_handler(this, |input_handler| input_handler.marked_range())
|
||||||
location: 0,
|
.flatten()
|
||||||
length: 0,
|
.map_or(NSRange::invalid(), |range| range.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
|
||||||
|
with_input_handler(this, |input_handler| input_handler.selected_range())
|
||||||
|
.flatten()
|
||||||
|
.map_or(NSRange::invalid(), |range| range.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn first_rect_for_character_range(_: &Object, _: Sel, _: NSRange, _: id) -> NSRect {
|
extern "C" fn first_rect_for_character_range(_: &Object, _: Sel, _: NSRange, _: id) -> NSRect {
|
||||||
NSRect::new(NSPoint::new(0., 0.), NSSize::new(20., 20.))
|
NSRect::new(NSPoint::new(0., 0.), NSSize::new(20., 20.))
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn insert_text(_: &Object, _: Sel, _: id, _: NSRange) {}
|
extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
|
||||||
|
unsafe {
|
||||||
|
let is_attributed_string: BOOL =
|
||||||
|
msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
|
||||||
|
let text: id = if is_attributed_string == YES {
|
||||||
|
msg_send![text, string]
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
extern "C" fn set_marked_text(_: &Object, _: Sel, _: id, _: NSRange, _: NSRange) {}
|
let window_state = get_window_state(this);
|
||||||
|
let mut window_state = window_state.borrow_mut();
|
||||||
|
if window_state.pending_key_event.is_some() && !replacement_range.is_valid() {
|
||||||
|
window_state.pending_key_event.as_mut().unwrap().insert_text = Some(text.to_string());
|
||||||
|
drop(window_state);
|
||||||
|
} else {
|
||||||
|
drop(window_state);
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
input_handler.commit(text, replacement_range.to_range());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn attributed_substring_for_proposed_range(_: &Object, _: Sel, _: NSRange, _: id) -> id {
|
with_input_handler(this, |input_handler| input_handler.unmark());
|
||||||
unsafe { msg_send![class!(NSAttributedString), alloc] }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn set_marked_text(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
text: id,
|
||||||
|
selected_range: NSRange,
|
||||||
|
replacement_range: NSRange,
|
||||||
|
) {
|
||||||
|
println!("set_marked_text");
|
||||||
|
unsafe {
|
||||||
|
let is_attributed_string: BOOL =
|
||||||
|
msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
|
||||||
|
let text: id = if is_attributed_string == YES {
|
||||||
|
msg_send![text, string]
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
let selected_range = selected_range.to_range();
|
||||||
|
let replacement_range = replacement_range.to_range();
|
||||||
|
let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let window_state = get_window_state(this);
|
||||||
|
let mut window_state = window_state.borrow_mut();
|
||||||
|
if let Some(pending) = window_state.pending_key_event.as_mut() {
|
||||||
|
pending.set_marked_text = Some((text.to_string(), selected_range, replacement_range));
|
||||||
|
} else {
|
||||||
|
drop(window_state);
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
input_handler.set_composition(text, selected_range, replacement_range);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn unmark_text(this: &Object, _: Sel) {
|
||||||
|
println!("unmark_text");
|
||||||
|
let window_state = unsafe { get_window_state(this) };
|
||||||
|
let mut window_state = window_state.borrow_mut();
|
||||||
|
if let Some(pending) = window_state.pending_key_event.as_mut() {
|
||||||
|
pending.unmark_text = true;
|
||||||
|
pending.set_marked_text.take();
|
||||||
|
} else {
|
||||||
|
drop(window_state);
|
||||||
|
with_input_handler(this, |input_handler| input_handler.finish_composition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn attributed_substring_for_proposed_range(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
range: NSRange,
|
||||||
|
actual_range: *mut c_void,
|
||||||
|
) -> id {
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
let actual_range = actual_range as *mut NSRange;
|
||||||
|
if !actual_range.is_null() {
|
||||||
|
unsafe { *actual_range = NSRange::invalid() };
|
||||||
|
}
|
||||||
|
|
||||||
|
let requested_range = range.to_range()?;
|
||||||
|
if requested_range.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selected_range = input_handler.selected_range()?;
|
||||||
|
let intersection = cmp::max(requested_range.start, selected_range.start)
|
||||||
|
..cmp::min(requested_range.end, selected_range.end);
|
||||||
|
if intersection.start >= intersection.end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let selected_text = ns_string(&input_handler.text_for_range(intersection)?);
|
||||||
|
let string: id = msg_send![class!(NSAttributedString), alloc];
|
||||||
|
let string: id = msg_send![string, initWithString: selected_text];
|
||||||
|
Some(string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn synthetic_drag(
|
async fn synthetic_drag(
|
||||||
|
@ -974,3 +1209,19 @@ async fn synthetic_drag(
|
||||||
unsafe fn ns_string(string: &str) -> id {
|
unsafe fn ns_string(string: &str) -> id {
|
||||||
NSString::alloc(nil).init_str(string).autorelease()
|
NSString::alloc(nil).init_str(string).autorelease()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut dyn InputHandler) -> R,
|
||||||
|
{
|
||||||
|
let window_state = unsafe { get_window_state(window) };
|
||||||
|
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
||||||
|
if let Some(mut input_handler) = window_state_borrow.input_handler.take() {
|
||||||
|
drop(window_state_borrow);
|
||||||
|
let result = f(input_handler.as_mut());
|
||||||
|
window_state.borrow_mut().input_handler = Some(input_handler);
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -255,6 +255,8 @@ impl super::Window for Window {
|
||||||
self.close_handlers.push(callback);
|
self.close_handlers.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_input_handler(&mut self, _: Box<dyn crate::InputHandler>) {}
|
||||||
|
|
||||||
fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver<usize> {
|
fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver<usize> {
|
||||||
let (done_tx, done_rx) = oneshot::channel();
|
let (done_tx, done_rx) = oneshot::channel();
|
||||||
self.pending_prompts.borrow_mut().push_back(done_tx);
|
self.pending_prompts.borrow_mut().push_back(done_tx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue