• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 WindowDelegate : NSObject<NSWindowDelegate>
13
14- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow;
15
16@end
17
18@interface MainView : NSView
19@end
20
21///////////////////////////////////////////////////////////////////////////////
22
23using sk_app::Window;
24
25namespace sk_app {
26
27SkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap;
28
29Window* Window::CreateNativeWindow(void*) {
30    Window_mac* window = new Window_mac();
31    if (!window->initWindow()) {
32        delete window;
33        return nullptr;
34    }
35
36    return window;
37}
38
39bool Window_mac::initWindow() {
40    if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
41        this->closeWindow();
42    }
43
44    // we already have a window
45    if (fWindow) {
46        return true;
47    }
48
49    constexpr int initialWidth = 1280;
50    constexpr int initialHeight = 960;
51
52    NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
53                              NSMiniaturizableWindowMask);
54
55    NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight);
56    fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle
57                                backing:NSBackingStoreBuffered defer:NO];
58    if (nil == fWindow) {
59        return false;
60    }
61    [fWindow setAcceptsMouseMovedEvents:YES];
62
63    WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this];
64    [fWindow setDelegate:delegate];
65    [delegate release];
66
67    // create view
68    MainView* view = [[MainView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)] ;
69    if (nil == view) {
70        [fWindow release];
71        fWindow = nil;
72        return false;
73    }
74
75    // attach view to window
76    [fWindow setContentView:view];
77
78    fWindowNumber = fWindow.windowNumber;
79    gWindowMap.add(this);
80
81    return true;
82}
83
84void Window_mac::closeWindow() {
85    if (nil != fWindow) {
86        gWindowMap.remove(fWindowNumber);
87        if (sk_app::Window_mac::gWindowMap.count() < 1) {
88            [NSApp terminate:fWindow];
89        }
90        [fWindow release];
91        fWindow = nil;
92    }
93}
94
95void Window_mac::setTitle(const char* title) {
96    NSString *titleString = [NSString stringWithCString:title encoding:NSUTF8StringEncoding];
97    [fWindow setTitle:titleString];
98}
99
100void Window_mac::show() {
101    [NSApp activateIgnoringOtherApps:YES];
102
103    [fWindow makeKeyAndOrderFront:NSApp];
104}
105
106bool Window_mac::attach(BackendType attachType) {
107    this->initWindow();
108
109    window_context_factory::MacWindowInfo info;
110    info.fMainView = [fWindow contentView];
111    switch (attachType) {
112        case kRaster_BackendType:
113            fWindowContext = NewRasterForMac(info, fRequestedDisplayParams);
114            break;
115#ifdef SK_VULKAN
116        case kVulkan_BackendType:
117            fWindowContext = NewVulkanForMac(info, fRequestedDisplayParams);
118            break;
119#endif
120#ifdef SK_METAL
121        case kMetal_BackendType:
122            fWindowContext = NewMetalForMac(info, fRequestedDisplayParams);
123            break;
124#endif
125        case kNativeGL_BackendType:
126        default:
127            fWindowContext = NewGLForMac(info, fRequestedDisplayParams);
128            break;
129    }
130    this->onBackendCreated();
131
132    return (SkToBool(fWindowContext));
133}
134
135static Window::Key get_key(unsigned short vk) {
136    // This will work with an ANSI QWERTY keyboard.
137    // Something more robust would be needed to support alternate keyboards.
138    static const struct {
139        unsigned short fVK;
140        Window::Key    fKey;
141    } gPair[] = {
142        { 0x33, Window::Key::kBack },
143        { 0x24, Window::Key::kOK },
144        { 0x7E, Window::Key::kUp },
145        { 0x7D, Window::Key::kDown },
146        { 0x7B, Window::Key::kLeft },
147        { 0x7C, Window::Key::kRight },
148        { 0x30, Window::Key::kTab },
149        { 0x74, Window::Key::kPageUp },
150        { 0x79, Window::Key::kPageDown },
151        { 0x73, Window::Key::kHome },
152        { 0x77, Window::Key::kEnd },
153        { 0x75, Window::Key::kDelete },
154        { 0x35, Window::Key::kEscape },
155        { 0x38, Window::Key::kShift },
156        { 0x3C, Window::Key::kShift },
157        { 0x3B, Window::Key::kCtrl },
158        { 0x3E, Window::Key::kCtrl },
159        { 0x3A, Window::Key::kOption },
160        { 0x3D, Window::Key::kOption },
161        { 0x00, Window::Key::kA },
162        { 0x08, Window::Key::kC },
163        { 0x09, Window::Key::kV },
164        { 0x07, Window::Key::kX },
165        { 0x10, Window::Key::kY },
166        { 0x06, Window::Key::kZ },
167    };
168    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
169        if (gPair[i].fVK == vk) {
170            return gPair[i].fKey;
171        }
172    }
173
174    return Window::Key::kNONE;
175}
176
177static uint32_t get_modifiers(const NSEvent* event) {
178    NSUInteger modifierFlags = [event modifierFlags];
179    auto modifiers = 0;
180
181    if (modifierFlags & NSEventModifierFlagShift) {
182        modifiers |= Window::kShift_ModifierKey;
183    }
184    if (modifierFlags & NSEventModifierFlagControl) {
185        modifiers |= Window::kControl_ModifierKey;
186    }
187    if (modifierFlags & NSEventModifierFlagOption) {
188        modifiers |= Window::kOption_ModifierKey;
189    }
190
191    if ((NSKeyDown == [event type] || NSKeyUp == [event type]) &&
192        NO == [event isARepeat]) {
193        modifiers |= Window::kFirstPress_ModifierKey;
194    }
195
196    return modifiers;
197}
198
199void Window_mac::PaintWindows() {
200    SkTDynamicHash<Window_mac, NSInteger>::Iter iter(&gWindowMap);
201    while (!iter.done()) {
202        if ((*iter).fIsContentInvalidated) {
203            (*iter).onPaint();
204        }
205        ++iter;
206    }
207}
208
209void Window_mac::HandleWindowEvent(const NSEvent* event) {
210    Window_mac* win = gWindowMap.find(event.window.windowNumber);
211    if (win) {
212        win->handleEvent(event);
213    }
214}
215
216void Window_mac::handleEvent(const NSEvent* event) {
217    switch (event.type) {
218        case NSEventTypeKeyDown: {
219            Window::Key key = get_key([event keyCode]);
220            if (key != Window::Key::kNONE) {
221                if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
222                    if (Window::Key::kEscape == key) {
223                        [NSApp terminate:fWindow];
224                    }
225                }
226            }
227
228            NSString* characters = [event charactersIgnoringModifiers];
229            NSUInteger len = [characters length];
230            if (len > 0) {
231                unichar* charBuffer = new unichar[len+1];
232                [characters getCharacters:charBuffer range:NSMakeRange(0, len)];
233                for (NSUInteger i = 0; i < len; ++i) {
234                    (void) this->onChar((SkUnichar) charBuffer[i], get_modifiers(event));
235                }
236                delete [] charBuffer;
237            }
238            break;
239        }
240        case NSEventTypeKeyUp: {
241            Window::Key key = get_key([event keyCode]);
242            if (key != Window::Key::kNONE) {
243                (void) this->onKey(key, Window::kUp_InputState, get_modifiers(event));
244            }
245            break;
246        }
247        case NSEventTypeLeftMouseDown: {
248            const NSPoint pos = [event locationInWindow];
249            const NSRect rect = [fWindow.contentView frame];
250            if (NSPointInRect(pos, rect)) {
251                // This might be a resize event -- until we know we'll store the event
252                // and reset later if need be
253                fIsMouseDown = true;
254                fMouseDownPos = pos;
255                fMouseModifiers = get_modifiers(event);
256                this->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState,
257                              fMouseModifiers);
258            }
259            break;
260        }
261        case NSEventTypeLeftMouseUp: {
262            const NSPoint pos = [event locationInWindow];
263            const NSRect rect = [fWindow.contentView frame];
264            this->onMouse(pos.x, rect.size.height - pos.y, Window::kUp_InputState,
265                          get_modifiers(event));
266            break;
267        }
268        case NSEventTypeMouseMoved:
269        case NSEventTypeLeftMouseDragged: {
270            const NSPoint pos = [event locationInWindow];
271            const NSRect rect = [fWindow.contentView frame];
272            this->onMouse(pos.x, rect.size.height - pos.y, Window::kMove_InputState,
273                          get_modifiers(event));
274            break;
275        }
276        case NSEventTypeScrollWheel:
277            // TODO: support hasPreciseScrollingDeltas?
278            this->onMouseWheel([event scrollingDeltaY], get_modifiers(event));
279            break;
280
281        default:
282            break;
283    }
284}
285
286void Window_mac::resetMouse() {
287    if (fIsMouseDown) {
288        // We're resizing so just send a mouse up event in the same place
289        const NSRect rect = [fWindow.contentView frame];
290        this->onMouse(fMouseDownPos.x, rect.size.height - fMouseDownPos.y, Window::kUp_InputState,
291                      fMouseModifiers);
292        fIsMouseDown = false;
293    }
294}
295
296}   // namespace sk_app
297
298///////////////////////////////////////////////////////////////////////////////
299
300@implementation WindowDelegate {
301    sk_app::Window_mac* fWindow;
302}
303
304- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow {
305    fWindow = initWindow;
306
307    return self;
308}
309
310- (void)windowDidResize:(NSNotification *)notification {
311    const NSRect mainRect = [fWindow->window().contentView bounds];
312
313    fWindow->onResize(mainRect.size.width, mainRect.size.height);
314    fWindow->resetMouse();
315}
316
317- (BOOL)windowShouldClose:(NSWindow*)sender {
318    fWindow->closeWindow();
319
320    return FALSE;
321}
322
323@end
324
325///////////////////////////////////////////////////////////////////////////////
326
327@implementation MainView
328- (BOOL)isOpaque {
329    return YES;
330}
331
332- (BOOL)canBecomeKeyView {
333    return YES;
334}
335
336- (BOOL)acceptsFirstResponder {
337    return YES;
338}
339
340// We keep these around to prevent beeping when the system can't determine
341// where the focus for key events is.
342- (void)keyDown:(NSEvent *)event {
343}
344
345- (void)keyUp:(NSEvent *)event {
346}
347@end
348