1/*
2  Copyright 2012-2020 David Robillard <d@drobilla.net>
3  Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
4
5  Permission to use, copy, modify, and/or distribute this software for any
6  purpose with or without fee is hereby granted, provided that the above
7  copyright notice and this permission notice appear in all copies.
8
9  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17
18#define GL_SILENCE_DEPRECATION 1
19
20#include "mac.h"
21
22#include "implementation.h"
23
24#include "pugl/pugl.h"
25
26#import <Cocoa/Cocoa.h>
27
28#include <mach/mach_time.h>
29
30#include <stdlib.h>
31
32#ifndef __MAC_10_10
33typedef NSUInteger NSEventModifierFlags;
34#endif
35
36#ifndef __MAC_10_12
37typedef NSUInteger NSWindowStyleMask;
38#endif
39
40static NSRect
41rectToScreen(NSScreen* screen, NSRect rect)
42{
43  const double screenHeight = [screen frame].size.height;
44
45  rect.origin.y = screenHeight - rect.origin.y - rect.size.height;
46  return rect;
47}
48
49static NSScreen*
50viewScreen(PuglView* view)
51{
52  return view->impl->window ? [view->impl->window screen]
53                            : [NSScreen mainScreen];
54}
55
56static NSRect
57nsRectToPoints(PuglView* view, const NSRect rect)
58{
59  const double scaleFactor = [viewScreen(view) backingScaleFactor];
60
61  return NSMakeRect(rect.origin.x / scaleFactor,
62                    rect.origin.y / scaleFactor,
63                    rect.size.width / scaleFactor,
64                    rect.size.height / scaleFactor);
65}
66
67static NSRect
68nsRectFromPoints(PuglView* view, const NSRect rect)
69{
70  const double scaleFactor = [viewScreen(view) backingScaleFactor];
71
72  return NSMakeRect(rect.origin.x * scaleFactor,
73                    rect.origin.y * scaleFactor,
74                    rect.size.width * scaleFactor,
75                    rect.size.height * scaleFactor);
76}
77
78static NSPoint
79nsPointFromPoints(PuglView* view, const NSPoint point)
80{
81  const double scaleFactor = [viewScreen(view) backingScaleFactor];
82
83  return NSMakePoint(point.x * scaleFactor, point.y * scaleFactor);
84}
85
86static NSRect
87rectToNsRect(const PuglRect rect)
88{
89  return NSMakeRect(rect.x, rect.y, rect.width, rect.height);
90}
91
92static NSSize
93sizePoints(PuglView* view, const double width, const double height)
94{
95  const double scaleFactor = [viewScreen(view) backingScaleFactor];
96
97  return NSMakeSize(width / scaleFactor, height / scaleFactor);
98}
99
100static void
101updateViewRect(PuglView* view)
102{
103  NSWindow* const window = view->impl->window;
104  if (window) {
105    const NSRect screenFramePt = [[NSScreen mainScreen] frame];
106    const NSRect screenFramePx = nsRectFromPoints(view, screenFramePt);
107    const NSRect framePt       = [window frame];
108    const NSRect contentPt     = [window contentRectForFrameRect:framePt];
109    const NSRect contentPx     = nsRectFromPoints(view, contentPt);
110    const double screenHeight  = screenFramePx.size.height;
111
112    view->frame.x = contentPx.origin.x;
113    view->frame.y = screenHeight - contentPx.origin.y - contentPx.size.height;
114    view->frame.width  = contentPx.size.width;
115    view->frame.height = contentPx.size.height;
116  }
117}
118
119@implementation PuglWindow {
120@public
121  PuglView* puglview;
122}
123
124- (id)initWithContentRect:(NSRect)contentRect
125                styleMask:(NSWindowStyleMask)aStyle
126                  backing:(NSBackingStoreType)bufferingType
127                    defer:(BOOL)flag
128{
129  (void)flag;
130
131  NSWindow* result = [super initWithContentRect:contentRect
132                                      styleMask:aStyle
133                                        backing:bufferingType
134                                          defer:NO];
135
136  [result setAcceptsMouseMovedEvents:YES];
137  return (PuglWindow*)result;
138}
139
140- (void)setPuglview:(PuglView*)view
141{
142  puglview = view;
143
144  [self setContentSize:sizePoints(view, view->frame.width, view->frame.height)];
145}
146
147- (BOOL)canBecomeKeyWindow
148{
149  return YES;
150}
151
152- (BOOL)canBecomeMainWindow
153{
154  return YES;
155}
156
157- (void)setIsVisible:(BOOL)flag
158{
159  if (flag && !puglview->visible) {
160    const PuglEventConfigure ev = {
161      PUGL_CONFIGURE,
162      0,
163      puglview->frame.x,
164      puglview->frame.y,
165      puglview->frame.width,
166      puglview->frame.height,
167    };
168
169    puglDispatchEvent(puglview, (const PuglEvent*)&ev);
170    puglDispatchSimpleEvent(puglview, PUGL_MAP);
171  } else if (!flag && puglview->visible) {
172    puglDispatchSimpleEvent(puglview, PUGL_UNMAP);
173  }
174
175  puglview->visible = flag;
176
177  [super setIsVisible:flag];
178}
179
180@end
181
182@implementation PuglWrapperView {
183@public
184  PuglView*                  puglview;
185  NSTrackingArea*            trackingArea;
186  NSMutableAttributedString* markedText;
187  NSMutableDictionary*       userTimers;
188  bool                       reshaped;
189}
190
191- (void)dispatchExpose:(NSRect)rect
192{
193  const double scaleFactor = [[NSScreen mainScreen] backingScaleFactor];
194
195  if (reshaped) {
196    updateViewRect(puglview);
197
198    const PuglEventConfigure ev = {
199      PUGL_CONFIGURE,
200      0,
201      puglview->frame.x,
202      puglview->frame.y,
203      puglview->frame.width,
204      puglview->frame.height,
205    };
206
207    puglDispatchEvent(puglview, (const PuglEvent*)&ev);
208    reshaped = false;
209  }
210
211  if (![[puglview->impl->drawView window] isVisible]) {
212    return;
213  }
214
215  const PuglEventExpose ev = {
216    PUGL_EXPOSE,
217    0,
218    rect.origin.x * scaleFactor,
219    rect.origin.y * scaleFactor,
220    rect.size.width * scaleFactor,
221    rect.size.height * scaleFactor,
222  };
223
224  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
225}
226
227- (NSSize)intrinsicContentSize
228{
229  if (puglview->defaultWidth || puglview->defaultHeight) {
230    return sizePoints(
231      puglview, puglview->defaultWidth, puglview->defaultHeight);
232  }
233
234  return NSMakeSize(NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric);
235}
236
237- (BOOL)isFlipped
238{
239  return YES;
240}
241
242- (BOOL)acceptsFirstResponder
243{
244  return YES;
245}
246
247- (void)setReshaped
248{
249  reshaped = true;
250}
251
252static uint32_t
253getModifiers(const NSEvent* const ev)
254{
255  const NSEventModifierFlags modifierFlags = [ev modifierFlags];
256
257  return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) |
258          ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) |
259          ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) |
260          ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0));
261}
262
263static PuglKey
264keySymToSpecial(const NSEvent* const ev)
265{
266  NSString* chars = [ev charactersIgnoringModifiers];
267  if ([chars length] == 1) {
268    switch ([chars characterAtIndex:0]) {
269    case NSF1FunctionKey:
270      return PUGL_KEY_F1;
271    case NSF2FunctionKey:
272      return PUGL_KEY_F2;
273    case NSF3FunctionKey:
274      return PUGL_KEY_F3;
275    case NSF4FunctionKey:
276      return PUGL_KEY_F4;
277    case NSF5FunctionKey:
278      return PUGL_KEY_F5;
279    case NSF6FunctionKey:
280      return PUGL_KEY_F6;
281    case NSF7FunctionKey:
282      return PUGL_KEY_F7;
283    case NSF8FunctionKey:
284      return PUGL_KEY_F8;
285    case NSF9FunctionKey:
286      return PUGL_KEY_F9;
287    case NSF10FunctionKey:
288      return PUGL_KEY_F10;
289    case NSF11FunctionKey:
290      return PUGL_KEY_F11;
291    case NSF12FunctionKey:
292      return PUGL_KEY_F12;
293    case NSDeleteCharacter:
294      return PUGL_KEY_BACKSPACE;
295    case NSDeleteFunctionKey:
296      return PUGL_KEY_DELETE;
297    case NSLeftArrowFunctionKey:
298      return PUGL_KEY_LEFT;
299    case NSUpArrowFunctionKey:
300      return PUGL_KEY_UP;
301    case NSRightArrowFunctionKey:
302      return PUGL_KEY_RIGHT;
303    case NSDownArrowFunctionKey:
304      return PUGL_KEY_DOWN;
305    case NSPageUpFunctionKey:
306      return PUGL_KEY_PAGE_UP;
307    case NSPageDownFunctionKey:
308      return PUGL_KEY_PAGE_DOWN;
309    case NSHomeFunctionKey:
310      return PUGL_KEY_HOME;
311    case NSEndFunctionKey:
312      return PUGL_KEY_END;
313    case NSInsertFunctionKey:
314      return PUGL_KEY_INSERT;
315    case NSMenuFunctionKey:
316      return PUGL_KEY_MENU;
317    case NSScrollLockFunctionKey:
318      return PUGL_KEY_SCROLL_LOCK;
319    case NSClearLineFunctionKey:
320      return PUGL_KEY_NUM_LOCK;
321    case NSPrintScreenFunctionKey:
322      return PUGL_KEY_PRINT_SCREEN;
323    case NSPauseFunctionKey:
324      return PUGL_KEY_PAUSE;
325    }
326    // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged]
327  }
328  return (PuglKey)0;
329}
330
331- (void)updateTrackingAreas
332{
333  if (trackingArea != nil) {
334    [self removeTrackingArea:trackingArea];
335    [trackingArea release];
336  }
337
338  const int opts = (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
339                    NSTrackingActiveAlways);
340  trackingArea   = [[NSTrackingArea alloc] initWithRect:[self bounds]
341                                              options:opts
342                                                owner:self
343                                             userInfo:nil];
344  [self addTrackingArea:trackingArea];
345  [super updateTrackingAreas];
346}
347
348- (NSPoint)eventLocation:(NSEvent*)event
349{
350  return nsPointFromPoints(
351    puglview, [self convertPoint:[event locationInWindow] fromView:nil]);
352}
353
354static void
355handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
356{
357  const NSPoint           wloc = [view eventLocation:event];
358  const NSPoint           rloc = [NSEvent mouseLocation];
359  const PuglEventCrossing ev   = {
360    type,
361    0,
362    [event timestamp],
363    wloc.x,
364    wloc.y,
365    rloc.x,
366    [[NSScreen mainScreen] frame].size.height - rloc.y,
367    getModifiers(event),
368    PUGL_CROSSING_NORMAL,
369  };
370
371  puglDispatchEvent(view->puglview, (const PuglEvent*)&ev);
372}
373
374- (void)mouseEntered:(NSEvent*)event
375{
376  handleCrossing(self, event, PUGL_POINTER_IN);
377  [puglview->impl->cursor set];
378  puglview->impl->mouseTracked = true;
379}
380
381- (void)mouseExited:(NSEvent*)event
382{
383  [[NSCursor arrowCursor] set];
384  handleCrossing(self, event, PUGL_POINTER_OUT);
385  puglview->impl->mouseTracked = false;
386}
387
388- (void)cursorUpdate:(NSEvent*)event
389{
390  (void)event;
391  [puglview->impl->cursor set];
392}
393
394- (void)mouseMoved:(NSEvent*)event
395{
396  const NSPoint         wloc = [self eventLocation:event];
397  const NSPoint         rloc = [NSEvent mouseLocation];
398  const PuglEventMotion ev   = {
399    PUGL_MOTION,
400    0,
401    [event timestamp],
402    wloc.x,
403    wloc.y,
404    rloc.x,
405    [[NSScreen mainScreen] frame].size.height - rloc.y,
406    getModifiers(event),
407  };
408
409  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
410}
411
412- (void)mouseDragged:(NSEvent*)event
413{
414  [self mouseMoved:event];
415}
416
417- (void)rightMouseDragged:(NSEvent*)event
418{
419  [self mouseMoved:event];
420}
421
422- (void)otherMouseDragged:(NSEvent*)event
423{
424  [self mouseMoved:event];
425}
426
427- (void)mouseDown:(NSEvent*)event
428{
429  const NSPoint         wloc = [self eventLocation:event];
430  const NSPoint         rloc = [NSEvent mouseLocation];
431  const PuglEventButton ev   = {
432    PUGL_BUTTON_PRESS,
433    0,
434    [event timestamp],
435    wloc.x,
436    wloc.y,
437    rloc.x,
438    [[NSScreen mainScreen] frame].size.height - rloc.y,
439    getModifiers(event),
440    (uint32_t)[event buttonNumber] + 1,
441  };
442
443  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
444}
445
446- (void)mouseUp:(NSEvent*)event
447{
448  const NSPoint         wloc = [self eventLocation:event];
449  const NSPoint         rloc = [NSEvent mouseLocation];
450  const PuglEventButton ev   = {
451    PUGL_BUTTON_RELEASE,
452    0,
453    [event timestamp],
454    wloc.x,
455    wloc.y,
456    rloc.x,
457    [[NSScreen mainScreen] frame].size.height - rloc.y,
458    getModifiers(event),
459    (uint32_t)[event buttonNumber] + 1,
460  };
461
462  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
463}
464
465- (void)rightMouseDown:(NSEvent*)event
466{
467  [self mouseDown:event];
468}
469
470- (void)rightMouseUp:(NSEvent*)event
471{
472  [self mouseUp:event];
473}
474
475- (void)otherMouseDown:(NSEvent*)event
476{
477  [self mouseDown:event];
478}
479
480- (void)otherMouseUp:(NSEvent*)event
481{
482  [self mouseUp:event];
483}
484
485- (void)scrollWheel:(NSEvent*)event
486{
487  const NSPoint             wloc = [self eventLocation:event];
488  const NSPoint             rloc = [NSEvent mouseLocation];
489  const double              dx   = [event scrollingDeltaX];
490  const double              dy   = [event scrollingDeltaY];
491  const PuglScrollDirection dir =
492    ((dx == 0.0 && dy > 0.0)
493       ? PUGL_SCROLL_UP
494       : ((dx == 0.0 && dy < 0.0)
495            ? PUGL_SCROLL_DOWN
496            : ((dy == 0.0 && dx > 0.0)
497                 ? PUGL_SCROLL_RIGHT
498                 : ((dy == 0.0 && dx < 0.0) ? PUGL_SCROLL_LEFT
499                                            : PUGL_SCROLL_SMOOTH))));
500
501  const PuglEventScroll ev = {
502    PUGL_SCROLL,
503    0,
504    [event timestamp],
505    wloc.x,
506    wloc.y,
507    rloc.x,
508    [[NSScreen mainScreen] frame].size.height - rloc.y,
509    getModifiers(event),
510    [event hasPreciseScrollingDeltas] ? PUGL_SCROLL_SMOOTH : dir,
511    dx,
512    dy,
513  };
514
515  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
516}
517
518- (void)keyDown:(NSEvent*)event
519{
520  if (puglview->hints[PUGL_IGNORE_KEY_REPEAT] && [event isARepeat]) {
521    return;
522  }
523
524  const NSPoint   wloc  = [self eventLocation:event];
525  const NSPoint   rloc  = [NSEvent mouseLocation];
526  const PuglKey   spec  = keySymToSpecial(event);
527  const NSString* chars = [event charactersIgnoringModifiers];
528  const char*     str   = [[chars lowercaseString] UTF8String];
529  const uint32_t  code  = (spec ? spec : puglDecodeUTF8((const uint8_t*)str));
530
531  const PuglEventKey ev = {
532    PUGL_KEY_PRESS,
533    0,
534    [event timestamp],
535    wloc.x,
536    wloc.y,
537    rloc.x,
538    [[NSScreen mainScreen] frame].size.height - rloc.y,
539    getModifiers(event),
540    [event keyCode],
541    (code != 0xFFFD) ? code : 0,
542  };
543
544  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
545
546  if (!spec) {
547    [self interpretKeyEvents:@[event]];
548  }
549}
550
551- (void)keyUp:(NSEvent*)event
552{
553  const NSPoint   wloc  = [self eventLocation:event];
554  const NSPoint   rloc  = [NSEvent mouseLocation];
555  const PuglKey   spec  = keySymToSpecial(event);
556  const NSString* chars = [event charactersIgnoringModifiers];
557  const char*     str   = [[chars lowercaseString] UTF8String];
558  const uint32_t  code  = (spec ? spec : puglDecodeUTF8((const uint8_t*)str));
559
560  const PuglEventKey ev = {
561    PUGL_KEY_RELEASE,
562    0,
563    [event timestamp],
564    wloc.x,
565    wloc.y,
566    rloc.x,
567    [[NSScreen mainScreen] frame].size.height - rloc.y,
568    getModifiers(event),
569    [event keyCode],
570    (code != 0xFFFD) ? code : 0,
571  };
572
573  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
574}
575
576- (BOOL)hasMarkedText
577{
578  return [markedText length] > 0;
579}
580
581- (NSRange)markedRange
582{
583  return (([markedText length] > 0) ? NSMakeRange(0, [markedText length] - 1)
584                                    : NSMakeRange(NSNotFound, 0));
585}
586
587- (NSRange)selectedRange
588{
589  return NSMakeRange(NSNotFound, 0);
590}
591
592- (void)setMarkedText:(id)string
593        selectedRange:(NSRange)selected
594     replacementRange:(NSRange)replacement
595{
596  (void)selected;
597  (void)replacement;
598  [markedText release];
599  markedText =
600    ([(NSObject*)string isKindOfClass:[NSAttributedString class]]
601       ? [[NSMutableAttributedString alloc] initWithAttributedString:string]
602       : [[NSMutableAttributedString alloc] initWithString:string]);
603}
604
605- (void)unmarkText
606{
607  [[markedText mutableString] setString:@""];
608}
609
610- (NSArray*)validAttributesForMarkedText
611{
612  return @[];
613}
614
615- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
616                                               actualRange:
617                                                 (NSRangePointer)actual
618{
619  (void)range;
620  (void)actual;
621  return nil;
622}
623
624- (NSUInteger)characterIndexForPoint:(NSPoint)point
625{
626  (void)point;
627  return 0;
628}
629
630- (NSRect)firstRectForCharacterRange:(NSRange)range
631                         actualRange:(NSRangePointer)actual
632{
633  (void)range;
634  (void)actual;
635
636  const NSRect frame = [self bounds];
637  return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
638}
639
640- (void)doCommandBySelector:(SEL)selector
641{
642  (void)selector;
643}
644
645- (void)insertText:(id)string replacementRange:(NSRange)replacement
646{
647  (void)replacement;
648
649  NSEvent* const  event = [NSApp currentEvent];
650  NSString* const characters =
651    ([(NSObject*)string isKindOfClass:[NSAttributedString class]]
652       ? [(NSAttributedString*)string string]
653       : (NSString*)string);
654
655  const NSPoint wloc = [self eventLocation:event];
656  const NSPoint rloc = [NSEvent mouseLocation];
657  for (size_t i = 0; i < [characters length]; ++i) {
658    const uint32_t code    = [characters characterAtIndex:i];
659    char           utf8[8] = {0};
660    NSUInteger     len     = 0;
661
662    [characters getBytes:utf8
663               maxLength:sizeof(utf8)
664              usedLength:&len
665                encoding:NSUTF8StringEncoding
666                 options:0
667                   range:NSMakeRange(i, i + 1)
668          remainingRange:nil];
669
670    PuglEventText ev = {
671      PUGL_TEXT,
672      0,
673      [event timestamp],
674      wloc.x,
675      wloc.y,
676      rloc.x,
677      [[NSScreen mainScreen] frame].size.height - rloc.y,
678      getModifiers(event),
679      [event keyCode],
680      code,
681      { 0, 0, 0, 0, 0, 0, 0, 0 },
682    };
683
684    memcpy(ev.string, utf8, len);
685    puglDispatchEvent(puglview, (const PuglEvent*)&ev);
686  }
687}
688
689- (void)flagsChanged:(NSEvent*)event
690{
691  const uint32_t mods    = getModifiers(event);
692  PuglEventType  type    = PUGL_NOTHING;
693  PuglKey        special = (PuglKey)0;
694
695  if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) {
696    type    = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
697    special = PUGL_KEY_SHIFT;
698  } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) {
699    type    = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
700    special = PUGL_KEY_CTRL;
701  } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) {
702    type    = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
703    special = PUGL_KEY_ALT;
704  } else if ((mods & PUGL_MOD_SUPER) !=
705             (puglview->impl->mods & PUGL_MOD_SUPER)) {
706    type    = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
707    special = PUGL_KEY_SUPER;
708  }
709
710  if (special != 0) {
711    const NSPoint wloc = [self eventLocation:event];
712    const NSPoint rloc = [NSEvent mouseLocation];
713    PuglEventKey  ev   = {type,
714                       0,
715                       [event timestamp],
716                       wloc.x,
717                       wloc.y,
718                       rloc.x,
719                       [[NSScreen mainScreen] frame].size.height - rloc.y,
720                       mods,
721                       [event keyCode],
722                       special};
723    puglDispatchEvent(puglview, (const PuglEvent*)&ev);
724  }
725
726  puglview->impl->mods = mods;
727}
728
729- (BOOL)preservesContentInLiveResize
730{
731  return NO;
732}
733
734- (void)viewWillStartLiveResize
735{
736  puglDispatchSimpleEvent(puglview, PUGL_LOOP_ENTER);
737}
738
739- (void)viewWillDraw
740{
741  puglDispatchSimpleEvent(puglview, PUGL_UPDATE);
742  [super viewWillDraw];
743}
744
745- (void)resizeTick
746{
747  puglPostRedisplay(puglview);
748}
749
750- (void)timerTick:(NSTimer*)userTimer
751{
752  const NSNumber*      userInfo = userTimer.userInfo;
753  const PuglEventTimer ev       = {PUGL_TIMER, 0, userInfo.unsignedLongValue};
754
755  puglDispatchEvent(puglview, (const PuglEvent*)&ev);
756}
757
758- (void)viewDidEndLiveResize
759{
760  puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE);
761}
762
763@end
764
765@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
766
767- (instancetype)initWithPuglWindow:(PuglWindow*)window;
768
769@end
770
771@implementation PuglWindowDelegate {
772  PuglWindow* window;
773}
774
775- (instancetype)initWithPuglWindow:(PuglWindow*)puglWindow
776{
777  if ((self = [super init])) {
778    window = puglWindow;
779  }
780
781  return self;
782}
783
784- (BOOL)windowShouldClose:(id)sender
785{
786  (void)sender;
787
788  puglDispatchSimpleEvent(window->puglview, PUGL_CLOSE);
789  return YES;
790}
791
792- (void)windowDidMove:(NSNotification*)notification
793{
794  (void)notification;
795
796  updateViewRect(window->puglview);
797}
798
799- (void)windowDidBecomeKey:(NSNotification*)notification
800{
801  (void)notification;
802
803  PuglEvent ev  = {{PUGL_FOCUS_IN, 0}};
804  ev.focus.mode = PUGL_CROSSING_NORMAL;
805  puglDispatchEvent(window->puglview, &ev);
806}
807
808- (void)windowDidResignKey:(NSNotification*)notification
809{
810  (void)notification;
811
812  PuglEvent ev  = {{PUGL_FOCUS_OUT, 0}};
813  ev.focus.mode = PUGL_CROSSING_NORMAL;
814  puglDispatchEvent(window->puglview, &ev);
815}
816
817@end
818
819PuglWorldInternals*
820puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags))
821{
822  PuglWorldInternals* impl =
823    (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
824
825  impl->app = [NSApplication sharedApplication];
826
827  if (type == PUGL_PROGRAM) {
828    impl->autoreleasePool = [NSAutoreleasePool new];
829  }
830
831  return impl;
832}
833
834void
835puglFreeWorldInternals(PuglWorld* world)
836{
837  if (world->impl->autoreleasePool) {
838    [world->impl->autoreleasePool drain];
839  }
840
841  free(world->impl);
842}
843
844void*
845puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world))
846{
847  return NULL;
848}
849
850PuglInternals*
851puglInitViewInternals(void)
852{
853  PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
854
855  impl->cursor = [NSCursor arrowCursor];
856
857  return impl;
858}
859
860static NSLayoutConstraint*
861puglConstraint(id item, NSLayoutAttribute attribute, float constant)
862{
863  return
864    [NSLayoutConstraint constraintWithItem:item
865                                 attribute:attribute
866                                 relatedBy:NSLayoutRelationGreaterThanOrEqual
867                                    toItem:nil
868                                 attribute:NSLayoutAttributeNotAnAttribute
869                                multiplier:1.0
870                                  constant:(CGFloat)constant];
871}
872
873PuglStatus
874puglRealize(PuglView* view)
875{
876  PuglInternals* impl = view->impl;
877  if (impl->wrapperView) {
878    return PUGL_FAILURE;
879  }
880
881  const NSScreen* const screen      = [NSScreen mainScreen];
882  const double          scaleFactor = [screen backingScaleFactor];
883
884  // Getting depth from the display mode seems tedious, just set usual values
885  if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) {
886    view->hints[PUGL_RED_BITS] = 8;
887  }
888  if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) {
889    view->hints[PUGL_BLUE_BITS] = 8;
890  }
891  if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) {
892    view->hints[PUGL_GREEN_BITS] = 8;
893  }
894  if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) {
895    view->hints[PUGL_ALPHA_BITS] = 8;
896  }
897
898  CGDirectDisplayID displayId = CGMainDisplayID();
899  CGDisplayModeRef  mode      = CGDisplayCopyDisplayMode(displayId);
900
901  // Try to get refresh rate from mode (usually fails)
902  view->hints[PUGL_REFRESH_RATE] = (int)CGDisplayModeGetRefreshRate(mode);
903
904  CGDisplayModeRelease(mode);
905  if (view->hints[PUGL_REFRESH_RATE] == 0) {
906    // Get refresh rate from a display link
907    // TODO: Keep and actually use the display link for something?
908    CVDisplayLinkRef link;
909    CVDisplayLinkCreateWithCGDisplay(displayId, &link);
910
911    const CVTime p = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
912    const double r = p.timeScale / (double)p.timeValue;
913    view->hints[PUGL_REFRESH_RATE] = (int)lrint(r);
914
915    CVDisplayLinkRelease(link);
916  }
917
918  if (view->frame.width == 0.0 && view->frame.height == 0.0) {
919    if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
920      return PUGL_BAD_CONFIGURATION;
921    }
922
923    const double screenWidthPx  = [screen frame].size.width * scaleFactor;
924    const double screenHeightPx = [screen frame].size.height * scaleFactor;
925
926    view->frame.width  = view->defaultWidth;
927    view->frame.height = view->defaultHeight;
928    view->frame.x      = screenWidthPx / 2.0 - view->frame.width / 2.0;
929    view->frame.y      = screenHeightPx / 2.0 - view->frame.height / 2.0;
930  }
931
932  const NSRect framePx = rectToNsRect(view->frame);
933  const NSRect framePt = NSMakeRect(framePx.origin.x / scaleFactor,
934                                    framePx.origin.y / scaleFactor,
935                                    framePx.size.width / scaleFactor,
936                                    framePx.size.height / scaleFactor);
937
938  // Create wrapper view to handle input
939  impl->wrapperView             = [PuglWrapperView alloc];
940  impl->wrapperView->puglview   = view;
941  impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init];
942  impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init];
943  [impl->wrapperView setAutoresizesSubviews:YES];
944  [impl->wrapperView initWithFrame:framePt];
945  [impl->wrapperView addConstraint:puglConstraint(impl->wrapperView,
946                                                  NSLayoutAttributeWidth,
947                                                  view->minWidth)];
948  [impl->wrapperView addConstraint:puglConstraint(impl->wrapperView,
949                                                  NSLayoutAttributeHeight,
950                                                  view->minHeight)];
951
952  // Create draw view to be rendered to
953  PuglStatus st = PUGL_SUCCESS;
954  if ((st = view->backend->configure(view)) ||
955      (st = view->backend->create(view))) {
956    return st;
957  }
958
959  // Add draw view to wrapper view
960  [impl->wrapperView addSubview:impl->drawView];
961  [impl->wrapperView setHidden:NO];
962  [impl->drawView setHidden:NO];
963
964  if (view->parent) {
965    NSView* pview = (NSView*)view->parent;
966    [pview addSubview:impl->wrapperView];
967    [impl->drawView setHidden:NO];
968    [[impl->drawView window] makeFirstResponder:impl->wrapperView];
969  } else {
970    unsigned style =
971      (NSClosableWindowMask | NSTitledWindowMask | NSMiniaturizableWindowMask);
972    if (view->hints[PUGL_RESIZABLE]) {
973      style |= NSResizableWindowMask;
974    }
975
976    PuglWindow* window = [[[PuglWindow alloc]
977      initWithContentRect:rectToScreen([NSScreen mainScreen], framePt)
978                styleMask:style
979                  backing:NSBackingStoreBuffered
980                    defer:NO] retain];
981    [window setPuglview:view];
982
983    if (view->title) {
984      NSString* titleString =
985        [[NSString alloc] initWithBytes:view->title
986                                 length:strlen(view->title)
987                               encoding:NSUTF8StringEncoding];
988
989      [window setTitle:titleString];
990    }
991
992    if (view->minWidth || view->minHeight) {
993      [window
994        setContentMinSize:sizePoints(view, view->minWidth, view->minHeight)];
995    }
996    impl->window = window;
997
998    ((NSWindow*)window).delegate =
999      [[PuglWindowDelegate alloc] initWithPuglWindow:window];
1000
1001    if (view->minAspectX && view->minAspectY) {
1002      [window setContentAspectRatio:sizePoints(view,
1003                                               view->minAspectX,
1004                                               view->minAspectY)];
1005    }
1006
1007    puglSetFrame(view, view->frame);
1008
1009    [window setContentView:impl->wrapperView];
1010    [view->world->impl->app activateIgnoringOtherApps:YES];
1011    [window makeFirstResponder:impl->wrapperView];
1012    [window makeKeyAndOrderFront:window];
1013    [impl->window setIsVisible:NO];
1014  }
1015
1016  [impl->wrapperView updateTrackingAreas];
1017
1018  puglDispatchSimpleEvent(view, PUGL_CREATE);
1019
1020  return PUGL_SUCCESS;
1021}
1022
1023PuglStatus
1024puglShow(PuglView* view)
1025{
1026  if (!view->impl->wrapperView) {
1027    const PuglStatus st = puglRealize(view);
1028    if (st) {
1029      return st;
1030    }
1031  }
1032
1033  if (![view->impl->window isVisible]) {
1034    [view->impl->window setIsVisible:YES];
1035    [view->impl->drawView setNeedsDisplay:YES];
1036    updateViewRect(view);
1037  }
1038
1039  return PUGL_SUCCESS;
1040}
1041
1042PuglStatus
1043puglHide(PuglView* view)
1044{
1045  [view->impl->window setIsVisible:NO];
1046  return PUGL_SUCCESS;
1047}
1048
1049void
1050puglFreeViewInternals(PuglView* view)
1051{
1052  if (view) {
1053    if (view->backend) {
1054      view->backend->destroy(view);
1055    }
1056
1057    if (view->impl) {
1058      [view->impl->wrapperView removeFromSuperview];
1059      view->impl->wrapperView->puglview = NULL;
1060      if (view->impl->window) {
1061        [view->impl->window close];
1062      }
1063      [view->impl->wrapperView release];
1064      if (view->impl->window) {
1065        [view->impl->window release];
1066      }
1067      free(view->impl);
1068    }
1069  }
1070}
1071
1072PuglStatus
1073puglGrabFocus(PuglView* view)
1074{
1075  NSWindow* window = [view->impl->wrapperView window];
1076
1077  [window makeKeyWindow];
1078  [window makeFirstResponder:view->impl->wrapperView];
1079  return PUGL_SUCCESS;
1080}
1081
1082bool
1083puglHasFocus(const PuglView* view)
1084{
1085  PuglInternals* const impl = view->impl;
1086
1087  return ([[impl->wrapperView window] isKeyWindow] &&
1088          [[impl->wrapperView window] firstResponder] == impl->wrapperView);
1089}
1090
1091PuglStatus
1092puglRequestAttention(PuglView* view)
1093{
1094  if (![view->impl->window isKeyWindow]) {
1095    [view->world->impl->app requestUserAttention:NSInformationalRequest];
1096  }
1097
1098  return PUGL_SUCCESS;
1099}
1100
1101PuglStatus
1102puglStartTimer(PuglView* view, uintptr_t id, double timeout)
1103{
1104  puglStopTimer(view, id);
1105
1106  NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id];
1107
1108  NSTimer* timer = [NSTimer timerWithTimeInterval:timeout
1109                                           target:view->impl->wrapperView
1110                                         selector:@selector(timerTick:)
1111                                         userInfo:idNumber
1112                                          repeats:YES];
1113
1114  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
1115
1116  view->impl->wrapperView->userTimers[idNumber] = timer;
1117
1118  return PUGL_SUCCESS;
1119}
1120
1121PuglStatus
1122puglStopTimer(PuglView* view, uintptr_t id)
1123{
1124  NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id];
1125  NSTimer*  timer    = view->impl->wrapperView->userTimers[idNumber];
1126
1127  if (timer) {
1128    [view->impl->wrapperView->userTimers removeObjectForKey:timer];
1129    [timer invalidate];
1130    return PUGL_SUCCESS;
1131  }
1132
1133  return PUGL_UNKNOWN_ERROR;
1134}
1135
1136PuglStatus
1137puglSendEvent(PuglView* view, const PuglEvent* event)
1138{
1139  if (event->type == PUGL_CLIENT) {
1140    PuglWrapperView* wrapper = view->impl->wrapperView;
1141    const NSWindow*  window  = [wrapper window];
1142    const NSRect     rect    = [wrapper frame];
1143    const NSPoint    center  = {NSMidX(rect), NSMidY(rect)};
1144
1145    NSEvent* nsevent =
1146      [NSEvent otherEventWithType:NSApplicationDefined
1147                         location:center
1148                    modifierFlags:0
1149                        timestamp:[[NSProcessInfo processInfo] systemUptime]
1150                     windowNumber:window.windowNumber
1151                          context:nil
1152                          subtype:PUGL_CLIENT
1153                            data1:(NSInteger)event->client.data1
1154                            data2:(NSInteger)event->client.data2];
1155
1156    [view->world->impl->app postEvent:nsevent atStart:false];
1157    return PUGL_SUCCESS;
1158  }
1159
1160  return PUGL_UNSUPPORTED_TYPE;
1161}
1162
1163#ifndef PUGL_DISABLE_DEPRECATED
1164PuglStatus
1165puglWaitForEvent(PuglView* view)
1166{
1167  return puglPollEvents(view->world, -1.0);
1168}
1169#endif
1170
1171static void
1172dispatchClientEvent(PuglWorld* world, NSEvent* ev)
1173{
1174  NSWindow* win = [ev window];
1175  NSPoint   loc = [ev locationInWindow];
1176  for (size_t i = 0; i < world->numViews; ++i) {
1177    PuglView*        view    = world->views[i];
1178    PuglWrapperView* wrapper = view->impl->wrapperView;
1179    if ([wrapper window] == win && NSPointInRect(loc, [wrapper frame])) {
1180      const PuglEventClient event = {
1181        PUGL_CLIENT, 0, (uintptr_t)[ev data1], (uintptr_t)[ev data2]};
1182
1183      puglDispatchEvent(view, (const PuglEvent*)&event);
1184    }
1185  }
1186}
1187
1188PuglStatus
1189puglUpdate(PuglWorld* world, const double timeout)
1190{
1191  NSDate* date =
1192    ((timeout < 0) ? [NSDate distantFuture]
1193                   : [NSDate dateWithTimeIntervalSinceNow:timeout]);
1194
1195  for (NSEvent* ev = NULL;
1196       (ev = [world->impl->app nextEventMatchingMask:NSAnyEventMask
1197                                           untilDate:date
1198                                              inMode:NSDefaultRunLoopMode
1199                                             dequeue:YES]);) {
1200    if ([ev type] == NSApplicationDefined && [ev subtype] == PUGL_CLIENT) {
1201      dispatchClientEvent(world, ev);
1202    }
1203
1204    [world->impl->app sendEvent:ev];
1205
1206    if (timeout < 0) {
1207      // Now that we've waited and got an event, set the date to now to
1208      // avoid looping forever
1209      date = [NSDate date];
1210    }
1211  }
1212
1213  for (size_t i = 0; i < world->numViews; ++i) {
1214    PuglView* const view = world->views[i];
1215
1216    if ([[view->impl->drawView window] isVisible]) {
1217      puglDispatchSimpleEvent(view, PUGL_UPDATE);
1218    }
1219
1220    [view->impl->drawView displayIfNeeded];
1221  }
1222
1223  return PUGL_SUCCESS;
1224}
1225
1226#ifndef PUGL_DISABLE_DEPRECATED
1227PuglStatus
1228puglProcessEvents(PuglView* view)
1229{
1230  return puglDispatchEvents(view->world);
1231}
1232#endif
1233
1234double
1235puglGetTime(const PuglWorld* world)
1236{
1237  return (mach_absolute_time() / 1e9) - world->startTime;
1238}
1239
1240PuglStatus
1241puglPostRedisplay(PuglView* view)
1242{
1243  [view->impl->drawView setNeedsDisplay:YES];
1244  return PUGL_SUCCESS;
1245}
1246
1247PuglStatus
1248puglPostRedisplayRect(PuglView* view, const PuglRect rect)
1249{
1250  const NSRect rectPx = rectToNsRect(rect);
1251
1252  [view->impl->drawView setNeedsDisplayInRect:nsRectToPoints(view, rectPx)];
1253
1254  return PUGL_SUCCESS;
1255}
1256
1257PuglNativeView
1258puglGetNativeWindow(PuglView* view)
1259{
1260  return (PuglNativeView)view->impl->wrapperView;
1261}
1262
1263PuglStatus
1264puglSetWindowTitle(PuglView* view, const char* title)
1265{
1266  puglSetString(&view->title, title);
1267
1268  if (view->impl->window) {
1269    NSString* titleString =
1270      [[NSString alloc] initWithBytes:title
1271                               length:strlen(title)
1272                             encoding:NSUTF8StringEncoding];
1273
1274    [view->impl->window setTitle:titleString];
1275  }
1276
1277  return PUGL_SUCCESS;
1278}
1279
1280PuglStatus
1281puglSetFrame(PuglView* view, const PuglRect frame)
1282{
1283  PuglInternals* const impl = view->impl;
1284
1285  // Update view frame to exactly the requested frame in Pugl coordinates
1286  view->frame = frame;
1287
1288  const NSRect framePx = rectToNsRect(frame);
1289  const NSRect framePt = nsRectToPoints(view, framePx);
1290  if (impl->window) {
1291    // Resize window to fit new content rect
1292    const NSRect screenPt = rectToScreen(viewScreen(view), framePt);
1293    const NSRect winFrame = [impl->window frameRectForContentRect:screenPt];
1294
1295    [impl->window setFrame:winFrame display:NO];
1296  }
1297
1298  // Resize views
1299  const NSRect sizePx = NSMakeRect(0, 0, frame.width, frame.height);
1300  const NSRect sizePt = [impl->drawView convertRectFromBacking:sizePx];
1301
1302  [impl->wrapperView setFrame:(impl->window ? sizePt : framePt)];
1303  [impl->drawView setFrame:sizePt];
1304
1305  return PUGL_SUCCESS;
1306}
1307
1308PuglStatus
1309puglSetDefaultSize(PuglView* const view, const int width, const int height)
1310{
1311  view->defaultWidth  = width;
1312  view->defaultHeight = height;
1313  return PUGL_SUCCESS;
1314}
1315
1316PuglStatus
1317puglSetMinSize(PuglView* const view, const int width, const int height)
1318{
1319  view->minWidth  = width;
1320  view->minHeight = height;
1321
1322  if (view->impl->window && (view->minWidth || view->minHeight)) {
1323    [view->impl->window
1324      setContentMinSize:sizePoints(view, view->minWidth, view->minHeight)];
1325  }
1326
1327  return PUGL_SUCCESS;
1328}
1329
1330PuglStatus
1331puglSetMaxSize(PuglView* const view, const int width, const int height)
1332{
1333  view->maxWidth  = width;
1334  view->maxHeight = height;
1335
1336  if (view->impl->window && (view->maxWidth || view->maxHeight)) {
1337    [view->impl->window
1338      setContentMaxSize:sizePoints(view, view->maxWidth, view->maxHeight)];
1339  }
1340
1341  return PUGL_SUCCESS;
1342}
1343
1344PuglStatus
1345puglSetAspectRatio(PuglView* const view,
1346                   const int       minX,
1347                   const int       minY,
1348                   const int       maxX,
1349                   const int       maxY)
1350{
1351  view->minAspectX = minX;
1352  view->minAspectY = minY;
1353  view->maxAspectX = maxX;
1354  view->maxAspectY = maxY;
1355
1356  if (view->impl->window && view->minAspectX && view->minAspectY) {
1357    [view->impl->window setContentAspectRatio:sizePoints(view,
1358                                                         view->minAspectX,
1359                                                         view->minAspectY)];
1360  }
1361
1362  return PUGL_SUCCESS;
1363}
1364
1365PuglStatus
1366puglSetTransientFor(PuglView* view, PuglNativeView parent)
1367{
1368  view->transientParent = parent;
1369
1370  if (view->impl->window) {
1371    NSWindow* parentWindow = [(NSView*)parent window];
1372    if (parentWindow) {
1373      [parentWindow addChildWindow:view->impl->window ordered:NSWindowAbove];
1374      return PUGL_SUCCESS;
1375    }
1376  }
1377
1378  return PUGL_FAILURE;
1379}
1380
1381const void*
1382puglGetClipboard(PuglView* const    view,
1383                 const char** const type,
1384                 size_t* const      len)
1385{
1386  NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
1387
1388  if ([[pasteboard types] containsObject:NSStringPboardType]) {
1389    const NSString* str  = [pasteboard stringForType:NSStringPboardType];
1390    const char*     utf8 = [str UTF8String];
1391
1392    puglSetBlob(&view->clipboard, utf8, strlen(utf8) + 1);
1393  }
1394
1395  return puglGetInternalClipboard(view, type, len);
1396}
1397
1398static NSCursor*
1399puglGetNsCursor(const PuglCursor cursor)
1400{
1401  switch (cursor) {
1402  case PUGL_CURSOR_ARROW:
1403    return [NSCursor arrowCursor];
1404  case PUGL_CURSOR_CARET:
1405    return [NSCursor IBeamCursor];
1406  case PUGL_CURSOR_CROSSHAIR:
1407    return [NSCursor crosshairCursor];
1408  case PUGL_CURSOR_HAND:
1409    return [NSCursor pointingHandCursor];
1410  case PUGL_CURSOR_NO:
1411    return [NSCursor operationNotAllowedCursor];
1412  case PUGL_CURSOR_LEFT_RIGHT:
1413    return [NSCursor resizeLeftRightCursor];
1414  case PUGL_CURSOR_UP_DOWN:
1415    return [NSCursor resizeUpDownCursor];
1416  }
1417
1418  return NULL;
1419}
1420
1421PuglStatus
1422puglSetCursor(PuglView* view, PuglCursor cursor)
1423{
1424  PuglInternals* const impl = view->impl;
1425  NSCursor* const      cur  = puglGetNsCursor(cursor);
1426  if (!cur) {
1427    return PUGL_FAILURE;
1428  }
1429
1430  impl->cursor = cur;
1431
1432  if (impl->mouseTracked) {
1433    [cur set];
1434  }
1435
1436  return PUGL_SUCCESS;
1437}
1438
1439PuglStatus
1440puglSetClipboard(PuglView* const   view,
1441                 const char* const type,
1442                 const void* const data,
1443                 const size_t      len)
1444{
1445  NSPasteboard* const pasteboard = [NSPasteboard generalPasteboard];
1446  const char* const   str        = (const char*)data;
1447
1448  PuglStatus st = puglSetInternalClipboard(view, type, data, len);
1449  if (st) {
1450    return st;
1451  }
1452
1453  NSString* nsString = [NSString stringWithUTF8String:str];
1454  if (nsString) {
1455    [pasteboard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil]
1456                       owner:nil];
1457
1458    [pasteboard setString:nsString forType:NSStringPboardType];
1459
1460    return PUGL_SUCCESS;
1461  }
1462
1463  return PUGL_UNKNOWN_ERROR;
1464}
1465