aboutsummaryrefslogtreecommitdiff
path: root/src/platform/gui/osx/Window.mm
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2022-05-15 14:08:06 +0800
committercrupest <crupest@outlook.com>2022-05-15 14:08:06 +0800
commit8ad2966933957ac5d6ff8dcd5e732736fd5e4dc6 (patch)
tree77e41cc14264060517c0f7ed95837012afb8342e /src/platform/gui/osx/Window.mm
parent9e0c9d3499bc50c3534b4dc500d8b5d0b5f22752 (diff)
downloadcru-8ad2966933957ac5d6ff8dcd5e732736fd5e4dc6.tar.gz
cru-8ad2966933957ac5d6ff8dcd5e732736fd5e4dc6.tar.bz2
cru-8ad2966933957ac5d6ff8dcd5e732736fd5e4dc6.zip
...
Diffstat (limited to 'src/platform/gui/osx/Window.mm')
-rw-r--r--src/platform/gui/osx/Window.mm800
1 files changed, 800 insertions, 0 deletions
diff --git a/src/platform/gui/osx/Window.mm b/src/platform/gui/osx/Window.mm
new file mode 100644
index 00000000..2c55d2dd
--- /dev/null
+++ b/src/platform/gui/osx/Window.mm
@@ -0,0 +1,800 @@
+#include "cru/platform/gui/osx/Window.h"
+#include "WindowPrivate.h"
+
+#include "CursorPrivate.h"
+#include "InputMethodPrivate.h"
+#include "cru/common/Range.h"
+#include "cru/common/log/Logger.h"
+#include "cru/platform/osx/Convert.h"
+#include "cru/platform/graphics/quartz/Convert.h"
+#include "cru/platform/graphics/quartz/Painter.h"
+#include "cru/platform/gui/osx/Cursor.h"
+#include "cru/platform/gui/osx/InputMethod.h"
+#include "cru/platform/gui/osx/Keyboard.h"
+#include "cru/platform/gui/osx/Resource.h"
+#include "cru/platform/gui/osx/UiApplication.h"
+#include "cru/platform/Check.h"
+#include "cru/platform/graphics/NullPainter.h"
+#include "cru/platform/gui/TimerHelper.h"
+
+#include <AppKit/AppKit.h>
+#include <Foundation/Foundation.h>
+
+#include <limits>
+#include <memory>
+#include <unordered_set>
+
+namespace {
+constexpr int key_down_debug = 0;
+}
+
+using cru::platform::osx::Convert;
+using cru::platform::graphics::quartz::Convert;
+
+namespace cru::platform::gui::osx {
+namespace {
+inline NSWindowStyleMask CalcWindowStyleMask(WindowStyleFlag flag) {
+ return flag & WindowStyleFlags::NoCaptionAndBorder
+ ? NSWindowStyleMaskBorderless
+ : NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
+ NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
+}
+}
+
+namespace details {
+OsxWindowPrivate::OsxWindowPrivate(OsxWindow* osx_window) : osx_window_(osx_window) {
+ window_delegate_ = [[CruWindowDelegate alloc] init:this];
+
+ content_rect_ = {100, 100, 400, 200};
+
+ input_method_context_ = std::make_unique<OsxInputMethodContext>(osx_window);
+}
+
+OsxWindowPrivate::~OsxWindowPrivate() {}
+
+void OsxWindowPrivate::OnWindowWillClose() {
+ if (window_) destroy_event_.Raise(nullptr);
+ window_ = nil;
+ CGLayerRelease(draw_layer_);
+ draw_layer_ = nullptr;
+
+ if (osx_window_->GetUiApplication()->IsQuitOnAllWindowClosed()) {
+ const auto& all_window = osx_window_->GetUiApplication()->GetAllWindow();
+
+ bool quit = true;
+
+ for (auto window : all_window) {
+ auto w = CheckPlatform<OsxWindow>(window, osx_window_->GetPlatformId());
+ if (w->p_->window_) {
+ quit = false;
+ break;
+ }
+ }
+
+ if (quit) {
+ osx_window_->GetUiApplication()->RequestQuit(0);
+ }
+ }
+}
+
+void OsxWindowPrivate::OnWindowDidExpose() { osx_window_->RequestRepaint(); }
+void OsxWindowPrivate::OnWindowDidUpdate() {}
+void OsxWindowPrivate::OnWindowDidMove() { content_rect_ = RetrieveContentRect(); }
+
+void OsxWindowPrivate::OnWindowDidResize() {
+ content_rect_ = RetrieveContentRect();
+
+ auto view = [window_ contentView];
+ [view removeTrackingArea:[view trackingAreas][0]];
+ auto tracking_area = [[NSTrackingArea alloc]
+ initWithRect:CGRectMake(0, 0, content_rect_.width, content_rect_.height)
+ options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways)
+ owner:view
+ userInfo:nil];
+ [view addTrackingArea:tracking_area];
+
+ CGLayerRelease(draw_layer_);
+ draw_layer_ = CreateLayer(Convert(content_rect_.GetSize()));
+
+ resize_event_.Raise(osx_window_->GetClientSize());
+
+ osx_window_->RequestRepaint();
+}
+
+void OsxWindowPrivate::OnBecomeKeyWindow() { focus_event_.Raise(FocusChangeType::Gain); }
+
+void OsxWindowPrivate::OnResignKeyWindow() { focus_event_.Raise(FocusChangeType::Lose); }
+
+void OsxWindowPrivate::OnMouseEnterLeave(MouseEnterLeaveType type) {
+ mouse_enter_leave_event_.Raise(type);
+ if (type == MouseEnterLeaveType::Enter) {
+ mouse_in_ = true;
+ UpdateCursor();
+ } else {
+ mouse_in_ = false;
+ }
+}
+
+void OsxWindowPrivate::OnMouseMove(Point p) { mouse_move_event_.Raise(TransformMousePoint(p)); }
+
+void OsxWindowPrivate::OnMouseDown(MouseButton button, Point p, KeyModifier key_modifier) {
+ mouse_down_event_.Raise({button, TransformMousePoint(p), key_modifier});
+}
+
+void OsxWindowPrivate::OnMouseUp(MouseButton button, Point p, KeyModifier key_modifier) {
+ mouse_up_event_.Raise({button, TransformMousePoint(p), key_modifier});
+}
+
+void OsxWindowPrivate::OnMouseWheel(float delta, Point p, KeyModifier key_modifier,
+ bool horizontal) {
+ mouse_wheel_event_.Raise({delta, TransformMousePoint(p), key_modifier, horizontal});
+}
+
+void OsxWindowPrivate::OnKeyDown(KeyCode key, KeyModifier key_modifier) {
+ key_down_event_.Raise({key, key_modifier});
+}
+
+void OsxWindowPrivate::OnKeyUp(KeyCode key, KeyModifier key_modifier) {
+ key_up_event_.Raise({key, key_modifier});
+}
+
+CGLayerRef OsxWindowPrivate::CreateLayer(const CGSize& size) {
+ auto s = size;
+ if (s.width == 0) s.width = 1;
+ if (s.height == 0) s.height = 1;
+
+ auto draw_layer = CGLayerCreateWithContext(nullptr, s, nullptr);
+ Ensures(draw_layer);
+
+ return draw_layer;
+}
+
+void OsxWindowPrivate::UpdateCursor() {
+ auto cursor = cursor_ == nullptr
+ ? std::dynamic_pointer_cast<OsxCursor>(
+ osx_window_->GetUiApplication()->GetCursorManager()->GetSystemCursor(
+ SystemCursorType::Arrow))
+ : cursor_;
+
+ [cursor->p_->ns_cursor_ set];
+}
+
+Point OsxWindowPrivate::TransformMousePoint(const Point& point) {
+ Point r = point;
+ r.y = content_rect_.height - r.y;
+ return r;
+}
+
+void OsxWindowPrivate::CreateWindow() {
+ Expects(!window_);
+
+ NSWindowStyleMask style_mask = CalcWindowStyleMask(style_flag_);
+ window_ = [[CruWindow alloc] init:this
+ contentRect:{0, 0, content_rect_.width, content_rect_.height}
+ style:style_mask];
+ Ensures(window_);
+
+ osx_window_->SetClientRect(content_rect_);
+
+ [window_ setDelegate:window_delegate_];
+
+ if (parent_) {
+ auto parent = CheckPlatform<OsxWindow>(parent_, this->osx_window_->GetPlatformId());
+ [window_ setParentWindow:parent->p_->window_];
+ }
+
+ NSView* content_view = [[CruView alloc] init:this
+ input_context_p:input_method_context_->p_.get()
+ frame:Rect(Point{}, content_rect_.GetSize())];
+
+ [window_ setContentView:content_view];
+
+ auto title_str = Convert(title_);
+ [window_ setTitle:(NSString*)title_str];
+ CFRelease(title_str);
+
+ draw_layer_ = CreateLayer(Convert(content_rect_.GetSize()));
+
+ create_event_.Raise(nullptr);
+
+ osx_window_->RequestRepaint();
+}
+
+Size OsxWindowPrivate::GetScreenSize() {
+ auto screen = window_ ? [window_ screen] : [NSScreen mainScreen];
+ auto size = [screen frame].size;
+ return Convert(size);
+}
+
+Rect OsxWindowPrivate::RetrieveContentRect() {
+ NSRect rect = [NSWindow contentRectForFrameRect:[window_ frame]
+ styleMask:CalcWindowStyleMask(style_flag_)];
+ rect.origin.y = GetScreenSize().height - rect.origin.y - rect.size.height;
+ return cru::platform::graphics::quartz::Convert(rect);
+}
+
+}
+
+OsxWindow::OsxWindow(OsxUiApplication* ui_application)
+ : OsxGuiResource(ui_application), p_(new details::OsxWindowPrivate(this)) {}
+
+OsxWindow::~OsxWindow() {
+ if (p_->window_) {
+ [p_->window_ close];
+ }
+ dynamic_cast<OsxUiApplication*>(GetUiApplication())->UnregisterWindow(this);
+}
+
+void OsxWindow::Close() {
+ if (p_->window_) {
+ [p_->window_ close];
+ }
+}
+
+INativeWindow* OsxWindow::GetParent() { return p_->parent_; }
+
+void OsxWindow::SetParent(INativeWindow* parent) {
+ auto p = CheckPlatform<OsxWindow>(parent, GetPlatformId());
+
+ p_->parent_ = parent;
+
+ if (p_->window_) {
+ [p_->window_ setParentWindow:p->p_->window_];
+ }
+}
+
+WindowStyleFlag OsxWindow::GetStyleFlag() { return p_->style_flag_; }
+
+void OsxWindow::SetStyleFlag(WindowStyleFlag flag) {
+ p_->style_flag_ = flag;
+
+ if (p_->window_) {
+ [p_->window_ close];
+ }
+}
+
+String OsxWindow::GetTitle() { return p_->title_; }
+
+void OsxWindow::SetTitle(String title) {
+ p_->title_ = title;
+
+ if (p_->window_) {
+ auto str = Convert(title);
+ [p_->window_ setTitle:(NSString*)str];
+ CFRelease(str);
+ }
+}
+
+WindowVisibilityType OsxWindow::GetVisibility() {
+ if (!p_->window_) return WindowVisibilityType::Hide;
+ if ([p_->window_ isMiniaturized]) return WindowVisibilityType::Minimize;
+ return [p_->window_ isVisible] ? WindowVisibilityType::Show : WindowVisibilityType::Hide;
+}
+
+void OsxWindow::SetVisibility(WindowVisibilityType visibility) {
+ if (p_->window_) {
+ if (visibility == WindowVisibilityType::Show) {
+ [p_->window_ orderFront:nil];
+ p_->visibility_change_event_.Raise(WindowVisibilityType::Show);
+ } else if (visibility == WindowVisibilityType::Hide) {
+ [p_->window_ orderOut:nil];
+ p_->visibility_change_event_.Raise(WindowVisibilityType::Hide);
+ } else if (visibility == WindowVisibilityType::Minimize) {
+ [p_->window_ miniaturize:nil];
+ }
+ } else {
+ if (visibility == WindowVisibilityType::Show) {
+ p_->CreateWindow();
+ [p_->window_ orderFront:nil];
+ p_->visibility_change_event_.Raise(WindowVisibilityType::Show);
+ }
+ }
+}
+
+Size OsxWindow::GetClientSize() { return p_->content_rect_.GetSize(); }
+
+void OsxWindow::SetClientSize(const Size& size) {
+ if (p_->window_) {
+ auto rect = GetClientRect();
+ rect.SetSize(size);
+ SetClientRect(rect);
+ } else {
+ p_->content_rect_.SetSize(size);
+ }
+}
+
+Rect OsxWindow::GetClientRect() { return p_->content_rect_; }
+
+void OsxWindow::SetClientRect(const Rect& rect) {
+ if (p_->window_) {
+ auto r = Convert(rect);
+ r.origin.y = p_->GetScreenSize().height - r.origin.y - r.size.height;
+ r = [NSWindow frameRectForContentRect:r styleMask:CalcWindowStyleMask(p_->style_flag_)];
+ [p_->window_ setFrame:r display:false];
+ } else {
+ p_->content_rect_ = rect;
+ }
+}
+
+Rect OsxWindow::GetWindowRect() {
+ auto r = Convert(p_->content_rect_);
+ r.origin.y = p_->GetScreenSize().height - r.origin.y - r.size.height;
+ r = [NSWindow frameRectForContentRect:r styleMask:CalcWindowStyleMask(p_->style_flag_)];
+ r.origin.y = p_->GetScreenSize().height - r.origin.y - r.size.height;
+ return Convert(r);
+}
+
+void OsxWindow::SetWindowRect(const Rect& rect) {
+ auto r = Convert(rect);
+ r.origin.y = p_->GetScreenSize().height - r.origin.y - r.size.height;
+ r = [NSWindow frameRectForContentRect:r styleMask:CalcWindowStyleMask(p_->style_flag_)];
+ r.origin.y = p_->GetScreenSize().height - r.origin.y - r.size.height;
+ SetClientRect(Convert(r));
+}
+
+void OsxWindow::RequestRepaint() {
+ if (!p_->draw_timer_) {
+ p_->draw_timer_ = GetUiApplication()->SetImmediate([this] {
+ p_->paint_event_.Raise(nullptr);
+ p_->draw_timer_.Release();
+ });
+ }
+}
+
+std::unique_ptr<graphics::IPainter> OsxWindow::BeginPaint() {
+ if (!p_->window_) {
+ return std::make_unique<graphics::NullPainter>();
+ }
+
+ CGContextRef cg_context = CGLayerGetContext(p_->draw_layer_);
+
+ return std::make_unique<cru::platform::graphics::quartz::QuartzCGContextPainter>(
+ GetUiApplication()->GetGraphicsFactory(), cg_context, false, GetClientSize(),
+ [this](graphics::quartz::QuartzCGContextPainter*) {
+ [[p_->window_ contentView] setNeedsDisplay:YES];
+ });
+}
+
+bool OsxWindow::RequestFocus() {
+ if (!p_->window_) return false;
+ [p_->window_ makeKeyWindow];
+ return true;
+}
+
+Point OsxWindow::GetMousePosition() {
+ auto p = [p_->window_ mouseLocationOutsideOfEventStream];
+ return Point(p.x, p.y);
+}
+
+bool OsxWindow::CaptureMouse() { return true; }
+
+bool OsxWindow::ReleaseMouse() { return true; }
+
+void OsxWindow::SetCursor(std::shared_ptr<ICursor> cursor) {
+ p_->cursor_ = CheckPlatform<OsxCursor>(cursor, GetPlatformId());
+ p_->UpdateCursor();
+}
+
+void OsxWindow::SetToForeground() {
+ if (!p_->window_) return;
+ [p_->window_ makeMainWindow];
+ [p_->window_ orderFrontRegardless];
+}
+
+IEvent<std::nullptr_t>* OsxWindow::CreateEvent() { return &p_->create_event_; }
+IEvent<std::nullptr_t>* OsxWindow::DestroyEvent() { return &p_->destroy_event_; }
+IEvent<std::nullptr_t>* OsxWindow::PaintEvent() { return &p_->paint_event_; }
+IEvent<WindowVisibilityType>* OsxWindow::VisibilityChangeEvent() {
+ return &p_->visibility_change_event_;
+}
+IEvent<Size>* OsxWindow::ResizeEvent() { return &p_->resize_event_; }
+IEvent<FocusChangeType>* OsxWindow::FocusEvent() { return &p_->focus_event_; }
+IEvent<MouseEnterLeaveType>* OsxWindow::MouseEnterLeaveEvent() {
+ return &p_->mouse_enter_leave_event_;
+}
+IEvent<Point>* OsxWindow::MouseMoveEvent() { return &p_->mouse_move_event_; }
+IEvent<NativeMouseButtonEventArgs>* OsxWindow::MouseDownEvent() { return &p_->mouse_down_event_; }
+IEvent<NativeMouseButtonEventArgs>* OsxWindow::MouseUpEvent() { return &p_->mouse_up_event_; }
+IEvent<NativeMouseWheelEventArgs>* OsxWindow::MouseWheelEvent() { return &p_->mouse_wheel_event_; }
+IEvent<NativeKeyEventArgs>* OsxWindow::KeyDownEvent() { return &p_->key_down_event_; }
+IEvent<NativeKeyEventArgs>* OsxWindow::KeyUpEvent() { return &p_->key_up_event_; }
+
+IInputMethodContext* OsxWindow::GetInputMethodContext() { return p_->input_method_context_.get(); }
+}
+
+namespace {
+cru::platform::gui::KeyModifier GetKeyModifier(NSEvent* event) {
+ cru::platform::gui::KeyModifier key_modifier;
+ if (event.modifierFlags & NSEventModifierFlagControl)
+ key_modifier |= cru::platform::gui::KeyModifiers::ctrl;
+ if (event.modifierFlags & NSEventModifierFlagOption)
+ key_modifier |= cru::platform::gui::KeyModifiers::alt;
+ if (event.modifierFlags & NSEventModifierFlagShift)
+ key_modifier |= cru::platform::gui::KeyModifiers::shift;
+ if (event.modifierFlags & NSEventModifierFlagCommand)
+ key_modifier |= cru::platform::gui::KeyModifiers::command;
+ return key_modifier;
+}
+}
+
+@implementation CruWindow {
+ cru::platform::gui::osx::details::OsxWindowPrivate* _p;
+}
+
+- (instancetype)init:(cru::platform::gui::osx::details::OsxWindowPrivate*)p
+ contentRect:(NSRect)contentRect
+ style:(NSWindowStyleMask)style {
+ [super initWithContentRect:contentRect
+ styleMask:style
+ backing:NSBackingStoreBuffered
+ defer:false];
+ _p = p;
+
+ [self setAcceptsMouseMovedEvents:YES];
+
+ return self;
+}
+
+- (BOOL)canBecomeMainWindow {
+ return YES;
+}
+
+- (BOOL)canBecomeKeyWindow {
+ return YES;
+}
+@end
+
+@implementation CruView {
+ cru::platform::gui::osx::details::OsxWindowPrivate* _p;
+ cru::platform::gui::osx::details::OsxInputMethodContextPrivate* _input_context_p;
+ NSMutableAttributedString* _input_context_text;
+}
+
+- (instancetype)init:(cru::platform::gui::osx::details::OsxWindowPrivate*)p
+ input_context_p:
+ (cru::platform::gui::osx::details::OsxInputMethodContextPrivate*)input_context_p
+ frame:(cru::platform::Rect)frame {
+ [super initWithFrame:cru::platform::graphics::quartz::Convert(frame)];
+ _p = p;
+ _input_context_p = input_context_p;
+
+ auto tracking_area = [[NSTrackingArea alloc]
+ initWithRect:Convert(frame)
+ options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways)
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:tracking_area];
+
+ return self;
+}
+
+- (void)drawRect:(NSRect)dirtyRect {
+ auto cg_context = [[NSGraphicsContext currentContext] CGContext];
+ auto layer = _p->GetDrawLayer();
+ Ensures(layer);
+ CGContextDrawLayerAtPoint(cg_context, CGPointMake(0, 0), layer);
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (BOOL)canBecomeKeyView {
+ return YES;
+}
+
+- (void)mouseMoved:(NSEvent*)event {
+ _p->OnMouseMove(cru::platform::Point(event.locationInWindow.x, event.locationInWindow.y));
+}
+
+- (void)mouseDragged:(NSEvent*)event {
+ _p->OnMouseMove(cru::platform::Point(event.locationInWindow.x, event.locationInWindow.y));
+}
+
+- (void)rightMouseDragged:(NSEvent*)event {
+ _p->OnMouseMove(cru::platform::Point(event.locationInWindow.x, event.locationInWindow.y));
+}
+
+- (void)mouseEntered:(NSEvent*)event {
+ _p->OnMouseEnterLeave(cru::platform::gui::MouseEnterLeaveType::Enter);
+}
+
+- (void)mouseExited:(NSEvent*)event {
+ _p->OnMouseEnterLeave(cru::platform::gui::MouseEnterLeaveType::Leave);
+}
+
+- (void)mouseDown:(NSEvent*)event {
+ [[self window] makeKeyWindow];
+
+ auto key_modifier = GetKeyModifier(event);
+ cru::platform::Point p(event.locationInWindow.x, event.locationInWindow.y);
+
+ _p->OnMouseDown(cru::platform::gui::mouse_buttons::left, p, key_modifier);
+}
+
+- (void)mouseUp:(NSEvent*)event {
+ auto key_modifier = GetKeyModifier(event);
+ cru::platform::Point p(event.locationInWindow.x, event.locationInWindow.y);
+
+ _p->OnMouseUp(cru::platform::gui::mouse_buttons::left, p, key_modifier);
+}
+
+- (void)rightMouseDown:(NSEvent*)event {
+ auto key_modifier = GetKeyModifier(event);
+ cru::platform::Point p(event.locationInWindow.x, event.locationInWindow.y);
+
+ _p->OnMouseDown(cru::platform::gui::mouse_buttons::right, p, key_modifier);
+}
+
+- (void)rightMouseUp:(NSEvent*)event {
+ auto key_modifier = GetKeyModifier(event);
+ cru::platform::Point p(event.locationInWindow.x, event.locationInWindow.y);
+
+ _p->OnMouseUp(cru::platform::gui::mouse_buttons::right, p, key_modifier);
+}
+
+- (void)scrollWheel:(NSEvent*)event {
+ auto key_modifier = GetKeyModifier(event);
+ cru::platform::Point p(event.locationInWindow.x, event.locationInWindow.y);
+
+ if (event.scrollingDeltaY) {
+ _p->OnMouseWheel(static_cast<float>(event.scrollingDeltaY), p, key_modifier, false);
+ }
+
+ if (event.scrollingDeltaX) {
+ _p->OnMouseWheel(static_cast<float>(event.scrollingDeltaX), p, key_modifier, true);
+ }
+}
+
+namespace {
+using cru::platform::gui::KeyCode;
+const std::unordered_set<KeyCode> input_context_handle_codes{
+ KeyCode::A,
+ KeyCode::B,
+ KeyCode::C,
+ KeyCode::D,
+ KeyCode::E,
+ KeyCode::F,
+ KeyCode::G,
+ KeyCode::H,
+ KeyCode::I,
+ KeyCode::J,
+ KeyCode::K,
+ KeyCode::L,
+ KeyCode::M,
+ KeyCode::N,
+ KeyCode::O,
+ KeyCode::P,
+ KeyCode::Q,
+ KeyCode::R,
+ KeyCode::S,
+ KeyCode::T,
+ KeyCode::U,
+ KeyCode::V,
+ KeyCode::W,
+ KeyCode::X,
+ KeyCode::Y,
+ KeyCode::Z,
+ KeyCode::N0,
+ KeyCode::N1,
+ KeyCode::N2,
+ KeyCode::N3,
+ KeyCode::N4,
+ KeyCode::N5,
+ KeyCode::N6,
+ KeyCode::N7,
+ KeyCode::N8,
+ KeyCode::N9,
+ KeyCode::Comma,
+ KeyCode::Period,
+ KeyCode::Slash,
+ KeyCode::Semicolon,
+ KeyCode::Quote,
+ KeyCode::LeftSquareBracket,
+ KeyCode::RightSquareBracket,
+ KeyCode::BackSlash,
+ KeyCode::Minus,
+ KeyCode::Equal,
+ KeyCode::GraveAccent,
+};
+}
+
+const std::unordered_set<KeyCode> input_context_handle_codes_when_has_text{
+ KeyCode::Backspace, KeyCode::Space, KeyCode::Return, KeyCode::Left,
+ KeyCode::Right, KeyCode::Up, KeyCode::Down};
+
+- (void)keyDown:(NSEvent*)event {
+ auto key_modifier = GetKeyModifier(event);
+
+ bool handled = false;
+
+ auto input_context = dynamic_cast<cru::platform::gui::osx::OsxInputMethodContext*>(
+ _p->GetWindow()->GetInputMethodContext());
+ Ensures(input_context);
+
+ auto c = cru::platform::gui::osx::KeyCodeFromOsxToCru(event.keyCode);
+
+ if (input_context->IsEnabled()) {
+ if (input_context_handle_codes.count(c) &&
+ !(key_modifier & ~cru::platform::gui::KeyModifiers::shift)) {
+ handled = [[self inputContext] handleEvent:event];
+ } else if (input_context_handle_codes_when_has_text.count(c) && !key_modifier) {
+ if (!input_context->GetCompositionText().text.empty()) {
+ handled = [[self inputContext] handleEvent:event];
+ } else {
+ if (c == KeyCode::Return) {
+ _input_context_p->RaiseTextEvent(u"\n");
+ handled = true;
+ } else if (c == KeyCode::Space) {
+ _input_context_p->RaiseTextEvent(u" ");
+ handled = true;
+ }
+ }
+ }
+ }
+
+ if (!handled) {
+ _p->OnKeyDown(c, key_modifier);
+ }
+}
+
+- (void)keyUp:(NSEvent*)event {
+ // cru::CRU_LOG_DEBUG(u"CruView", u"Recieved key up.");
+
+ auto key_modifier = GetKeyModifier(event);
+ auto c = cru::platform::gui::osx::KeyCodeFromOsxToCru(event.keyCode);
+
+ _p->OnKeyUp(c, key_modifier);
+}
+
+- (BOOL)hasMarkedText {
+ return _input_context_text != nil;
+}
+
+- (NSRange)markedRange {
+ return _input_context_text == nil ? NSRange{NSNotFound, 0}
+ : NSRange{0, [_input_context_text length]};
+}
+
+- (NSRange)selectedRange {
+ return NSMakeRange(_input_context_p->GetSelectionRange().position,
+ _input_context_p->GetSelectionRange().count);
+}
+
+- (void)setMarkedText:(id)string
+ selectedRange:(NSRange)selectedRange
+ replacementRange:(NSRange)replacementRange {
+ CFStringRef s;
+ if ([string isKindOfClass:[NSString class]]) {
+ s = (CFStringRef)string;
+ } else {
+ auto as = (CFAttributedStringRef)string;
+ s = CFAttributedStringGetString(as);
+ }
+
+ auto ss = Convert(s);
+
+ // cru::CRU_LOG_DEBUG(u"CruView",
+ // u"Received setMarkedText string: {}, selected range: ({}, {}), "
+ // u"replacement range: ({}, {}).",
+ // ss, selectedRange.location, selectedRange.length, replacementRange.location,
+ // replacementRange.length);
+
+ if (_input_context_text == nil) {
+ _input_context_text = [[NSMutableAttributedString alloc] init];
+ _input_context_p->RaiseCompositionStartEvent();
+ }
+
+ if (replacementRange.location == NSNotFound) replacementRange.location = 0;
+
+ [_input_context_text
+ replaceCharactersInRange:NSMakeRange(0, [_input_context_text length])
+ withAttributedString:[[NSAttributedString alloc] initWithString:(NSString*)s]];
+
+ cru::platform::gui::CompositionText composition_text;
+ composition_text.text = Convert((CFStringRef)[_input_context_text string]);
+ composition_text.selection.position = ss.IndexFromCodePointToCodeUnit(selectedRange.location);
+ composition_text.selection.count =
+ ss.IndexFromCodePointToCodeUnit(selectedRange.location + selectedRange.length) -
+ composition_text.selection.position;
+ _input_context_p->SetCompositionText(composition_text);
+ _input_context_p->RaiseCompositionEvent();
+}
+
+- (void)unmarkText {
+ _input_context_text = nil;
+ _input_context_p->RaiseCompositionEndEvent();
+}
+
+- (NSArray<NSAttributedStringKey>*)validAttributesForMarkedText {
+ return @[
+ (NSString*)kCTUnderlineColorAttributeName, (NSString*)kCTUnderlineStyleAttributeName,
+ (NSString*)kCTForegroundColorAttributeName, (NSString*)kCTBackgroundColorAttributeName
+ ];
+}
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
+ actualRange:(NSRangePointer)actualRange {
+ cru::Range r(range.location, range.length);
+
+ r = r.CoerceInto(0, [_input_context_text length]);
+
+ return [_input_context_text attributedSubstringFromRange:NSMakeRange(r.position, r.count)];
+}
+
+- (void)insertText:(id)string replacementRange:(NSRange)replacementRange {
+ CFStringRef s;
+ if ([string isKindOfClass:[NSString class]]) {
+ s = (CFStringRef)string;
+ } else {
+ auto as = (CFAttributedStringRef)string;
+ s = CFAttributedStringGetString(as);
+ }
+
+ _input_context_text = nil;
+ _input_context_p->SetCompositionText(cru::platform::gui::CompositionText());
+ cru::String ss = Convert(s);
+
+ // cru::CRU_LOG_DEBUG(u"CruView", u"Finish composition: {}, replacement range: ({}, {})", ss,
+ // replacementRange.location, replacementRange.length);
+
+ _input_context_p->RaiseCompositionEvent();
+ _input_context_p->RaiseCompositionEndEvent();
+ _input_context_p->RaiseTextEvent(ss);
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)point {
+ return NSNotFound;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {
+ NSRect result;
+ result.origin.x = _input_context_p->GetCandidateWindowPosition().x;
+ result.origin.y = _input_context_p->GetCandidateWindowPosition().y;
+ result.size.height = 16;
+ result.size.width = 0;
+ return result;
+}
+
+- (void)doCommandBySelector:(SEL)selector {
+ _input_context_p->PerformSel(selector);
+}
+@end
+
+@implementation CruWindowDelegate {
+ cru::platform::gui::osx::details::OsxWindowPrivate* _p;
+}
+
+- (id)init:(cru::platform::gui::osx::details::OsxWindowPrivate*)p {
+ _p = p;
+ return self;
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ _p->OnWindowWillClose();
+}
+
+- (void)windowDidExpose:(NSNotification*)notification {
+ _p->OnWindowDidExpose();
+}
+
+- (void)windowDidUpdate:(NSNotification*)notification {
+ _p->OnWindowDidUpdate();
+}
+
+- (void)windowDidMove:(NSNotification*)notification {
+ _p->OnWindowDidMove();
+}
+
+- (void)windowDidResize:(NSNotification*)notification {
+ _p->OnWindowDidResize();
+}
+
+- (void)windowDidBecomeKey:(NSNotification*)notification {
+ _p->OnBecomeKeyWindow();
+}
+
+- (void)windowDidResignKey:(NSNotification*)notification {
+ _p->OnResignKeyWindow();
+}
+@end