| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
-
- // Missing features:
- // * The zoom button has been disabled to prevent instability from many types of partial full-screen that are currently not supported.
- // Potential optimizations:
- // * Let the MacOS compositor handle the up-scaling of pixels.
- // The compositor in MacOS is already rendering the uploaded canvas using bi-linear interpolation when the canvas is smaller than the window.
- // So changing the interpolation mode and adjusting the frame size to canvas size times pixel scale should give the same result.
- // It might reduce the amount of data sent to the compositor without depending directly on Metal.
- // CocoaWindow can then override a canvas upload from the original canvas resolution to reduce the amount of data sent to the compositor.
- // Then one can get thousands of frames per second, just like when changing the desktop resolution manually, but without getting blurry pixels from scaling with the wrong interpolation mode.
- // * Double buffering is disabled for safety by assigining bufferCount to 1 instead of 2 and copying presented pixel data to delayedCanvas.
- // Find a way to wait for the previous image to be displayed before giving Cocoa the next image, so that a full copy is not needed.
- #import <Cocoa/Cocoa.h>
- #include "../DFPSR/api/imageAPI.h"
- #include "../DFPSR/api/drawAPI.h"
- #include "../DFPSR/api/timeAPI.h"
- #include "../DFPSR/implementation/gui/BackendWindow.h"
- #include "../DFPSR/base/heap.h"
- #include <climits>
- #include "../DFPSR/settings.h"
- static const int bufferCount = 1;
- static bool applicationInitialized = false;
- static NSApplication *application;
- class CocoaWindow : public dsr::BackendWindow {
- private:
- // Handle to the Cocoa window
- NSWindow *window = nullptr;
- NSView *view = nullptr;
- // The Core Graphics color space
- CGColorSpace *colorSpace = nullptr;
- // Identity to track enter and exit events for.
- SInt trackingNumber = 0;
- // Only accept non-drag move events when inside of the window.
- bool cursorInside = false;
- // Keeping track of control and command clicks.
- // 0 for regular left click.
- // 1 for control click converted to right mouse button.
- // 2 for command click converted to middle mouse button.
- int modifiedClick = 0;
- // Last modifiers to allow converting NSEventTypeFlagsChanged into up and down key press events.
- bool pressedControl = false;
- bool pressedCommand = false;
- bool pressedControlCommand = false;
- bool pressedShift = false;
- bool pressedAltOption = false;
- // Double buffering to allow drawing to a canvas while displaying the previous one
- // The image which can be drawn to, sharing memory with the Cocoa image
- dsr::AlignedImageRgbaU8 canvas[bufferCount];
- // To prevent seeing unfinished scenes when rendering faster than the results can be displayed, a third canvas takes a copy from the finished image.
- dsr::Buffer delayedCanvas;
- // An Cocoa image wrapped around the canvas pixel data
- //NSImage *canvasNS[bufferCount] = {};
- int drawIndex = 0 % bufferCount;
- int showIndex = 1 % bufferCount;
- // Remembers the dimensions of the window from creation and resize events
- // This allow requesting the size of the window at any time
- int windowWidth = 0, windowHeight = 0;
- // Called before the application fetches events from the input queue
- // Closing the window, moving the mouse, pressing a key, et cetera
- void prefetchEvents() override;
- // Called to change the cursor visibility and returning true on success
- bool setCursorVisibility(bool visible) override;
- // Place the cursor within the window
- bool setCursorPosition(int x, int y) override;
- private:
- // Helper methods specific to calling XLib
- void updateTitle();
- private:
- // Canvas methods
- dsr::AlignedImageRgbaU8 getCanvas() override { return this->canvas[this->drawIndex]; }
- void resizeCanvas(int width, int height) override;
- // Window methods
- void setTitle(const dsr::String &newTitle) override {
- this->title = newTitle;
- this->updateTitle();
- }
- int windowState = 0; // 0=none, 1=windowed, 2=fullscreen
- public:
- // Constructors
- CocoaWindow(const CocoaWindow&) = delete; // Non-copyable because of pointer aliasing.
- CocoaWindow(const dsr::String& title, int width, int height);
- int getWidth() const override { return this->windowWidth; };
- int getHeight() const override { return this->windowHeight; };
- // Destructor
- ~CocoaWindow();
- // Full-screen
- void setFullScreen(bool enabled) override;
- bool isFullScreen() override { return this->windowState == 2; }
- // Showing the content
- void showCanvas() override;
- // Clipboard access
- dsr::ReadableString loadFromClipboard(double timeoutInSeconds) override;
- void saveToClipboard(const dsr::ReadableString &text, double timeoutInSeconds) override;
- };
- static dsr::String nsToDsrString(const NSString *text) {
- dsr::String result;
- if (text != nullptr) {
- // Convert to a UTF-8 C string.
- const char *utf8text = [text cStringUsingEncoding:NSUTF8StringEncoding];
- if (utf8text != nullptr) {
- // Convert to a DSR string.
- result = dsr::string_dangerous_decodeFromData(utf8text, dsr::CharacterEncoding::BOM_UTF8);
- }
- }
- return result;
- }
- static NSString *dsrToNsString(const dsr::ReadableString &text) {
- dsr::Buffer utf8buffer = dsr::string_saveToMemory(text, dsr::CharacterEncoding::BOM_UTF8, dsr::LineEncoding::Lf, false, true);
- char *utf8text = (char *)dsr::buffer_dangerous_getUnsafeData(utf8buffer);
- return [NSString stringWithUTF8String:utf8text];
- }
- dsr::ReadableString CocoaWindow::loadFromClipboard(double timeoutInSeconds) {
- NSPasteboard *clipboard = [NSPasteboard generalPasteboard];
- NSString *text = [clipboard stringForType:NSPasteboardTypeString];
- if (text != nullptr) {
- return nsToDsrString(text);
- } else {
- return U"";
- }
- }
- void CocoaWindow::saveToClipboard(const dsr::ReadableString &text, double timeoutInSeconds) {
- NSString *savedText = dsrToNsString(text);
- NSPasteboard *clipboard = [NSPasteboard generalPasteboard];
- [clipboard clearContents];
- [clipboard setString:savedText forType:NSPasteboardTypeString];
- }
- bool CocoaWindow::setCursorVisibility(bool visible) {
- if (visible) {
- [NSCursor unhide];
- } else {
- [NSCursor hide];
- }
- return true;
- }
- bool CocoaWindow::setCursorPosition(int x, int y) {
- // Get the offset from window pixels to screen pixels.
- NSWindow *window = [this->view window];
- NSRect viewBounds = [this->view bounds];
- NSRect viewRectInScreenCoords = [window convertRectToScreen:viewBounds];
- CGPoint windowToScreenOffset = CGPointMake(
- viewRectInScreenCoords.origin.x,
- [[NSScreen mainScreen] frame].size.height - (viewRectInScreenCoords.origin.y + viewRectInScreenCoords.size.height)
- );
- // Set the cursor position using screen coordinates.
- CGWarpMouseCursorPosition(CGPointMake(windowToScreenOffset.x + x, windowToScreenOffset.y + y));
- // Prevent stalling after the move.
- CGAssociateMouseAndMouseCursorPosition(true);
- // TODO: How can the mouse move event be sent in the correct order in case of already having move events waiting?
- this->receivedMouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, dsr::IVector2D(x, y));
- return true;
- }
- void CocoaWindow::setFullScreen(bool enabled) {
- int newWindowState = enabled ? 2 : 1;
- if (newWindowState != this->windowState) {
- if (enabled) {
- // Entering full screen from the start or for an existing window.
- [this->view enterFullScreenMode:[NSScreen mainScreen] withOptions:nil];
- this->windowState = 2;
- } else {
- if (this->windowState == 2) {
- // Leaving full screen instead of initializing a new window.
- [this->view exitFullScreenModeWithOptions:nil];
- }
- this->windowState = 1;
- }
- }
- }
- void CocoaWindow::updateTitle() {
- // Get the title and convert it into the native string type.
- NSString *windowTitle = dsrToNsString(this->title);
- // Set the window title.
- [window setTitle:windowTitle];
- }
- CocoaWindow::CocoaWindow(const dsr::String& title, int width, int height) {
- if (!applicationInitialized) {
- application = [NSApplication sharedApplication];
- [application setActivationPolicy:NSApplicationActivationPolicyRegular];
- [application setPresentationOptions:NSApplicationPresentationDefault];
- [application activateIgnoringOtherApps:YES];
- applicationInitialized = true;
- }
- bool fullScreen = false;
- if (width < 1 || height < 1) {
- fullScreen = true;
- width = 400;
- height = 300;
- }
- // Create a window
- @autoreleasepool {
- this->window = [[NSWindow alloc]
- initWithContentRect:NSMakeRect(0, 0, width, height)
- styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
- backing: NSBackingStoreBuffered
- defer: NO];
- }
- NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
- [zoomButton setEnabled:NO];
- // Get the view
- this->view = [window contentView];
- this->setFullScreen(fullScreen);
- // Create a color space
- this->colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
- if (this->colorSpace == nullptr) {
- dsr::throwError(U"Could not create a Core Graphics color space!\n");
- }
- // Set the title
- this->setTitle(title);
- // Allocate a canvas
- this->resizeCanvas(width, height);
- // Show the window.
- [window center];
- [window makeKeyAndOrderFront:nil];
- [window makeFirstResponder:nil];
- }
- static dsr::DsrKey getDsrKey(uint16_t keyCode) {
- dsr::DsrKey result = dsr::DsrKey_Unhandled;
- if (keyCode == 53) {
- result = dsr::DsrKey_Escape;
- } else if (keyCode == 122) {
- result = dsr::DsrKey_F1;
- } else if (keyCode == 120) {
- result = dsr::DsrKey_F2;
- } else if (keyCode == 99) {
- result = dsr::DsrKey_F3;
- } else if (keyCode == 118) {
- result = dsr::DsrKey_F4;
- } else if (keyCode == 96) {
- result = dsr::DsrKey_F5;
- } else if (keyCode == 97) {
- result = dsr::DsrKey_F6;
- } else if (keyCode == 98) {
- result = dsr::DsrKey_F7;
- } else if (keyCode == 100) {
- result = dsr::DsrKey_F8;
- } else if (keyCode == 101) {
- result = dsr::DsrKey_F9;
- } else if (keyCode == 109) {
- result = dsr::DsrKey_F10;
- } else if (keyCode == 103) {
- result = dsr::DsrKey_F11;
- } else if (keyCode == 111) {
- result = dsr::DsrKey_F12;
- } else if (keyCode == 105) { // F13 replaces the pause key that does not even have a keycode on MacOS.
- result = dsr::DsrKey_Pause;
- } else if (keyCode == 49) {
- result = dsr::DsrKey_Space;
- } else if (keyCode == 48) {
- result = dsr::DsrKey_Tab;
- } else if (keyCode == 36) {
- result = dsr::DsrKey_Return;
- } else if (keyCode == 51) {
- result = dsr::DsrKey_BackSpace;
- } else if (keyCode == 117) {
- result = dsr::DsrKey_Delete;
- } else if (keyCode == 123) {
- result = dsr::DsrKey_LeftArrow;
- } else if (keyCode == 124) {
- result = dsr::DsrKey_RightArrow;
- } else if (keyCode == 126) {
- result = dsr::DsrKey_UpArrow;
- } else if (keyCode == 125) {
- result = dsr::DsrKey_DownArrow;
- } else if (keyCode == 29) {
- result = dsr::DsrKey_0;
- } else if (keyCode == 18) {
- result = dsr::DsrKey_1;
- } else if (keyCode == 19) {
- result = dsr::DsrKey_2;
- } else if (keyCode == 20) {
- result = dsr::DsrKey_3;
- } else if (keyCode == 21) {
- result = dsr::DsrKey_4;
- } else if (keyCode == 23) {
- result = dsr::DsrKey_5;
- } else if (keyCode == 22) {
- result = dsr::DsrKey_6;
- } else if (keyCode == 26) {
- result = dsr::DsrKey_7;
- } else if (keyCode == 28) {
- result = dsr::DsrKey_8;
- } else if (keyCode == 25) {
- result = dsr::DsrKey_9;
- } else if (keyCode == 0) {
- result = dsr::DsrKey_A;
- } else if (keyCode == 11) {
- result = dsr::DsrKey_B;
- } else if (keyCode == 8) {
- result = dsr::DsrKey_C;
- } else if (keyCode == 2) {
- result = dsr::DsrKey_D;
- } else if (keyCode == 14) {
- result = dsr::DsrKey_E;
- } else if (keyCode == 3) {
- result = dsr::DsrKey_F;
- } else if (keyCode == 5) {
- result = dsr::DsrKey_G;
- } else if (keyCode == 4) {
- result = dsr::DsrKey_H;
- } else if (keyCode == 34) {
- result = dsr::DsrKey_I;
- } else if (keyCode == 38) {
- result = dsr::DsrKey_J;
- } else if (keyCode == 40) {
- result = dsr::DsrKey_K;
- } else if (keyCode == 37) {
- result = dsr::DsrKey_L;
- } else if (keyCode == 46) {
- result = dsr::DsrKey_M;
- } else if (keyCode == 45) {
- result = dsr::DsrKey_N;
- } else if (keyCode == 31) {
- result = dsr::DsrKey_O;
- } else if (keyCode == 35) {
- result = dsr::DsrKey_P;
- } else if (keyCode == 12) {
- result = dsr::DsrKey_Q;
- } else if (keyCode == 15) {
- result = dsr::DsrKey_R;
- } else if (keyCode == 1) {
- result = dsr::DsrKey_S;
- } else if (keyCode == 17) {
- result = dsr::DsrKey_T;
- } else if (keyCode == 32) {
- result = dsr::DsrKey_U;
- } else if (keyCode == 9) {
- result = dsr::DsrKey_V;
- } else if (keyCode == 13) {
- result = dsr::DsrKey_W;
- } else if (keyCode == 7) {
- result = dsr::DsrKey_X;
- } else if (keyCode == 16) {
- result = dsr::DsrKey_Y;
- } else if (keyCode == 6) {
- result = dsr::DsrKey_Z;
- } else if (keyCode == 114 || keyCode == 106) { // Insert on PC keyboard or F16 on Mac Keyboard.
- result = dsr::DsrKey_Insert;
- } else if (keyCode == 115) {
- result = dsr::DsrKey_Home;
- } else if (keyCode == 119) {
- result = dsr::DsrKey_End;
- } else if (keyCode == 116) {
- result = dsr::DsrKey_PageUp;
- } else if (keyCode == 121) {
- result = dsr::DsrKey_PageDown;
- }
- return result;
- }
- void CocoaWindow::prefetchEvents() {
- @autoreleasepool {
- CGFloat canvasWidth = NSWidth(this->view.bounds);
- CGFloat canvasHeight = NSHeight(this->view.bounds);
- // Process events
- while (true) {
- NSEvent *event = [application nextEventMatchingMask:NSEventMaskAny untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES];
- if (event == nullptr) break;
- if ([event type] == NSEventTypeLeftMouseDown
- || [event type] == NSEventTypeLeftMouseDragged
- || [event type] == NSEventTypeLeftMouseUp
- || [event type] == NSEventTypeRightMouseDown
- || [event type] == NSEventTypeRightMouseDragged
- || [event type] == NSEventTypeRightMouseUp
- || [event type] == NSEventTypeOtherMouseDown
- || [event type] == NSEventTypeOtherMouseDragged
- || [event type] == NSEventTypeOtherMouseUp
- || [event type] == NSEventTypeMouseMoved
- || [event type] == NSEventTypeMouseEntered
- || [event type] == NSEventTypeMouseExited
- || [event type] == NSEventTypeScrollWheel) {
- NSPoint point = [this->view convertPoint:[event locationInWindow] fromView:nil];
- // This nasty hack combines an old mouse event with a canvas size that may have changed since the mouse event was created.
- // TODO: Find a way to get the canvas height from when the mouse event was actually created, so that lagging while resizing a window can not place click events at the wrong coordiates.
- dsr::IVector2D mousePosition = dsr::IVector2D(int32_t(point.x), int32_t(canvasHeight - point.y));
- if ([event type] == NSEventTypeLeftMouseDown) {
- //dsr::printText(U"LeftMouseDown at ", mousePosition, U"\n");
- this->cursorInside = true; // In case that enter events are missing, any proof of being inside of the window should be used.
- if (this->pressedControl) {
- // In case that control is released before the click is done, remember that the left click is a right click.
- this->modifiedClick = 1;
- this->receivedMouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Right, mousePosition);
- } else if (this->pressedCommand) {
- // In case that control is released before the click is done, remember that the left click is a middle click.
- this->modifiedClick = 2;
- this->receivedMouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Middle, mousePosition);
- } else {
- // Assume that the user only has one left mouse button, so that the state can be reset on each new left click.
- this->modifiedClick = 0;
- this->receivedMouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Left, mousePosition);
- }
- } else if ([event type] == NSEventTypeLeftMouseDragged) {
- this->receivedMouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, mousePosition);
- } else if ([event type] == NSEventTypeLeftMouseUp) {
- if (this->modifiedClick == 1) {
- // If the last left click was a control click, then the release should be treated as releasing the right mouse button.
- this->receivedMouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Right, mousePosition);
- this->modifiedClick = 0;
- } else if (this->modifiedClick == 2) {
- // If the last left click was a command click, then the release should be treated as releasing the middle mouse button.
- this->receivedMouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Middle, mousePosition);
- this->modifiedClick = 0;
- } else {
- this->receivedMouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Left, mousePosition);
- }
- } else if ([event type] == NSEventTypeRightMouseDown) {
- this->cursorInside = true; // In case that enter events are missing, any proof of being inside of the window should be used.
- this->receivedMouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Right, mousePosition);
- } else if ([event type] == NSEventTypeRightMouseDragged) {
- this->receivedMouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, mousePosition);
- } else if ([event type] == NSEventTypeRightMouseUp) {
- this->receivedMouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Right, mousePosition);
- } else if ([event type] == NSEventTypeOtherMouseDown) {
- this->cursorInside = true; // In case that enter events are missing, any proof of being inside of the window should be used.
- this->receivedMouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Middle, mousePosition);
- } else if ([event type] == NSEventTypeOtherMouseDragged) {
- this->receivedMouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, mousePosition);
- } else if ([event type] == NSEventTypeOtherMouseUp) {
- this->receivedMouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Middle, mousePosition);
- } else if ([event type] == NSEventTypeMouseMoved) {
- // When not dragging, only allow move events inside of the view, to be consistent with other operating systems.
- if ((this->cursorInside || this->windowState == 2) && mousePosition.y >= 0) {
- this->receivedMouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, mousePosition);
- }
- } else if ([event type] == NSEventTypeMouseEntered) {
- // TODO: This hack assumes that the first entering event goes to our view, but it would be more robust to get the tracking number directly from view.
- if (this->trackingNumber == 0) this->trackingNumber = event.trackingNumber;
- // Only accept enter events to our view.
- if (event.trackingNumber == this->trackingNumber) {
- this->cursorInside = true;
- }
- } else if ([event type] == NSEventTypeMouseExited) {
- // Only accept exit events from our view.
- if (event.trackingNumber == this->trackingNumber) {
- this->cursorInside = false;
- }
- } else if ([event type] == NSEventTypeScrollWheel) {
- if (event.scrollingDeltaY > 0.0) {
- this->receivedMouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollUp, mousePosition);
- }
- if (event.scrollingDeltaY < 0.0) {
- this->receivedMouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollDown, mousePosition);
- }
- }
- [application sendEvent:event];
- } else if ([event type] == NSEventTypeKeyDown
- || [event type] == NSEventTypeKeyUp
- || [event type] == NSEventTypeFlagsChanged) {
- dsr::DsrKey code = getDsrKey(event.keyCode);
- if ([event type] == NSEventTypeKeyDown) {
- if (!(event.isARepeat)) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyDown, U'\0', code);
- }
- // Get typed characters
- if (event.characters != nullptr) {
- // Convert to a standard text format.
- const char *characters = [event.characters cStringUsingEncoding:NSUTF8StringEncoding];
- if (characters != nullptr) {
- // Convert to a DSR string.
- dsr::String dsrCharacters = dsr::string_dangerous_decodeFromData(characters, dsr::CharacterEncoding::BOM_UTF8);
- // Send one type event for each character.
- for (intptr_t c = 0; c < string_length(dsrCharacters); c++) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyType, dsrCharacters[c], code);
- }
- }
- }
- } else if ([event type] == NSEventTypeKeyUp) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyUp, U'\0', code);
- } else if ([event type] == NSEventTypeFlagsChanged) {
- NSEventModifierFlags newModifierFlags = [event modifierFlags];
- bool newControl = (newModifierFlags & NSEventModifierFlagControl) != 0u;
- bool newCommand = (newModifierFlags & NSEventModifierFlagCommand) != 0u;
- bool newControlCommand = (newModifierFlags & (NSEventModifierFlagControl | NSEventModifierFlagCommand)) != 0u;
- bool newShift = (newModifierFlags & NSEventModifierFlagShift) != 0u;
- bool newAltOption = (newModifierFlags & NSEventModifierFlagOption) != 0u;
- if (newControlCommand && !pressedControlCommand) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyDown, U'\0', dsr::DsrKey_Control);
- } else if (!newControlCommand && pressedControlCommand) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyUp, U'\0', dsr::DsrKey_Control);
- }
- if (newShift && !pressedShift) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyDown, U'\0', dsr::DsrKey_Shift);
- } else if (!newShift && pressedShift) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyUp, U'\0', dsr::DsrKey_Shift);
- }
- if (newAltOption && !pressedAltOption) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyDown, U'\0', dsr::DsrKey_Alt);
- } else if (!newAltOption && pressedAltOption) {
- this->receivedKeyboardEvent(dsr::KeyboardEventType::KeyUp, U'\0', dsr::DsrKey_Alt);
- }
- this->pressedControl = newControl;
- this->pressedCommand = newCommand;
- this->pressedControlCommand = newControlCommand;
- this->pressedShift = newShift;
- this->pressedAltOption = newAltOption;
- }
- // TODO: Make sure that this does not break anything important.
- // Supressing beeps by not forwarding key events to the system.
- } else {
- [application sendEvent:event];
- }
- [application updateWindows];
- }
- // Handle changes to the window.
- if (![window isMiniaturized]) {
- if ([window isVisible]) {
- // The window is still visible, so check if it needs to resize the canvas.
- int32_t wholeCanvasWidth = int32_t(canvasWidth);
- int32_t wholeCanvasHeight = int32_t(canvasHeight);
- if (this->windowWidth != wholeCanvasWidth || this->windowHeight != wholeCanvasHeight) {
- this->resizeCanvas(wholeCanvasWidth, wholeCanvasHeight);
- this->windowWidth = wholeCanvasWidth;
- this->windowHeight = wholeCanvasHeight;
- // Make a request to resize the canvas
- this->receivedWindowResize(wholeCanvasWidth, wholeCanvasHeight);
- }
- } else {
- // The window is no longer visible, so send a close event to the application.
- this->receivedWindowCloseEvent();
- }
- }
- }
- }
- static const dsr::PackOrderIndex MacOSPackOrder = dsr::PackOrderIndex::ABGR;
- void CocoaWindow::resizeCanvas(int width, int height) {
- for (int b = 0; b < bufferCount; b++) {
- if (image_exists(this->canvas[b])) {
- if (image_getWidth(this->canvas[b]) == width && image_getHeight(this->canvas[b]) == height) {
- // The canvas already has the requested resolution.
- return;
- } else {
- // Preserve the pre-existing image.
- dsr::AlignedImageRgbaU8 newImage = image_create_RgbaU8_native(width, height, MacOSPackOrder);
- dsr::draw_copy(newImage, this->canvas[b]);
- this->canvas[b] = newImage;
- }
- } else {
- // Allocate a new image.
- this->canvas[b] = image_create_RgbaU8_native(width, height, MacOSPackOrder);
- }
- }
- }
- CocoaWindow::~CocoaWindow() {
- if (this->colorSpace != nullptr) {
- CGColorSpaceRelease(this->colorSpace);
- }
- [this->window close];
- window = nullptr;
- }
- void CocoaWindow::showCanvas() {
- if (![window isMiniaturized]) {
- @autoreleasepool {
- this->drawIndex = (this->drawIndex + 1) % bufferCount;
- this->showIndex = (this->showIndex + 1) % bufferCount;
- this->prefetchEvents();
- int displayIndex = this->showIndex;
- if (this->view != nullptr) {
- // Get image dimensions.
- int32_t width = dsr::image_getWidth(this->canvas[displayIndex]);
- int32_t height = dsr::image_getHeight(this->canvas[displayIndex]);
- int32_t stride = dsr::image_getStride(this->canvas[displayIndex]);
- // Make a deep clone of the finished image before it gets overwritten by another frame.
- this->delayedCanvas = dsr::buffer_clone(this->canvas[displayIndex].impl_buffer);
- uint8_t *pixelData = dsr::buffer_dangerous_getUnsafeData(this->delayedCanvas);
- CGDataProvider *provider = CGDataProviderCreateWithData(nullptr, pixelData, stride * height, nullptr);
- CGImage *image = CGImageCreate(width, height, 8, 32, stride, this->colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast, provider, nullptr, false, kCGRenderingIntentDefault);
- CGDataProviderRelease(provider);
- if (image == nullptr) {
- dsr::throwError(U"Could not create a Core Graphics image!\n");
- return;
- }
- this->view.wantsLayer = YES;
- this->view.layer.contents = (__bridge id)image;
- CGImageRelease(image);
- }
- }
- }
- }
- dsr::Handle<dsr::BackendWindow> createBackendWindow(const dsr::String& title, int width, int height) {
- return dsr::handle_create<CocoaWindow>(title, width, height);
- }
|