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