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 if (type && strcmp(type, "text/plain")) { 1449 return PUGL_UNSUPPORTED_TYPE; 1450 } 1451 1452 PuglStatus st = puglSetInternalClipboard(view, type, data, len); 1453 if (st) { 1454 return st; 1455 } 1456 1457 NSString* nsString = [NSString stringWithUTF8String:str]; 1458 if (nsString) { 1459 [pasteboard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] 1460 owner:nil]; 1461 1462 [pasteboard setString:nsString forType:NSStringPboardType]; 1463 1464 return PUGL_SUCCESS; 1465 } 1466 1467 return PUGL_UNKNOWN_ERROR; 1468} 1469