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