1/*
2* Copyright 2019 Google Inc.
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#include "SkUtils.h"
9#include "WindowContextFactory_mac.h"
10#include "Window_mac.h"
11
12@interface MainView : NSView
13
14- (MainView*)initWithWindow:(sk_app::Window*)initWindow;
15
16@end
17
18@interface WindowDelegate : NSObject<NSWindowDelegate>
19
20- (WindowDelegate*)initWithWindow:(sk_app::Window*)initWindow;
21
22@end
23
24///////////////////////////////////////////////////////////////////////////////
25
26using sk_app::Window;
27
28namespace sk_app {
29
30static int gWindowCount = 0;
31
32Window* Window::CreateNativeWindow(void*) {
33    Window_mac* window = new Window_mac();
34    if (!window->initWindow()) {
35        delete window;
36        return nullptr;
37    }
38
39    ++gWindowCount;
40    return window;
41}
42
43bool Window_mac::initWindow() {
44    if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
45        this->closeWindow();
46    }
47
48    // we already have a window
49    if (fWindow) {
50        return true;
51    }
52
53    constexpr int initialWidth = 1280;
54    constexpr int initialHeight = 960;
55
56    NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
57                              NSMiniaturizableWindowMask);
58
59    NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight);
60    fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle
61                                backing:NSBackingStoreBuffered defer:NO];
62    if (nil == fWindow) {
63        return false;
64    }
65    WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this];
66    [fWindow setDelegate:delegate];
67    [delegate release];
68
69    // create view
70    MainView* view = [[[MainView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)] initWithWindow:this];
71    if (nil == view) {
72        [fWindow release];
73        fWindow = nil;
74        return false;
75    }
76
77    // attach view to window
78    [fWindow setContentView:view];
79
80    return true;
81}
82
83void Window_mac::closeWindow() {
84    [fWindow release];
85    fWindow = nil;
86}
87
88void Window_mac::setTitle(const char* title) {
89    NSString *titleString = [NSString stringWithCString:title encoding:NSUTF8StringEncoding];
90    [fWindow setTitle:titleString];
91}
92
93void Window_mac::show() {
94    [NSApp activateIgnoringOtherApps:YES];
95
96    [fWindow makeKeyAndOrderFront:NSApp];
97}
98
99bool Window_mac::attach(BackendType attachType) {
100    this->initWindow();
101
102    window_context_factory::MacWindowInfo info;
103    info.fMainView = this->view();
104    switch (attachType) {
105        case kRaster_BackendType:
106            fWindowContext = NewRasterForMac(info, fRequestedDisplayParams);
107            break;
108
109        case kNativeGL_BackendType:
110        default:
111            fWindowContext = NewGLForMac(info, fRequestedDisplayParams);
112            break;
113    }
114    this->onBackendCreated();
115
116    return (SkToBool(fWindowContext));
117}
118
119void Window_mac::onInval() {
120    [[fWindow contentView] setNeedsDisplay:YES];
121    // MacOS already queues a single drawRect event for multiple invalidations
122    // so we don't need to use our invalidation method (and it can mess things up
123    // if for some reason MacOS skips a drawRect when we need one).
124    this->markInvalProcessed();
125}
126
127}   // namespace sk_app
128
129///////////////////////////////////////////////////////////////////////////////
130
131@implementation WindowDelegate {
132    sk_app::Window* fWindow;
133}
134
135- (WindowDelegate*)initWithWindow:(sk_app::Window *)initWindow {
136    fWindow = initWindow;
137
138    return self;
139}
140
141- (void)windowDidResize:(NSNotification *)notification {
142    sk_app::Window_mac* macWindow = reinterpret_cast<sk_app::Window_mac*>(fWindow);
143    const NSRect mainRect = [macWindow->view() bounds];
144
145    fWindow->onResize(mainRect.size.width, mainRect.size.height);
146}
147
148- (BOOL)windowShouldClose:(NSWindow*)sender {
149    --sk_app::gWindowCount;
150    if (sk_app::gWindowCount < 1) {
151        [NSApp terminate:self];
152    }
153
154    reinterpret_cast<sk_app::Window_mac*>(fWindow)->closeWindow();
155
156    return FALSE;
157}
158
159@end
160
161///////////////////////////////////////////////////////////////////////////////
162
163@implementation MainView {
164    sk_app::Window* fWindow;
165}
166
167- (MainView*)initWithWindow:(sk_app::Window *)initWindow {
168    fWindow = initWindow;
169
170    return self;
171}
172
173- (BOOL)isOpaque {
174    return YES;
175}
176
177- (BOOL)canBecomeKeyView {
178    return YES;
179}
180
181- (BOOL)acceptsFirstResponder {
182    return YES;
183}
184
185- (void)drawRect:(NSRect)dirtyRect {
186    fWindow->onPaint();
187}
188
189static Window::Key get_key(unsigned short vk) {
190    // This will work with an ANSI QWERTY keyboard.
191    // Something more robust would be needed to support alternate keyboards.
192    static const struct {
193        unsigned short fVK;
194        Window::Key    fKey;
195    } gPair[] = {
196        { 0x33, Window::Key::kBack },
197        { 0x24, Window::Key::kOK },
198        { 0x7E, Window::Key::kUp },
199        { 0x7D, Window::Key::kDown },
200        { 0x7B, Window::Key::kLeft },
201        { 0x7C, Window::Key::kRight },
202        { 0x30, Window::Key::kTab },
203        { 0x74, Window::Key::kPageUp },
204        { 0x79, Window::Key::kPageDown },
205        { 0x73, Window::Key::kHome },
206        { 0x77, Window::Key::kEnd },
207        { 0x75, Window::Key::kDelete },
208        { 0x35, Window::Key::kEscape },
209        { 0x38, Window::Key::kShift },
210        { 0x3C, Window::Key::kShift },
211        { 0x3B, Window::Key::kCtrl },
212        { 0x3E, Window::Key::kCtrl },
213        { 0x3A, Window::Key::kOption },
214        { 0x3D, Window::Key::kOption },
215        { 0x00, Window::Key::kA },
216        { 0x08, Window::Key::kC },
217        { 0x09, Window::Key::kV },
218        { 0x07, Window::Key::kX },
219        { 0x10, Window::Key::kY },
220        { 0x06, Window::Key::kZ },
221    };
222    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
223        if (gPair[i].fVK == vk) {
224            return gPair[i].fKey;
225        }
226    }
227
228    return Window::Key::kNONE;
229}
230
231static uint32_t get_modifiers(const NSEvent* event) {
232    NSUInteger modifierFlags = [event modifierFlags];
233    auto modifiers = 0;
234
235    if (modifierFlags & NSEventModifierFlagShift) {
236        modifiers |= Window::kShift_ModifierKey;
237    }
238    if (modifierFlags & NSEventModifierFlagControl) {
239        modifiers |= Window::kControl_ModifierKey;
240    }
241    if (modifierFlags & NSEventModifierFlagOption) {
242        modifiers |= Window::kOption_ModifierKey;
243    }
244
245    if ((NSKeyDown == [event type] || NSKeyUp == [event type]) &&
246        NO == [event isARepeat]) {
247        modifiers |= Window::kFirstPress_ModifierKey;
248    }
249
250    return modifiers;
251}
252
253- (void)keyDown:(NSEvent *)event {
254    Window::Key key = get_key([event keyCode]);
255    if (key != Window::Key::kNONE) {
256        if (!fWindow->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
257            if (Window::Key::kEscape == key) {
258                [NSApp terminate:self];
259            }
260        }
261    }
262
263    NSString* characters = [event charactersIgnoringModifiers];
264    NSUInteger len = [characters length];
265    if (len > 0) {
266        unichar* charBuffer = new unichar[len+1];
267        [characters getCharacters:charBuffer range:NSMakeRange(0, len)];
268        for (NSUInteger i = 0; i < len; ++i) {
269            (void) fWindow->onChar((SkUnichar) charBuffer[i], get_modifiers(event));
270        }
271        delete [] charBuffer;
272    }
273}
274
275- (void)keyUp:(NSEvent *)event {
276    Window::Key key = get_key([event keyCode]);
277    if (key != Window::Key::kNONE) {
278        (void) fWindow->onKey(key, Window::kUp_InputState, get_modifiers(event));
279    }
280}
281
282- (void)mouseDown:(NSEvent *)event {
283    const NSPoint pos = [event locationInWindow];
284    const NSRect rect = [self frame];
285    fWindow->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState,
286                     get_modifiers(event));
287}
288
289- (void)mouseDragged:(NSEvent *)event {
290    [self mouseMoved:event];
291}
292
293- (void)mouseUp:(NSEvent *)event {
294    const NSPoint pos = [event locationInWindow];
295    const NSRect rect = [self frame];
296    fWindow->onMouse(pos.x, rect.size.height - pos.y, Window::kUp_InputState,
297                     get_modifiers(event));
298}
299
300- (void)mouseMoved:(NSEvent *)event {
301    const NSPoint pos = [event locationInWindow];
302    const NSRect rect = [self frame];
303    fWindow->onMouse(pos.x, rect.size.height - pos.y, Window::kMove_InputState,
304                     get_modifiers(event));
305}
306
307- (void)scrollWheel:(NSEvent *)event {
308    // TODO: support hasPreciseScrollingDeltas?
309    fWindow->onMouseWheel([event scrollingDeltaY], get_modifiers(event));
310}
311
312
313@end
314
315