diff options
author | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2017-08-07 14:09:56 +0300 |
---|---|---|
committer | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2017-08-09 19:09:33 +0300 |
commit | 8aa86cb9bcb5db8a1909d4b1595e90dbffbff11e (patch) | |
tree | 22df47c5dc375ed1ef40e1da0eab29c7be4e704c | |
parent | b5c6b0cf1c3d644321ed134dbb26e111d6dbd43f (diff) |
Implement NSTextInputClient protocol for IME
-rw-r--r-- | core/os/os.h | 3 | ||||
-rw-r--r-- | platform/osx/os_osx.h | 6 | ||||
-rw-r--r-- | platform/osx/os_osx.mm | 146 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 82 | ||||
-rw-r--r-- | scene/gui/line_edit.h | 3 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 99 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 5 |
7 files changed, 312 insertions, 32 deletions
diff --git a/core/os/os.h b/core/os/os.h index 8e2257a0e4..4d64e4a9f0 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -63,6 +63,8 @@ class OS { void *_stack_bottom; public: + typedef void (*ImeCallback)(void *p_inp, String p_text, Point2 p_selection); + enum RenderThreadMode { RENDER_THREAD_UNSAFE, @@ -183,6 +185,7 @@ public: virtual bool get_borderless_window() { return 0; } virtual void set_ime_position(const Point2 &p_pos) {} + virtual void set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp) {} virtual Error open_dynamic_library(const String p_path, void *&p_library_handle) { return ERR_UNAVAILABLE; } virtual Error close_dynamic_library(void *p_library_handle) { return ERR_UNAVAILABLE; } diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 4b5682518f..56e6802eeb 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -104,6 +104,10 @@ public: Size2 window_size; Rect2 restore_rect; + Point2 im_position; + ImeCallback im_callback; + void *im_target; + power_osx *power_manager; float _mouse_scale(float p_scale) { @@ -203,6 +207,8 @@ public: virtual void set_borderless_window(int p_borderless); virtual bool get_borderless_window(); + virtual void set_ime_position(const Point2 &p_pos); + virtual void set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp); virtual PowerState get_power_state(); virtual int get_power_seconds_left(); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index e884058052..6d8a6eca66 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -261,10 +261,12 @@ static bool mouse_down_control = false; @end -@interface GodotContentView : NSView { +@interface GodotContentView : NSView <NSTextInputClient> { NSTrackingArea *trackingArea; + NSMutableAttributedString *markedText; + bool imeMode; } - +- (void)cancelComposition; @end @implementation GodotContentView @@ -278,16 +280,128 @@ static bool mouse_down_control = false; - (id)init { self = [super init]; trackingArea = nil; + imeMode = false; [self updateTrackingAreas]; [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; + markedText = [[NSMutableAttributedString alloc] init]; return self; } - (void)dealloc { [trackingArea release]; + [markedText release]; [super dealloc]; } +static const NSRange kEmptyRange = { NSNotFound, 0 }; + +- (BOOL)hasMarkedText { + return (markedText.length > 0); +} + +- (NSRange)markedRange { + return (markedText.length > 0) ? NSMakeRange(0, markedText.length - 1) : kEmptyRange; +} + +- (NSRange)selectedRange { + return kEmptyRange; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + if ([aString isKindOfClass:[NSAttributedString class]]) { + [markedText initWithAttributedString:aString]; + } else { + [markedText initWithString:aString]; + } + if (OS_OSX::singleton->im_callback) { + imeMode = true; + String ret; + ret.parse_utf8([[markedText mutableString] UTF8String]); + OS_OSX::singleton->im_callback(OS_OSX::singleton->im_target, ret, Point2(selectedRange.location, selectedRange.length)); + } +} + +- (void)doCommandBySelector:(SEL)aSelector { + if ([self respondsToSelector:aSelector]) + [self performSelector:aSelector]; +} + +- (void)unmarkText { + imeMode = false; + [[markedText mutableString] setString:@""]; + if (OS_OSX::singleton->im_callback) + OS_OSX::singleton->im_callback(OS_OSX::singleton->im_target, "", Point2()); +} + +- (NSArray *)validAttributesForMarkedText { + return [NSArray array]; +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + const NSRect contentRect = [OS_OSX::singleton->window_view frame]; + NSRect pointInWindowRect = NSMakeRect(OS_OSX::singleton->im_position.x / OS_OSX::singleton->display_scale, contentRect.size.height - (OS_OSX::singleton->im_position.y / OS_OSX::singleton->display_scale) - 1, 0, 0); + NSPoint pointOnScreen = [[OS_OSX::singleton->window_view window] convertRectToScreen:pointInWindowRect].origin; + + return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); +} + +- (void)cancelComposition { + [self unmarkText]; + NSInputManager *currentInputManager = [NSInputManager currentInputManager]; + [currentInputManager markedTextAbandoned:self]; +} + +- (void)insertText:(id)aString { + [self insertText:aString replacementRange:NSMakeRange(0, 0)]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { + NSEvent *event = [NSApp currentEvent]; + Ref<InputEventKey> k; + k.instance(); + + get_key_modifier_state([event modifierFlags], k); + k->set_pressed(true); + k->set_echo(false); + k->set_scancode(0); + + NSString *characters; + if ([aString isKindOfClass:[NSAttributedString class]]) { + characters = [aString string]; + } else { + characters = (NSString *)aString; + } + + NSUInteger i, length = [characters length]; + + NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; + NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { + NSInputManager *currentInputManager = [NSInputManager currentInputManager]; + [currentInputManager markedTextAbandoned:self]; + [self cancelComposition]; + return; + } + + for (i = 0; i < length; i++) { + const unichar codepoint = [characters characterAtIndex:i]; + if ((codepoint & 0xFF00) == 0xF700) + continue; + + k->set_unicode(codepoint); + OS_OSX::singleton->push_input(k); + } + [self cancelComposition]; +} + - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { return NSDragOperationCopy; } @@ -634,15 +748,12 @@ static int translateKey(unsigned int key) { NSString *characters = [event characters]; NSUInteger i, length = [characters length]; - if (length > 0 && keycode_has_unicode(k->get_scancode())) { - for (i = 0; i < length; i++) { - k->set_unicode([characters characterAtIndex:i]); - OS_OSX::singleton->push_input(k); - k->set_scancode(0); - } - } else { + //disable raw input in IME mode + if (!imeMode) OS_OSX::singleton->push_input(k); - } + + if ((OS_OSX::singleton->im_position.x != 0) && (OS_OSX::singleton->im_position.y != 0)) + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; } - (void)flagsChanged:(NSEvent *)event { @@ -761,6 +872,18 @@ inline void sendScrollEvent(int button, double factor, int modifierFlags) { @end +void OS_OSX::set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp) { + im_callback = p_callback; + im_target = p_inp; + if (!im_callback) { + [window_view cancelComposition]; + } +} + +void OS_OSX::set_ime_position(const Point2 &p_pos) { + im_position = p_pos; +} + int OS_OSX::get_video_driver_count() const { return 1; } @@ -1735,6 +1858,9 @@ OS_OSX::OS_OSX() { mouse_mode = OS::MOUSE_MODE_VISIBLE; main_loop = NULL; singleton = this; + im_position = Point2(); + im_callback = NULL; + im_target = NULL; autoreleasePool = [[NSAutoreleasePool alloc] init]; eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 8556ce5db1..aa4651df24 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -641,6 +641,35 @@ void LineEdit::_notification(int p_what) { if (char_ofs >= t.length()) break; + if (char_ofs == cursor_pos) { + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; + + CharType cchar = (pass && !text.empty()) ? '*' : ime_text[ofs]; + CharType next = (pass && !text.empty()) ? '*' : ime_text[ofs + 1]; + int im_char_width = font->get_char_size(cchar, next).width; + + if ((x_ofs + im_char_width) > ofs_max) + break; + + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 3)), font_color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color); + } + + font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color); + + x_ofs += im_char_width; + ofs++; + } + } + } + CharType cchar = (pass && !text.empty()) ? '*' : t[char_ofs]; CharType next = (pass && !text.empty()) ? '*' : t[char_ofs + 1]; int char_width = font->get_char_size(cchar, next).width; @@ -657,24 +686,54 @@ void LineEdit::_notification(int p_what) { font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, selected ? font_color_selected : font_color); if (char_ofs == cursor_pos && draw_caret) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2( - Point2(x_ofs, y_ofs), Size2(1, caret_height)), - cursor_color); + if (ime_text.length() == 0) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(1, caret_height)), cursor_color); + } } x_ofs += char_width; char_ofs++; } + if (char_ofs == cursor_pos) { + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; + + CharType cchar = (pass && !text.empty()) ? '*' : ime_text[ofs]; + CharType next = (pass && !text.empty()) ? '*' : ime_text[ofs + 1]; + int im_char_width = font->get_char_size(cchar, next).width; + + if ((x_ofs + im_char_width) > ofs_max) + break; + + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 3)), font_color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color); + } + + font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color); + + x_ofs += im_char_width; + ofs++; + } + } + } + if (char_ofs == cursor_pos && draw_caret) { //may be at the end - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2( - Point2(x_ofs, y_ofs), Size2(1, caret_height)), - cursor_color); + if (ime_text.length() == 0) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(1, caret_height)), cursor_color); + } } if (has_focus()) { OS::get_singleton()->set_ime_position(get_global_position() + Point2(x_ofs, y_ofs + caret_height)); + OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); } } break; case NOTIFICATION_FOCUS_ENTER: { @@ -685,6 +744,7 @@ void LineEdit::_notification(int p_what) { Point2 cursor_pos = Point2(get_cursor_pos(), 1) * get_minimum_size().height; OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); + OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); if (OS::get_singleton()->has_virtual_keyboard()) OS::get_singleton()->show_virtual_keyboard(text, get_global_rect()); @@ -693,6 +753,9 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_FOCUS_EXIT: { OS::get_singleton()->set_ime_position(Point2()); + OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL); + ime_text = ""; + ime_selection = Point2(); if (OS::get_singleton()->has_virtual_keyboard()) OS::get_singleton()->hide_virtual_keyboard(); @@ -1231,6 +1294,13 @@ bool LineEdit::get_expand_to_text_length() const { return expand_to_text_length; } +void LineEdit::_ime_text_callback(void *p_self, String p_text, Point2 p_selection) { + LineEdit *self = (LineEdit *)p_self; + self->ime_text = p_text; + self->ime_selection = p_selection; + self->update(); +} + void LineEdit::_text_changed() { if (expand_to_text_length) diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 1167cfb6d0..73c5a46937 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -70,6 +70,8 @@ private: String text; String placeholder; float placeholder_alpha; + String ime_text; + Point2 ime_selection; PopupMenu *menu; @@ -92,6 +94,7 @@ private: Timer *caret_blink_timer; + static void _ime_text_callback(void *p_self, String p_text, Point2 p_selection); void _text_changed(); bool expand_to_text_length; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index a7c31361e8..acc8639350 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -987,13 +987,41 @@ void TextEdit::_notification(int p_what) { } int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; - if (draw_caret) { - if (insert_mode) { - int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); - } else { - caret_w = (block_caret) ? caret_w : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; + + CharType cchar = ime_text[ofs]; + CharType next = ime_text[ofs + 1]; + int im_char_width = cache.font->get_char_size(cchar, next).width; + + if ((char_ofs + char_margin + im_char_width) >= xmargin_end) + break; + + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); + } + + cache.font->draw_char(ci, Point2(char_ofs + char_margin, ofs_y + ascent), cchar, next, color); + + char_ofs += im_char_width; + ofs++; + } + } + if (ime_text.length() == 0) { + if (draw_caret) { + if (insert_mode) { + int caret_h = (block_caret) ? 4 : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + } else { + caret_w = (block_caret) ? caret_w : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + } } } } @@ -1024,16 +1052,43 @@ void TextEdit::_notification(int p_what) { if (insert_mode) { cursor_pos.y += (get_row_height() - 3); } + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; - if (draw_caret) { - if (insert_mode) { - int char_w = cache.font->get_char_size(' ').width; - int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); - } else { - int char_w = cache.font->get_char_size(' ').width; - int caret_w = (block_caret) ? char_w : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + CharType cchar = ime_text[ofs]; + CharType next = ime_text[ofs + 1]; + int im_char_width = cache.font->get_char_size(cchar, next).width; + + if ((char_ofs + char_margin + im_char_width) >= xmargin_end) + break; + + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); + } + + cache.font->draw_char(ci, Point2(char_ofs + char_margin, ofs_y + ascent), cchar, next, color); + + char_ofs += im_char_width; + ofs++; + } + } + if (ime_text.length() == 0) { + if (draw_caret) { + if (insert_mode) { + int char_w = cache.font->get_char_size(' ').width; + int caret_h = (block_caret) ? 4 : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); + } else { + int char_w = cache.font->get_char_size(' ').width; + int caret_w = (block_caret) ? char_w : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + } } } } @@ -1197,6 +1252,7 @@ void TextEdit::_notification(int p_what) { if (has_focus()) { OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height())); + OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); } } break; case NOTIFICATION_FOCUS_ENTER: { @@ -1207,6 +1263,7 @@ void TextEdit::_notification(int p_what) { Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height(); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); + OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); if (OS::get_singleton()->has_virtual_keyboard()) OS::get_singleton()->show_virtual_keyboard(get_text(), get_global_rect()); @@ -1218,6 +1275,9 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_FOCUS_EXIT: { OS::get_singleton()->set_ime_position(Point2()); + OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL); + ime_text = ""; + ime_selection = Point2(); if (OS::get_singleton()->has_virtual_keyboard()) OS::get_singleton()->hide_virtual_keyboard(); @@ -1228,6 +1288,13 @@ void TextEdit::_notification(int p_what) { } } +void TextEdit::_ime_text_callback(void *p_self, String p_text, Point2 p_selection) { + TextEdit *self = (TextEdit *)p_self; + self->ime_text = p_text; + self->ime_selection = p_selection; + self->update(); +} + void TextEdit::_consume_pair_symbol(CharType ch) { int cursor_position_to_move = cursor_get_column() + 1; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index f8f60d2b03..afb4b1c547 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -184,6 +184,9 @@ class TextEdit : public Control { bool chain_backward; }; + String ime_text; + Point2 ime_selection; + TextOperation current_op; List<TextOperation> undo_stack; @@ -299,6 +302,8 @@ class TextEdit : public Control { void _scroll_lines_up(); void _scroll_lines_down(); + static void _ime_text_callback(void *p_self, String p_text, Point2 p_selection); + //void mouse_motion(const Point& p_pos, const Point& p_rel, int p_button_mask); Size2 get_minimum_size() const; |