1 /** 2 * Cocoa window implementation. 3 * Copyright: Copyright Guillaume Piolat 2015 - 2021. 4 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 5 */ 6 module dplug.window.cocoawindow; 7 8 import core.stdc.stdlib; 9 10 import std.string; 11 import std.uuid; 12 13 import dplug.math.vector; 14 import dplug.math.box; 15 import dplug.graphics.image; 16 17 import dplug.core.sync; 18 import dplug.core.runtime; 19 import dplug.core.nogc; 20 import dplug.core.random; 21 import dplug.core.thread; 22 import dplug.window.window; 23 24 import derelict.cocoa; 25 26 27 version = useCoreGraphicsContext; 28 29 final class CocoaWindow : IWindow 30 { 31 nothrow: 32 @nogc: 33 private: 34 IWindowListener _listener; 35 36 NSColorSpace _nsColorSpace; 37 CGColorSpaceRef _cgColorSpaceRef; 38 NSData _imageData; 39 NSString _logFormatStr; 40 41 // Only used by host windows 42 NSWindow _nsWindow; 43 NSApplication _nsApplication; 44 45 DPlugCustomView _view = null; 46 47 bool _terminated = false; 48 49 int _lastMouseX, _lastMouseY; 50 bool _firstMouseMove = true; 51 52 int _width; 53 int _height; 54 55 ImageRef!RGBA _wfb; 56 57 uint _timeAtCreationInMs; 58 uint _lastMeasturedTimeInMs; 59 bool _dirtyAreasAreNotYetComputed; 60 61 bool _isHostWindow; 62 bool _drawRectWorkaround; // See Issue #505 & #705, drawRect: returning always one big rectangle, killing CPU 63 64 MouseCursor _lastMouseCursor; 65 66 public: 67 68 this(WindowUsage usage, void* parentWindow, IWindowListener listener, int width, int height) 69 { 70 _isHostWindow = (usage == WindowUsage.host); 71 72 _listener = listener; 73 74 acquireCocoaFunctions(); 75 acquireCoreGraphicsFunctions(); 76 NSApplicationLoad(); // to use Cocoa in Carbon applications 77 bool parentViewExists = parentWindow !is null; 78 79 _width = 0; 80 _height = 0; 81 82 _nsColorSpace = NSColorSpace.sRGBColorSpace(); 83 // hopefully not null else the colors will be brighter 84 _cgColorSpaceRef = _nsColorSpace.CGColorSpace(); 85 86 _logFormatStr = NSString.stringWith("%@"); 87 88 _timeAtCreationInMs = getTimeMs(); 89 _lastMeasturedTimeInMs = _timeAtCreationInMs; 90 91 _dirtyAreasAreNotYetComputed = true; 92 93 // The drawRect: failure of having small rectangles started with 11.0 Big Sur beta 9. 94 // It was afterwards removed for Issue #705, it's no longer useful in Monterey, and in Ardour was making things worse. 95 version(OSX) 96 _drawRectWorkaround = (getMacOSVersion().major == 11); 97 98 if (!_isHostWindow) 99 { 100 DPlugCustomView.generateClassName(); 101 DPlugCustomView.registerSubclass(); 102 103 _view = DPlugCustomView.alloc(); 104 _view.initialize(this, width, height); 105 106 // GOAL: Force display by the GPU, this is supposed to solve 107 // resampling problems on HiDPI like 4k and 5k 108 // REALITY: QA reports this to be blurrier AND slower than previously 109 // Layer has to be there for the drawRect workaround. 110 if (_drawRectWorkaround) 111 _view.setWantsLayer(YES); 112 113 //_view.layer.setDrawsAsynchronously(YES); 114 // This is supposed to make things faster, but doesn't 115 //_view.layer.setOpaque(YES); 116 117 // In VST, add the view to the parent view. 118 // In AU (parentWindow == null), a reference to the view is returned instead and the host does it. 119 if (parentWindow !is null) 120 { 121 NSView parentView = NSView(cast(id)parentWindow); 122 parentView.addSubview(_view); 123 } 124 125 // See Issue #688: when changing the buffer size or sampling rate, 126 // Logic destroy and reloads the plugin, with same settings. The window 127 // is reused, thus layout doesn't get called and the plugin is unsized! 128 layout(); 129 } 130 else 131 { 132 _nsApplication = NSApplication.sharedApplication; 133 _nsApplication.setActivationPolicy(NSApplicationActivationPolicyRegular); 134 _nsWindow = NSWindow.alloc(); 135 _nsWindow.initWithContentRect(NSMakeRect(0, 0, width, height), 136 NSBorderlessWindowMask, NSBackingStoreBuffered, NO); 137 _nsWindow.makeKeyAndOrderFront(); 138 _nsApplication.activateIgnoringOtherApps(YES); 139 } 140 } 141 142 ~this() 143 { 144 if (!_isHostWindow) 145 { 146 _terminated = true; 147 148 { 149 _view.killTimer(); 150 } 151 152 _view.removeFromSuperview(); 153 _view.release(); 154 _view = DPlugCustomView(null); 155 156 DPlugCustomView.unregisterSubclass(); 157 } 158 else 159 { 160 _nsWindow.destroy(); 161 } 162 163 releaseCocoaFunctions(); 164 } 165 166 // Implements IWindow 167 override void waitEventAndDispatch() 168 { 169 if (!_isHostWindow) 170 assert(false); // only valid for a host window 171 172 NSEvent event = _nsWindow.nextEventMatchingMask(cast(NSUInteger)-1); 173 _nsApplication.sendEvent(event); 174 } 175 176 override bool terminated() 177 { 178 return _terminated; 179 } 180 181 override uint getTimeMs() 182 { 183 double timeMs = NSDate.timeIntervalSinceReferenceDate() * 1000.0; 184 185 // WARNING: ARM and x86 do not convert float to int in the same way. 186 // Convert to 64-bit and use integer truncation rather than UB. 187 // See: https://github.com/ldc-developers/ldc/issues/3603 188 long timeMs_integer = cast(long)timeMs; 189 uint ms = cast(uint)(timeMs_integer); 190 return ms; 191 } 192 193 override void* systemHandle() 194 { 195 if (_isHostWindow) 196 return _nsWindow.contentView()._id; // return the main NSView 197 else 198 return _view._id; 199 } 200 201 override bool requestResize(int widthLogicalPixels, int heightLogicalPixels, bool alsoResizeParentWindow) 202 { 203 assert(!alsoResizeParentWindow); // unsupported here 204 NSSize size = NSSize(cast(CGFloat)widthLogicalPixels, 205 cast(CGFloat)heightLogicalPixels); 206 _view.setFrameSize(size); 207 return true; 208 } 209 210 private: 211 212 MouseState getMouseState(NSEvent event) 213 { 214 // not working 215 MouseState state; 216 uint pressedMouseButtons = event.pressedMouseButtons(); 217 if (pressedMouseButtons & 1) 218 state.leftButtonDown = true; 219 if (pressedMouseButtons & 2) 220 state.rightButtonDown = true; 221 if (pressedMouseButtons & 4) 222 state.middleButtonDown = true; 223 224 NSEventModifierFlags mod = event.modifierFlags(); 225 if (mod & NSControlKeyMask) 226 state.ctrlPressed = true; 227 if (mod & NSShiftKeyMask) 228 state.shiftPressed = true; 229 if (mod & NSAlternateKeyMask) 230 state.altPressed = true; 231 232 return state; 233 } 234 235 void handleMouseWheel(NSEvent event) 236 { 237 double ddeltaX = event.deltaX; 238 double ddeltaY = event.deltaY; 239 int deltaX = 0; 240 int deltaY = 0; 241 if (ddeltaX > 0) deltaX = 1; 242 if (ddeltaX < 0) deltaX = -1; 243 if (ddeltaY > 0) deltaY = 1; 244 if (ddeltaY < 0) deltaY = -1; 245 if (deltaX || deltaY) 246 { 247 vec2i mousePos = getMouseXY(_view, event, _height); 248 _listener.onMouseWheel(mousePos.x, mousePos.y, deltaX, deltaY, getMouseState(event)); 249 } 250 } 251 252 bool handleKeyEvent(NSEvent event, bool released) 253 { 254 uint keyCode = event.keyCode(); 255 Key key; 256 switch (keyCode) 257 { 258 case kVK_ANSI_Keypad0: key = Key.digit0; break; 259 case kVK_ANSI_Keypad1: key = Key.digit1; break; 260 case kVK_ANSI_Keypad2: key = Key.digit2; break; 261 case kVK_ANSI_Keypad3: key = Key.digit3; break; 262 case kVK_ANSI_Keypad4: key = Key.digit4; break; 263 case kVK_ANSI_Keypad5: key = Key.digit5; break; 264 case kVK_ANSI_Keypad6: key = Key.digit6; break; 265 case kVK_ANSI_Keypad7: key = Key.digit7; break; 266 case kVK_ANSI_Keypad8: key = Key.digit8; break; 267 case kVK_ANSI_Keypad9: key = Key.digit9; break; 268 case kVK_Return: key = Key.enter; break; 269 case kVK_Escape: key = Key.escape; break; 270 case kVK_LeftArrow: key = Key.leftArrow; break; 271 case kVK_RightArrow: key = Key.rightArrow; break; 272 case kVK_DownArrow: key = Key.downArrow; break; 273 case kVK_UpArrow: key = Key.upArrow; break; 274 case kVK_Delete: key = Key.backspace; break; 275 case kVK_ForwardDelete: key = Key.suppr; break; 276 default: 277 { 278 NSString characters = event.charactersIgnoringModifiers(); 279 if (characters.length() == 0) 280 { 281 key = Key.unsupported; 282 } 283 else 284 { 285 wchar ch = characters.characterAtIndex(0); 286 if (ch >= '0' && ch <= '9') 287 key = cast(Key)(Key.digit0 + (ch - '0')); 288 else if (ch >= 'A' && ch <= 'Z') 289 key = cast(Key)(Key.A + (ch - 'A')); 290 else if (ch >= 'a' && ch <= 'z') 291 key = cast(Key)(Key.a + (ch - 'a')); 292 else 293 key = Key.unsupported; 294 } 295 } 296 } 297 298 bool handled = false; 299 300 if (released) 301 { 302 if (_listener.onKeyDown(key)) 303 handled = true; 304 } 305 else 306 { 307 if (_listener.onKeyUp(key)) 308 handled = true; 309 } 310 311 return handled; 312 } 313 314 void handleMouseMove(NSEvent event) 315 { 316 vec2i mousePos = getMouseXY(_view, event, _height); 317 318 if (_firstMouseMove) 319 { 320 _firstMouseMove = false; 321 _lastMouseX = mousePos.x; 322 _lastMouseY = mousePos.y; 323 } 324 325 _listener.onMouseMove(mousePos.x, mousePos.y, mousePos.x - _lastMouseX, mousePos.y - _lastMouseY, 326 getMouseState(event)); 327 328 version(legacyMouseCursor) 329 {} 330 else 331 { 332 setMouseCursor(_listener.getMouseCursor()); 333 } 334 335 _lastMouseX = mousePos.x; 336 _lastMouseY = mousePos.y; 337 } 338 339 void handleMouseEntered(NSEvent event) 340 { 341 // Welcome to Issue #737. 342 // 343 // Consider the mouse cursor has changed, since the mouse was elsewhere. 344 // Give it an impossible mouse cursor cached value. 345 _lastMouseCursor = cast(MouseCursor) -1; 346 347 // Furthermore, because: 348 // 1. either the cursor might not be set by subsequent 349 // 2. either the mouseMove event might not be called, because the window is hosted in another process 350 // and isn't active yet (macOS has "click through", a sort of mouse focus) 351 // then we need to set the mouse cursor upon entry. Tricky! 352 version(legacyMouseCursor) 353 { 354 setMouseCursor(MouseCursor.pointer); 355 } 356 else 357 { 358 setMouseCursor(_listener.getMouseCursor()); 359 } 360 } 361 362 void handleMouseClicks(NSEvent event, MouseButton mb, bool released) 363 { 364 vec2i mousePos = getMouseXY(_view, event, _height); 365 366 if (released) 367 _listener.onMouseRelease(mousePos.x, mousePos.y, mb, getMouseState(event)); 368 else 369 { 370 // Fix Issue #281 371 // This resets _lastMouseX and _lastMouseY on new clicks, 372 // necessary if the focus was lost for a while. 373 _firstMouseMove = true; 374 375 int clickCount = event.clickCount(); 376 bool isDoubleClick = clickCount >= 2; 377 _listener.onMouseClick(mousePos.x, mousePos.y, mb, isDoubleClick, getMouseState(event)); 378 } 379 } 380 381 void layout() 382 { 383 // Updates internal buffers in case of startup/resize 384 { 385 NSRect frameRect = _view.frame(); 386 // Note: even if the frame rect is wrong, we can support any internal size with cropping etc. 387 // TODO: is it really wrong though? 388 int width = cast(int)(frameRect.size.width); // truncating down the dimensions of bounds 389 int height = cast(int)(frameRect.size.height); 390 updateSizeIfNeeded(width, height); 391 } 392 } 393 394 void viewWillDraw() 395 { 396 if (_drawRectWorkaround) 397 { 398 CALayer layer = _view.layer(); 399 400 if (layer) 401 { 402 // On Big Sur this is technically a no-op, but that reverts the drawRect behaviour! 403 // This workaround is sanctionned by Apple: https://gist.github.com/lukaskubanek/9a61ac71dc0db8bb04db2028f2635779#gistcomment-3901461 404 layer.setContentsFormat(kCAContentsFormatRGBA8Uint); 405 } 406 } 407 } 408 409 void drawRect(NSRect rect) 410 { 411 NSGraphicsContext nsContext = NSGraphicsContext.currentContext(); 412 413 // The first drawRect callback occurs before the timer triggers. 414 // But because recomputeDirtyAreas() wasn't called before there is nothing to draw. 415 // Hence, do it. 416 if (_dirtyAreasAreNotYetComputed) 417 { 418 _dirtyAreasAreNotYetComputed = false; 419 _listener.recomputeDirtyAreas(); 420 } 421 422 _listener.onDraw(WindowPixelFormat.ARGB8); 423 424 version(useCoreGraphicsContext) 425 { 426 CGContextRef cgContext = nsContext.getCGContext(); 427 428 enum bool fullDraw = false; 429 430 static if (fullDraw) 431 { 432 size_t sizeNeeded = _wfb.pitch * _wfb.h; 433 size_t bytesPerRow = _wfb.pitch; 434 435 CGDataProviderRef provider = CGDataProviderCreateWithData(null, _wfb.pixels, sizeNeeded, null); 436 CGImageRef image = CGImageCreate(_width, 437 _height, 438 8, 439 32, 440 bytesPerRow, 441 _cgColorSpaceRef, 442 kCGImageByteOrderDefault | kCGImageAlphaNoneSkipFirst, 443 provider, 444 null, 445 true, 446 kCGRenderingIntentDefault); 447 // "on return, you may safely release [the provider]" 448 CGDataProviderRelease(provider); 449 scope(exit) CGImageRelease(image); 450 451 CGRect fullRect = CGMakeRect(0, 0, _width, _height); 452 CGContextDrawImage(cgContext, fullRect, image); 453 } 454 else 455 { 456 alias r = rect; 457 458 int origX = cast(int)rect.origin.x; 459 int origY = cast(int)rect.origin.y; 460 int width = cast(int)rect.size.width; 461 int height = cast(int)rect.size.height; 462 463 int ysource = -origY + _height - height; 464 465 assert(ysource >= 0); 466 assert(ysource < _height); 467 468 const (RGBA)* firstPixel = &(_wfb.scanline(ysource)[origX]); 469 size_t sizeNeeded = _wfb.pitch * height; 470 size_t bytesPerRow = _wfb.pitch; 471 472 CGDataProviderRef provider = CGDataProviderCreateWithData(null, firstPixel, sizeNeeded, null); 473 474 CGImageRef image = CGImageCreate(width, 475 height, 476 8, 477 32, 478 bytesPerRow, 479 _cgColorSpaceRef, 480 kCGImageByteOrderDefault | kCGImageAlphaNoneSkipFirst, 481 provider, 482 null, 483 true, 484 kCGRenderingIntentDefault); 485 // "on return, you may safely release [the provider]" 486 CGDataProviderRelease(provider); 487 scope(exit) CGImageRelease(image); 488 489 //CGRect fullRect = CGMakeRect(0, 0, width, height); 490 CGContextDrawImage(cgContext, rect, image); 491 } 492 } 493 else 494 { 495 size_t sizeNeeded = _wfb.pitch * _wfb.h; 496 size_t bytesPerRow = _wfb.pitch; 497 CIContext ciContext = nsContext.getCIContext(); 498 _imageData = NSData.dataWithBytesNoCopy(_wfb.pixels, sizeNeeded, false); 499 500 CIImage image = CIImage.imageWithBitmapData(_imageData, 501 bytesPerRow, 502 CGSize(_width, _height), 503 kCIFormatARGB8, 504 _cgColorSpaceRef); 505 ciContext.drawImage(image, rect, rect); 506 } 507 } 508 509 /// Returns: true if window size changed. 510 bool updateSizeIfNeeded(int newWidth, int newHeight) 511 { 512 // only do something if the client size has changed 513 if ( (newWidth != _width) || (newHeight != _height) ) 514 { 515 _width = newWidth; 516 _height = newHeight; 517 _wfb = _listener.onResized(_width, _height); 518 return true; 519 } 520 else 521 return false; 522 } 523 524 void doAnimation() 525 { 526 uint now = getTimeMs(); 527 double dt = (now - _lastMeasturedTimeInMs) * 0.001; 528 double time = (now - _timeAtCreationInMs) * 0.001; // hopefully no plug-in will be open more than 49 days 529 _lastMeasturedTimeInMs = now; 530 _listener.onAnimate(dt, time); 531 } 532 533 void onTimer() 534 { 535 // Deal with animation 536 doAnimation(); 537 538 _listener.recomputeDirtyAreas(); 539 _dirtyAreasAreNotYetComputed = false; 540 box2i dirtyRect = _listener.getDirtyRectangle(); 541 if (!dirtyRect.empty()) 542 { 543 544 NSRect boundsRect = _view.bounds(); 545 int height = cast(int)(boundsRect.size.height); 546 NSRect r = NSMakeRect(dirtyRect.min.x, 547 height - dirtyRect.min.y - dirtyRect.height, 548 dirtyRect.width, 549 dirtyRect.height); 550 _view.setNeedsDisplayInRect(r); 551 } 552 } 553 554 void setMouseCursor(MouseCursor dplugCursor) 555 { 556 if(dplugCursor != _lastMouseCursor) 557 { 558 if(dplugCursor == MouseCursor.hidden) 559 { 560 NSCursor.hide(); 561 } 562 else 563 { 564 if(_lastMouseCursor == MouseCursor.hidden) 565 { 566 NSCursor.unhide(); 567 } 568 569 NSCursor.pop(); 570 NSCursor nsCursor; 571 switch(dplugCursor) 572 { 573 case MouseCursor.linkSelect: 574 nsCursor = NSCursor.pointingHandCursor(); 575 break; 576 case MouseCursor.drag: 577 nsCursor = NSCursor.crosshairCursor(); 578 break; 579 case MouseCursor.move: 580 nsCursor = NSCursor.openHandCursor(); 581 break; 582 case MouseCursor.verticalResize: 583 nsCursor = NSCursor.resizeUpDownCursor(); 584 break; 585 case MouseCursor.horizontalResize: 586 nsCursor = NSCursor.resizeLeftRightCursor(); 587 break; 588 case MouseCursor.diagonalResize: 589 nsCursor = NSCursor.crosshairCursor(); // macOS doesn't seem to have this 590 break; 591 case MouseCursor.pointer: 592 default: 593 nsCursor = NSCursor.arrowCursor(); 594 } 595 nsCursor.push(); 596 } 597 598 _lastMouseCursor = dplugCursor; 599 } 600 } 601 } 602 603 struct DPlugCustomView 604 { 605 public: 606 nothrow: 607 @nogc: 608 609 NSView parent; 610 alias parent this; 611 612 // create from an id 613 this (id id_) 614 { 615 this._id = id_; 616 } 617 618 /// Allocates, but do not init 619 static DPlugCustomView alloc() 620 { 621 alias fun_t = extern(C) id function (id obj, SEL sel) nothrow @nogc; 622 return DPlugCustomView( (cast(fun_t)objc_msgSend)(getClassID(), sel!"alloc") ); 623 } 624 625 static Class getClass() 626 { 627 return cast(Class)( getClassID() ); 628 } 629 630 static id getClassID() 631 { 632 return objc_getClass(customClassName.ptr); 633 } 634 635 // This class uses a unique class name for each plugin instance 636 static __gshared char[16 + 36 + 1] customClassName; 637 638 static void generateClassName() nothrow @nogc 639 { 640 generateNullTerminatedRandomUUID!char(customClassName, "DPlugCustomView_"); 641 } 642 643 private: 644 645 CocoaWindow _window; 646 NSTimer _timer = null; 647 NSString _runLoopMode; 648 NSTrackingArea _trackingArea; 649 650 void initialize(CocoaWindow window, int width, int height) 651 { 652 // Warning: taking this address is fishy since DPlugCustomView is a struct and thus could be copied 653 // we rely on the fact it won't :| 654 void* thisPointer = cast(void*)(&this); 655 object_setInstanceVariable(_id, "this", thisPointer); 656 657 this._window = window; 658 659 NSRect r = NSRect(NSPoint(0, 0), NSSize(width, height)); 660 initWithFrame(r); 661 662 _timer = NSTimer.timerWithTimeInterval(1 / 60.0, this, sel!"onTimer:", null, true); 663 _runLoopMode = NSString.stringWith("kCFRunLoopCommonModes"w); 664 NSRunLoop.currentRunLoop().addTimer(_timer, _runLoopMode); 665 } 666 667 static __gshared Class clazz; 668 669 static void registerSubclass() 670 { 671 clazz = objc_allocateClassPair(cast(Class) lazyClass!"NSView", customClassName.ptr, 0); 672 673 class_addMethod(clazz, sel!"keyDown:", cast(IMP) &keyDown, "v@:@"); 674 class_addMethod(clazz, sel!"keyUp:", cast(IMP) &keyUp, "v@:@"); 675 class_addMethod(clazz, sel!"mouseDown:", cast(IMP) &mouseDown, "v@:@"); 676 class_addMethod(clazz, sel!"mouseUp:", cast(IMP) &mouseUp, "v@:@"); 677 class_addMethod(clazz, sel!"rightMouseDown:", cast(IMP) &rightMouseDown, "v@:@"); 678 class_addMethod(clazz, sel!"rightMouseUp:", cast(IMP) &rightMouseUp, "v@:@"); 679 class_addMethod(clazz, sel!"otherMouseDown:", cast(IMP) &otherMouseDown, "v@:@"); 680 class_addMethod(clazz, sel!"otherMouseUp:", cast(IMP) &otherMouseUp, "v@:@"); 681 class_addMethod(clazz, sel!"mouseMoved:", cast(IMP) &mouseMoved, "v@:@"); 682 class_addMethod(clazz, sel!"mouseDragged:", cast(IMP) &mouseMoved, "v@:@"); 683 class_addMethod(clazz, sel!"rightMouseDragged:", cast(IMP) &mouseMoved, "v@:@"); 684 class_addMethod(clazz, sel!"otherMouseDragged:", cast(IMP) &mouseMoved, "v@:@"); 685 class_addMethod(clazz, sel!"acceptsFirstResponder", cast(IMP) &acceptsFirstResponder, "b@:"); 686 class_addMethod(clazz, sel!"isOpaque", cast(IMP) &isOpaque, "b@:"); 687 class_addMethod(clazz, sel!"acceptsFirstMouse:", cast(IMP) &acceptsFirstMouse, "b@:@"); 688 class_addMethod(clazz, sel!"viewDidMoveToWindow", cast(IMP) &viewDidMoveToWindow, "v@:"); 689 class_addMethod(clazz, sel!"layout", cast(IMP) &layout, "v@:"); 690 class_addMethod(clazz, sel!"drawRect:", cast(IMP) &drawRect, "v@:" ~ encode!NSRect); 691 class_addMethod(clazz, sel!"onTimer:", cast(IMP) &onTimer, "v@:@"); 692 class_addMethod(clazz, sel!"viewWillDraw", cast(IMP) &viewWillDraw, "v@:"); 693 694 class_addMethod(clazz, sel!"mouseEntered:", cast(IMP) &mouseEntered, "v@:@"); 695 class_addMethod(clazz, sel!"mouseExited:", cast(IMP) &mouseExited, "v@:@"); 696 class_addMethod(clazz, sel!"updateTrackingAreas", cast(IMP)&updateTrackingAreas, "v@:"); 697 698 // This ~ is to avoid a strange DMD ICE. Didn't succeed in isolating it. 699 class_addMethod(clazz, sel!("scroll" ~ "Wheel:") , cast(IMP) &scrollWheel, "v@:@"); 700 701 // very important: add an instance variable for the this pointer so that the D object can be 702 // retrieved from an id 703 class_addIvar(clazz, "this", (void*).sizeof, (void*).sizeof == 4 ? 2 : 3, "^v"); 704 705 objc_registerClassPair(clazz); 706 } 707 708 static void unregisterSubclass() 709 { 710 // For some reason the class need to continue to exist, so we leak it 711 // objc_disposeClassPair(clazz); 712 // TODO: remove this crap 713 } 714 715 void killTimer() 716 { 717 if (_timer) 718 { 719 _timer.invalidate(); 720 _timer = NSTimer(null); 721 } 722 } 723 } 724 725 DPlugCustomView getInstance(id anId) nothrow @nogc 726 { 727 // strange thing: object_getInstanceVariable definition is odd (void**) 728 // and only works for pointer-sized values says SO 729 void* thisPointer = null; 730 Ivar var = object_getInstanceVariable(anId, "this", &thisPointer); 731 assert(var !is null); 732 assert(thisPointer !is null); 733 return *cast(DPlugCustomView*)thisPointer; 734 } 735 736 vec2i getMouseXY(NSView view, NSEvent event, int windowHeight) nothrow @nogc 737 { 738 NSPoint mouseLocation = event.locationInWindow(); 739 mouseLocation = view.convertPoint(mouseLocation, NSView(null)); 740 int px = cast(int)(mouseLocation.x) - 2; 741 int py = windowHeight - cast(int)(mouseLocation.y) - 3; 742 return vec2i(px, py); 743 } 744 745 746 747 alias CocoaScopedCallback = ScopedForeignCallback!(true, true); 748 749 // Overridden function gets called with an id, instead of the self pointer. 750 // So we have to get back the D class object address. 751 // Big thanks to Mike Ash (@macdev) 752 // MAYDO: why are these methods members??? 753 extern(C) 754 { 755 void keyDown(id self, SEL selector, id event) nothrow @nogc 756 { 757 CocoaScopedCallback scopedCallback; 758 scopedCallback.enter(); 759 760 DPlugCustomView view = getInstance(self); 761 bool handled = view._window.handleKeyEvent(NSEvent(event), false); 762 763 // send event to superclass if event not handled 764 if (!handled) 765 { 766 objc_super sup; 767 sup.receiver = self; 768 sup.clazz = cast(Class) lazyClass!"NSView"; 769 alias fun_t = extern(C) void function (objc_super*, SEL, id) nothrow @nogc; 770 (cast(fun_t)objc_msgSendSuper)(&sup, selector, event); 771 } 772 } 773 774 void keyUp(id self, SEL selector, id event) nothrow @nogc 775 { 776 CocoaScopedCallback scopedCallback; 777 scopedCallback.enter(); 778 779 DPlugCustomView view = getInstance(self); 780 view._window.handleKeyEvent(NSEvent(event), true); 781 } 782 783 void mouseDown(id self, SEL selector, id event) nothrow @nogc 784 { 785 CocoaScopedCallback scopedCallback; 786 scopedCallback.enter(); 787 788 DPlugCustomView view = getInstance(self); 789 view._window.handleMouseClicks(NSEvent(event), MouseButton.left, false); 790 } 791 792 void mouseUp(id self, SEL selector, id event) nothrow @nogc 793 { 794 CocoaScopedCallback scopedCallback; 795 scopedCallback.enter(); 796 797 DPlugCustomView view = getInstance(self); 798 view._window.handleMouseClicks(NSEvent(event), MouseButton.left, true); 799 } 800 801 void rightMouseDown(id self, SEL selector, id event) nothrow @nogc 802 { 803 CocoaScopedCallback scopedCallback; 804 scopedCallback.enter(); 805 806 DPlugCustomView view = getInstance(self); 807 view._window.handleMouseClicks(NSEvent(event), MouseButton.right, false); 808 } 809 810 void rightMouseUp(id self, SEL selector, id event) nothrow @nogc 811 { 812 CocoaScopedCallback scopedCallback; 813 scopedCallback.enter(); 814 815 DPlugCustomView view = getInstance(self); 816 view._window.handleMouseClicks(NSEvent(event), MouseButton.right, true); 817 } 818 819 void otherMouseDown(id self, SEL selector, id event) nothrow @nogc 820 { 821 CocoaScopedCallback scopedCallback; 822 scopedCallback.enter(); 823 824 DPlugCustomView view = getInstance(self); 825 auto nsEvent = NSEvent(event); 826 if (nsEvent.buttonNumber == 2) 827 view._window.handleMouseClicks(nsEvent, MouseButton.middle, false); 828 } 829 830 void otherMouseUp(id self, SEL selector, id event) nothrow @nogc 831 { 832 CocoaScopedCallback scopedCallback; 833 scopedCallback.enter(); 834 835 DPlugCustomView view = getInstance(self); 836 auto nsEvent = NSEvent(event); 837 if (nsEvent.buttonNumber == 2) 838 view._window.handleMouseClicks(nsEvent, MouseButton.middle, true); 839 } 840 841 void mouseMoved(id self, SEL selector, id event) nothrow @nogc 842 { 843 CocoaScopedCallback scopedCallback; 844 scopedCallback.enter(); 845 846 DPlugCustomView view = getInstance(self); 847 view._window.handleMouseMove(NSEvent(event)); 848 } 849 850 void mouseEntered(id self, SEL selector, id event) nothrow @nogc 851 { 852 CocoaScopedCallback scopedCallback; 853 scopedCallback.enter(); 854 DPlugCustomView view = getInstance(self); 855 view._window.handleMouseEntered(NSEvent(event)); 856 } 857 858 void mouseExited(id self, SEL selector, id event) nothrow @nogc 859 { 860 CocoaScopedCallback scopedCallback; 861 scopedCallback.enter(); 862 DPlugCustomView view = getInstance(self); 863 view._window._listener.onMouseExitedWindow(); 864 } 865 866 void updateTrackingAreas(id self, SEL selector) nothrow @nogc 867 { 868 CocoaScopedCallback scopedCallback; 869 scopedCallback.enter(); 870 871 // Call superclass's updateTrackingAreas:, equivalent to [super updateTrackingAreas]; 872 { 873 objc_super sup; 874 sup.receiver = self; 875 sup.clazz = cast(Class) lazyClass!"NSView"; 876 alias fun_t = extern(C) void function (objc_super*, SEL) nothrow @nogc; 877 (cast(fun_t)objc_msgSendSuper)(&sup, selector); 878 } 879 880 DPlugCustomView view = getInstance(self); 881 882 // Remove an existing tracking area, if any. 883 if (view._trackingArea._id !is null) 884 { 885 view.removeTrackingArea(view._trackingArea); 886 view._trackingArea.release(); 887 view._trackingArea._id = null; 888 } 889 890 // This is needed to get mouseEntered and mouseExited 891 int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways); 892 893 NSRect bounds = view.bounds(); 894 view._trackingArea = NSTrackingArea.alloc(); 895 view._trackingArea.initWithRect(bounds, opts, view, null); 896 view.addTrackingArea(view._trackingArea); 897 } 898 899 900 void scrollWheel(id self, SEL selector, id event) nothrow @nogc 901 { 902 CocoaScopedCallback scopedCallback; 903 scopedCallback.enter(); 904 DPlugCustomView view = getInstance(self); 905 view._window.handleMouseWheel(NSEvent(event)); 906 } 907 908 bool acceptsFirstResponder(id self, SEL selector) nothrow @nogc 909 { 910 return YES; 911 } 912 913 bool acceptsFirstMouse(id self, SEL selector, id pEvent) nothrow @nogc 914 { 915 return YES; 916 } 917 918 bool isOpaque(id self, SEL selector) nothrow @nogc 919 { 920 return YES; 921 } 922 923 // Since 10.7, called on resize. 924 void layout(id self, SEL selector) nothrow @nogc 925 { 926 CocoaScopedCallback scopedCallback; 927 scopedCallback.enter(); 928 929 DPlugCustomView view = getInstance(self); 930 view._window.layout(); 931 932 // Call superclass's layout:, equivalent to [super layout]; 933 { 934 objc_super sup; 935 sup.receiver = self; 936 sup.clazz = cast(Class) lazyClass!"NSView"; 937 alias fun_t = extern(C) void function (objc_super*, SEL) nothrow @nogc; 938 (cast(fun_t)objc_msgSendSuper)(&sup, selector); 939 } 940 } 941 942 // Necessary for the Big Sur drawRect: fuckup 943 // See Issue #505. 944 void viewWillDraw(id self, SEL selector) nothrow @nogc 945 { 946 CocoaScopedCallback scopedCallback; 947 scopedCallback.enter(); 948 949 DPlugCustomView view = getInstance(self); 950 view._window.viewWillDraw(); 951 952 // Call superclass's layout:, equivalent to [super viewWillDraw]; 953 { 954 objc_super sup; 955 sup.receiver = self; 956 sup.clazz = cast(Class) lazyClass!"NSView"; 957 alias fun_t = extern(C) void function (objc_super*, SEL) nothrow @nogc; 958 (cast(fun_t)objc_msgSendSuper)(&sup, selector); 959 } 960 } 961 962 void viewDidMoveToWindow(id self, SEL selector) nothrow @nogc 963 { 964 CocoaScopedCallback scopedCallback; 965 scopedCallback.enter(); 966 967 DPlugCustomView view = getInstance(self); 968 NSWindow parentWindow = view.window(); 969 if (parentWindow) 970 { 971 parentWindow.makeFirstResponder(view); 972 parentWindow.setAcceptsMouseMovedEvents(true); 973 } 974 } 975 976 void drawRect(id self, SEL selector, NSRect rect) nothrow @nogc 977 { 978 CocoaScopedCallback scopedCallback; 979 scopedCallback.enter(); 980 981 DPlugCustomView view = getInstance(self); 982 view._window.drawRect(rect); 983 } 984 985 void onTimer(id self, SEL selector, id timer) nothrow @nogc 986 { 987 CocoaScopedCallback scopedCallback; 988 scopedCallback.enter(); 989 990 DPlugCustomView view = getInstance(self); 991 view._window.onTimer(); 992 } 993 }