• 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 <Carbon/Carbon.h>
9
10#include "include/core/SkTypes.h"
11#include "tools/sk_app/mac/WindowContextFactory_mac.h"
12#include "tools/sk_app/mac/Window_mac.h"
13#include "tools/skui/ModifierKey.h"
14
15@interface WindowDelegate : NSObject<NSWindowDelegate>
16
17- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow;
18
19@end
20
21@interface MainView : NSView
22
23- (MainView*)initWithWindow:(sk_app::Window_mac*)initWindow;
24
25@end
26
27///////////////////////////////////////////////////////////////////////////////
28
29using sk_app::Window;
30
31namespace sk_app {
32
33SkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap;
34
35Window* Window::CreateNativeWindow(void*) {
36    Window_mac* window = new Window_mac();
37    if (!window->initWindow()) {
38        delete window;
39        return nullptr;
40    }
41
42    return window;
43}
44
45bool Window_mac::initWindow() {
46    // we already have a window
47    if (fWindow) {
48        return true;
49    }
50
51    // Create a delegate to track certain events
52    WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this];
53    if (nil == delegate) {
54        return false;
55    }
56
57    // Create Cocoa window
58    constexpr int initialWidth = 1280;
59    constexpr int initialHeight = 960;
60    NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight);
61
62    NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
63                              NSMiniaturizableWindowMask);
64
65    fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle
66                                backing:NSBackingStoreBuffered defer:NO];
67    if (nil == fWindow) {
68        [delegate release];
69        return false;
70    }
71
72    // create view
73    MainView* view = [[MainView alloc] initWithWindow:this];
74    if (nil == view) {
75        [fWindow release];
76        [delegate release];
77        return false;
78    }
79
80    [fWindow setContentView:view];
81    [fWindow makeFirstResponder:view];
82    [fWindow setDelegate:delegate];
83    [fWindow setAcceptsMouseMovedEvents:YES];
84    [fWindow setRestorable:NO];
85
86    // Should be retained by window now
87    [view release];
88
89    fWindowNumber = fWindow.windowNumber;
90    gWindowMap.add(this);
91
92    return true;
93}
94
95void Window_mac::closeWindow() {
96    if (nil != fWindow) {
97        gWindowMap.remove(fWindowNumber);
98        if (sk_app::Window_mac::gWindowMap.count() < 1) {
99            [NSApp terminate:fWindow];
100        }
101        [fWindow close];
102        fWindow = nil;
103    }
104}
105
106void Window_mac::setTitle(const char* title) {
107    if (NSString* titleStr = [NSString stringWithUTF8String:title]) {
108        [fWindow setTitle:titleStr];
109    }
110}
111
112void Window_mac::show() {
113    [fWindow orderFront:nil];
114
115    [NSApp activateIgnoringOtherApps:YES];
116    [fWindow makeKeyAndOrderFront:NSApp];
117}
118
119bool Window_mac::attach(BackendType attachType) {
120    this->initWindow();
121
122    window_context_factory::MacWindowInfo info;
123    info.fMainView = [fWindow contentView];
124    switch (attachType) {
125#ifdef SK_DAWN
126        case kDawn_BackendType:
127            fWindowContext = MakeDawnMTLForMac(info, fRequestedDisplayParams);
128            break;
129#endif
130#ifdef SK_VULKAN
131        case kVulkan_BackendType:
132            fWindowContext = MakeVulkanForMac(info, fRequestedDisplayParams);
133            break;
134#endif
135#ifdef SK_METAL
136        case kMetal_BackendType:
137            fWindowContext = MakeMetalForMac(info, fRequestedDisplayParams);
138            break;
139#ifdef SK_GRAPHITE_ENABLED
140        case kGraphiteMetal_BackendType:
141            fWindowContext = MakeGraphiteMetalForMac(info, fRequestedDisplayParams);
142            break;
143#endif
144#endif
145#ifdef SK_GL
146        case kNativeGL_BackendType:
147            fWindowContext = MakeGLForMac(info, fRequestedDisplayParams);
148            break;
149        case kRaster_BackendType:
150            fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams);
151            break;
152#endif
153        default:
154            SkASSERT_RELEASE(false);
155    }
156    this->onBackendCreated();
157
158    return SkToBool(fWindowContext);
159}
160
161float Window_mac::scaleFactor() const {
162    return sk_app::GetBackingScaleFactor(fWindow.contentView);
163}
164
165void Window_mac::PaintWindows() {
166    gWindowMap.foreach([&](Window_mac* window) {
167        if (window->fIsContentInvalidated) {
168            window->onPaint();
169        }
170    });
171}
172
173}   // namespace sk_app
174
175///////////////////////////////////////////////////////////////////////////////
176
177@implementation WindowDelegate {
178    sk_app::Window_mac* fWindow;
179}
180
181- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow {
182    fWindow = initWindow;
183
184    return self;
185}
186
187- (void)windowDidResize:(NSNotification *)notification {
188    NSView* view = fWindow->window().contentView;
189    CGFloat scale = sk_app::GetBackingScaleFactor(view);
190    fWindow->onResize(view.bounds.size.width * scale, view.bounds.size.height * scale);
191    fWindow->inval();
192}
193
194- (BOOL)windowShouldClose:(NSWindow*)sender {
195    fWindow->closeWindow();
196
197    return FALSE;
198}
199
200@end
201
202///////////////////////////////////////////////////////////////////////////////
203
204static skui::Key get_key(unsigned short vk) {
205    // This will work with an ANSI QWERTY keyboard.
206    // Something more robust would be needed to support alternate keyboards.
207    static const struct {
208        unsigned short fVK;
209        skui::Key      fKey;
210    } gPair[] = {
211        { kVK_Delete,        skui::Key::kBack },
212        { kVK_Return,        skui::Key::kOK },
213        { kVK_UpArrow,       skui::Key::kUp },
214        { kVK_DownArrow,     skui::Key::kDown },
215        { kVK_LeftArrow,     skui::Key::kLeft },
216        { kVK_RightArrow,    skui::Key::kRight },
217        { kVK_Tab,           skui::Key::kTab },
218        { kVK_PageUp,        skui::Key::kPageUp },
219        { kVK_PageDown,      skui::Key::kPageDown },
220        { kVK_Home,          skui::Key::kHome },
221        { kVK_End,           skui::Key::kEnd },
222        { kVK_ForwardDelete, skui::Key::kDelete },
223        { kVK_Escape,        skui::Key::kEscape },
224        { kVK_Shift,         skui::Key::kShift },
225        { kVK_RightShift,    skui::Key::kShift },
226        { kVK_Control,       skui::Key::kCtrl },
227        { kVK_RightControl,  skui::Key::kCtrl },
228        { kVK_Option,        skui::Key::kOption },
229        { kVK_RightOption,   skui::Key::kOption },
230        { kVK_Command,       skui::Key::kSuper },
231        { kVK_RightCommand,  skui::Key::kSuper },
232        { kVK_ANSI_A,        skui::Key::kA },
233        { kVK_ANSI_C,        skui::Key::kC },
234        { kVK_ANSI_V,        skui::Key::kV },
235        { kVK_ANSI_X,        skui::Key::kX },
236        { kVK_ANSI_Y,        skui::Key::kY },
237        { kVK_ANSI_Z,        skui::Key::kZ },
238    };
239
240    for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
241        if (gPair[i].fVK == vk) {
242            return gPair[i].fKey;
243        }
244    }
245
246    return skui::Key::kNONE;
247}
248
249static skui::ModifierKey get_modifiers(const NSEvent* event) {
250    NSUInteger modifierFlags = [event modifierFlags];
251    skui::ModifierKey modifiers = skui::ModifierKey::kNone;
252
253    if (modifierFlags & NSEventModifierFlagCommand) {
254        modifiers |= skui::ModifierKey::kCommand;
255    }
256    if (modifierFlags & NSEventModifierFlagShift) {
257        modifiers |= skui::ModifierKey::kShift;
258    }
259    if (modifierFlags & NSEventModifierFlagControl) {
260        modifiers |= skui::ModifierKey::kControl;
261    }
262    if (modifierFlags & NSEventModifierFlagOption) {
263        modifiers |= skui::ModifierKey::kOption;
264    }
265
266    if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) {
267        modifiers |= skui::ModifierKey::kFirstPress;
268    }
269
270    return modifiers;
271}
272
273@implementation MainView {
274    sk_app::Window_mac* fWindow;
275    // A TrackingArea prevents us from capturing events outside the view
276    NSTrackingArea* fTrackingArea;
277    // We keep track of the state of the modifier keys on each event in order to synthesize
278    // key-up/down events for each modifier.
279    skui::ModifierKey fLastModifiers;
280}
281
282- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow {
283    self = [super init];
284
285    fWindow = initWindow;
286    fTrackingArea = nil;
287
288    [self updateTrackingAreas];
289
290    return self;
291}
292
293- (void)dealloc
294{
295    [fTrackingArea release];
296    [super dealloc];
297}
298
299- (BOOL)isOpaque {
300    return YES;
301}
302
303- (BOOL)canBecomeKeyView {
304    return YES;
305}
306
307- (BOOL)acceptsFirstResponder {
308    return YES;
309}
310
311- (void)updateTrackingAreas {
312    if (fTrackingArea != nil) {
313        [self removeTrackingArea:fTrackingArea];
314        [fTrackingArea release];
315    }
316
317    const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
318                                          NSTrackingActiveInKeyWindow |
319                                          NSTrackingEnabledDuringMouseDrag |
320                                          NSTrackingCursorUpdate |
321                                          NSTrackingInVisibleRect |
322                                          NSTrackingAssumeInside;
323
324    fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
325                                                 options:options
326                                                   owner:self
327                                                userInfo:nil];
328
329    [self addTrackingArea:fTrackingArea];
330    [super updateTrackingAreas];
331}
332
333- (skui::ModifierKey) updateModifierKeys:(NSEvent*) event {
334    using sknonstd::Any;
335
336    skui::ModifierKey modifiers = get_modifiers(event);
337    skui::ModifierKey changed = modifiers ^ fLastModifiers;
338    fLastModifiers = modifiers;
339
340    struct ModMap {
341        skui::ModifierKey modifier;
342        skui::Key         key;
343    };
344
345    // Map each modifier bit to the equivalent skui Key and send key-up/down events.
346    for (const ModMap& cur : {ModMap{skui::ModifierKey::kCommand, skui::Key::kSuper},
347                              ModMap{skui::ModifierKey::kShift,   skui::Key::kShift},
348                              ModMap{skui::ModifierKey::kControl, skui::Key::kCtrl},
349                              ModMap{skui::ModifierKey::kOption,  skui::Key::kOption}}) {
350        if (Any(changed & cur.modifier)) {
351            const skui::InputState state = Any(modifiers & cur.modifier) ? skui::InputState::kDown
352                                                                         : skui::InputState::kUp;
353            (void) fWindow->onKey(cur.key, state, modifiers);
354        }
355    }
356
357    return modifiers;
358}
359
360- (BOOL)performKeyEquivalent:(NSEvent *)event {
361    [self updateModifierKeys:event];
362
363    // By default, unhandled key equivalents send -keyDown events; unfortunately, they do not send
364    // a matching -keyUp. In other words, we can claim that we didn't handle the event and OS X will
365    // turn this event into a -keyDown automatically, but we need to synthesize a matching -keyUp on
366    // a later frame. Since we only read the modifiers and key code from the event, we can reuse
367    // this "key-equivalent" event as a "key up".
368    [self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1];
369    return NO;
370}
371
372- (void)keyDown:(NSEvent *)event {
373    skui::ModifierKey modifiers = [self updateModifierKeys:event];
374
375    skui::Key key = get_key([event keyCode]);
376    if (key != skui::Key::kNONE) {
377        if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) {
378            if (skui::Key::kEscape == key) {
379                [NSApp terminate:fWindow->window()];
380            }
381        }
382    }
383
384    NSString* characters = [event charactersIgnoringModifiers];
385    NSUInteger len = [characters length];
386    if (len > 0) {
387        unichar* charBuffer = new unichar[len+1];
388        [characters getCharacters:charBuffer range:NSMakeRange(0, len)];
389        for (NSUInteger i = 0; i < len; ++i) {
390            (void) fWindow->onChar((SkUnichar) charBuffer[i], modifiers);
391        }
392        delete [] charBuffer;
393    }
394}
395
396- (void)keyUp:(NSEvent *)event {
397    skui::ModifierKey modifiers = [self updateModifierKeys:event];
398
399    skui::Key key = get_key([event keyCode]);
400    if (key != skui::Key::kNONE) {
401        (void) fWindow->onKey(key, skui::InputState::kUp, modifiers);
402    }
403}
404
405-(void)flagsChanged:(NSEvent *)event {
406    [self updateModifierKeys:event];
407}
408
409- (void)mouseDown:(NSEvent *)event {
410    NSView* view = fWindow->window().contentView;
411    CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view);
412
413    skui::ModifierKey modifiers = [self updateModifierKeys:event];
414
415    const NSPoint pos = [event locationInWindow];
416    const NSRect rect = [view frame];
417    fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor,
418                     skui::InputState::kDown, modifiers);
419}
420
421- (void)mouseUp:(NSEvent *)event {
422    NSView* view = fWindow->window().contentView;
423    CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view);
424
425    skui::ModifierKey modifiers = [self updateModifierKeys:event];
426
427    const NSPoint pos = [event locationInWindow];
428    const NSRect rect = [view frame];
429    fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor,
430                     skui::InputState::kUp, modifiers);
431}
432
433- (void)mouseDragged:(NSEvent *)event {
434    [self updateModifierKeys:event];
435    [self mouseMoved:event];
436}
437
438- (void)mouseMoved:(NSEvent *)event {
439    NSView* view = fWindow->window().contentView;
440    CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view);
441
442    skui::ModifierKey modifiers = [self updateModifierKeys:event];
443
444    const NSPoint pos = [event locationInWindow];
445    const NSRect rect = [view frame];
446    fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor,
447                     skui::InputState::kMove, modifiers);
448}
449
450- (void)scrollWheel:(NSEvent *)event {
451    skui::ModifierKey modifiers = [self updateModifierKeys:event];
452
453    // TODO: support hasPreciseScrollingDeltas?
454    fWindow->onMouseWheel([event scrollingDeltaY], modifiers);
455}
456
457- (void)drawRect:(NSRect)rect {
458    fWindow->onPaint();
459}
460
461@end
462