diff --git a/.github/workflows/check-single-header.yml b/.github/workflows/check-single-header.yml index b7c7b9c..96f2e09 100644 --- a/.github/workflows/check-single-header.yml +++ b/.github/workflows/check-single-header.yml @@ -24,7 +24,7 @@ jobs: - name: Build and run single header amalgamation tool (olcGimmeHead) run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + cmake -S dev -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --config=Release - name: Check that both headers are identical diff --git a/README.md b/README.md index 1b3e2f9..f0702b7 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,8 @@ Header file should now be located at `build/olcPixelGameEngine3.h`. ### Available CMake Configuration Flags -* ``-DBUILD_EXAMPLES=ON`` - builds examples, will be located at `build/examples` +* ``-DBUILD_EXAMPLES=ON`` - builds examples, will be located at `build/dev/examples` +* ``-DBUILD_EXTENSIONS=ON`` - builds examples, will be located at `build/dev/extensions` * ``-DBUILD_WAYLAND=ON`` - if examples are built, build them using wayland. Linux ONLY! * ``-DUSE_STB=ON`` - build using stb_image for the image loader (See section on STB Image) @@ -138,13 +139,13 @@ cd examples ### Using GCC ```bash -g++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lpthread +g++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lXi -lpthread ``` ### Using Clang ```bash -clang++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lpthread +clang++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lXi -lpthread ``` ### Using Emscripten diff --git a/dev/CMakeLists.txt b/dev/CMakeLists.txt index 4895ce0..61704c9 100644 --- a/dev/CMakeLists.txt +++ b/dev/CMakeLists.txt @@ -45,7 +45,8 @@ else() endif() add_custom_target(CopyHeader - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/olcPixelGameEngine3.h ${REPO_ROOT_DIR}/olcPixelGameEngine3.h + BYPRODUCTS ${REPO_ROOT_DIR}/olcPixelGameEngine3.h + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/olcPixelGameEngine3.h ${REPO_ROOT_DIR}/olcPixelGameEngine3.h DEPENDS olcPixelGameEngine3.h ) diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index fcff341..ab6ff8e 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -176,6 +176,7 @@ + @@ -276,6 +277,7 @@ + true true diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 03573c3..04da150 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -198,6 +198,9 @@ Hosts\wxWidgets Specific + + Extensions\MiniAudio + @@ -278,6 +281,9 @@ Hosts\wxWidgets Specific + + Extensions\MiniAudio + diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index cdad635..a0e3ce7 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -147,6 +147,7 @@ static constexpr const char* kSetContentViewSel = "setContentVie static constexpr const char* kContentViewSel = "contentView"; static constexpr const char* kBoundsSel = "bounds"; static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru +static constexpr const char* kConvertRectFromBackingSel = "convertRectFromBacking:"; // Thank you - Mr B the Ultimate Teacher!!! static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; @@ -330,6 +331,7 @@ namespace ObjectiveCSEL { static SEL contentViewSel = nullptr; static SEL boundsSel = nullptr; static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru + static SEL convertRectFromBackingSel = nullptr; // Thank you - Mr B the Ultimate Teacher!!! static SEL convertPointToBackingSel = nullptr; static SEL convertPointFromBackingSel = nullptr; static SEL convertPointFromViewSel = nullptr; @@ -479,6 +481,7 @@ namespace ObjectiveCSEL { contentViewSel = sel_registerName(kContentViewSel); boundsSel = sel_registerName(kBoundsSel); convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru + convertRectFromBackingSel = sel_registerName(kConvertRectFromBackingSel); // Thank you - Mr B the Ultimate Teacher!!! convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); @@ -1365,8 +1368,8 @@ void view_mouseDown(id self, SEL _cmd, id event) { CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B + if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { MouseEventData data = extractMouseEventData(event); @@ -1397,8 +1400,8 @@ void view_mouseUp(id self, SEL _cmd, id event) { CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B + if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] { @@ -1429,8 +1432,8 @@ void view_mouseDragged(id self, SEL _cmd, id event) { CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B + if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { MouseEventData data = extractMouseEventData(event); @@ -1456,12 +1459,7 @@ void view_mouseDragged(id self, SEL _cmd, id event) { // Handle mouse movement events void view_mouseMoved(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // if it is a trackpad event, ignore it. - if (nPointerEventType > 2) return; - + // a Mouse move event and Stlus roximity Event have the same properties. NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); @@ -1476,12 +1474,7 @@ void view_mouseMoved(id self, SEL _cmd, id event) { // Handle right mouse down events void view_rightMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1492,11 +1485,6 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { void view_rightMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1506,11 +1494,6 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { // Handle right mouse drag events void view_rightMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { @@ -1523,11 +1506,6 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { void view_otherMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1538,11 +1516,6 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { void view_otherMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1551,11 +1524,6 @@ void view_otherMouseUp(id self, SEL _cmd, id event) { void view_otherMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { @@ -2100,7 +2068,17 @@ extern "C" { // Destroy the window void window_destroy(Window* self) { - (void)self; + + // Thanks Mr B! + if (self) { + self->acceptsInputEvents = NO; + if (gptrNSWindowEvents == self) { + gptrNSWindowEvents = NULL; + } + if (gptrWindowDelegate == self) { + gptrWindowDelegate = NULL; + } + } // NSWindow will be cleaned up by autorelease pool } @@ -2411,8 +2389,9 @@ extern "C" { // Make context current ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - // Update the view frame - NSRect newFrame = {0.0, 0.0, width, height}; + // Update the view frame Without folks like Mr B the Ultimate Teacher, this would have been tricky + NSRect pixelFrame = {0.0, 0.0, width, height}; + NSRect newFrame = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::convertRectFromBackingSel, pixelFrame); ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); // Force context to reshape diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index f3e19c4..86660a5 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -254,11 +254,27 @@ olc::Pixel olc::Draw::GetPixel(olc::Image& image, const olc::vf2d& pos, const ol return failcol; } +void olc::Draw::SetPixel(olc::Image& image, const olc::vf2d& pos, const olc::Pixel col) +{ + // Check if in bounds + olc::vf2d tpos = transformAffine.forwardRound(pos); + if (tpos.x >= 0 && tpos.y >= 0 && tpos.x < float(pTarget->Size().x) && tpos.y < float(pTarget->Size().y)) + { + PrepareImageForSW(image); + pTarget->Pixel(tpos) = col; + } +} + olc::Pixel olc::Draw::GetPixel(const olc::vf2d& pos, const olc::Pixel failcol) { return GetPixel(GetTarget(), pos, failcol); } +void olc::Draw::SetPixel(const olc::vf2d& pos, const olc::Pixel col) +{ + SetPixel(GetTarget(), pos, col); +} + olc::Pixel olc::Draw::GetRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel failcol) { if (pos.x >= 0 && pos.y >= 0 && pos.x < float(image.Size().x) && pos.y < float(image.Size().y)) @@ -270,22 +286,47 @@ olc::Pixel olc::Draw::GetRawPixel(olc::Image& image, const olc::vi2d& pos, const return failcol; } +void olc::Draw::SetRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel col) +{ + if (pos.x >= 0 && pos.y >= 0 && pos.x < float(image.Size().x) && pos.y < float(image.Size().y)) + { + PrepareImageForSW(image); + image.Pixel(pos) = col; + } +} + olc::Pixel olc::Draw::GetRawPixel(const olc::vi2d& pos, const olc::Pixel failcol) { return GetRawPixel(GetTarget(), pos, failcol); } +void olc::Draw::SetRawPixel(const olc::vi2d& pos, const olc::Pixel col) +{ + SetRawPixel(GetTarget(), pos, col); +} + olc::Pixel olc::Draw::GetUnsafeRawPixel(olc::Image& image, const olc::vi2d& pos) { PrepareImageForSW(image); return image.Pixel(pos); } +void olc::Draw::SetUnsafeRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel col) +{ + PrepareImageForSW(image); + image.Pixel(pos) = col; +} + olc::Pixel olc::Draw::GetUnsafeRawPixel(const olc::vi2d& pos) { return GetUnsafeRawPixel(GetTarget(), pos); } +void olc::Draw::SetUnsafeRawPixel(const olc::vi2d& pos, const olc::Pixel col) +{ + SetUnsafeRawPixel(GetTarget(), pos, col); +} + void olc::Draw::Clear(const olc::Pixel& col) { PrepareTargetForHW(); diff --git a/dev/src/draw.h b/dev/src/draw.h index dcdac67..e082107 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -233,37 +233,72 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Lower-Level Pixel Manipulation + // Read a pixel from an image (guarantees fresh) olc::Pixel GetPixel( olc::Image& image, const olc::vf2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write a pixel to an image + void SetPixel( + olc::Image& image, + const olc::vf2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read a pixel from target image (guarantees fresh) olc::Pixel GetPixel( const olc::vf2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write a pixel to target image + void SetPixel( + const olc::vf2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from an image (guarantees fresh) olc::Pixel GetRawPixel( olc::Image& image, const olc::vi2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write an untransformed pixel to an image + void SetRawPixel( + olc::Image& image, + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from target image (guarantees fresh) olc::Pixel GetRawPixel( const olc::vi2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write an untransformed pixel to target image + void SetRawPixel( + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from a target image with no bounds checking (gurantees fresh) olc::Pixel GetUnsafeRawPixel( olc::Image& image, const olc::vi2d& pos); + // Write an untransformed pixel to a target image with no bounds checking + void SetUnsafeRawPixel( + olc::Image& image, + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from target image with no bounds checking (gurantees fresh) olc::Pixel GetUnsafeRawPixel( const olc::vi2d& pos); + // Write an untransformed pixel to target image with no bounds checking + void SetUnsafeRawPixel( + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Clear entire draw target to specific colour void Clear(const olc::Pixel& col); diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index b475999..a79b86a 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -212,16 +212,15 @@ namespace olc::host { bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) { olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorPosition(vPos.x, vPos.y); - }); + vPositionMouse = vPos; + this->vPendingMainThreadTasks.push_back(SET_MOUSE_POSITION); return false; } bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ + dispatch_async(dispatch_get_main_queue(), ^{ pMacOSWindow->setCursorVisibility(bVisible); }); return true; @@ -555,6 +554,13 @@ namespace olc::host { pPGEwindow->olc_OnFocus(false); break; } + case SET_MOUSE_POSITION: + { + // Set mouse position on main thread + pMacOSWindow->setCursorPosition(vPositionMouse.x, vPositionMouse.y); + res = false; // No need to skip frame + break; + } case NONE: default: { diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 303ffd1..3d510bf 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -109,7 +109,8 @@ namespace olc BECOME_ACTIVE, RESIGN_ACTIVE, MINIMIZE_WINDOW, - DEMINIMIZE_WINDOW + DEMINIMIZE_WINDOW, + SET_MOUSE_POSITION }; // Internal Mac OS functions @@ -149,6 +150,8 @@ namespace olc void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start uint16_t ConvertPGE2WindowStyle(); + + olc::vi2d vPositionMouse = {0, 0}; // Stores the desired mouse position for SET_MOUSE_POSITION task }; diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 831cef3..84300f0 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -219,7 +219,7 @@ namespace olc::host { pPrimaryPGE->OnPreContextStart(); - // Create system thread - handles gpu context + // Create system thread - handles gpu context std::thread threadSystem([this]() { // Notify start of system thread @@ -247,11 +247,16 @@ namespace olc::host } }); + pollfd decor_wl_fd {.fd = libdecor_get_fd(decor_context), .events = POLLIN}; + bool keep_running = true; while(systemActive && keep_running) { if(decor_context) { - std::lock_guard l{decor_mutex}; - keep_running = libdecor_dispatch(decor_context, 0) >= 0; + poll(&decor_wl_fd, 1, -1); + { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; + } } } diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index acbd1ea..d5f4100 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "libdecor.h" diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index cb6866c..750f669 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -131,6 +131,10 @@ namespace olc::host return nullptr; }; + // Create a pollfd that we will use later to wait for events to appear on the queue + // preventing the event loop from becoming a busy loop + pollfd x11_connection_fd {.fd = ConnectionNumber(olc_Display), .events = POLLIN}; + X11::XEvent xev; while(systemActive){ while (XPending(olc_Display)) @@ -297,6 +301,9 @@ namespace olc::host } } } + + // Wait until an event appears on the x11 event queue file descriptor + poll(&x11_connection_fd, 1, -1); } systemActive = false; diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 832f268..f5604bd 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -16,6 +16,7 @@ //! START DECLARATION #include +#include namespace X11 { #include diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index c17379f..c694782 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -165,7 +165,11 @@ +#if OLC_HOST == OLC_HOST_MACOS +//! GRAB api_macos.h DECLARATION +//! GRAB api_macos_wrapper.hpp DECLARATION +#endif #if !defined(OLC_USE_WXWIDGETS) @@ -182,10 +186,6 @@ #endif #if OLC_HOST == OLC_HOST_MACOS -//! GRAB api_macos.h DECLARATION - -//! GRAB api_macos_wrapper.hpp DECLARATION - //! GRAB host_apple_macos.h MACOS_CONFIG //! GRAB host_apple_macos.h DECLARATION @@ -260,6 +260,10 @@ #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) +#if OLC_HOST == OLC_HOST_MACOS +//! GRAB api_macos.cpp IMPLEMENTATION +#endif + #if !defined(OLC_USE_WXWIDGETS) #if OLC_HOST == OLC_HOST_NONE @@ -272,8 +276,6 @@ #if OLC_HOST == OLC_HOST_MACOS //! GRAB host_apple_macos.cpp IMPLEMENTATION - -//! GRAB api_macos.cpp IMPLEMENTATION #endif #if OLC_HOST == OLC_HOST_LINUX_X11 diff --git a/dev/src/vector2d.h b/dev/src/vector2d.h index 5b6a88b..90d71cc 100644 --- a/dev/src/vector2d.h +++ b/dev/src/vector2d.h @@ -195,6 +195,26 @@ namespace olc { return { static_cast(this->x), static_cast(this->y) }; } + + template + auto& get() & { + return std::get(xy); + } + + template + const auto& get() const & { + return std::get(xy); + } + + template + auto&& get() && { + return std::get(xy); + } + + template + const auto& get() const && { + return std::get(xy); + } }; // Multiplication operator overloads between vectors and scalars, and vectors and vectors @@ -356,6 +376,16 @@ namespace olc typedef v_2d vf2d; typedef v_2d vd2d; } + +namespace std { + template + struct tuple_size> : std::integral_constant {}; + + + template + struct tuple_element> : tuple_element> {}; +} + #define PGE_VECTOR2D_DECLARED 1 #endif //! END DECLARATION \ No newline at end of file diff --git a/dev/src/vector4d.h b/dev/src/vector4d.h index 3661b50..939d1f1 100644 --- a/dev/src/vector4d.h +++ b/dev/src/vector4d.h @@ -183,6 +183,26 @@ namespace olc { return { static_cast(this->x), static_cast(this->y), static_cast(this->z), static_cast(this->w) }; } + + template + auto& get() & { + return std::get(xyzw); + } + + template + const auto& get() const & { + return std::get(xyzw); + } + + template + auto&& get() && { + return std::get(xyzw); + } + + template + const auto& get() const && { + return std::get(xyzw); + } }; // Multiplication operator overloads between vectors and scalars, and vectors and vectors @@ -310,6 +330,13 @@ namespace olc return lhs; } + template + inline constexpr auto operator -= (v_4d& lhs, const v_4d& rhs) + { + lhs = lhs - rhs; + return lhs; + } + // Greater/Less-Than Operator overloads - mathematically useless, but handy for "sorted" container storage template inline constexpr bool operator < (const v_4d& lhs, const v_4d& rhs) @@ -343,6 +370,16 @@ namespace olc typedef v_4d vf4d; typedef v_4d vd4d; } + +namespace std { + template + struct tuple_size> : std::integral_constant {}; + + + template + struct tuple_element> : tuple_element> {}; +} + #define PGE_VECTOR4D_DECLARED 1 #endif //! END DECLARATION \ No newline at end of file diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index f0bbb81..b9f4874 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -84,7 +84,6 @@ 460495F12E300E000047E223 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = ""; }; 460495F22E300E000047E223 /* window.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = window.cpp; sourceTree = ""; }; 460495FB2E300E0F0047E223 /* test_mh.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_mh.cpp; sourceTree = ""; }; - 4610522030593E6300C2BC3A /* olcPixelGameEngine3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = olcPixelGameEngine3.h; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/olcPixelGameEngine3.h; sourceTree = ""; }; 4610526330593E6D00C2BC3A /* olcPGE3_SH */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = olcPGE3_SH; sourceTree = BUILT_PRODUCTS_DIR; }; 4610526530593EE400C2BC3A /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../examples/assets; sourceTree = ""; }; 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_touch.h; sourceTree = ""; }; @@ -110,6 +109,7 @@ 46B1E5942E40F8B0009734EC /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; 46C8F5B72E7DA67900C11019 /* imload_macos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = imload_macos.h; sourceTree = ""; }; 46C8F5B82E7DA67900C11019 /* imload_macos.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = imload_macos.cpp; sourceTree = ""; }; + 46E03AD8305C0D3A003BFA1D /* olcPixelGameEngine3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = olcPixelGameEngine3.h; path = ../../olcPixelGameEngine3.h; sourceTree = ""; }; 46E85DF62E39135300B74FA3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 46E85DF82E39135B00B74FA3 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; @@ -124,7 +124,7 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 4610526430593E6D00C2BC3A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + 46E03B94305C0DC1003BFA1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( olcPGE3_AntiAliasing.cpp, @@ -162,9 +162,9 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 4610521930593E6300C2BC3A /* examples */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4610526430593E6D00C2BC3A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); name = examples; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/examples; sourceTree = ""; }; - 4610521F30593E6300C2BC3A /* extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = extensions; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/extensions; sourceTree = ""; }; - 4610522730593E6300C2BC3A /* utilities */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = utilities; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/utilities; sourceTree = ""; }; + 46E03B67305C0D9A003BFA1D /* examples */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (46E03B94305C0DC1003BFA1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); name = examples; path = ../../examples; sourceTree = ""; }; + 46E03B6E305C0D9A003BFA1D /* extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = extensions; path = ../../extensions; sourceTree = ""; }; + 46E03B75305C0D9A003BFA1D /* utilities */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = utilities; path = ../../utilities; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -213,11 +213,11 @@ 8998CE172E057F4E007DDD96 = { isa = PBXGroup; children = ( + 46E03B67305C0D9A003BFA1D /* examples */, + 46E03B6E305C0D9A003BFA1D /* extensions */, + 46E03B75305C0D9A003BFA1D /* utilities */, + 46E03AD8305C0D3A003BFA1D /* olcPixelGameEngine3.h */, 4610526530593EE400C2BC3A /* assets */, - 4610521930593E6300C2BC3A /* examples */, - 4610521F30593E6300C2BC3A /* extensions */, - 4610522030593E6300C2BC3A /* olcPixelGameEngine3.h */, - 4610522730593E6300C2BC3A /* utilities */, 8998CE322E057F92007DDD96 /* src */, 8998CE2A2E057F85007DDD96 /* tests */, 46E85DF52E39135300B74FA3 /* Frameworks */, @@ -316,9 +316,9 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - 4610521930593E6300C2BC3A /* examples */, - 4610521F30593E6300C2BC3A /* extensions */, - 4610522730593E6300C2BC3A /* utilities */, + 46E03B67305C0D9A003BFA1D /* examples */, + 46E03B6E305C0D9A003BFA1D /* extensions */, + 46E03B75305C0D9A003BFA1D /* utilities */, ); name = olcPGE3_SH; productName = olcPGE3; @@ -337,11 +337,6 @@ ); dependencies = ( ); - fileSystemSynchronizedGroups = ( - 4610521930593E6300C2BC3A /* examples */, - 4610521F30593E6300C2BC3A /* extensions */, - 4610522730593E6300C2BC3A /* utilities */, - ); name = olcPGE3; productName = olcPGE3; productReference = 8998CE202E057F4E007DDD96 /* olcPGE3 */; diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index 1839d6d..ce127eb 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -1,7 +1,7 @@ /* olc::PixelGameEngine3 Example - olc::SanityCube!!! - Draws teh infamous olc::SanityCube using the hardware 3D rendering capabilities. + Draws the infamous olc::SanityCube using the hardware 3D rendering capabilities. Licenced under the OLC-3 License */ @@ -12,6 +12,8 @@ #define OLC_PGE3_APPLICATION #include "../olcPixelGameEngine3.h" +#include + // Example application demonstrating basic 3D rendering. This class // overrides the olc::PixelGameEngine base class by implementing // the OnUserCreate() and OnUserUpdate() functions @@ -123,7 +125,11 @@ class Example_3DCube : public olc::PixelGameEngine draw.Line({ 0,0,0 }, { 0, 1, 0 }, olc::Colour::GREEN); draw.Line({ 0,0,0 }, { 0, 0, 1 }, olc::Colour::BLUE); - draw.StringProp({ 4, 4 }, "+X: Right\n-X: Left\n+Y: Up\n-Y: Down\n+Z: Q\n-Z: A\nSPIN: Space", olc::Colour::BLACK); + // Decompose the quaternion to demonstrate the structured binding support + const auto [x, y, z, w] = vViewTranslate; + draw.String({ 4, 4 }, std::format("X:{: 4.1f} Y:{: 4.1f} Z:{: 4.1f} W:{: 4.1f}", x, y, z, w), olc::Colour::BLACK); + draw.StringProp({ 4, 14 }, "+X: Right\n-X: Left\n+Y: Up\n-Y: Down\n+Z: Q\n-Z: A\nSPIN: Space", olc::Colour::BLACK); + // Successful frame return true; diff --git a/examples/olcPGE3_WorldTransform.cpp b/examples/olcPGE3_WorldTransform.cpp index 699b6bc..bdf4748 100644 --- a/examples/olcPGE3_WorldTransform.cpp +++ b/examples/olcPGE3_WorldTransform.cpp @@ -132,6 +132,12 @@ class Example_WorldTransform: public olc::PixelGameEngine // Get Mouse in "transformed world" space olc::vf2d vMouseWorld = draw.ScreenToWorld(vMouseScreen); + // Unpack the mouse coordinate to x and y just for fun and to serve as a compilation test for unpacking v_2d objects + const auto& [x1, y1] = vMouseWorld; + auto& [x2, y2] = vMouseWorld; + auto [x3, y3] = vMouseWorld; + auto&& [x4, y4] = vMouseWorld; + // Draw Status draw.WorldReset(); draw.StringProp({ 8, 300 }, "Offset: Arrow Keys, Scale: Shift & Arrow Keys, Rotate: Q/A, SPACE: Reset"); diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 9409cd7..e30af16 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -1,2 +1,2 @@ -# add_subdirectory(miniaudio) +add_subdirectory(miniaudio) # add_subdirectory(splashscreen) diff --git a/extensions/miniaudio/CMakeLists.txt b/extensions/miniaudio/CMakeLists.txt new file mode 100644 index 0000000..f678d58 --- /dev/null +++ b/extensions/miniaudio/CMakeLists.txt @@ -0,0 +1,14 @@ +# Fetch 3rd Party miniaudio library +FetchContent_Declare( + miniaudio + GIT_REPOSITORY https://github.com/mackron/miniaudio.git + GIT_TAG 0.11.25 +) + +FetchContent_MakeAvailable(miniaudio) + +add_example(ext_demo_Miniaudio demo_Miniaudio.cpp) + +target_include_directories(ext_demo_Miniaudio PRIVATE ${miniaudio_SOURCE_DIR}) + +handle_assets(ext_demo_Miniaudio ${CMAKE_CURRENT_SOURCE_DIR}/assets) diff --git a/extensions/miniaudio/README.md b/extensions/miniaudio/README.md index e69de29..c1e1526 100644 --- a/extensions/miniaudio/README.md +++ b/extensions/miniaudio/README.md @@ -0,0 +1,424 @@ +# olcPGEX3_Miniaudio - Usage Documentation + +## Overview + +**olcPGEX3_Miniaudio** is an olcPixelGameEngine3 extension that provides a simple, abstracted interface to the powerful **miniaudio** library. It enables easy loading and playback of WAV and MP3 audio files with support for various audio effects and controls. + +### Key Features +- Load and play WAV and MP3 files +- Generate procedural waveforms (Sine, Square, Triangle, Sawtooth) +- Full playback control (play, pause, stop, toggle) +- Audio effects: volume, pan, pitch adjustment +- Audio seeking and cursor position tracking +- Multiple voice support for simultaneous playback +- Synthesizer callbacks for procedural audio generation +- Cross-platform support (including Emscripten) +- Custom audio data callbacks for advanced users +- Exposure to underlying miniaudio library, for advanced users +--- + +## Installation & Setup + +### 1. Acquire miniaudio + +Visit the [miniaudio website](https://miniaud.io/) to download the header file. Simply add it to your project with the rest of your source/header files. + +### 2. Include the Header after PGE3 +```cpp +#define OLC_PGEX3_MINIAUDIO +#include "olcPGEX3_Miniaudio.h" +``` + +### 3. Install the Extension in Your PGE Application +```cpp +class MyGame : public olc::PixelGameEngine +{ +public: + MyGame() + { + sAppName = "My Audio Game"; + if(!InstallSystemExtension(&audio)) + throw std::runtime_error("Failed to install olcPGEX3_miniaudio"); + } + +private: + olc::ext::Miniaudio::AudioEngine audio; +}; +``` + +See [olcPGE3_Miniaudio.cpp](olcPGE3_Miniaudio.cpp) for practical example usage! + +--- +## Core Components + +### olc::ext::Miniaudio::AudioEngine + +The main interface to the audio system. Manages all sounds, waveforms, and the underlying audio device. + +#### Configuration + +Configure the audio engine before creation in your constructor: + +```cpp +olc::ext::Miniaudio::AudioEngine::Config config; +config.DeviceChannels = 2; // Mono (1) or Stereo (2) +config.DeviceSampleRate = 48000; // Sample rate in Hz +config.DeviceFormat = ma_format_f32; // Audio format (32-bit float) +config.BackgroundPlay = true; // Play when window unfocused +config.Verbose = false; // Enable verbose logging + +audio.Configure(config); +``` + +#### Background Playback + +Control whether audio plays when the window loses focus: + +```cpp +audio.EnableBackgroundPlayback(); // Continue playing in background +audio.DisableBackgroundPlayback(); // Stop when window loses focus +``` + +#### Accessing Low-Level Objects + +For advanced users who need direct access to miniaudio objects: + +```cpp +ma_engine& engine = audio.GetEngine(); +ma_device& device = audio.GetDevice(); +ma_resource_manager& rm = audio.GetResourceManager(); + +int channels = audio.GetDeviceChannels(); +int sampleRate = audio.GetDeviceSampleRate(); +ma_format format = audio.GetDeviceFormat(); +``` + +> **Note:** In the example above `audio` is the instance of `olc::ext::Miniaudio::AudioEngine` + +--- + +## olc::ext::Miniaudio::Sound + +Represents a playable audio resource that can be loaded from file or memory. + +### Loading Sounds + +#### From File +```cpp +olc::ext::Miniaudio::Sound mySound; + +// Load from disk +audio.CreateSoundFromFile(mySound, "path/to/sound.mp3"); // could also be .wav +``` + +#### From Memory +```cpp +// Load from memory buffer +std::vector audioData = /* ... load file data ... */; +audio.CreateSoundFromMemory(mySound, audioData.data(), audioData.size()); + +// Or pass vector directly +audio.CreateSoundFromMemory(mySound, audioData); +``` +> **Note:** In the example above `audio` is the instance of `olc::ext::Miniaudio::AudioEngine` + +### Playback Control + +#### Play +```cpp +// Play once +mySound.Play(); + +// Play with looping +mySound.Play(true); // Loops continuously +``` + +#### Stop +```cpp +// Stop and rewind to beginning +mySound.Stop(); +``` + +#### Pause +```cpp +// Pause without changing position +mySound.Pause(); +``` + +#### Toggle +```cpp +// Play if paused, pause if playing +mySound.Toggle(); +``` + +### Seeking (Cursor Control) + +#### Seek to Position +```cpp +// Seek to specific time (milliseconds) +mySound.Seek(5000); // Go to 5 seconds + +// Seek to position (0.0 = start, 1.0 = end) +mySound.Seek(0.5f); // Go to middle of sound +``` + +#### Forward/Rewind +```cpp +// Move forward by 2 seconds +mySound.Forward(2000); + +// Move backward by 1 second +mySound.Rewind(1000); +``` + +### Audio Effects + +#### Volume Control +```cpp +// Set volume (0.0 = mute, 1.0 = full volume) +mySound.SetVolume(0.5f); // 50% volume +mySound.SetVolume(0.0f); // Muted +mySound.SetVolume(1.0f); // Full volume +``` + +#### Panning +```cpp +// Set pan (-1.0 = left, 0.0 = center, 1.0 = right) +mySound.SetPan(-1.0f); // Full left +mySound.SetPan(0.0f); // Center +mySound.SetPan(1.0f); // Full right +mySound.SetPan(-0.5f); // 50% left +``` + +#### Pitch +```cpp +// Set pitch (1.0 = normal, 0.5 = half speed, 2.0 = double speed) +mySound.SetPitch(1.0f); // Normal speed +mySound.SetPitch(0.5f); // Half speed (lower pitch) +mySound.SetPitch(2.0f); // Double speed (higher pitch) +mySound.SetPitch(1.5f); // 1.5x speed +``` + +### Information Queries + +#### Playback Status +```cpp +// Check if currently playing +if(mySound.IsPlaying()) +{ + // Sound is playing +} + +// Check if loaded successfully +if(mySound.IsLoaded()) +{ + // Sound was loaded successfully +} +``` + +#### Cursor Position +```cpp +// Get cursor position in milliseconds +ma_uint64 timeMs = mySound.GetCursor(); +std::cout << "Position: " << timeMs << " ms\n"; + +// Get cursor position as float (0.0 to 1.0) +float progress = mySound.GetCursorFloat(); +std::cout << "Progress: " << (progress * 100) << "%\n"; +``` + +### Advanced Usage + +#### Direct Access to Miniaudio Sound +```cpp +// Get pointer to underlying ma_sound for advanced features +ma_sound* maSound = mySound.GetMASound(); + +// Example: Set 3D position +ma_sound_set_position(maSound, 10.0f, 0.0f, -5.0f); +``` + +--- + +## olc::ext::Miniaudio::Waveform + +Generate and play procedural waveforms for synthesis and sound effects. + +### Supported Waveform Types + +```cpp +enum class Waveform::Type +{ + Sine, // Smooth, pure tone + Square, // Digital, buzzy tone + Triangle, // Bright, harmonic tone + Sawtooth // Bright, harsh tone +}; +``` + +### Creating Waveforms + +```cpp +olc::ext::Miniaudio::Waveform sineWave; + +audio.CreateWaveform( + sineWave, + olc::ext::Miniaudio::Waveform::Type::Sine, // Type + 0.1, // Amplitude (0.0-1.0) + 440.0 // Frequency in Hz (A4 note) +); +``` +> **Note:** In the example above `audio` is the instance of `olc::ext::Miniaudio::AudioEngine` +### Playback Control + +#### Play +```cpp +sineWave.Play(); +``` + +#### Stop +```cpp +sineWave.Stop(); +``` + +### Configuration + +#### Change Amplitude +```cpp +// Amplitude controls volume (0.0 = silent, 1.0 = loud) +sineWave.SetAmplitude(0.1); // 10% volume +sineWave.SetAmplitude(0.5); // 50% volume +``` + +#### Change Frequency +```cpp +// Frequency controls pitch in Hertz +sineWave.SetFrequency(440.0); // A4 note +sineWave.SetFrequency(880.0); // One octave higher +sineWave.SetFrequency(220.0); // One octave lower +``` + +#### Change Type +```cpp +// Set waveform types +sineWave.SetType(olc::ext::Miniaudio::Waveform::Type::Square); +sineWave.SetType(olc::ext::Miniaudio::Waveform::Type::Triangle); +sineWave.SetType(olc::ext::Miniaudio::Waveform::Type::Sawtooth); +``` + +### Information Queries + +```cpp +// Check if waveform is currently playing +if(sineWave.IsPlaying()) +{ + // Waveform is playing +} + +// Check if waveform was created successfully +if(sineWave.IsLoaded()) +{ + // Waveform is ready to use +} +``` + +## Advanced Features + +### Synthesizer Callback + +Provide custom synthesis function for procedural audio generation: +> **HELP:** I need a sound guy to help flesh out this example so that it's actually useful. I'm not versed enough to do something actually useful here. Thanks to whoever steps up! +> +> -Moros1138 + +```cpp +class MyGame : public olc::PixelGameEngine +{ +private: + olc::ext::Miniaudio::AudioEngine audio; + +public: + bool OnUserCreate() override + { + // Set custom synthesizer callback + audio.SetSynthCallback([this](float& fLeftChannel, float& fRightChannel, float fElapsedTime) + { + // Generate custom audio + // fElapsedTime is in seconds since last sample + float frequency = 440.0f; + float phase = fmodf(/* your phase */, 6.28f); + float sample = sinf(phase); + + fLeftChannel = sample * 0.1f; + fRightChannel = sample * 0.1f; + }); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Clear callback if no longer needed + if(keyboard.GetKey(olc::Key::C).bPressed) + { + audio.ClearSynthCallback(); + } + + return true; + } +}; +``` + +### Raw Audio Data Callback + +For maximum control, provide a custom data callback: + +```cpp +audio.SetDataCallback([this](float* pFramesOut, ma_uint64 frameCount) +{ + // pFramesOut: pointer to output buffer + // frameCount: number of frames to fill + + // Fill the buffer with your custom audio data + for(ma_uint64 i = 0; i < frameCount * 2; ++i) // *2 for stereo + { + pFramesOut[i] = /* your audio sample */; + } +}); + +// Clear callback later +audio.ClearDataCallback(); +``` +> **Note:** This example doesn't do anything useful, that's up to you! + +--- + +## Troubleshooting + +1. **Sound Won't Play?** + - Is the file path correct? + - Is the audio engine installed via `InstallSystemExtension(&audio)`? + - Use `IsLoaded()` to verify the sound loaded successfully + - Is the volume set to 0? + +2. **Audio Crackles or Pops** + - This often indicates a limiter is catching clipping. Reduce the amplitude of waveforms or volume of sounds. Ensure the total output doesn't exceed 1.0 in amplitude + +3. **Sounds Not Working on Emscripten/Web** + - Browser audio requires user interaction before playing. Ensure playback is triggered by a user event (click, key press) + +--- + +## Acknowledgements (From Moros1138) + +I'd like to give a special thanks to JavidX9 (aka OneLoneCoder), AniCator, JustinRichardsMusic, and everybody else who was a part of that audiophile conversation when I asked for help! Your patience and feedback made this project possible. Thank you! + +Also, for v2 of this extension, I'd also like to single out sigonasr2 (Dense Dance 2π) for the waveform functionality and for crafting the demos for them! While the waveform and synthesis has seen some change, overall the v3 version of this extension wouldn't have this functionality were it not for sigonasr2's contributions. Thank you Sig! + +## License + +Licensed under the OLC-3 License (OneLoneCoder Public License v3) + +Copyright 2023-2026 Moros Smith + +For full license details, see the header file or original source. diff --git a/extensions/miniaudio/assets/SampleA.wav b/extensions/miniaudio/assets/SampleA.wav new file mode 100644 index 0000000..e7cf99f Binary files /dev/null and b/extensions/miniaudio/assets/SampleA.wav differ diff --git a/extensions/miniaudio/assets/song1-license.txt b/extensions/miniaudio/assets/song1-license.txt new file mode 100644 index 0000000..a0b183d --- /dev/null +++ b/extensions/miniaudio/assets/song1-license.txt @@ -0,0 +1,3 @@ +Music: Joy Ride [Full version] by MusicLFiles +Free download: https://filmmusic.io/song/11627-joy-ride-full-version +Licensed under CC BY 4.0: https://filmmusic.io/standard-license diff --git a/extensions/miniaudio/assets/song1.mp3 b/extensions/miniaudio/assets/song1.mp3 new file mode 100644 index 0000000..0e2c777 Binary files /dev/null and b/extensions/miniaudio/assets/song1.mp3 differ diff --git a/extensions/miniaudio/demo_Miniaudio.cpp b/extensions/miniaudio/demo_Miniaudio.cpp new file mode 100644 index 0000000..fdce653 --- /dev/null +++ b/extensions/miniaudio/demo_Miniaudio.cpp @@ -0,0 +1,299 @@ +/* + olc::PixelGameEngine3 Example - olcPGEX3_Miniaudio + + Demonstrates using olcPGEX3_Miniaudio + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "olcPixelGameEngine3.h" + +#define OLC_PGEX3_MINIAUDIO +#include "olcPGEX3_Miniaudio.h" + +// Example application demonstrating the miniaudio extension. +// This class overrides the olc::PixelGameEngine base class +// by implementing the OnUserCreate() and OnUserUpdate() +// functions +class Example_Miniaudio : public olc::PixelGameEngine +{ +public: + Example_Miniaudio() + { + sAppName = "Example - olcPGEX3_Miniaudio"; + if(!InstallSystemExtension(&audio)) + throw std::runtime_error("Failed to install olcPGEX3_Miniaudio"); + } + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // load `assets/song1.mp3` into `song1` + audio.CreateSoundFromFile(song1, "assets/song1.mp3"); + + /** + * this is here to demonstrate how the adventurous can + * exploit other features of miniaudio that hasn't been + * abstracted by the PGEX + * + * Here you get a pointer to a next active voice, or the + * currently playing voice. + */ + ma_sound_set_position(song1.GetMASound(), 0.0f, 0.0f, 0.0f); + + // load `assets/SampleA.wav` into `sample` + audio.CreateSoundFromFile(sample, "assets/SampleA.wav"); + + // create all of the waveforms at 0.1 amplitude at 440Mhz (A4) + audio.CreateWaveform(sine, olc::ext::Miniaudio::Waveform::Type::Sine, 0.1, 440.0); + audio.CreateWaveform(square, olc::ext::Miniaudio::Waveform::Type::Square, 0.1, 440.0); + audio.CreateWaveform(triangle, olc::ext::Miniaudio::Waveform::Type::Triangle, 0.1, 440.0); + audio.CreateWaveform(sawtooth, olc::ext::Miniaudio::Waveform::Type::Sawtooth, 0.1, 440.0); + + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // toggle background playback + if(keyboard.GetKey(olc::Key::K1).bPressed) + { + backgroundPlay = !backgroundPlay; + if(backgroundPlay) + audio.EnableBackgroundPlayback(); + else + audio.DisableBackgroundPlayback(); + } + + // ensure all waveforms are stopped + // before we check for held keys + sine.Stop(); square.Stop(); + triangle.Stop(); sawtooth.Stop(); + + // play `sine` when held + if(keyboard.GetKey(olc::Key::K7).bHeld) + sine.Play(); + + // play `sqaure` when held + if(keyboard.GetKey(olc::Key::K8).bHeld) + square.Play(); + + // play `triangle` when held + if(keyboard.GetKey(olc::Key::K9).bHeld) + triangle.Play(); + + // play `sawtooth` when held + if(keyboard.GetKey(olc::Key::K0).bHeld) + sawtooth.Play(); + + // toggle `song1` playback/pause + if(keyboard.GetKey(olc::Key::SPACE).bPressed) + song1.Toggle(); + + // play `sample` + if(keyboard.GetKey(olc::Key::S).bPressed) + sample.Play(); + + // panning + if(keyboard.GetKey(olc::Key::MINUS).bHeld) + pan -= 1.0f * fElapsedTime; + + if(keyboard.GetKey(olc::Key::EQUALS).bHeld) + pan += 1.0f * fElapsedTime; + + // pitch + if(keyboard.GetKey(olc::Key::OEM_4).bHeld) + pitch -= 1.0f * fElapsedTime; + + if(keyboard.GetKey(olc::Key::OEM_6).bHeld) + pitch += 1.0f * fElapsedTime; + + // volume + if(keyboard.GetKey(olc::Key::DOWN).bHeld) + volume -= 1.0f * fElapsedTime; + + if(keyboard.GetKey(olc::Key::UP).bHeld) + volume += 1.0f * fElapsedTime; + + // distance + if(keyboard.GetKey(olc::Key::LEFT).bHeld) + distance -= 10.0f * fElapsedTime; + + if(keyboard.GetKey(olc::Key::RIGHT).bHeld) + distance += 10.0f * fElapsedTime; + + // Reset pan, pitch, and volume + if(keyboard.GetKey(olc::Key::R).bPressed) + { + pan = 0.0f; + pitch = 1.0f; + volume = 1.0f; + distance = 0.0f; + } + + // panning + pan = std::clamp(pan, -1.0f, 1.0f); + song1.SetPan(pan); + + // pitch + pitch = std::clamp(pitch, 0.0f, 2.0f); + song1.SetPitch(pitch); + + // volume + volume = std::clamp(volume, 0.0f, 1.0f); + song1.SetVolume(volume); + + // this is here to demosntrate how the adventurous can exploit other + // features of miniaudio that haven't been abstracted by the PGEX. + distance = std::clamp(distance, 0.0f, 100.0f); + ma_engine_listener_set_position(&audio.GetEngine(), 0, 0.0f, distance, 0.0f); + + // // get float cursor. 0.0f to 1.0f + cursorFloat = song1.GetCursorFloat(); + // // get cursor in milliseconds + cursorMillis = song1.GetCursor(); + + // Clear whole screen + draw.Clear(olc::Colour::BLACK); + + if(song1.IsPlaying()) + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + draw.String( + {10, 10}, + "--CONTROLS---------INFORMATION--------------\n" + "\n" + " - = | Pan <" + std::to_string(pan) + ">\n" + "\n" + " [ ] | Pitch <" + std::to_string(pitch) + ">\n" + "\n" + " Up Down | Volume <" + std::to_string(volume) + ">\n" + "\n" + " Left Right | Distance <" + std::to_string(distance) + ">\n" + "\n" + " S | One-Off Sounds\n" + "\n" + " K1 | BackgroundPlay <" + ((backgroundPlay) ? "On": "Off") + ">\n", + olc::Colour::WHITE + ); + + draw.String( + {480, 10}, + "Controls - Waveform""\n" + "-------------------" + "\n\n" + " K7 | Sine" + "\n\n" + " K8 | Square" + "\n\n" + " K9 | Triangle" + "\n\n" + " K0 | Sawtooth", + olc::Colour::WHITE + ); + + olc::vi2d center = ScreenSize() / 2; + olc::vf2d scale{2.8f, 2.8f}; + std::string demoMessage = "olcPGEX3_miniaudio Demo"; + + draw.String( + center - olc::vi2d{0, 32} - (draw.GetTextSize(demoMessage, false, scale) / 2), + demoMessage, + olc::Colour::WHITE, + scale + ); + + scale = {1.5f, 1.5f}; + demoMessage = "Hit To Toggle Playback"; + draw.String( + center - (draw.GetTextSize(demoMessage, false, scale) / 2), + demoMessage, + olc::Colour::WHITE, + scale + ); + + demoMessage = "Hit TO Reset Pan/Pitch/Volume"; + draw.String( + center + olc::vi2d{0, 24} - (draw.GetTextSize(demoMessage, false, scale) / 2), + demoMessage, + olc::Colour::WHITE, + scale + ); + + draw.String( + {10, 288}, + "Cursor (ms): " + std::to_string(cursorMillis) + "\n" + "Cursor (float): " + std::to_string(cursorFloat), + olc::Colour::WHITE + ); + + draw.String( + {10, 320}, + "Music: Joy Ride [Full version] by MusicLFiles\n" + "Free download: https://filmmusic.io/song/11627-joy-ride-full-version\n" + "Licensed under CC BY 4.0: https://filmmusic.io/standard-license\n", + olc::Colour::WHITE + ); + + // Draw The Playback Cursor (aka the position in the sound file) + draw.FilledRect({0, 350}, {ScreenSize().x * cursorFloat, 20}, olc::Colour::YELLOW); + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + return true; +#else + return !keyboard.GetKey(olc::Key::ESCAPE).bPressed; +#endif + } + + // put this here to have access to audio! + olc::ext::Miniaudio::AudioEngine audio; + +private: + // sounds + olc::ext::Miniaudio::Sound song1; + olc::ext::Miniaudio::Sound sample; + + olc::ext::Miniaudio::Waveform sine; + olc::ext::Miniaudio::Waveform square; + olc::ext::Miniaudio::Waveform triangle; + olc::ext::Miniaudio::Waveform sawtooth; + + // For demonstration controls, with sensible default values + float pan = 0.0f; + float pitch = 1.0f; + float volume = 1.0f; + float distance = 0.0f; + bool backgroundPlay = false; + ma_uint64 cursorMillis = 0ull; + float cursorFloat = 0.0f; + +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Miniaudio demo; + + // Create "screen" of 640x360 "pixels" + // with a pixel size of 2x2 actual screen pixels + PGEConfig config; + config.bVSync = false; + config.vPixelSize = { 2,2 }; + config.vScreenSize = { 640,360 }; + + if (demo.Construct(config)) + { + // Start the application + demo.Start(); + } + + return 0; +} diff --git a/extensions/miniaudio/olcPGEX3_Miniaudio.h b/extensions/miniaudio/olcPGEX3_Miniaudio.h new file mode 100644 index 0000000..7d1145c --- /dev/null +++ b/extensions/miniaudio/olcPGEX3_Miniaudio.h @@ -0,0 +1,1037 @@ +#pragma once +/* + olcPGEX3_MiniAudio.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine Extension | + | Miniaudio v3.0 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + This extension abstracts the very robust and powerful miniaudio + library. It provides simple loading and playback of WAV and MP3 + files. Because it's built on top of miniaudio, it requires next + to no addictional build configurations in order to be built + for cross-platform. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2023-2026 Moros Smith + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/@Moros1138 + GitHub: https://www.github.com/Moros1138 + Homepage: https://moros1138.com +*/ +#if defined(OLC_MULTIHEADER) +#include "olcpge3.h" +#else +#include "olcPixelGameEngine3.h" +#endif + +#ifdef OLC_PGEX3_MINIAUDIO +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio.h" +#endif + +#include +#include +#include +#include +#include +#include + +namespace olc::ext::Miniaudio +{ + class AudioEngine; + +#pragma region Sound + + class Sound + { + friend class AudioEngine; + public: + Sound() = default; + ~Sound(); + + public: // Loaders + // Create an image resource based on an image file asset on disk + bool CreateSoundFromFile(const std::string& sFileName, AudioEngine* pgex, uint32_t nNumVoices); + // Create an image resource based on an image file asset in memory + bool CreateSoundFromMemory(const uint8_t* data, const size_t bytes, AudioEngine* pgex, uint32_t nNumVoices); + // Create an image resource based on an image file asset in memory + bool CreateSoundFromMemory(const std::vector& data, AudioEngine* pgex, uint32_t nNumVoices); + private: + // performs the necessary unloading of the parts of this sound + void DestroySound(); + + private: // loader function common to all loaders + bool _internalSoundLoader(); + + + public: // playback routines + // plays a sound, can be set to loop + void Play(const bool looping = false); + // stops a sound, rewinds to beginning + void Stop(); + // pauses a sound, does not change position + void Pause(); + // toggle between play and pause + void Toggle(); + + public: // seeking controls + // seek to the provided position in the sound, by milliseconds + void Seek(const ma_uint64 milliseconds); + // seek to the provided position in the sound, by float 0.f is beginning, 1.0f is end + void Seek(const float& location); + // seek forward from current position by the provided time + void Forward(const ma_uint64 milliseconds); + // seek forward from current position by the provided time + void Rewind(const ma_uint64 milliseconds); + + public: // expression controls + // set volume of a sound, 0.0f is mute, 1.0f is full + void SetVolume(const float& volume); + // set pan of a sound, -1.0f is left, 1.0f is right, 0.0f is center + void SetPan(const float& pan); + // set pitch of a sound, 1.0f is normal + void SetPitch(const float& pitch); + + public: // misc information + // determine if a sound is playing + bool IsPlaying(); + // gets the current position in the sound, in milliseconds + ma_uint64 GetCursor(); + // gets the current position in the sound, as a float between 0.0f and 1.0f + float GetCursorFloat(); + // determine if this sound has been loaded successfully + bool IsLoaded() const; + public: // advanced usage + ma_sound* GetMASound(); + + private: + // pointer to the calling pgex + AudioEngine* m_pgex; + // contains the sound file in memory + std::vector m_buffer; + // the base sound from which the voices are copied + ma_sound m_base_sound{0}; + // the voices of this sound + std::vector m_voices; + // the id of this sound, used to derive virtual path + uint32_t m_id{0}; + // the virtual path for the resource manager, usually sound/ + std::string m_virtual_path{""}; + private: // sound status + // has the sound been loaded successfully + bool m_is_loaded{false}; + // is the sound in a paused state + bool m_is_paused{false}; + // number of voices + uint32_t m_num_voices{8}; + // track the current voice + uint32_t m_current_voice{0}; + + private: // info + // the length of this sound in pcm frames + ma_uint64 m_length_in_pcm_frames{0}; + // the length of this sound in seconds + float m_length_in_seconds{0.0f}; + + private: // globals, has an effect on all sounds + // id tracker allows us to ensure every sound has a unique id + static uint32_t m_id_tracker; + }; +#pragma endregion + +#pragma region Waveform + + + class Waveform + { + friend class AudioEngine; + + public: + enum class Type + { + Sine, + Square, + Triangle, + Sawtooth + }; + + public: // lifecycle + Waveform() = default; + + bool CreateWaveform(Waveform& waveform, const Type type, const double amplitude, const double frequency, AudioEngine* pgex); + private: + void DestroyWaveform(); + + public: // playback + void Play(); + void Stop(); + + public: // configuration + void SetAmplitude(const double amplitude); + void SetFrequency(const double frequency); + void SetType(const Type type); + + bool IsPlaying() const; + bool IsLoaded() const; + + ma_waveform& Get(); + + private: + bool m_is_loaded{false}; + float m_gain = 0.0f; // current gain + float m_target = 0.0f; // 0.0 = stopped, 1.0 = playing + float m_rampStep = 0.0f; // set once at init: 1.0f / (sampleRate * 0.010f) + + ma_waveform m_waveform; + ma_waveform_config m_waveform_config; + + AudioEngine* m_pgex{nullptr}; + }; + +#pragma endregion + +#pragma region Miniaudio + + class AudioEngine : public olc::PGESystemExtension + { + friend class Sound; + friend class Waveform; + + public: + struct Config + { + // device: number of channels. default(2) + int DeviceChannels{2}; + // device: format of the audio data. default(ma_format_f32) + ma_format DeviceFormat{ma_format_f32}; + // device: sample rate. default(48000) + int DeviceSampleRate{ma_standard_sample_rate_48000}; + // device: type of device. default(ma_device_type_playback) + ma_device_type DeviceType{ma_device_type_playback}; + // Miniaudio: is background play enabled? default(false) + bool BackgroundPlay{false}; + // Logging: is logging verbose? default(false) + bool Verbose{false}; + }; + + // configure the audio engine, see struct Config + void Configure(const Config& cfg); + // enable playback when the application window does not have focus. + void EnableBackgroundPlayback(); + // disable playback when the application window does not have focus. + void DisableBackgroundPlayback(); + + public: // Callback + static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + public: // Sounds + // Create a sound resource based on a sound file asset on disk + bool CreateSoundFromFile(Sound& sound, const std::string& sFileName, uint32_t nNumVoices = 8); + // Create a sound resource based on a sound file asset in memory + bool CreateSoundFromMemory(Sound& sound, const uint8_t* data, const size_t bytes, uint32_t nNumVoices = 8); + bool CreateSoundFromMemory(Sound& sound, const std::vector& data, uint32_t nNumVoices = 8); + void DestroySound(Sound& sound); + + public: // Waveforms + bool CreateWaveform(Waveform& waveform, const Waveform::Type type, const double amplitude, const double frequency); + void DestroyWaveform(Waveform& waveform); + public: // Synth + void SetSynthCallback(std::function callback); + void ClearSynthCallback(); + public: // Absolute POWER!! + void SetDataCallback(std::function callback); + void ClearDataCallback(); + + public: // getters + // + ma_device& GetDevice(); + ma_engine& GetEngine(); + ma_resource_manager& GetResourceManager(); + + int GetDeviceChannels() const; + ma_format GetDeviceFormat() const; + int GetDeviceSampleRate() const; + ma_device_type GetDeviceType() const; + + public: + AudioEngine(); + ~AudioEngine(); + + virtual bool OnInstall([[maybe_unused]] olc::PixelGameEngine* pge); + virtual bool OnBeforeUserCreate([[maybe_unused]] olc::PixelGameEngine* pge); + virtual bool OnAfterUserCreate([[maybe_unused]] olc::PixelGameEngine* pge); + virtual bool OnBeforeSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime); + virtual bool OnAfterSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime); + + private: + Config m_cfg; + ma_device m_device; + ma_device_config m_device_config; + + ma_resource_manager m_resource_manager; + ma_resource_manager_config m_resource_manager_config; + + ma_engine m_engine; + ma_engine_config m_engine_config; + std::vector m_waveform_buffer; + + // synth callback function + std::function m_synth_callback; + // data callback function + std::function m_data_callback; + + // track sounds and waveforms + std::vector m_sounds; + std::vector m_waveforms; + + bool m_is_initialized{false}; + olc::PixelGameEngine* m_pge{nullptr}; + }; +} +#pragma endregion + + +#if defined(OLC_PGEX3_MINIAUDIO) +#undef OLC_PGEX3_MINIAUDIO + +namespace olc::ext::Miniaudio +{ + +#pragma region Sound + + uint32_t Sound::m_id_tracker = 0; + + Sound::~Sound() + { + DestroySound(); + } + + bool Sound::CreateSoundFromFile(const std::string& sFileName, AudioEngine* pgex, uint32_t nNumVoices) + { +#if OLC_HOST == OLC_HOST_ANDROID + AAsset* pAsset = AAssetManager_open( + olc::host::Host_Android::androidApp->activity->assetManager, + sFileName.c_str(), + AASSET_MODE_BUFFER + ); + + if (pAsset == nullptr) + return false; + + off_t size = AAsset_getLength(pAsset); + m_buffer.resize(size); + AAsset_read(pAsset, m_buffer.data(), size); + AAsset_close(pAsset); +#else + std::ifstream f(sFileName, std::ios::binary | std::ios::ate); + if(f.fail()) + return false; + m_buffer.resize(f.tellg()); + f.seekg(0); + f.read(reinterpret_cast(m_buffer.data()), m_buffer.size()); + f.close(); +#endif + m_pgex = pgex; + m_num_voices = nNumVoices; + return _internalSoundLoader(); + } + + bool Sound::CreateSoundFromMemory(const uint8_t* data, const size_t bytes, AudioEngine* pgex, uint32_t nNumVoices) + { + if(!data) return false; + if(bytes <= 0) return false; + + m_buffer.resize(bytes); + uint8_t* result = reinterpret_cast(std::memcpy(m_buffer.data(), data, m_buffer.size())); + if(result == m_buffer.data()) + return false; + + m_pgex = pgex; + m_num_voices = nNumVoices; + return _internalSoundLoader(); + } + + bool Sound::CreateSoundFromMemory(const std::vector& data, AudioEngine* pgex, uint32_t nNumVoices) + { + if(data.size() <= 0) return false; + m_buffer = data; + m_pgex = pgex; + m_num_voices = nNumVoices; + return _internalSoundLoader(); + } + + void Sound::DestroySound() + { + if(!m_is_loaded) return; + + for(auto& v : m_voices) + { + if(ma_sound_is_playing(&v)) + ma_sound_stop(&v); + + ma_sound_uninit(&v); + } + m_voices.clear(); + ma_sound_uninit(&m_base_sound); + ma_resource_manager_unregister_data(ma_engine_get_resource_manager(&m_pgex->GetEngine()), m_virtual_path.c_str()); + m_is_loaded = false; + } + + bool Sound::_internalSoundLoader() + { + ma_result result; + m_id = ++m_id_tracker; + m_virtual_path = "sound/" + std::to_string(m_id); + + result = ma_resource_manager_register_encoded_data( + ma_engine_get_resource_manager(&m_pgex->GetEngine()), + m_virtual_path.c_str(), + m_buffer.data(), m_buffer.size() + ); + + if(result != MA_SUCCESS) + return false; + + ma_fence fence; + result = ma_fence_init(&fence); + + if(result != MA_SUCCESS) + { + ma_resource_manager_unregister_data(ma_engine_get_resource_manager(&m_pgex->GetEngine()), m_virtual_path.c_str()); + return false; + } + + result = ma_sound_init_from_file( + &m_pgex->GetEngine(), + m_virtual_path.c_str(), + MA_SOUND_FLAG_DECODE, + nullptr, + &fence, + &m_base_sound + ); + + if(result != MA_SUCCESS) + { + ma_resource_manager_unregister_data(ma_engine_get_resource_manager(&m_pgex->GetEngine()), m_virtual_path.c_str()); + return false; + } + + m_voices.resize(m_num_voices); + for(int i = 0; i < m_num_voices; ++i) + { + result = ma_sound_init_copy(&m_pgex->GetEngine(), &m_base_sound, 0, nullptr, &m_voices[i]); + if(result != MA_SUCCESS) + break; + } + + // if the last result out of that loop isn't success, we failed + if(result != MA_SUCCESS) + { + for(auto& v : m_voices) + ma_sound_uninit(&v); + ma_sound_uninit(&m_base_sound); + ma_resource_manager_unregister_data(ma_engine_get_resource_manager(&m_pgex->GetEngine()), m_virtual_path.c_str()); + return false; + } + + // wait here until the sound is fully loaded and dedoded + ma_fence_wait(&fence); + ma_fence_uninit(&fence); + + ma_sound_get_length_in_pcm_frames(&m_base_sound, &m_length_in_pcm_frames); + ma_sound_get_length_in_seconds(&m_base_sound, &m_length_in_seconds); + + m_is_loaded = true; + return true; + } + + // plays a sound, can be set to loop + void Sound::Play(const bool looping) + { + if(!m_is_paused) + m_current_voice = (m_current_voice + 1) % m_num_voices; + + ma_sound_set_looping(&m_voices[m_current_voice], looping); + ma_sound_seek_to_pcm_frame(&m_voices[m_current_voice], 0); + ma_sound_start(&m_voices[m_current_voice]); + m_is_paused = false; + } + + // stops a sound, rewinds to beginning + void Sound::Stop() + { + if(!ma_sound_is_playing(&m_voices[m_current_voice])) + return; + ma_sound_stop(&m_voices[m_current_voice]); + ma_sound_seek_to_pcm_frame(&m_voices[m_current_voice], 0); + } + + // pauses a sound, does not change position + void Sound::Pause() + { + if(!ma_sound_is_playing(&m_voices[m_current_voice])) + return; + + ma_sound_stop(&m_voices[m_current_voice]); + m_is_paused = true; + } + + // toggle between play and pause + void Sound::Toggle() + { + if(ma_sound_is_playing(&m_voices[m_current_voice])) + { + ma_sound_stop(&m_voices[m_current_voice]); + m_is_paused = true; + return; + } + + ma_sound_start(&m_voices[m_current_voice]); + m_is_paused = false; + } + + // seek to the provided position in the sound, by milliseconds + void Sound::Seek(const ma_uint64 milliseconds) + { + ma_uint64 frame_to_seek_to = (milliseconds * m_pgex->GetDeviceSampleRate()) / 1000; + ma_sound_seek_to_pcm_frame(&m_voices[m_current_voice], frame_to_seek_to); + } + + // seek to the provided position in the sound, by float 0.f is beginning, 1.0f is end + void Sound::Seek(const float& location) + { + ma_uint64 frame_to_seek_to = static_cast(m_length_in_pcm_frames * location); + ma_sound_seek_to_pcm_frame(&m_voices[m_current_voice], frame_to_seek_to); + } + + // seek forward from current position by the provided time + void Sound::Forward(const ma_uint64 milliseconds) + { + ma_uint64 frame_to_seek_to; + + // get the current position + ma_sound_get_cursor_in_pcm_frames(&m_voices[m_current_voice], &frame_to_seek_to); + + // calculate the step and add it to the current position + frame_to_seek_to += ((milliseconds * m_pgex->GetDeviceSampleRate()) / 1000); + + // seek to the new position + ma_sound_seek_to_pcm_frame(&m_voices[m_current_voice], frame_to_seek_to); + } + + // seek forward from current position by the provided time + void Sound::Rewind(const ma_uint64 milliseconds) + { + ma_uint64 frame_to_seek_to; + + // get the current position + ma_sound_get_cursor_in_pcm_frames(&m_voices[m_current_voice], &frame_to_seek_to); + + // calculate the step and add it to the current position + frame_to_seek_to -= ((milliseconds * m_pgex->GetDeviceSampleRate()) / 1000); + + // seek to the new position + ma_sound_seek_to_pcm_frame(&m_voices[m_current_voice], frame_to_seek_to); + } + + // set volume of a sound, 0.0f is mute, 1.0f is full + void Sound::SetVolume(const float& volume) + { + for(auto& v : m_voices) + { + ma_sound_set_volume(&v, std::clamp(volume, 0.0f, 1.0f)); + } + } + + // set pan of a sound, -1.0f is left, 1.0f is right, 0.0f is center + void Sound::SetPan(const float& pan) + { + for(auto& v : m_voices) + { + ma_sound_set_pan(&v, std::clamp(pan, -1.0f, 1.0f)); + } + } + + // set pitch of a sound, 1.0f is normal + void Sound::SetPitch(const float& pitch) + { + for(auto& v : m_voices) + { + ma_sound_set_pitch(&v, std::max({0.0f, pitch})); + } + } + + // determine if a sound is playing + bool Sound::IsPlaying() + { + for(auto& v : m_voices) + { + if(ma_sound_is_playing(&v)) + return true; + } + return false; + } + + // gets the current position in the sound, in milliseconds + ma_uint64 Sound::GetCursor() + { + ma_uint64 cursor; + ma_sound_get_cursor_in_pcm_frames(&m_voices[m_current_voice], &cursor); + return (cursor * 1000) / m_pgex->GetDeviceSampleRate(); + } + + // gets the current position in the sound, as a float between 0.0f and 1.0f + float Sound::GetCursorFloat() + { + float cursor; + ma_sound_get_cursor_in_seconds(&m_voices[m_current_voice], &cursor); + return cursor / m_length_in_seconds; + } + + // determine if this sound has been loaded successfully + bool Sound::IsLoaded() const + { + return m_is_loaded; + } + + ma_sound* Sound::GetMASound() + { + // realistically, one wouldn't call this unless it was loaded + if(!IsLoaded()) + return nullptr; + + // if we're not currently playing, get the pointer of the next voice + if(!IsPlaying()) + return &m_voices[(m_current_voice + 1) % m_num_voices]; + + // if we're playing, get the pointer of the current voice + return &m_voices[m_current_voice]; + } + +#pragma endregion + + +#pragma region Waveform + + bool Waveform::CreateWaveform(Waveform& waveform, const Type type, const double amplitude, const double frequency, AudioEngine* pgex) + { + m_pgex = pgex; + m_waveform_config = ma_waveform_config_init( + m_pgex->GetDeviceFormat(), + m_pgex->GetDeviceChannels(), + m_pgex->GetDeviceSampleRate(), + static_cast(type), + amplitude, + frequency + ); + + m_rampStep = 1.0f / (m_pgex->GetDeviceSampleRate() * 0.02f); + + if(ma_waveform_init(&m_waveform_config, &m_waveform) != MA_SUCCESS) + { + return false; + } + + m_is_loaded = true; + return true; + } + + void Waveform::DestroyWaveform() + { + ma_waveform_uninit(&m_waveform); + m_is_loaded = false; + } + + void Waveform::Play() + { + if(!IsLoaded()) + return; + + m_target = 1.0f; + } + + void Waveform::Stop() + { + if(!IsLoaded()) + return; + + m_target = 0.0f; + } + + + void Waveform::SetAmplitude(const double amplitude) + { + if(!IsLoaded()) + return; + ma_waveform_set_amplitude(&m_waveform, amplitude); + } + + void Waveform::SetFrequency(const double frequency) + { + if(!IsLoaded()) + return; + ma_waveform_set_frequency(&m_waveform, frequency); + } + + void Waveform::SetType(const Type type) + { + if(!IsLoaded()) + return; + ma_waveform_set_type(&m_waveform, static_cast(type)); + } + + bool Waveform::IsPlaying() const + { + return m_target > 0.0f || m_gain > 0.0f; + } + + bool Waveform::IsLoaded() const + { + return m_is_loaded; + } + + ma_waveform& Waveform::Get() + { + return m_waveform; + } + +#pragma endregion + +#pragma region Miniaudio + + AudioEngine::AudioEngine() + { + } + + AudioEngine::~AudioEngine() + { + if(m_is_initialized) + { + for(auto sound : m_sounds) + sound->DestroySound(); + + m_sounds.clear(); + ma_resource_manager_uninit(&m_resource_manager); + + ma_engine_stop(&m_engine); + ma_engine_uninit(&m_engine); + + + ma_device_stop(&m_device); + ma_device_uninit(&m_device); + } + } + + void AudioEngine::Configure(const Config& cfg) + { + if(m_is_initialized) + { + std::cerr << "olcPGEX3_miniaudio: Configure called after initialized.\n"; + return; + } + + m_cfg = cfg; + } + + void AudioEngine::EnableBackgroundPlayback() + { + m_cfg.BackgroundPlay = true; + } + + void AudioEngine::DisableBackgroundPlayback() + { + m_cfg.BackgroundPlay = false; + } + + void AudioEngine::data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) + { + AudioEngine* ma = (AudioEngine*)pDevice->pUserData; + if(ma == nullptr) + throw std::runtime_error{"unable to access miniaudio pgex instance from data_callback"}; + + if(!ma->m_cfg.BackgroundPlay && !ma->m_pge->IsFocused()) + return; + + // with great power comes... + if(ma->m_data_callback) + { + ma->m_data_callback((float*)pOutput, frameCount); + return; + } + + std::span engineBuffer((float*)pOutput, frameCount * ma->GetDeviceChannels()); + ma_engine_read_pcm_frames(&ma->m_engine, engineBuffer.data(), frameCount, NULL); + + // resize, if required. frameCount is not guaranteed not to change. + if(ma->m_waveform_buffer.size() != (frameCount * ma->GetDeviceChannels())) + { + ma->m_waveform_buffer.resize(frameCount * ma->GetDeviceChannels(), 0); + } + + // waveforms + for(auto& waveform : ma->m_waveforms) + { + if (!waveform->IsPlaying()) + continue; + + + ma_waveform_read_pcm_frames(&waveform->m_waveform, ma->m_waveform_buffer.data(), frameCount, NULL); + + for(int frame = 0; frame < frameCount; ++frame) + { + // Ramp gain toward target one step per frame + if (waveform->m_gain < waveform->m_target) + waveform->m_gain = std::min(waveform->m_gain + waveform->m_rampStep, waveform->m_target); + else if (waveform->m_gain > waveform->m_target) + waveform->m_gain = std::max(waveform->m_gain - waveform->m_rampStep, waveform->m_target); + + for(int channel = 0; channel < ma->GetDeviceChannels(); ++channel) + { + int i = frame * ma->GetDeviceChannels() + channel; + engineBuffer[i] += ma->m_waveform_buffer[i] * waveform->m_gain; + } + } + } + + // synth function + if(ma->m_synth_callback) + { + for(ma_uint32 i = 0; i < frameCount; i++) + { + float left, right; + ma->m_synth_callback(left, right, 1.0f / ma->GetDeviceSampleRate()); + + engineBuffer[(i * ma->GetDeviceChannels())] += left; + engineBuffer[(i * ma->GetDeviceChannels()) + 1] += right; + } + } + + // limiter + static float envelope = 1.0f; + + for(int i = 0; i < engineBuffer.size(); i++) + { + float peak = fabsf(engineBuffer[i]); + + if (peak > 1.0f) + envelope = fminf(envelope, 1.0f / peak); // duck the gain + else + envelope = fminf(1.0f, envelope * 1.001f); // slowly recover + // limit the output + engineBuffer[i] *= envelope; + } + } + + bool AudioEngine::CreateSoundFromFile(Sound& sound, const std::string& sFileName, uint32_t nNumVoices) + { + m_sounds.push_back(&sound); + return sound.CreateSoundFromFile(sFileName, this, nNumVoices); + } + + bool AudioEngine::CreateSoundFromMemory(Sound& sound, const uint8_t* data, const size_t bytes, uint32_t nNumVoices) + { + m_sounds.push_back(&sound); + return sound.CreateSoundFromMemory(data, bytes, this, nNumVoices); + } + + bool AudioEngine::CreateSoundFromMemory(Sound& sound, const std::vector& data, uint32_t nNumVoices) + { + m_sounds.push_back(&sound); + return sound.CreateSoundFromMemory(data, this, nNumVoices); + } + + void AudioEngine::DestroySound(Sound& sound) + { + sound.DestroySound(); + + m_sounds.erase( + std::remove_if( + m_sounds.begin(), + m_sounds.end(), + [&](Sound* s) { return (s == &sound); } + ), + m_sounds.end() + ); + } + + bool AudioEngine::CreateWaveform(Waveform& waveform, const Waveform::Type type, const double amplitude, const double frequency) + { + m_waveforms.push_back(&waveform); + return waveform.CreateWaveform(waveform, type, amplitude, frequency, this); + } + + void AudioEngine::DestroyWaveform(Waveform& waveform) + { + waveform.DestroyWaveform(); + m_waveforms.erase( + std::remove_if( + m_waveforms.begin(), + m_waveforms.end(), + [&](Waveform* w) { return (w == &waveform); } + ), + m_waveforms.end() + ); + } + + void AudioEngine::SetSynthCallback(std::function callback) + { + m_synth_callback = callback; + } + + void AudioEngine::ClearSynthCallback() + { + m_synth_callback = {}; + } + + void AudioEngine::SetDataCallback(std::function callback) + { + m_data_callback = callback; + } + + void AudioEngine::ClearDataCallback() + { + m_data_callback = {}; + } + + ma_device& AudioEngine::GetDevice() + { + return m_device; + } + + ma_engine& AudioEngine::GetEngine() + { + return m_engine; + } + + ma_resource_manager& AudioEngine::GetResourceManager() + { + return m_resource_manager; + } + + int AudioEngine::GetDeviceChannels() const + { + return m_cfg.DeviceChannels; + } + + ma_format AudioEngine::GetDeviceFormat() const + { + return m_cfg.DeviceFormat; + } + + int AudioEngine::GetDeviceSampleRate() const + { + return m_cfg.DeviceSampleRate; + } + + ma_device_type AudioEngine::GetDeviceType() const + { + return m_cfg.DeviceType; + } + + bool AudioEngine::OnInstall([[maybe_unused]] olc::PixelGameEngine* pge) + { + m_pge = pge; + + m_device_config = ma_device_config_init(GetDeviceType()); + m_device_config.playback.format = GetDeviceFormat(); + m_device_config.playback.channels = GetDeviceChannels(); + m_device_config.sampleRate = GetDeviceSampleRate(); + m_device_config.dataCallback = AudioEngine::data_callback; + m_device_config.pUserData = this; + + if(ma_device_init(NULL, &m_device_config, &m_device) != MA_SUCCESS) + { + std::cerr << "PGEX3_Miniaudio: failed to initialize device\n"; + return false; + } + + m_resource_manager_config = ma_resource_manager_config_init(); + m_resource_manager_config.decodedFormat = GetDeviceFormat(); + m_resource_manager_config.decodedChannels = GetDeviceChannels(); + m_resource_manager_config.decodedSampleRate = GetDeviceSampleRate(); + + #ifdef __EMSCRIPTEN__ + m_resource_manager_config.jobThreadCount = 0; + m_resource_manager_config.flags |= MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; + m_resource_manager_config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + #endif + + if(ma_resource_manager_init(&m_resource_manager_config, &m_resource_manager) != MA_SUCCESS) + { + std::cerr <<"PGEX3_Miniaudio: failed to initialize resource manager\n"; + return false; + } + + m_engine_config = ma_engine_config_init(); + m_engine_config.pDevice = &m_device; + m_engine_config.pResourceManager = &m_resource_manager; + + if(ma_engine_init(&m_engine_config, &m_engine) != MA_SUCCESS) + { + std::cerr << "PGEX3_Miniaudio: failed to initialize engine\n"; + return false; + } + m_is_initialized = true; + return true; + } + + bool AudioEngine::OnBeforeUserCreate([[maybe_unused]] olc::PixelGameEngine* pge) + { + return true; + } + + bool AudioEngine::OnAfterUserCreate([[maybe_unused]] olc::PixelGameEngine* pge) + { + return true; + } + + bool AudioEngine::OnBeforeSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime) + { + #if OLC_HOST == OLC_HOST_EMSCRIPTEN + ma_resource_manager_process_next_job(&m_resource_manager); + #endif + + return true; + } + + bool AudioEngine::OnAfterSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime) + { + return true; + } +#pragma endregion + +} +#endif diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 5e75791..3725ed0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -849,6 +849,26 @@ namespace olc { return { static_cast(this->x), static_cast(this->y) }; } + + template + auto& get() & { + return std::get(xy); + } + + template + const auto& get() const & { + return std::get(xy); + } + + template + auto&& get() && { + return std::get(xy); + } + + template + const auto& get() const && { + return std::get(xy); + } }; // Multiplication operator overloads between vectors and scalars, and vectors and vectors @@ -1010,6 +1030,16 @@ namespace olc typedef v_2d vf2d; typedef v_2d vd2d; } + +namespace std { + template + struct tuple_size> : std::integral_constant {}; + + + template + struct tuple_element> : tuple_element> {}; +} + #define PGE_VECTOR2D_DECLARED 1 #endif @@ -1182,6 +1212,26 @@ namespace olc { return { static_cast(this->x), static_cast(this->y), static_cast(this->z), static_cast(this->w) }; } + + template + auto& get() & { + return std::get(xyzw); + } + + template + const auto& get() const & { + return std::get(xyzw); + } + + template + auto&& get() && { + return std::get(xyzw); + } + + template + const auto& get() const && { + return std::get(xyzw); + } }; // Multiplication operator overloads between vectors and scalars, and vectors and vectors @@ -1309,6 +1359,13 @@ namespace olc return lhs; } + template + inline constexpr auto operator -= (v_4d& lhs, const v_4d& rhs) + { + lhs = lhs - rhs; + return lhs; + } + // Greater/Less-Than Operator overloads - mathematically useless, but handy for "sorted" container storage template inline constexpr bool operator < (const v_4d& lhs, const v_4d& rhs) @@ -1342,6 +1399,16 @@ namespace olc typedef v_4d vf4d; typedef v_4d vd4d; } + +namespace std { + template + struct tuple_size> : std::integral_constant {}; + + + template + struct tuple_element> : tuple_element> {}; +} + #define PGE_VECTOR4D_DECLARED 1 #endif @@ -2842,37 +2909,72 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Lower-Level Pixel Manipulation + // Read a pixel from an image (guarantees fresh) olc::Pixel GetPixel( olc::Image& image, const olc::vf2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write a pixel to an image + void SetPixel( + olc::Image& image, + const olc::vf2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read a pixel from target image (guarantees fresh) olc::Pixel GetPixel( const olc::vf2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write a pixel to target image + void SetPixel( + const olc::vf2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from an image (guarantees fresh) olc::Pixel GetRawPixel( olc::Image& image, const olc::vi2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write an untransformed pixel to an image + void SetRawPixel( + olc::Image& image, + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from target image (guarantees fresh) olc::Pixel GetRawPixel( const olc::vi2d& pos, const olc::Pixel failcol = olc::Colour::BLACK); + // Write an untransformed pixel to target image + void SetRawPixel( + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from a target image with no bounds checking (gurantees fresh) olc::Pixel GetUnsafeRawPixel( olc::Image& image, const olc::vi2d& pos); + // Write an untransformed pixel to a target image with no bounds checking + void SetUnsafeRawPixel( + olc::Image& image, + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read an untransformed pixel from target image with no bounds checking (gurantees fresh) olc::Pixel GetUnsafeRawPixel( const olc::vi2d& pos); + // Write an untransformed pixel to target image with no bounds checking + void SetUnsafeRawPixel( + const olc::vi2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Clear entire draw target to specific colour void Clear(const olc::Pixel& col); @@ -4416,172 +4518,6 @@ namespace olc - - -#if !defined(OLC_USE_WXWIDGETS) - -#if OLC_HOST == OLC_HOST_NONE - -namespace olc::host -{ - class Host_None : public olc::host::Host - { - public: // OS Window Handling - // Make OS Create a window frame, associated with olc::Window - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - // Make OS Close a window frame, associated with olc::Window - bool CloseWindowFrame(olc::Window* pWindow) override; - // Make OS Update a window frame title, associated with olc::Window - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - // Get OS-specific window descriptor(s) for given olc::Window - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for OS desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; - - public: // Platform specific Mouse Control - // Force the mouse position in pixels relative to window - bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; - // Show or hide mouse cursor for given window - bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; - - public: // OS Specific Environment Information - virtual olc::KeyboardLayout GetKeyboardLayout() const override; - - public: // Platform Specific OS<->PGE Linkage - // Called at very start of application - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - bool StartSystem() override; - // Called to stop the host, and shutdown all resources - bool StopSystem() override; - // Called at start of system event loop - bool OnSystemThreadStart() override; - // Called to perform primary window update - bool OnSystemTick() override; - // Called at end of system event loop - bool OnSystemThreadEnd() override; - // Called at very end of application - bool OnApplicationEnd() override; - private: - bool systemActive = false; - }; -} -#endif - -#if OLC_HOST == OLC_HOST_WINDOWS -#if defined(UNICODE) || defined(_UNICODE) - #define olcT(s) L##s -#else - #define olcT(s) s -#endif - -#define _WINSOCKAPI_ - -#if !defined(VC_EXTRALEAN) -#define VC_EXTRALEAN -#endif - -#if !defined(NOMINMAX) -#define NOMINMAX -#endif - -// In Code::Blocks -#if !defined(_WIN32_WINNT) - #ifdef HAVE_MSMF - #define _WIN32_WINNT 0x0600 // Windows Vista - #else - //#define _WIN32_WINNT 0x0603 // Windows 8.1 - - // Windows 10 Minimum - #define WINVER 0x0A00 - #define _WIN32_WINNT 0x0A00 - #endif -#endif - -// Embrace MSVC superiority -#pragma comment(lib, "user32.lib") -#pragma comment(lib, "gdi32.lib") -#pragma comment(lib, "Dwmapi.lib") - -#include -#include -#include -#include -#undef _WINSOCKAPI_ - -namespace olc -{ - namespace host - { - // Host for Windows OS - Single Window Only! - class Host_Windows_WinAPI : public olc::host::Host - { - - - public: - Host_Windows_WinAPI(); - virtual ~Host_Windows_WinAPI() {}; - - - public: // OS Window Handling - // Make OS Create a window frame, associated with olc::Window - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - // Make OS Close a window frame, associated with olc::Window - bool CloseWindowFrame(olc::Window* pWindow) override; - // Make OS Update a window frame title, associated with olc::Window - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - // Get OS-specific window descriptor(s) for given olc::Window - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for OS desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; - - public: // Platform specific Mouse Control - bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; - bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; - bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; - - public: // OS Specific Environment Information - olc::KeyboardLayout GetKeyboardLayout() const override; - - public: - // Called at very start of application - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - bool StartSystem() override; - // Called to stop the host, and shutdown all resources - bool StopSystem() override; - // Called at start of system event loop - bool OnSystemThreadStart() override; - // Called to perform primary window update - bool OnSystemTick() override; - // Called at end of system event loop - bool OnSystemThreadEnd() override; - // Called at very end of application - bool OnApplicationEnd() override; - - - - private: // Windows OS Specific Things - std::unordered_map mapUID2HWND; - std::unordered_map mapHWND2PTR; - std::wstring ConvertS2W(std::string s); - std::atomic systemActive = false; - HCURSOR hCursorDefault = nullptr; - HCURSOR hCursorNow = nullptr; - DWORD ConvertPGE2WindowStyle(const olc::Window* pWindow); - - public: - LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - // Map of system keycodes to olc::Keycodes - std::unordered_map mapKeys; - - }; - } -} -#endif - #if OLC_HOST == OLC_HOST_MACOS #include #include @@ -5830,34 +5766,200 @@ namespace olc { } // namespace apis } // namespace olc +#endif +#if !defined(OLC_USE_WXWIDGETS) +#if OLC_HOST == OLC_HOST_NONE -#include // Grand Central Dispatch: Apple's C-based API for managing concurrent operations on macOS and iOS. -#include // POSIX threads: Used for pthread_main_np() to check if current thread is the main thread -namespace olc +namespace olc::host { - namespace host - { - // Manages our MacOS Host - class Host_Apple_MacOS : public olc::host::Host - { - public: - olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window - - public: - Host_Apple_MacOS(); - virtual ~Host_Apple_MacOS() {}; - - public: - HostError GetLastError() const { return lastError; } - - public: - virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - virtual bool CloseWindowFrame(olc::Window* pWindow) override; - virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + class Host_None : public olc::host::Host + { + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; - virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + private: + bool systemActive = false; + }; +} +#endif + +#if OLC_HOST == OLC_HOST_WINDOWS +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif + +#define _WINSOCKAPI_ + +#if !defined(VC_EXTRALEAN) +#define VC_EXTRALEAN +#endif + +#if !defined(NOMINMAX) +#define NOMINMAX +#endif + +// In Code::Blocks +#if !defined(_WIN32_WINNT) + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + //#define _WIN32_WINNT 0x0603 // Windows 8.1 + + // Windows 10 Minimum + #define WINVER 0x0A00 + #define _WIN32_WINNT 0x0A00 + #endif +#endif + +// Embrace MSVC superiority +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "Dwmapi.lib") + +#include +#include +#include +#include +#undef _WINSOCKAPI_ + +namespace olc +{ + namespace host + { + // Host for Windows OS - Single Window Only! + class Host_Windows_WinAPI : public olc::host::Host + { + + + public: + Host_Windows_WinAPI(); + virtual ~Host_Windows_WinAPI() {}; + + + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + + public: // OS Specific Environment Information + olc::KeyboardLayout GetKeyboardLayout() const override; + + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + + + private: // Windows OS Specific Things + std::unordered_map mapUID2HWND; + std::unordered_map mapHWND2PTR; + std::wstring ConvertS2W(std::string s); + std::atomic systemActive = false; + HCURSOR hCursorDefault = nullptr; + HCURSOR hCursorNow = nullptr; + DWORD ConvertPGE2WindowStyle(const olc::Window* pWindow); + + public: + LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + + }; + } +} +#endif + +#if OLC_HOST == OLC_HOST_MACOS + + +#include // Grand Central Dispatch: Apple's C-based API for managing concurrent operations on macOS and iOS. +#include // POSIX threads: Used for pthread_main_np() to check if current thread is the main thread +namespace olc +{ + namespace host + { + // Manages our MacOS Host + class Host_Apple_MacOS : public olc::host::Host + { + public: + olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window + + public: + Host_Apple_MacOS(); + virtual ~Host_Apple_MacOS() {}; + + public: + HostError GetLastError() const { return lastError; } + + public: + virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + virtual bool CloseWindowFrame(olc::Window* pWindow) override; + virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; @@ -5914,7 +6016,8 @@ namespace olc BECOME_ACTIVE, RESIGN_ACTIVE, MINIMIZE_WINDOW, - DEMINIMIZE_WINDOW + DEMINIMIZE_WINDOW, + SET_MOUSE_POSITION }; // Internal Mac OS functions @@ -5954,6 +6057,8 @@ namespace olc void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start uint16_t ConvertPGE2WindowStyle(); + + olc::vi2d vPositionMouse = {0, 0}; // Stores the desired mouse position for SET_MOUSE_POSITION task }; @@ -5965,6 +6070,7 @@ namespace olc #if OLC_HOST == OLC_HOST_LINUX_X11 #include +#include namespace X11 { #include @@ -6057,6 +6163,7 @@ namespace olc::host #include #include #include +#include #include "libdecor.h" @@ -7499,4737 +7606,4724 @@ namespace olc::imload #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) -#if !defined(OLC_USE_WXWIDGETS) +#if OLC_HOST == OLC_HOST_MACOS -#if OLC_HOST == OLC_HOST_NONE -namespace olc::host -{ - // Make OS Create a window frame, associated with olc::Window - bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - return true; - } +// Application consts selectors +static constexpr const char* kRespondsToSelector = "respondsToSelector:"; +static constexpr const char* kNSApplicationClass = "NSApplication"; +static constexpr const char* kNSWindowClass = "NSWindow"; +static constexpr const char* kNSStringClass = "NSString"; +static constexpr const char* kNSOpenGLPixelFormatClass = "NSOpenGLPixelFormat"; +static constexpr const char* kNSOpenGLViewClass = "NSOpenGLView"; +static constexpr const char* kNSObjectClass = "NSObject"; +static constexpr const char* kAppDelegateClass = "AppDelegate"; +static constexpr const char* kWindowDelegateClass = "WindowDelegate"; +static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; +static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; - // Make OS Close a window frame, associated with olc::Window - bool Host_None::CloseWindowFrame(olc::Window* pWindow) - { - return true; - } +// Application Screen management selectors +//static constexpr const char* kNSScreenClass = "NSScreen"; // Temp remove unused variable warning +static constexpr const char* kScreenSel = "screen"; +static constexpr const char* kNSCursorClass = "NSCursor"; +static constexpr const char* kUnhideSel = "unhide"; +static constexpr const char* kHideSel = "hide"; +static constexpr const char* kIsHiddenSel = "isHidden"; - // Make OS Update a window frame title, associated with olc::Window - bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) - { - return true; - } - // Get OS-specific window descriptor(s) for given olc::Window - std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) - { - return {}; - } +// Application memory management selectors +static constexpr const char* kAllocSel = "alloc"; +static constexpr const char* kInitSel = "init"; +static constexpr const char* kSetDelegateSel = "setDelegate:"; +static constexpr const char* kReleaseSel = "release"; +static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; - // Wait for OS desktop refresh (for smooooth vsync) - bool Host_None::SyncWithDesktopComposite() - { - return true; - } +// NSApplication lifecycle and management selectors +static constexpr const char* kSharedApplicationSel = "sharedApplication"; +static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; +static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; +static constexpr const char* kRunSel = "run"; +static constexpr const char* kStopSel = "stop:"; +static constexpr const char* kTerminateSel = "terminate:"; - // Force the mouse position in pixels relative to window - bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - return true; - } - - // Show or hide mouse cursor for given window - bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - return true; - } +// NSApplicationDelegate lifecycle methods +static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; +static constexpr const char* kApplicationDidFinishLaunchingSel = "applicationDidFinishLaunching:"; +static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; +static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; +static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; - olc::KeyboardLayout Host_None::GetKeyboardLayout() const - { - return OLC_DEFAULT_KEYBOARD_LAYOUT; - } - - // Called at very start of application - bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - pPrimaryPGE = pPrimary; - return true; - } +// NSWindow creation, display, and management selectors +static constexpr const char* kInitWithContentRectSel = "initWithContentRect:styleMask:backing:defer:"; +static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; +static constexpr const char* kSetTitleSel = "setTitle:"; +static constexpr const char* kOrderFrontRegardlessSel = "orderFrontRegardless"; +static constexpr const char* kSetAcceptsMouseMovedEventsSel = "setAcceptsMouseMovedEvents:"; +static constexpr const char* kMakeFirstResponderSel = "makeFirstResponder:"; +static constexpr const char* kMakeKeyAndOrderFrontSel = "makeKeyAndOrderFront:"; +static constexpr const char* kMakeKeyWindowSel = "makeKeyWindow"; +static constexpr const char* kFrameSel = "frame"; +static constexpr const char* kSetFrameDisplaySel = "setFrame:display:"; +static constexpr const char* kSetFrameSel = "setFrame:"; +static constexpr const char* kStyleMaskSel = "styleMask"; +static constexpr const char* kToggleFullScreenSel = "toggleFullScreen:"; - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - bool Host_None::StartSystem() - { - pPrimaryPGE->OnPreContextStart(); +// NSWindowDelegate lifecycle and event methods selectors +static constexpr const char* kWindowDidResizeSel = "windowDidResize:"; +static constexpr const char* kWindowWillCloseSel = "windowWillClose:"; +static constexpr const char* kWindowDidBecomeKeySel = "windowDidBecomeKey:"; +static constexpr const char* kWindowDidResignKeySel = "windowDidResignKey:"; +static constexpr const char* kWindowDidMiniaturizeSel = "windowDidMiniaturize:"; +static constexpr const char* kWindowDidDeminiaturizeSel = "windowDidDeminiaturize:"; - systemActive = true; +// NSResponder keyboard and mouse event methods selectors +static constexpr const char* kKeyDownSel = "keyDown:"; +static constexpr const char* kKeyUpSel = "keyUp:"; +static constexpr const char* kFlagsChangedSel = "flagsChanged:"; +static constexpr const char* kMouseDownSel = "mouseDown:"; +static constexpr const char* kMouseUpSel = "mouseUp:"; +static constexpr const char* kMouseDraggedSel = "mouseDragged:"; +static constexpr const char* kMouseMovedSel = "mouseMoved:"; +static constexpr const char* kRightMouseDownSel = "rightMouseDown:"; +static constexpr const char* kRightMouseUpSel = "rightMouseUp:"; +static constexpr const char* kRightMouseDraggedSel = "rightMouseDragged:"; +static constexpr const char* kOtherMouseDownSel = "otherMouseDown:"; +static constexpr const char* kOtherMouseUpSel = "otherMouseUp:"; +static constexpr const char* kOtherMouseDraggedSel = "otherMouseDragged:"; +static constexpr const char* kScrollWheelSel = "scrollWheel:"; +static constexpr const char* kDeltaXSel = "deltaX"; +static constexpr const char* kDeltaYSel = "deltaY"; +static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; +static constexpr const char* kMouseEnteredSel = "mouseEntered:"; +static constexpr const char* kMouseExitedSel = "mouseExited:"; +static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; +static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; +static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; - if(!OnSystemThreadStart()) - return pPrimaryPGE->OnPostContextEnd(); - - while(systemActive) - { - if(!OnSystemTick()) - { - StopSystem(); - } - } +// Copied a lot of this code from iOS, will need to be tested +// NSResponder touch event method selectors +static constexpr const char* kSetAcceptsTouchEventsSel = "setAcceptsTouchEvents:"; +static constexpr const char* kSetWantsRestingTouchesSel = "setWantsRestingTouches:"; +static constexpr const char* kCGEventSel = "CGEvent"; +static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; - OnSystemThreadEnd(); +// NSTouch / NSSet data extraction selectors +static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; +static constexpr const char* kAllObjectsSel = "allObjects"; +static constexpr const char* kNormalizedPositionSel = "normalizedPosition"; +static constexpr const char* kDeviceSizeSel = "deviceSize"; +static constexpr const char* kIdentitySel = "identity"; +static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; +static constexpr const char* kTouchCountSel = "count"; - return pPrimaryPGE->OnPostContextEnd(); - } +// NSResponder tablet / stylus event method selectors +static constexpr const char* kTabletPointSel = "tabletPoint:"; +static constexpr const char* kTabletProximitySel = "tabletProximity:"; +static constexpr const char* kPressureChangeSel = "pressureChangeWithEvent:"; +static constexpr const char* kStageSel = "stage"; - // Called to stop the host, and shutdown all resources - bool Host_None::StopSystem() - { - systemActive = false; - return true; - } +// NSEvent tablet data extraction selectors +static constexpr const char* kPenPressureSel = "pressure"; +static constexpr const char* kPenRotationSel = "rotation"; +static constexpr const char* kPenTiltSel = "tilt"; +static constexpr const char* kPenIsEnteringProximitySel = "isEnteringProximity"; +static constexpr const char* kPointingDeviceTypeSel = "pointingDeviceType"; +static constexpr const char* kPenTangentialPressureSel = "tangentialPressure"; - // Called at start of system event loop - bool Host_None::OnSystemThreadStart() - { - return pPrimaryPGE->OnContextStart(); - } - // Called to perform primary window update - bool Host_None::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } - - // Called at end of system event loop - bool Host_None::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } - - // Called at very end of application - bool Host_None::OnApplicationEnd() - { - return true; - } -} -#endif +// Managing first responder status and keyboard focus selectors +static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; +static constexpr const char* kBecomeFirstResponderSel = "becomeFirstResponder"; +static constexpr const char* kCanBecomeKeyViewSel = "canBecomeKeyView"; +static constexpr const char* kNeedsPanelToBecomeKeySel = "needsPanelToBecomeKey"; +static constexpr const char* kDrawRectSel = "drawRect:"; +static constexpr const char* kReshapeSel = "reshape"; +static constexpr const char* kUpdateSel = "update"; -#if OLC_HOST == OLC_HOST_WINDOWS -namespace olc::host -{ - bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - pPrimaryPGE = pPrimary; - return true; - } +// NSOpenGL pixel format, view, and context management selectors +static constexpr const char* kInitWithAttributesSel = "initWithAttributes:"; +static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame:pixelFormat:"; +static constexpr const char* kSetContentViewSel = "setContentView:"; +static constexpr const char* kContentViewSel = "contentView"; +static constexpr const char* kBoundsSel = "bounds"; +static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru +static constexpr const char* kConvertRectFromBackingSel = "convertRectFromBacking:"; // Thank you - Mr B the Ultimate Teacher!!! +static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; +static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; +static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; +static constexpr const char* kBackingScaleFactorSel = "backingScaleFactor"; +static constexpr const char* kOpenGLContextSel = "openGLContext"; +static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; +static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; +static constexpr const char* kFlushBufferSel = "flushBuffer"; +static constexpr const char* kDisplaySel = "display"; +static constexpr const char* kCGLContextObjSel = "CGLContextObj"; +static constexpr const char* kSetValuesSel = "setValues:forParameter:"; - bool Host_Windows_WinAPI::StartSystem() - { - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); +// Extracting data from NSEvent objects selectors +static constexpr const char* kKeyCodeSel = "keyCode"; +static constexpr const char* kCharactersSel = "characters"; +static constexpr const char* kLocationInWindowSel = "locationInWindow"; +static constexpr const char* kButtonNumberSel = "buttonNumber"; +static constexpr const char* kClickCountSel = "clickCount"; +static constexpr const char* kModifierFlagsSel = "modifierFlags"; +static constexpr const char* kUTF8StringSel = "UTF8String"; - // Mark system as active - systemActive = true; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; - // Create system thread - handles gpu context - std::thread threadSystem([this]() - { - // Notify start of system thread - if (!this->OnSystemThreadStart()) - { - // PGE->OnContextStart() failed, or user aborted OnUserCreate() - return; - } - // Main system loop - while (systemActive) - { - // Perform primary window update - if (!this->OnSystemTick()) - { - StopSystem(); - } - } +// Default values and configuration settings +static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; +static constexpr double kDefaultWindowWidth = 800.0; +static constexpr double kDefaultWindowHeight = 600.0; +// static constexpr int kBitsPerByte = 8; // Temp remove unused variable warning +static constexpr int kMinValidDimension = 0; +static constexpr int kRGBABytesPerPixel = 4; +static constexpr int kFullyOpaque = 255; +static constexpr int kZeroBytes = 0; +static constexpr int kZeroRows = 0; +static constexpr int kZeroWidth = 0; +static constexpr int kZeroHeight = 0; +static constexpr int kFlippedOffset = 1; +static constexpr int kNoButton = -1; +// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning +bool bAllowHideCursor = true; +bool bHideCursor = false; - // Notify end of system thread - if (!this->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } - }); +// Objective-C method type encoding constants +// Type encoding for methods returning BOOL with no parameters: "c@:" +static constexpr const char* kBoolMethodTypeEncoding = "c@:"; - // Blocking event loop on this thread - handles windows - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } +// Type encoding for void methods with event/notification parameter: "v@:@" +static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; - systemActive = false; - if(threadSystem.joinable()) - threadSystem.join(); +// Type encoding for drawRect method with NSRect parameter: "v@:{NSRect={NSPoint=dd}{NSSize=dd}}" +static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"; - // Post-context end hook - return pPrimaryPGE->OnPostContextEnd(); - } +// Type encoding for void methods with no parameters: "v@:" +static constexpr const char* kVoidMethodTypeEncoding = "v@:"; - bool Host_Windows_WinAPI::StopSystem() - { - systemActive = false; - return true; - } +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; - bool Host_Windows_WinAPI::OnSystemThreadStart() - { - return pPrimaryPGE->OnContextStart(); - } +namespace ObjectiveCSEL { + +// Application memory management selectors + static SEL allocSel = nullptr; + static SEL initSel = nullptr; + static SEL setDelegateSel = nullptr; + static SEL releaseSel = nullptr; + static SEL isKindOfClassSel = nullptr; - bool Host_Windows_WinAPI::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } + // NSApplication lifecycle and management selectors + static SEL sharedApplicationSel = nullptr; + static SEL activateIgnoringOtherAppsSel = nullptr; + static SEL setActivationPolicySel = nullptr; + static SEL runSel = nullptr; + static SEL stopSel = nullptr; + static SEL terminateSEL = nullptr; - bool Host_Windows_WinAPI::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } + // Application Screen management selectors + static SEL screenSel = nullptr; + static SEL unhideSel = nullptr; + static SEL hideSel = nullptr; + static SEL isHiddenSel = nullptr; - bool Host_Windows_WinAPI::OnApplicationEnd() - { - return true; - } + // NSApplicationDelegate lifecycle methods + static SEL applicationWillFinishLaunchingSel = nullptr; + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; + // NSWindow creation, display, and management selectors + static SEL initWithContentRectSel = nullptr; + static SEL stringWithUTF8StringSel = nullptr; + static SEL setTitleSel = nullptr; + static SEL orderFrontRegardlessSel = nullptr; + static SEL setAcceptsMouseMovedEventsSel = nullptr; + static SEL makeKeyAndOrderFrontSel = nullptr; + static SEL makeKeyWindowSel = nullptr; + static SEL frameSel = nullptr; + static SEL setFrameDisplaySel = nullptr; + static SEL setFrameSel = nullptr; + static SEL makeFirstResponderSel = nullptr; + static SEL styleMaskSel = nullptr; + static SEL toggleFullScreenSel = nullptr; + // NSWindowDelegate lifecycle and event methods selectors + static SEL windowDidResizeSel = nullptr; + static SEL windowWillCloseSel = nullptr; + static SEL windowDidBecomeKeySel = nullptr; + static SEL windowDidResignKeySel = nullptr; + static SEL windowDidMiniaturizeSel = nullptr; + static SEL windowDidDeminiaturizeSel = nullptr; - // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE + // NSResponder keyboard and mouse event methods selectors + static SEL keyDownSel = nullptr; + static SEL keyUpSel = nullptr; + static SEL flagsChangedSel = nullptr; + static SEL mouseDownSel = nullptr; + static SEL mouseUpSel = nullptr; + static SEL mouseDraggedSel = nullptr; + static SEL mouseMovedSel = nullptr; + static SEL rightMouseDownSel = nullptr; + static SEL rightMouseUpSel = nullptr; + static SEL rightMouseDraggedSel = nullptr; + static SEL otherMouseDownSel = nullptr; + static SEL otherMouseUpSel = nullptr; + static SEL otherMouseDraggedSel = nullptr; + static SEL scrollWheelSel = nullptr; + static SEL deltaXSel = nullptr; + static SEL deltaYSel = nullptr; + static SEL updateTrackingAreasSel = nullptr; + static SEL mouseEnteredSel = nullptr; + static SEL mouseExitedSel = nullptr; + // Mouse Tracking + static SEL addTrackingAreaSel = nullptr; + static SEL initWithRectSel = nullptr; - // Forward Declaration - static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + // Touch event selectors + static SEL setAcceptsTouchEventsSel = nullptr; + static SEL setWantsRestingTouchesSel = nullptr; + static SEL cgEventSel = nullptr; + static SEL touchesBeganSel = nullptr; + static SEL touchesMovedSel = nullptr; + static SEL touchesEndedSel = nullptr; + static SEL touchesCancelledSel = nullptr; + static SEL touchesMatchingPhaseSel = nullptr; + static SEL allObjectsSel = nullptr; + static SEL normalizedPositionSel = nullptr; + static SEL deviceSizeSel = nullptr; + static SEL identitySel = nullptr; + static SEL objectAtIndexSel = nullptr; + static SEL touchesCountSel = nullptr; + // Tablet / stylus event selectors + static SEL tabletPointSel = nullptr; + static SEL tabletProximitySel = nullptr; + static SEL penPressureSel = nullptr; + static SEL penRotationSel = nullptr; + static SEL penTiltSel = nullptr; + static SEL penIsEnteringProximitySel = nullptr; + static SEL pointingDeviceTypeSel = nullptr; + static SEL penTangentialPressureSel = nullptr; + static SEL pressureChangeSel = nullptr; + static SEL stageSel = nullptr; - // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get - // access to the actual host instance, which can more conveninetly process - // the event across multiple window instances - static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - // CreateWindowEx will fire a WM_CREATE event at the window, which we're - // not interested in, and this will occur before we've populated our - // linkage maps. We can detect for this condition here which means - // subsequent look-ups dont fail. NOTE: Do not assume WM_CREATE is the - // first message that is sent, its just one that we know is reliably sent. - if (uMsg == WM_CREATE) - { - // Associate the window's little blob of user memory with host iface - auto cfg = ((CREATESTRUCT*)lParam)->lpCreateParams; - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cfg); - } - else - { - // If this returns a value, then the host iface should be responsible - // for handling the message. - auto host = (Host_Windows_WinAPI*)GetWindowLongPtr(hWnd, GWLP_USERDATA); - if (host) - return host->OnWindowEvent(hWnd, uMsg, wParam, lParam); - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } + // Managing first responder status and keyboard focus selectors + static SEL acceptsFirstResponderSel = nullptr; + static SEL becomeFirstResponderSel = nullptr; + static SEL canBecomeKeyViewSel = nullptr; + static SEL needsPanelToBecomeKeySel = nullptr; + static SEL drawRectSel = nullptr; + static SEL reshapeSel = nullptr; + static SEL updateSel = nullptr; - std::wstring Host_Windows_WinAPI::ConvertS2W(std::string s) - { -#ifdef __MINGW32__ - wchar_t* buffer = new wchar_t[s.length() + 1]; - mbstowcs(buffer, s.c_str(), s.length()); - buffer[s.length()] = L'\0'; -#else - int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); - wchar_t* buffer = new wchar_t[count]; - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); -#endif - std::wstring w(buffer); - delete[] buffer; - return w; - } + // NSOpenGL pixel format, view, and context management selectors + static SEL initWithAttributesSel = nullptr; + static SEL initWithFramePixelFormatSel = nullptr; + static SEL setContentViewSel = nullptr; + static SEL contentViewSel = nullptr; + static SEL boundsSel = nullptr; + static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru + static SEL convertRectFromBackingSel = nullptr; // Thank you - Mr B the Ultimate Teacher!!! + static SEL convertPointToBackingSel = nullptr; + static SEL convertPointFromBackingSel = nullptr; + static SEL convertPointFromViewSel = nullptr; + static SEL openGLContextSel = nullptr; + static SEL makeCurrentContextSel = nullptr; + static SEL setAutoresizingMaskSel = nullptr; + static SEL flushBufferSel = nullptr; + static SEL displaySel = nullptr; + static SEL CGLContextObjSel = nullptr; + static SEL setValuesSel = nullptr; + static SEL backingScaleFactorSel = nullptr; - Host_Windows_WinAPI::Host_Windows_WinAPI() - { - // Map Windows Defined VK_ Codes to olc::KeyCodes - mapKeys[0x00] = Key::NONE; + // Extracting data from NSEvent objects selectors + static SEL keyCodeSel = nullptr; + static SEL charactersSel = nullptr; + static SEL locationInWindowSel = nullptr; + static SEL buttonNumberSel = nullptr; + static SEL clickCountSel = nullptr; + static SEL modifierFlagsSel = nullptr; + static SEL utf8StringSel = nullptr; - // Windows doesn't define A-Z - mapKeys[0x41] = Key::A; - mapKeys[0x42] = Key::B; - mapKeys[0x43] = Key::C; - mapKeys[0x44] = Key::D; - mapKeys[0x45] = Key::E; - mapKeys[0x46] = Key::F; - mapKeys[0x47] = Key::G; - mapKeys[0x48] = Key::H; - mapKeys[0x49] = Key::I; - mapKeys[0x4A] = Key::J; - mapKeys[0x4B] = Key::K; - mapKeys[0x4C] = Key::L; - mapKeys[0x4D] = Key::M; - mapKeys[0x4E] = Key::N; - mapKeys[0x4F] = Key::O; - mapKeys[0x50] = Key::P; - mapKeys[0x51] = Key::Q; - mapKeys[0x52] = Key::R; - mapKeys[0x53] = Key::S; - mapKeys[0x54] = Key::T; - mapKeys[0x55] = Key::U; - mapKeys[0x56] = Key::V; - mapKeys[0x57] = Key::W; - mapKeys[0x58] = Key::X; - mapKeys[0x59] = Key::Y; - mapKeys[0x5A] = Key::Z; + // NSLocale selectors + static SEL currentLocaleSel = nullptr; + static SEL localeIdentifierSel = nullptr; + // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning + // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning - // Windows doesnt define numeric keys - mapKeys[0x30] = Key::K0; - mapKeys[0x31] = Key::K1; - mapKeys[0x32] = Key::K2; - mapKeys[0x33] = Key::K3; - mapKeys[0x34] = Key::K4; - mapKeys[0x35] = Key::K5; - mapKeys[0x36] = Key::K6; - mapKeys[0x37] = Key::K7; - mapKeys[0x38] = Key::K8; - mapKeys[0x39] = Key::K9; + // Initialize all selectors - called once at startup + void initializeSelectors() { + if (allocSel) return; // Already initialized + + // Application memory management selectors + allocSel = sel_registerName(kAllocSel); + initSel = sel_registerName(kInitSel); + setDelegateSel = sel_registerName(kSetDelegateSel); + releaseSel = sel_registerName(kReleaseSel); + isKindOfClassSel = sel_registerName(kIsKindOfClassSel); - // Function Keys - mapKeys[VK_F1] = Key::F1; - mapKeys[VK_F2] = Key::F2; - mapKeys[VK_F3] = Key::F3; - mapKeys[VK_F4] = Key::F4; - mapKeys[VK_F5] = Key::F5; - mapKeys[VK_F6] = Key::F6; - mapKeys[VK_F7] = Key::F7; - mapKeys[VK_F8] = Key::F8; - mapKeys[VK_F9] = Key::F9; - mapKeys[VK_F10] = Key::F10; - mapKeys[VK_F11] = Key::F11; - mapKeys[VK_F12] = Key::F12; - - // Arrow Keys - mapKeys[VK_DOWN] = Key::DOWN; - mapKeys[VK_LEFT] = Key::LEFT; - mapKeys[VK_RIGHT] = Key::RIGHT; - mapKeys[VK_UP] = Key::UP; + // NSApplication lifecycle and management selectors + sharedApplicationSel = sel_registerName(kSharedApplicationSel); + activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); + setActivationPolicySel = sel_registerName(kSetActivationPolicySel); + runSel = sel_registerName(kRunSel); + stopSel = sel_registerName(kStopSel); + terminateSEL = sel_registerName(kTerminateSel); + + // Application Screen management selectors + screenSel = sel_registerName(kScreenSel); + unhideSel = sel_registerName(kUnhideSel); + hideSel = sel_registerName(kHideSel); + isHiddenSel = sel_registerName(kIsHiddenSel); - // Other Keys - mapKeys[VK_BACK] = Key::BACK; - mapKeys[VK_ESCAPE] = Key::ESCAPE; - mapKeys[VK_RETURN] = Key::ENTER; - mapKeys[VK_PAUSE] = Key::PAUSE; - mapKeys[VK_SCROLL] = Key::SCROLL; - mapKeys[VK_TAB] = Key::TAB; - mapKeys[VK_DELETE] = Key::DEL; - mapKeys[VK_HOME] = Key::HOME; - mapKeys[VK_END] = Key::END; - mapKeys[VK_PRIOR] = Key::PGUP; - mapKeys[VK_NEXT] = Key::PGDN; - mapKeys[VK_INSERT] = Key::INS; - mapKeys[VK_SHIFT] = Key::SHIFT; - mapKeys[VK_CONTROL] = Key::CTRL; - mapKeys[VK_SPACE] = Key::SPACE; - mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; - mapKeys[VK_MENU] = Key::ALT; + // NSApplicationDelegate lifecycle methods + applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); + applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); + applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); + applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); + applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); - // Numpad - mapKeys[VK_NUMPAD0] = Key::NP0; - mapKeys[VK_NUMPAD1] = Key::NP1; - mapKeys[VK_NUMPAD2] = Key::NP2; - mapKeys[VK_NUMPAD3] = Key::NP3; - mapKeys[VK_NUMPAD4] = Key::NP4; - mapKeys[VK_NUMPAD5] = Key::NP5; - mapKeys[VK_NUMPAD6] = Key::NP6; - mapKeys[VK_NUMPAD7] = Key::NP7; - mapKeys[VK_NUMPAD8] = Key::NP8; - mapKeys[VK_NUMPAD9] = Key::NP9; - mapKeys[VK_MULTIPLY] = Key::NP_MUL; - mapKeys[VK_ADD] = Key::NP_ADD; - mapKeys[VK_DIVIDE] = Key::NP_DIV; - mapKeys[VK_SUBTRACT] = Key::NP_SUB; - mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + // NSWindow creation, display, and management selectors + initWithContentRectSel = sel_registerName(kInitWithContentRectSel); + stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); + setTitleSel = sel_registerName(kSetTitleSel); + orderFrontRegardlessSel = sel_registerName(kOrderFrontRegardlessSel); + setAcceptsMouseMovedEventsSel = sel_registerName(kSetAcceptsMouseMovedEventsSel); + makeFirstResponderSel = sel_registerName(kMakeFirstResponderSel); + makeKeyAndOrderFrontSel = sel_registerName(kMakeKeyAndOrderFrontSel); + makeKeyWindowSel = sel_registerName(kMakeKeyWindowSel); + frameSel = sel_registerName(kFrameSel); + setFrameDisplaySel = sel_registerName(kSetFrameDisplaySel); + setFrameSel = sel_registerName(kSetFrameSel); + styleMaskSel = sel_registerName(kStyleMaskSel); + toggleFullScreenSel = sel_registerName(kToggleFullScreenSel); + + // NSWindowDelegate lifecycle and event methods selectors + windowDidResizeSel = sel_registerName(kWindowDidResizeSel); + windowWillCloseSel = sel_registerName(kWindowWillCloseSel); + windowDidBecomeKeySel = sel_registerName(kWindowDidBecomeKeySel); + windowDidResignKeySel = sel_registerName(kWindowDidResignKeySel); + windowDidMiniaturizeSel = sel_registerName(kWindowDidMiniaturizeSel); + windowDidDeminiaturizeSel = sel_registerName(kWindowDidDeminiaturizeSel); - // OEM Keys - mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key - mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key - mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key - mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key - mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. - mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key - mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard - mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard - mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard - mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard - mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard - } + // NSResponder keyboard and mouse event methods selectors + keyDownSel = sel_registerName(kKeyDownSel); + keyUpSel = sel_registerName(kKeyUpSel); + flagsChangedSel = sel_registerName(kFlagsChangedSel); + mouseDownSel = sel_registerName(kMouseDownSel); + mouseUpSel = sel_registerName(kMouseUpSel); + mouseDraggedSel = sel_registerName(kMouseDraggedSel); + mouseMovedSel = sel_registerName(kMouseMovedSel); + rightMouseDownSel = sel_registerName(kRightMouseDownSel); + rightMouseUpSel = sel_registerName(kRightMouseUpSel); + rightMouseDraggedSel = sel_registerName(kRightMouseDraggedSel); + otherMouseDownSel = sel_registerName(kOtherMouseDownSel); + otherMouseUpSel = sel_registerName(kOtherMouseUpSel); + otherMouseDraggedSel = sel_registerName(kOtherMouseDraggedSel); + scrollWheelSel = sel_registerName(kScrollWheelSel); + deltaXSel = sel_registerName(kDeltaXSel); + deltaYSel = sel_registerName(kDeltaYSel); + updateTrackingAreasSel = sel_registerName(kUpdateTrackingAreasSel); + mouseEnteredSel = sel_registerName(kMouseEnteredSel); + mouseExitedSel = sel_registerName(kMouseExitedSel); + addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); + initWithRectSel = sel_registerName(kInitWithRectSel); + + // Touch event selectors + setAcceptsTouchEventsSel = sel_registerName(kSetAcceptsTouchEventsSel); + setWantsRestingTouchesSel = sel_registerName(kSetWantsRestingTouchesSel); + cgEventSel = sel_registerName(kCGEventSel); + touchesBeganSel = sel_registerName(kTouchesBeganSel); + touchesMovedSel = sel_registerName(kTouchesMovedSel); + touchesEndedSel = sel_registerName(kTouchesEndedSel); + touchesCancelledSel = sel_registerName(kTouchesCancelledSel); + touchesMatchingPhaseSel = sel_registerName(kTouchesMatchingPhaseSel); + allObjectsSel = sel_registerName(kAllObjectsSel); + normalizedPositionSel = sel_registerName(kNormalizedPositionSel); + deviceSizeSel = sel_registerName(kDeviceSizeSel); + identitySel = sel_registerName(kIdentitySel); + objectAtIndexSel = sel_registerName(kObjectAtIndexSel); + touchesCountSel = sel_registerName(kTouchCountSel); - bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - olc_IgnoreUnused(bFullScreen); + // Tablet / stylus event selectors + tabletPointSel = sel_registerName(kTabletPointSel); + tabletProximitySel = sel_registerName(kTabletProximitySel); + penPressureSel = sel_registerName(kPenPressureSel); + penRotationSel = sel_registerName(kPenRotationSel); + penTiltSel = sel_registerName(kPenTiltSel); + penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); + pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); + penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); + pressureChangeSel = sel_registerName(kPressureChangeSel); + stageSel = sel_registerName(kStageSel); - // The user created olc::Window object is the SSoT for what a window - // should look like, so get that sort of thing from there - olc::vi2d vWinPos = vWindowPos; - olc::vi2d vWinSize = vWindowSize; - - hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); + // Managing first responder status and keyboard focus selectors + acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); + becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); + canBecomeKeyViewSel = sel_registerName(kCanBecomeKeyViewSel); + needsPanelToBecomeKeySel = sel_registerName(kNeedsPanelToBecomeKeySel); + drawRectSel = sel_registerName(kDrawRectSel); + reshapeSel = sel_registerName(kReshapeSel); + updateSel = sel_registerName(kUpdateSel); - // Define WindowClass - WNDCLASSEX wc = { 0 }; - wc.cbSize = sizeof(WNDCLASSEX); - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = hCursorDefault; - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpfnWndProc = WINAPI_EventHandler; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0;// sizeof(this); // For static meta-info - wc.lpszMenuName = nullptr; - wc.hbrBackground = nullptr; - wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); - RegisterClassEx(&wc); + // NSOpenGL pixel format, view, and context management selectors + initWithAttributesSel = sel_registerName(kInitWithAttributesSel); + initWithFramePixelFormatSel = sel_registerName(kInitWithFramePixelFormatSel); + setContentViewSel = sel_registerName(kSetContentViewSel); + contentViewSel = sel_registerName(kContentViewSel); + boundsSel = sel_registerName(kBoundsSel); + convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru + convertRectFromBackingSel = sel_registerName(kConvertRectFromBackingSel); // Thank you - Mr B the Ultimate Teacher!!! + convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); + convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); + convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); + openGLContextSel = sel_registerName(kOpenGLContextSel); + makeCurrentContextSel = sel_registerName(kMakeCurrentContextSel); + setAutoresizingMaskSel = sel_registerName(kSetAutoresizingMaskSel); + flushBufferSel = sel_registerName(kFlushBufferSel); + displaySel = sel_registerName(kDisplaySel); + CGLContextObjSel = sel_registerName(kCGLContextObjSel); + setValuesSel = sel_registerName(kSetValuesSel); + backingScaleFactorSel = sel_registerName(kBackingScaleFactorSel); - // Define window furniture - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); + // Extracting data from NSEvent objects selectors + keyCodeSel = sel_registerName(kKeyCodeSel); + charactersSel = sel_registerName(kCharactersSel); + locationInWindowSel = sel_registerName(kLocationInWindowSel); + buttonNumberSel = sel_registerName(kButtonNumberSel); + clickCountSel = sel_registerName(kClickCountSel); + modifierFlagsSel = sel_registerName(kModifierFlagsSel); + utf8StringSel = sel_registerName(kUTF8StringSel); - olc::vi2d vTopLeft = vWindowPos; + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + } - if (bFullScreen || pPrimaryPGE->config.bFullScreen) - { - dwExStyle = 0; - dwStyle = WS_VISIBLE | WS_POPUP; - POINT olc_pt = { vWinPos.x, vWinPos.y }; - HMONITOR hmon = MonitorFromPoint(olc_pt, MONITOR_DEFAULTTONEAREST); - MONITORINFO mi = { sizeof(mi) }; - if (!GetMonitorInfo(hmon, &mi)) return false; - vWinSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; - vTopLeft.x = 0; - vTopLeft.y = 0; - } + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); + } +} +// Single initialization function called once at startup +void initialize_macos_api() { + ObjectiveCSEL::initializeSelectors(); +} - // Keep client size as requested - RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; - AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); +// Concepts safety templates for Objective-C interactions +template +concept CallbackType = std::is_function_v>; // Checks if T is a function type - // +1 Hack to remove black bar between client and title bar for "perfect" window - // sizes anyway. This could be DPI related on later windows, but this makes it look - // tidier for "normal" applications - int width = rWndRect.right - rWndRect.left + 1; - int height = rWndRect.bottom - rWndRect.top; +template +concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) - // Create the actual OS window, return a handle - HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, - vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +// Autorelease pool management functions +extern "C" { + extern void* objc_autoreleasePoolPush(void); + extern void objc_autoreleasePoolPop(void* pool); +} - // Update window size to match actual client area given. In situations where the window - // is clamped to the desktop, the client area may be smaller than requested. - RECT rClient; - GetClientRect(hWnd, &rClient); - pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); - - // Hide the close button if the user requested it, but only after styles are applied, - if (!pPrimaryPGE->config.bShowWindowCloseButton) - { - HMENU hMenu = GetSystemMenu(hWnd, FALSE); - DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); - } +// Objective-C autorelease pools +class AutoreleasePool { +public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } + + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; - LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); - lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); +private: + void* pool_; +}; - pWindow->olc_OnFocus(true); - //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); - //ShowWindow(hWnd, 1); - //UpdateWindow(hWnd); +using NSPoint = CGPoint; +using NSInteger = long; +using NSUInteger = unsigned long; - // Now... awkwardly, the above has already fired off some window messages - // and they arent necessarily in a consistent order. Whereas one might - // assume WM_CREATE would be the first, there are some others on more - // modern systems. This is awkward because we havent yet associated the - // source window with a long_ptr to this class, and therefore we can't - // call the appropriate event handler. +// Forward declarations +struct Application; +struct Window; +struct OpenGLRenderer; +struct ApplicationDelegate; +struct CustomOpenGLView; +struct WindowDelegate; +struct ImageLoader; - // Store the link bewteen host resource and window - mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); - mapHWND2PTR.insert_or_assign(hWnd, pWindow); +// enums for OpenGL pixel format and rendering capabilities +enum class NSOpenGLPixelFormatAttribute : uint16_t { + // Renderer selection + AllRenderers = 1, // Use all available renderers + RendererID = 70, // Specific renderer ID + NoRecovery = 72, // No recovery from renderer failures + Accelerated = 73, // Hardware accelerated rendering + AllowOfflineRenderers = 96, // Allow offline renderers + AcceleratedCompute = 97, // Accelerated compute support - + // Buffer configuration + TripleBuffer = 3, // Triple buffering support + DoubleBuffer = 5, // Double buffering (recommended) + Stereo = 6, // Stereo buffering for 3D + AuxBuffers = 7, // Number of auxiliary buffers + BackingStore = 76, // Backing store configuration + + // Color and depth configuration + ColorSize = 8, // Total color buffer size (bits) + AlphaSize = 11, // Alpha channel size (bits) + DepthSize = 12, // Depth buffer size (bits) + StencilSize = 13, // Stencil buffer size (bits) + AccumSize = 14, // Accumulation buffer size (bits) + ColorFloat = 58, // Floating-point color buffer + // Antialiasing configuration + Multisample = 59, // Multisampling antialiasing + Supersample = 60, // Supersampling antialiasing + SampleBuffers = 55, // Number of sample buffers + Samples = 56, // Samples per pixel + SampleAlpha = 61, // Sample alpha to coverage + AuxDepthStencil = 57, // Auxiliary depth/stencil buffer - //DragAcceptFiles(olc_hWnd, true); + // Policy configuration + MinimumPolicy = 51, // Minimum attribute policy + MaximumPolicy = 52, // Maximum attribute policy + ClosestPolicy = 74, // Closest match policy - return true; - } + // Advanced configuration + OpenGLProfile = 99, // OpenGL profile selection + ScreenMask = 84, // Screen mask for multi-display + VirtualScreenCount = 128, // Number of virtual screens +}; - bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) - { - DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); - return false; - } +// Backward compatibility +static constexpr int NSOpenGLPFADoubleBuffer = static_cast(NSOpenGLPixelFormatAttribute::DoubleBuffer); +static constexpr int NSOpenGLPFAColorSize = static_cast(NSOpenGLPixelFormatAttribute::ColorSize); +static constexpr int NSOpenGLPFADepthSize = static_cast(NSOpenGLPixelFormatAttribute::DepthSize); +static constexpr int NSOpenGLPFAAccelerated = static_cast(NSOpenGLPixelFormatAttribute::Accelerated); +static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFormatAttribute::OpenGLProfile); +static constexpr int NSOpenGLPFASampleBuffers = static_cast(NSOpenGLPixelFormatAttribute::SampleBuffers); +static constexpr int NSOpenGLPFAMultisample = static_cast(NSOpenGLPixelFormatAttribute::Multisample); +static constexpr int NSOpenGLAllowOfflineRenderers = static_cast(NSOpenGLPixelFormatAttribute::AllowOfflineRenderers); +// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning - bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) - { -#ifdef UNICODE - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); -#else - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); -#endif - return true; - } +// enum for OpenGL profile versions +enum class NSOpenGLProfile : int { + VersionLegacy = 0x1000, // Legacy OpenGL (deprecated) + Version3_2Core = 0x3200, // OpenGL 3.2 Core Profile + Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile +}; - std::vector Host_Windows_WinAPI::GetHostWindowDescriptor(olc::Window* pWindow) - { - return { mapUID2HWND[pWindow->GetUID()] }; - } +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; - bool Host_Windows_WinAPI::SyncWithDesktopComposite() - { - return DwmFlush() == S_OK; - } +// Backward compatibility +//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); +static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); - bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - POINT pt; - pt.x = vPos.x; - pt.y = vPos.y; - ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); - SetCursorPos(pt.x, pt.y); - return true; - } +// enum for window appearance and behavior bit flags +enum class NSWindowStyleMask : uint16_t { + Titled = (1 << 0), // Window has a title bar + Closable = (1 << 1), // Window can be closed + Miniaturizable = (1 << 2), // Window can be minimized + Resizable = (1 << 3), // Window can be resized + UtilityWindow = (1 << 4), // Utility window style + DocModalWindow = (1 << 6), // Document-modal window + NonactivatingPanel = (1 << 7), // Non-activating panel + HUDWindow = (1 << 13), // Heads-up display window + TexturedBackground = (1 << 8), // Textured background + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view +}; - bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - olc_IgnoreUnused(pWindow); +// Backward compatibility +// static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); // Temp remove unused variable warning +static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); - hCursorNow = bVisible ? hCursorDefault : NULL; +// enum for backing store types +enum class NSBackingStoreType : uint8_t { + Retained = 0, // Deprecated - backing store is retained + Nonretained = 1, // Deprecated - backing store is not retained + Buffered = 2 // Modern - double-buffered backing store (recommended) +}; - // Fire fake move event to update cursor visibility immediately - POINT p; - GetCursorPos(&p); - SetCursorPos(p.x, p.y + 1); - SetCursorPos(p.x, p.y); - return true; - } +static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); - bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) - { - HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); +// enum for window creation timing options +enum class NSWindowDefer : uint8_t { + CreateNow = 0, // Create window immediately (NO/false) + DeferCreation = 1 // Defer window creation (YES/true) +}; - if (bFullScreen) - { - // Maximise, make on top, remove border and titlebar - SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); - ShowWindow(hWnd, SW_MAXIMIZE); - } - else - { - // Restore original window style and position - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - // Get the style we should have based on the window config - DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); +// Backward compatibility +static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); - LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | dwStyle); - lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | dwExStyle); - ShowWindow(hWnd, SW_NORMAL); - } +// enum for view autoresizing behavior flags +enum class NSAutoresizingMask : uint8_t { + NotSizable = 0, // View maintains fixed size + MinXMargin = 1, // Left margin is flexible + WidthSizable = 2, // Width changes with superview width + MaxXMargin = 4, // Right margin is flexible + MinYMargin = 8, // Bottom margin is flexible + HeightSizable = 16, // Height changes with superview height + MaxYMargin = 32 // Top margin is flexible +}; - UpdateWindow(hWnd); - SetForegroundWindow(hWnd); - SetFocus(hWnd); - SetActiveWindow(hWnd); - return true; - } +// Backward compatibility +static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); +static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); - DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) - { - olc_IgnoreUnused(pWindow); +// enum for application activation and UI visibility policies +enum class NSApplicationActivationPolicy : uint8_t { + Regular = 0, // Normal app with dock icon and menu bar Recommended + Accessory = 1, // Utility app, no dock icon but can have menu bar + Prohibited = 2 // Background only, no UI elements +}; - DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx +// Backward compatibility +static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); - // Note for Microsoft: if you hide the border, it hides the title bar too, and via versa +// Mouse Tracking const +static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; +static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; +static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; - // For fullscreen,borderless/noTitlebar we want to skip all the window furniture and just have a big ol canvas - if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return dwStyle |= WS_POPUP; - - // If any max/min/close button(s) display the button menu - if (pPrimaryPGE->config.bShowWindowCloseButton || pPrimaryPGE->config.bShowWindowMaximiseButton || pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_SYSMENU; - if (pPrimaryPGE->config.bShowWindowTilebar) dwStyle |= WS_CAPTION; // Add a title bar - if (pPrimaryPGE->config.bShowWindowBorder) dwStyle |= WS_BORDER; // Add a border - if (pPrimaryPGE->config.bResizeable) dwStyle |= WS_THICKFRAME; // Enable resizing - if (pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_MINIMIZEBOX; // Add Min Button - if (pPrimaryPGE->config.bShowWindowMaximiseButton) dwStyle |= WS_MAXIMIZEBOX; // Add Max Button +/* +* WARNING: Global delegate pointers for Objective-C callbacks +* These global pointers are used to route Objective-C delegate callbacks +* to the appropriate C++ structures and methods. +* +* In short: Don't mess with these unless you know what you're doing! +* +* Global variables are evil, but sometimes necessary for bridging C++ and Objective-C. +*/ +static Window* gptrNSWindowEvents = nullptr; +static Application* gptrApplicationDelegate = nullptr; +static Window* gptrWindowDelegate = nullptr; - // Note: Close button is handled after dwStlyes are applied +// Handles NSApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; - return dwStyle; +// Custom NSOpenGLView subclass for handling input events +struct CustomOpenGLView { + Class isa; // Objective-C class pointer (required) + void* renderer; // C Pointer to associated renderer +}; +// Handles NSWindow delegate methods and window events +struct WindowDelegate { + Class isa; // Objective-C class pointer (required) + void* renderer; // Pointer to associated renderer +}; - } - - LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - if (!mapHWND2PTR.contains(hWnd)) - return DefWindowProc(hWnd, uMsg, wParam, lParam);; +// Application management with member initialization +struct Application { + id nsApp{nullptr}; // NSApplication instance + id delegate{nullptr}; // Application delegate instance - // Get target olc::Window - const auto& window = mapHWND2PTR.at(hWnd); + // Callback function pointers + std::function willFinishLaunchingCallback{nullptr}; + std::function didFinishLaunchingCallback{nullptr}; + std::function willTerminateCallback{nullptr}; + std::function didBecomeActiveCallback{nullptr}; + std::function willResignActiveCallback{nullptr}; - // Many WinAPI events are literally ancient these days, so need some interpretation - // to get to the useful data. + void* willFinishLaunchingUserData{nullptr}; // User data for will finish launching callback + void* didFinishLaunchingUserData{nullptr}; // User data for did finish launching callback + void* willTerminateUserData{nullptr}; // User data for will terminate callback + void* didBecomeActiveUserData{nullptr}; // User data for did become active callback + void* willResignActiveUserData{nullptr}; // User data for will resign active callback - switch (uMsg) - { - case WM_MOUSEMOVE: // Mouse has moved within a window - { - // Extract mouse X & Y - uint16_t x = uint16_t(lParam & 0xFFFF); - uint16_t y = uint16_t((lParam >> 16) & 0xFFFF); - int16_t ix = *(int16_t*)&x; - int16_t iy = *(int16_t*)&y; - // Tell window new mouse location - window->olc_OnMouseMove(olc::vi2d{ ix, iy }); - break; - } - + // Method function pointers + void (*initialize) (struct Application* self){nullptr}; + void (*activate) (struct Application* self){nullptr}; + void (*run) (struct Application* self){nullptr}; + void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; + + Application() = default; + + ~Application() {} + + // Delete copy constructor and assignment + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + Application(Application&&) = default; + Application& operator=(Application&&) = default; +}; - // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; - case WM_SIZE: - { - window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); - break; - } +// Window management with member initialization +struct Window { + id nsWindow{nullptr}; // NSWindow instance + id delegate{nullptr}; // Window delegate instance + OpenGLRenderer* renderer{nullptr}; // Associated OpenGL renderer + NSRect windowFrame{}; // Window frame rectangle + NSRect contentViewFrame{}; // Content view frame rectangle + const char* title{nullptr}; // Window title string - case WM_MOUSEWHEEL: - { - window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); - break; - } + // Event callback function pointers with nullptr initialization + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*flagsChangedCallback) (unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseDraggedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*rightMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*rightMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*rightMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*otherMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*otherMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*otherMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseEnteredCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseExitedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*scrollWheelCallback) (double x, double y, double deltaX, double deltaY, unsigned int modifierFlags, void* userData){nullptr}; - case WM_ACTIVATE: - { - window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); - return 0; - } + // Touch event callback function pointers with nullptr initialization + void (*touchBeganCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchMovedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchEndedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchCancelledCallback)(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void* touchUserData{nullptr}; - case WM_MOUSEACTIVATE: - { - window->olc_OnFocus(true); - return MA_ACTIVATE; - } - - case WM_SETFOCUS: - { - window->olc_OnFocus(true); - return 0; - } + // Stylus (tablet) event callback function pointer + void (*stylusCallback)(uint32_t touchID, double x, double y, + float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData){nullptr}; + void* stylusUserData{nullptr}; + bool bStylusInProximity{false}; // true while stylus is hovering near the surface + + void* eventUserData{nullptr}; // User data for event callbacks + BOOL acceptsInputEvents{NO}; // Whether the window accepts input events + + // Window delegate event callback function pointers with nullptr initialization + void (*windowDidResizeCallback) (void* userData){nullptr}; + void (*windowWillCloseCallback) (void* userData){nullptr}; + void (*windowDidBecomeKeyCallback) (void* userData){nullptr}; + void (*windowDidResignKeyCallback) (void* userData){nullptr}; + void (*windowDidMiniaturizeCallback) (void* userData){nullptr}; + void (*windowDidDeminiaturizeCallback)(void* userData){nullptr}; - case WM_KILLFOCUS: - { - window->olc_OnFocus(false); - return 0; - } + void* windowDidResizeUserData{nullptr}; // User data for window resize callback + void* windowWillCloseUserData{nullptr}; // User data for window will close callback + void* windowDidBecomeKeyUserData{nullptr}; // User data for window did become key callback + void* windowDidResignKeyUserData{nullptr}; // User data for window did resign key callback + void* windowDidMiniaturizeUserData{nullptr}; // User data for window did miniaturize callback + void* windowDidDeminiaturizeUserData{nullptr}; // User data for window did deminiaturize callback + + // Method function pointers with nullptr initialization + void (*create) (struct Window* self, unsigned long styleMask){nullptr}; + void (*show) (struct Window* self){nullptr}; + void (*destroy) (struct Window* self){nullptr}; + void (*setDelegate) (struct Window* self, id delegate){nullptr}; + void (*removeDelegate) (struct Window* self){nullptr}; + const char* (*getTitle) (const struct Window* self){nullptr}; + void (*setTitle) (struct Window* self, const char* title){nullptr}; - case WM_KEYDOWN: - { - window->olc_OnFocus(true); - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); - } - break; - } + void (*setWindowSize) (struct Window* self, double width, double height){nullptr}; + void (*getWindowSize) (const struct Window* self, double* width, double* height){nullptr}; + void (*setWindowPosition) (struct Window* self, double x, double y){nullptr}; + void (*getWindowPosition) (const struct Window* self, double* x, double* y){nullptr}; + void (*setWindowFrame) (struct Window* self, double x, double y, double width, double height){nullptr}; + void (*getWindowFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; + void (*getCurrentFrame) (struct Window* self){nullptr}; + void (*getContentViewFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; + void (*setContentViewFrame) (struct Window* self, double* x, double* y, double* width, double* height){nullptr}; +}; - case WM_KEYUP: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); - } - break; - } +// Modern OpenGL context management and rendering operations +struct OpenGLRenderer { + id pixelFormat{nullptr}; // NSOpenGLPixelFormat instance + id glView{nullptr}; // NSOpenGLView instance + id glContext{nullptr}; // NSOpenGLContext instance + unsigned int MSAA_Samples{OLC_MSAA_SAMPLES}; // Multisample anti-aliasing samples + Window* window{nullptr}; // Associated window pointer - case WM_SYSKEYDOWN: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); - } - break; - } + // Method function pointers with nullptr initialization + void (*initialize) (struct OpenGLRenderer* self, Window* window){nullptr}; + void (*setupContext) (struct OpenGLRenderer* self){nullptr}; + void (*renderYellowBackground)(struct OpenGLRenderer* self){nullptr}; + void (*renderTexturedQuad) (struct OpenGLRenderer* self, unsigned int textureID){nullptr}; + void* (*getOpenGLContext) (const struct OpenGLRenderer* self){nullptr}; + void* (*getCGLContextObj) (struct OpenGLRenderer* self){nullptr}; + void* (*getCGLContextObjPtr) (struct OpenGLRenderer* self){nullptr}; + void (*makeCurrentContext) (struct OpenGLRenderer* self){nullptr}; + void (*setVsync) (struct OpenGLRenderer* self, BOOL enabled){nullptr}; + void (*destroy) (struct OpenGLRenderer* self){nullptr}; + bool (*resetContextForSize) (struct OpenGLRenderer* self, double width, double height){nullptr}; - case WM_SYSKEYUP: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); - } - break; - } - - case WM_LBUTTONDOWN: - { - window->olc_OnMouseButton(0, true); - break; - } - case WM_LBUTTONUP: - { - window->olc_OnMouseButton(0, false); - break; - } - case WM_RBUTTONDOWN: - { - window->olc_OnMouseButton(1, true); - break; - } - case WM_RBUTTONUP: - { - window->olc_OnMouseButton(1, false); - break; - } - case WM_MBUTTONDOWN: - { - window->olc_OnMouseButton(2, true); - break; - } - case WM_MBUTTONUP: - { - window->olc_OnMouseButton(2, false); - break; - } - case WM_XBUTTONDOWN: - { - UINT button = GET_XBUTTON_WPARAM(wParam); - if(button == XBUTTON1) - { - window->olc_OnMouseButton(3, true); - } - else if(button == XBUTTON2) - { - window->olc_OnMouseButton(4, true); - } - - break; - } - case WM_XBUTTONUP: - { - UINT button = GET_XBUTTON_WPARAM(wParam); - if(button == XBUTTON1) - { - window->olc_OnMouseButton(3, false); - } - else if(button == XBUTTON2) - { - window->olc_OnMouseButton(4, false); - } - - break; - } - - case WM_POINTERDOWN: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - true, - false, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - true, - false, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - - } - - break; - } - - case WM_POINTERUP: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - true, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - true, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - } - - break; - } - - case WM_POINTERUPDATE: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - false, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - if (IS_POINTER_INCONTACT_WPARAM(wParam)) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - false, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - } - } - break; - } - - - // case WM_DROPFILES: - // { - // // This is all eww... - // HDROP drop = (HDROP)wParam; - // - // uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); - // std::vector vFiles; - // for (uint32_t i = 0; i < nFiles; i++) - // { - // TCHAR dfbuffer[256]{}; - // uint32_t len = DragQueryFile(drop, i, nullptr, 0); - // DragQueryFile(drop, i, dfbuffer, 256); - //#ifdef UNICODE - //#ifdef __MINGW32__ - // char* buffer = new char[len + 1]; - // wcstombs(buffer, dfbuffer, len); - // buffer[len] = '\0'; - //#else - // int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); - // char* buffer = new char[count]; - // WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); - //#endif - // vFiles.push_back(std::string(buffer)); - // delete[] buffer; - //#else - // vFiles.push_back(std::string(dfbuffer)); - //#endif - // } - // - // // Even more eww... - // POINT p; DragQueryPoint(drop, &p); - // ptrPGE->olc_DropFiles(p.x, p.y, vFiles); - // DragFinish(drop); - // return 0; - // } - // break; - // - // - case WM_CLOSE: - { - window->olc_OnWindowClose(); - break; - //return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - case WM_SETCURSOR: - { - if (LOWORD(lParam) == HTCLIENT) - { - SetCursor(hCursorNow); - return TRUE; // Sigh ffs microsoft... - } - - break; - } - - case WM_DESTROY: - PostQuitMessage(0); - DestroyWindow(hWnd); - - } - - - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const - { - HKL kbl = ::GetKeyboardLayout(0); - size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; - - if (highWord == 0x00000409) // US - return olc::KeyboardLayout::QWERTY_US; - else if(highWord == 0x00000809) // UK - return olc::KeyboardLayout::QWERTY_UK; - else if(highWord == 0x00000407) // DE - return olc::KeyboardLayout::QWERTZ; - else if(highWord == 0x0000040C) // FR - return olc::KeyboardLayout::AZERTY; - - return OLC_DEFAULT_KEYBOARD_LAYOUT; - } +}; +// Modern image loading and pixel data extraction +struct ImageLoader { + imageloader_pixel_t* pixelData{nullptr}; // Raw pixel data (RGBA format) + int width{kMinValidDimension}; // Image width in pixels + int height{kMinValidDimension}; // Image height in pixels + int bytesPerPixel{kZeroBytes}; // Number of bytes per pixel (typically 4 for RGBA) + int bytesPerRow{kZeroRows}; // Number of bytes per row + BOOL hasAlpha{NO}; // Whether image has alpha channel + + // Method function pointers with nullptr initialization + BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; + BOOL (*loadFromMemory) (struct ImageLoader* self, const uint8_t* data, size_t bytes); + void (*destroy) (struct ImageLoader* self){nullptr}; + unsigned char* (*getPixelData) (const struct ImageLoader* self){nullptr}; + void (*getImageInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel){nullptr}; + void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; + BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; + BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; + unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; }; -#endif - -#if OLC_HOST == OLC_HOST_MACOS -namespace olc::host { - - - // NSEventModifierFlags values - // constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 // Temp remove unused variable warning - constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 - constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 - // constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 // Temp remove unused variable warning - constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 - // constexpr unsigned int NSEventModifierFlagNumericPad = 1 << 21; // 0x200000 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 // Temp remove unused variable warning - - // enum for window appearance and behavior bit flags - enum class NSWindowStyleMask : uint16_t { - Titled = (1 << 0), // Window has a title bar - Closable = (1 << 1), // Window can be closed - Miniaturizable = (1 << 2), // Window can be minimized - Resizable = (1 << 3), // Window can be resized - UtilityWindow = (1 << 4), // Utility window style - DocModalWindow = (1 << 6), // Document-modal window - NonactivatingPanel = (1 << 7), // Non-activating panel - TexturedBackground = (1 << 8), // Textured background - HUDWindow = (1 << 13), // Heads-up display window - UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar - FullScreen = (1 << 14), // Full-screen window - FullSizeContentView = (1 << 15) // Full-size content view - }; - - - Host_Apple_MacOS::Host_Apple_MacOS() - { - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - mapKeys[0x00] = Key::NONE; - // Map macOS key codes to olc::Key codes - mapKeys[0] = Key::A; - mapKeys[11] = Key::B; - mapKeys[8] = Key::C; - mapKeys[2] = Key::D; - mapKeys[14] = Key::E; - mapKeys[3] = Key::F; - mapKeys[5] = Key::G; - mapKeys[4] = Key::H; - mapKeys[34] = Key::I; - mapKeys[38] = Key::J; - mapKeys[40] = Key::K; - mapKeys[37] = Key::L; - mapKeys[46] = Key::M; - mapKeys[45] = Key::N; - mapKeys[31] = Key::O; - mapKeys[35] = Key::P; - mapKeys[12] = Key::Q; - mapKeys[15] = Key::R; - mapKeys[1] = Key::S; - mapKeys[17] = Key::T; - mapKeys[32] = Key::U; - mapKeys[9] = Key::V; - mapKeys[13] = Key::W; - mapKeys[7] = Key::X; - mapKeys[16] = Key::Y; - mapKeys[6] = Key::Z; - - // Numeric keys - mapKeys[29] = Key::K0; - mapKeys[18] = Key::K1; - mapKeys[19] = Key::K2; - mapKeys[20] = Key::K3; - mapKeys[21] = Key::K4; - mapKeys[23] = Key::K5; - mapKeys[22] = Key::K6; - mapKeys[26] = Key::K7; - mapKeys[28] = Key::K8; - mapKeys[25] = Key::K9; - - // Function Keys - mapKeys[122] = Key::F1; - mapKeys[120] = Key::F2; - mapKeys[99] = Key::F3; - mapKeys[118] = Key::F4; - mapKeys[96] = Key::F5; - mapKeys[97] = Key::F6; - mapKeys[98] = Key::F7; - mapKeys[100] = Key::F8; - mapKeys[101] = Key::F9; - mapKeys[109] = Key::F10; - mapKeys[103] = Key::F11; - mapKeys[111] = Key::F12; - - // Arrow Keys - mapKeys[125] = Key::DOWN; - mapKeys[123] = Key::LEFT; - mapKeys[124] = Key::RIGHT; - mapKeys[126] = Key::UP; +// Called when application is about to finish launching +void applicationWillFinishLaunching(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; // Remove unused parameter warnings + if (gptrApplicationDelegate && gptrApplicationDelegate->willFinishLaunchingCallback) { + gptrApplicationDelegate->willFinishLaunchingCallback(gptrApplicationDelegate->willFinishLaunchingUserData); + } +} - // Other Keys - mapKeys[51] = Key::BACK; // Delete (Backspace) - mapKeys[53] = Key::ESCAPE; // Escape - mapKeys[36] = Key::ENTER; // Return - mapKeys[113] = Key::PAUSE; // F16 (often used as pause) - mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) - mapKeys[48] = Key::TAB; // Tab - mapKeys[117] = Key::DEL; // Forward Delete - mapKeys[115] = Key::HOME; // Home - mapKeys[119] = Key::END; // End - mapKeys[116] = Key::PGUP; // Page Up - mapKeys[121] = Key::PGDN; // Page Down - mapKeys[114] = Key::INS; // Help (Insert equivalent) - mapKeys[56] = Key::SHIFT; // Left Shift - mapKeys[59] = Key::CTRL; // Left Control - mapKeys[49] = Key::SPACE; // Space - mapKeys[57] = Key::CAPS_LOCK; // Caps Lock +// Called when application has finished launching +void applicationDidFinishLaunching(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { + gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); + } +} - // Numpad - mapKeys[82] = Key::NP0; - mapKeys[83] = Key::NP1; - mapKeys[84] = Key::NP2; - mapKeys[85] = Key::NP3; - mapKeys[86] = Key::NP4; - mapKeys[87] = Key::NP5; - mapKeys[88] = Key::NP6; - mapKeys[89] = Key::NP7; - mapKeys[91] = Key::NP8; - mapKeys[92] = Key::NP9; - mapKeys[67] = Key::NP_MUL; // Numpad * - mapKeys[69] = Key::NP_ADD; // Numpad + - mapKeys[75] = Key::NP_DIV; // Numpad / - mapKeys[78] = Key::NP_SUB; // Numpad - - mapKeys[65] = Key::NP_DECIMAL; // Numpad . +// Called when application is about to terminate +void applicationWillTerminate(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->willTerminateCallback) { + gptrApplicationDelegate->willTerminateCallback(gptrApplicationDelegate->willTerminateUserData); + } +} - // Symbol Keys (OEM equivalents) - mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key - mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key - mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) - mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key - mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. - mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key - mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) - mapKeys[24] = Key::EQUALS; // Equal sign = - mapKeys[43] = Key::COMMA; // Comma , - mapKeys[27] = Key::MINUS; // Minus - - mapKeys[47] = Key::PERIOD; // Period . +// Called when application becomes active +void applicationDidBecomeActive(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->didBecomeActiveCallback) { + gptrApplicationDelegate->didBecomeActiveCallback(gptrApplicationDelegate->didBecomeActiveUserData); + } +} +// Called when application resigns active status +void applicationWillResignActive(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->willResignActiveCallback(gptrApplicationDelegate->willResignActiveUserData); } +} +// Create and configure application delegate +id createApplicationDelegate() { - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ - olc_IgnoreUnused(bFullScreen); - pPGEwindow = pWindow; - pPGEwindow->SetWindowPosition(vWindowPos); - pPGEwindow->SetWindowSize(vWindowSize); - pPGEwindow->LinkToHost(this); + // Get NSObject class as superclass using const string + Class NSObjectClass = objc_getClass(kNSObjectClass); + + // Create a new class inheriting from NSObject using const string + Class AppDelegateClass = objc_allocateClassPair(NSObjectClass, kAppDelegateClass, 0); + + // Add application delegate methods + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillFinishLaunchingSel, (IMP)applicationWillFinishLaunching, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidFinishLaunchingSel, (IMP)applicationDidFinishLaunching, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillTerminateSel, (IMP)applicationWillTerminate, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidBecomeActiveSel, (IMP)applicationDidBecomeActive, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillResignActiveSel, (IMP)applicationWillResignActive, kEventHandlerMethodTypeEncoding); - frameBounds.x = 0.0; - frameBounds.y = 0.0; - frameBounds.width = static_cast(vWindowSize.x); - frameBounds.height = static_cast(vWindowSize.y); - - return true; - } + // Register the class with the runtime + objc_registerClassPair(AppDelegateClass); + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(AppDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + return delegate; +} - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ - pWindow->olc_OnWindowClose(); - return true; - } +//====================================================================// +// Keyboard Event Handling - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); - }); - return true; - } +// Structure to hold common key event data +struct KeyEventData { + unsigned short keyCode = 0; + const char* characters = nullptr; + unsigned int modifierFlags = 0; +}; - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - olc_IgnoreUnused(pWindow); - // Ensure OpenGL renderer is created - if(pMacOSOpenGLRenderer == nullptr) - CreateCGLContextObj(); +// Extract common key event data from NSEvent +KeyEventData extractKeyEventData(id event) { + KeyEventData data; + data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); + id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); + data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + return data; +} - return vMacOSWindowDescriptors; - - } + // Handle key down events +void view_keyDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; // Remove unused parameter warnings + KeyEventData data = extractKeyEventData(event); - bool Host_Apple_MacOS::SyncWithDesktopComposite() - { - /* - core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, - the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, - set to false and toggle it on first call, so that vSync is only enabled once - */ - - if(!enableVSync) - { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; - } - - return enableVSync; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } +} - bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorPosition(vPos.x, vPos.y); - }); - return false; - } +// Handle key up events +void view_keyUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorVisibility(bVisible); - }); - return true; + KeyEventData data = extractKeyEventData(event); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } +} - bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) - { - olc_IgnoreUnused(pWindow); - // if we're already in the specified state, return early - if(pMacOSWindow->isFullScreen() == bFullScreen) - return true; +void view_flagsChanged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->toggleFullScreen(); - }); - return true; + unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { + gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); } +} - uint16_t Host_Apple_MacOS::ConvertPGE2WindowStyle() +//====================================================================// +// Mouse Event Handling + +// All values are converted to physical backing pixels so they match the OpenGL +// viewport on HighDPI displays - AppKit reports locations in logical points, but +// PGE is in physical pixels. +void convertToContentViewCoordinates(NSPoint& location) { + + if(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] { - uint16_t nsStyle = 0; - - // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window - if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, - // For MacOS you can only disable the buttons, you can't hide them - if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window - if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar - if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border - if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing - if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button - if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button + + // Convert from window coordinates to view coordinates (still logical points) + NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + + // Scale from logical points to physical backing pixels + NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); - return nsStyle; - - } + // Get content bounds in physical pixels for Y-flip + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); - bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ - pPrimaryPGE = pPrimary; - return true; + // Flip Y coordinate from bottom-left to top-left + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + + } + } + else + { + // Fallback: use default window height + location.y = kDefaultWindowHeight - location.y; } +} - bool Host_Apple_MacOS::StartSystem(){ - - // Create MacOS Application instance - pMacApplication = std::make_unique(); +// Mouse event data structure to hold common mouse event information +struct MouseEventData { + NSPoint location = {0.0, 0.0}; + NSUInteger modifierFlags = 0; + NSInteger buttonNumber = 0; +}; - // Set up application delegate event handlers - MacApplicationEventsHandler(); +// Extract common mouse event data from NSEvent +MouseEventData extractMouseEventData(id event) { + MouseEventData data; + data.location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + data.modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + data.buttonNumber = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::buttonNumberSel); + + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.location); + return data; +} - // Initialize and activate application first - pMacApplication->initialize(); - pMacApplication->activate(); - - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - - // Initialize the MacOS Window - pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); - pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); - pMacOSWindow->setContentViewPosition(0, 0); - - // Set up window event handlers - MacWindowEventsHandler(); - - // Create Input Event handler - pMacOSEventHandler = std::make_unique(*pMacOSWindow); - - // Setup Event handlers - MacEventsHandler(); - - // Create the window - unsigned long styleMask = ConvertPGE2WindowStyle(); - pMacOSWindow->show(styleMask); - pMacOSEventHandler->enable(); - - //--- Start up our engine threading system ---- - // Start the PGE context on the main thread - // Mark system as active - systemActive = true; +// Stylus event data structure to hold common stylus event information +struct StylusEventData { + + uint32_t nTabletDeviceID = 0; // Unique identifier for the tablet device + uint8_t nPointerType = 0; // Pointer type 0 = Mouse, 1 = Pen, 2 = Eraser + uint8_t nPointerEventType = 0; // Type of pointer 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + NSPoint nspLocation = {0.0, 0.0}; // Location of the stylus event + double dPressure = 0.0; // Pressure applied by the stylus + double dTangentialPressure = 0.0; // Tangential pressure applied by the stylus + double dRotationRadians = 0.0; // Rotation of the stylus in radians + double dTiltX_radians = 0.0; // Tilt of the stylus in the X direction in radians + double dTiltY_radians = 0.0; // Tilt of the stylus in the Y direction in radians + uint16_t buttonMask = 0; // Bitmask for stylus buttons (MAX 16 buttons, 1 = pressed, 0 = released) - // Create system thread - handles gpu context - std::thread threadSystem([this]() - { - // Notify start of system thread - if (!this->OnSystemThreadStart()) - { - // PGE->OnContextStart() failed, or user aborted OnUserCreate() - return; - } +}; - // Main system loop - while (systemActive) - { - // Perform primary window update - if (!this->OnSystemTick()) - { - StopSystem(); - } - } +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { - // Notify end of system thread - if (!this->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } - }); + StylusEventData data; + id cgEvent = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + data.nTabletDeviceID = (uint32_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventDeviceID); - - // Start the main event loop (this will block) - pMacApplication->run(); - - // Once the application run loop ends, join the system thread - if(threadSystem.joinable()) - threadSystem.join(); + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event - // Post-context end hook - return pPrimaryPGE->OnPostContextEnd(); + data.dPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointPressure); + data.dRotationRadians = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventRotation) * (M_PI / 180.0); + data.dTangentialPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventTangentialPressure); - } + data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); - bool Host_Apple_MacOS::StopSystem() - { - dispatch_sync(dispatch_get_main_queue(), ^{ - // clean up and close application - if (pMacOSOpenGLRenderer) - { - pMacOSOpenGLRenderer->destoryContext(); - pMacOSOpenGLRenderer = nullptr; - } - if (pMacOSWindow) - { - pMacOSWindow->destoryWindow(); - pMacOSWindow = nullptr; - } - if (pMacApplication) - { - pMacApplication->stop(); - } + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); - }); - systemActive = false; - return true; - } + // Clamp to safe math bounds (-1.0 to 1.0) to avoid domain errors in asin + tiltX_raw = std::fmax(-1.0, std::fmin(1.0, tiltX_raw)); + tiltY_raw = std::fmax(-1.0, std::fmin(1.0, tiltY_raw)); - bool Host_Apple_MacOS::OnSystemThreadStart() - { - // Hold back threading until application is fully initialized - bSkipFrame = ExecutePendingMainThreadTasks(); - return pPrimaryPGE->OnContextStart(); - } + // Convert to true physical angles in RADIANS -PI/2 to +PI/2 + data.dTiltX_radians = std::asin(tiltX_raw); + data.dTiltY_radians = std::asin(tiltY_raw); - bool Host_Apple_MacOS::OnSystemTick() - { - // Execute any pending main thread tasks - bSkipFrame = ExecutePendingMainThreadTasks(); - return pPrimaryPGE->OnContextTick(); - } + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} - bool Host_Apple_MacOS::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) - bool Host_Apple_MacOS::OnApplicationEnd() - { - return true; - } +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; -//-- OS Window Event Handling ----- - - olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const - { - // Get system locale from MacOS Application - // We need to wait until the application has launched to get the keyboard layout - // Therefore this function is called again from setDidFinishLaunchingCallback event - if (pMacApplication) - { - std::string locale = pMacApplication->getSystemLocale(); - if (locale == "en_GB") - { - return olc::KeyboardLayout::QWERTY_UK; - } - else if (locale == "en_US") - { - return olc::KeyboardLayout::QWERTY_US; - } - else if (locale == "fr_FR") - { - return olc::KeyboardLayout::AZERTY; - } - else if (locale == "de_DE") - { - return olc::KeyboardLayout::QWERTZ; - } - } - // Default to QWERTY if unknown - return olc::KeyboardLayout::QWERTY_UK; - } +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; -// ------- Priavate Main Thread Task Handling for MacOS Host ------- +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; - bool Host_Apple_MacOS::CreateCGLContextObj() - { - // This method should only be called on the PGE thread, use AddPendingMainThreadTask(CREATE_OPENGL_RENDERER); to queue it if needed - if(pMacOSOpenGLRenderer == nullptr) - { - vMacOSWindowDescriptors.clear(); // ensure we are starting fresh - pMacOSOpenGLRenderer = std::make_shared(); - - dispatch_sync(dispatch_get_main_queue(), ^{ - // Edge case for when the window is auto resize due to MacOS clamping to screen size - pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); - pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); +// Returns the approximated trackpad touch size in pixels based on the linear force value (0.0 to 1.0) +CGSize getApproxTrackPadTouchSize(float linearForce){ + + CGSize cgTouchSize = {0.0, 0.0}; + const float mmToPoints = 72.0f / 25.4f; // ~2.834 points per mm + float minSizePoints = 2.0f * mmToPoints; // 3mm lower bound approx values from Google + float maxSizePoints = 20.0f * mmToPoints; // 16mm upper bound + + // Interpolate the finger footprint size within our point boundaries + float touchSizeInPoints = minSizePoints + (linearForce * (maxSizePoints - minSizePoints) / 2.0f); - pMacOSOpenGLRenderer->attachToWindow(*pMacOSWindow); - pMacOSOpenGLRenderer->setupContext(); - }); - - pMacGLConextObj = pMacOSOpenGLRenderer->getCGLContextObj(); - - pMacOSOpenGLRenderer->setVsync(false); - - vMacOSWindowDescriptors.push_back(pMacGLConextObj); // Pointer to CGLContextObj - vMacOSWindowDescriptors.push_back(&bSkipFrame); // Pointer to skip frame flag + // Retrieve the window scale factor (1.0 for Standard, 2.0 for Retina, 3.0 for bannana displays, etc.) + CGFloat pixelScale = 1.0f; + if (gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) { + // Get backing scale factor from the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + pixelScale = ((CGFloat(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::backingScaleFactorSel); + } + } + cgTouchSize = CGSizeMake(touchSizeInPoints * pixelScale, touchSizeInPoints * pixelScale); + return cgTouchSize; +}; - // Set up OpenGL renderer for visual feedback - pMacOSOpenGLRenderer->makeCurrentContext(); +// Mouse event data structure to hold common mouse event information +struct TouchEventData { + uint32_t touchID = 0; // Stable sequential touch ID + NSPoint nspLocation = {0.0,0.0}; // X coordinate in content-view pixel coordinates + double pressure = 0.0; // Pressure value + CGSize nsTouchSize = {0.0, 0.0}; // Touch Size X (not available on macOS trackpad) + CGSize cgDeviceSize = {0.0, 0.0}; // Device size (width, height) in ponits + bool bIsActive = true; // Touch is active (true for Began/Moved, false for Ended/Cancelled) + +}; + +std::unordered_map sTouchIDEvents; +static std::unordered_map sTouchIDMap; +static uint32_t sNextTouchID = 0; +CGSize cgsApproxTouchSize{0.0, 0.0}; // Stores the approx width and height of a touch event in CG coordinates + +//Iterate through Touches, updates and fires the callback. +static void updateTouchData(id event, NSUInteger phase, + void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) +{ + if (!callback) return; + + NSUInteger count = 0; + id touchArray = nullptr; + id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); + + // Edge case, there can be an touch event before a pressure event, therefore we need to check if the approximate touch size has been initialized + if(cgsApproxTouchSize.width == 0.0 || cgsApproxTouchSize.height == 0.0) + { + cgsApproxTouchSize = getApproxTrackPadTouchSize(NSPressureStageNormal); + } + + + if (touchCollection) + { + count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + + // Get current screen the window is on + id currentScreen = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::screenSel); + NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); + + for (NSUInteger i = 0; i < count; ++i) { + + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + TouchEventData data; + + // normalizedPosition is NSPoint in [0,1] range on the trackpad surface + NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); - // Finally we set full screen if needed to ensure all out OpenGL setup is done before toggling full screen, - if(pPrimaryPGE->config.bFullScreen){ - SetFullScreen(pPGEwindow, true); - } - - } - - return true; - } + // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) + data.nspLocation.x = normPos.x * screenFrame.width; + data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; + + // Get Device Size + data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); + // Approximate touch size based on pressure stage (macOS does not provide actual touch size) + data.nsTouchSize = cgsApproxTouchSize; + data.bIsActive = !bRemove; - bool Host_Apple_MacOS::ExecutePendingMainThreadTasks() - { - // 1: Check if main thread wants us to wait - std::unique_lock lock(pgeThreadPendingTasksMutex); - - if (isPGEThreadResetting.load()) { + // Assign a stable sequential uint32_t IDs same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); - // 2. PGE Thread signals it's waiting + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + data.touchID = tid; + sTouchIDEvents.emplace(key, data); + callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) { - std::lock_guard mainLock(mainThreadPendingTasksMutex); - isMainThreadResetting = true; // Signal to main thread we're waiting + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); } - mainThreadResetCondition.notify_all(); // Wake up main thread - - // Note: MainThreadTasks(); will be called by the main thread to process tasks - // 3. PGE Thread waits for main thread to finish - pgeThreadResetCondition.wait(lock, [this] { - return !isPGEThreadResetting.load(); - }); - - //4: return true indicating we processed tasks - return true; - } - else - { - // No pending tasks, just return - return false; } + } - bool Host_Apple_MacOS::AddPendingMainThreadTask(MAINTASKS task) + // A bit painful, but we need to check of any keys that have been removed as the touchleave event happened outside of our application + std::vector vFoundKeys; + std::vector vMissingKeys; + touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, NSTouchPhaseAny, nil); + + // If we have a touch collection and the phase is not cancelled, we can check for missing keys, otherwise we will just remove all keys from the map + if (touchCollection && phase != NSTouchPhaseCancelled) { - // NOTE: Note: You should only add tasks that require main thread execution - vPendingMainThreadTasks.push_back(task); - MainThreadTasks(); + count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + for (NSUInteger i = 0; i < count; ++i) { - return true; + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + vFoundKeys.push_back(key); + + } } - bool Host_Apple_MacOS::MainThreadTasks() - { - bool res = false; - if(vPendingMainThreadTasks.empty()) - return res; // edge case + // Find the missing keys and fire the touchEndedCallback for them + for(auto& [key, data] : sTouchIDEvents) { - // 1. Main Thread locks PGE Thread - { - std::lock_guard lock(pgeThreadPendingTasksMutex); - isPGEThreadResetting = true; // Signal PGE to stop + if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { + gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); + vMissingKeys.push_back(key); } - pgeThreadResetCondition.notify_all(); // Wake up PGE thread to check flag - - // 2. Main Thread waits for PGE Thread to acknowledge and wait - std::unique_lock lock(mainThreadPendingTasksMutex); - mainThreadResetCondition.wait(lock, [this] { - return isMainThreadResetting.load(); // Wait until PGE signals it's waiting - }); + + } + + // Remove the missing keys from the maps + for(auto& fKeys: vMissingKeys) { + sTouchIDEvents.erase(fKeys); + sTouchIDMap.erase(fKeys); + } + + vFoundKeys.clear(); vMissingKeys.clear(); + + // Finally reset the next touch ID if there are no active touches remaining + if (sTouchIDMap.size() == 0) sNextTouchID = 0; +} + +// TODO move to new location +// Handle Force Touch pressure changes on trackpad (single-finger press) +void view_pressureChange(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + auto toNormalizedPressure = [](ForceTouchPressure pressure) -> double { + return static_cast(static_cast(pressure)) / 100.0; + }; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - // Process any pending main thread tasks - for (const auto& task : vPendingMainThreadTasks) - { - switch (task) + // Get CGEvent to determine event subtype + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + + // A force touch trackpad event will have a subtype of 0 (default) or greater than 2 (tablet proximity) + if (nPointerEventType == kCGEventMouseSubtypeDefault || nPointerEventType > kCGEventMouseSubtypeTabletProximity) { + + // KRespondsToSelector + BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + + NSInteger stage = NSPressureStageNormal; + if(supportsStage) { - case CREATE_OPENGL_RENDERER: - { - // In this case, the PGE will be waiting for main thread to singal, so the ContextOBJ can be created - res = false; // No need to skip frame - break; - } - case RESIZE_WINDOW: - { - // Resize window on main thread - pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); - pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); - pMacOSOpenGLRenderer->resetContextSize(frameBounds.width, frameBounds.height); - res = true; // Skip frame to allow resize to take effect + // stage returns: 0 = normal click, 1 = light press, 2 = force click + stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); + } + + // Map stage to normalized pressure values as requested + double pressure = toNormalizedPressure(ForceTouchPressure::Normal); + + switch (stage) { + case NSPressureStageLight: + pressure = toNormalizedPressure(ForceTouchPressure::Light); break; - } - case DEMINIMIZE_WINDOW: - case BECOME_ACTIVE: - { - pPGEwindow->olc_OnFocus(true); + case NSPressureStageNormal: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); break; - } - case MINIMIZE_WINDOW: - case RESIGN_ACTIVE: - { - pPGEwindow->olc_OnFocus(false); + case NSPressureStageForce: + pressure = toNormalizedPressure(ForceTouchPressure::Force); break; - } - case NONE: default: - { - res = false; + pressure = toNormalizedPressure(ForceTouchPressure::Normal); break; - } - } - } - vPendingMainThreadTasks.clear(); - - // 4. Main Thread unlocks PGE Thread - { - std::lock_guard lock(pgeThreadPendingTasksMutex); - isPGEThreadResetting = false; // Release PGE thread - isMainThreadResetting = false; // Reset main thread flag - } - pgeThreadResetCondition.notify_all(); // Wake up PGE thread - return res; - } - -//------ Events Handlers ----- - - void Host_Apple_MacOS::MacApplicationEventsHandler() - { - pMacApplication->setWillFinishLaunchingCallback([]() { }); - - pMacApplication->setDidFinishLaunchingCallback([&]() { - // Queue the Create OpenGL context task - vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); - // We need to wait until the application has launched to get the keyboard layout - pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); - - - }); - - pMacApplication->setWillTerminateCallback([&]() { - //todo : add any cleanup code here if needed - }); - - pMacApplication->setDidBecomeActiveCallback([]() { }); - - pMacApplication->setWillResignActiveCallback([]() { }); - - } - - void Host_Apple_MacOS::MacWindowEventsHandler() - { - pMacOSWindow->setWindowDidResizeCallback([&]() { - AddPendingMainThreadTask(RESIZE_WINDOW); - }); - - pMacOSWindow->setWindowWillCloseCallback([&]() { - // NOTE: Do not add this event to PendingMainThreadTasks as it will cause deadlock since the main thread is required to process the close event but the close event is waiting on the main thread tasks to process it - pPGEwindow->olc_OnWindowClose(); - pPGEwindow->olc_ShouldRemove(); - }); - - pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { - AddPendingMainThreadTask(BECOME_ACTIVE); - }); - - pMacOSWindow->setWindowDidResignKeyCallback([&]() { - AddPendingMainThreadTask(RESIGN_ACTIVE); - }); - - pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { - AddPendingMainThreadTask(MINIMIZE_WINDOW); - }); - - pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { - AddPendingMainThreadTask(DEMINIMIZE_WINDOW); - }); - + + // now we need to work out the size + cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); + + for(auto& [key, data] : sTouchIDEvents) { + data.pressure = pressure; + data.nsTouchSize = cgsApproxTouchSize; + } + + } } +} + +// Handle left mouse down events +void view_mouseDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; // Remove unused parameter warnings - // handles both down and up strokes for every supported key that isn't a modifier - void Host_Apple_MacOS::KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed) - { - unsigned short keyCode = event.keyCode; - - // handle num clear/lock key only on the down stroke. - if(isPressed && keyCode == 71) - { - bNumLockActive = !bNumLockActive; - return; - } + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(!bNumLockActive) - { - // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - switch(keyCode) - { - case 84: keyCode = 125; break; - case 86: keyCode = 123; break; - case 88: keyCode = 124; break; - case 91: keyCode = 126; break; - default: break; - } - } + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B + if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) - keyCode = 50; - - pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], isPressed); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { + MouseEventData data = extractMouseEventData(event); + gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, + (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } } - - void Host_Apple_MacOS::MacEventsHandler() + else if (nPointerEventType == 1 || nPointerEventType == 2) { - // General MacOS key event handling code here - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + true, false, true, + gptrNSWindowEvents->stylusUserData); - // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - KeyboardEventHandler(event, true); - }); + } - pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - KeyboardEventHandler(event, false); - }); +} - // Set up keyboard flag event handlers - pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { +// Handle left mouse up events +void view_mouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - static unsigned int prevFlags = 0; - unsigned int changedFlags = event.modifierFlags ^ prevFlags; - - // Check For Shift key - if (changedFlags & NSEventModifierFlagShift) { - bool isPressed = event.modifierFlags & NSEventModifierFlagShift; - pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); - } - - // Check for Control key - if (changedFlags & NSEventModifierFlagControl) { - bool isPressed = event.modifierFlags & NSEventModifierFlagControl; - pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); - } + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if (changedFlags & NSEventModifierFlagCommand) { - bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; - if(isPressed) - std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; - - // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); - } + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B + if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { - // caps lock doesn't appear to trigger any event - prevFlags = event.modifierFlags; - }); + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] { + gptrNSWindowEvents->mouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, + (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + } + else if(nPointerEventType == 1 || nPointerEventType == 2) + { + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + false, true, true, + gptrNSWindowEvents->stylusUserData); - // Set up mouse event handlers - pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); - }); - - pMacOSEventHandler->onMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); - }); - - pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); - }); - - pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); - }); + } - pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); - }); +} - pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); - }); +// Handle mouse drag events +void view_mouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - pMacOSEventHandler->onRightMouseDown([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); - }); - - pMacOSEventHandler->onRightMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); - - }); + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - pMacOSEventHandler->onOtherMouseDown([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); - }); + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B + if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { - pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); - }); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { + MouseEventData data = extractMouseEventData(event); + gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + } + else if (nPointerEventType == 1 || nPointerEventType == 2) + { + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + true, false, true, + gptrNSWindowEvents->stylusUserData); + } - pMacOSEventHandler->onOtherMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); - }); +} - pMacOSEventHandler->onScrollWheel([&](const olc::apis::macos::ScrollWheelEvent& event) { - // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling - pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); - }); - // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch - pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - true, false, - {static_cast(event.sizeX), static_cast(event.sizeY)}); - }); +// Handle mouse movement events +void view_mouseMoved(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + // a Mouse move event and Stlus roximity Event have the same properties. + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + convertToContentViewCoordinates(location); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { + gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } + + +} - pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - false, false, - {static_cast(event.sizeX), static_cast(event.sizeY)}); - }); +// Handle right mouse down events +void view_rightMouseDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { + gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - false, true, - {static_cast(event.sizeX), static_cast(event.sizeY)}); - }); +// Handle right mouse up events +void view_rightMouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { + gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - false, true, // treat cancel as release - {static_cast(event.sizeX), static_cast(event.sizeY)}); - }); +// Handle right mouse drag events +void view_rightMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { + gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + +} + +// Handle other mouse button down events +void view_otherMouseDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { + gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - // Stylus (tablet) events — full pressure, tilt and rotation data - pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { - - pPGEwindow->olc_OnTouch( - event.touchID, - {static_cast(event.x), static_cast(event.y)}, - event.bPress, - event.bRelease, - {1.0f, 1.0f}, // stylus contact size — nominal 1x1 - true, // bStylus = true - event.pressure, - event.rotation, - {event.tiltX, event.tiltY} - ); - }); - +// Handle other mouse button up events +void view_otherMouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { + gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } +} +void view_otherMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { + gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } } +// Handle scroll wheel events +void view_scrollWheel(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + double deltaX = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaXSel); + double deltaY = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaYSel); -// Application consts selectors -static constexpr const char* kRespondsToSelector = "respondsToSelector:"; -static constexpr const char* kNSApplicationClass = "NSApplication"; -static constexpr const char* kNSWindowClass = "NSWindow"; -static constexpr const char* kNSStringClass = "NSString"; -static constexpr const char* kNSOpenGLPixelFormatClass = "NSOpenGLPixelFormat"; -static constexpr const char* kNSOpenGLViewClass = "NSOpenGLView"; -static constexpr const char* kNSObjectClass = "NSObject"; -static constexpr const char* kAppDelegateClass = "AppDelegate"; -static constexpr const char* kWindowDelegateClass = "WindowDelegate"; -static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; -static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; + convertToContentViewCoordinates(location); -// Application Screen management selectors -//static constexpr const char* kNSScreenClass = "NSScreen"; // Temp remove unused variable warning -static constexpr const char* kScreenSel = "screen"; -static constexpr const char* kNSCursorClass = "NSCursor"; -static constexpr const char* kUnhideSel = "unhide"; -static constexpr const char* kHideSel = "hide"; -static constexpr const char* kIsHiddenSel = "isHidden"; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { + gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} +// New events for mouse entered/exited +void view_mouseEntered(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Allow hiding cursor when mouse is inside window + bAllowHideCursor = true; + + // If the cursor is currently hidden, hide it again to ensure it stays hidden while inside the window + if(bHideCursor) + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); -// Application memory management selectors -static constexpr const char* kAllocSel = "alloc"; -static constexpr const char* kInitSel = "init"; -static constexpr const char* kSetDelegateSel = "setDelegate:"; -static constexpr const char* kReleaseSel = "release"; -static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; + convertToContentViewCoordinates(location); -// NSApplication lifecycle and management selectors -static constexpr const char* kSharedApplicationSel = "sharedApplication"; -static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; -static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; -static constexpr const char* kRunSel = "run"; -static constexpr const char* kStopSel = "stop:"; -static constexpr const char* kTerminateSel = "terminate:"; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { + gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// NSApplicationDelegate lifecycle methods -static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; -static constexpr const char* kApplicationDidFinishLaunchingSel = "applicationDidFinishLaunching:"; -static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; -static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; -static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; +void view_mouseExited(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Ensure cursor is visible when leaving window + bAllowHideCursor = false; + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); -// NSWindow creation, display, and management selectors -static constexpr const char* kInitWithContentRectSel = "initWithContentRect:styleMask:backing:defer:"; -static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; -static constexpr const char* kSetTitleSel = "setTitle:"; -static constexpr const char* kOrderFrontRegardlessSel = "orderFrontRegardless"; -static constexpr const char* kSetAcceptsMouseMovedEventsSel = "setAcceptsMouseMovedEvents:"; -static constexpr const char* kMakeFirstResponderSel = "makeFirstResponder:"; -static constexpr const char* kMakeKeyAndOrderFrontSel = "makeKeyAndOrderFront:"; -static constexpr const char* kMakeKeyWindowSel = "makeKeyWindow"; -static constexpr const char* kFrameSel = "frame"; -static constexpr const char* kSetFrameDisplaySel = "setFrame:display:"; -static constexpr const char* kSetFrameSel = "setFrame:"; -static constexpr const char* kStyleMaskSel = "styleMask"; -static constexpr const char* kToggleFullScreenSel = "toggleFullScreen:"; + convertToContentViewCoordinates(location); -// NSWindowDelegate lifecycle and event methods selectors -static constexpr const char* kWindowDidResizeSel = "windowDidResize:"; -static constexpr const char* kWindowWillCloseSel = "windowWillClose:"; -static constexpr const char* kWindowDidBecomeKeySel = "windowDidBecomeKey:"; -static constexpr const char* kWindowDidResignKeySel = "windowDidResignKey:"; -static constexpr const char* kWindowDidMiniaturizeSel = "windowDidMiniaturize:"; -static constexpr const char* kWindowDidDeminiaturizeSel = "windowDidDeminiaturize:"; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { + gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// NSResponder keyboard and mouse event methods selectors -static constexpr const char* kKeyDownSel = "keyDown:"; -static constexpr const char* kKeyUpSel = "keyUp:"; -static constexpr const char* kFlagsChangedSel = "flagsChanged:"; -static constexpr const char* kMouseDownSel = "mouseDown:"; -static constexpr const char* kMouseUpSel = "mouseUp:"; -static constexpr const char* kMouseDraggedSel = "mouseDragged:"; -static constexpr const char* kMouseMovedSel = "mouseMoved:"; -static constexpr const char* kRightMouseDownSel = "rightMouseDown:"; -static constexpr const char* kRightMouseUpSel = "rightMouseUp:"; -static constexpr const char* kRightMouseDraggedSel = "rightMouseDragged:"; -static constexpr const char* kOtherMouseDownSel = "otherMouseDown:"; -static constexpr const char* kOtherMouseUpSel = "otherMouseUp:"; -static constexpr const char* kOtherMouseDraggedSel = "otherMouseDragged:"; -static constexpr const char* kScrollWheelSel = "scrollWheel:"; -static constexpr const char* kDeltaXSel = "deltaX"; -static constexpr const char* kDeltaYSel = "deltaY"; -static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; -static constexpr const char* kMouseEnteredSel = "mouseEntered:"; -static constexpr const char* kMouseExitedSel = "mouseExited:"; -static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; -static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; -static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; -// Copied a lot of this code from iOS, will need to be tested -// NSResponder touch event method selectors -static constexpr const char* kSetAcceptsTouchEventsSel = "setAcceptsTouchEvents:"; -static constexpr const char* kSetWantsRestingTouchesSel = "setWantsRestingTouches:"; -static constexpr const char* kCGEventSel = "CGEvent"; -static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; -static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; -static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; -static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; +void view_touchesBegan(id self, SEL _cmd, id event) { + (void)_cmd; + + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseBegan, + gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); + } +} -// NSTouch / NSSet data extraction selectors -static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; -static constexpr const char* kAllObjectsSel = "allObjects"; -static constexpr const char* kNormalizedPositionSel = "normalizedPosition"; -static constexpr const char* kDeviceSizeSel = "deviceSize"; -static constexpr const char* kIdentitySel = "identity"; -static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; -static constexpr const char* kTouchCountSel = "count"; +void view_touchesMoved(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseMoved, + gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); + } +} -// NSResponder tablet / stylus event method selectors -static constexpr const char* kTabletPointSel = "tabletPoint:"; -static constexpr const char* kTabletProximitySel = "tabletProximity:"; -static constexpr const char* kPressureChangeSel = "pressureChangeWithEvent:"; -static constexpr const char* kStageSel = "stage"; +void view_touchesEnded(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseEnded, + gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); + } +} -// NSEvent tablet data extraction selectors -static constexpr const char* kPenPressureSel = "pressure"; -static constexpr const char* kPenRotationSel = "rotation"; -static constexpr const char* kPenTiltSel = "tilt"; -static constexpr const char* kPenIsEnteringProximitySel = "isEnteringProximity"; -static constexpr const char* kPointingDeviceTypeSel = "pointingDeviceType"; -static constexpr const char* kPenTangentialPressureSel = "tangentialPressure"; +void view_touchesCancelled(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseCancelled, + gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); + } +} +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) -// Managing first responder status and keyboard focus selectors -static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; -static constexpr const char* kBecomeFirstResponderSel = "becomeFirstResponder"; -static constexpr const char* kCanBecomeKeyViewSel = "canBecomeKeyView"; -static constexpr const char* kNeedsPanelToBecomeKeySel = "needsPanelToBecomeKey"; -static constexpr const char* kDrawRectSel = "drawRect:"; -static constexpr const char* kReshapeSel = "reshape"; -static constexpr const char* kUpdateSel = "update"; +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletProximity(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet proximity events here + BOOL entering = ((BOOL(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::penIsEnteringProximitySel); + // For furture support should call a stylusCallback with proximity event data, but for now just set the flag + gptrNSWindowEvents->bStylusInProximity = (entering == YES); + } +} + +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletPoint(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet point events here + } +} -// NSOpenGL pixel format, view, and context management selectors -static constexpr const char* kInitWithAttributesSel = "initWithAttributes:"; -static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame:pixelFormat:"; -static constexpr const char* kSetContentViewSel = "setContentView:"; -static constexpr const char* kContentViewSel = "contentView"; -static constexpr const char* kBoundsSel = "bounds"; -static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru -static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; -static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; -static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; -static constexpr const char* kBackingScaleFactorSel = "backingScaleFactor"; -static constexpr const char* kOpenGLContextSel = "openGLContext"; -static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; -static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; -static constexpr const char* kFlushBufferSel = "flushBuffer"; -static constexpr const char* kDisplaySel = "display"; -static constexpr const char* kCGLContextObjSel = "CGLContextObj"; -static constexpr const char* kSetValuesSel = "setValues:forParameter:"; -// Extracting data from NSEvent objects selectors -static constexpr const char* kKeyCodeSel = "keyCode"; -static constexpr const char* kCharactersSel = "characters"; -static constexpr const char* kLocationInWindowSel = "locationInWindow"; -static constexpr const char* kButtonNumberSel = "buttonNumber"; -static constexpr const char* kClickCountSel = "clickCount"; -static constexpr const char* kModifierFlagsSel = "modifierFlags"; -static constexpr const char* kUTF8StringSel = "UTF8String"; +void view_updateTrackingAreas(id self, SEL _cmd) { + (void)self;(void)_cmd; -// NSLocale class and method names -static constexpr const char* kNSLocaleClass = "NSLocale"; -static constexpr const char* kCurrentLocaleSel = "currentLocale"; -static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + // Create new tracking area + NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); + + // Correct tracking options: mouse entered/exited + active in key window + NSUInteger options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; + + Class NSTrackingAreaClass = objc_getClass(kTrackingAreaClass); + id trackingArea = ((id(*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSTrackingAreaClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithRectSel, bounds, options, self, nil); + + ((void(*)(id, SEL, id))objc_msgSend)(self, ObjectiveCSEL::addTrackingAreaSel, trackingArea); +} +// Determine if view can accept first responder status +id view_acceptsFirstResponder(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; // Return YES to accept first responder status +} -// Default values and configuration settings -static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; -static constexpr double kDefaultWindowWidth = 800.0; -static constexpr double kDefaultWindowHeight = 600.0; -// static constexpr int kBitsPerByte = 8; // Temp remove unused variable warning -static constexpr int kMinValidDimension = 0; -static constexpr int kRGBABytesPerPixel = 4; -static constexpr int kFullyOpaque = 255; -static constexpr int kZeroBytes = 0; -static constexpr int kZeroRows = 0; -static constexpr int kZeroWidth = 0; -static constexpr int kZeroHeight = 0; -static constexpr int kFlippedOffset = 1; -static constexpr int kNoButton = -1; -// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning -bool bAllowHideCursor = true; -bool bHideCursor = false; +// Make the view the first responder +id view_becomeFirstResponder(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; // Return YES to become first responder +} -// Objective-C method type encoding constants -// Type encoding for methods returning BOOL with no parameters: "c@:" -static constexpr const char* kBoolMethodTypeEncoding = "c@:"; +// Determine if the view can become key view +id view_canBecomeKeyView(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; +} -// Type encoding for void methods with event/notification parameter: "v@:@" -static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; +// Determine if the view needs the panel to become key +id view_needsPanelToBecomeKey(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; +} -// Type encoding for drawRect method with NSRect parameter: "v@:{NSRect={NSPoint=dd}{NSSize=dd}}" -static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"; +// Empty drawRect method - prevents automatic drawing on main thread +void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { + (void)self;(void)_cmd;(void)dirtyRect; +} -// Type encoding for void methods with no parameters: "v@:" -static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// Empty reshape method - prevents automatic context reshaping on main thread +void view_reshape(id self, SEL _cmd) { + (void)self;(void)_cmd; +} -// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" -static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; +// Empty update method - prevents automatic context updates on main thread +void view_update(id self, SEL _cmd) { + (void)self;(void)_cmd; +} -namespace ObjectiveCSEL { - -// Application memory management selectors - static SEL allocSel = nullptr; - static SEL initSel = nullptr; - static SEL setDelegateSel = nullptr; - static SEL releaseSel = nullptr; - static SEL isKindOfClassSel = nullptr; +// Create custom OpenGL view class +Class createCustomOpenGLViewClass() { + Class existingClass = objc_getClass(kCustomOpenGLViewClass); + if (existingClass) { + return existingClass; + } + + // Get NSOpenGLView class as superclass + Class NSOpenGLViewClass = objc_getClass(kNSOpenGLViewClass); + + // Create a new class inheriting from NSOpenGLView + Class CustomViewClass = objc_allocateClassPair(NSOpenGLViewClass, kCustomOpenGLViewClass, 0); + + if (!CustomViewClass) { + return NULL; + } + + // Keyboard event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::keyDownSel, (IMP)view_keyDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::keyUpSel, (IMP)view_keyUp, kEventHandlerMethodTypeEncoding); + + // Flags Changed event handler + class_addMethod(CustomViewClass, ObjectiveCSEL::flagsChangedSel, (IMP)view_flagsChanged, kEventHandlerMethodTypeEncoding); + + // Mouse event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDownSel, (IMP)view_mouseDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseUpSel, (IMP)view_mouseUp, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDraggedSel, (IMP)view_mouseDragged, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseMovedSel, (IMP)view_mouseMoved, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDownSel, (IMP)view_rightMouseDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseUpSel, (IMP)view_rightMouseUp, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDraggedSel, (IMP)view_rightMouseDragged, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDownSel, (IMP)view_otherMouseDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseUpSel, (IMP)view_otherMouseUp, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); + + // Touch event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)view_touchesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::pressureChangeSel, (IMP)view_pressureChange, kEventHandlerMethodTypeEncoding); + + // Tablet / stylus event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); + + // Area tracking for Mouse entered/exited event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseExitedSel, (IMP)view_mouseExited, kEventHandlerMethodTypeEncoding); + + // First responder methods + class_addMethod(CustomViewClass, ObjectiveCSEL::acceptsFirstResponderSel, (IMP)view_acceptsFirstResponder, kBoolMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::becomeFirstResponderSel, (IMP)view_becomeFirstResponder, kBoolMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::canBecomeKeyViewSel, (IMP)view_canBecomeKeyView, kBoolMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::needsPanelToBecomeKeySel, (IMP)view_needsPanelToBecomeKey, kBoolMethodTypeEncoding); - // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel = nullptr; - static SEL activateIgnoringOtherAppsSel = nullptr; - static SEL setActivationPolicySel = nullptr; - static SEL runSel = nullptr; - static SEL stopSel = nullptr; - static SEL terminateSEL = nullptr; + // DrawRect method + class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); - // Application Screen management selectors - static SEL screenSel = nullptr; - static SEL unhideSel = nullptr; - static SEL hideSel = nullptr; - static SEL isHiddenSel = nullptr; + // Block automatic OpenGL operations on main thread + class_addMethod(CustomViewClass, ObjectiveCSEL::reshapeSel, (IMP)view_reshape, kVoidMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::updateSel, (IMP)view_update, kVoidMethodTypeEncoding); - // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel = nullptr; - static SEL applicationDidFinishLaunchingSel = nullptr; - static SEL applicationWillTerminateSel = nullptr; - static SEL applicationDidBecomeActiveSel = nullptr; - static SEL applicationWillResignActiveSel = nullptr; + // Register the class with the runtime + objc_registerClassPair(CustomViewClass); + + return CustomViewClass; +} - // NSWindow creation, display, and management selectors - static SEL initWithContentRectSel = nullptr; - static SEL stringWithUTF8StringSel = nullptr; - static SEL setTitleSel = nullptr; - static SEL orderFrontRegardlessSel = nullptr; - static SEL setAcceptsMouseMovedEventsSel = nullptr; - static SEL makeKeyAndOrderFrontSel = nullptr; - static SEL makeKeyWindowSel = nullptr; - static SEL frameSel = nullptr; - static SEL setFrameDisplaySel = nullptr; - static SEL setFrameSel = nullptr; - static SEL makeFirstResponderSel = nullptr; - static SEL styleMaskSel = nullptr; - static SEL toggleFullScreenSel = nullptr; +// Forward declaration for C API function used by window event handlers +extern "C" void window_updateFrameFromOSX(Window* self); - // NSWindowDelegate lifecycle and event methods selectors - static SEL windowDidResizeSel = nullptr; - static SEL windowWillCloseSel = nullptr; - static SEL windowDidBecomeKeySel = nullptr; - static SEL windowDidResignKeySel = nullptr; - static SEL windowDidMiniaturizeSel = nullptr; - static SEL windowDidDeminiaturizeSel = nullptr; +// handle window resize events +void windowDidResize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; - // NSResponder keyboard and mouse event methods selectors - static SEL keyDownSel = nullptr; - static SEL keyUpSel = nullptr; - static SEL flagsChangedSel = nullptr; - static SEL mouseDownSel = nullptr; - static SEL mouseUpSel = nullptr; - static SEL mouseDraggedSel = nullptr; - static SEL mouseMovedSel = nullptr; - static SEL rightMouseDownSel = nullptr; - static SEL rightMouseUpSel = nullptr; - static SEL rightMouseDraggedSel = nullptr; - static SEL otherMouseDownSel = nullptr; - static SEL otherMouseUpSel = nullptr; - static SEL otherMouseDraggedSel = nullptr; - static SEL scrollWheelSel = nullptr; - static SEL deltaXSel = nullptr; - static SEL deltaYSel = nullptr; - static SEL updateTrackingAreasSel = nullptr; - static SEL mouseEnteredSel = nullptr; - static SEL mouseExitedSel = nullptr; + if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { + + // Update frame data only + window_updateFrameFromOSX(gptrWindowDelegate); + + // Get the new content view size + double width, height; + window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); - // Mouse Tracking - static SEL addTrackingAreaSel = nullptr; - static SEL initWithRectSel = nullptr; + if (gptrWindowDelegate->windowDidResizeCallback) { + gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); + } + + } +} - // Touch event selectors - static SEL setAcceptsTouchEventsSel = nullptr; - static SEL setWantsRestingTouchesSel = nullptr; - static SEL cgEventSel = nullptr; - static SEL touchesBeganSel = nullptr; - static SEL touchesMovedSel = nullptr; - static SEL touchesEndedSel = nullptr; - static SEL touchesCancelledSel = nullptr; - static SEL touchesMatchingPhaseSel = nullptr; - static SEL allObjectsSel = nullptr; - static SEL normalizedPositionSel = nullptr; - static SEL deviceSizeSel = nullptr; - static SEL identitySel = nullptr; - static SEL objectAtIndexSel = nullptr; - static SEL touchesCountSel = nullptr; +// handle window will close events +void windowWillClose(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + dispatch_async(dispatch_get_main_queue(), ^{ + // Ensure all pending events are processed before closing the window + gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window + gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window + if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { + gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); + } + }); +} - // Tablet / stylus event selectors - static SEL tabletPointSel = nullptr; - static SEL tabletProximitySel = nullptr; - static SEL penPressureSel = nullptr; - static SEL penRotationSel = nullptr; - static SEL penTiltSel = nullptr; - static SEL penIsEnteringProximitySel = nullptr; - static SEL pointingDeviceTypeSel = nullptr; - static SEL penTangentialPressureSel = nullptr; - static SEL pressureChangeSel = nullptr; - static SEL stageSel = nullptr; +// handle window did become key events +void windowDidBecomeKey(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidBecomeKeyCallback) { + gptrWindowDelegate->windowDidBecomeKeyCallback(gptrWindowDelegate->windowDidBecomeKeyUserData); + } +} - // Managing first responder status and keyboard focus selectors - static SEL acceptsFirstResponderSel = nullptr; - static SEL becomeFirstResponderSel = nullptr; - static SEL canBecomeKeyViewSel = nullptr; - static SEL needsPanelToBecomeKeySel = nullptr; - static SEL drawRectSel = nullptr; - static SEL reshapeSel = nullptr; - static SEL updateSel = nullptr; +// handle window did resign key events +void windowDidResignKey(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidResignKeyCallback) { + gptrWindowDelegate->windowDidResignKeyCallback(gptrWindowDelegate->windowDidResignKeyUserData); + } +} - // NSOpenGL pixel format, view, and context management selectors - static SEL initWithAttributesSel = nullptr; - static SEL initWithFramePixelFormatSel = nullptr; - static SEL setContentViewSel = nullptr; - static SEL contentViewSel = nullptr; - static SEL boundsSel = nullptr; - static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru - static SEL convertPointToBackingSel = nullptr; - static SEL convertPointFromBackingSel = nullptr; - static SEL convertPointFromViewSel = nullptr; - static SEL openGLContextSel = nullptr; - static SEL makeCurrentContextSel = nullptr; - static SEL setAutoresizingMaskSel = nullptr; - static SEL flushBufferSel = nullptr; - static SEL displaySel = nullptr; - static SEL CGLContextObjSel = nullptr; - static SEL setValuesSel = nullptr; - static SEL backingScaleFactorSel = nullptr; +// Handle window did miniaturize events +void windowDidMiniaturize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidMiniaturizeCallback) { + gptrWindowDelegate->windowDidMiniaturizeCallback(gptrWindowDelegate->windowDidMiniaturizeUserData); + } +} - // Extracting data from NSEvent objects selectors - static SEL keyCodeSel = nullptr; - static SEL charactersSel = nullptr; - static SEL locationInWindowSel = nullptr; - static SEL buttonNumberSel = nullptr; - static SEL clickCountSel = nullptr; - static SEL modifierFlagsSel = nullptr; - static SEL utf8StringSel = nullptr; +// Handle window did deminiaturize events +void windowDidDeminiaturize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidDeminiaturizeCallback) { + gptrWindowDelegate->windowDidDeminiaturizeCallback(gptrWindowDelegate->windowDidDeminiaturizeUserData); + } +} - // NSLocale selectors - static SEL currentLocaleSel = nullptr; - static SEL localeIdentifierSel = nullptr; - // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning - // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning +// Create and configure window delegate +id createWindowDelegate(OpenGLRenderer* renderer) { + // Get NSObject class as superclass using const string + Class NSObjectClass = objc_getClass(kNSObjectClass); + + // Create a new class inheriting from NSObject using const string + Class WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kWindowDelegateClass, kZeroBytes); + + // Add window delegate methods + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); - // Initialize all selectors - called once at startup - void initializeSelectors() { - if (allocSel) return; // Already initialized - - // Application memory management selectors - allocSel = sel_registerName(kAllocSel); - initSel = sel_registerName(kInitSel); - setDelegateSel = sel_registerName(kSetDelegateSel); - releaseSel = sel_registerName(kReleaseSel); - isKindOfClassSel = sel_registerName(kIsKindOfClassSel); + // Register the class with the runtime + objc_registerClassPair(WindowDelegateClass); + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + // Store renderer reference in delegate + struct WindowDelegate* windowDelegate = (struct WindowDelegate*)delegate; + windowDelegate->renderer = renderer; + + return delegate; +} - // NSApplication lifecycle and management selectors - sharedApplicationSel = sel_registerName(kSharedApplicationSel); - activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); - setActivationPolicySel = sel_registerName(kSetActivationPolicySel); - runSel = sel_registerName(kRunSel); - stopSel = sel_registerName(kStopSel); - terminateSEL = sel_registerName(kTerminateSel); +// Create a window delegate for general window events +id createWindowDelegateForWindow(void) { + + // Check if WindowDelegate class already exists to avoid duplicate registration + static Class WindowDelegateClass = NULL; + if (!WindowDelegateClass) { + // Get NSObject class as superclass using const string + Class NSObjectClass = objc_getClass(kNSObjectClass); - // Application Screen management selectors - screenSel = sel_registerName(kScreenSel); - unhideSel = sel_registerName(kUnhideSel); - hideSel = sel_registerName(kHideSel); - isHiddenSel = sel_registerName(kIsHiddenSel); + // Create a new class inheriting from NSObject using const string + WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kGeneralWindowDelegateClass, kZeroBytes); - // NSApplicationDelegate lifecycle methods - applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); - applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); - applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); - applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); - applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); + if (!WindowDelegateClass) { + return NULL; + } - // NSWindow creation, display, and management selectors - initWithContentRectSel = sel_registerName(kInitWithContentRectSel); - stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); - setTitleSel = sel_registerName(kSetTitleSel); - orderFrontRegardlessSel = sel_registerName(kOrderFrontRegardlessSel); - setAcceptsMouseMovedEventsSel = sel_registerName(kSetAcceptsMouseMovedEventsSel); - makeFirstResponderSel = sel_registerName(kMakeFirstResponderSel); - makeKeyAndOrderFrontSel = sel_registerName(kMakeKeyAndOrderFrontSel); - makeKeyWindowSel = sel_registerName(kMakeKeyWindowSel); - frameSel = sel_registerName(kFrameSel); - setFrameDisplaySel = sel_registerName(kSetFrameDisplaySel); - setFrameSel = sel_registerName(kSetFrameSel); - styleMaskSel = sel_registerName(kStyleMaskSel); - toggleFullScreenSel = sel_registerName(kToggleFullScreenSel); - - // NSWindowDelegate lifecycle and event methods selectors - windowDidResizeSel = sel_registerName(kWindowDidResizeSel); - windowWillCloseSel = sel_registerName(kWindowWillCloseSel); - windowDidBecomeKeySel = sel_registerName(kWindowDidBecomeKeySel); - windowDidResignKeySel = sel_registerName(kWindowDidResignKeySel); - windowDidMiniaturizeSel = sel_registerName(kWindowDidMiniaturizeSel); - windowDidDeminiaturizeSel = sel_registerName(kWindowDidDeminiaturizeSel); + // Window delegate methods + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); - // NSResponder keyboard and mouse event methods selectors - keyDownSel = sel_registerName(kKeyDownSel); - keyUpSel = sel_registerName(kKeyUpSel); - flagsChangedSel = sel_registerName(kFlagsChangedSel); - mouseDownSel = sel_registerName(kMouseDownSel); - mouseUpSel = sel_registerName(kMouseUpSel); - mouseDraggedSel = sel_registerName(kMouseDraggedSel); - mouseMovedSel = sel_registerName(kMouseMovedSel); - rightMouseDownSel = sel_registerName(kRightMouseDownSel); - rightMouseUpSel = sel_registerName(kRightMouseUpSel); - rightMouseDraggedSel = sel_registerName(kRightMouseDraggedSel); - otherMouseDownSel = sel_registerName(kOtherMouseDownSel); - otherMouseUpSel = sel_registerName(kOtherMouseUpSel); - otherMouseDraggedSel = sel_registerName(kOtherMouseDraggedSel); - scrollWheelSel = sel_registerName(kScrollWheelSel); - deltaXSel = sel_registerName(kDeltaXSel); - deltaYSel = sel_registerName(kDeltaYSel); - updateTrackingAreasSel = sel_registerName(kUpdateTrackingAreasSel); - mouseEnteredSel = sel_registerName(kMouseEnteredSel); - mouseExitedSel = sel_registerName(kMouseExitedSel); - addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); - initWithRectSel = sel_registerName(kInitWithRectSel); - - // Touch event selectors - setAcceptsTouchEventsSel = sel_registerName(kSetAcceptsTouchEventsSel); - setWantsRestingTouchesSel = sel_registerName(kSetWantsRestingTouchesSel); - cgEventSel = sel_registerName(kCGEventSel); - touchesBeganSel = sel_registerName(kTouchesBeganSel); - touchesMovedSel = sel_registerName(kTouchesMovedSel); - touchesEndedSel = sel_registerName(kTouchesEndedSel); - touchesCancelledSel = sel_registerName(kTouchesCancelledSel); - touchesMatchingPhaseSel = sel_registerName(kTouchesMatchingPhaseSel); - allObjectsSel = sel_registerName(kAllObjectsSel); - normalizedPositionSel = sel_registerName(kNormalizedPositionSel); - deviceSizeSel = sel_registerName(kDeviceSizeSel); - identitySel = sel_registerName(kIdentitySel); - objectAtIndexSel = sel_registerName(kObjectAtIndexSel); - touchesCountSel = sel_registerName(kTouchCountSel); + // Register the class with the runtime + objc_registerClassPair(WindowDelegateClass); + } + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + return delegate; +} - // Tablet / stylus event selectors - tabletPointSel = sel_registerName(kTabletPointSel); - tabletProximitySel = sel_registerName(kTabletProximitySel); - penPressureSel = sel_registerName(kPenPressureSel); - penRotationSel = sel_registerName(kPenRotationSel); - penTiltSel = sel_registerName(kPenTiltSel); - penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); - pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); - penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); - pressureChangeSel = sel_registerName(kPressureChangeSel); - stageSel = sel_registerName(kStageSel); + // Forward declarations for C API functions used by internal helpers + extern "C" void window_setDelegate(Window* self, id delegate); - // Managing first responder status and keyboard focus selectors - acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); - becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); - canBecomeKeyViewSel = sel_registerName(kCanBecomeKeyViewSel); - needsPanelToBecomeKeySel = sel_registerName(kNeedsPanelToBecomeKeySel); - drawRectSel = sel_registerName(kDrawRectSel); - reshapeSel = sel_registerName(kReshapeSel); - updateSel = sel_registerName(kUpdateSel); +extern "C" { + // Initialize the application + void application_initialize(Application* self) { - // NSOpenGL pixel format, view, and context management selectors - initWithAttributesSel = sel_registerName(kInitWithAttributesSel); - initWithFramePixelFormatSel = sel_registerName(kInitWithFramePixelFormatSel); - setContentViewSel = sel_registerName(kSetContentViewSel); - contentViewSel = sel_registerName(kContentViewSel); - boundsSel = sel_registerName(kBoundsSel); - convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru - convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); - convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); - convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); - openGLContextSel = sel_registerName(kOpenGLContextSel); - makeCurrentContextSel = sel_registerName(kMakeCurrentContextSel); - setAutoresizingMaskSel = sel_registerName(kSetAutoresizingMaskSel); - flushBufferSel = sel_registerName(kFlushBufferSel); - displaySel = sel_registerName(kDisplaySel); - CGLContextObjSel = sel_registerName(kCGLContextObjSel); - setValuesSel = sel_registerName(kSetValuesSel); - backingScaleFactorSel = sel_registerName(kBackingScaleFactorSel); + // Initialize cached selectors for performance + initialize_macos_api(); + + // Set global application reference for delegate callbacks + gptrApplicationDelegate = self; + + // Get NSApplication + Class NSApplicationClass = objc_getClass(kNSApplicationClass); + self->nsApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, ObjectiveCSEL::sharedApplicationSel); + + // Set activation policy to regular + ((void(*)(id, SEL, NSInteger))objc_msgSend)(self->nsApp, ObjectiveCSEL::setActivationPolicySel, NSApplicationActivationPolicyRegular); + + // Create and set application delegate + self->delegate = createApplicationDelegate(); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::setDelegateSel, self->delegate); + } - // Extracting data from NSEvent objects selectors - keyCodeSel = sel_registerName(kKeyCodeSel); - charactersSel = sel_registerName(kCharactersSel); - locationInWindowSel = sel_registerName(kLocationInWindowSel); - buttonNumberSel = sel_registerName(kButtonNumberSel); - clickCountSel = sel_registerName(kClickCountSel); - modifierFlagsSel = sel_registerName(kModifierFlagsSel); - utf8StringSel = sel_registerName(kUTF8StringSel); + // Activate the application + void application_activate(Application* self) { + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); + } - // NSLocale selectors - currentLocaleSel = sel_registerName(kCurrentLocaleSel); - localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + // Run the application event loop + void application_run(Application* self) { + ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); } - // Ensures we only initialize selectors once (Thread-safe) - void ensureInitialized() { - static std::once_flag initialized; - std::call_once(initialized, [&]() { - initializeSelectors(); - }); + // Request to OS to gracefully terminate the application (RAII compatible) + void application_stop(Application* self) { + if (self && self->nsApp) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::stopSel, self->nsApp); + } } -} -// Single initialization function called once at startup -void initialize_macos_api() { - ObjectiveCSEL::initializeSelectors(); -} + // Destroy the application + void application_destroy(Application* self) { + if (self) { + delete self; + } + } -// Concepts safety templates for Objective-C interactions -template -concept CallbackType = std::is_function_v>; // Checks if T is a function type + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } -template -concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) -// Autorelease pool management functions -extern "C" { - extern void* objc_autoreleasePoolPush(void); - extern void objc_autoreleasePoolPop(void* pool); -} + // Ensure window has a delegate for window events + void ensureWindowDelegate(struct Window* self) { -// Objective-C autorelease pools -class AutoreleasePool { -public: - AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} - ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } - - AutoreleasePool(const AutoreleasePool&) = delete; - AutoreleasePool& operator=(const AutoreleasePool&) = delete; - AutoreleasePool(AutoreleasePool&&) = delete; - AutoreleasePool& operator=(AutoreleasePool&&) = delete; + // If the windows is created but has no delegate, create and set one + if (self && !self->delegate) { + // Create a window delegate + id delegate = createWindowDelegateForWindow(); + if (delegate) { + // Set it on the window + window_setDelegate(self, delegate); + } + } + } -private: - void* pool_; -}; + // Application constructor + Application* application_init() { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized -using NSPoint = CGPoint; -using NSInteger = long; -using NSUInteger = unsigned long; + Application* app = new Application(); -// Forward declarations -struct Application; -struct Window; -struct OpenGLRenderer; -struct ApplicationDelegate; -struct CustomOpenGLView; -struct WindowDelegate; -struct ImageLoader; + // Assign the method pointers + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; -// enums for OpenGL pixel format and rendering capabilities -enum class NSOpenGLPixelFormatAttribute : uint16_t { - // Renderer selection - AllRenderers = 1, // Use all available renderers - RendererID = 70, // Specific renderer ID - NoRecovery = 72, // No recovery from renderer failures - Accelerated = 73, // Hardware accelerated rendering - AllowOfflineRenderers = 96, // Allow offline renderers - AcceleratedCompute = 97, // Accelerated compute support + return app; + } - // Buffer configuration - TripleBuffer = 3, // Triple buffering support - DoubleBuffer = 5, // Double buffering (recommended) - Stereo = 6, // Stereo buffering for 3D - AuxBuffers = 7, // Number of auxiliary buffers - BackingStore = 76, // Backing store configuration - - // Color and depth configuration - ColorSize = 8, // Total color buffer size (bits) - AlphaSize = 11, // Alpha channel size (bits) - DepthSize = 12, // Depth buffer size (bits) - StencilSize = 13, // Stencil buffer size (bits) - AccumSize = 14, // Accumulation buffer size (bits) - ColorFloat = 58, // Floating-point color buffer + // Create the NSWindow instance + void window_create(Window* self, unsigned long styleMask) { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - // Antialiasing configuration - Multisample = 59, // Multisampling antialiasing - Supersample = 60, // Supersampling antialiasing - SampleBuffers = 55, // Number of sample buffers - Samples = 56, // Samples per pixel - SampleAlpha = 61, // Sample alpha to coverage - AuxDepthStencil = 57, // Auxiliary depth/stencil buffer + // Get classes using const strings + Class NSWindowClass = objc_getClass(kNSWindowClass); + Class NSStringClass = objc_getClass(kNSStringClass); - // Policy configuration - MinimumPolicy = 51, // Minimum attribute policy - MaximumPolicy = 52, // Maximum attribute policy - ClosestPolicy = 74, // Closest match policy + // Create window + id windowAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSWindowClass, ObjectiveCSEL::allocSel); + + self->nsWindow = ((id(*)(id, SEL, NSRect, unsigned long, unsigned long, BOOL))objc_msgSend)( + windowAlloc, ObjectiveCSEL::initWithContentRectSel, self->windowFrame, styleMask, + NSBackingStoreBuffered, NSWindowCreateNow); // Use modern buffered backing store, create immediately + + // Set window title using stored title or default + const char* titleToUse = self->title ? self->title : kWindowTitle; + id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, titleToUse); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); - // Advanced configuration - OpenGLProfile = 99, // OpenGL profile selection - ScreenMask = 84, // Screen mask for multi-display - VirtualScreenCount = 128, // Number of virtual screens -}; + // Set the content view frame + NSRect contentFrame = {self->windowFrame.x, self->windowFrame.y, self->windowFrame.width, self->windowFrame.height}; + if (self->nsWindow) { + id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); + } + } -// Backward compatibility -static constexpr int NSOpenGLPFADoubleBuffer = static_cast(NSOpenGLPixelFormatAttribute::DoubleBuffer); -static constexpr int NSOpenGLPFAColorSize = static_cast(NSOpenGLPixelFormatAttribute::ColorSize); -static constexpr int NSOpenGLPFADepthSize = static_cast(NSOpenGLPixelFormatAttribute::DepthSize); -static constexpr int NSOpenGLPFAAccelerated = static_cast(NSOpenGLPixelFormatAttribute::Accelerated); -static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFormatAttribute::OpenGLProfile); -static constexpr int NSOpenGLPFASampleBuffers = static_cast(NSOpenGLPixelFormatAttribute::SampleBuffers); -static constexpr int NSOpenGLPFAMultisample = static_cast(NSOpenGLPixelFormatAttribute::Multisample); -static constexpr int NSOpenGLAllowOfflineRenderers = static_cast(NSOpenGLPixelFormatAttribute::AllowOfflineRenderers); -// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning + window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, + &self->contentViewFrame.width, &self->contentViewFrame.height); + } -// enum for OpenGL profile versions -enum class NSOpenGLProfile : int { - VersionLegacy = 0x1000, // Legacy OpenGL (deprecated) - Version3_2Core = 0x3200, // OpenGL 3.2 Core Profile - Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile -}; + // Show the window and set up event handling + void window_show(Window* self) { -// enum for OpenGL context parameters -enum NSOpenGLContextParameter : int { - NSOpenGLContextParameterSwapInterval = 222, - NSOpenGLContextParameterSurfaceOrder = 235, - NSOpenGLContextParameterSurfaceOpacity = 236, - NSOpenGLContextParameterSurfaceBackingSize = 237 -}; + // Show window + ((void(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::orderFrontRegardlessSel); + + // Enable mouse moved events + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setAcceptsMouseMovedEventsSel, YES); + + // Ensure window delegate is created and set + ensureWindowDelegate(self); + if (self->delegate) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, self->delegate); + } + + // Make the window key to ensure it can receive keyboard events + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::makeKeyAndOrderFrontSel, NULL); + } -// Backward compatibility -//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); -static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); + // Destroy the window + void window_destroy(Window* self) { -// enum for window appearance and behavior bit flags -enum class NSWindowStyleMask : uint16_t { - Titled = (1 << 0), // Window has a title bar - Closable = (1 << 1), // Window can be closed - Miniaturizable = (1 << 2), // Window can be minimized - Resizable = (1 << 3), // Window can be resized - UtilityWindow = (1 << 4), // Utility window style - DocModalWindow = (1 << 6), // Document-modal window - NonactivatingPanel = (1 << 7), // Non-activating panel - HUDWindow = (1 << 13), // Heads-up display window - TexturedBackground = (1 << 8), // Textured background - UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar - FullScreen = (1 << 14), // Full-screen window - FullSizeContentView = (1 << 15) // Full-size content view -}; + // Thanks Mr B! + if (self) { + self->acceptsInputEvents = NO; + if (gptrNSWindowEvents == self) { + gptrNSWindowEvents = NULL; + } + if (gptrWindowDelegate == self) { + gptrWindowDelegate = NULL; + } + } + // NSWindow will be cleaned up by autorelease pool + } -// Backward compatibility -// static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); // Temp remove unused variable warning -// static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); // Temp remove unused variable warning -// static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); // Temp remove unused variable warning -// static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); // Temp remove unused variable warning -static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); + // Window delegate setter + void window_setDelegate(Window* self, id delegate) { + self->delegate = delegate; + + // Set global window reference for delegate callbacks + gptrWindowDelegate = self; + + // Set the delegate on the actual NSWindow + if (self->nsWindow && delegate) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, delegate); + } + } -// enum for backing store types -enum class NSBackingStoreType : uint8_t { - Retained = 0, // Deprecated - backing store is retained - Nonretained = 1, // Deprecated - backing store is not retained - Buffered = 2 // Modern - double-buffered backing store (recommended) -}; + // Removes the delegate from the window and clears the reference to prevent accidental use after close + void window_removeDelegate(Window* self) { + + if (self->nsWindow) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, nil); + } + self->delegate = NULL; + + } -static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); + // Window title getter and setter + const char* window_getTitle(const Window* self) { + return self->title; + } -// enum for window creation timing options -enum class NSWindowDefer : uint8_t { - CreateNow = 0, // Create window immediately (NO/false) - DeferCreation = 1 // Defer window creation (YES/true) -}; + // Window title setter + void window_setTitle(Window* self, const char* title) { + self->title = title; -// Backward compatibility -static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); + if (self->nsWindow) { + Class NSStringClass = objc_getClass(kNSStringClass); -// enum for view autoresizing behavior flags -enum class NSAutoresizingMask : uint8_t { - NotSizable = 0, // View maintains fixed size - MinXMargin = 1, // Left margin is flexible - WidthSizable = 2, // Width changes with superview width - MaxXMargin = 4, // Right margin is flexible - MinYMargin = 8, // Bottom margin is flexible - HeightSizable = 16, // Height changes with superview height - MaxYMargin = 32 // Top margin is flexible -}; + id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); -// Backward compatibility -static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); -static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); + } + } -// enum for application activation and UI visibility policies -enum class NSApplicationActivationPolicy : uint8_t { - Regular = 0, // Normal app with dock icon and menu bar Recommended - Accessory = 1, // Utility app, no dock icon but can have menu bar - Prohibited = 2 // Background only, no UI elements -}; + // Refresh internal frame representation from actual NSWindow (OSX) + void window_updateFrameFromOSX(Window* self) { + self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); + } -// Backward compatibility -static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); + // Get the content view size (excludes title bar and borders) + void window_getContentViewFrame(const Window* self, double* x, double* y, double* width, double* height) { + if (!self || !self->nsWindow) { + if (x) *x = kMinValidDimension; + if (y) *y = kMinValidDimension; + if (width) *width = kDefaultWindowWidth; + if (height) *height = kDefaultWindowHeight; + return; + } + + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); + if (!contentView) { + if (x) *x = kMinValidDimension; + if (y) *y = kMinValidDimension; + if (width) *width = kDefaultWindowWidth; + if (height) *height = kDefaultWindowHeight; + return; + } + + // Thank you, Ben the ultimate Guru, + // Get the content view bounds in points (logical coordinates). + // On macOS, AppKit uses points rather than physical pixels. On HighDPI + // displays (that are scaling) a point maps to >1 pixels. + // convertRectToBacking: converts the point-based rect to physical pixels, + // which is what OpenGL expects + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + + if (x) *x = pixelBounds.x; + if (y) *y = pixelBounds.y; + if (width) *width = pixelBounds.width; + if (height) *height = pixelBounds.height; + + } -// Mouse Tracking const -static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; -static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; -static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; + // Get the content view size (excludes title bar and borders) + void window_setContentViewFrame(struct Window* self, double* x, double* y, double* width, double* height) { + if (!self || !self->nsWindow) { + if (x) *x = kMinValidDimension; + if (y) *y = kMinValidDimension; + if (width) *width = kDefaultWindowWidth; + if (height) *height = kDefaultWindowHeight; + return; + } -/* -* WARNING: Global delegate pointers for Objective-C callbacks -* These global pointers are used to route Objective-C delegate callbacks -* to the appropriate C++ structures and methods. -* -* In short: Don't mess with these unless you know what you're doing! -* -* Global variables are evil, but sometimes necessary for bridging C++ and Objective-C. -*/ -static Window* gptrNSWindowEvents = nullptr; -static Application* gptrApplicationDelegate = nullptr; -static Window* gptrWindowDelegate = nullptr; + // Set the content view frame + NSRect contentFrame = {x ? *x : 0, y ? *y : 0, width ? *width : 0, height ? *height : 0}; + if (self->nsWindow) { + id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); + } + } + } -// Handles NSApplication delegate methods and lifecycle events -struct ApplicationDelegate { - Class isa; // Objective-C class pointer (required) -}; + // Get Window frame (x, y, width, height) + void window_getWindowFrame(const Window* self, double* x, double* y, double* width, double* height) { + + window_updateFrameFromOSX(const_cast(self)); -// Custom NSOpenGLView subclass for handling input events -struct CustomOpenGLView { - Class isa; // Objective-C class pointer (required) - void* renderer; // C Pointer to associated renderer -}; + auto posX = self ? self->windowFrame.x : kMinValidDimension; + auto posY = self ? self->windowFrame.y : kMinValidDimension; + auto w = self ? self->windowFrame.width : kDefaultWindowWidth; + auto h = self ? self->windowFrame.height : kDefaultWindowHeight; + if (x) *x = posX; + if (y) *y = posY; + if (width) *width = w; + if (height) *height = h; + } -// Handles NSWindow delegate methods and window events -struct WindowDelegate { - Class isa; // Objective-C class pointer (required) - void* renderer; // Pointer to associated renderer -}; + // Get window position (x, y) + void window_getWindowPosition(const struct Window* self, double* x, double* y) { + window_getWindowFrame(self, x, y, nullptr, nullptr); + } -// Application management with member initialization -struct Application { - id nsApp{nullptr}; // NSApplication instance - id delegate{nullptr}; // Application delegate instance + // Get Window size (width, height) + void window_getWindowSize(const Window* self, double* width, double* height) { + window_getWindowFrame(self, nullptr, nullptr, width, height); + } + + // Set window frame (x, y, width, height) + void window_setWindowFrame(Window* self, double x, double y, double width, double height) { + if (self) { + // Update internal frame representation + self->windowFrame.x = x; + self->windowFrame.y = y; + self->windowFrame.width = width; + self->windowFrame.height = height; - // Callback function pointers - std::function willFinishLaunchingCallback{nullptr}; - std::function didFinishLaunchingCallback{nullptr}; - std::function willTerminateCallback{nullptr}; - std::function didBecomeActiveCallback{nullptr}; - std::function willResignActiveCallback{nullptr}; + // Create new NSRect for the frame + NSRect newFrame = {x, y, width, height}; + + // Set the frame on the actual NSWindow + if (self->nsWindow) { + ((void(*)(id, SEL, NSRect, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setFrameDisplaySel, newFrame, YES); + } + } + } - void* willFinishLaunchingUserData{nullptr}; // User data for will finish launching callback - void* didFinishLaunchingUserData{nullptr}; // User data for did finish launching callback - void* willTerminateUserData{nullptr}; // User data for will terminate callback - void* didBecomeActiveUserData{nullptr}; // User data for did become active callback - void* willResignActiveUserData{nullptr}; // User data for will resign active callback + // Set window position (x, y) + void window_setWindowPosition(Window* self, double x, double y) { + window_setWindowFrame(self, x, y, self->windowFrame.width, self->windowFrame.height); + } - // Method function pointers - void (*initialize) (struct Application* self){nullptr}; - void (*activate) (struct Application* self){nullptr}; - void (*run) (struct Application* self){nullptr}; - void (*destroy) (struct Application* self){nullptr}; - const char* (*getSystemLocale)(struct Application* self){nullptr}; - - Application() = default; - - ~Application() {} - - // Delete copy constructor and assignment - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - Application(Application&&) = default; - Application& operator=(Application&&) = default; -}; + // Set window size (width, height) + void window_setWindowSize(Window* self, double width, double height) { + window_setWindowFrame(self, self->windowFrame.x, self->windowFrame.y, width, height); + } -// Window management with member initialization -struct Window { - id nsWindow{nullptr}; // NSWindow instance - id delegate{nullptr}; // Window delegate instance - OpenGLRenderer* renderer{nullptr}; // Associated OpenGL renderer - NSRect windowFrame{}; // Window frame rectangle - NSRect contentViewFrame{}; // Content view frame rectangle - const char* title{nullptr}; // Window title string + // Set the cusor position relative to the content view + void window_setCursorPosition(Window* self, double x, double y) { + if (!self || !self->nsWindow) return; + + // Get current screen the window is on + id currentScreen = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::screenSel); + NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); + + // Update internal frame representation to get the latest window position + window_updateFrameFromOSX(self); + NSPoint location = {self->windowFrame.x, self->windowFrame.y}; + + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + + if (contentView) { + + // x,y are in physical backing pixels. CGWarpMouseCursorPosition + // and all AppKit frame/bounds values use logical points, so convert the incoming + // pixel position to points. + NSPoint pixelPos = { x, y }; + NSPoint pointPos = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( contentView, ObjectiveCSEL::convertPointFromBackingSel, pixelPos); + + // Get content bounds in points for clamping and Y-flip + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + + // NOTE: clamp to keep the cursor within the window content area + auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); + CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + + } + + } - // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; - void (*flagsChangedCallback) (unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseDraggedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*rightMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*rightMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*rightMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*otherMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*otherMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*otherMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseEnteredCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseExitedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*scrollWheelCallback) (double x, double y, double deltaX, double deltaY, unsigned int modifierFlags, void* userData){nullptr}; + // Set cursor visibility + void window_setCursorVisibility(Window* self, BOOL visible) { + if (!self || !self->nsWindow) return; + bHideCursor = !visible; + if (bHideCursor && bAllowHideCursor) { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + } else { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + } + } - // Touch event callback function pointers with nullptr initialization - void (*touchBeganCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void (*touchMovedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void (*touchEndedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void (*touchCancelledCallback)(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void* touchUserData{nullptr}; + void window_toggleFullScreen(Window* self) { + // Toggle fullscreen + ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); + } - // Stylus (tablet) event callback function pointer - void (*stylusCallback)(uint32_t touchID, double x, double y, - float pressure, float rotation, - float tiltX, float tiltY, - bool bPress, bool bRelease, bool bIsStylus, - void* userData){nullptr}; - void* stylusUserData{nullptr}; - bool bStylusInProximity{false}; // true while stylus is hovering near the surface - - void* eventUserData{nullptr}; // User data for event callbacks - BOOL acceptsInputEvents{NO}; // Whether the window accepts input events - - // Window delegate event callback function pointers with nullptr initialization - void (*windowDidResizeCallback) (void* userData){nullptr}; - void (*windowWillCloseCallback) (void* userData){nullptr}; - void (*windowDidBecomeKeyCallback) (void* userData){nullptr}; - void (*windowDidResignKeyCallback) (void* userData){nullptr}; - void (*windowDidMiniaturizeCallback) (void* userData){nullptr}; - void (*windowDidDeminiaturizeCallback)(void* userData){nullptr}; + bool window_isFullScreen(Window* self) { + unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); + return (mask & NSWindowStyleMaskFullScreen); + } - void* windowDidResizeUserData{nullptr}; // User data for window resize callback - void* windowWillCloseUserData{nullptr}; // User data for window will close callback - void* windowDidBecomeKeyUserData{nullptr}; // User data for window did become key callback - void* windowDidResignKeyUserData{nullptr}; // User data for window did resign key callback - void* windowDidMiniaturizeUserData{nullptr}; // User data for window did miniaturize callback - void* windowDidDeminiaturizeUserData{nullptr}; // User data for window did deminiaturize callback - - // Method function pointers with nullptr initialization - void (*create) (struct Window* self, unsigned long styleMask){nullptr}; - void (*show) (struct Window* self){nullptr}; - void (*destroy) (struct Window* self){nullptr}; - void (*setDelegate) (struct Window* self, id delegate){nullptr}; - void (*removeDelegate) (struct Window* self){nullptr}; - const char* (*getTitle) (const struct Window* self){nullptr}; - void (*setTitle) (struct Window* self, const char* title){nullptr}; + // Initialize OpenGL renderer + void opengl_initialize(OpenGLRenderer* self, Window* window) { + self->window = window; + window->renderer = self; + + // Get classes using const strings + Class NSOpenGLPixelFormatClass = objc_getClass(kNSOpenGLPixelFormatClass); + Class CustomOpenGLViewClass = createCustomOpenGLViewClass(); // Use our custom class + + unsigned int sampleBuffers = (self->MSAA_Samples > 0) ? 1 : 0; + std::vector preferredSamples = {32, 16, 8, 4, 2, 0}; + if(self->MSAA_Samples <= 0) { + // If no multisampling requested, only try 0 samples + preferredSamples.clear(); + preferredSamples.resize(1); + preferredSamples.push_back(0); + } - void (*setWindowSize) (struct Window* self, double width, double height){nullptr}; - void (*getWindowSize) (const struct Window* self, double* width, double* height){nullptr}; - void (*setWindowPosition) (struct Window* self, double x, double y){nullptr}; - void (*getWindowPosition) (const struct Window* self, double* x, double* y){nullptr}; - void (*setWindowFrame) (struct Window* self, double x, double y, double width, double height){nullptr}; - void (*getWindowFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; - void (*getCurrentFrame) (struct Window* self){nullptr}; - void (*getContentViewFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; - void (*setContentViewFrame) (struct Window* self, double* x, double* y, double* width, double* height){nullptr}; -}; + for(unsigned int samples: preferredSamples) + { + // Create pixel format attributes array using enum values + const unsigned int attrs[] = { + NSOpenGLPFADoubleBuffer, // Enable double buffering + NSOpenGLPFADepthSize, 32, // 32-bit depth buffer + NSOpenGLPFAColorSize, 24, // 24-bit color + NSOpenGLPFAAccelerated, // Hardware acceleration + NSOpenGLAllowOfflineRenderers, // Allow offline renderers + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, // OpenGL 4.1 Core Profile + NSOpenGLPFAMultisample, samples, // 0x --> 32X Multisampling + NSOpenGLPFASampleBuffers, sampleBuffers, // Number of sample buffers + 0 // null terminator + }; + + // Create pixel format + self->pixelFormat = ((id(*)(id, SEL, const unsigned int*))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSOpenGLPixelFormatClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithAttributesSel, attrs); + + // Check if pixel format was created successfully + if (self->pixelFormat) { + self->MSAA_Samples = samples; + break; // Successfully created pixel format + } + } + + // Create custom OpenGL view with event handling + NSRect glViewFrame = {0.0, 0.0, window->contentViewFrame.width, window->contentViewFrame.height}; + self->glView = ((id(*)(id, SEL, NSRect, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(CustomOpenGLViewClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithFramePixelFormatSel, glViewFrame, self->pixelFormat); + + // Set autoresizing mask to make the view resize with the window + unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); + + // Enable touch events (NSTouch) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + + // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); + + + } -// Modern OpenGL context management and rendering operations -struct OpenGLRenderer { - id pixelFormat{nullptr}; // NSOpenGLPixelFormat instance - id glView{nullptr}; // NSOpenGLView instance - id glContext{nullptr}; // NSOpenGLContext instance - unsigned int MSAA_Samples{OLC_MSAA_SAMPLES}; // Multisample anti-aliasing samples - Window* window{nullptr}; // Associated window pointer + // Set up OpenGL context and make view first responder + void opengl_setupContext(OpenGLRenderer* self) { + // Set the OpenGL view as the window's content view + ((void(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::setContentViewSel, self->glView); + + // Make the OpenGL context current + self->glContext = ((id(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::openGLContextSel); + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + + // Make sure the window is key first + ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); + + // Make the OpenGL view the first responder so it can receive keyboard and mouse events + ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); + + // Additional debug: check if view can become key view + ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); + } - // Method function pointers with nullptr initialization - void (*initialize) (struct OpenGLRenderer* self, Window* window){nullptr}; - void (*setupContext) (struct OpenGLRenderer* self){nullptr}; - void (*renderYellowBackground)(struct OpenGLRenderer* self){nullptr}; - void (*renderTexturedQuad) (struct OpenGLRenderer* self, unsigned int textureID){nullptr}; - void* (*getOpenGLContext) (const struct OpenGLRenderer* self){nullptr}; - void* (*getCGLContextObj) (struct OpenGLRenderer* self){nullptr}; - void* (*getCGLContextObjPtr) (struct OpenGLRenderer* self){nullptr}; - void (*makeCurrentContext) (struct OpenGLRenderer* self){nullptr}; - void (*setVsync) (struct OpenGLRenderer* self, BOOL enabled){nullptr}; - void (*destroy) (struct OpenGLRenderer* self){nullptr}; - bool (*resetContextForSize) (struct OpenGLRenderer* self, double width, double height){nullptr}; + // Implementation function + bool opengl_resetContextForSize(OpenGLRenderer* self, double width, double height) { + if (!self || !self->glContext || !self->glView) { + return false; + } + + // Make context current + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + + // Update the view frame Without folks like Mr B the Ultimate Teacher, this would have been tricky + NSRect pixelFrame = {0.0, 0.0, width, height}; + NSRect newFrame = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::convertRectFromBackingSel, pixelFrame); + ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); + + // Force context to reshape + ((void(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::reshapeSel); + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::updateSel); + + // Update viewport + glViewport(0, 0, (GLsizei)width, (GLsizei)height); + + // Clear buffers + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -}; + return true; + } -// Modern image loading and pixel data extraction -struct ImageLoader { - imageloader_pixel_t* pixelData{nullptr}; // Raw pixel data (RGBA format) - int width{kMinValidDimension}; // Image width in pixels - int height{kMinValidDimension}; // Image height in pixels - int bytesPerPixel{kZeroBytes}; // Number of bytes per pixel (typically 4 for RGBA) - int bytesPerRow{kZeroRows}; // Number of bytes per row - BOOL hasAlpha{NO}; // Whether image has alpha channel - - // Method function pointers with nullptr initialization - BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; - BOOL (*loadFromMemory) (struct ImageLoader* self, const uint8_t* data, size_t bytes); - void (*destroy) (struct ImageLoader* self){nullptr}; - unsigned char* (*getPixelData) (const struct ImageLoader* self){nullptr}; - void (*getImageInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel){nullptr}; - void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; - BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; - BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; - unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; -}; -// Called when application is about to finish launching -void applicationWillFinishLaunching(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; // Remove unused parameter warnings - if (gptrApplicationDelegate && gptrApplicationDelegate->willFinishLaunchingCallback) { - gptrApplicationDelegate->willFinishLaunchingCallback(gptrApplicationDelegate->willFinishLaunchingUserData); + // Get the underlying OpenGL context + void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { + return (void*)self->glContext; } -} -// Called when application has finished launching -void applicationDidFinishLaunching(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { - gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); + // Get the underlying CGLContextObj from the OpenGL renderer + void* opengl_getCGLContextObj(struct OpenGLRenderer* self) { + if (!self || !self->glContext) { return nullptr;} + + return (void*)((void*(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::CGLContextObjSel); + } + + // Get the underlying CGLContextObj (C API version) + void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { + return (void*)opengl_getCGLContextObj(self); } -} -// Called when application is about to terminate -void applicationWillTerminate(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->willTerminateCallback) { - gptrApplicationDelegate->willTerminateCallback(gptrApplicationDelegate->willTerminateUserData); + // Make the OpenGL context current + void opengl_makeCurrentContext(struct OpenGLRenderer* self) { + if (self && self->glContext) { + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + } } -} -// Called when application becomes active -void applicationDidBecomeActive(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->didBecomeActiveCallback) { - gptrApplicationDelegate->didBecomeActiveCallback(gptrApplicationDelegate->didBecomeActiveUserData); + // Enable or disable vertical synchronization (vsync) + void opengl_setVsync(OpenGLRenderer* self, BOOL enabled) { + if (!self || !self->glContext) { return; } + + // Make context current first + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + + // Set swap interval: 1 for vsync enabled, 0 for disabled + GLint swapInterval = enabled ? 1 : 0; + + // Use NSOpenGLContext setValues:forParameter: to set swap interval + const GLint parameter = NSOpenGLContextParameterSwapInterval; + ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } -} -// Called when application resigns active status -void applicationWillResignActive(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { - gptrApplicationDelegate->willResignActiveCallback(gptrApplicationDelegate->willResignActiveUserData); + // Destroy the OpenGL renderer + void opengl_destroy(OpenGLRenderer* self) { + if (self) { + // Release OpenGL resources + if (self->glContext) { + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::releaseSel); + } + free(self); + } } -} -// Create and configure application delegate -id createApplicationDelegate() { + // OpenGL renderer constructor + OpenGLRenderer* opengl_init() { - // Get NSObject class as superclass using const string - Class NSObjectClass = objc_getClass(kNSObjectClass); - - // Create a new class inheriting from NSObject using const string - Class AppDelegateClass = objc_allocateClassPair(NSObjectClass, kAppDelegateClass, 0); - - // Add application delegate methods - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillFinishLaunchingSel, (IMP)applicationWillFinishLaunching, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidFinishLaunchingSel, (IMP)applicationDidFinishLaunching, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillTerminateSel, (IMP)applicationWillTerminate, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidBecomeActiveSel, (IMP)applicationDidBecomeActive, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillResignActiveSel, (IMP)applicationWillResignActive, kEventHandlerMethodTypeEncoding); + OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); + renderer->pixelFormat = NULL; + renderer->glView = NULL; + renderer->glContext = NULL; + renderer->window = NULL; - // Register the class with the runtime - objc_registerClassPair(AppDelegateClass); - - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(AppDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); - - return delegate; -} + // Assign method pointers + renderer->initialize = opengl_initialize; + renderer->setupContext = opengl_setupContext; + renderer->getOpenGLContext = opengl_getOpenGLContext; + renderer->getCGLContextObj = opengl_getCGLContextObj; + renderer->getCGLContextObjPtr = opengl_getCGLContextObjPtr; + renderer->makeCurrentContext = opengl_makeCurrentContext; + renderer->setVsync = opengl_setVsync; + renderer->destroy = opengl_destroy; + renderer->resetContextForSize = opengl_resetContextForSize; -//====================================================================// -// Keyboard Event Handling + return renderer; + } -// Structure to hold common key event data -struct KeyEventData { - unsigned short keyCode = 0; - const char* characters = nullptr; - unsigned int modifierFlags = 0; -}; + static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + { + if(!image) return NO; -// Extract common key event data from NSEvent -KeyEventData extractKeyEventData(id event) { - KeyEventData data; - data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); - id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); - data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); - data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - return data; -} + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } - // Handle key down events -void view_keyDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); - KeyEventData data = extractKeyEventData(event); + //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning + //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning + //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning + + self->width = (int)CGImageGetWidth(image); + self->height = (int)CGImageGetHeight(image); + self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + const uint8_t* imageData = CFDataGetBytePtr(rawData); + self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); + if(!self->pixelData) + { + return NO; + } + memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); + + // NOTE from Moros1138 + // + // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far + // the most common case, so in practice this block of code will never + // be run. However, if we find that there is a need to adjust the + // pixel data, this will need fleshing out. + + /* + if(byteOrder != kCGBitmapByteOrder32Little) + { + int pixelCount = self->width * self->height; + for(int i = 0; i < pixelCount; ++i) + { + auto p = self->pixelData[i]; + + // TODO: detect byte order, adjust as appropriate + + self->pixelData[i] = p; + } + } + */ + + CFRelease(rawData); + CGImageRelease(image); + + return YES; } -} -// Handle key up events -void view_keyUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + + CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); + CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); + CFRelease(pathStr); - KeyEventData data = extractKeyEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + if(!url) + { + printf("loadImageFromFile: bad path '%s'\n", filePath); + return NO; + } -void view_flagsChanged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); + CFRelease(url); - unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + if(!src) + { + printf("loadImageFromFile: couldn't open '%s'\n", filePath); + return NO; + } + + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { - gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + return imageloader_decodeImage(self, image); } -} -//====================================================================// -// Mouse Event Handling + BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { + + CFDataRef cfData = CFDataCreateWithBytesNoCopy( + nullptr, + reinterpret_cast(data), + (CFIndex)bytes, + kCFAllocatorNull // we own the buffer, CF must not free it + ); -// All values are converted to physical backing pixels so they match the OpenGL -// viewport on HighDPI displays - AppKit reports locations in logical points, but -// PGE is in physical pixels. -void convertToContentViewCoordinates(NSPoint& location) { + if (!cfData) + { + printf("loadImageFromMemory: CFData creation failed\n"); + return NO; + } - if(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] - { - // Get the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { + CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); + CFRelease(cfData); - - // Convert from window coordinates to view coordinates (still logical points) - NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); - - // Scale from logical points to physical backing pixels - NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + if (!src) + { + printf("loadImageFromMemory: src creation failed\n"); + return NO; + } - // Get content bounds in physical pixels for Y-flip - NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); + + return imageloader_decodeImage(self, image); + } - // Flip Y coordinate from bottom-left to top-left - location.x = pixelLocation.x; - location.y = pixelBounds.height - pixelLocation.y; - - } + // Get raw pixel data pointer + unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return (unsigned char*)self->pixelData; } - else - { - // Fallback: use default window height - location.y = kDefaultWindowHeight - location.y; + + // Get image information + void imageloader_getImageInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; } -} -// Mouse event data structure to hold common mouse event information -struct MouseEventData { - NSPoint location = {0.0, 0.0}; - NSUInteger modifierFlags = 0; - NSInteger buttonNumber = 0; -}; + // Get detailed image format information + void imageloader_getDetailedInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; + if (bytesPerRow) *bytesPerRow = self->bytesPerRow; + if (hasAlpha) *hasAlpha = self->hasAlpha; + } -// Extract common mouse event data from NSEvent -MouseEventData extractMouseEventData(id event) { - MouseEventData data; - data.location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - data.modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - data.buttonNumber = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::buttonNumberSel); - - // Convert to content view coordinates and flip Y coordinate - convertToContentViewCoordinates(data.location); - return data; -} + // Check if image is loaded + BOOL imageloader_isLoaded(const struct ImageLoader* self) { + return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); + } -// Stylus event data structure to hold common stylus event information -struct StylusEventData { + // Get pixel at specific coordinates + BOOL imageloader_getPixel(const struct ImageLoader* self, int x, int y, + unsigned char* red, unsigned char* green, + unsigned char* blue, unsigned char* alpha) { + if (!imageloader_isLoaded(self) || x < kMinValidDimension || x >= self->width || y < kMinValidDimension || y >= self->height) { + return NO; + } + + // Calculate pixel offset (macOS uses bottom-left origin, so flip Y) + int flippedY = self->height - kFlippedOffset - y; + imageloader_pixel_t* pixel = self->pixelData + (flippedY * self->width) + x; - uint32_t nTabletDeviceID = 0; // Unique identifier for the tablet device - uint8_t nPointerType = 0; // Pointer type 0 = Mouse, 1 = Pen, 2 = Eraser - uint8_t nPointerEventType = 0; // Type of pointer 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event - NSPoint nspLocation = {0.0, 0.0}; // Location of the stylus event - double dPressure = 0.0; // Pressure applied by the stylus - double dTangentialPressure = 0.0; // Tangential pressure applied by the stylus - double dRotationRadians = 0.0; // Rotation of the stylus in radians - double dTiltX_radians = 0.0; // Tilt of the stylus in the X direction in radians - double dTiltY_radians = 0.0; // Tilt of the stylus in the Y direction in radians - uint16_t buttonMask = 0; // Bitmask for stylus buttons (MAX 16 buttons, 1 = pressed, 0 = released) + // Extract color components (assuming RGBA or RGB format) + if (red) *red = pixel->r; + if (green) *green = pixel->g; + if (blue) *blue = pixel->b; + if (alpha && self->bytesPerPixel >= kRGBABytesPerPixel) *alpha = pixel->a; + else if (alpha) *alpha = kFullyOpaque; // Fully opaque if no alpha channel + + return YES; + } -}; + // Cleanup ImageLoader resources + void imageloader_destroy(struct ImageLoader* self) { + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + self->width = kZeroWidth; + self->height = kZeroHeight; + self->bytesPerPixel = kZeroBytes; + self->bytesPerRow = kZeroRows; + self->hasAlpha = NO; + } + + // ImageLoader constructor + struct ImageLoader* imageloader_init() { + struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); + + // Initialize properties + loader->pixelData = NULL; + loader->width = kZeroWidth; + loader->height = kZeroHeight; + loader->bytesPerPixel = kZeroBytes; + loader->bytesPerRow = kZeroRows; + loader->hasAlpha = NO; + + // Assign method pointers + loader->loadFromFile = imageloader_loadFromFile; + loader->loadFromMemory = imageloader_loadFromMemory; + loader->destroy = imageloader_destroy; + loader->getPixelData = imageloader_getPixelData; + loader->getImageInfo = imageloader_getImageInfo; + loader->getDetailedInfo = imageloader_getDetailedInfo; + loader->isLoaded = imageloader_isLoaded; + loader->getPixel = imageloader_getPixel; + + return loader; + } -// Extract common mouse event data from NSEvent -StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + // Window constructor + Window* window_init(double x, double y, double width, double height) { + Window* window = (Window*)malloc(sizeof(Window)); + + // Initialize window properties + window->nsWindow = NULL; + window->delegate = NULL; + window->title = NULL; + + // Set initial window frame + window->windowFrame.x = x; + window->windowFrame.y = y; + window->windowFrame.width = width; + window->windowFrame.height = height; - StylusEventData data; - id cgEvent = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - data.nTabletDeviceID = (uint32_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventDeviceID); + // Set window content view frame + window->contentViewFrame.x = x; + window->contentViewFrame.y = y; + window->contentViewFrame.width = width; + window->contentViewFrame.height = height; - data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); - data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + // Initialize callback pointers + window->keyDownCallback = NULL; + window->keyUpCallback = NULL; + window->mouseDownCallback = NULL; + window->mouseUpCallback = NULL; + window->mouseMovedCallback = NULL; + window->mouseDraggedCallback = NULL; + window->rightMouseDownCallback = NULL; + window->rightMouseUpCallback = NULL; + window->otherMouseDownCallback = NULL; + window->otherMouseUpCallback = NULL; + window->scrollWheelCallback = NULL; - data.dPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointPressure); - data.dRotationRadians = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventRotation) * (M_PI / 180.0); - data.dTangentialPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventTangentialPressure); + // Event handling disabled by default + window->acceptsInputEvents = NO; - data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); + // Initialize window delegate callback pointers + window->windowDidResizeCallback = NULL; + window->windowWillCloseCallback = NULL; + window->windowDidBecomeKeyCallback = NULL; + window->windowDidResignKeyCallback = NULL; + window->windowDidMiniaturizeCallback = NULL; + window->windowDidDeminiaturizeCallback = NULL; + window->windowDidResizeUserData = NULL; + window->windowWillCloseUserData = NULL; + window->windowDidBecomeKeyUserData = NULL; + window->windowDidResignKeyUserData = NULL; + window->windowDidMiniaturizeUserData = NULL; + window->windowDidDeminiaturizeUserData = NULL; - double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); - double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + // Assign method pointers + window->create = window_create; + window->show = window_show; + window->destroy = window_destroy; + window->setDelegate = window_setDelegate; + window->removeDelegate = window_removeDelegate; + window->getTitle = window_getTitle; + window->setTitle = window_setTitle; + window->setWindowSize = window_setWindowSize; + window->getWindowSize = window_getWindowSize; + window->setWindowPosition = window_setWindowPosition; + window->getWindowPosition = window_getWindowPosition; + window->setWindowFrame = window_setWindowFrame; + window->getWindowFrame = window_getWindowFrame; + window->getCurrentFrame = window_updateFrameFromOSX; + window->getContentViewFrame = window_getContentViewFrame; + window->setContentViewFrame = window_setContentViewFrame; - // Clamp to safe math bounds (-1.0 to 1.0) to avoid domain errors in asin - tiltX_raw = std::fmax(-1.0, std::fmin(1.0, tiltX_raw)); - tiltY_raw = std::fmax(-1.0, std::fmin(1.0, tiltY_raw)); + return window; + } - // Convert to true physical angles in RADIANS -PI/2 to +PI/2 - data.dTiltX_radians = std::asin(tiltX_raw); - data.dTiltY_radians = std::asin(tiltY_raw); + // Event callback setter functions + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyDownCallback = callback; + self->eventUserData = userData; + } - // Convert to content view coordinates and flip Y coordinate - convertToContentViewCoordinates(data.nspLocation); + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyUpCallback = callback; + self->eventUserData = userData; + } - return data; -} + void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { + self->flagsChangedCallback = callback; + self->eventUserData = userData; + } -//====================================================================// -// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDownCallback = callback; + self->eventUserData = userData; + } + void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseUpCallback = callback; + self->eventUserData = userData; + } -// NSTouchPhase bitmask values -enum NSTouchPhase : NSUInteger { - NSTouchPhaseBegan = 1 << 0, - NSTouchPhaseMoved = 1 << 1, - NSTouchPhaseStationary = 1 << 2, - NSTouchPhaseEnded = 1 << 3, - NSTouchPhaseCancelled = 1 << 4, - NSTouchPhaseAny = ULONG_MAX -}; + void window_setMouseMovedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseMovedCallback = callback; + self->eventUserData = userData; + } -enum NSPressureStage : NSInteger { - NSPressureStageLight = 0, // Light press - NSPressureStageNormal = 1, // Normal press - NSPressureStageForce = 2 // Force click -}; + void window_setMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDraggedCallback = callback; + self->eventUserData = userData; + } -enum class ForceTouchPressure : int { - Light = 25, // 0.25 - Light touch - Normal = 50, // 0.50 - Normal press - Force = 100, // 1.00 - Force click -}; + void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDownCallback = callback; + self->eventUserData = userData; + } -// Returns the approximated trackpad touch size in pixels based on the linear force value (0.0 to 1.0) -CGSize getApproxTrackPadTouchSize(float linearForce){ - - CGSize cgTouchSize = {0.0, 0.0}; - const float mmToPoints = 72.0f / 25.4f; // ~2.834 points per mm - float minSizePoints = 2.0f * mmToPoints; // 3mm lower bound approx values from Google - float maxSizePoints = 20.0f * mmToPoints; // 16mm upper bound - - // Interpolate the finger footprint size within our point boundaries - float touchSizeInPoints = minSizePoints + (linearForce * (maxSizePoints - minSizePoints) / 2.0f); + void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDraggedCallback = callback; + self->eventUserData = userData; + } - // Retrieve the window scale factor (1.0 for Standard, 2.0 for Retina, 3.0 for bannana displays, etc.) - CGFloat pixelScale = 1.0f; - if (gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) { - // Get backing scale factor from the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { - pixelScale = ((CGFloat(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::backingScaleFactorSel); - } + void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseUpCallback = callback; + self->eventUserData = userData; } - cgTouchSize = CGSizeMake(touchSizeInPoints * pixelScale, touchSizeInPoints * pixelScale); - return cgTouchSize; -}; -// Mouse event data structure to hold common mouse event information -struct TouchEventData { - uint32_t touchID = 0; // Stable sequential touch ID - NSPoint nspLocation = {0.0,0.0}; // X coordinate in content-view pixel coordinates - double pressure = 0.0; // Pressure value - CGSize nsTouchSize = {0.0, 0.0}; // Touch Size X (not available on macOS trackpad) - CGSize cgDeviceSize = {0.0, 0.0}; // Device size (width, height) in ponits - bool bIsActive = true; // Touch is active (true for Began/Moved, false for Ended/Cancelled) - -}; + void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDownCallback = callback; + self->eventUserData = userData; + } -std::unordered_map sTouchIDEvents; -static std::unordered_map sTouchIDMap; -static uint32_t sNextTouchID = 0; -CGSize cgsApproxTouchSize{0.0, 0.0}; // Stores the approx width and height of a touch event in CG coordinates + void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseUpCallback = callback; + self->eventUserData = userData; + } -//Iterate through Touches, updates and fires the callback. -static void updateTouchData(id event, NSUInteger phase, - void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) -{ - if (!callback) return; - - NSUInteger count = 0; - id touchArray = nullptr; - id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); - - // Edge case, there can be an touch event before a pressure event, therefore we need to check if the approximate touch size has been initialized - if(cgsApproxTouchSize.width == 0.0 || cgsApproxTouchSize.height == 0.0) - { - cgsApproxTouchSize = getApproxTrackPadTouchSize(NSPressureStageNormal); + void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDraggedCallback = callback; + self->eventUserData = userData; } - - - if (touchCollection) - { - count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - - // Get current screen the window is on - id currentScreen = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::screenSel); - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); - - for (NSUInteger i = 0; i < count; ++i) { - - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - TouchEventData data; - - // normalizedPosition is NSPoint in [0,1] range on the trackpad surface - NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); - - // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) - data.nspLocation.x = normPos.x * screenFrame.width; - data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; - - // Get Device Size - data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); - // Approximate touch size based on pressure stage (macOS does not provide actual touch size) - data.nsTouchSize = cgsApproxTouchSize; - data.bIsActive = !bRemove; - // Assign a stable sequential uint32_t IDs same as iOS does - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - - auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); - if (inserted) ++sNextTouchID; - uint32_t tid = it->second; - data.touchID = tid; - sTouchIDEvents.emplace(key, data); - callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { + self->scrollWheelCallback = callback; + self->eventUserData = userData; + } - // Clean up map entry once the touch has ended or been cancelled - if (bRemove) - { - sTouchIDEvents.erase(key); - sTouchIDMap.erase(key); - } - - } - + void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseEnteredCallback = callback; + self->eventUserData = userData; } - // A bit painful, but we need to check of any keys that have been removed as the touchleave event happened outside of our application - std::vector vFoundKeys; - std::vector vMissingKeys; - touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, NSTouchPhaseAny, nil); - - // If we have a touch collection and the phase is not cancelled, we can check for missing keys, otherwise we will just remove all keys from the map - if (touchCollection && phase != NSTouchPhaseCancelled) - { - count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - for (NSUInteger i = 0; i < count; ++i) { - - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - vFoundKeys.push_back(key); + void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseExitedCallback = callback; + self->eventUserData = userData; + } - } + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; } - - // Find the missing keys and fire the touchEndedCallback for them - for(auto& [key, data] : sTouchIDEvents) { - - if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { - gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); - vMissingKeys.push_back(key); - } - + + void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchMovedCallback = callback; + self->touchUserData = userData; } - - // Remove the missing keys from the maps - for(auto& fKeys: vMissingKeys) { - sTouchIDEvents.erase(fKeys); - sTouchIDMap.erase(fKeys); + + void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchEndedCallback = callback; + self->touchUserData = userData; } - - vFoundKeys.clear(); vMissingKeys.clear(); - - // Finally reset the next touch ID if there are no active touches remaining - if (sTouchIDMap.size() == 0) sNextTouchID = 0; -} -// TODO move to new location -// Handle Force Touch pressure changes on trackpad (single-finger press) -void view_pressureChange(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - auto toNormalizedPressure = [](ForceTouchPressure pressure) -> double { - return static_cast(static_cast(pressure)) / 100.0; - }; - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - - // Get CGEvent to determine event subtype - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - - // A force touch trackpad event will have a subtype of 0 (default) or greater than 2 (tablet proximity) - if (nPointerEventType == kCGEventMouseSubtypeDefault || nPointerEventType > kCGEventMouseSubtypeTabletProximity) { + void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchCancelledCallback = callback; + self->touchUserData = userData; + } - // KRespondsToSelector - BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + void window_setStylusCallback(Window* self, + void (*callback)(uint32_t, double, double, float, float, float, float, bool, bool, bool, void*), + void* userData) + { + self->stylusCallback = callback; + self->stylusUserData = userData; + } - NSInteger stage = NSPressureStageNormal; - if(supportsStage) - { - // stage returns: 0 = normal click, 1 = light press, 2 = force click - stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); - } - - // Map stage to normalized pressure values as requested - double pressure = toNormalizedPressure(ForceTouchPressure::Normal); - - switch (stage) { - case NSPressureStageLight: - pressure = toNormalizedPressure(ForceTouchPressure::Light); - break; - case NSPressureStageNormal: - pressure = toNormalizedPressure(ForceTouchPressure::Normal); - break; - case NSPressureStageForce: - pressure = toNormalizedPressure(ForceTouchPressure::Force); - break; - default: - pressure = toNormalizedPressure(ForceTouchPressure::Normal); - break; - } - - // now we need to work out the size - cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); - - for(auto& [key, data] : sTouchIDEvents) { - data.pressure = pressure; - data.nsTouchSize = cgsApproxTouchSize; + void window_enableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = YES; + gptrNSWindowEvents = self; // Set global reference for event handlers + } + } + + void window_disableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = NO; + if (gptrNSWindowEvents == self) { + gptrNSWindowEvents = NULL; } - } } -} -// Handle left mouse down events -void view_mouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + // Window delegate callback setter functions + void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResizeCallback = callback; + self->windowDidResizeUserData = userData; - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - MouseEventData data = extractMouseEventData(event); - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowWillCloseCallback = callback; + self->windowWillCloseUserData = userData; } - else if (nPointerEventType == 1 || nPointerEventType == 2) - { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - true, false, true, - gptrNSWindowEvents->stylusUserData); + void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidBecomeKeyCallback = callback; + self->windowDidBecomeKeyUserData = userData; } -} + void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResignKeyCallback = callback; + self->windowDidResignKeyUserData = userData; + } -// Handle left mouse up events -void view_mouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidMiniaturizeCallback = callback; + self->windowDidMiniaturizeUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidDeminiaturizeCallback = callback; + self->windowDidDeminiaturizeUserData = userData; + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + // Application delegate callback setter functions + void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->willFinishLaunchingCallback = callback; + self->willFinishLaunchingUserData = userData; + } - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] { - gptrNSWindowEvents->mouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->didFinishLaunchingCallback = callback; + self->didFinishLaunchingUserData = userData; } - else if(nPointerEventType == 1 || nPointerEventType == 2) - { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - false, true, true, - gptrNSWindowEvents->stylusUserData); + void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { + self->willTerminateCallback = callback; + self->willTerminateUserData = userData; } -} + void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->didBecomeActiveCallback = callback; + self->didBecomeActiveUserData = userData; + } -// Handle mouse drag events -void view_mouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->willResignActiveCallback = callback; + self->willResignActiveUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); +} // extern "C" + +#endif + +#if !defined(OLC_USE_WXWIDGETS) + +#if OLC_HOST == OLC_HOST_NONE +namespace olc::host +{ + // Make OS Create a window frame, associated with olc::Window + bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + return true; + } + + // Make OS Close a window frame, associated with olc::Window + bool Host_None::CloseWindowFrame(olc::Window* pWindow) + { + return true; + } + + // Make OS Update a window frame title, associated with olc::Window + bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) + { + return true; + } + + // Get OS-specific window descriptor(s) for given olc::Window + std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) + { + return {}; + } + + // Wait for OS desktop refresh (for smooooth vsync) + bool Host_None::SyncWithDesktopComposite() + { + return true; + } + + // Force the mouse position in pixels relative to window + bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return true; + } + + // Show or hide mouse cursor for given window + bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return true; + } + + olc::KeyboardLayout Host_None::GetKeyboardLayout() const + { + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } + + // Called at very start of application + bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool Host_None::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + + systemActive = true; + + if(!OnSystemThreadStart()) + return pPrimaryPGE->OnPostContextEnd(); + + while(systemActive) + { + if(!OnSystemTick()) + { + StopSystem(); + } + } + + OnSystemThreadEnd(); - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + return pPrimaryPGE->OnPostContextEnd(); + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - MouseEventData data = extractMouseEventData(event); - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } - } - else if (nPointerEventType == 1 || nPointerEventType == 2) - { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - true, false, true, - gptrNSWindowEvents->stylusUserData); - } + // Called to stop the host, and shutdown all resources + bool Host_None::StopSystem() + { + systemActive = false; + return true; + } + + // Called at start of system event loop + bool Host_None::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + // Called to perform primary window update + bool Host_None::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_None::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_None::OnApplicationEnd() + { + return true; + } } +#endif +#if OLC_HOST == OLC_HOST_WINDOWS +namespace olc::host +{ + bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } -// Handle mouse movement events -void view_mouseMoved(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + bool Host_Windows_WinAPI::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // if it is a trackpad event, ignore it. - if (nPointerEventType > 2) return; - - // a Mouse move event and Stlus roximity Event have the same properties. - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { - gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } - - -} + // Mark system as active + systemActive = true; -// Handle right mouse down events -void view_rightMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } -// Handle right mouse up events -void view_rightMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { - gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } -// Handle right mouse drag events -void view_rightMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } - -} + // Blocking event loop on this thread - handles windows + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } -// Handle other mouse button down events -void view_otherMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); -// Handle other mouse button up events -void view_otherMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { - gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); + } -void view_otherMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + bool Host_Windows_WinAPI::StopSystem() + { + systemActive = false; + return true; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + bool Host_Windows_WinAPI::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } -// Handle scroll wheel events -void view_scrollWheel(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - double deltaX = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaXSel); - double deltaY = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaYSel); + bool Host_Windows_WinAPI::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } - convertToContentViewCoordinates(location); + bool Host_Windows_WinAPI::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { - gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + bool Host_Windows_WinAPI::OnApplicationEnd() + { + return true; + } -// New events for mouse entered/exited -void view_mouseEntered(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - - // Allow hiding cursor when mouse is inside window - bAllowHideCursor = true; - - // If the cursor is currently hidden, hide it again to ensure it stays hidden while inside the window - if(bHideCursor) - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); - - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { - gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE -void view_mouseExited(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - - // Ensure cursor is visible when leaving window - bAllowHideCursor = false; - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); - - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); + // Forward Declaration + static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { - gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get + // access to the actual host instance, which can more conveninetly process + // the event across multiple window instances + static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + // CreateWindowEx will fire a WM_CREATE event at the window, which we're + // not interested in, and this will occur before we've populated our + // linkage maps. We can detect for this condition here which means + // subsequent look-ups dont fail. NOTE: Do not assume WM_CREATE is the + // first message that is sent, its just one that we know is reliably sent. + if (uMsg == WM_CREATE) + { + // Associate the window's little blob of user memory with host iface + auto cfg = ((CREATESTRUCT*)lParam)->lpCreateParams; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cfg); + } + else + { + // If this returns a value, then the host iface should be responsible + // for handling the message. + auto host = (Host_Windows_WinAPI*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + if (host) + return host->OnWindowEvent(hWnd, uMsg, wParam, lParam); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } -void view_touchesBegan(id self, SEL _cmd, id event) { - (void)_cmd; - - // Ensure event is not null before parsing - if (!event) return; - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseBegan, - gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); - } -} + std::wstring Host_Windows_WinAPI::ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } -void view_touchesMoved(id self, SEL _cmd, id event) { - (void)_cmd; - // Ensure event is not null before parsing - if (!event) return; - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseMoved, - gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); - } -} + Host_Windows_WinAPI::Host_Windows_WinAPI() + { + // Map Windows Defined VK_ Codes to olc::KeyCodes + mapKeys[0x00] = Key::NONE; -void view_touchesEnded(id self, SEL _cmd, id event) { - (void)_cmd; - // Ensure event is not null before parsing - if (!event) return; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseEnded, - gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); - } -} + // Windows doesn't define A-Z + mapKeys[0x41] = Key::A; + mapKeys[0x42] = Key::B; + mapKeys[0x43] = Key::C; + mapKeys[0x44] = Key::D; + mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; + mapKeys[0x47] = Key::G; + mapKeys[0x48] = Key::H; + mapKeys[0x49] = Key::I; + mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; + mapKeys[0x4C] = Key::L; + mapKeys[0x4D] = Key::M; + mapKeys[0x4E] = Key::N; + mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; + mapKeys[0x51] = Key::Q; + mapKeys[0x52] = Key::R; + mapKeys[0x53] = Key::S; + mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; + mapKeys[0x56] = Key::V; + mapKeys[0x57] = Key::W; + mapKeys[0x58] = Key::X; + mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; -void view_touchesCancelled(id self, SEL _cmd, id event) { - (void)_cmd; - // Ensure event is not null before parsing - if (!event) return; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseCancelled, - gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); - } -} + // Windows doesnt define numeric keys + mapKeys[0x30] = Key::K0; + mapKeys[0x31] = Key::K1; + mapKeys[0x32] = Key::K2; + mapKeys[0x33] = Key::K3; + mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; + mapKeys[0x36] = Key::K6; + mapKeys[0x37] = Key::K7; + mapKeys[0x38] = Key::K8; + mapKeys[0x39] = Key::K9; -//====================================================================// -// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + // Function Keys + mapKeys[VK_F1] = Key::F1; + mapKeys[VK_F2] = Key::F2; + mapKeys[VK_F3] = Key::F3; + mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; + mapKeys[VK_F6] = Key::F6; + mapKeys[VK_F7] = Key::F7; + mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; + mapKeys[VK_F10] = Key::F10; + mapKeys[VK_F11] = Key::F11; + mapKeys[VK_F12] = Key::F12; -// This should only be called when a Stylus driver is not installed or it an Apple device -void view_tabletProximity(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - // Handle tablet proximity events here - BOOL entering = ((BOOL(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::penIsEnteringProximitySel); - // For furture support should call a stylusCallback with proximity event data, but for now just set the flag - gptrNSWindowEvents->bStylusInProximity = (entering == YES); - } -} + // Arrow Keys + mapKeys[VK_DOWN] = Key::DOWN; + mapKeys[VK_LEFT] = Key::LEFT; + mapKeys[VK_RIGHT] = Key::RIGHT; + mapKeys[VK_UP] = Key::UP; -// This should only be called when a Stylus driver is not installed or it an Apple device -void view_tabletPoint(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - // Handle tablet point events here - } -} + // Other Keys + mapKeys[VK_BACK] = Key::BACK; + mapKeys[VK_ESCAPE] = Key::ESCAPE; + mapKeys[VK_RETURN] = Key::ENTER; + mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; + mapKeys[VK_TAB] = Key::TAB; + mapKeys[VK_DELETE] = Key::DEL; + mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; + mapKeys[VK_PRIOR] = Key::PGUP; + mapKeys[VK_NEXT] = Key::PGDN; + mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; + mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + mapKeys[VK_MENU] = Key::ALT; + // Numpad + mapKeys[VK_NUMPAD0] = Key::NP0; + mapKeys[VK_NUMPAD1] = Key::NP1; + mapKeys[VK_NUMPAD2] = Key::NP2; + mapKeys[VK_NUMPAD3] = Key::NP3; + mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; + mapKeys[VK_NUMPAD6] = Key::NP6; + mapKeys[VK_NUMPAD7] = Key::NP7; + mapKeys[VK_NUMPAD8] = Key::NP8; + mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; + mapKeys[VK_ADD] = Key::NP_ADD; + mapKeys[VK_DIVIDE] = Key::NP_DIV; + mapKeys[VK_SUBTRACT] = Key::NP_SUB; + mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; -void view_updateTrackingAreas(id self, SEL _cmd) { - (void)self;(void)_cmd; + // OEM Keys + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + } - // Create new tracking area - NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); - - // Correct tracking options: mouse entered/exited + active in key window - NSUInteger options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; - - Class NSTrackingAreaClass = objc_getClass(kTrackingAreaClass); - id trackingArea = ((id(*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSTrackingAreaClass, ObjectiveCSEL::allocSel), - ObjectiveCSEL::initWithRectSel, bounds, options, self, nil); - - ((void(*)(id, SEL, id))objc_msgSend)(self, ObjectiveCSEL::addTrackingAreaSel, trackingArea); -} + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + olc_IgnoreUnused(bFullScreen); -// Determine if view can accept first responder status -id view_acceptsFirstResponder(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; // Return YES to accept first responder status -} + // The user created olc::Window object is the SSoT for what a window + // should look like, so get that sort of thing from there + olc::vi2d vWinPos = vWindowPos; + olc::vi2d vWinSize = vWindowSize; + + hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); -// Make the view the first responder -id view_becomeFirstResponder(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; // Return YES to become first responder -} + // Define WindowClass + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof(WNDCLASSEX); + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = hCursorDefault; + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = WINAPI_EventHandler; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0;// sizeof(this); // For static meta-info + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); + RegisterClassEx(&wc); -// Determine if the view can become key view -id view_canBecomeKeyView(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; -} + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); -// Determine if the view needs the panel to become key -id view_needsPanelToBecomeKey(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; -} + olc::vi2d vTopLeft = vWindowPos; -// Empty drawRect method - prevents automatic drawing on main thread -void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { - (void)self;(void)_cmd;(void)dirtyRect; -} + if (bFullScreen || pPrimaryPGE->config.bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + POINT olc_pt = { vWinPos.x, vWinPos.y }; + HMONITOR hmon = MonitorFromPoint(olc_pt, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return false; + vWinSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } -// Empty reshape method - prevents automatic context reshaping on main thread -void view_reshape(id self, SEL _cmd) { - (void)self;(void)_cmd; -} -// Empty update method - prevents automatic context updates on main thread -void view_update(id self, SEL _cmd) { - (void)self;(void)_cmd; -} + // Keep client size as requested + RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); -// Create custom OpenGL view class -Class createCustomOpenGLViewClass() { - Class existingClass = objc_getClass(kCustomOpenGLViewClass); - if (existingClass) { - return existingClass; - } - - // Get NSOpenGLView class as superclass - Class NSOpenGLViewClass = objc_getClass(kNSOpenGLViewClass); - - // Create a new class inheriting from NSOpenGLView - Class CustomViewClass = objc_allocateClassPair(NSOpenGLViewClass, kCustomOpenGLViewClass, 0); - - if (!CustomViewClass) { - return NULL; - } - - // Keyboard event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::keyDownSel, (IMP)view_keyDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::keyUpSel, (IMP)view_keyUp, kEventHandlerMethodTypeEncoding); - - // Flags Changed event handler - class_addMethod(CustomViewClass, ObjectiveCSEL::flagsChangedSel, (IMP)view_flagsChanged, kEventHandlerMethodTypeEncoding); - - // Mouse event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDownSel, (IMP)view_mouseDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseUpSel, (IMP)view_mouseUp, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDraggedSel, (IMP)view_mouseDragged, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseMovedSel, (IMP)view_mouseMoved, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDownSel, (IMP)view_rightMouseDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseUpSel, (IMP)view_rightMouseUp, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDraggedSel, (IMP)view_rightMouseDragged, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDownSel, (IMP)view_otherMouseDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseUpSel, (IMP)view_otherMouseUp, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); - - // Touch event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)view_touchesBegan, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::pressureChangeSel, (IMP)view_pressureChange, kEventHandlerMethodTypeEncoding); - - // Tablet / stylus event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); - - // Area tracking for Mouse entered/exited event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseExitedSel, (IMP)view_mouseExited, kEventHandlerMethodTypeEncoding); - - // First responder methods - class_addMethod(CustomViewClass, ObjectiveCSEL::acceptsFirstResponderSel, (IMP)view_acceptsFirstResponder, kBoolMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::becomeFirstResponderSel, (IMP)view_becomeFirstResponder, kBoolMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::canBecomeKeyViewSel, (IMP)view_canBecomeKeyView, kBoolMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::needsPanelToBecomeKeySel, (IMP)view_needsPanelToBecomeKey, kBoolMethodTypeEncoding); + // +1 Hack to remove black bar between client and title bar for "perfect" window + // sizes anyway. This could be DPI related on later windows, but this makes it look + // tidier for "normal" applications + int width = rWndRect.right - rWndRect.left + 1; + int height = rWndRect.bottom - rWndRect.top; - // DrawRect method - class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); + // Create the actual OS window, return a handle + HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); - // Block automatic OpenGL operations on main thread - class_addMethod(CustomViewClass, ObjectiveCSEL::reshapeSel, (IMP)view_reshape, kVoidMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::updateSel, (IMP)view_update, kVoidMethodTypeEncoding); + // Update window size to match actual client area given. In situations where the window + // is clamped to the desktop, the client area may be smaller than requested. + RECT rClient; + GetClientRect(hWnd, &rClient); + pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); - // Register the class with the runtime - objc_registerClassPair(CustomViewClass); - - return CustomViewClass; -} + // Hide the close button if the user requested it, but only after styles are applied, + if (!pPrimaryPGE->config.bShowWindowCloseButton) + { + HMENU hMenu = GetSystemMenu(hWnd, FALSE); + DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + } -// Forward declaration for C API function used by window event handlers -extern "C" void window_updateFrameFromOSX(Window* self); + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); -// handle window resize events -void windowDidResize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; + pWindow->olc_OnFocus(true); - if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); + //ShowWindow(hWnd, 1); + //UpdateWindow(hWnd); - // Update frame data only - window_updateFrameFromOSX(gptrWindowDelegate); + // Now... awkwardly, the above has already fired off some window messages + // and they arent necessarily in a consistent order. Whereas one might + // assume WM_CREATE would be the first, there are some others on more + // modern systems. This is awkward because we havent yet associated the + // source window with a long_ptr to this class, and therefore we can't + // call the appropriate event handler. - // Get the new content view size - double width, height; - window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); + // Store the link bewteen host resource and window + mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); + mapHWND2PTR.insert_or_assign(hWnd, pWindow); - if (gptrWindowDelegate->windowDidResizeCallback) { - gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); - } - - } -} + -// handle window will close events -void windowWillClose(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - dispatch_async(dispatch_get_main_queue(), ^{ - // Ensure all pending events are processed before closing the window - gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window - gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window - if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { - gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); - } - }); -} -// handle window did become key events -void windowDidBecomeKey(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidBecomeKeyCallback) { - gptrWindowDelegate->windowDidBecomeKeyCallback(gptrWindowDelegate->windowDidBecomeKeyUserData); - } -} + //DragAcceptFiles(olc_hWnd, true); -// handle window did resign key events -void windowDidResignKey(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidResignKeyCallback) { - gptrWindowDelegate->windowDidResignKeyCallback(gptrWindowDelegate->windowDidResignKeyUserData); - } -} + return true; + } -// Handle window did miniaturize events -void windowDidMiniaturize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidMiniaturizeCallback) { - gptrWindowDelegate->windowDidMiniaturizeCallback(gptrWindowDelegate->windowDidMiniaturizeUserData); - } -} + bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) + { + DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); + return false; + } -// Handle window did deminiaturize events -void windowDidDeminiaturize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidDeminiaturizeCallback) { - gptrWindowDelegate->windowDidDeminiaturizeCallback(gptrWindowDelegate->windowDidDeminiaturizeUserData); - } -} + bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) + { +#ifdef UNICODE + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); +#else + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); +#endif + return true; + } -// Create and configure window delegate -id createWindowDelegate(OpenGLRenderer* renderer) { - // Get NSObject class as superclass using const string - Class NSObjectClass = objc_getClass(kNSObjectClass); - - // Create a new class inheriting from NSObject using const string - Class WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kWindowDelegateClass, kZeroBytes); - - // Add window delegate methods - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); + std::vector Host_Windows_WinAPI::GetHostWindowDescriptor(olc::Window* pWindow) + { + return { mapUID2HWND[pWindow->GetUID()] }; + } - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); - - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); - - // Store renderer reference in delegate - struct WindowDelegate* windowDelegate = (struct WindowDelegate*)delegate; - windowDelegate->renderer = renderer; - - return delegate; -} + bool Host_Windows_WinAPI::SyncWithDesktopComposite() + { + return DwmFlush() == S_OK; + } -// Create a window delegate for general window events -id createWindowDelegateForWindow(void) { + bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + POINT pt; + pt.x = vPos.x; + pt.y = vPos.y; + ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); + SetCursorPos(pt.x, pt.y); + return true; + } - // Check if WindowDelegate class already exists to avoid duplicate registration - static Class WindowDelegateClass = NULL; - if (!WindowDelegateClass) { - // Get NSObject class as superclass using const string - Class NSObjectClass = objc_getClass(kNSObjectClass); - - // Create a new class inheriting from NSObject using const string - WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kGeneralWindowDelegateClass, kZeroBytes); + bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); - if (!WindowDelegateClass) { - return NULL; - } + hCursorNow = bVisible ? hCursorDefault : NULL; - // Window delegate methods - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); + // Fire fake move event to update cursor visibility immediately + POINT p; + GetCursorPos(&p); + SetCursorPos(p.x, p.y + 1); + SetCursorPos(p.x, p.y); + return true; + } - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); - } - - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); - - return delegate; -} + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); - // Forward declarations for C API functions used by internal helpers - extern "C" void window_setDelegate(Window* self, id delegate); + if (bFullScreen) + { + // Maximise, make on top, remove border and titlebar + SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); + ShowWindow(hWnd, SW_MAXIMIZE); + } + else + { + // Restore original window style and position + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + // Get the style we should have based on the window config + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); -extern "C" { - // Initialize the application - void application_initialize(Application* self) { + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | dwStyle); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | dwExStyle); + ShowWindow(hWnd, SW_NORMAL); + } - // Initialize cached selectors for performance - initialize_macos_api(); - - // Set global application reference for delegate callbacks - gptrApplicationDelegate = self; - - // Get NSApplication - Class NSApplicationClass = objc_getClass(kNSApplicationClass); - self->nsApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, ObjectiveCSEL::sharedApplicationSel); - - // Set activation policy to regular - ((void(*)(id, SEL, NSInteger))objc_msgSend)(self->nsApp, ObjectiveCSEL::setActivationPolicySel, NSApplicationActivationPolicyRegular); - - // Create and set application delegate - self->delegate = createApplicationDelegate(); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::setDelegateSel, self->delegate); - } + UpdateWindow(hWnd); + SetForegroundWindow(hWnd); + SetFocus(hWnd); + SetActiveWindow(hWnd); + return true; + } - // Activate the application - void application_activate(Application* self) { - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); - } + DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) + { + olc_IgnoreUnused(pWindow); - // Run the application event loop - void application_run(Application* self) { - ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); - } + DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx - // Request to OS to gracefully terminate the application (RAII compatible) - void application_stop(Application* self) { - if (self && self->nsApp) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::stopSel, self->nsApp); - } - } + // Note for Microsoft: if you hide the border, it hides the title bar too, and via versa - // Destroy the application - void application_destroy(Application* self) { - if (self) { - delete self; - } - } + // For fullscreen,borderless/noTitlebar we want to skip all the window furniture and just have a big ol canvas + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return dwStyle |= WS_POPUP; - // Get system locale identifier - const char* application_getSystemLocale(Application* self) { - (void)self; - - // Get NSLocale class - Class NSLocaleClass = objc_getClass(kNSLocaleClass); - - // Get current locale - id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); - - // Get locale identifier - id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); - - // Convert to C string (en-US, en-GB, en-IE etc) - const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); - - return localeIdentifier; - } + // If any max/min/close button(s) display the button menu + if (pPrimaryPGE->config.bShowWindowCloseButton || pPrimaryPGE->config.bShowWindowMaximiseButton || pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_SYSMENU; + if (pPrimaryPGE->config.bShowWindowTilebar) dwStyle |= WS_CAPTION; // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) dwStyle |= WS_BORDER; // Add a border + if (pPrimaryPGE->config.bResizeable) dwStyle |= WS_THICKFRAME; // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_MINIMIZEBOX; // Add Min Button + if (pPrimaryPGE->config.bShowWindowMaximiseButton) dwStyle |= WS_MAXIMIZEBOX; // Add Max Button + // Note: Close button is handled after dwStlyes are applied - // Ensure window has a delegate for window events - void ensureWindowDelegate(struct Window* self) { + return dwStyle; - // If the windows is created but has no delegate, create and set one - if (self && !self->delegate) { - // Create a window delegate - id delegate = createWindowDelegateForWindow(); - if (delegate) { - // Set it on the window - window_setDelegate(self, delegate); - } - } - } - // Application constructor - Application* application_init() { + } + + LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + if (!mapHWND2PTR.contains(hWnd)) + return DefWindowProc(hWnd, uMsg, wParam, lParam);; - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + // Get target olc::Window + const auto& window = mapHWND2PTR.at(hWnd); - Application* app = new Application(); + // Many WinAPI events are literally ancient these days, so need some interpretation + // to get to the useful data. - // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; - app->getSystemLocale = application_getSystemLocale; + switch (uMsg) + { + case WM_MOUSEMOVE: // Mouse has moved within a window + { + // Extract mouse X & Y + uint16_t x = uint16_t(lParam & 0xFFFF); + uint16_t y = uint16_t((lParam >> 16) & 0xFFFF); + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + // Tell window new mouse location + window->olc_OnMouseMove(olc::vi2d{ ix, iy }); + break; + } + - return app; - } + // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_SIZE: + { + window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); + break; + } - // Create the NSWindow instance - void window_create(Window* self, unsigned long styleMask) { - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + case WM_MOUSEWHEEL: + { + window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; + } - // Get classes using const strings - Class NSWindowClass = objc_getClass(kNSWindowClass); - Class NSStringClass = objc_getClass(kNSStringClass); + case WM_ACTIVATE: + { + window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); + return 0; + } - // Create window - id windowAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSWindowClass, ObjectiveCSEL::allocSel); - - self->nsWindow = ((id(*)(id, SEL, NSRect, unsigned long, unsigned long, BOOL))objc_msgSend)( - windowAlloc, ObjectiveCSEL::initWithContentRectSel, self->windowFrame, styleMask, - NSBackingStoreBuffered, NSWindowCreateNow); // Use modern buffered backing store, create immediately + case WM_MOUSEACTIVATE: + { + window->olc_OnFocus(true); + return MA_ACTIVATE; + } - // Set window title using stored title or default - const char* titleToUse = self->title ? self->title : kWindowTitle; - id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, titleToUse); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); + case WM_SETFOCUS: + { + window->olc_OnFocus(true); + return 0; + } - // Set the content view frame - NSRect contentFrame = {self->windowFrame.x, self->windowFrame.y, self->windowFrame.width, self->windowFrame.height}; - if (self->nsWindow) { - id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { - ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); - } - } + case WM_KILLFOCUS: + { + window->olc_OnFocus(false); + return 0; + } - window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, - &self->contentViewFrame.width, &self->contentViewFrame.height); - } + case WM_KEYDOWN: + { + window->olc_OnFocus(true); + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } - // Show the window and set up event handling - void window_show(Window* self) { + case WM_KEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } - // Show window - ((void(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::orderFrontRegardlessSel); - - // Enable mouse moved events - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setAcceptsMouseMovedEventsSel, YES); - - // Ensure window delegate is created and set - ensureWindowDelegate(self); - if (self->delegate) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, self->delegate); - } - - // Make the window key to ensure it can receive keyboard events - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::makeKeyAndOrderFrontSel, NULL); - } + case WM_SYSKEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } - // Destroy the window - void window_destroy(Window* self) { - (void)self; - // NSWindow will be cleaned up by autorelease pool - } + case WM_SYSKEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } - // Window delegate setter - void window_setDelegate(Window* self, id delegate) { - self->delegate = delegate; - - // Set global window reference for delegate callbacks - gptrWindowDelegate = self; - - // Set the delegate on the actual NSWindow - if (self->nsWindow && delegate) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, delegate); - } - } + case WM_LBUTTONDOWN: + { + window->olc_OnMouseButton(0, true); + break; + } + case WM_LBUTTONUP: + { + window->olc_OnMouseButton(0, false); + break; + } + case WM_RBUTTONDOWN: + { + window->olc_OnMouseButton(1, true); + break; + } + case WM_RBUTTONUP: + { + window->olc_OnMouseButton(1, false); + break; + } + case WM_MBUTTONDOWN: + { + window->olc_OnMouseButton(2, true); + break; + } + case WM_MBUTTONUP: + { + window->olc_OnMouseButton(2, false); + break; + } + case WM_XBUTTONDOWN: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, true); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, true); + } + + break; + } + case WM_XBUTTONUP: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, false); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, false); + } + + break; + } - // Removes the delegate from the window and clears the reference to prevent accidental use after close - void window_removeDelegate(Window* self) { - - if (self->nsWindow) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, nil); - } - self->delegate = NULL; - - } + case WM_POINTERDOWN: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + true, + false, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + true, + false, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + + } - // Window title getter and setter - const char* window_getTitle(const Window* self) { - return self->title; - } + break; + } - // Window title setter - void window_setTitle(Window* self, const char* title) { - self->title = title; + case WM_POINTERUP: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + true, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + true, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + } - if (self->nsWindow) { - Class NSStringClass = objc_getClass(kNSStringClass); + break; + } - id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); + case WM_POINTERUPDATE: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + false, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + if (IS_POINTER_INCONTACT_WPARAM(wParam)) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + false, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + } + } + break; + } - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); - } - } - // Refresh internal frame representation from actual NSWindow (OSX) - void window_updateFrameFromOSX(Window* self) { - self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); - } + // case WM_DROPFILES: + // { + // // This is all eww... + // HDROP drop = (HDROP)wParam; + // + // uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); + // std::vector vFiles; + // for (uint32_t i = 0; i < nFiles; i++) + // { + // TCHAR dfbuffer[256]{}; + // uint32_t len = DragQueryFile(drop, i, nullptr, 0); + // DragQueryFile(drop, i, dfbuffer, 256); + //#ifdef UNICODE + //#ifdef __MINGW32__ + // char* buffer = new char[len + 1]; + // wcstombs(buffer, dfbuffer, len); + // buffer[len] = '\0'; + //#else + // int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); + // char* buffer = new char[count]; + // WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); + //#endif + // vFiles.push_back(std::string(buffer)); + // delete[] buffer; + //#else + // vFiles.push_back(std::string(dfbuffer)); + //#endif + // } + // + // // Even more eww... + // POINT p; DragQueryPoint(drop, &p); + // ptrPGE->olc_DropFiles(p.x, p.y, vFiles); + // DragFinish(drop); + // return 0; + // } + // break; + // + // + case WM_CLOSE: + { + window->olc_OnWindowClose(); + break; + //return DefWindowProc(hWnd, uMsg, wParam, lParam); + } - // Get the content view size (excludes title bar and borders) - void window_getContentViewFrame(const Window* self, double* x, double* y, double* width, double* height) { - if (!self || !self->nsWindow) { - if (x) *x = kMinValidDimension; - if (y) *y = kMinValidDimension; - if (width) *width = kDefaultWindowWidth; - if (height) *height = kDefaultWindowHeight; - return; - } - - // Get the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); - if (!contentView) { - if (x) *x = kMinValidDimension; - if (y) *y = kMinValidDimension; - if (width) *width = kDefaultWindowWidth; - if (height) *height = kDefaultWindowHeight; - return; - } - - // Thank you, Ben the ultimate Guru, - // Get the content view bounds in points (logical coordinates). - // On macOS, AppKit uses points rather than physical pixels. On HighDPI - // displays (that are scaling) a point maps to >1 pixels. - // convertRectToBacking: converts the point-based rect to physical pixels, - // which is what OpenGL expects - NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); - - if (x) *x = pixelBounds.x; - if (y) *y = pixelBounds.y; - if (width) *width = pixelBounds.width; - if (height) *height = pixelBounds.height; - - } + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(hCursorNow); + return TRUE; // Sigh ffs microsoft... + } - // Get the content view size (excludes title bar and borders) - void window_setContentViewFrame(struct Window* self, double* x, double* y, double* width, double* height) { - if (!self || !self->nsWindow) { - if (x) *x = kMinValidDimension; - if (y) *y = kMinValidDimension; - if (width) *width = kDefaultWindowWidth; - if (height) *height = kDefaultWindowHeight; - return; - } + break; + } - // Set the content view frame - NSRect contentFrame = {x ? *x : 0, y ? *y : 0, width ? *width : 0, height ? *height : 0}; - if (self->nsWindow) { - id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { - ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); - } - } - } + case WM_DESTROY: + PostQuitMessage(0); + DestroyWindow(hWnd); - // Get Window frame (x, y, width, height) - void window_getWindowFrame(const Window* self, double* x, double* y, double* width, double* height) { - - window_updateFrameFromOSX(const_cast(self)); + } - auto posX = self ? self->windowFrame.x : kMinValidDimension; - auto posY = self ? self->windowFrame.y : kMinValidDimension; - auto w = self ? self->windowFrame.width : kDefaultWindowWidth; - auto h = self ? self->windowFrame.height : kDefaultWindowHeight; - if (x) *x = posX; - if (y) *y = posY; - if (width) *width = w; - if (height) *height = h; - } - // Get window position (x, y) - void window_getWindowPosition(const struct Window* self, double* x, double* y) { - window_getWindowFrame(self, x, y, nullptr, nullptr); - } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } - // Get Window size (width, height) - void window_getWindowSize(const Window* self, double* width, double* height) { - window_getWindowFrame(self, nullptr, nullptr, width, height); - } + olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const + { + HKL kbl = ::GetKeyboardLayout(0); + size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; - // Set window frame (x, y, width, height) - void window_setWindowFrame(Window* self, double x, double y, double width, double height) { - if (self) { - // Update internal frame representation - self->windowFrame.x = x; - self->windowFrame.y = y; - self->windowFrame.width = width; - self->windowFrame.height = height; + if (highWord == 0x00000409) // US + return olc::KeyboardLayout::QWERTY_US; + else if(highWord == 0x00000809) // UK + return olc::KeyboardLayout::QWERTY_UK; + else if(highWord == 0x00000407) // DE + return olc::KeyboardLayout::QWERTZ; + else if(highWord == 0x0000040C) // FR + return olc::KeyboardLayout::AZERTY; - // Create new NSRect for the frame - NSRect newFrame = {x, y, width, height}; - - // Set the frame on the actual NSWindow - if (self->nsWindow) { - ((void(*)(id, SEL, NSRect, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setFrameDisplaySel, newFrame, YES); - } - } - } + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } - // Set window position (x, y) - void window_setWindowPosition(Window* self, double x, double y) { - window_setWindowFrame(self, x, y, self->windowFrame.width, self->windowFrame.height); - } +}; +#endif - // Set window size (width, height) - void window_setWindowSize(Window* self, double width, double height) { - window_setWindowFrame(self, self->windowFrame.x, self->windowFrame.y, width, height); - } +#if OLC_HOST == OLC_HOST_MACOS +namespace olc::host { - // Set the cusor position relative to the content view - void window_setCursorPosition(Window* self, double x, double y) { - if (!self || !self->nsWindow) return; - - // Get current screen the window is on - id currentScreen = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::screenSel); - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); - - // Update internal frame representation to get the latest window position - window_updateFrameFromOSX(self); - NSPoint location = {self->windowFrame.x, self->windowFrame.y}; - - // Get the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); - - if (contentView) { - - // x,y are in physical backing pixels. CGWarpMouseCursorPosition - // and all AppKit frame/bounds values use logical points, so convert the incoming - // pixel position to points. - NSPoint pixelPos = { x, y }; - NSPoint pointPos = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( contentView, ObjectiveCSEL::convertPointFromBackingSel, pixelPos); - - // Get content bounds in points for clamping and Y-flip - NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - - // NOTE: clamp to keep the cursor within the window content area - auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); - auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, - screenFrame.height - location.y - contentBounds.height, - screenFrame.height - location.y); - CGWarpMouseCursorPosition(CGPointMake(posX, posY)); - - } - - } - // Set cursor visibility - void window_setCursorVisibility(Window* self, BOOL visible) { - if (!self || !self->nsWindow) return; - bHideCursor = !visible; - if (bHideCursor && bAllowHideCursor) { - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); - } else { - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); - } - } + // NSEventModifierFlags values + // constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 // Temp remove unused variable warning + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + // constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 // Temp remove unused variable warning + constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 + // constexpr unsigned int NSEventModifierFlagNumericPad = 1 << 21; // 0x200000 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 // Temp remove unused variable warning - void window_toggleFullScreen(Window* self) { - // Toggle fullscreen - ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); - } + // enum for window appearance and behavior bit flags + enum class NSWindowStyleMask : uint16_t { + Titled = (1 << 0), // Window has a title bar + Closable = (1 << 1), // Window can be closed + Miniaturizable = (1 << 2), // Window can be minimized + Resizable = (1 << 3), // Window can be resized + UtilityWindow = (1 << 4), // Utility window style + DocModalWindow = (1 << 6), // Document-modal window + NonactivatingPanel = (1 << 7), // Non-activating panel + TexturedBackground = (1 << 8), // Textured background + HUDWindow = (1 << 13), // Heads-up display window + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view + }; - bool window_isFullScreen(Window* self) { - unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); - return (mask & NSWindowStyleMaskFullScreen); - } - // Initialize OpenGL renderer - void opengl_initialize(OpenGLRenderer* self, Window* window) { - self->window = window; - window->renderer = self; - - // Get classes using const strings - Class NSOpenGLPixelFormatClass = objc_getClass(kNSOpenGLPixelFormatClass); - Class CustomOpenGLViewClass = createCustomOpenGLViewClass(); // Use our custom class - - unsigned int sampleBuffers = (self->MSAA_Samples > 0) ? 1 : 0; - std::vector preferredSamples = {32, 16, 8, 4, 2, 0}; - if(self->MSAA_Samples <= 0) { - // If no multisampling requested, only try 0 samples - preferredSamples.clear(); - preferredSamples.resize(1); - preferredSamples.push_back(0); - } + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; - for(unsigned int samples: preferredSamples) - { - // Create pixel format attributes array using enum values - const unsigned int attrs[] = { - NSOpenGLPFADoubleBuffer, // Enable double buffering - NSOpenGLPFADepthSize, 32, // 32-bit depth buffer - NSOpenGLPFAColorSize, 24, // 24-bit color - NSOpenGLPFAAccelerated, // Hardware acceleration - NSOpenGLAllowOfflineRenderers, // Allow offline renderers - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, // OpenGL 4.1 Core Profile - NSOpenGLPFAMultisample, samples, // 0x --> 32X Multisampling - NSOpenGLPFASampleBuffers, sampleBuffers, // Number of sample buffers - 0 // null terminator - }; - - // Create pixel format - self->pixelFormat = ((id(*)(id, SEL, const unsigned int*))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSOpenGLPixelFormatClass, ObjectiveCSEL::allocSel), - ObjectiveCSEL::initWithAttributesSel, attrs); - - // Check if pixel format was created successfully - if (self->pixelFormat) { - self->MSAA_Samples = samples; - break; // Successfully created pixel format - } - } - - // Create custom OpenGL view with event handling - NSRect glViewFrame = {0.0, 0.0, window->contentViewFrame.width, window->contentViewFrame.height}; - self->glView = ((id(*)(id, SEL, NSRect, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(CustomOpenGLViewClass, ObjectiveCSEL::allocSel), - ObjectiveCSEL::initWithFramePixelFormatSel, glViewFrame, self->pixelFormat); - - // Set autoresizing mask to make the view resize with the window - unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); - - // Enable touch events (NSTouch) - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); - - // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); - - - } + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; - // Set up OpenGL context and make view first responder - void opengl_setupContext(OpenGLRenderer* self) { - // Set the OpenGL view as the window's content view - ((void(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::setContentViewSel, self->glView); - - // Make the OpenGL context current - self->glContext = ((id(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::openGLContextSel); - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Make sure the window is key first - ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); - - // Make the OpenGL view the first responder so it can receive keyboard and mouse events - ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); - - // Additional debug: check if view can become key view - ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); - } + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; - // Implementation function - bool opengl_resetContextForSize(OpenGLRenderer* self, double width, double height) { - if (!self || !self->glContext || !self->glView) { - return false; - } - - // Make context current - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Update the view frame - NSRect newFrame = {0.0, 0.0, width, height}; - ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); - - // Force context to reshape - ((void(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::reshapeSel); - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::updateSel); - - // Update viewport - glViewport(0, 0, (GLsizei)width, (GLsizei)height); - - // Clear buffers - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; - return true; - } + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock - // Get the underlying OpenGL context - void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { - return (void*)self->glContext; - } + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . - // Get the underlying CGLContextObj from the OpenGL renderer - void* opengl_getCGLContextObj(struct OpenGLRenderer* self) { - if (!self || !self->glContext) { return nullptr;} - - return (void*)((void*(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::CGLContextObjSel); - } - - // Get the underlying CGLContextObj (C API version) - void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { - return (void*)opengl_getCGLContextObj(self); - } + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . - // Make the OpenGL context current - void opengl_makeCurrentContext(struct OpenGLRenderer* self) { - if (self && self->glContext) { - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - } } - // Enable or disable vertical synchronization (vsync) - void opengl_setVsync(OpenGLRenderer* self, BOOL enabled) { - if (!self || !self->glContext) { return; } - - // Make context current first - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Set swap interval: 1 for vsync enabled, 0 for disabled - GLint swapInterval = enabled ? 1 : 0; + + bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ + olc_IgnoreUnused(bFullScreen); + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); + + frameBounds.x = 0.0; + frameBounds.y = 0.0; + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); - // Use NSOpenGLContext setValues:forParameter: to set swap interval - const GLint parameter = NSOpenGLContextParameterSwapInterval; - ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); + return true; } - // Destroy the OpenGL renderer - void opengl_destroy(OpenGLRenderer* self) { - if (self) { - // Release OpenGL resources - if (self->glContext) { - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::releaseSel); - } - free(self); - } + bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ + pWindow->olc_OnWindowClose(); + return true; } - // OpenGL renderer constructor - OpenGLRenderer* opengl_init() { - - OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); - renderer->pixelFormat = NULL; - renderer->glView = NULL; - renderer->glContext = NULL; - renderer->window = NULL; - - // Assign method pointers - renderer->initialize = opengl_initialize; - renderer->setupContext = opengl_setupContext; - renderer->getOpenGLContext = opengl_getOpenGLContext; - renderer->getCGLContextObj = opengl_getCGLContextObj; - renderer->getCGLContextObjPtr = opengl_getCGLContextObjPtr; - renderer->makeCurrentContext = opengl_makeCurrentContext; - renderer->setVsync = opengl_setVsync; - renderer->destroy = opengl_destroy; - renderer->resetContextForSize = opengl_resetContextForSize; - - return renderer; + bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ + if (!pMacOSWindow) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + }); + return true; } - static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) - { - if(!image) return NO; + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + olc_IgnoreUnused(pWindow); + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } + return vMacOSWindowDescriptors; + + } - CGDataProviderRef provider = CGImageGetDataProvider(image); - CFDataRef rawData = CGDataProviderCopyData(provider); - //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning - //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning - //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning + bool Host_Apple_MacOS::SyncWithDesktopComposite() + { + /* + core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, + the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, + set to false and toggle it on first call, so that vSync is only enabled once + */ - self->width = (int)CGImageGetWidth(image); - self->height = (int)CGImageGetHeight(image); - self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA - self->bytesPerRow = self->width * self->bytesPerPixel; - self->hasAlpha = YES; - - const uint8_t* imageData = CFDataGetBytePtr(rawData); - self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); - if(!self->pixelData) + if(!enableVSync) { - return NO; + pMacOSOpenGLRenderer->enableVsync(); + enableVSync = true; } - memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); - // NOTE from Moros1138 - // - // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far - // the most common case, so in practice this block of code will never - // be run. However, if we find that there is a need to adjust the - // pixel data, this will need fleshing out. - - /* - if(byteOrder != kCGBitmapByteOrder32Little) - { - int pixelCount = self->width * self->height; - for(int i = 0; i < pixelCount; ++i) - { - auto p = self->pixelData[i]; - - // TODO: detect byte order, adjust as appropriate - - self->pixelData[i] = p; - } - } - */ + return enableVSync; + } - CFRelease(rawData); - CGImageRelease(image); + bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + olc_IgnoreUnused(pWindow); + vPositionMouse = vPos; + this->vPendingMainThreadTasks.push_back(SET_MOUSE_POSITION); + return false; + } - return YES; + bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; } - BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + olc_IgnoreUnused(pWindow); + // if we're already in the specified state, return early + if(pMacOSWindow->isFullScreen() == bFullScreen) + return true; - CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); - CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); - CFRelease(pathStr); + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->toggleFullScreen(); + }); + return true; + } - if(!url) - { - printf("loadImageFromFile: bad path '%s'\n", filePath); - return NO; - } + uint16_t Host_Apple_MacOS::ConvertPGE2WindowStyle() + { + uint16_t nsStyle = 0; + + // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); - CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); - CFRelease(url); + // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, + // For MacOS you can only disable the buttons, you can't hide them + if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window + if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border + if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button + if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button - if(!src) - { - printf("loadImageFromFile: couldn't open '%s'\n", filePath); - return NO; - } + return nsStyle; - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); + } - return imageloader_decodeImage(self, image); + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; } - BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { + bool Host_Apple_MacOS::StartSystem(){ + + // Create MacOS Application instance + pMacApplication = std::make_unique(); + + // Set up application delegate event handlers + MacApplicationEventsHandler(); + + // Initialize and activate application first + pMacApplication->initialize(); + pMacApplication->activate(); - CFDataRef cfData = CFDataCreateWithBytesNoCopy( - nullptr, - reinterpret_cast(data), - (CFIndex)bytes, - kCFAllocatorNull // we own the buffer, CF must not free it - ); + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Initialize the MacOS Window + pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); + pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); + pMacOSWindow->setContentViewPosition(0, 0); + + // Set up window event handlers + MacWindowEventsHandler(); + + // Create Input Event handler + pMacOSEventHandler = std::make_unique(*pMacOSWindow); + + // Setup Event handlers + MacEventsHandler(); + + // Create the window + unsigned long styleMask = ConvertPGE2WindowStyle(); + pMacOSWindow->show(styleMask); + pMacOSEventHandler->enable(); + + //--- Start up our engine threading system ---- + // Start the PGE context on the main thread + // Mark system as active + systemActive = true; - if (!cfData) + // Create system thread - handles gpu context + std::thread threadSystem([this]() { - printf("loadImageFromMemory: CFData creation failed\n"); - return NO; - } + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } - CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); - CFRelease(cfData); + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } - if (!src) - { - printf("loadImageFromMemory: src creation failed\n"); - return NO; - } + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); - return imageloader_decodeImage(self, image); - } + // Start the main event loop (this will block) + pMacApplication->run(); + + // Once the application run loop ends, join the system thread + if(threadSystem.joinable()) + threadSystem.join(); - // Get raw pixel data pointer - unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return (unsigned char*)self->pixelData; - } + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); - // Get image information - void imageloader_getImageInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel) { - if (width) *width = self->width; - if (height) *height = self->height; - if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; } - // Get detailed image format information - void imageloader_getDetailedInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha) { - if (width) *width = self->width; - if (height) *height = self->height; - if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; - if (bytesPerRow) *bytesPerRow = self->bytesPerRow; - if (hasAlpha) *hasAlpha = self->hasAlpha; - } + bool Host_Apple_MacOS::StopSystem() + { + dispatch_sync(dispatch_get_main_queue(), ^{ + // clean up and close application + if (pMacOSOpenGLRenderer) + { + pMacOSOpenGLRenderer->destoryContext(); + pMacOSOpenGLRenderer = nullptr; + } + if (pMacOSWindow) + { + pMacOSWindow->destoryWindow(); + pMacOSWindow = nullptr; + } + if (pMacApplication) + { + pMacApplication->stop(); + } - // Check if image is loaded - BOOL imageloader_isLoaded(const struct ImageLoader* self) { - return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); + }); + systemActive = false; + return true; } - // Get pixel at specific coordinates - BOOL imageloader_getPixel(const struct ImageLoader* self, int x, int y, - unsigned char* red, unsigned char* green, - unsigned char* blue, unsigned char* alpha) { - if (!imageloader_isLoaded(self) || x < kMinValidDimension || x >= self->width || y < kMinValidDimension || y >= self->height) { - return NO; - } - - // Calculate pixel offset (macOS uses bottom-left origin, so flip Y) - int flippedY = self->height - kFlippedOffset - y; - imageloader_pixel_t* pixel = self->pixelData + (flippedY * self->width) + x; - - // Extract color components (assuming RGBA or RGB format) - if (red) *red = pixel->r; - if (green) *green = pixel->g; - if (blue) *blue = pixel->b; - if (alpha && self->bytesPerPixel >= kRGBABytesPerPixel) *alpha = pixel->a; - else if (alpha) *alpha = kFullyOpaque; // Fully opaque if no alpha channel - - return YES; + bool Host_Apple_MacOS::OnSystemThreadStart() + { + // Hold back threading until application is fully initialized + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextStart(); } - // Cleanup ImageLoader resources - void imageloader_destroy(struct ImageLoader* self) { - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } - self->width = kZeroWidth; - self->height = kZeroHeight; - self->bytesPerPixel = kZeroBytes; - self->bytesPerRow = kZeroRows; - self->hasAlpha = NO; - } - - // ImageLoader constructor - struct ImageLoader* imageloader_init() { - struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); - - // Initialize properties - loader->pixelData = NULL; - loader->width = kZeroWidth; - loader->height = kZeroHeight; - loader->bytesPerPixel = kZeroBytes; - loader->bytesPerRow = kZeroRows; - loader->hasAlpha = NO; - - // Assign method pointers - loader->loadFromFile = imageloader_loadFromFile; - loader->loadFromMemory = imageloader_loadFromMemory; - loader->destroy = imageloader_destroy; - loader->getPixelData = imageloader_getPixelData; - loader->getImageInfo = imageloader_getImageInfo; - loader->getDetailedInfo = imageloader_getDetailedInfo; - loader->isLoaded = imageloader_isLoaded; - loader->getPixel = imageloader_getPixel; - - return loader; + bool Host_Apple_MacOS::OnSystemTick() + { + // Execute any pending main thread tasks + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextTick(); } - // Window constructor - Window* window_init(double x, double y, double width, double height) { - Window* window = (Window*)malloc(sizeof(Window)); - - // Initialize window properties - window->nsWindow = NULL; - window->delegate = NULL; - window->title = NULL; - - // Set initial window frame - window->windowFrame.x = x; - window->windowFrame.y = y; - window->windowFrame.width = width; - window->windowFrame.height = height; + bool Host_Apple_MacOS::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } - // Set window content view frame - window->contentViewFrame.x = x; - window->contentViewFrame.y = y; - window->contentViewFrame.width = width; - window->contentViewFrame.height = height; + bool Host_Apple_MacOS::OnApplicationEnd() + { + return true; + } - // Initialize callback pointers - window->keyDownCallback = NULL; - window->keyUpCallback = NULL; - window->mouseDownCallback = NULL; - window->mouseUpCallback = NULL; - window->mouseMovedCallback = NULL; - window->mouseDraggedCallback = NULL; - window->rightMouseDownCallback = NULL; - window->rightMouseUpCallback = NULL; - window->otherMouseDownCallback = NULL; - window->otherMouseUpCallback = NULL; - window->scrollWheelCallback = NULL; - // Event handling disabled by default - window->acceptsInputEvents = NO; +//-- OS Window Event Handling ----- + + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } - // Initialize window delegate callback pointers - window->windowDidResizeCallback = NULL; - window->windowWillCloseCallback = NULL; - window->windowDidBecomeKeyCallback = NULL; - window->windowDidResignKeyCallback = NULL; - window->windowDidMiniaturizeCallback = NULL; - window->windowDidDeminiaturizeCallback = NULL; - window->windowDidResizeUserData = NULL; - window->windowWillCloseUserData = NULL; - window->windowDidBecomeKeyUserData = NULL; - window->windowDidResignKeyUserData = NULL; - window->windowDidMiniaturizeUserData = NULL; - window->windowDidDeminiaturizeUserData = NULL; +// ------- Priavate Main Thread Task Handling for MacOS Host ------- - // Assign method pointers - window->create = window_create; - window->show = window_show; - window->destroy = window_destroy; - window->setDelegate = window_setDelegate; - window->removeDelegate = window_removeDelegate; - window->getTitle = window_getTitle; - window->setTitle = window_setTitle; - window->setWindowSize = window_setWindowSize; - window->getWindowSize = window_getWindowSize; - window->setWindowPosition = window_setWindowPosition; - window->getWindowPosition = window_getWindowPosition; - window->setWindowFrame = window_setWindowFrame; - window->getWindowFrame = window_getWindowFrame; - window->getCurrentFrame = window_updateFrameFromOSX; - window->getContentViewFrame = window_getContentViewFrame; - window->setContentViewFrame = window_setContentViewFrame; + bool Host_Apple_MacOS::CreateCGLContextObj() + { + // This method should only be called on the PGE thread, use AddPendingMainThreadTask(CREATE_OPENGL_RENDERER); to queue it if needed + if(pMacOSOpenGLRenderer == nullptr) + { + vMacOSWindowDescriptors.clear(); // ensure we are starting fresh + pMacOSOpenGLRenderer = std::make_shared(); + + dispatch_sync(dispatch_get_main_queue(), ^{ + // Edge case for when the window is auto resize due to MacOS clamping to screen size + pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); - return window; - } + pMacOSOpenGLRenderer->attachToWindow(*pMacOSWindow); + pMacOSOpenGLRenderer->setupContext(); + }); + + pMacGLConextObj = pMacOSOpenGLRenderer->getCGLContextObj(); + + pMacOSOpenGLRenderer->setVsync(false); + + vMacOSWindowDescriptors.push_back(pMacGLConextObj); // Pointer to CGLContextObj + vMacOSWindowDescriptors.push_back(&bSkipFrame); // Pointer to skip frame flag - // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { - self->keyDownCallback = callback; - self->eventUserData = userData; + // Set up OpenGL renderer for visual feedback + pMacOSOpenGLRenderer->makeCurrentContext(); + + // Finally we set full screen if needed to ensure all out OpenGL setup is done before toggling full screen, + if(pPrimaryPGE->config.bFullScreen){ + SetFullScreen(pPGEwindow, true); + } + + } + + return true; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { - self->keyUpCallback = callback; - self->eventUserData = userData; - } - - void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { - self->flagsChangedCallback = callback; - self->eventUserData = userData; - } + bool Host_Apple_MacOS::ExecutePendingMainThreadTasks() + { + // 1: Check if main thread wants us to wait + std::unique_lock lock(pgeThreadPendingTasksMutex); + + if (isPGEThreadResetting.load()) { + + // 2. PGE Thread signals it's waiting + { + std::lock_guard mainLock(mainThreadPendingTasksMutex); + isMainThreadResetting = true; // Signal to main thread we're waiting + } + mainThreadResetCondition.notify_all(); // Wake up main thread - void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDownCallback = callback; - self->eventUserData = userData; - } + // Note: MainThreadTasks(); will be called by the main thread to process tasks + + // 3. PGE Thread waits for main thread to finish + pgeThreadResetCondition.wait(lock, [this] { + return !isPGEThreadResetting.load(); + }); - void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseUpCallback = callback; - self->eventUserData = userData; + //4: return true indicating we processed tasks + return true; + } + else + { + // No pending tasks, just return + return false; + } } - void window_setMouseMovedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseMovedCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::AddPendingMainThreadTask(MAINTASKS task) + { + // NOTE: Note: You should only add tasks that require main thread execution + vPendingMainThreadTasks.push_back(task); + MainThreadTasks(); + + return true; } - - void window_setMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDraggedCallback = callback; - self->eventUserData = userData; + + bool Host_Apple_MacOS::MainThreadTasks() + { + bool res = false; + if(vPendingMainThreadTasks.empty()) + return res; // edge case + + // 1. Main Thread locks PGE Thread + { + std::lock_guard lock(pgeThreadPendingTasksMutex); + isPGEThreadResetting = true; // Signal PGE to stop + } + pgeThreadResetCondition.notify_all(); // Wake up PGE thread to check flag + + // 2. Main Thread waits for PGE Thread to acknowledge and wait + std::unique_lock lock(mainThreadPendingTasksMutex); + mainThreadResetCondition.wait(lock, [this] { + return isMainThreadResetting.load(); // Wait until PGE signals it's waiting + }); + + // Process any pending main thread tasks + for (const auto& task : vPendingMainThreadTasks) + { + switch (task) + { + case CREATE_OPENGL_RENDERER: + { + // In this case, the PGE will be waiting for main thread to singal, so the ContextOBJ can be created + res = false; // No need to skip frame + break; + } + case RESIZE_WINDOW: + { + // Resize window on main thread + pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + pMacOSOpenGLRenderer->resetContextSize(frameBounds.width, frameBounds.height); + res = true; // Skip frame to allow resize to take effect + break; + } + case DEMINIMIZE_WINDOW: + case BECOME_ACTIVE: + { + pPGEwindow->olc_OnFocus(true); + break; + } + case MINIMIZE_WINDOW: + case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnFocus(false); + break; + } + case SET_MOUSE_POSITION: + { + // Set mouse position on main thread + pMacOSWindow->setCursorPosition(vPositionMouse.x, vPositionMouse.y); + res = false; // No need to skip frame + break; + } + case NONE: + default: + { + res = false; + break; + } + + } + } + vPendingMainThreadTasks.clear(); + + // 4. Main Thread unlocks PGE Thread + { + std::lock_guard lock(pgeThreadPendingTasksMutex); + isPGEThreadResetting = false; // Release PGE thread + isMainThreadResetting = false; // Reset main thread flag + } + pgeThreadResetCondition.notify_all(); // Wake up PGE thread + return res; } - void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDownCallback = callback; - self->eventUserData = userData; - } +//------ Events Handlers ----- - void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDraggedCallback = callback; - self->eventUserData = userData; + void Host_Apple_MacOS::MacApplicationEventsHandler() + { + pMacApplication->setWillFinishLaunchingCallback([]() { }); + + pMacApplication->setDidFinishLaunchingCallback([&]() { + // Queue the Create OpenGL context task + vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); + + + }); + + pMacApplication->setWillTerminateCallback([&]() { + //todo : add any cleanup code here if needed + }); + + pMacApplication->setDidBecomeActiveCallback([]() { }); + + pMacApplication->setWillResignActiveCallback([]() { }); + } - void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseUpCallback = callback; - self->eventUserData = userData; - } + void Host_Apple_MacOS::MacWindowEventsHandler() + { + pMacOSWindow->setWindowDidResizeCallback([&]() { + AddPendingMainThreadTask(RESIZE_WINDOW); + }); - void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDownCallback = callback; - self->eventUserData = userData; - } + pMacOSWindow->setWindowWillCloseCallback([&]() { + // NOTE: Do not add this event to PendingMainThreadTasks as it will cause deadlock since the main thread is required to process the close event but the close event is waiting on the main thread tasks to process it + pPGEwindow->olc_OnWindowClose(); + pPGEwindow->olc_ShouldRemove(); + }); - void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseUpCallback = callback; - self->eventUserData = userData; - } + pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { + AddPendingMainThreadTask(BECOME_ACTIVE); + }); - void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDraggedCallback = callback; - self->eventUserData = userData; + pMacOSWindow->setWindowDidResignKeyCallback([&]() { + AddPendingMainThreadTask(RESIGN_ACTIVE); + }); + + pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { + AddPendingMainThreadTask(MINIMIZE_WINDOW); + }); + + pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { + AddPendingMainThreadTask(DEMINIMIZE_WINDOW); + }); + } + + // handles both down and up strokes for every supported key that isn't a modifier + void Host_Apple_MacOS::KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed) + { + unsigned short keyCode = event.keyCode; + + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) + { + bNumLockActive = !bNumLockActive; + return; + } - void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { - self->scrollWheelCallback = callback; - self->eventUserData = userData; - } + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } + } - void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseEnteredCallback = callback; - self->eventUserData = userData; + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; + + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], isPressed); } - void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseExitedCallback = callback; - self->eventUserData = userData; - } + void Host_Apple_MacOS::MacEventsHandler() + { + // General MacOS key event handling code here + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchBeganCallback = callback; - self->touchUserData = userData; - } + // Set up keyboard event handlers + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + KeyboardEventHandler(event, true); + }); - void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchMovedCallback = callback; - self->touchUserData = userData; - } + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + KeyboardEventHandler(event, false); + }); - void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchEndedCallback = callback; - self->touchUserData = userData; - } + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { - void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchCancelledCallback = callback; - self->touchUserData = userData; - } + static unsigned int prevFlags = 0; + unsigned int changedFlags = event.modifierFlags ^ prevFlags; + + // Check For Shift key + if (changedFlags & NSEventModifierFlagShift) { + bool isPressed = event.modifierFlags & NSEventModifierFlagShift; + pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); + } + + // Check for Control key + if (changedFlags & NSEventModifierFlagControl) { + bool isPressed = event.modifierFlags & NSEventModifierFlagControl; + pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); + } - void window_setStylusCallback(Window* self, - void (*callback)(uint32_t, double, double, float, float, float, float, bool, bool, bool, void*), - void* userData) - { - self->stylusCallback = callback; - self->stylusUserData = userData; - } + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + + // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); + } - void window_enableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = YES; - gptrNSWindowEvents = self; // Set global reference for event handlers - } - } + // caps lock doesn't appear to trigger any event + prevFlags = event.modifierFlags; + }); - void window_disableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = NO; - if (gptrNSWindowEvents == self) { - gptrNSWindowEvents = NULL; - } - } - } + // Set up mouse event handlers + pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); + + pMacOSEventHandler->onMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); + + pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); + + pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - // Window delegate callback setter functions - void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResizeCallback = callback; - self->windowDidResizeUserData = userData; + pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - } + pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); - void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowWillCloseCallback = callback; - self->windowWillCloseUserData = userData; - } + pMacOSEventHandler->onRightMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); + + pMacOSEventHandler->onRightMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + + }); - void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidBecomeKeyCallback = callback; - self->windowDidBecomeKeyUserData = userData; - } + pMacOSEventHandler->onOtherMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); - void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResignKeyCallback = callback; - self->windowDidResignKeyUserData = userData; - } + pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); - void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidMiniaturizeCallback = callback; - self->windowDidMiniaturizeUserData = userData; - } + pMacOSEventHandler->onOtherMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidDeminiaturizeCallback = callback; - self->windowDidDeminiaturizeUserData = userData; - } + pMacOSEventHandler->onScrollWheel([&](const olc::apis::macos::ScrollWheelEvent& event) { + // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling + pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); + }); - // Application delegate callback setter functions - void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->willFinishLaunchingCallback = callback; - self->willFinishLaunchingUserData = userData; - } + // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch + pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->didFinishLaunchingCallback = callback; - self->didFinishLaunchingUserData = userData; - } + pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { - self->willTerminateCallback = callback; - self->willTerminateUserData = userData; - } + pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->didBecomeActiveCallback = callback; - self->didBecomeActiveUserData = userData; - } + pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->willResignActiveCallback = callback; - self->willResignActiveUserData = userData; + // Stylus (tablet) events — full pressure, tilt and rotation data + pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + + pPGEwindow->olc_OnTouch( + event.touchID, + {static_cast(event.x), static_cast(event.y)}, + event.bPress, + event.bRelease, + {1.0f, 1.0f}, // stylus contact size — nominal 1x1 + true, // bStylus = true + event.pressure, + event.rotation, + {event.tiltX, event.tiltY} + ); + }); + } -} // extern "C" - +} #endif #if OLC_HOST == OLC_HOST_LINUX_X11 @@ -12363,6 +12457,10 @@ namespace olc::host return nullptr; }; + // Create a pollfd that we will use later to wait for events to appear on the queue + // preventing the event loop from becoming a busy loop + pollfd x11_connection_fd {.fd = ConnectionNumber(olc_Display), .events = POLLIN}; + X11::XEvent xev; while(systemActive){ while (XPending(olc_Display)) @@ -12529,6 +12627,9 @@ namespace olc::host } } } + + // Wait until an event appears on the x11 event queue file descriptor + poll(&x11_connection_fd, 1, -1); } systemActive = false; @@ -13093,7 +13194,7 @@ namespace olc::host { pPrimaryPGE->OnPreContextStart(); - // Create system thread - handles gpu context + // Create system thread - handles gpu context std::thread threadSystem([this]() { // Notify start of system thread @@ -13121,11 +13222,16 @@ namespace olc::host } }); + pollfd decor_wl_fd {.fd = libdecor_get_fd(decor_context), .events = POLLIN}; + bool keep_running = true; while(systemActive && keep_running) { if(decor_context) { - std::lock_guard l{decor_mutex}; - keep_running = libdecor_dispatch(decor_context, 0) >= 0; + poll(&decor_wl_fd, 1, -1); + { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; + } } } @@ -18166,11 +18272,27 @@ olc::Pixel olc::Draw::GetPixel(olc::Image& image, const olc::vf2d& pos, const ol return failcol; } +void olc::Draw::SetPixel(olc::Image& image, const olc::vf2d& pos, const olc::Pixel col) +{ + // Check if in bounds + olc::vf2d tpos = transformAffine.forwardRound(pos); + if (tpos.x >= 0 && tpos.y >= 0 && tpos.x < float(pTarget->Size().x) && tpos.y < float(pTarget->Size().y)) + { + PrepareImageForSW(image); + pTarget->Pixel(tpos) = col; + } +} + olc::Pixel olc::Draw::GetPixel(const olc::vf2d& pos, const olc::Pixel failcol) { return GetPixel(GetTarget(), pos, failcol); } +void olc::Draw::SetPixel(const olc::vf2d& pos, const olc::Pixel col) +{ + SetPixel(GetTarget(), pos, col); +} + olc::Pixel olc::Draw::GetRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel failcol) { if (pos.x >= 0 && pos.y >= 0 && pos.x < float(image.Size().x) && pos.y < float(image.Size().y)) @@ -18182,22 +18304,47 @@ olc::Pixel olc::Draw::GetRawPixel(olc::Image& image, const olc::vi2d& pos, const return failcol; } +void olc::Draw::SetRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel col) +{ + if (pos.x >= 0 && pos.y >= 0 && pos.x < float(image.Size().x) && pos.y < float(image.Size().y)) + { + PrepareImageForSW(image); + image.Pixel(pos) = col; + } +} + olc::Pixel olc::Draw::GetRawPixel(const olc::vi2d& pos, const olc::Pixel failcol) { return GetRawPixel(GetTarget(), pos, failcol); } +void olc::Draw::SetRawPixel(const olc::vi2d& pos, const olc::Pixel col) +{ + SetRawPixel(GetTarget(), pos, col); +} + olc::Pixel olc::Draw::GetUnsafeRawPixel(olc::Image& image, const olc::vi2d& pos) { PrepareImageForSW(image); return image.Pixel(pos); } +void olc::Draw::SetUnsafeRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel col) +{ + PrepareImageForSW(image); + image.Pixel(pos) = col; +} + olc::Pixel olc::Draw::GetUnsafeRawPixel(const olc::vi2d& pos) { return GetUnsafeRawPixel(GetTarget(), pos); } +void olc::Draw::SetUnsafeRawPixel(const olc::vi2d& pos, const olc::Pixel col) +{ + SetUnsafeRawPixel(GetTarget(), pos, col); +} + void olc::Draw::Clear(const olc::Pixel& col) { PrepareTargetForHW();