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