• 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#if defined(SK_GRAPHITE)
130        case kGraphiteDawn_BackendType:
131            fWindowContext = MakeGraphiteDawnMetalForMac(info, fRequestedDisplayParams);
132            break;
133#endif
134#endif
135#ifdef SK_VULKAN
136        case kVulkan_BackendType:
137            fWindowContext = MakeVulkanForMac(info, fRequestedDisplayParams);
138            break;
139#endif
140#ifdef SK_METAL
141        case kMetal_BackendType:
142            fWindowContext = MakeMetalForMac(info, fRequestedDisplayParams);
143            break;
144#if defined(SK_GRAPHITE)
145        case kGraphiteMetal_BackendType:
146            fWindowContext = MakeGraphiteMetalForMac(info, fRequestedDisplayParams);
147            break;
148#endif
149#endif
150#ifdef SK_GL
151        case kNativeGL_BackendType:
152            fWindowContext = MakeGLForMac(info, fRequestedDisplayParams);
153            break;
154        case kRaster_BackendType:
155            fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams);
156            break;
157#endif
158        default:
159            SkASSERT_RELEASE(false);
160    }
161    this->onBackendCreated();
162
163    return SkToBool(fWindowContext);
164}
165
166float Window_mac::scaleFactor() const {
167    return sk_app::GetBackingScaleFactor(fWindow.contentView);
168}
169
170void Window_mac::PaintWindows() {
171    gWindowMap.foreach([&](Window_mac* window) {
172        if (window->fIsContentInvalidated) {
173            window->onPaint();
174        }
175    });
176}
177
178}   // namespace sk_app
179
180///////////////////////////////////////////////////////////////////////////////
181
182@implementation WindowDelegate {
183    sk_app::Window_mac* fWindow;
184}
185
186- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow {
187    fWindow = initWindow;
188
189    return self;
190}
191
192- (void)windowDidResize:(NSNotification *)notification {
193    NSView* view = fWindow->window().contentView;
194    CGFloat scale = sk_app::GetBackingScaleFactor(view);
195    fWindow->onResize(view.bounds.size.width * scale, view.bounds.size.height * scale);
196    fWindow->inval();
197}
198
199- (BOOL)windowShouldClose:(NSWindow*)sender {
200    fWindow->closeWindow();
201
202    return FALSE;
203}
204
205@end
206
207///////////////////////////////////////////////////////////////////////////////
208
209static skui::Key get_key(unsigned short vk) {
210    // This will work with an ANSI QWERTY keyboard.
211    // Something more robust would be needed to support alternate keyboards.
212    static const struct {
213        unsigned short fVK;
214        skui::Key      fKey;
215    } gPair[] = {
216        { kVK_Delete,        skui::Key::kBack },
217        { kVK_Return,        skui::Key::kOK },
218        { kVK_UpArrow,       skui::Key::kUp },
219        { kVK_DownArrow,     skui::Key::kDown },
220        { kVK_LeftArrow,     skui::Key::kLeft },
221        { kVK_RightArrow,    skui::Key::kRight },
222        { kVK_Tab,           skui::Key::kTab },
223        { kVK_PageUp,        skui::Key::kPageUp },
224        { kVK_PageDown,      skui::Key::kPageDown },
225        { kVK_Home,          skui::Key::kHome },
226        { kVK_End,           skui::Key::kEnd },
227        { kVK_ForwardDelete, skui::Key::kDelete },
228        { kVK_Escape,        skui::Key::kEscape },
229        { kVK_Shift,         skui::Key::kShift },
230        { kVK_RightShift,    skui::Key::kShift },
231        { kVK_Control,       skui::Key::kCtrl },
232        { kVK_RightControl,  skui::Key::kCtrl },
233        { kVK_Option,        skui::Key::kOption },
234        { kVK_RightOption,   skui::Key::kOption },
235        { kVK_Command,       skui::Key::kSuper },
236        { kVK_RightCommand,  skui::Key::kSuper },
237        { kVK_ANSI_A,        skui::Key::kA },
238        { kVK_ANSI_C,        skui::Key::kC },
239        { kVK_ANSI_V,        skui::Key::kV },
240        { kVK_ANSI_X,        skui::Key::kX },
241        { kVK_ANSI_Y,        skui::Key::kY },
242        { kVK_ANSI_Z,        skui::Key::kZ },
243    };
244
245    for (size_t i = 0; i < std::size(gPair); i++) {
246        if (gPair[i].fVK == vk) {
247            return gPair[i].fKey;
248        }
249    }
250
251    return skui::Key::kNONE;
252}
253
254static skui::ModifierKey get_modifiers(const NSEvent* event) {
255    NSUInteger modifierFlags = [event modifierFlags];
256    skui::ModifierKey modifiers = skui::ModifierKey::kNone;
257
258    if (modifierFlags & NSEventModifierFlagCommand) {
259        modifiers |= skui::ModifierKey::kCommand;
260    }
261    if (modifierFlags & NSEventModifierFlagShift) {
262        modifiers |= skui::ModifierKey::kShift;
263    }
264    if (modifierFlags & NSEventModifierFlagControl) {
265        modifiers |= skui::ModifierKey::kControl;
266    }
267    if (modifierFlags & NSEventModifierFlagOption) {
268        modifiers |= skui::ModifierKey::kOption;
269    }
270
271    if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) {
272        modifiers |= skui::ModifierKey::kFirstPress;
273    }
274
275    return modifiers;
276}
277
278@implementation MainView {
279    sk_app::Window_mac* fWindow;
280    // A TrackingArea prevents us from capturing events outside the view
281    NSTrackingArea* fTrackingArea;
282    // We keep track of the state of the modifier keys on each event in order to synthesize
283    // key-up/down events for each modifier.
284    skui::ModifierKey fLastModifiers;
285}
286
287- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow {
288    self = [super init];
289
290    fWindow = initWindow;
291    fTrackingArea = nil;
292
293    [self updateTrackingAreas];
294
295    return self;
296}
297
298- (void)dealloc
299{
300    [fTrackingArea release];
301    [super dealloc];
302}
303
304- (BOOL)isOpaque {
305    return YES;
306}
307
308- (BOOL)canBecomeKeyView {
309    return YES;
310}
311
312- (BOOL)acceptsFirstResponder {
313    return YES;
314}
315
316- (void)updateTrackingAreas {
317    if (fTrackingArea != nil) {
318        [self removeTrackingArea:fTrackingArea];
319        [fTrackingArea release];
320    }
321
322    const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
323                                          NSTrackingActiveInKeyWindow |
324                                          NSTrackingEnabledDuringMouseDrag |
325                                          NSTrackingCursorUpdate |
326                                          NSTrackingInVisibleRect |
327                                          NSTrackingAssumeInside;
328
329    fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
330                                                 options:options
331                                                   owner:self
332                                                userInfo:nil];
333
334    [self addTrackingArea:fTrackingArea];
335    [super updateTrackingAreas];
336}
337
338- (skui::ModifierKey) updateModifierKeys:(NSEvent*) event {
339    using sknonstd::Any;
340
341    skui::ModifierKey modifiers = get_modifiers(event);
342    skui::ModifierKey changed = modifiers ^ fLastModifiers;
343    fLastModifiers = modifiers;
344
345    struct ModMap {
346        skui::ModifierKey modifier;
347        skui::Key         key;
348    };
349
350    // Map each modifier bit to the equivalent skui Key and send key-up/down events.
351    for (const ModMap& cur : {ModMap{skui::ModifierKey::kCommand, skui::Key::kSuper},
352                              ModMap{skui::ModifierKey::kShift,   skui::Key::kShift},
353                              ModMap{skui::ModifierKey::kControl, skui::Key::kCtrl},
354                              ModMap{skui::ModifierKey::kOption,  skui::Key::kOption}}) {
355        if (Any(changed & cur.modifier)) {
356            const skui::InputState state = Any(modifiers & cur.modifier) ? skui::InputState::kDown
357                                                                         : skui::InputState::kUp;
358            (void) fWindow->onKey(cur.key, state, modifiers);
359        }
360    }
361
362    return modifiers;
363}
364
365- (BOOL)performKeyEquivalent:(NSEvent *)event {
366    [self updateModifierKeys:event];
367
368    // By default, unhandled key equivalents send -keyDown events; unfortunately, they do not send
369    // a matching -keyUp. In other words, we can claim that we didn't handle the event and OS X will
370    // turn this event into a -keyDown automatically, but we need to synthesize a matching -keyUp on
371    // a later frame. Since we only read the modifiers and key code from the event, we can reuse
372    // this "key-equivalent" event as a "key up".
373    [self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1];
374    return NO;
375}
376
377- (void)keyDown:(NSEvent *)event {
378    skui::ModifierKey modifiers = [self updateModifierKeys:event];
379
380    skui::Key key = get_key([event keyCode]);
381    if (key != skui::Key::kNONE) {
382        if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) {
383            if (skui::Key::kEscape == key) {
384                [NSApp terminate:fWindow->window()];
385            }
386        }
387    }
388
389    NSString* characters = [event charactersIgnoringModifiers];
390    NSUInteger len = [characters length];
391    if (len > 0) {
392        unichar* charBuffer = new unichar[len+1];
393        [characters getCharacters:charBuffer range:NSMakeRange(0, len)];
394        for (NSUInteger i = 0; i < len; ++i) {
395            (void) fWindow->onChar((SkUnichar) charBuffer[i], modifiers);
396        }
397        delete [] charBuffer;
398    }
399}
400
401- (void)keyUp:(NSEvent *)event {
402    skui::ModifierKey modifiers = [self updateModifierKeys:event];
403
404    skui::Key key = get_key([event keyCode]);
405    if (key != skui::Key::kNONE) {
406        (void) fWindow->onKey(key, skui::InputState::kUp, modifiers);
407    }
408}
409
410-(void)flagsChanged:(NSEvent *)event {
411    [self updateModifierKeys:event];
412}
413
414- (void)mouseDown:(NSEvent *)event {
415    NSView* view = fWindow->window().contentView;
416    CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view);
417
418    skui::ModifierKey modifiers = [self updateModifierKeys:event];
419
420    const NSPoint pos = [event locationInWindow];
421    const NSRect rect = [view frame];
422    fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor,
423                     skui::InputState::kDown, modifiers);
424}
425
426- (void)mouseUp:(NSEvent *)event {
427    NSView* view = fWindow->window().contentView;
428    CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view);
429
430    skui::ModifierKey modifiers = [self updateModifierKeys:event];
431
432    const NSPoint pos = [event locationInWindow];
433    const NSRect rect = [view frame];
434    fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor,
435                     skui::InputState::kUp, modifiers);
436}
437
438- (void)mouseDragged:(NSEvent *)event {
439    [self updateModifierKeys:event];
440    [self mouseMoved:event];
441}
442
443- (void)mouseMoved:(NSEvent *)event {
444    NSView* view = fWindow->window().contentView;
445    CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view);
446
447    skui::ModifierKey modifiers = [self updateModifierKeys:event];
448
449    const NSPoint pos = [event locationInWindow];
450    const NSRect rect = [view frame];
451    fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor,
452                     skui::InputState::kMove, modifiers);
453}
454
455- (void)scrollWheel:(NSEvent *)event {
456    skui::ModifierKey modifiers = [self updateModifierKeys:event];
457
458    // TODO: support hasPreciseScrollingDeltas?
459    fWindow->onMouseWheel([event scrollingDeltaY], modifiers);
460}
461
462- (void)drawRect:(NSRect)rect {
463    fWindow->onPaint();
464}
465
466@end
467