From 4e3af223b3ae160d549a68b50725824ade27ac1e Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Sun, 22 Mar 2026 17:16:18 +0000 Subject: [PATCH 01/28] Revert TotalTimeElapsed usage in extension example --- examples/olcPGE3_Extensions.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/olcPGE3_Extensions.cpp b/examples/olcPGE3_Extensions.cpp index 582e5633..b6a36757 100644 --- a/examples/olcPGE3_Extensions.cpp +++ b/examples/olcPGE3_Extensions.cpp @@ -24,7 +24,7 @@ class ExamplePGEX : public olc::PGEWindowExtension { // Called when extension is installed, usually in PGEWindow Constructor // Return true to continue application - virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) + virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) override { return true; } @@ -98,6 +98,8 @@ class Example_Extensions : public olc::PixelGameEngine // We need to create an instance of our extension class, and then install it in the PGE constructor ExamplePGEX pgex; + float fTotalTime = 0.0f; + public: // Called once at the start, so create things here bool OnUserCreate() override @@ -115,7 +117,7 @@ class Example_Extensions : public olc::PixelGameEngine draw.String({ 10.0f, 10.0f }, "This text is drawn from the\nmain application!\n\nMouse Move in X To Warp Time", olc::Colour::WHITE); // Draw a clocking line to illustrate the passage of time... - float fTotalTime = TotalTimeElapsed(); + fTotalTime += fElapsedTime; draw.Line(GetScreen().Size() / 2, olc::vf2d(cos(fTotalTime), sin(fTotalTime)) * 50.0f + GetScreen().Size() / 2, olc::Colour::GREEN); // Successful frame @@ -139,4 +141,4 @@ int main() } return 0; -} \ No newline at end of file +} From 13989a5e2f406695f5f60f0d375175e4289f7f66 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Tue, 24 Mar 2026 13:49:45 +0000 Subject: [PATCH 02/28] 239-remove-complier-clang-warning-for-macos-xcode --- dev/src/api_macos.cpp | 42 +++++++++------ dev/src/api_opengl.cpp | 2 + dev/src/config.h | 11 +++- dev/src/draw.cpp | 10 +++- dev/src/font.cpp | 3 +- dev/src/gpu_opengl33.cpp | 1 + dev/src/host_apple_macos.cpp | 18 ++++--- dev/src/imload_macos.cpp | 2 + dev/src/pixel.h | 8 +++ dev/src/transform2d.h | 9 +++- dev/tests/test_mh.cpp | 11 ++-- olcPixelGameEngine3.h | 100 ++++++++++++++++++++++++++--------- 12 files changed, 159 insertions(+), 58 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 40188d23..d0cbcccc 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -18,7 +18,7 @@ static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLV static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; // Application Screen management selectors -static constexpr const char* kNSScreenClass = "NSScreen"; +//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"; @@ -139,7 +139,7 @@ static constexpr const char* kLocaleIdentifierSel = "localeIdentif 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; +// 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; @@ -149,7 +149,7 @@ 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; +// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning bool bAllowHideCursor = true; bool bHideCursor = false; @@ -280,8 +280,8 @@ namespace ObjectiveCSEL { // NSLocale selectors static SEL currentLocaleSel = nullptr; static SEL localeIdentifierSel = nullptr; - static SEL currentInputContextSel = nullptr; - static SEL localizedNameSel = nullptr; + // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning + // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning // Initialize all selectors - called once at startup void initializeSelectors() { @@ -508,7 +508,7 @@ static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFo 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); +// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning // enum for OpenGL profile versions enum class NSOpenGLProfile : int { @@ -517,6 +517,14 @@ enum class NSOpenGLProfile : int { Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile }; +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; + // Backward compatibility //static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); @@ -538,10 +546,10 @@ enum class NSWindowStyleMask : uint16_t { }; // Backward compatibility -static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); -static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); -static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); -static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); +// 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); // enum for backing store types @@ -1846,7 +1854,7 @@ extern "C" { GLint swapInterval = enabled ? 1 : 0; // Use NSOpenGLContext setValues:forParameter: to set swap interval - const GLint parameter = 222; // NSOpenGLContextParameterSwapInterval + const GLint parameter = NSOpenGLContextParameterSwapInterval; ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } @@ -1897,13 +1905,13 @@ extern "C" { CGDataProviderRef provider = CGImageGetDataProvider(image); CFDataRef rawData = CGDataProviderCopyData(provider); - CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); - CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); - CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; + //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning + //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning + //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning - self->width = CGImageGetWidth(image); - self->height = CGImageGetHeight(image); - self->bytesPerPixel = 4; + self->width = (int)CGImageGetWidth(image); + self->height = (int)CGImageGetHeight(image); + self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA self->bytesPerRow = self->width * self->bytesPerPixel; self->hasAlpha = YES; diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 4923ef82..578a8473 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -265,6 +265,8 @@ namespace olc::apis::opengl { #if OLC_HOST == OLC_HOST_WINDOWS _wglSwapIntervalEXT(n); +#else + olc_IgnoreUnused(n); #endif } diff --git a/dev/src/config.h b/dev/src/config.h index b193a529..b974500b 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -152,11 +152,20 @@ #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " +#if OLC_HOST == OLC_HOST_MACOS +// De-Noise in clang (C++20) MacOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" // Allow unknown pragmas for compatibility with different compilers +#pragma clang diagnostic ignored "-Wgnu-anonymous-struct" // Allow anonymous structs in unions + +#endif + // De-Noise in MSVC (C++20) /Wall #pragma warning(disable:4820) // Disable Padding Warnings #pragma warning(disable:5045) // Disable Spectre Mitigation Warnings #pragma warning(disable:4514) // Disable Unreferenced Inline Function Warnings + template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} @@ -188,4 +197,4 @@ inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #define OLC_FRIENDLY_HOST Host_Android #endif -//! END CONFIGURATION \ No newline at end of file +//! END CONFIGURATION diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 91208319..4b34b017 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -3,6 +3,10 @@ #include "gpu_iface.h" //! START IMPLEMENTATION +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpessimizing-move" // Suppress clang C++20 warning about moves preventing copy elision on macOS +#endif using namespace olc; // Some local pools to reduce allocations @@ -1168,4 +1172,8 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } -//! END IMPLEMENTATION \ No newline at end of file +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + +//! END IMPLEMENTATION diff --git a/dev/src/font.cpp b/dev/src/font.cpp index 928f9d7d..be13d212 100644 --- a/dev/src/font.cpp +++ b/dev/src/font.cpp @@ -83,7 +83,8 @@ namespace olc fontClassicPGE.glyphs.push_back(glyph); } else - fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f} }); + // Added missing vMonoSize for non-printable characters, which was causing -Wmissing-field-initializers + fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f}, { 0.0f, 0.0f } }); } fontClassicPGE.fLineHeight = 8.0f; diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 3c1a792f..0cbb38a9 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -1242,6 +1242,7 @@ void main() #if OLC_HOST == OLC_HOST_MACOS // The pointer value in os_win_id[1] will be set to true, when the OS requests to skip the frame swap + olc_IgnoreUnused(bVerticalSyncNow); const bool* bSkipFrame = static_cast(os_win_id[1]); if (*bSkipFrame) return true; CGLContextObj cglContext = static_cast(os_win_id[0]); diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index f2bde38c..5130bb62 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -9,15 +9,15 @@ namespace olc::host { // NSEventModifierFlags values - constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 - constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + // 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 + // 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 - constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 - constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 + // 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 { @@ -154,6 +154,7 @@ namespace olc::host { 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); @@ -181,7 +182,7 @@ namespace olc::host { } std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - + olc_IgnoreUnused(pWindow); // Ensure OpenGL renderer is created if(pMacOSOpenGLRenderer == nullptr) CreateCGLContextObj(); @@ -210,6 +211,7 @@ 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); }); @@ -218,6 +220,7 @@ namespace olc::host { bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + olc_IgnoreUnused(pWindow); dispatch_sync(dispatch_get_main_queue(), ^{ pMacOSWindow->setCursorVisibility(bVisible); }); @@ -226,6 +229,7 @@ namespace olc::host { 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; diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index 94734e6e..3136bc73 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -94,11 +94,13 @@ namespace olc::imload bool ImageLoader_MacOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { + olc_IgnoreUnused(image, sFileName); return false; } bool ImageLoader_MacOS::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) { + olc_IgnoreUnused(image, data); return false; } } diff --git a/dev/src/pixel.h b/dev/src/pixel.h index 54d7b154..95bb5de5 100644 --- a/dev/src/pixel.h +++ b/dev/src/pixel.h @@ -17,6 +17,10 @@ //! START DECLARATION #if !defined(PGE_PIXEL_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" // Silence warnings about implicitly generated copy constructor for Pixel +#endif namespace olc { class Pixel @@ -324,6 +328,10 @@ namespace olc } } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + #define PGE_PIXEL_DECLARED 1 #endif //! END DECLARATION \ No newline at end of file diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index c4cec130..68b92edb 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -17,6 +17,10 @@ //! START DECLARATION #if !defined(PGE_TRANSFORM2D_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wignored-qualifiers" // Silence warnings about ignored qualifiers in olc::t_2d +#endif namespace olc { namespace internal @@ -259,6 +263,9 @@ namespace olc typedef t_2d tf2d; typedef t_2d td2d; } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif #define PGE_TRANSFORM2D_DECLARED 1 #endif -//! END DECLARATION \ No newline at end of file +//! END DECLARATION diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index b2fb1a3b..4fefc1f3 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -364,8 +364,8 @@ class Example : public olc::PixelGameEngine - // Draw 3 triangle points - for (int i = 0; i < vecTestPoints.size(); i++) + // Draw 3 triangle points (change to size_t to remove warning -Wsign-compare) + for (size_t i = 0; i < vecTestPoints.size(); i++) { //draw.Rect(vecTestPoints[i] - (vTestPointSize * 0.5f), vTestPointSize, olc::Colour::MAGENTA); @@ -398,8 +398,8 @@ class Example : public olc::PixelGameEngine t2.translate(0.0f, 6.0f, 0.0f); t3.translate(7.0f, 0.0f, 0.0f); - olc::vf4d v1 = { 1,0,0,1 }; - olc::vf4d v2 = t3 * t2 * t1 * v1; + //olc::vf4d v1 = { 1,0,0,1 }; // Temp remove unused variable warning + //olc::vf4d v2 = t3 * t2 * t1 * v1; // Temp remove unused variable warning olc::mf4d matProj; @@ -452,7 +452,8 @@ class Example : public olc::PixelGameEngine if (mouse.GetButton(0).bPressed) { nSelectedPoint = -1; - for (int i = 0; i < vecTestPoints.size(); i++) + // change to size_t to remove warning -Wsign-compare + for (int i = 0; i < (int)vecTestPoints.size(); i++) { if ((mouse.GetPosition() - vecTestPoints[i]).mag2() < 9) { diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 2a0a1feb..dfd2ddef 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -290,11 +290,20 @@ #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " +#if OLC_HOST == OLC_HOST_MACOS +// De-Noise in clang (C++20) MacOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" // Allow unknown pragmas for compatibility with different compilers +#pragma clang diagnostic ignored "-Wgnu-anonymous-struct" // Allow anonymous structs in unions + +#endif + // De-Noise in MSVC (C++20) /Wall #pragma warning(disable:4820) // Disable Padding Warnings #pragma warning(disable:5045) // Disable Spectre Mitigation Warnings #pragma warning(disable:4514) // Disable Unreferenced Inline Function Warnings + template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} @@ -328,6 +337,10 @@ inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #if !defined(PGE_PIXEL_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" // Silence warnings about implicitly generated copy constructor for Pixel +#endif namespace olc { class Pixel @@ -635,6 +648,10 @@ namespace olc } } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + #define PGE_PIXEL_DECLARED 1 #endif @@ -1965,6 +1982,10 @@ namespace olc #endif #if !defined(PGE_TRANSFORM2D_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wignored-qualifiers" // Silence warnings about ignored qualifiers in olc::t_2d +#endif namespace olc { namespace internal @@ -2207,6 +2228,9 @@ namespace olc typedef t_2d tf2d; typedef t_2d td2d; } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif #define PGE_TRANSFORM2D_DECLARED 1 #endif @@ -7866,15 +7890,15 @@ namespace olc::host { // NSEventModifierFlags values - constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 - constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + // 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 + // 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 - constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 - constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 + // 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 { @@ -8011,6 +8035,7 @@ namespace olc::host { 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); @@ -8038,7 +8063,7 @@ namespace olc::host { } std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - + olc_IgnoreUnused(pWindow); // Ensure OpenGL renderer is created if(pMacOSOpenGLRenderer == nullptr) CreateCGLContextObj(); @@ -8067,6 +8092,7 @@ 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); }); @@ -8075,6 +8101,7 @@ namespace olc::host { bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + olc_IgnoreUnused(pWindow); dispatch_sync(dispatch_get_main_queue(), ^{ pMacOSWindow->setCursorVisibility(bVisible); }); @@ -8083,6 +8110,7 @@ namespace olc::host { 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; @@ -8629,7 +8657,7 @@ static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLV static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; // Application Screen management selectors -static constexpr const char* kNSScreenClass = "NSScreen"; +//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"; @@ -8750,7 +8778,7 @@ static constexpr const char* kLocaleIdentifierSel = "localeIdentif 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; +// 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; @@ -8760,7 +8788,7 @@ 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; +// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning bool bAllowHideCursor = true; bool bHideCursor = false; @@ -8891,8 +8919,8 @@ namespace ObjectiveCSEL { // NSLocale selectors static SEL currentLocaleSel = nullptr; static SEL localeIdentifierSel = nullptr; - static SEL currentInputContextSel = nullptr; - static SEL localizedNameSel = nullptr; + // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning + // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning // Initialize all selectors - called once at startup void initializeSelectors() { @@ -9119,7 +9147,7 @@ static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFo 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); +// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning // enum for OpenGL profile versions enum class NSOpenGLProfile : int { @@ -9128,6 +9156,14 @@ enum class NSOpenGLProfile : int { Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile }; +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; + // Backward compatibility //static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); @@ -9149,10 +9185,10 @@ enum class NSWindowStyleMask : uint16_t { }; // Backward compatibility -static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); -static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); -static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); -static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); +// 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); // enum for backing store types @@ -10457,7 +10493,7 @@ extern "C" { GLint swapInterval = enabled ? 1 : 0; // Use NSOpenGLContext setValues:forParameter: to set swap interval - const GLint parameter = 222; // NSOpenGLContextParameterSwapInterval + const GLint parameter = NSOpenGLContextParameterSwapInterval; ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } @@ -10508,13 +10544,13 @@ extern "C" { CGDataProviderRef provider = CGImageGetDataProvider(image); CFDataRef rawData = CGDataProviderCopyData(provider); - CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); - CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); - CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; + //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning + //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning + //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning - self->width = CGImageGetWidth(image); - self->height = CGImageGetHeight(image); - self->bytesPerPixel = 4; + self->width = (int)CGImageGetWidth(image); + self->height = (int)CGImageGetHeight(image); + self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA self->bytesPerRow = self->width * self->bytesPerPixel; self->hasAlpha = YES; @@ -14401,6 +14437,8 @@ namespace olc::apis::opengl { #if OLC_HOST == OLC_HOST_WINDOWS _wglSwapIntervalEXT(n); +#else + olc_IgnoreUnused(n); #endif } @@ -15901,6 +15939,7 @@ void main() #if OLC_HOST == OLC_HOST_MACOS // The pointer value in os_win_id[1] will be set to true, when the OS requests to skip the frame swap + olc_IgnoreUnused(bVerticalSyncNow); const bool* bSkipFrame = static_cast(os_win_id[1]); if (*bSkipFrame) return true; CGLContextObj cglContext = static_cast(os_win_id[0]); @@ -15979,6 +16018,10 @@ void main() #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpessimizing-move" // Suppress clang C++20 warning about moves preventing copy elision on macOS +#endif using namespace olc; // Some local pools to reduce allocations @@ -17144,6 +17187,10 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + using namespace olc; const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) @@ -18409,7 +18456,8 @@ namespace olc fontClassicPGE.glyphs.push_back(glyph); } else - fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f} }); + // Added missing vMonoSize for non-printable characters, which was causing -Wmissing-field-initializers + fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f}, { 0.0f, 0.0f } }); } fontClassicPGE.fLineHeight = 8.0f; @@ -19083,11 +19131,13 @@ namespace olc::imload bool ImageLoader_MacOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { + olc_IgnoreUnused(image, sFileName); return false; } bool ImageLoader_MacOS::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) { + olc_IgnoreUnused(image, data); return false; } } From cb2cef1aa5c444ed64e263605eec1d660da2a5fd Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 4 Apr 2026 10:45:02 +0100 Subject: [PATCH 03/28] Added blending at pixel level - alphas could be mixed up but we'll see --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 5 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/core.cpp | 4 + dev/src/draw.cpp | 18 +- dev/src/draw.h | 6 +- dev/src/gpu_opengl33.cpp | 34 +++- dev/src/gputask.h | 37 +++- examples/olcPGE3_BlendingModes.cpp | 172 ++++++++++++++++++ olcPixelGameEngine3.h | 99 ++++++++-- 9 files changed, 338 insertions(+), 40 deletions(-) create mode 100644 examples/olcPGE3_BlendingModes.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index d809d4dc..0b979b34 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -43,11 +43,12 @@ true true + true true - false - false + true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 6ebcfc8f..236e8825 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -93,6 +93,9 @@ Source Files + + Source Files + diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 7ac6568a..612d3d68 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -201,6 +201,8 @@ namespace olc // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.ImageRect(GetScreen().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); @@ -514,6 +516,8 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.SetTarget(GetScreen()); draw.WorldReset(); gpu->ApplyDefaultShader(); diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index a239b244..59a76727 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -266,6 +266,7 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std task.vertexBuffer[i*2+0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; task.vertexBuffer[i*2+1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, vColours[i + 1], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; } + task.blendmode = blendMode; task.tint = tint; return task; } @@ -278,6 +279,7 @@ GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector vecGels; + olc::vf2d vGelSize = { 32.0f, 32.0f }; + sGel* pSelectedGel = nullptr; + olc::BlendMode currentBlendMode = olc::BlendMode::Alpha; + olc::Image imgTest; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Create "gels" - these are just coloured rectangles that we + // will draw with different blend modes + vecGels.push_back({ { 98.0f, 74.0f }, olc::Colour::RED }); + vecGels.push_back({ { 114.0f, 57.0f }, olc::Colour::GREEN }); + vecGels.push_back({ { 118.0f, 80.0f }, olc::Colour::BLUE }); + vecGels.push_back({ { 145.0f, 178.0f }, olc::Colour::WHITE }); + vecGels.push_back({ { 113.0f, 135.0f }, olc::Colour::BLACK }); + vecGels.push_back({ { 77.0f, 176.0f }, olc::Pixel(128, 128, 128) }); + vecGels.push_back({ { 47.0f, 128.0f }, olc::Pixel(255, 255, 255, 64) }); + + // Last gel uses an image for complexity + vecGels.push_back({ { 18.0f, 169.0f }, olc::Pixel(255,255,255,64)}); + CreateImageFromFile(imgTest, "./assets/minsanity_texture.png"); + + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Draw Colour Bars + draw.FilledRect({ 0.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::VERY_DARK_RED, olc::Colour::VERY_DARK_RED, + olc::Colour::RED, olc::Colour::RED); + draw.FilledRect({ 32.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::VERY_DARK_GREEN, olc::Colour::VERY_DARK_GREEN, + olc::Colour::GREEN, olc::Colour::GREEN); + draw.FilledRect({ 64.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::VERY_DARK_BLUE, olc::Colour::VERY_DARK_BLUE, + olc::Colour::BLUE, olc::Colour::BLUE); + draw.FilledRect({ 96.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::BLACK); + draw.FilledRect({ 128.0f, 0.0f }, { 128.0f, 240.0f }, + olc::Colour::WHITE, olc::Colour::WHITE, + olc::Colour::BLACK, olc::Colour::BLACK); + + // TADA!! Setting the Blend Mode changes how the GPU draws pixels to the screen. + // This is a global state, so all drawing operations will be affected by this + // setting until it is changed again + draw.SetBlendMode(currentBlendMode); + + // Note drawing happens as normal, the GPU has been instructed to + // use the current blend mode when drawing pixels + + // Draw Gels with appropriate mode + int gelCount = 0; + for (auto& gel : vecGels) + { + if (gelCount == vecGels.size() - 1) + { + // Gel is a texture + draw.ImageRect(imgTest, gel.vPos, vGelSize); + } + else // Gel is just a coloured rectangle + draw.FilledRect(gel.vPos, vGelSize, gel.col); + + gelCount++; + } + + + + // Switch back to alpha mode (aka Normal) for UI + draw.SetBlendMode(olc::BlendMode::Alpha); + + // Hilioght frame of gel and detect if mouse is over it + for (auto& gel : vecGels) + { + draw.Rect(gel.vPos, vGelSize, olc::Colour::WHITE); + + if (mouse.GetPosition().x >= gel.vPos.x && mouse.GetPosition().x < gel.vPos.x + vGelSize.x && + mouse.GetPosition().y >= gel.vPos.y && mouse.GetPosition().y < gel.vPos.y + vGelSize.y) + { + if (mouse.GetButton(0).bPressed) + { + pSelectedGel = ⋛ + } + } + } + + // Handle mouse + if(pSelectedGel && mouse.GetButton(0).bHeld) + { + pSelectedGel->vPos = mouse.GetPosition() - vGelSize / 2.0f; + } + + if (mouse.GetButton(0).bReleased) + { + pSelectedGel = nullptr; + } + + + // Change blend mode with number keys + if (keyboard.GetKey(olc::Key::K1).bPressed) + currentBlendMode = olc::BlendMode::Alpha; + if (keyboard.GetKey(olc::Key::K2).bPressed) + currentBlendMode = olc::BlendMode::Multiplicative; + if (keyboard.GetKey(olc::Key::K3).bPressed) + currentBlendMode = olc::BlendMode::Additive; + + + // Draw UI + draw.String({ 10.0f, 2.0f }, "1: Alpha Blend", + currentBlendMode == olc::BlendMode::Alpha ? olc::Colour::YELLOW : olc::Colour::WHITE); + draw.String({ 10.0f, 12.0f }, "2: Multiplicative Blend", + currentBlendMode == olc::BlendMode::Multiplicative ? olc::Colour::YELLOW : olc::Colour::WHITE); + draw.String({ 10.0f, 22.0f }, "3: Additive Blend", + currentBlendMode == olc::BlendMode::Additive ? olc::Colour::YELLOW : olc::Colour::WHITE); + draw.String({ 10.0f, 32.0f }, "Mouse to drag gels", olc::Colour::WHITE); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_BlendingModes demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 8d6c2169..5b848a80 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2479,6 +2479,29 @@ namespace olc // Vertex buffer is a series of discrete triangles List, }; + + // Define blend modes for drawing operations + enum class BlendMode : uint8_t + { + // Alpha blend source and destination pixels based on source alpha + Alpha = 0, + // Additively blend source and destination pixels together + Additive, + // Multiplicatively blend source and destination pixels together + Multiplicative, + // No blending, just overwrite pixels with source colour + None, + }; + + enum class CullMode : uint8_t + { + // No + None = 0, + // Cull if vertices are listed in clockwise order + ClockWise, + // Cull if vertices are listed in anticlockwise order + CounterClockWise + }; // This is the default "packet" of work that is sent to // a GPU for drawing. Various drawing operations throughout @@ -2527,21 +2550,17 @@ namespace olc // Overall biasing colour (great for blends) olc::Pixel tint = olc::Colour::WHITE; + // Texture to be used for drawing (if any) (in slot #0) olc::Image* pImage = nullptr; // Define super structure to be drawn Structure structure = Structure::Fan; // Define if GPU should face cull based on winding order - enum class CullMode : uint8_t - { - // No - None = 0, - // Cull if vertices are listed in clockwise order - ClockWise, - // Cull if vertices are listed in anticlockwise order - CounterClockWise - } cullmode = CullMode::None; + CullMode cullmode = CullMode::None; + + // Define blend mode for drawing + BlendMode blendmode = BlendMode::Alpha; }; } #define PGE_GPUTASK_DECLARED 1 @@ -3349,7 +3368,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE); public: // Applied Rendering Modes - void SetCullMode(const olc::GPUTask::CullMode mode); + void SetCullMode(const olc::CullMode mode); + void SetBlendMode(const olc::BlendMode mode); void EnableDepth(const bool bEnable); void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); @@ -3549,7 +3569,8 @@ namespace olc olc::vi2d vViewportSize = { 0, 0 }; - olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + olc::CullMode cullMode = olc::CullMode::None; + olc::BlendMode blendMode = olc::BlendMode::Alpha; bool bDepth = true; @@ -15823,31 +15844,51 @@ void main() // Apply Culling modes - if (task.cullmode == GPUTask::CullMode::None) + if (task.cullmode == olc::CullMode::None) { gl.glDisable(GL_CULL_FACE); } - else if (task.cullmode == GPUTask::CullMode::ClockWise) + else if (task.cullmode == olc::CullMode::ClockWise) { gl.glCullFace(GL_FRONT); gl.glEnable(GL_CULL_FACE); } - else if (task.cullmode == GPUTask::CullMode::CounterClockWise) + else if (task.cullmode == olc::CullMode::CounterClockWise) { gl.glCullFace(GL_BACK); gl.glEnable(GL_CULL_FACE); } - //// Apply Depth Testing (if required) + // Apply Depth Testing (if required) if (task.bDepth) + { gl.glEnable(GL_DEPTH_TEST); + gl.glDepthFunc(GL_LESS); + } + - glDepthFunc(GL_LESS); + // Apply Blending Mode + if (task.blendmode == olc::BlendMode::Alpha) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + else if(task.blendmode == olc::BlendMode::Additive) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + else if(task.blendmode == olc::BlendMode::Multiplicative) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + } - gl.glEnable(GL_BLEND); + //gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + //gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + // Apply Rendering Mode if (task.bWireframe) gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); @@ -16282,6 +16323,7 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std task.vertexBuffer[i*2+0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; task.vertexBuffer[i*2+1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, vColours[i + 1], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; } + task.blendmode = blendMode; task.tint = tint; return task; } @@ -16294,6 +16336,7 @@ GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vectorSetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.ImageRect(GetScreen().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); @@ -18257,6 +18316,8 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.SetTarget(GetScreen()); draw.WorldReset(); gpu->ApplyDefaultShader(); From ba58c5a30620b96921c1083da2bcaad6cd0cb9e8 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:31:28 +0100 Subject: [PATCH 04/28] 247-some-macos-users-are-finding-the-display-doesnt-fill-the-screen --- dev/src/api_macos.cpp | 71 +++++++++++++++++++++++--------- dev/src/host_apple_macos.cpp | 8 ++-- olcPixelGameEngine3.h | 79 +++++++++++++++++++++++++----------- 3 files changed, 110 insertions(+), 48 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index d0cbcccc..2bc7d6c5 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -111,6 +111,9 @@ static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame static constexpr const char* kSetContentViewSel = "setContentView:"; static constexpr const char* kContentViewSel = "contentView"; static constexpr const char* kBoundsSel = "bounds"; +static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru +static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; +static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; static constexpr const char* kOpenGLContextSel = "openGLContext"; static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; @@ -259,6 +262,9 @@ namespace ObjectiveCSEL { static SEL setContentViewSel = nullptr; static SEL contentViewSel = nullptr; static SEL boundsSel = nullptr; + static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru + static SEL convertPointToBackingSel = nullptr; + static SEL convertPointFromBackingSel = nullptr; static SEL convertPointFromViewSel = nullptr; static SEL openGLContextSel = nullptr; static SEL makeCurrentContextSel = nullptr; @@ -376,6 +382,9 @@ namespace ObjectiveCSEL { setContentViewSel = sel_registerName(kSetContentViewSel); contentViewSel = sel_registerName(kContentViewSel); boundsSel = sel_registerName(kBoundsSel); + convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru + convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); + convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); openGLContextSel = sel_registerName(kOpenGLContextSel); makeCurrentContextSel = sel_registerName(kMakeCurrentContextSel); @@ -897,8 +906,9 @@ void view_flagsChanged(id self, SEL _cmd, id event) { //====================================================================// // Mouse Event Handling -// Convert from window coordinates to content view coordinates and flip Y coordinate -// from bottom-left (macOS format) to top-left (standard format) +// 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]] @@ -907,16 +917,23 @@ void convertToContentViewCoordinates(NSPoint& location) { id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); if (contentView) { - // Convert from window coordinates to view coordinates + + // Convert from window coordinates to view coordinates (still logical points) NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); - // Get the content view bounds to flip Y coordinate + // Scale from logical points to physical backing pixels + NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + + // 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); + // Flip Y coordinate from bottom-left to top-left - location.x = contentLocation.x; - location.y = contentBounds.height - contentLocation.y; + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + } } else @@ -1582,13 +1599,19 @@ extern "C" { return; } - // Get the content view bounds + // Thank you, Ben the ultimate Guru, + // Get the content view bounds in points (logical coordinates). + // On macOS, AppKit uses points rather than physical pixels. On HighDPI + // displays (that are scaling) a point maps to >1 pixels. + // convertRectToBacking: converts the point-based rect to physical pixels, + // which is what OpenGL expects NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); - if (x) *x = contentBounds.x; - if (y) *y = contentBounds.y; - if (width) *width = contentBounds.width; - if (height) *height = contentBounds.height; + if (x) *x = pixelBounds.x; + if (y) *y = pixelBounds.y; + if (width) *width = pixelBounds.width; + if (height) *height = pixelBounds.height; } @@ -1680,17 +1703,25 @@ extern "C" { // Get the content view id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - // Get the content view bounds to flip Y coordinate + // x,y are in physical backing pixels. CGWarpMouseCursorPosition + // and all AppKit frame/bounds values use logical points, so convert the incoming + // pixel position to points. + NSPoint pixelPos = { x, y }; + NSPoint pointPos = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( contentView, ObjectiveCSEL::convertPointFromBackingSel, pixelPos); + + // Get content bounds in points for clamping and Y-flip NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - - // NOTE: we need to ensure the new cursor position is within the window bounds to prevent unexpected behavior - auto posX = std::clamp(location.x +x, location.x, location.x + contentBounds.width); - auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + y, - screenFrame.height - location.y - contentBounds.height, - screenFrame.height - location.y); + + // NOTE: clamp to keep the cursor within the window content area + auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + } } diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 5130bb62..14a6ff94 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -277,6 +277,9 @@ namespace olc::host { 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); @@ -296,10 +299,7 @@ namespace olc::host { pMacOSWindow->show(styleMask); pMacOSEventHandler->enable(); - //--- Start up our engine threading system ----- - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - + //--- Start up our engine threading system ---- // Start the PGE context on the main thread // Mark system as active systemActive = true; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 48bd405a..48303f8c 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8219,6 +8219,9 @@ namespace olc::host { 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); @@ -8238,10 +8241,7 @@ namespace olc::host { pMacOSWindow->show(styleMask); pMacOSEventHandler->enable(); - //--- Start up our engine threading system ----- - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - + //--- Start up our engine threading system ---- // Start the PGE context on the main thread // Mark system as active systemActive = true; @@ -8811,6 +8811,9 @@ static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame static constexpr const char* kSetContentViewSel = "setContentView:"; static constexpr const char* kContentViewSel = "contentView"; static constexpr const char* kBoundsSel = "bounds"; +static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru +static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; +static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; static constexpr const char* kOpenGLContextSel = "openGLContext"; static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; @@ -8959,6 +8962,9 @@ namespace ObjectiveCSEL { static SEL setContentViewSel = nullptr; static SEL contentViewSel = nullptr; static SEL boundsSel = nullptr; + static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru + static SEL convertPointToBackingSel = nullptr; + static SEL convertPointFromBackingSel = nullptr; static SEL convertPointFromViewSel = nullptr; static SEL openGLContextSel = nullptr; static SEL makeCurrentContextSel = nullptr; @@ -9076,6 +9082,9 @@ namespace ObjectiveCSEL { setContentViewSel = sel_registerName(kSetContentViewSel); contentViewSel = sel_registerName(kContentViewSel); boundsSel = sel_registerName(kBoundsSel); + convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru + convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); + convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); openGLContextSel = sel_registerName(kOpenGLContextSel); makeCurrentContextSel = sel_registerName(kMakeCurrentContextSel); @@ -9597,8 +9606,9 @@ void view_flagsChanged(id self, SEL _cmd, id event) { //====================================================================// // Mouse Event Handling -// Convert from window coordinates to content view coordinates and flip Y coordinate -// from bottom-left (macOS format) to top-left (standard format) +// 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]] @@ -9607,16 +9617,23 @@ void convertToContentViewCoordinates(NSPoint& location) { id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); if (contentView) { - // Convert from window coordinates to view coordinates + + // Convert from window coordinates to view coordinates (still logical points) NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); - // Get the content view bounds to flip Y coordinate + // Scale from logical points to physical backing pixels + NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + + // 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); + // Flip Y coordinate from bottom-left to top-left - location.x = contentLocation.x; - location.y = contentBounds.height - contentLocation.y; + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + } } else @@ -10282,13 +10299,19 @@ extern "C" { return; } - // Get the content view bounds + // Thank you, Ben the ultimate Guru, + // Get the content view bounds in points (logical coordinates). + // On macOS, AppKit uses points rather than physical pixels. On HighDPI + // displays (that are scaling) a point maps to >1 pixels. + // convertRectToBacking: converts the point-based rect to physical pixels, + // which is what OpenGL expects NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); - if (x) *x = contentBounds.x; - if (y) *y = contentBounds.y; - if (width) *width = contentBounds.width; - if (height) *height = contentBounds.height; + if (x) *x = pixelBounds.x; + if (y) *y = pixelBounds.y; + if (width) *width = pixelBounds.width; + if (height) *height = pixelBounds.height; } @@ -10380,17 +10403,25 @@ extern "C" { // Get the content view id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - // Get the content view bounds to flip Y coordinate + // x,y are in physical backing pixels. CGWarpMouseCursorPosition + // and all AppKit frame/bounds values use logical points, so convert the incoming + // pixel position to points. + NSPoint pixelPos = { x, y }; + NSPoint pointPos = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( contentView, ObjectiveCSEL::convertPointFromBackingSel, pixelPos); + + // Get content bounds in points for clamping and Y-flip NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - - // NOTE: we need to ensure the new cursor position is within the window bounds to prevent unexpected behavior - auto posX = std::clamp(location.x +x, location.x, location.x + contentBounds.width); - auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + y, - screenFrame.height - location.y - contentBounds.height, - screenFrame.height - location.y); + + // NOTE: clamp to keep the cursor within the window content area + auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + } } From bd80aa31778ab6d4113df84b2f60c7b2956f2009 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 7 Apr 2026 16:09:38 -0400 Subject: [PATCH 05/28] [opengl33] fix opengl bindings for egl on wayland backend --- dev/src/gpu_opengl33.cpp | 3 +++ olcPixelGameEngine3.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index da9c747f..96d36689 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -346,6 +346,9 @@ void main() EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; /* create an EGL rendering context */ +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + eglBindAPI(EGL_OPENGL_API); +#endif glRenderContext.context = eglCreateContext(glRenderContext.display, glRenderContext.config, EGL_NO_CONTEXT, context_config); glRenderContext.surface = eglCreateWindowSurface(glRenderContext.display, glRenderContext.config, window_handle, nullptr); if(glRenderContext.surface == EGL_NO_SURFACE) { diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 48bd405a..d409b5b0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -15104,6 +15104,9 @@ void main() EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; /* create an EGL rendering context */ +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + eglBindAPI(EGL_OPENGL_API); +#endif glRenderContext.context = eglCreateContext(glRenderContext.display, glRenderContext.config, EGL_NO_CONTEXT, context_config); glRenderContext.surface = eglCreateWindowSurface(glRenderContext.display, glRenderContext.config, window_handle, nullptr); if(glRenderContext.surface == EGL_NO_SURFACE) { From 19bbbaeb5a992b9ff8fd3fdf9e45734ee36ad0d0 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:35:45 -0500 Subject: [PATCH 06/28] Always use libdecor for wayland decorations --- dev/src/host_lin_wayland.cpp | 148 ++++----------------------- dev/src/host_lin_wayland.h | 40 +------- examples/CMakeLists.txt | 26 +---- examples/olcPGE3_3DCube.cpp | 2 +- olcPixelGameEngine3.h | 188 ++++------------------------------- 5 files changed, 47 insertions(+), 357 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 3e6f6db2..b6d3edbd 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -54,15 +54,8 @@ namespace olc::host .configure_bounds = Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback, .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback }; - - #ifdef ENABLE_DECORATION_PROTOCOL - static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback - }; - #endif } - #ifdef ENABLE_LIBDECOR namespace decor { static libdecor_interface libdecor_error_listener = { .error = Host_Linux_Wayland::libdecor_error_callback, @@ -75,7 +68,6 @@ namespace olc::host .dismiss_popup = Host_Linux_Wayland::libdecor_dismiss_popup_callback }; } - #endif Host_Linux_Wayland::Host_Linux_Wayland() { @@ -90,29 +82,7 @@ namespace olc::host throw; } - // If only the decoration protocol is enabled, then not having the protocol is a hard error - #if defined(ENABLE_DECORATION_PROTOCOL) && !defined(ENABLE_LIBDECOR) - if(decoration_manager == nullptr) { - throw; - } - // If only libdecor is enabled, then flag "using_libdecor" - #elif !defined(ENABLE_DECORATION_PROTOCOL) && defined(ENABLE_LIBDECOR) - using_libdecor = true; - - // If both are enabled, use libdecor if the decoration protocol is not present - #else - using_libdecor = (decoration_manager == nullptr); - #endif - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - } else { - decor_context = libdecor_new(display, &decor::libdecor_error_listener); - } - #else - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - #endif + decor_context = libdecor_new(display, &decor::libdecor_error_listener); // Load the default cursor cursor_theme = wl_cursor_theme_load(NULL, 24, shm); @@ -200,14 +170,9 @@ namespace olc::host if(surface_xdg) { xdg_surface_destroy(surface_xdg); } - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1_destroy(decorations); - #endif - #ifdef ENABLE_LIBDECOR if(decor_frame) { libdecor_frame_unref(decor_frame); } - #endif wl_surface_destroy(surface); } @@ -216,15 +181,10 @@ namespace olc::host mapUID2OlcWindow.clear(); mapUID2Window.clear(); - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1_destroy(decoration_manager); - #endif - #ifdef ENABLE_LIBDECOR if(decor_context) { libdecor_unref(decor_context); decor_context = nullptr; } - #endif xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); @@ -284,21 +244,13 @@ namespace olc::host } }); - #if !defined(ENABLE_LIBDECOR) - while(systemActive && wl_display_dispatch_pending(display) != -1) { } - #else bool keep_running = true; while(systemActive && keep_running) { - if(using_libdecor) { - if(decor_context) { - std::lock_guard l{decor_mutex}; - keep_running = libdecor_dispatch(decor_context, 0) >= 0; - } - } else { - keep_running = wl_display_dispatch_pending(display) != -1; + if(decor_context) { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; } } - #endif systemActive = false; if(threadSystem.joinable()) @@ -341,43 +293,19 @@ namespace olc::host wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); w.surface = wl_compositor_create_surface(compositor); - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - #endif - w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); - - xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); - w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); - xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); - xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); - if (!pWindow->config.bResizeable) { - xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); - xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); - } - - #ifdef ENABLE_DECORATION_PROTOCOL - w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); - zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); - zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); - #endif - #ifdef ENABLE_LIBDECOR - } else { - std::lock_guard l{decor_mutex}; - w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); - w.floating_width = vWindowSize.x; - w.floating_height = vWindowSize.y; - libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); - libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); - - if(!pWindow->config.bResizeable) { - libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); - } - libdecor_frame_map(w.decor_frame); + std::lock_guard l{decor_mutex}; + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); } - #endif + + libdecor_frame_map(w.decor_frame); wl_surface_set_opaque_region(w.surface, region); w.window = wl_egl_window_create(w.surface, vWindowSize.x, vWindowSize.y); @@ -405,16 +333,8 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - std::lock_guard l{decor_mutex}; - libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); - } else { - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - } - #else - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - #endif + std::lock_guard l{decor_mutex}; + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); } return true; } @@ -491,25 +411,10 @@ namespace olc::host if(itr != mapUID2Window.end()) { pWindow->bWindowIsFullscreen = bFullScreen; if(bFullScreen) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); - } else { - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - } - #else - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - #endif + libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); + } else { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_unset_fullscreen(itr->second.decor_frame); - } else { - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - } - #else - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - #endif + libdecor_frame_unset_fullscreen(itr->second.decor_frame); } } return true; @@ -530,11 +435,6 @@ namespace olc::host seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); wl_seat_add_listener(seat, &wayland::seat_listener, this); } - #ifdef ENABLE_DECORATION_PROTOCOL - if(std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - decoration_manager = static_cast(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, version)); - } - #endif if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } @@ -994,15 +894,6 @@ namespace olc::host return; } - #ifdef ENABLE_DECORATION_PROTOCOL - void Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode) - { - // auto* host = reinterpret_cast(data); - // fprintf(stderr, "zxdg_decoration_manager_v1 mode %d\n", mode); - } - #endif - - #ifdef ENABLE_LIBDECOR void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) { std::cerr << "libdecor: " << error << ": " << message << "\n"; @@ -1089,7 +980,6 @@ namespace olc::host { } - #endif std::vector Host_Linux_Wayland::GetHostWindowDescriptor(olc::Window* pWindow) { diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index b7286027..1208c64b 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -16,28 +16,12 @@ //! END CUSTOMHEADER //! START DECLARATION -#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_LIBDECOR -#endif - -#if !defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_DECORATION_PROTOCOL -#endif - -#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) -#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." -#endif #include #include #include #include "xdg-shell.h" -// Only include the decoration protocol if we are not forcing libdecor -#ifdef ENABLE_DECORATION_PROTOCOL -#include "xdg-decoration.h" -#endif - #include "pointer-warp.h" #include #include @@ -45,9 +29,8 @@ #include #include -#ifdef ENABLE_LIBDECOR #include "libdecor.h" -#endif + #include #include @@ -62,9 +45,6 @@ namespace olc::host wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; xdg_toplevel* toplevel{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1* decorations{nullptr}; - #endif wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -73,7 +53,6 @@ namespace olc::host // Ignore window size bounds for fullscreen events bool fullscreen{false}; - #ifdef ENABLE_LIBDECOR // libdecor support libdecor_frame* decor_frame{nullptr}; int configured_width{}; @@ -81,7 +60,6 @@ namespace olc::host libdecor_window_state decor_window_state; int floating_width{}; int floating_height{}; - #endif ~WaylandWindow(); }; @@ -134,9 +112,6 @@ namespace olc::host xkb_keymap* kb_keymap{nullptr}; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1* decoration_manager{nullptr}; - #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; @@ -147,12 +122,10 @@ namespace olc::host size_t active_window_id; - #ifdef ENABLE_LIBDECOR // libdecor support bool using_libdecor{false}; libdecor* decor_context{nullptr}; std::mutex decor_mutex; - #endif public: Host_Linux_Wayland(); @@ -217,20 +190,15 @@ namespace olc::host static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); - #ifdef ENABLE_DECORATION_PROTOCOL - static void xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode); - #endif - #ifdef ENABLE_LIBDECOR // libdecor callbacks static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); static void libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data); static void libdecor_close_callback(libdecor_frame* frame, void* data); static void libdecor_commit_callback(libdecor_frame* frame, void* data); static void libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data); - #endif - - private: + + private: // Wayland callback functions void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void registry_handle_global_remove(wl_registry* registry, uint32_t name); @@ -261,12 +229,10 @@ namespace olc::host void xdg_toplevel_close(xdg_toplevel* toplevel); void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); - #ifdef ENABLE_LIBDECOR // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); void libdecor_commit(libdecor_frame* frame); - #endif bool CreateEGLContext(WaylandWindow* window); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b81dc40c..ca84eda0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -28,10 +28,7 @@ if(UNIX AND BUILD_WAYLAND) pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) pkg_check_modules(EGL REQUIRED egl) pkg_check_modules(WAYLAND_CURSOR REQUIRED wayland-cursor) - - if(NOT DISABLE_LIBDECOR) pkg_check_modules(LIBDECOR REQUIRED IMPORTED_TARGET libdecor-0) - endif() find_package(OpenGL REQUIRED) find_package(PNG REQUIRED) @@ -69,22 +66,13 @@ if(UNIX AND BUILD_WAYLAND) generate_wayland_protocol("xdg-shell" "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") generate_wayland_protocol("pointer-warp" "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") - generate_wayland_protocol("tablet" "/usr/share/wayland-protocols/stable/tablet/tablet-v2.xml") - + # Create a library for the wayland protocol files add_library(wayland_protocols INTERFACE) target_link_libraries(wayland_protocols INTERFACE wayland_xdg-shell wayland_pointer-warp - wayland_tablet ) - - if(NOT FORCE_WAYLAND_LIBDECOR) - generate_wayland_protocol("xdg-decoration" "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") - target_link_libraries(wayland_protocols INTERFACE - wayland_xdg-decoration - ) - endif() target_include_directories(wayland_protocols INTERFACE ${CMAKE_CURRENT_BINARY_DIR} @@ -116,12 +104,6 @@ foreach(source ${SOURCES}) if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) if(BUILD_WAYLAND) target_compile_definitions(${EXE_NAME} PRIVATE OLC_HOST=3) - if(FORCE_WAYLAND_LIBDECOR) - target_compile_definitions(${EXE_NAME} PRIVATE FORCE_WAYLAND_LIBDECOR=1) - endif() - if(DISABLE_LIBDECOR) - target_compile_definitions(${EXE_NAME} PRIVATE DISABLE_LIBDECOR=1) - endif() target_link_libraries(${EXE_NAME} PRIVATE wayland_protocols @@ -135,11 +117,7 @@ foreach(source ${SOURCES}) Threads::Threads ) - if(NOT DISABLE_LIBDECOR) - target_link_libraries(${EXE_NAME} PRIVATE - PkgConfig::LIBDECOR - ) - endif() + target_link_libraries(${EXE_NAME} PRIVATE PkgConfig::LIBDECOR ) else() # x11 target_link_libraries(${EXE_NAME} PRIVATE png) diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index a51cdbc3..1839d6de 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -110,7 +110,7 @@ class Example_3DCube : public olc::PixelGameEngine // so cull counter-clockwise faces to show it off in all its glory! This is // counter to OpenGL's default culling mode, so it's a good test of the culling // system as well. - draw.SetCullMode(olc::GPUTask::CullMode::CounterClockWise); + draw.SetCullMode(olc::CullMode::CounterClockWise); // Draw the cube using the sanity cube's layout, and vectors of vertices, colours // and texture coordinates. diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 48bd405a..bef8d110 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5778,28 +5778,12 @@ namespace olc::host #if OLC_HOST == OLC_HOST_LINUX_WAYLAND -#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_LIBDECOR -#endif - -#if !defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_DECORATION_PROTOCOL -#endif - -#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) -#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." -#endif #include #include #include #include "xdg-shell.h" -// Only include the decoration protocol if we are not forcing libdecor -#ifdef ENABLE_DECORATION_PROTOCOL -#include "xdg-decoration.h" -#endif - #include "pointer-warp.h" #include #include @@ -5807,9 +5791,8 @@ namespace olc::host #include #include -#ifdef ENABLE_LIBDECOR #include "libdecor.h" -#endif + #include #include @@ -5824,9 +5807,6 @@ namespace olc::host wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; xdg_toplevel* toplevel{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1* decorations{nullptr}; - #endif wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -5835,7 +5815,6 @@ namespace olc::host // Ignore window size bounds for fullscreen events bool fullscreen{false}; - #ifdef ENABLE_LIBDECOR // libdecor support libdecor_frame* decor_frame{nullptr}; int configured_width{}; @@ -5843,7 +5822,6 @@ namespace olc::host libdecor_window_state decor_window_state; int floating_width{}; int floating_height{}; - #endif ~WaylandWindow(); }; @@ -5896,9 +5874,6 @@ namespace olc::host xkb_keymap* kb_keymap{nullptr}; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1* decoration_manager{nullptr}; - #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; @@ -5909,12 +5884,10 @@ namespace olc::host size_t active_window_id; - #ifdef ENABLE_LIBDECOR // libdecor support bool using_libdecor{false}; libdecor* decor_context{nullptr}; std::mutex decor_mutex; - #endif public: Host_Linux_Wayland(); @@ -5979,20 +5952,15 @@ namespace olc::host static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); - #ifdef ENABLE_DECORATION_PROTOCOL - static void xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode); - #endif - #ifdef ENABLE_LIBDECOR // libdecor callbacks static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); static void libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data); static void libdecor_close_callback(libdecor_frame* frame, void* data); static void libdecor_commit_callback(libdecor_frame* frame, void* data); static void libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data); - #endif - - private: + + private: // Wayland callback functions void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void registry_handle_global_remove(wl_registry* registry, uint32_t name); @@ -6023,12 +5991,10 @@ namespace olc::host void xdg_toplevel_close(xdg_toplevel* toplevel); void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); - #ifdef ENABLE_LIBDECOR // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); void libdecor_commit(libdecor_frame* frame); - #endif bool CreateEGLContext(WaylandWindow* window); @@ -11631,15 +11597,8 @@ namespace olc::host .configure_bounds = Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback, .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback }; - - #ifdef ENABLE_DECORATION_PROTOCOL - static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback - }; - #endif } - #ifdef ENABLE_LIBDECOR namespace decor { static libdecor_interface libdecor_error_listener = { .error = Host_Linux_Wayland::libdecor_error_callback, @@ -11652,7 +11611,6 @@ namespace olc::host .dismiss_popup = Host_Linux_Wayland::libdecor_dismiss_popup_callback }; } - #endif Host_Linux_Wayland::Host_Linux_Wayland() { @@ -11667,29 +11625,7 @@ namespace olc::host throw; } - // If only the decoration protocol is enabled, then not having the protocol is a hard error - #if defined(ENABLE_DECORATION_PROTOCOL) && !defined(ENABLE_LIBDECOR) - if(decoration_manager == nullptr) { - throw; - } - // If only libdecor is enabled, then flag "using_libdecor" - #elif !defined(ENABLE_DECORATION_PROTOCOL) && defined(ENABLE_LIBDECOR) - using_libdecor = true; - - // If both are enabled, use libdecor if the decoration protocol is not present - #else - using_libdecor = (decoration_manager == nullptr); - #endif - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - } else { - decor_context = libdecor_new(display, &decor::libdecor_error_listener); - } - #else - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - #endif + decor_context = libdecor_new(display, &decor::libdecor_error_listener); // Load the default cursor cursor_theme = wl_cursor_theme_load(NULL, 24, shm); @@ -11777,14 +11713,9 @@ namespace olc::host if(surface_xdg) { xdg_surface_destroy(surface_xdg); } - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1_destroy(decorations); - #endif - #ifdef ENABLE_LIBDECOR if(decor_frame) { libdecor_frame_unref(decor_frame); } - #endif wl_surface_destroy(surface); } @@ -11793,15 +11724,10 @@ namespace olc::host mapUID2OlcWindow.clear(); mapUID2Window.clear(); - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1_destroy(decoration_manager); - #endif - #ifdef ENABLE_LIBDECOR if(decor_context) { libdecor_unref(decor_context); decor_context = nullptr; } - #endif xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); @@ -11861,21 +11787,13 @@ namespace olc::host } }); - #if !defined(ENABLE_LIBDECOR) - while(systemActive && wl_display_dispatch_pending(display) != -1) { } - #else bool keep_running = true; while(systemActive && keep_running) { - if(using_libdecor) { - if(decor_context) { - std::lock_guard l{decor_mutex}; - keep_running = libdecor_dispatch(decor_context, 0) >= 0; - } - } else { - keep_running = wl_display_dispatch_pending(display) != -1; + if(decor_context) { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; } } - #endif systemActive = false; if(threadSystem.joinable()) @@ -11918,43 +11836,19 @@ namespace olc::host wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); w.surface = wl_compositor_create_surface(compositor); - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - #endif - w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); - - xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); - w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); - xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); - xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); - if (!pWindow->config.bResizeable) { - xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); - xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); - } - - #ifdef ENABLE_DECORATION_PROTOCOL - w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); - zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); - zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); - #endif - #ifdef ENABLE_LIBDECOR - } else { - std::lock_guard l{decor_mutex}; - w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); - w.floating_width = vWindowSize.x; - w.floating_height = vWindowSize.y; - libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); - libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); - - if(!pWindow->config.bResizeable) { - libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); - } - libdecor_frame_map(w.decor_frame); + std::lock_guard l{decor_mutex}; + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); } - #endif + + libdecor_frame_map(w.decor_frame); wl_surface_set_opaque_region(w.surface, region); w.window = wl_egl_window_create(w.surface, vWindowSize.x, vWindowSize.y); @@ -11982,16 +11876,8 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - std::lock_guard l{decor_mutex}; - libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); - } else { - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - } - #else - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - #endif + std::lock_guard l{decor_mutex}; + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); } return true; } @@ -12068,25 +11954,10 @@ namespace olc::host if(itr != mapUID2Window.end()) { pWindow->bWindowIsFullscreen = bFullScreen; if(bFullScreen) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); - } else { - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - } - #else - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - #endif + libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); + } else { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_unset_fullscreen(itr->second.decor_frame); - } else { - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - } - #else - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - #endif + libdecor_frame_unset_fullscreen(itr->second.decor_frame); } } return true; @@ -12107,11 +11978,6 @@ namespace olc::host seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); wl_seat_add_listener(seat, &wayland::seat_listener, this); } - #ifdef ENABLE_DECORATION_PROTOCOL - if(std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - decoration_manager = static_cast(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, version)); - } - #endif if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } @@ -12571,15 +12437,6 @@ namespace olc::host return; } - #ifdef ENABLE_DECORATION_PROTOCOL - void Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode) - { - // auto* host = reinterpret_cast(data); - // fprintf(stderr, "zxdg_decoration_manager_v1 mode %d\n", mode); - } - #endif - - #ifdef ENABLE_LIBDECOR void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) { std::cerr << "libdecor: " << error << ": " << message << "\n"; @@ -12666,7 +12523,6 @@ namespace olc::host { } - #endif std::vector Host_Linux_Wayland::GetHostWindowDescriptor(olc::Window* pWindow) { From bff67b44e787bf7d4376d209de3831e15a6c395f Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:41:36 -0500 Subject: [PATCH 07/28] Remove the remaining xdg toplevel things --- dev/src/core.h | 10 +-- dev/src/host_lin_wayland.cpp | 95 ---------------------------- dev/src/host_lin_wayland.h | 10 --- olcPixelGameEngine3.h | 119 +++-------------------------------- 4 files changed, 14 insertions(+), 220 deletions(-) diff --git a/dev/src/core.h b/dev/src/core.h index 3cb282a4..3c45a22b 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -42,11 +42,11 @@ namespace olc // These three are inherited from WindowConfig // Start in full-screen mode - bool bFullScreen = false; - // Allow full screen as an option with ALT-ENTER - bool bFullScreenable = true; - // Allow the window to be resized by user - bool bResizeable = true; + // bool bFullScreen = false; + // // Allow full screen as an option with ALT-ENTER + // bool bFullScreenable = true; + // // Allow the window to be resized by user + // bool bResizeable = true; // Allow the window border to be hidden by user bool bShowWindowBorder = true; // Allow the window title bar to be hidden by user diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index b6d3edbd..1f1b881e 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -47,13 +47,6 @@ namespace olc::host static const xdg_surface_listener surface_listener { .configure = Host_Linux_Wayland::xdg_surface_configure_callback }; - - static const xdg_toplevel_listener xdg_top_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_configure_callback, - .close = Host_Linux_Wayland::xdg_toplevel_close_callback, - .configure_bounds = Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback, - .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback - }; } namespace decor { @@ -164,9 +157,6 @@ namespace olc::host if(window) { wl_egl_window_destroy(window); } - if(toplevel) { - xdg_toplevel_destroy(toplevel); - } if(surface_xdg) { xdg_surface_destroy(surface_xdg); } @@ -462,69 +452,6 @@ namespace olc::host } } - void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { - const auto& itr = std::find_if(mapUID2Window.begin(), mapUID2Window.end(), [=](const auto& w){return w.second.toplevel == toplevel;}); - if(itr == mapUID2Window.end()) - { - return; - } - - auto& [uid, window] = *itr; - const auto& olc_window = mapUID2OlcWindow.at(uid); - - bool attempt_fullscreen {false}; - auto* state = reinterpret_cast(states->data); - auto* end = static_cast(states->data) + states->size; - for(;reinterpret_cast(state) < end; state++) - { - // The window.fullscreen is set any time the user commands via ShowFullscreen(), so we should allow this - if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) - { - attempt_fullscreen = true; - } - } - - // If we're not going fullscreen, try to obey the bounds that have been configured by the compositor - if(!attempt_fullscreen) { - if(window.bounds_x != 0) { - width = std::min(width, window.bounds_x); - } - - if(window.bounds_y != 0) { - height = std::min(height, window.bounds_y); - } - } - - olc_window->bWindowIsFullscreen = attempt_fullscreen; - olc_window->olc_OnWindowSize({width, height}); - wl_egl_window_resize(window.window, width, height, 0, 0); - wl_surface_commit(window.surface); - } - - void Host_Linux_Wayland::xdg_toplevel_close(xdg_toplevel* toplevel) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - auto itr = mapUID2OlcWindow.find(i.second.olc_window_uid); - if(itr != mapUID2OlcWindow.end()) { - auto* ptr = itr->second; - ptr->olc_OnWindowClose(); - } - } - } - } - - void Host_Linux_Wayland::xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - i.second.bounds_x = width; - i.second.bounds_y = height; - } - } - } - void Host_Linux_Wayland::registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { auto* host = reinterpret_cast(data); @@ -871,28 +798,6 @@ namespace olc::host xdg_surface_ack_configure(surface, serial); } - void Host_Linux_Wayland::xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { - auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure(toplevel, width, height, states); - } - - void Host_Linux_Wayland::xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel) - { - auto* host = reinterpret_cast(data); - host->xdg_toplevel_close(toplevel); - } - - void Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height) { - auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure_bounds(toplevel, width, height); - return; - } - - void Host_Linux_Wayland::xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities) - { - return; - } void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) { diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 1208c64b..c731e0b0 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -44,7 +44,6 @@ namespace olc::host struct WaylandWindow { wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; - xdg_toplevel* toplevel{nullptr}; wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -186,10 +185,6 @@ namespace olc::host // xdg callbacks static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); - static void xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); - static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); - static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); // libdecor callbacks static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); @@ -224,11 +219,6 @@ namespace olc::host void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); - // XDG toplevel callback functions - void xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - void xdg_toplevel_close(xdg_toplevel* toplevel); - void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); - // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index bef8d110..e3160a53 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4086,11 +4086,11 @@ namespace olc // These three are inherited from WindowConfig // Start in full-screen mode - bool bFullScreen = false; - // Allow full screen as an option with ALT-ENTER - bool bFullScreenable = true; - // Allow the window to be resized by user - bool bResizeable = true; + // bool bFullScreen = false; + // // Allow full screen as an option with ALT-ENTER + // bool bFullScreenable = true; + // // Allow the window to be resized by user + // bool bResizeable = true; // Allow the window border to be hidden by user bool bShowWindowBorder = true; // Allow the window title bar to be hidden by user @@ -5806,7 +5806,7 @@ namespace olc::host struct WaylandWindow { wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; - xdg_toplevel* toplevel{nullptr}; + //xdg_toplevel* toplevel{nullptr}; wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -5948,10 +5948,6 @@ namespace olc::host // xdg callbacks static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); - static void xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); - static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); - static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); // libdecor callbacks static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); @@ -5986,11 +5982,6 @@ namespace olc::host void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); - // XDG toplevel callback functions - void xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - void xdg_toplevel_close(xdg_toplevel* toplevel); - void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); - // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); @@ -11590,13 +11581,6 @@ namespace olc::host static const xdg_surface_listener surface_listener { .configure = Host_Linux_Wayland::xdg_surface_configure_callback }; - - static const xdg_toplevel_listener xdg_top_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_configure_callback, - .close = Host_Linux_Wayland::xdg_toplevel_close_callback, - .configure_bounds = Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback, - .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback - }; } namespace decor { @@ -11707,9 +11691,9 @@ namespace olc::host if(window) { wl_egl_window_destroy(window); } - if(toplevel) { - xdg_toplevel_destroy(toplevel); - } + // if(toplevel) { + // xdg_toplevel_destroy(toplevel); + // } if(surface_xdg) { xdg_surface_destroy(surface_xdg); } @@ -12005,69 +11989,6 @@ namespace olc::host } } - void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { - const auto& itr = std::find_if(mapUID2Window.begin(), mapUID2Window.end(), [=](const auto& w){return w.second.toplevel == toplevel;}); - if(itr == mapUID2Window.end()) - { - return; - } - - auto& [uid, window] = *itr; - const auto& olc_window = mapUID2OlcWindow.at(uid); - - bool attempt_fullscreen {false}; - auto* state = reinterpret_cast(states->data); - auto* end = static_cast(states->data) + states->size; - for(;reinterpret_cast(state) < end; state++) - { - // The window.fullscreen is set any time the user commands via ShowFullscreen(), so we should allow this - if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) - { - attempt_fullscreen = true; - } - } - - // If we're not going fullscreen, try to obey the bounds that have been configured by the compositor - if(!attempt_fullscreen) { - if(window.bounds_x != 0) { - width = std::min(width, window.bounds_x); - } - - if(window.bounds_y != 0) { - height = std::min(height, window.bounds_y); - } - } - - olc_window->bWindowIsFullscreen = attempt_fullscreen; - olc_window->olc_OnWindowSize({width, height}); - wl_egl_window_resize(window.window, width, height, 0, 0); - wl_surface_commit(window.surface); - } - - void Host_Linux_Wayland::xdg_toplevel_close(xdg_toplevel* toplevel) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - auto itr = mapUID2OlcWindow.find(i.second.olc_window_uid); - if(itr != mapUID2OlcWindow.end()) { - auto* ptr = itr->second; - ptr->olc_OnWindowClose(); - } - } - } - } - - void Host_Linux_Wayland::xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - i.second.bounds_x = width; - i.second.bounds_y = height; - } - } - } - void Host_Linux_Wayland::registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { auto* host = reinterpret_cast(data); @@ -12414,28 +12335,6 @@ namespace olc::host xdg_surface_ack_configure(surface, serial); } - void Host_Linux_Wayland::xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { - auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure(toplevel, width, height, states); - } - - void Host_Linux_Wayland::xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel) - { - auto* host = reinterpret_cast(data); - host->xdg_toplevel_close(toplevel); - } - - void Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height) { - auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure_bounds(toplevel, width, height); - return; - } - - void Host_Linux_Wayland::xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities) - { - return; - } void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) { From b5c2c6490e324e414d74c50910b768e1f741d210 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:05:01 -0500 Subject: [PATCH 08/28] Hopefully fix a rare crash when resizing --- dev/src/host_lin_wayland.cpp | 12 ++++-------- olcPixelGameEngine3.h | 16 ++++------------ 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 1f1b881e..d3f81c8f 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -842,7 +842,6 @@ namespace olc::host olc_window->bWindowIsFullscreen = (window->decor_window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); - wl_surface_commit(window->surface); } } } @@ -868,17 +867,14 @@ namespace olc::host void Host_Linux_Wayland::libdecor_commit_callback(libdecor_frame* frame, void* data) { - auto* host = reinterpret_cast(data); - host->libdecor_commit(frame); + // Don't want to actually do anything here because it messes with the EGL surface swapping for refresh + // and causes the application to close (not crash) due to a wayland protocol violation + //auto* host = reinterpret_cast(data); + //host->libdecor_commit(frame); } void Host_Linux_Wayland::libdecor_commit(libdecor_frame* frame) { - for(auto& i : mapUID2Window) { - if(i.second.decor_frame == frame) { - wl_surface_commit(i.second.surface); - } - } } void Host_Linux_Wayland::libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e3160a53..d2ed4af5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5806,7 +5806,6 @@ namespace olc::host struct WaylandWindow { wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; - //xdg_toplevel* toplevel{nullptr}; wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -11691,9 +11690,6 @@ namespace olc::host if(window) { wl_egl_window_destroy(window); } - // if(toplevel) { - // xdg_toplevel_destroy(toplevel); - // } if(surface_xdg) { xdg_surface_destroy(surface_xdg); } @@ -12379,7 +12375,6 @@ namespace olc::host olc_window->bWindowIsFullscreen = (window->decor_window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); - wl_surface_commit(window->surface); } } } @@ -12405,17 +12400,14 @@ namespace olc::host void Host_Linux_Wayland::libdecor_commit_callback(libdecor_frame* frame, void* data) { - auto* host = reinterpret_cast(data); - host->libdecor_commit(frame); + // Don't want to actually do anything here because it messes with the EGL surface swapping for refresh + // and causes the application to close (not crash) due to a wayland protocol violation + //auto* host = reinterpret_cast(data); + //host->libdecor_commit(frame); } void Host_Linux_Wayland::libdecor_commit(libdecor_frame* frame) { - for(auto& i : mapUID2Window) { - if(i.second.decor_frame == frame) { - wl_surface_commit(i.second.surface); - } - } } void Host_Linux_Wayland::libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data) From c33e87a4e3bb386e8e557f1703b78ba2d91d4d4e Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 31 May 2026 11:42:57 +0100 Subject: [PATCH 09/28] circles decrappified both fills and outlines. Abandoning wireframe mode and "free and easy" rendering to remove biases. Might explore if that benefits us elsewhere too. --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 9 +- dev/src/draw.cpp | 33 +++++-- dev/src/draw.h | 9 +- dev/src/gpu_opengl33.cpp | 48 ++++++++-- dev/src/gputask.h | 3 + examples/olcPGE3_CirclesEllipses.cpp | 14 +++ olcPixelGameEngine3.h | 93 +++++++++++++++---- 7 files changed, 166 insertions(+), 43 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 0b979b34..70a7e2b1 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -28,8 +28,8 @@ true true - true - true + false + false true @@ -43,7 +43,10 @@ true true - + + true + true + true true diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 388f00ea..53183f9b 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -260,7 +260,7 @@ void olc::Draw::Clear(const olc::Pixel& col) pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = olc::Structure::Line; @@ -272,6 +272,7 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std } task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } @@ -301,7 +302,7 @@ GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; @@ -310,10 +311,11 @@ GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; @@ -322,6 +324,7 @@ GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i] = col; } - return vecGPUTasks.data.emplace_back(std::move( + /*return vecGPUTasks.data.emplace_back(std::move( TaskDrawPolygon( olc::Structure::Line, buffPoints.data, buffColours.data, tint + )));*/ + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + buffPoints.data, + buffColours.data, + tint, + false // Don't constrain ))); } @@ -588,7 +604,7 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c for (int32_t i = 0; i <= nFacets; i++) { - buffPoints.data[i + 1] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i + 1] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i + 1] = colOuter; } @@ -597,7 +613,8 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c olc::Structure::Fan, buffPoints.data, buffColours.data, - tint + tint, + false // Don't constrain ))); } diff --git a/dev/src/draw.h b/dev/src/draw.h index b78995e6..7f034e82 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -908,7 +908,8 @@ namespace olc GPUTask TaskDrawLine( const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskDrawPolygon( olc::Structure structure, @@ -926,13 +927,15 @@ namespace olc olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskFillPolygon( olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskTexturedPolygon( olc::Structure structure, diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index da9c747f..2fed2f5b 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -89,6 +89,20 @@ void main() oTex = aTex; } + else if (pgeDrawType == 3) // Unconstrained 2D Line + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = aTex; + } + + else if (pgeDrawType == 4) // Unconstrained 2D Polygon + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = p * vec2(aTex.x, aTex.y); + } + else if (pgeDrawType == 0) // 2D Polygon { float p = 1.0 / aPos.z; @@ -1179,16 +1193,32 @@ void main() } else { - if (task.structure == olc::Structure::Point) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::Line) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineLoop) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineList) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + if (task.bPixelConstrained) + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + } else - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 4); + } } if (task.structure == olc::Structure::Fan) diff --git a/dev/src/gputask.h b/dev/src/gputask.h index d2549def..0803465a 100644 --- a/dev/src/gputask.h +++ b/dev/src/gputask.h @@ -102,6 +102,9 @@ namespace olc // Use hardware wire drawing bool bWireframe = false; + // Constrain with pixel biases + bool bPixelConstrained = true; + // Define how to interpret vertex buffer bool bIs3D = false; diff --git a/examples/olcPGE3_CirclesEllipses.cpp b/examples/olcPGE3_CirclesEllipses.cpp index 51a6fabf..dbd1e58f 100644 --- a/examples/olcPGE3_CirclesEllipses.cpp +++ b/examples/olcPGE3_CirclesEllipses.cpp @@ -28,6 +28,8 @@ class Example_RoundThings : public olc::PixelGameEngine std::vector vecBubbleVel; std::vector vecBubbleCol; + float fRadius = 4.0f; + public: // Called once at the start, so create things here bool OnUserCreate() override @@ -138,6 +140,18 @@ class Example_RoundThings : public olc::PixelGameEngine } + if (mouse.GetWheel() < 0) + fRadius -= 1.0f; + if (mouse.GetWheel() > 0) + fRadius += 1.0f; + + fRadius = std::clamp(fRadius, 0.5f, 120.0f); + + // Draw a small circle at mouse location + draw.FilledCircle(mouse.GetPosition().round(), fRadius, olc::Colour::RED); + draw.Circle(mouse.GetPosition().round(), fRadius, olc::Colour::WHITE); + + // Successful frame return true; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 48bd405a..23ec8b48 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2568,6 +2568,9 @@ namespace olc // Use hardware wire drawing bool bWireframe = false; + // Constrain with pixel biases + bool bPixelConstrained = true; + // Define how to interpret vertex buffer bool bIs3D = false; @@ -3474,7 +3477,8 @@ namespace olc GPUTask TaskDrawLine( const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskDrawPolygon( olc::Structure structure, @@ -3492,13 +3496,15 @@ namespace olc olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskFillPolygon( olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskTexturedPolygon( olc::Structure structure, @@ -14847,6 +14853,20 @@ void main() oTex = aTex; } + else if (pgeDrawType == 3) // Unconstrained 2D Line + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = aTex; + } + + else if (pgeDrawType == 4) // Unconstrained 2D Polygon + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = p * vec2(aTex.x, aTex.y); + } + else if (pgeDrawType == 0) // 2D Polygon { float p = 1.0 / aPos.z; @@ -15937,16 +15957,32 @@ void main() } else { - if (task.structure == olc::Structure::Point) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::Line) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineLoop) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineList) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + if (task.bPixelConstrained) + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + } else - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 4); + } } if (task.structure == olc::Structure::Fan) @@ -16356,7 +16392,7 @@ void olc::Draw::Clear(const olc::Pixel& col) pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = olc::Structure::Line; @@ -16368,6 +16404,7 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std } task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } @@ -16397,7 +16434,7 @@ GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; @@ -16406,10 +16443,11 @@ GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; @@ -16418,6 +16456,7 @@ GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i] = col; } - return vecGPUTasks.data.emplace_back(std::move( + /*return vecGPUTasks.data.emplace_back(std::move( TaskDrawPolygon( olc::Structure::Line, buffPoints.data, buffColours.data, tint + )));*/ + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + buffPoints.data, + buffColours.data, + tint, + false // Don't constrain ))); } @@ -16684,7 +16736,7 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c for (int32_t i = 0; i <= nFacets; i++) { - buffPoints.data[i + 1] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i + 1] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i + 1] = colOuter; } @@ -16693,7 +16745,8 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c olc::Structure::Fan, buffPoints.data, buffColours.data, - tint + tint, + false // Don't constrain ))); } From 094c923cd50f059b2d37d03fdba91e93f01ebd89 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 31 May 2026 13:38:30 +0100 Subject: [PATCH 10/28] changed 2D wireframes over to line asssemblies --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +-- dev/src/draw.cpp | 41 ++++++------ dev/src/draw.h | 10 ++- dev/src/gpu_opengl33.cpp | 10 +-- dev/src/gputask.h | 3 - examples/olcPGE3_Polygons.cpp | 7 +- olcPixelGameEngine3.h | 64 ++++++++++--------- 7 files changed, 78 insertions(+), 65 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 70a7e2b1..29dfe754 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -28,8 +28,8 @@ true true - false - false + true + true true @@ -134,8 +134,8 @@ true true - true - true + false + false true diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 53183f9b..4cf05a53 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -260,10 +260,10 @@ void olc::Draw::Clear(const olc::Pixel& col) pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = olc::Structure::Line; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; task.vertexBuffer.resize((vPoints.size()-1) * 2); for (size_t i = 0; i < vPoints.size() - 1; i++) { @@ -276,30 +276,31 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std return task; } -GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; + task.vertexBuffer.resize((vPoints.size() - 1) * 2); + for (size_t i = 0; i < vPoints.size() - 1; i++) + { + task.vertexBuffer[i * 2 + 0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.vertexBuffer[i * 2 + 1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + } task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; + +} + +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + return TaskDrawLine(vPoints, vColours, tint, false, true); } GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { - GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = {{vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - task.blendmode = blendMode; - task.tint = tint; - return task; + return TaskDrawLine(vPoints, colour, tint, false, true); } GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) @@ -360,7 +361,6 @@ GPUTask olc::Draw::TaskWireMesh(olc::Structure structure, const std::vector({ p1, p2 }), { c1, c2 }, tint - ))); + ))); } diff --git a/dev/src/draw.h b/dev/src/draw.h index 7f034e82..cf7af215 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -909,7 +909,15 @@ namespace olc const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint = olc::Colour::WHITE, - const bool constrain = true); + const bool constrain = true, + const bool looped = false); + + GPUTask TaskDrawLine( + const std::vector& vPoints, + const olc::Pixel colour, + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true, + const bool looped = false); GPUTask TaskDrawPolygon( olc::Structure structure, diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index accfd2cc..8c4e5a6d 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -1185,9 +1185,9 @@ void main() //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - // Apply Rendering Mode - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //// Apply Rendering Mode + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (task.bIs3D) { @@ -1240,8 +1240,8 @@ void main() gl.glDrawArrays(GL_POINTS, 0, (GLsizei)task.vertexBuffer.size()); - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (task.bDepth) gl.glDisable(GL_DEPTH_TEST); diff --git a/dev/src/gputask.h b/dev/src/gputask.h index 0803465a..12e094a9 100644 --- a/dev/src/gputask.h +++ b/dev/src/gputask.h @@ -99,9 +99,6 @@ namespace olc // Use depth components bool bDepth = false; - // Use hardware wire drawing - bool bWireframe = false; - // Constrain with pixel biases bool bPixelConstrained = true; diff --git a/examples/olcPGE3_Polygons.cpp b/examples/olcPGE3_Polygons.cpp index 7f291a08..300330b2 100644 --- a/examples/olcPGE3_Polygons.cpp +++ b/examples/olcPGE3_Polygons.cpp @@ -137,11 +137,16 @@ class Example_Pixels : public olc::PixelGameEngine std::vector polygon3_points(vecPolygonPoints.begin() + 16, vecPolygonPoints.end()); draw.FilledPolygon(olc::Structure::Strip, polygon3_points, vecPolygonColours); + // Draw polygon 3, a strip, filled, gradient + draw.Polygon(olc::Structure::Strip, polygon3_points, olc::Colour::WHITE); + + + // Draw Control Points for (int i = 0; i < vecPolygonPoints.size(); i++) { - draw.Circle(vecPolygonPoints[i], 4, olc::Colour::RED); + draw.Circle(vecPolygonPoints[i].floor(), 4, olc::Colour::RED); } // Successful frame diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 0b255f9b..4be4bf6f 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2565,9 +2565,6 @@ namespace olc // Use depth components bool bDepth = false; - // Use hardware wire drawing - bool bWireframe = false; - // Constrain with pixel biases bool bPixelConstrained = true; @@ -3478,7 +3475,15 @@ namespace olc const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint = olc::Colour::WHITE, - const bool constrain = true); + const bool constrain = true, + const bool looped = false); + + GPUTask TaskDrawLine( + const std::vector& vPoints, + const olc::Pixel colour, + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true, + const bool looped = false); GPUTask TaskDrawPolygon( olc::Structure structure, @@ -15727,9 +15732,9 @@ void main() //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - // Apply Rendering Mode - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //// Apply Rendering Mode + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (task.bIs3D) { @@ -15782,8 +15787,8 @@ void main() gl.glDrawArrays(GL_POINTS, 0, (GLsizei)task.vertexBuffer.size()); - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (task.bDepth) gl.glDisable(GL_DEPTH_TEST); @@ -16173,10 +16178,10 @@ void olc::Draw::Clear(const olc::Pixel& col) pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = olc::Structure::Line; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; task.vertexBuffer.resize((vPoints.size()-1) * 2); for (size_t i = 0; i < vPoints.size() - 1; i++) { @@ -16189,30 +16194,31 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std return task; } -GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; + task.vertexBuffer.resize((vPoints.size() - 1) * 2); + for (size_t i = 0; i < vPoints.size() - 1; i++) + { + task.vertexBuffer[i * 2 + 0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.vertexBuffer[i * 2 + 1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + } task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; + +} + +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + return TaskDrawLine(vPoints, vColours, tint, false, true); } GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { - GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = {{vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - task.blendmode = blendMode; - task.tint = tint; - return task; + return TaskDrawLine(vPoints, colour, tint, false, true); } GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) @@ -16273,7 +16279,6 @@ GPUTask olc::Draw::TaskWireMesh(olc::Structure structure, const std::vector({ p1, p2 }), { c1, c2 }, tint - ))); + ))); } From 7633469ab55b11a060e4d996936dbf83f4652aa4 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 8 Jun 2026 23:34:08 +0100 Subject: [PATCH 11/28] Moved to "Pointer API" to capture touch and pens. Its crude right now, loads of optimisation, and may need breaking into "touch" and "stylus" --- dev/msvc/olcPGE3.vcxproj | 1 + dev/msvc/olcPGE3.vcxproj.filters | 3 + .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 5 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/config.h | 2 + dev/src/core.cpp | 7 + dev/src/core.h | 1 + dev/src/host_win_winapi.cpp | 156 ++++++++ dev/src/host_win_winapi.h | 4 +- dev/src/hw_touch.cpp | 80 +++++ dev/src/hw_touch.h | 63 ++++ dev/src/sh_template.h | 7 + dev/src/window.cpp | 7 + dev/src/window.h | 6 + dev/tests/test_mh.cpp | 7 + examples/olcPGE3_Touch.cpp | 68 ++++ olcPixelGameEngine3.h | 339 +++++++++++++++++- 17 files changed, 742 insertions(+), 17 deletions(-) create mode 100644 dev/src/hw_touch.cpp create mode 100644 examples/olcPGE3_Touch.cpp diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index d7b7a70b..e739e722 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -316,6 +316,7 @@ + true diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 58f63f12..25fc3da8 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -248,5 +248,8 @@ Source Files + + Hardware + \ No newline at end of file diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 29dfe754..fe181624 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -134,8 +134,8 @@ true true - false - false + true + true true @@ -167,6 +167,7 @@ true true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 236e8825..1da31eaa 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -96,6 +96,9 @@ Source Files + + Source Files + diff --git a/dev/src/config.h b/dev/src/config.h index b974500b..60136e88 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -142,6 +142,8 @@ #define OLC_MOUSE_BUTTONS 5 +#define OLC_MAX_TOUCHPOINTS 4 + #define OLC_DEFAULT_KEYBOARD_LAYOUT olc::KeyboardLayout::QWERTY_UK #define OLC_GPU_MAX_VERTICES 8192 diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 612d3d68..e08d73e2 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -104,6 +104,7 @@ namespace olc // Input Changes mouse.UpdateState(); keyboard.UpdateState(); + touch.UpdateState(); draw.SetGPU(pRenderer); draw.SetTarget(GetScreen()); @@ -360,6 +361,12 @@ namespace olc return true; } + bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + { + touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), bPress, bRelease, vSize); + return true; + } + diff --git a/dev/src/core.h b/dev/src/core.h index 3c45a22b..7755a2af 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -128,6 +128,7 @@ namespace olc protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; + bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) override; public: virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index b95c0048..293d646e 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -313,6 +313,8 @@ namespace olc::host HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + RegisterTouchWindow(hWnd, 0); + // 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; @@ -622,6 +624,160 @@ namespace olc::host break; } + + /*case WM_TOUCH: + { + UINT nTouches = LOWORD(wParam); + PTOUCHINPUT pTouches = new TOUCHINPUT[nTouches]; + if (pTouches != nullptr) + { + if (GetTouchInputInfo((HTOUCHINPUT)lParam, nTouches, pTouches, sizeof(TOUCHINPUT))) + { + for (UINT i = 0; i < nTouches; i++) + { + TOUCHINPUT ti = pTouches[i]; + POINT pt; + pt.x = TOUCH_COORD_TO_PIXEL(ti.x); + pt.y = TOUCH_COORD_TO_PIXEL(ti.y); + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + ti.dwID, + olc::vf2d{ float(pt.x), float(pt.y) }, + (ti.dwFlags & TOUCHEVENTF_DOWN) != 0, + (ti.dwFlags & TOUCHEVENTF_UP) != 0, + olc::vf2d{ float(ti.cxContact), float(ti.cyContact) }); + } + } + delete[] pTouches; + } + + break; + }*/ + + case WM_POINTERDOWN: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + { + 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, + olc::vf2d{ float(penInfo.pressure), float(penInfo.pressure) }); + } + } + + } + + break; + } + + case WM_POINTERUP: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + { + 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, + olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + } + } + } + + break; + } + + case WM_POINTERUPDATE: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + { + 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, + olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + } + } + } + } + break; + } + + // case WM_DROPFILES: // { // // This is all eww... diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 469e309f..8a94d8c4 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -33,7 +33,7 @@ #ifdef HAVE_MSMF #define _WIN32_WINNT 0x0600 // Windows Vista #else - #define _WIN32_WINNT 0x0500 // Windows 2000 + #define _WIN32_WINNT 0x0603 // Windows 8.1 #endif #endif @@ -44,6 +44,8 @@ #include #include +#include +#include #undef _WINSOCKAPI_ //! END WINAPI_CONFIG diff --git a/dev/src/hw_touch.cpp b/dev/src/hw_touch.cpp new file mode 100644 index 00000000..70e018b6 --- /dev/null +++ b/dev/src/hw_touch.cpp @@ -0,0 +1,80 @@ +#include "hw_touch.h" + +//! START IMPLEMENTATION +namespace olc::hw +{ + const std::vector& Touch::GetTouchIDs() const + { + static std::vector touchIDs; + touchIDs.clear(); + for (const auto& [id, index] : touches_cache) + touchIDs.push_back(id); + return touchIDs; + } + + const bool Touch::IsTouch(const uint32_t nID) const + { + return touches_cache.contains(nID); + } + + const TouchPoint& Touch::GetTouch(const uint32_t nID) const + { + return touches_cache.at(nID); + } + + void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + { + // // Does touch already exist? + //if (touches_live.contains(nID)) + //{ + // It does, so update existing touch + + touches_live[nID].position = vPos; + touches_live[nID].size = vSize; + touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; + touches_live[nID].bReleased = bRelease; + touches_live[nID].bHeld = !bRelease; + //} + // else + // { + // // Touch doesnt exist + // if (bPress) + // { + // // Add new touch + // touches_live[nID] = { vPos, vSize }; + // } + //} + } + + void Touch::UpdateState() + { + // // Copy over the "live" touch state. Any that are missing from this, but exist in the + // // cached state, will remain cached for one more update, but with the released flag set + + //for (const auto& [id, touch] : touches_cache) + //{ + // if (!touches_live.contains(id) && !touch.bReleased) + // { + // // Touch was released, but we want to keep it around for one more update so we can report the release event + // touches_live[id] = touch; + // touches_live[id].bReleased = true; + // touches_live[id].bHeld = false; + // } + //} + + touches_cache = touches_live; + + // Remove released touches from live state + for (auto it = touches_live.begin(); it != touches_live.end(); ) + { + if (it->second.bPressed) + it->second.bPressed = false; + + if (it->second.bReleased) + it = touches_live.erase(it); + else + ++it; + } + } +} +//! END IMPLEMENTATION diff --git a/dev/src/hw_touch.h b/dev/src/hw_touch.h index 6f70f09b..6f74a047 100644 --- a/dev/src/hw_touch.h +++ b/dev/src/hw_touch.h @@ -1 +1,64 @@ #pragma once + +//! START STDHEADER +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "config.h" +#include "hw_input.h" +#include "vector2d.h" +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_HW_TOUCH_DECLARED) +namespace olc +{ + // Forward declare for friendship + class Window; + class PGEWindow; + + + namespace hw + { + struct TouchPoint + { + olc::vf2d position; + olc::vf2d size; + float pressure = 0; + float orientation = 0; + olc::vf2d tilt; + bool bPressed = false; + bool bReleased = false; + bool bHeld = false; + bool bIsPen = false; + bool bIsFinger = false; + }; + + class Touch + { + friend class olc::Window; + friend class olc::PGEWindow; + + public: + Touch() = default; + + public: + const std::vector& GetTouchIDs() const; + const bool IsTouch(const uint32_t nID) const; + const TouchPoint& GetTouch(const uint32_t nID) const; + + protected: + std::unordered_map touches_cache{}; + std::unordered_map touches_live{}; + + + private: + void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + void UpdateState(); + }; + } +} +#define PGE_HW_TOUCH_DECLARED 1 +#endif diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 99c88e02..d25012f2 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -143,6 +143,8 @@ //! GRAB hw_keyboard.h DECLARATION +//! GRAB hw_touch.h DECLARATION + //! GRAB window.h DECLARATION //! GRAB host_iface.h DECLARATION @@ -321,6 +323,11 @@ #define PGE_HW_KEYBOARD_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_TOUCH_IMPLEMENTED) +//! GRAB hw_touch.cpp IMPLEMENTATION +#define PGE_HW_TOUCH_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) //! GRAB window.cpp IMPLEMENTATION #define PGE_WINDOW_IMPLEMENTED 1 diff --git a/dev/src/window.cpp b/dev/src/window.cpp index 466f7eca..6bfb1bb3 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -38,6 +38,7 @@ namespace olc bool Window::olc_OnMouseMove(const olc::vi2d& vMousePos) { + // Handled by PGEWindow mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize())); return true; } @@ -77,6 +78,12 @@ namespace olc return true; } + bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + { + // Handled by PGEWindow + return true; + } + bool Window::olc_ShouldRemove() const { return bShouldRemove; diff --git a/dev/src/window.h b/dev/src/window.h index dbc20c37..3cb892ab 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -16,6 +16,7 @@ #include "draw.h" #include "hw_mouse.h" #include "hw_keyboard.h" +#include "hw_touch.h" //! END CUSTOMHEADER //! START DECLARATION @@ -42,6 +43,7 @@ namespace olc { class Mouse; class Keyboard; + class Touch; } struct WindowConfig @@ -84,6 +86,9 @@ namespace olc // Set Keyboard State virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); + // Touch Handler + virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + public: @@ -133,6 +138,7 @@ namespace olc protected: olc::hw::Mouse mouse; olc::hw::Keyboard keyboard; + olc::hw::Touch touch; }; } diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 4fefc1f3..058008d6 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -474,6 +474,13 @@ class Example : public olc::PixelGameEngine draw.String({ 10, 10 }, mouse.GetPosition().str(), olc::Colour::BLACK); + auto touches = touch.GetTouchIDs(); + draw.String({ 10.0f, 20.0f }, "Touch Points: " + std::to_string(touches.size()), olc::Colour::WHITE); + + + if(!touches.empty()) + draw.FilledCircle(touch.GetTouch(touches[0]).position, 10, olc::Colour::MAGENTA, olc::Pixel(255, 0, 255, 0), olc::Colour::WHITE, 16); + if (keyboard.GetKey(olc::Key::ESCAPE).bPressed) return false; // End application diff --git a/examples/olcPGE3_Touch.cpp b/examples/olcPGE3_Touch.cpp new file mode 100644 index 00000000..aa31b6ee --- /dev/null +++ b/examples/olcPGE3_Touch.cpp @@ -0,0 +1,68 @@ +/* +olc::PixelGameEngine3 Example - Touch + +Demonstrates touch input handling + +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" + +// Example application demonstrating touch input handling. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_Touch : public olc::PixelGameEngine +{ +public: + Example_Touch() + { + sAppName = "Example - Touch"; + } + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Nothing to do here, so return true + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + auto touches = touch.GetTouchIDs(); + + + draw.String({ 2.0f, 2.0f }, "Touch Points: " + std::to_string(touches.size()), olc::Colour::WHITE); + + + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Touch demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4be4bf6f..99eab97f 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -280,6 +280,8 @@ #define OLC_MOUSE_BUTTONS 5 +#define OLC_MAX_TOUCHPOINTS 4 + #define OLC_DEFAULT_KEYBOARD_LAYOUT olc::KeyboardLayout::QWERTY_UK #define OLC_GPU_MAX_VERTICES 8192 @@ -3805,6 +3807,57 @@ namespace olc #define PGE_HW_KEYBOARD_DECLARED 1 #endif +#if !defined(PGE_HW_TOUCH_DECLARED) +namespace olc +{ + // Forward declare for friendship + class Window; + class PGEWindow; + + + namespace hw + { + struct TouchPoint + { + olc::vf2d position; + olc::vf2d size; + float pressure = 0; + float orientation = 0; + olc::vf2d tilt; + bool bPressed = false; + bool bReleased = false; + bool bHeld = false; + bool bIsPen = false; + bool bIsFinger = false; + }; + + class Touch + { + friend class olc::Window; + friend class olc::PGEWindow; + + public: + Touch() = default; + + public: + const std::vector& GetTouchIDs() const; + const bool IsTouch(const uint32_t nID) const; + const TouchPoint& GetTouch(const uint32_t nID) const; + + protected: + std::unordered_map touches_cache{}; + std::unordered_map touches_live{}; + + + private: + void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + void UpdateState(); + }; + } +} +#define PGE_HW_TOUCH_DECLARED 1 +#endif + #if !defined(PGE_WINDOW_DECLARED) namespace olc { @@ -3828,6 +3881,7 @@ namespace olc { class Mouse; class Keyboard; + class Touch; } struct WindowConfig @@ -3870,6 +3924,9 @@ namespace olc // Set Keyboard State virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); + // Touch Handler + virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + public: @@ -3919,6 +3976,7 @@ namespace olc protected: olc::hw::Mouse mouse; olc::hw::Keyboard keyboard; + olc::hw::Touch touch; }; } @@ -4183,6 +4241,7 @@ namespace olc protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; + bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) override; public: virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); @@ -4364,7 +4423,7 @@ namespace olc::host #ifdef HAVE_MSMF #define _WIN32_WINNT 0x0600 // Windows Vista #else - #define _WIN32_WINNT 0x0500 // Windows 2000 + #define _WIN32_WINNT 0x0603 // Windows 8.1 #endif #endif @@ -4375,6 +4434,8 @@ namespace olc::host #include #include +#include +#include #undef _WINSOCKAPI_ namespace olc @@ -7519,6 +7580,8 @@ namespace olc::host HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + RegisterTouchWindow(hWnd, 0); + // 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; @@ -7683,17 +7746,17 @@ namespace olc::host 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; - } + //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; + // } // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; @@ -7770,7 +7833,7 @@ namespace olc::host break; } - case WM_LBUTTONDOWN: + /* case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); break; @@ -7799,7 +7862,7 @@ namespace olc::host { window->olc_OnMouseButton(2, false); break; - } + }*/ case WM_XBUTTONDOWN: { UINT button = GET_XBUTTON_WPARAM(wParam); @@ -7828,6 +7891,160 @@ namespace olc::host break; } + + /*case WM_TOUCH: + { + UINT nTouches = LOWORD(wParam); + PTOUCHINPUT pTouches = new TOUCHINPUT[nTouches]; + if (pTouches != nullptr) + { + if (GetTouchInputInfo((HTOUCHINPUT)lParam, nTouches, pTouches, sizeof(TOUCHINPUT))) + { + for (UINT i = 0; i < nTouches; i++) + { + TOUCHINPUT ti = pTouches[i]; + POINT pt; + pt.x = TOUCH_COORD_TO_PIXEL(ti.x); + pt.y = TOUCH_COORD_TO_PIXEL(ti.y); + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + ti.dwID, + olc::vf2d{ float(pt.x), float(pt.y) }, + (ti.dwFlags & TOUCHEVENTF_DOWN) != 0, + (ti.dwFlags & TOUCHEVENTF_UP) != 0, + olc::vf2d{ float(ti.cxContact), float(ti.cyContact) }); + } + } + delete[] pTouches; + } + + break; + }*/ + + case WM_POINTERDOWN: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + { + 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, + olc::vf2d{ float(penInfo.pressure), float(penInfo.pressure) }); + } + } + + } + + break; + } + + case WM_POINTERUP: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + { + 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, + olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + } + } + } + + break; + } + + case WM_POINTERUPDATE: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + { + 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, + olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + } + } + } + } + break; + } + + // case WM_DROPFILES: // { // // This is all eww... @@ -17789,6 +18006,7 @@ namespace olc // Input Changes mouse.UpdateState(); keyboard.UpdateState(); + touch.UpdateState(); draw.SetGPU(pRenderer); draw.SetTarget(GetScreen()); @@ -18045,6 +18263,12 @@ namespace olc return true; } + bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + { + touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), bPress, bRelease, vSize); + return true; + } + @@ -18902,6 +19126,86 @@ namespace olc::hw #define PGE_HW_KEYBOARD_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_TOUCH_IMPLEMENTED) +namespace olc::hw +{ + const std::vector& Touch::GetTouchIDs() const + { + static std::vector touchIDs; + touchIDs.clear(); + for (const auto& [id, index] : touches_cache) + touchIDs.push_back(id); + return touchIDs; + } + + const bool Touch::IsTouch(const uint32_t nID) const + { + return touches_cache.contains(nID); + } + + const TouchPoint& Touch::GetTouch(const uint32_t nID) const + { + return touches_cache.at(nID); + } + + void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + { + // // Does touch already exist? + //if (touches_live.contains(nID)) + //{ + // It does, so update existing touch + + touches_live[nID].position = vPos; + touches_live[nID].size = vSize; + touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; + touches_live[nID].bReleased = bRelease; + touches_live[nID].bHeld = !bRelease; + //} + // else + // { + // // Touch doesnt exist + // if (bPress) + // { + // // Add new touch + // touches_live[nID] = { vPos, vSize }; + // } + //} + } + + void Touch::UpdateState() + { + // // Copy over the "live" touch state. Any that are missing from this, but exist in the + // // cached state, will remain cached for one more update, but with the released flag set + + //for (const auto& [id, touch] : touches_cache) + //{ + // if (!touches_live.contains(id) && !touch.bReleased) + // { + // // Touch was released, but we want to keep it around for one more update so we can report the release event + // touches_live[id] = touch; + // touches_live[id].bReleased = true; + // touches_live[id].bHeld = false; + // } + //} + + touches_cache = touches_live; + + // Remove released touches from live state + for (auto it = touches_live.begin(); it != touches_live.end(); ) + { + if (it->second.bPressed) + it->second.bPressed = false; + + if (it->second.bReleased) + it = touches_live.erase(it); + else + ++it; + } + } +} +#define PGE_HW_TOUCH_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) namespace olc { @@ -18936,6 +19240,7 @@ namespace olc bool Window::olc_OnMouseMove(const olc::vi2d& vMousePos) { + // Handled by PGEWindow mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize())); return true; } @@ -18975,6 +19280,12 @@ namespace olc return true; } + bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + { + // Handled by PGEWindow + return true; + } + bool Window::olc_ShouldRemove() const { return bShouldRemove; From f35d4dc305291a9534ac93ac0183120f7764ec1b Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:42:31 +0100 Subject: [PATCH 12/28] Added "Touch" example --- dev/src/core.cpp | 5 +- dev/src/core.h | 2 +- dev/src/host_win_winapi.cpp | 58 +++++--------- dev/src/hw_touch.cpp | 47 +++--------- dev/src/hw_touch.h | 6 +- dev/src/window.cpp | 2 +- dev/src/window.h | 3 +- examples/olcPGE3_Touch.cpp | 34 ++++++-- olcPixelGameEngine3.h | 149 ++++++++++++++---------------------- 9 files changed, 128 insertions(+), 178 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index e08d73e2..eebce74f 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -361,9 +361,10 @@ namespace olc return true; } - bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { - touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), bPress, bRelease, vSize); + touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), + bPress, bRelease, (vSize / olc::vf2d(vWindowSize)) * GetScreen().Size(), stylus, pressure, orientation, tilt); return true; } diff --git a/dev/src/core.h b/dev/src/core.h index 7755a2af..809a37de 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -128,7 +128,7 @@ namespace olc protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; - bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) override; + bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }) override; public: virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 293d646e..3b84336f 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -313,8 +313,6 @@ namespace olc::host HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); - RegisterTouchWindow(hWnd, 0); - // 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; @@ -625,41 +623,12 @@ namespace olc::host break; } - /*case WM_TOUCH: - { - UINT nTouches = LOWORD(wParam); - PTOUCHINPUT pTouches = new TOUCHINPUT[nTouches]; - if (pTouches != nullptr) - { - if (GetTouchInputInfo((HTOUCHINPUT)lParam, nTouches, pTouches, sizeof(TOUCHINPUT))) - { - for (UINT i = 0; i < nTouches; i++) - { - TOUCHINPUT ti = pTouches[i]; - POINT pt; - pt.x = TOUCH_COORD_TO_PIXEL(ti.x); - pt.y = TOUCH_COORD_TO_PIXEL(ti.y); - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - ti.dwID, - olc::vf2d{ float(pt.x), float(pt.y) }, - (ti.dwFlags & TOUCHEVENTF_DOWN) != 0, - (ti.dwFlags & TOUCHEVENTF_UP) != 0, - olc::vf2d{ float(ti.cxContact), float(ti.cyContact) }); - } - } - delete[] pTouches; - } - - break; - }*/ - case WM_POINTERDOWN: { POINTER_INPUT_TYPE pointerType; if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) { - if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + if (pointerType == PT_TOUCH) { POINTER_TOUCH_INFO touchInfo; if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) @@ -686,7 +655,12 @@ namespace olc::host olc::vf2d{ float(pt.x), float(pt.y) }, true, false, - olc::vf2d{ float(penInfo.pressure), float(penInfo.pressure) }); + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); } } @@ -700,7 +674,7 @@ namespace olc::host POINTER_INPUT_TYPE pointerType; if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) { - if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + if (pointerType == PT_TOUCH) { POINTER_TOUCH_INFO touchInfo; if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) @@ -727,7 +701,12 @@ namespace olc::host olc::vf2d{ float(pt.x), float(pt.y) }, false, true, - olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); } } } @@ -740,7 +719,7 @@ namespace olc::host POINTER_INPUT_TYPE pointerType; if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) { - if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + if (pointerType == PT_TOUCH) { POINTER_TOUCH_INFO touchInfo; if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) @@ -769,7 +748,12 @@ namespace olc::host olc::vf2d{ float(pt.x), float(pt.y) }, false, false, - olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); } } } diff --git a/dev/src/hw_touch.cpp b/dev/src/hw_touch.cpp index 70e018b6..8da7cd20 100644 --- a/dev/src/hw_touch.cpp +++ b/dev/src/hw_touch.cpp @@ -22,46 +22,23 @@ namespace olc::hw return touches_cache.at(nID); } - void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { - // // Does touch already exist? - //if (touches_live.contains(nID)) - //{ - // It does, so update existing touch - - touches_live[nID].position = vPos; - touches_live[nID].size = vSize; - touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; - touches_live[nID].bReleased = bRelease; - touches_live[nID].bHeld = !bRelease; - //} - // else - // { - // // Touch doesnt exist - // if (bPress) - // { - // // Add new touch - // touches_live[nID] = { vPos, vSize }; - // } - //} + // If touch doesnt exist, this will create one or + // it will update an existing one + touches_live[nID].position = vPos; + touches_live[nID].size = vSize; + touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; + touches_live[nID].bReleased = bRelease; + touches_live[nID].bHeld = !bRelease; + touches_live[nID].pressure = pressure; + touches_live[nID].orientation = orientation; + touches_live[nID].tilt = tilt; + touches_live[nID].bStylus = stylus; } void Touch::UpdateState() { - // // Copy over the "live" touch state. Any that are missing from this, but exist in the - // // cached state, will remain cached for one more update, but with the released flag set - - //for (const auto& [id, touch] : touches_cache) - //{ - // if (!touches_live.contains(id) && !touch.bReleased) - // { - // // Touch was released, but we want to keep it around for one more update so we can report the release event - // touches_live[id] = touch; - // touches_live[id].bReleased = true; - // touches_live[id].bHeld = false; - // } - //} - touches_cache = touches_live; // Remove released touches from live state diff --git a/dev/src/hw_touch.h b/dev/src/hw_touch.h index 6f74a047..ed2076ae 100644 --- a/dev/src/hw_touch.h +++ b/dev/src/hw_touch.h @@ -32,8 +32,7 @@ namespace olc bool bPressed = false; bool bReleased = false; bool bHeld = false; - bool bIsPen = false; - bool bIsFinger = false; + bool bStylus = false; }; class Touch @@ -55,7 +54,8 @@ namespace olc private: - void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); void UpdateState(); }; } diff --git a/dev/src/window.cpp b/dev/src/window.cpp index 6bfb1bb3..9b70a3de 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -78,7 +78,7 @@ namespace olc return true; } - bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { // Handled by PGEWindow return true; diff --git a/dev/src/window.h b/dev/src/window.h index 3cb892ab..46adb50f 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -87,7 +87,8 @@ namespace olc virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); // Touch Handler - virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); diff --git a/examples/olcPGE3_Touch.cpp b/examples/olcPGE3_Touch.cpp index aa31b6ee..c611e66e 100644 --- a/examples/olcPGE3_Touch.cpp +++ b/examples/olcPGE3_Touch.cpp @@ -1,9 +1,9 @@ /* -olc::PixelGameEngine3 Example - Touch + olc::PixelGameEngine3 Example - Touch -Demonstrates touch input handling + Demonstrates touch input handling -Licenced under the OLC-3 License + Licenced under the OLC-3 License */ @@ -37,13 +37,37 @@ class Example_Touch : public olc::PixelGameEngine // Clear whole screen draw.Clear(olc::Colour::VERY_DARK_BLUE); + // All "touches" get given a unique ID and are tracked by the OS. This ID + // could be anything, so we never really want to work with t directly. auto touches = touch.GetTouchIDs(); - + // Display total number of touches draw.String({ 2.0f, 2.0f }, "Touch Points: " + std::to_string(touches.size()), olc::Colour::WHITE); - + // Draw Touches + for (auto id : touches) + { + // Get an olc::TouchPoint for this touch + auto& t = touch.GetTouch(id); + + if (t.bStylus) + { + // Touch comes from a stylus, so draw a small circle at the tip position, with size based on the touch size + draw.Circle(t.position, t.pressure * 10.0f, olc::Colour::WHITE); + + // Stylus *MAY* also have an orientation, so draw a line from the tip in the direction of the stylus + draw.Line(t.position, t.position + olc::vf2d{ std::cos(t.orientation), std::sin(t.orientation) } * 10.0f * t.pressure, olc::Colour::WHITE); + } + else + { + // Touch comes from a finger, so draw a circle at the touch position, with size based on the touch size + draw.FilledRect(t.position, t.size, olc::Colour::YELLOW); + // Draw a smaller circle at the touch position, with size based on the touch pressure + draw.FilledRect(t.position, t.size, olc::Colour::YELLOW); + } + } + // Successful frame return true; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 99eab97f..9b4f3c3c 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3827,8 +3827,7 @@ namespace olc bool bPressed = false; bool bReleased = false; bool bHeld = false; - bool bIsPen = false; - bool bIsFinger = false; + bool bStylus = false; }; class Touch @@ -3850,7 +3849,8 @@ namespace olc private: - void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); void UpdateState(); }; } @@ -3925,7 +3925,8 @@ namespace olc virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); // Touch Handler - virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize); + virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); @@ -4241,7 +4242,7 @@ namespace olc protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; - bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) override; + bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }) override; public: virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); @@ -7580,8 +7581,6 @@ namespace olc::host HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); - RegisterTouchWindow(hWnd, 0); - // 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; @@ -7746,17 +7745,17 @@ namespace olc::host 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; - // } + 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; + } // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; @@ -7833,7 +7832,7 @@ namespace olc::host break; } - /* case WM_LBUTTONDOWN: + case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); break; @@ -7862,7 +7861,7 @@ namespace olc::host { window->olc_OnMouseButton(2, false); break; - }*/ + } case WM_XBUTTONDOWN: { UINT button = GET_XBUTTON_WPARAM(wParam); @@ -7892,41 +7891,12 @@ namespace olc::host break; } - /*case WM_TOUCH: - { - UINT nTouches = LOWORD(wParam); - PTOUCHINPUT pTouches = new TOUCHINPUT[nTouches]; - if (pTouches != nullptr) - { - if (GetTouchInputInfo((HTOUCHINPUT)lParam, nTouches, pTouches, sizeof(TOUCHINPUT))) - { - for (UINT i = 0; i < nTouches; i++) - { - TOUCHINPUT ti = pTouches[i]; - POINT pt; - pt.x = TOUCH_COORD_TO_PIXEL(ti.x); - pt.y = TOUCH_COORD_TO_PIXEL(ti.y); - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - ti.dwID, - olc::vf2d{ float(pt.x), float(pt.y) }, - (ti.dwFlags & TOUCHEVENTF_DOWN) != 0, - (ti.dwFlags & TOUCHEVENTF_UP) != 0, - olc::vf2d{ float(ti.cxContact), float(ti.cyContact) }); - } - } - delete[] pTouches; - } - - break; - }*/ - case WM_POINTERDOWN: { POINTER_INPUT_TYPE pointerType; if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) { - if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + if (pointerType == PT_TOUCH) { POINTER_TOUCH_INFO touchInfo; if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) @@ -7953,7 +7923,12 @@ namespace olc::host olc::vf2d{ float(pt.x), float(pt.y) }, true, false, - olc::vf2d{ float(penInfo.pressure), float(penInfo.pressure) }); + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); } } @@ -7967,7 +7942,7 @@ namespace olc::host POINTER_INPUT_TYPE pointerType; if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) { - if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + if (pointerType == PT_TOUCH) { POINTER_TOUCH_INFO touchInfo; if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) @@ -7994,7 +7969,12 @@ namespace olc::host olc::vf2d{ float(pt.x), float(pt.y) }, false, true, - olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); } } } @@ -8007,7 +7987,7 @@ namespace olc::host POINTER_INPUT_TYPE pointerType; if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) { - if (pointerType == PT_TOUCH || pointerType == PT_TOUCHPAD) + if (pointerType == PT_TOUCH) { POINTER_TOUCH_INFO touchInfo; if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) @@ -8036,7 +8016,12 @@ namespace olc::host olc::vf2d{ float(pt.x), float(pt.y) }, false, false, - olc::vf2d{ float(penInfo.pressure), float(penInfo.rotation) }); + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); } } } @@ -18263,9 +18248,10 @@ namespace olc return true; } - bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { - touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), bPress, bRelease, vSize); + touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), + bPress, bRelease, (vSize / olc::vf2d(vWindowSize)) * GetScreen().Size(), stylus, pressure, orientation, tilt); return true; } @@ -19148,46 +19134,23 @@ namespace olc::hw return touches_cache.at(nID); } - void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { - // // Does touch already exist? - //if (touches_live.contains(nID)) - //{ - // It does, so update existing touch - - touches_live[nID].position = vPos; - touches_live[nID].size = vSize; - touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; - touches_live[nID].bReleased = bRelease; - touches_live[nID].bHeld = !bRelease; - //} - // else - // { - // // Touch doesnt exist - // if (bPress) - // { - // // Add new touch - // touches_live[nID] = { vPos, vSize }; - // } - //} + // If touch doesnt exist, this will create one or + // it will update an existing one + touches_live[nID].position = vPos; + touches_live[nID].size = vSize; + touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; + touches_live[nID].bReleased = bRelease; + touches_live[nID].bHeld = !bRelease; + touches_live[nID].pressure = pressure; + touches_live[nID].orientation = orientation; + touches_live[nID].tilt = tilt; + touches_live[nID].bStylus = stylus; } void Touch::UpdateState() { - // // Copy over the "live" touch state. Any that are missing from this, but exist in the - // // cached state, will remain cached for one more update, but with the released flag set - - //for (const auto& [id, touch] : touches_cache) - //{ - // if (!touches_live.contains(id) && !touch.bReleased) - // { - // // Touch was released, but we want to keep it around for one more update so we can report the release event - // touches_live[id] = touch; - // touches_live[id].bReleased = true; - // touches_live[id].bHeld = false; - // } - //} - touches_cache = touches_live; // Remove released touches from live state @@ -19280,7 +19243,7 @@ namespace olc return true; } - bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize) + bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { // Handled by PGEWindow return true; From 04ad9ac0147f297185293c9b49b9c04ce6d48955 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:53:16 +0100 Subject: [PATCH 13/28] as title --- dev/src/core.cpp | 10 ++++++++-- olcPixelGameEngine3.h | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index eebce74f..092d8bdb 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -363,8 +363,14 @@ namespace olc bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { - touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), - bPress, bRelease, (vSize / olc::vf2d(vWindowSize)) * GetScreen().Size(), stylus, pressure, orientation, tilt); + olc::vf2d pos = vPos; + pos.x -= float(vViewPos.x); + pos.y -= float(vViewPos.y); + + olc::vf2d vScale = (1.0f / olc::vf2d(vWindowSize - (vViewPos * 2))) * GetScreen().Size(); + + touch.UpdateTouch(nID, pos * vScale, + bPress, bRelease, vSize * vScale, stylus, pressure, orientation, tilt); return true; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 9b4f3c3c..4bc572ef 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -18250,8 +18250,14 @@ namespace olc bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) { - touch.UpdateTouch(nID, (vPos / olc::vf2d(vWindowSize)) * GetScreen().Size(), - bPress, bRelease, (vSize / olc::vf2d(vWindowSize)) * GetScreen().Size(), stylus, pressure, orientation, tilt); + olc::vf2d pos = vPos; + pos.x -= float(vViewPos.x); + pos.y -= float(vViewPos.y); + + olc::vf2d vScale = (1.0f / olc::vf2d(vWindowSize - (vViewPos * 2))) * GetScreen().Size(); + + touch.UpdateTouch(nID, pos * vScale, + bPress, bRelease, vSize * vScale, stylus, pressure, orientation, tilt); return true; } From d8f5096005e5f22074b603ef4841c1b9f279f562 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:24:18 -0500 Subject: [PATCH 14/28] Implement touch --- dev/src/host_lin_wayland.cpp | 179 ++++++++++++++++++++++++++++ dev/src/host_lin_wayland.h | 43 +++++++ olcPixelGameEngine3.h | 222 +++++++++++++++++++++++++++++++++++ 3 files changed, 444 insertions(+) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index d3f81c8f..4c016d66 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -37,6 +37,16 @@ namespace olc::host .modifiers = Host_Linux_Wayland::keyboard_modifiers_callback, .repeat_info = Host_Linux_Wayland::keyboard_repeat_info_callback }; + + static const wl_touch_listener touch_listener { + .down = Host_Linux_Wayland::touch_down_callback, + .up = Host_Linux_Wayland::touch_up_callback, + .motion = Host_Linux_Wayland::touch_motion_callback, + .frame = Host_Linux_Wayland::touch_frame_callback, + .cancel = Host_Linux_Wayland::touch_cancel_callback, + .shape = Host_Linux_Wayland::touch_shape_callback, + .orientation = Host_Linux_Wayland::touch_orientation_callback + }; } namespace xdg { @@ -187,6 +197,7 @@ namespace olc::host { wp_pointer_warp_v1_destroy(pointer_warp); } + wl_touch_destroy(touch); wl_keyboard_destroy(keyboard); wl_pointer_destroy(pointer); wl_seat_destroy(seat); @@ -450,6 +461,11 @@ namespace olc::host keyboard_version = wl_keyboard_get_version(keyboard); wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); } + + if (capabilities & WL_SEAT_CAPABILITY_TOUCH && touch == nullptr) { + touch = wl_seat_get_touch(seat); + wl_touch_add_listener(touch, &wayland::touch_listener, this); + } } void Host_Linux_Wayland::registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) @@ -788,6 +804,169 @@ namespace olc::host // host->keyboard_repeat_info(keyboard, rate, delay); } + // Touch Callbacks + void Host_Linux_Wayland::touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) + { + auto* host = reinterpret_cast(data); + host->touch_down(touch, serial, time, surface, id, x, y); + } + + void Host_Linux_Wayland::touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventDown; + touch_state.surface = surface; + touch_state.id = id; + touch_state.surface_x = x; + touch_state.surface_y = y; + touch_state.serial = serial; + touch_state.time = time; + } + + void Host_Linux_Wayland::touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { + auto* host = reinterpret_cast(data); + host->touch_up(touch, serial, time, id); + } + + void Host_Linux_Wayland::touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventUp; + touch_state.serial = serial; + touch_state.time = time; + } + + void Host_Linux_Wayland::touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto* host = reinterpret_cast(data); + host->touch_motion(touch, time, id, x, y); + } + + void Host_Linux_Wayland::touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventMotion; + touch_state.time = time; + touch_state.surface_x = x; + touch_state.surface_y = y; + } + + void Host_Linux_Wayland::touch_frame_callback(void* data, wl_touch* touch) { + auto* host = reinterpret_cast(data); + host->touch_frame(touch); + } + + void Host_Linux_Wayland::touch_frame(wl_touch* touch) { + // Commit all of the touches + for(auto itr = touches.begin(); itr != touches.end();) { + const auto& id = itr->first; + const auto& touch = itr->second; + + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + touch.event_mask & wayland::TouchEventMask::TouchEventUp, + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + + if(touch.event_mask & wayland::TouchEventMask::TouchEventUp) { + itr = touches.erase(itr); + } else { + ++itr; + } + } + } + + void Host_Linux_Wayland::touch_cancel_callback(void* data, wl_touch* touch) { + auto* host = reinterpret_cast(data); + host->touch_cancel(touch); + } + + void Host_Linux_Wayland::touch_cancel(wl_touch* touch) { + // according to the protocol, this ends all touch events, so send an Up and clear the whole thing + for(const auto& [id, touch] : touches) { + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + true, // Forced Up event to clear all these IDs on the PGE side + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + } + + touches.clear(); + } + + void Host_Linux_Wayland::touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto* host = reinterpret_cast(data); + host->touch_shape(touch, id, major, minor); + } + + void Host_Linux_Wayland::touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventShape; + touch_state.major = major; + touch_state.minor = minor; + } + + void Host_Linux_Wayland::touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto* host = reinterpret_cast(data); + host->touch_orientation(touch, id, orientation); + } + + void Host_Linux_Wayland::touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventOrientation; + touch_state.orientation = orientation; + } + + // XDG Callbacks void Host_Linux_Wayland::xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial) { xdg_wm_base_pong(wm, serial); diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index c731e0b0..31967d91 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -93,6 +93,29 @@ namespace olc::host std::array axes{}; uint32_t axis_source{0}; }; + + enum TouchEventMask { + TouchEventDown = 1 << 0, + TouchEventUp = 1 << 1, + TouchEventMotion = 1 << 2, + TouchEventCancel = 1 << 3, + TouchEventShape = 1 << 4, + TouchEventOrientation = 1 << 5 + }; + + struct TouchState { + uint32_t event_mask{0}; + wl_surface* surface{nullptr}; + wl_fixed_t surface_x{}; + wl_fixed_t surface_y{}; + wl_fixed_t major{}; + wl_fixed_t minor{}; + wl_fixed_t orientation{}; + + uint32_t time{0}; + uint32_t serial{0}; + int32_t id{0}; + }; } class Host_Linux_Wayland : public olc::host::Host @@ -105,6 +128,7 @@ namespace olc::host wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; wl_keyboard* keyboard{nullptr}; + wl_touch* touch{nullptr}; uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; @@ -115,6 +139,7 @@ namespace olc::host uint32_t enter_serial{0}; wayland::PointerState pointer_state; + std::unordered_map touches; wl_surface* cursor_surface{nullptr}; wl_cursor_image* cursor_image{nullptr}; wl_cursor_theme* cursor_theme{nullptr}; @@ -182,6 +207,15 @@ namespace olc::host static void keyboard_modifiers_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); static void keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay); + // Touch callbacks + static void touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + static void touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_frame_callback(void* data, wl_touch* touch); + static void touch_cancel_callback(void* data, wl_touch* touch); + static void touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + static void touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation); + // xdg callbacks static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); @@ -219,6 +253,15 @@ namespace olc::host void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); + // Touch callbacks + void touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + void touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_frame(wl_touch* touch); + void touch_cancel(wl_touch* touch); + void touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + void touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation); + // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4bc572ef..765568b5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5928,6 +5928,29 @@ namespace olc::host std::array axes{}; uint32_t axis_source{0}; }; + + enum TouchEventMask { + TouchEventDown = 1 << 0, + TouchEventUp = 1 << 1, + TouchEventMotion = 1 << 2, + TouchEventCancel = 1 << 3, + TouchEventShape = 1 << 4, + TouchEventOrientation = 1 << 5 + }; + + struct TouchState { + uint32_t event_mask{0}; + wl_surface* surface{nullptr}; + wl_fixed_t surface_x{}; + wl_fixed_t surface_y{}; + wl_fixed_t major{}; + wl_fixed_t minor{}; + wl_fixed_t orientation{}; + + uint32_t time{0}; + uint32_t serial{0}; + int32_t id{0}; + }; } class Host_Linux_Wayland : public olc::host::Host @@ -5940,6 +5963,7 @@ namespace olc::host wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; wl_keyboard* keyboard{nullptr}; + wl_touch* touch{nullptr}; uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; @@ -5950,6 +5974,7 @@ namespace olc::host uint32_t enter_serial{0}; wayland::PointerState pointer_state; + std::unordered_map touches; wl_surface* cursor_surface{nullptr}; wl_cursor_image* cursor_image{nullptr}; wl_cursor_theme* cursor_theme{nullptr}; @@ -6017,6 +6042,15 @@ namespace olc::host static void keyboard_modifiers_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); static void keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay); + // Touch callbacks + static void touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + static void touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_frame_callback(void* data, wl_touch* touch); + static void touch_cancel_callback(void* data, wl_touch* touch); + static void touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + static void touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation); + // xdg callbacks static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); @@ -6054,6 +6088,15 @@ namespace olc::host void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); + // Touch callbacks + void touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + void touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_frame(wl_touch* touch); + void touch_cancel(wl_touch* touch); + void touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + void touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation); + // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); @@ -11814,6 +11857,16 @@ namespace olc::host .modifiers = Host_Linux_Wayland::keyboard_modifiers_callback, .repeat_info = Host_Linux_Wayland::keyboard_repeat_info_callback }; + + static const wl_touch_listener touch_listener { + .down = Host_Linux_Wayland::touch_down_callback, + .up = Host_Linux_Wayland::touch_up_callback, + .motion = Host_Linux_Wayland::touch_motion_callback, + .frame = Host_Linux_Wayland::touch_frame_callback, + .cancel = Host_Linux_Wayland::touch_cancel_callback, + .shape = Host_Linux_Wayland::touch_shape_callback, + .orientation = Host_Linux_Wayland::touch_orientation_callback + }; } namespace xdg { @@ -11964,6 +12017,7 @@ namespace olc::host { wp_pointer_warp_v1_destroy(pointer_warp); } + wl_touch_destroy(touch); wl_keyboard_destroy(keyboard); wl_pointer_destroy(pointer); wl_seat_destroy(seat); @@ -12227,6 +12281,11 @@ namespace olc::host keyboard_version = wl_keyboard_get_version(keyboard); wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); } + + if (capabilities & WL_SEAT_CAPABILITY_TOUCH && touch == nullptr) { + touch = wl_seat_get_touch(seat); + wl_touch_add_listener(touch, &wayland::touch_listener, this); + } } void Host_Linux_Wayland::registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) @@ -12565,6 +12624,169 @@ namespace olc::host // host->keyboard_repeat_info(keyboard, rate, delay); } + // Touch Callbacks + void Host_Linux_Wayland::touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) + { + auto* host = reinterpret_cast(data); + host->touch_down(touch, serial, time, surface, id, x, y); + } + + void Host_Linux_Wayland::touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventDown; + touch_state.surface = surface; + touch_state.id = id; + touch_state.surface_x = x; + touch_state.surface_y = y; + touch_state.serial = serial; + touch_state.time = time; + } + + void Host_Linux_Wayland::touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { + auto* host = reinterpret_cast(data); + host->touch_up(touch, serial, time, id); + } + + void Host_Linux_Wayland::touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventUp; + touch_state.serial = serial; + touch_state.time = time; + } + + void Host_Linux_Wayland::touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto* host = reinterpret_cast(data); + host->touch_motion(touch, time, id, x, y); + } + + void Host_Linux_Wayland::touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventMotion; + touch_state.time = time; + touch_state.surface_x = x; + touch_state.surface_y = y; + } + + void Host_Linux_Wayland::touch_frame_callback(void* data, wl_touch* touch) { + auto* host = reinterpret_cast(data); + host->touch_frame(touch); + } + + void Host_Linux_Wayland::touch_frame(wl_touch* touch) { + // Commit all of the touches + for(auto itr = touches.begin(); itr != touches.end();) { + const auto& id = itr->first; + const auto& touch = itr->second; + + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + touch.event_mask & wayland::TouchEventMask::TouchEventUp, + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + + if(touch.event_mask & wayland::TouchEventMask::TouchEventUp) { + itr = touches.erase(itr); + } else { + ++itr; + } + } + } + + void Host_Linux_Wayland::touch_cancel_callback(void* data, wl_touch* touch) { + auto* host = reinterpret_cast(data); + host->touch_cancel(touch); + } + + void Host_Linux_Wayland::touch_cancel(wl_touch* touch) { + // according to the protocol, this ends all touch events, so send an Up and clear the whole thing + for(const auto& [id, touch] : touches) { + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + true, // Forced Up event to clear all these IDs on the PGE side + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + } + + touches.clear(); + } + + void Host_Linux_Wayland::touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto* host = reinterpret_cast(data); + host->touch_shape(touch, id, major, minor); + } + + void Host_Linux_Wayland::touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventShape; + touch_state.major = major; + touch_state.minor = minor; + } + + void Host_Linux_Wayland::touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto* host = reinterpret_cast(data); + host->touch_orientation(touch, id, orientation); + } + + void Host_Linux_Wayland::touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventOrientation; + touch_state.orientation = orientation; + } + + // XDG Callbacks void Host_Linux_Wayland::xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial) { xdg_wm_base_pong(wm, serial); From 5dcdd1d19827329d374b11fded7ffdb7fb0b27a4 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:58:14 +0100 Subject: [PATCH 15/28] Updated xCode project file to support hw_touch --- dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index 904e7939..f8fd6154 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 460496082E362F190047E223 /* test_mh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495FB2E300E0F0047E223 /* test_mh.cpp */; }; + 46152BD72FE2DD1E00DCAC34 /* hw_touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */; }; 462680A42E69D03800799C36 /* image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495EC2E300E000047E223 /* image.cpp */; }; 462680A52E69D15C00799C36 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; @@ -66,6 +67,8 @@ 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 = ""; }; + 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_touch.h; sourceTree = ""; }; + 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_touch.cpp; sourceTree = ""; }; 462680D02E6C3C4500799C36 /* gpu_iface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpu_iface.cpp; sourceTree = ""; }; 466C38492F3A0B0E00F75AF4 /* matrix4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix4d.h; sourceTree = ""; }; 466C384A2F3A0B0E00F75AF4 /* vector4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vector4d.h; sourceTree = ""; }; @@ -161,6 +164,8 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( + 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */, + 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */, 466C385F2F41D69000F75AF4 /* draw_batch.cpp */, 466C385B2F41D5D400F75AF4 /* draw.h */, 466C385C2F41D5D400F75AF4 /* draw.cpp */, @@ -283,6 +288,7 @@ 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */, 46C8F5B92E7DA67900C11019 /* imload_macos.cpp in Sources */, 460496082E362F190047E223 /* test_mh.cpp in Sources */, + 46152BD72FE2DD1E00DCAC34 /* hw_touch.cpp in Sources */, 466C385E2F41D5D400F75AF4 /* draw.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From e2fc6ca588813fc2a7044eff216321d4e29d86b9 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:25:55 +0100 Subject: [PATCH 16/28] 274-implement-stylus-touch-and-touch-for-macos --- dev/src/api_macos.cpp | 385 ++++++++++++++++++++++- dev/src/api_macos.h | 17 +- dev/src/api_macos_wrapper.hpp | 103 ++++++ dev/src/host_apple_macos.cpp | 46 +++ olcPixelGameEngine3.h | 569 +++++++++++++++++++++++++++++++++- 5 files changed, 1088 insertions(+), 32 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 2bc7d6c5..e80190ac 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -2,6 +2,7 @@ #include #include #include +#include //! START IMPLEMENTATION @@ -95,6 +96,35 @@ static constexpr const char* kTrackingAreaClass = "NSTrackingAre static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; +// Copied a lot of this code from iOS, will need to be tested +// NSResponder touch event method selectors +static constexpr const char* kCGEventSel = "CGEvent"; +static constexpr const char* kTouchesBeganSel = "touchesBegan:withEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMoved:withEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEnded:withEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelled:withEvent:"; + +// 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"; + +// NSResponder tablet / stylus event method selectors +static constexpr const char* kTabletPointSel = "tabletPoint:"; +static constexpr const char* kTabletProximitySel = "tabletProximity:"; + +// 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"; + // Managing first responder status and keyboard focus selectors static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; @@ -169,6 +199,9 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@@"; + namespace ObjectiveCSEL { // Application memory management selectors @@ -247,6 +280,30 @@ namespace ObjectiveCSEL { static SEL addTrackingAreaSel = nullptr; static SEL initWithRectSel = nullptr; + // Touch event selectors + 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; + // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; static SEL becomeFirstResponderSel = nullptr; @@ -367,6 +424,30 @@ namespace ObjectiveCSEL { addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); initWithRectSel = sel_registerName(kInitWithRectSel); + // Touch event selectors + 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); + + // 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); + // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); @@ -702,6 +783,21 @@ struct Window { 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}; + // 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}; + + // 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 @@ -943,6 +1039,7 @@ void convertToContentViewCoordinates(NSPoint& location) { } } +// Mouse event data structure to hold common mouse event information struct MouseEventData { NSPoint location = {0.0, 0.0}; NSUInteger modifierFlags = 0; @@ -961,52 +1058,164 @@ MouseEventData extractMouseEventData(id event) { return data; } +// 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) + +}; + +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + + 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); + + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + + 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); + + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + + // 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)); + + // 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); + + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} + // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings - MouseEventData data = extractMouseEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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]] { + + 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); + } } -} + else + { + // 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); + } + +} // Handle left mouse up events void view_mouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - 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); + 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]] { + + 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 + { + // 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); + } + } // Handle mouse drag events void view_mouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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]] { + + 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 + { + // 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); + } + } + // Handle mouse movement events void view_mouseMoved(id self, SEL _cmd, id event) { (void)self;(void)_cmd; + // a Mouse move event and Stlus roximity Event have the same properties. NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); } + + } // Handle right mouse down events @@ -1123,6 +1332,115 @@ void view_mouseExited(id self, SEL _cmd, id event) { } } +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; + +// Stable sequential touch ID map — maps identity pointer → assigned uint32_t ID +static std::unordered_map sTouchIDMap; +static uint32_t sNextTouchID = 0; + +/* + Iterate through Touches, updates and fires the callback. + Pass bRemove = true for Ended/Cancelled to clean up the ID map entry. +*/ +static void updateTouchData(id event, NSUInteger phase, + void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) +{ + if (!callback) return; + + // Get the current collection of touches (nil view = window coords) + id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); + if (!touchCollection) return; + + NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + id touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + + for (NSUInteger i = 0; i < count; ++i) { + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + + // normalizedPosition is NSPoint in [0,1] range on the trackpad surface + NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); + // deviceSize gives the physical trackpad size in points + NXSize devSize = ((NXSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); + + // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) + double x = normPos.x * devSize.width; + double y = (1.0 - normPos.y) * devSize.height; + + // Assign a stable sequential uint32_t ID same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); // I LOVE C++20 structured bindings + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + + // This is a trackpad touch event, so we don't have a sizeX/sizeY like on iOS. We'll just pass the device size as a proxy. + callback(tid, x, y, 1.0f, 1.0f, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) sTouchIDMap.erase(key); + } +} + +void view_touchesBegan(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseBegan, + gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesMoved(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseMoved, + gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesEnded(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseEnded, + gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); + } +} + +void view_touchesCancelled(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseCancelled, + gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); + } +} + +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletProximity(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet proximity events here + BOOL entering = ((BOOL(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::penIsEnteringProximitySel); + // For furture support should call a stylusCallback with proximity event data, but for now just set the flag + gptrNSWindowEvents->bStylusInProximity = (entering == YES); + } +} + + void view_updateTrackingAreas(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -1216,6 +1534,15 @@ Class createCustomOpenGLViewClass() { class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); + // Touch event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)view_touchesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); + + // Tablet / stylus event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); + // Area tracking for Mouse entered/exited event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); @@ -2283,6 +2610,34 @@ extern "C" { self->eventUserData = userData; } + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchMovedCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchEndedCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchCancelledCallback = callback; + self->touchUserData = userData; + } + + void window_setStylusCallback(Window* self, + void (*callback)(uint32_t, double, double, float, float, float, float, bool, bool, bool, void*), + void* userData) + { + self->stylusCallback = callback; + self->stylusUserData = userData; + } + void window_enableEventHandling(Window* self) { if (self) { self->acceptsInputEvents = YES; diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index 62c17049..3729f2c1 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -114,7 +114,12 @@ extern "C" { // Event callback function types typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); - + typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); + typedef void (*StylusEventCallback) (uint32_t touchID, double x, double y,float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData); + // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); @@ -137,6 +142,16 @@ extern "C" { void window_enableEventHandling (struct Window* self); void window_disableEventHandling (struct Window* self); + // Touch event handler setup + void window_setTouchBeganCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchMovedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchEndedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchCancelledCallback (struct Window* self, TouchEventCallback callback, void* userData); + + // Stylus (tablet) event handler setup + void window_setStylusCallback (struct Window* self, StylusEventCallback callback, void* userData); + + // Application delegate callback function type typedef void (*ApplicationDelegateCallback)(void* userData); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index a346b9cc..987b585c 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -756,10 +756,46 @@ namespace olc { : x(x), y(y), deltaX(deltaX), deltaY(deltaY), modifierFlags(flags) {} }; + // Touch event data structure + struct TouchEvent { + uint32_t touchID; + double x, y; + double sizeX, sizeY; + + TouchEvent(uint32_t id, double x, double y, double sx, double sy) noexcept + : touchID(id), x(x), y(y), sizeX(sx), sizeY(sy) {} + + TouchEvent(TouchEvent&&) noexcept = default; + TouchEvent& operator=(TouchEvent&&) noexcept = default; + TouchEvent(const TouchEvent&) = default; + TouchEvent& operator=(const TouchEvent&) = default; + }; + + // Stylus (tablet) event data structure + struct StylusEvent { + uint32_t touchID; + double x, y; + float pressure, rotation; + float tiltX, tiltY; + bool bPress, bRelease; + bool bIsStylus; + + StylusEvent(uint32_t id, double x, double y, float pressure, float rotation, float tiltX, float tiltY, bool bPress, bool bRelease, bool bIsStylus) noexcept + : touchID(id), x(x), y(y), pressure(pressure), rotation(rotation), tiltX(tiltX), tiltY(tiltY), + bPress(bPress), bRelease(bRelease), bIsStylus(bIsStylus) {} + + StylusEvent(StylusEvent&&) noexcept = default; + StylusEvent& operator=(StylusEvent&&) noexcept = default; + StylusEvent(const StylusEvent&) = default; + StylusEvent& operator=(const StylusEvent&) = default; + }; + // Event handler class for keyboard and mouse events class EventHandler { private: Window& window_; + + // Key and mouse event handlers std::function keyDownHandler_; std::function keyUpHandler_; std::function flagsChangedHandler_; @@ -777,6 +813,15 @@ namespace olc { std::function mouseMovedEnteredHandler_; std::function mouseMovedExitedHandler_; + // Touch event handlers + std::function touchBeganHandler_; + std::function touchMovedHandler_; + std::function touchEndedHandler_; + std::function touchCancelledHandler_; + + // Stylus event handler + std::function stylusHandler_; + // Template helpers for static callbacks to reduce code duplication template static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { @@ -888,6 +933,37 @@ namespace olc { } } + static void touchBeganCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchBeganHandler_) + eventHandler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + static void touchMovedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchMovedHandler_) + eventHandler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchEndedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchEndedHandler_) + eventHandler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchCancelledCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchCancelledHandler_) + eventHandler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static 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) + { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->stylusHandler_) + eventHandler->stylusHandler_(StylusEvent(touchID, x, y, pressure, rotation, tiltX, tiltY, bPress, bRelease, bIsStylus)); + } + // Template helper for setting event handlers to reduce repetition template void setEventHandler(HandlerType EventHandler::*member, SetterFunc setter, CallbackFunc callback, std::function handler) { @@ -981,6 +1057,33 @@ namespace olc { window_setMouseExitedCallback(window_.getCHandle(), mouseExitedCallback, this); } + // Touch event handler setters + void onTouchBegan(std::function handler) { + touchBeganHandler_ = std::move(handler); + window_setTouchBeganCallback(window_.getCHandle(), touchBeganCallback, this); + } + + void onTouchMoved(std::function handler) { + touchMovedHandler_ = std::move(handler); + window_setTouchMovedCallback(window_.getCHandle(), touchMovedCallback, this); + } + + void onTouchEnded(std::function handler) { + touchEndedHandler_ = std::move(handler); + window_setTouchEndedCallback(window_.getCHandle(), touchEndedCallback, this); + } + + void onTouchCancelled(std::function handler) { + touchCancelledHandler_ = std::move(handler); + window_setTouchCancelledCallback(window_.getCHandle(), touchCancelledCallback, this); + } + + // Stylus event handler setter + void onStylus(std::function handler) { + stylusHandler_ = std::move(handler); + window_setStylusCallback(window_.getCHandle(), stylusCallback, this); + } + // Enable/disable event handling void enable() noexcept { window_enableEventHandling(window_.getCHandle()); diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 14a6ff94..b4759995 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -757,9 +757,55 @@ namespace olc::host { // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); }); + + // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch + pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + // Stylus (tablet) events — full pressure, tilt and rotation data + pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + + pPGEwindow->olc_OnTouch( + event.touchID, + {static_cast(event.x), static_cast(event.y)}, + event.bPress, + event.bRelease, + {1.0f, 1.0f}, // stylus contact size — nominal 1x1 + true, // bStylus = true + event.pressure, + event.rotation, + {event.tiltX, event.tiltY} + ); + }); } } //! END IMPLEMENTATION + #endif /* OLC_HOST == OLC_HOST_MACOS */ diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4bc572ef..41478828 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4609,7 +4609,13 @@ extern "C" { // Event callback function types typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); - + typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); + typedef void (*StylusEventCallback) (uint32_t touchID, double x, double y, + float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData); + // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); @@ -4632,6 +4638,16 @@ extern "C" { void window_enableEventHandling (struct Window* self); void window_disableEventHandling (struct Window* self); + // Touch event handler setup + void window_setTouchBeganCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchMovedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchEndedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchCancelledCallback (struct Window* self, TouchEventCallback callback, void* userData); + + // Stylus (tablet) event handler setup + void window_setStylusCallback (struct Window* self, StylusEventCallback callback, void* userData); + + // Application delegate callback function type typedef void (*ApplicationDelegateCallback)(void* userData); @@ -5396,10 +5412,46 @@ namespace olc { : x(x), y(y), deltaX(deltaX), deltaY(deltaY), modifierFlags(flags) {} }; + // Touch event data structure + struct TouchEvent { + uint32_t touchID; + double x, y; + double sizeX, sizeY; + + TouchEvent(uint32_t id, double x, double y, double sx, double sy) noexcept + : touchID(id), x(x), y(y), sizeX(sx), sizeY(sy) {} + + TouchEvent(TouchEvent&&) noexcept = default; + TouchEvent& operator=(TouchEvent&&) noexcept = default; + TouchEvent(const TouchEvent&) = default; + TouchEvent& operator=(const TouchEvent&) = default; + }; + + // Stylus (tablet) event data structure + struct StylusEvent { + uint32_t touchID; + double x, y; + float pressure, rotation; + float tiltX, tiltY; + bool bPress, bRelease; + bool bIsStylus; + + StylusEvent(uint32_t id, double x, double y, float pressure, float rotation, float tiltX, float tiltY, bool bPress, bool bRelease, bool bIsStylus) noexcept + : touchID(id), x(x), y(y), pressure(pressure), rotation(rotation), tiltX(tiltX), tiltY(tiltY), + bPress(bPress), bRelease(bRelease), bIsStylus(bIsStylus) {} + + StylusEvent(StylusEvent&&) noexcept = default; + StylusEvent& operator=(StylusEvent&&) noexcept = default; + StylusEvent(const StylusEvent&) = default; + StylusEvent& operator=(const StylusEvent&) = default; + }; + // Event handler class for keyboard and mouse events class EventHandler { private: Window& window_; + + // Key and mouse event handlers std::function keyDownHandler_; std::function keyUpHandler_; std::function flagsChangedHandler_; @@ -5417,6 +5469,15 @@ namespace olc { std::function mouseMovedEnteredHandler_; std::function mouseMovedExitedHandler_; + // Touch event handlers + std::function touchBeganHandler_; + std::function touchMovedHandler_; + std::function touchEndedHandler_; + std::function touchCancelledHandler_; + + // Stylus event handler + std::function stylusHandler_; + // Template helpers for static callbacks to reduce code duplication template static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { @@ -5528,6 +5589,38 @@ namespace olc { } } + static void touchBeganCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchBeganHandler_) + eventHandler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + static void touchMovedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchMovedHandler_) + eventHandler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchEndedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchEndedHandler_) + eventHandler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchCancelledCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchCancelledHandler_) + eventHandler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static 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) + { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->stylusHandler_) + eventHandler->stylusHandler_(StylusEvent(touchID, x, y, pressure, rotation, tiltX, tiltY, bPress, bRelease, bIsStylus)); + } + // Template helper for setting event handlers to reduce repetition template void setEventHandler(HandlerType EventHandler::*member, SetterFunc setter, CallbackFunc callback, std::function handler) { @@ -5621,6 +5714,33 @@ namespace olc { window_setMouseExitedCallback(window_.getCHandle(), mouseExitedCallback, this); } + // Touch event handler setters + void onTouchBegan(std::function handler) { + touchBeganHandler_ = std::move(handler); + window_setTouchBeganCallback(window_.getCHandle(), touchBeganCallback, this); + } + + void onTouchMoved(std::function handler) { + touchMovedHandler_ = std::move(handler); + window_setTouchMovedCallback(window_.getCHandle(), touchMovedCallback, this); + } + + void onTouchEnded(std::function handler) { + touchEndedHandler_ = std::move(handler); + window_setTouchEndedCallback(window_.getCHandle(), touchEndedCallback, this); + } + + void onTouchCancelled(std::function handler) { + touchCancelledHandler_ = std::move(handler); + window_setTouchCancelledCallback(window_.getCHandle(), touchCancelledCallback, this); + } + + // Stylus event handler setter + void onStylus(std::function handler) { + stylusHandler_ = std::move(handler); + window_setStylusCallback(window_.getCHandle(), stylusCallback, this); + } + // Enable/disable event handling void enable() noexcept { window_enableEventHandling(window_.getCHandle()); @@ -8868,6 +8988,51 @@ namespace olc::host { // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); }); + + // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch + pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + // Stylus (tablet) events — full pressure, tilt and rotation data + pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + + pPGEwindow->olc_OnTouch( + event.touchID, + {static_cast(event.x), static_cast(event.y)}, + event.bPress, + event.bRelease, + {1.0f, 1.0f}, // stylus contact size — nominal 1x1 + true, // bStylus = true + event.pressure, + event.rotation, + {event.tiltX, event.tiltY} + ); + }); } @@ -8964,6 +9129,35 @@ static constexpr const char* kTrackingAreaClass = "NSTrackingAre static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; +// Copied a lot of this code from iOS, will need to be tested +// NSResponder touch event method selectors +static constexpr const char* kCGEventSel = "CGEvent"; +static constexpr const char* kTouchesBeganSel = "touchesBegan:withEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMoved:withEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEnded:withEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelled:withEvent:"; + +// 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"; + +// NSResponder tablet / stylus event method selectors +static constexpr const char* kTabletPointSel = "tabletPoint:"; +static constexpr const char* kTabletProximitySel = "tabletProximity:"; + +// 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"; + // Managing first responder status and keyboard focus selectors static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; @@ -9038,6 +9232,9 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@@"; + namespace ObjectiveCSEL { // Application memory management selectors @@ -9116,6 +9313,30 @@ namespace ObjectiveCSEL { static SEL addTrackingAreaSel = nullptr; static SEL initWithRectSel = nullptr; + // Touch event selectors + 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; + // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; static SEL becomeFirstResponderSel = nullptr; @@ -9236,6 +9457,30 @@ namespace ObjectiveCSEL { addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); initWithRectSel = sel_registerName(kInitWithRectSel); + // Touch event selectors + 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); + + // 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); + // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); @@ -9571,6 +9816,21 @@ struct Window { 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}; + // 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}; + + // 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 @@ -9812,6 +10072,7 @@ void convertToContentViewCoordinates(NSPoint& location) { } } +// Mouse event data structure to hold common mouse event information struct MouseEventData { NSPoint location = {0.0, 0.0}; NSUInteger modifierFlags = 0; @@ -9830,52 +10091,164 @@ MouseEventData extractMouseEventData(id event) { return data; } +// 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) + +}; + +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + + 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); + + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + + 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); + + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + + // 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)); + + // 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); + + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} + // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings - MouseEventData data = extractMouseEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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]] { + + 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); + } } -} + else + { + // 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); + } + +} // Handle left mouse up events void view_mouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - 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); + 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]] { + + 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 + { + // 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); + } + } // Handle mouse drag events void view_mouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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]] { + + 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 + { + // 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); + } + } + // Handle mouse movement events void view_mouseMoved(id self, SEL _cmd, id event) { (void)self;(void)_cmd; + // a Mouse move event and Stlus roximity Event have the same properties. NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); } + + } // Handle right mouse down events @@ -9992,6 +10365,132 @@ void view_mouseExited(id self, SEL _cmd, id event) { } } +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; + +// Stable sequential touch ID map — maps identity pointer → assigned uint32_t ID +static std::unordered_map sTouchIDMap; +static uint32_t sNextTouchID = 0; + +/* + Iterate through Touches, updates and fires the callback. + Pass bRemove = true for Ended/Cancelled to clean up the ID map entry. +*/ +static void updateTouchData(id event, NSUInteger phase, + void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) +{ + if (!callback) return; + + // Get the current collection of touches (nil view = window coords) + id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); + if (!touchCollection) return; + + NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + id touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + + for (NSUInteger i = 0; i < count; ++i) { + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + + // normalizedPosition is NSPoint in [0,1] range on the trackpad surface + NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); + // deviceSize gives the physical trackpad size in points + NXSize devSize = ((NXSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); + + // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) + double x = normPos.x * devSize.width; + double y = (1.0 - normPos.y) * devSize.height; + + // Assign a stable sequential uint32_t ID same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); // I LOVE C++20 structured bindings + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + + // This is a trackpad touch event, so we don't have a sizeX/sizeY like on iOS. We'll just pass the device size as a proxy. + callback(tid, x, y, 1.0f, 1.0f, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) sTouchIDMap.erase(key); + } +} + +void view_touchesBegan(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseBegan, + gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesMoved(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseMoved, + gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesEnded(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseEnded, + gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); + } +} + +void view_touchesCancelled(id self, SEL _cmd, id touches, id event) { + (void)self;(void)_cmd;(void)touches; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseCancelled, + gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); + } +} + +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletProximity(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet proximity events here + BOOL entering = ((BOOL(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::penIsEnteringProximitySel); + // For furture support should call a stylusCallback with proximity event data, but for now just set the flag + gptrNSWindowEvents->bStylusInProximity = (entering == YES); + } +} + +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletPoint(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet point events here + //StylusEventData data = extractStylusEventData(event, 1); // 1 = Pen Event + //bool bPressed = data.buttonMask & 1; // Check if the first button is pressed + //bool bReleased = !bPressed; + //gptrNSWindowEvents->stylusCallback( + // data.nTabletDeviceID, + // data.nspLocation.x, data.nspLocation.y, + // data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + // bPressed, bReleased, true, + // gptrNSWindowEvents->stylusUserData); + } +} + + void view_updateTrackingAreas(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -10085,6 +10584,16 @@ Class createCustomOpenGLViewClass() { class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); + // Touch event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)view_touchesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); + + // Tablet / stylus event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); + // Area tracking for Mouse entered/exited event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); @@ -11152,6 +11661,34 @@ extern "C" { self->eventUserData = userData; } + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchMovedCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchEndedCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchCancelledCallback = callback; + self->touchUserData = userData; + } + + void window_setStylusCallback(Window* self, + void (*callback)(uint32_t, double, double, float, float, float, float, bool, bool, bool, void*), + void* userData) + { + self->stylusCallback = callback; + self->stylusUserData = userData; + } + void window_enableEventHandling(Window* self) { if (self) { self->acceptsInputEvents = YES; From 852d789dfca14d3d77146fbdd7ca6af93ea9ac22 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:34:47 +0100 Subject: [PATCH 17/28] fixed regular alpha blending for polygon draws, need to test images --- dev/msvc/olcPGE3.vcxproj | 1 + dev/msvc/olcPGE3.vcxproj.filters | 3 + .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 9 +- dev/src/gpu_opengl33.cpp | 3 +- olcPixelGameEngine3.h | 3 +- utilities/olcUTIL3_QuadTree.h | 351 ++++++++++++++++++ 6 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 utilities/olcUTIL3_QuadTree.h diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index e739e722..c6fd61d1 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -180,6 +180,7 @@ + true true diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 25fc3da8..c323ce4d 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -174,6 +174,9 @@ Header Files + + Utilities + diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index fe181624..c1248d8d 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -50,8 +50,8 @@ true true - true - true + false + false true @@ -167,7 +167,10 @@ true true - + + true + true + true true diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 8c4e5a6d..c217235f 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -1168,7 +1168,8 @@ void main() if (task.blendmode == olc::BlendMode::Alpha) { gl.glEnable(GL_BLEND); - gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } else if(task.blendmode == olc::BlendMode::Additive) { diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4bc572ef..1a64b652 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -15917,7 +15917,8 @@ void main() if (task.blendmode == olc::BlendMode::Alpha) { gl.glEnable(GL_BLEND); - gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } else if(task.blendmode == olc::BlendMode::Additive) { diff --git a/utilities/olcUTIL3_QuadTree.h b/utilities/olcUTIL3_QuadTree.h new file mode 100644 index 00000000..69f34c24 --- /dev/null +++ b/utilities/olcUTIL3_QuadTree.h @@ -0,0 +1,351 @@ +/* + OneLoneCoder - QuadTree v2.0 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A Dynamic Quad-Tree implementation to store objects in a 2D space + with a fast retrieval. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2026 OneLoneCoder.com + + 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/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019 - 2026 + + + Changes + ~~~~~~~ + v1.01: Bug fix clear() function + v1.02: File Restructure + +Added insertion return iterator + v2.0 : PGE 3.0 Refactor + +*/ + + +/* + WARNING! VECTORS OF QUAD-TREES + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + This quad tree implementation will suffer when stored inside a container + that can reallocate its contents. Upon reallocation, all of the iterators + used to link the quad tree to its items will be invalidated. + + Currently, I advise caution when using std::vector of this quad tree, and + should you choose to, then reserve space in advance so reallocation cannot + occur. +*/ + + +#pragma once + +#include "utilities/olcUTIL3_Geometry2D.h" + +namespace olc::utils +{ + template + struct QuadTreeItemLocation + { + // Pointer to Top-Level Container + typename std::list, T>>* container = nullptr; + // Iterator that points to item in Top-Level Container + typename std::list, T>>::iterator iterator; + }; + + template + class DynamicQuadTree + { + public: + DynamicQuadTree(const geom2d::rect& size, const size_t nDepth = 0, const size_t nMaxDepth = 8) + { + m_depth = nDepth; + m_rect = size; + m_maxdepth = nMaxDepth; + resize(m_rect); + } + + // Insert a region into this area + QuadTreeItemLocation insert(const pT item, const geom2d::rect& itemsize) + { + for (int i = 0; i < 4; i++) + { + if (geom2d::contains(m_rChild[i], itemsize)) + { + // Have we reached depth limit? + if (m_depth + 1 < m_maxdepth) + { + // No, so does child exist? + if (!m_pChild[i]) + { + // No, so create it + m_pChild[i] = std::make_shared>(m_rChild[i], m_depth + 1, m_maxdepth); + } + + // Yes, so add item to it + return m_pChild[i]->insert(item, itemsize); + } + } + } + + // It didnt fit, so item must belong to this geom2d::rect + m_pItems.push_back({ itemsize, item }); + return { &m_pItems, std::prev(m_pItems.end()) }; + } + + void relocate(pT item, const geom2d::rect& rArea) + { + // Remove it + remove(item); + + // Reinsert it with new location + insert(item, rArea); + } + + size_t size() const + { + size_t nCount = m_pItems.size(); + for (int i = 0; i < 4; i++) + if (m_pChild[i]) nCount += m_pChild[i]->size(); + return nCount; + } + + void search(const geom2d::rect& rArea, std::list& listItems) const + { + // First, check for items belonging to this area, add them to the list + // if there is overlap + for (const auto& p : m_pItems) + { + if (geom2d::overlaps(rArea,p.first)) + listItems.push_back(p.second); + } + + // Second, recurse through children and see if they can + // add to the list + for (int i = 0; i < 4; i++) + { + if (m_pChild[i]) + { + // If child is entirely contained within area, recursively + // add all of its children, no need to check boundaries + if (geom2d::contains(rArea,m_rChild[i])) + m_pChild[i]->items(listItems); + + // If child overlaps with search area then checks need + // to be made + else if (geom2d::overlaps(m_rChild[i],rArea)) + m_pChild[i]->search(rArea, listItems); + } + } + + + } + + void items(std::list& listItems) const + { + // No questions asked, just return child items + for (const auto& p : m_pItems) + listItems.push_back(p.second); + + for (int i = 0; i < 4; i++) + if (m_pChild[i]) m_pChild[i]->items(listItems); + } + + void clear() + { + m_pItems.clear(); + + for (int i = 0; i < 4; i++) + { + if (m_pChild[i]) + m_pChild[i]->clear(); + else + m_pChild[i].reset(); + } + } + + void resize(const geom2d::rect& rArea) + { + clear(); + m_rect = rArea; + olc::v_2d vChildSize = m_rect.size / CTYPE(2); + m_rChild = + { + geom2d::rect(m_rect.pos, vChildSize), + geom2d::rect({m_rect.pos.x + vChildSize.x, m_rect.pos.y}, vChildSize), + geom2d::rect({m_rect.pos.x, m_rect.pos.y + vChildSize.y}, vChildSize), + geom2d::rect(m_rect.pos + vChildSize, vChildSize) + }; + } + + + const geom2d::rect& area() + { + return m_rect; + } + + protected: + size_t m_depth = 0; + size_t m_maxdepth = 8; + + // Area of this quadnode + geom2d::rect m_rect; + + // 4 child areas of this quadnode + std::array, 4> m_rChild{}; + + // 4 potential children of this quadnode + std::array>, 4> m_pChild{}; + + // Items which belong to this quadnode + std::list, pT>> m_pItems; + + }; + + template + struct QuadTreeItem + { + // The item Itself + T item; + + // A "locator" to the container/iterator that points to this item's iterator in the + // top level list - phew + QuadTreeItemLocation>::iterator> pItem; + }; + + template + class QuadTreeContainer + { + using IQuadtreeContainer = std::list>; + + public: + QuadTreeContainer(const geom2d::rect& size = { {0.0f, 0.0f}, { 100.0f, 100.0f } }, const size_t nDepth = 0, const size_t nMaxDepth = 8) : root(size, nDepth, nMaxDepth) + { + + } + + // Sets the spatial coverage area of teh quadtree + void resize(const geom2d::rect& rArea) + { + root.resize(rArea); + } + + // Inserts an item into the quadtree + typename IQuadtreeContainer::iterator insert(const T& item, const geom2d::rect& itemsize) + { + QuadTreeItem newItem; + newItem.item = item; + + // Item i stored in container + m_allItems.emplace_back(newItem); + + // Pointer/Area of item is stored in geom2d::rect tree + m_allItems.back().pItem = root.insert(std::prev(m_allItems.end()), itemsize); + + return std::prev(m_allItems.end()); + } + + // Returns a std::list of pointers to items within the search area + std::list search(const geom2d::rect& rArea) const + { + std::list listItemPointers; + root.search(rArea, listItemPointers); + return listItemPointers; + } + + void remove(typename IQuadtreeContainer::iterator& item) + { + // Iterator points to a QuadTreeItem + item->pItem.container->erase(item->pItem.iterator); + + // Remove actual item from container + m_allItems.erase(item); + } + + void relocate(typename IQuadtreeContainer::iterator& item, const geom2d::rect& itemsize) + { + // Remove pointer to item from whichever container its stored in + item->pItem.container->erase(item->pItem.iterator); + + // Update the items pointer by reinsertion into geom2d::rect tree + item->pItem = root.insert(item, itemsize); + + } + + typename IQuadtreeContainer::iterator begin() + { + return m_allItems.begin(); + } + + typename IQuadtreeContainer::iterator end() + { + return m_allItems.end(); + } + + typename IQuadtreeContainer::const_iterator cbegin() + { + return m_allItems.cbegin(); + } + + typename IQuadtreeContainer::const_iterator cend() + { + return m_allItems.cend(); + } + + size_t size() const + { + return root.size(); + } + + void clear() + { + m_allItems.clear(); + root.clear(); + } + + const geom2d::rect& area() + { + return root.area(); + } + + protected: + DynamicQuadTree root; + IQuadtreeContainer m_allItems; + }; +} From f5e290340dbf798d492ff6d933eeb7d66cf5918a Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:38:04 +0100 Subject: [PATCH 18/28] imagery seems fine now, but need more testing --- dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 ++++---- dev/src/gpu_opengl33.cpp | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index c1248d8d..0a32e182 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -44,14 +44,14 @@ true - true - true + false + false true true - false - false + true + true true diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index c217235f..8877890a 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -1181,6 +1181,11 @@ void main() gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); } + else if (task.blendmode == olc::BlendMode::None) + { + gl.glDisable(GL_BLEND); + } + //gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); From 84149ca5d2afeefa6c21bb130fb0c5e888d25710 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:06:51 +0100 Subject: [PATCH 19/28] little bodge to camera class --- utilities/olcUTIL3_Camera2D.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index b6839154..f645510f 100644 --- a/utilities/olcUTIL3_Camera2D.h +++ b/utilities/olcUTIL3_Camera2D.h @@ -223,13 +223,13 @@ namespace olc::utils case Mode::FixedScreens: { - m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f); + m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vViewSize) * olc::vi2d(m_vViewSize)) + (m_vViewSize * 0.5f); } break; case Mode::SlideScreens: { - olc::vf2d vScreen = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f); + olc::vf2d vScreen = olc::vf2d(olc::vi2d(GetTarget() / m_vViewSize) * olc::vi2d(m_vViewSize)) + (m_vViewSize * 0.5f); m_vPosition += (vScreen - m_vPosition) * m_fLazyFollowRate * 2.0f * fElapsedTime; } break; From b79ee3123556bcc967b3b3d5cefd90a1b9d71e71 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:43:18 +0100 Subject: [PATCH 20/28] Completed TrackPad integration for touch point PR can now be merged --- dev/src/api_macos.cpp | 434 +++++++++++++++++++++++++++++++++--------- olcPixelGameEngine3.h | 434 ++++++++++++++++++++++++++++++++---------- 2 files changed, 677 insertions(+), 191 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index e80190ac..cdad635b 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -7,6 +7,7 @@ //! START IMPLEMENTATION // 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"; @@ -98,11 +99,13 @@ static constexpr const char* kInitWithRectSel = "initWithRect: // 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 = "touchesBegan:withEvent:"; -static constexpr const char* kTouchesMovedSel = "touchesMoved:withEvent:"; -static constexpr const char* kTouchesEndedSel = "touchesEnded:withEvent:"; -static constexpr const char* kTouchesCancelledSel = "touchesCancelled:withEvent:"; +static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; // NSTouch / NSSet data extraction selectors static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; @@ -116,6 +119,8 @@ static constexpr const char* kTouchCountSel = "count"; // 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"; // NSEvent tablet data extraction selectors static constexpr const char* kPenPressureSel = "pressure"; @@ -145,6 +150,7 @@ static constexpr const char* kConvertRectToBackingSel = "convertRectTo 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:"; @@ -199,8 +205,8 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; -// Type encoding for touch event methods: two id params (touches set + event) → "v@:@@" -static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@@"; +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; namespace ObjectiveCSEL { @@ -281,7 +287,9 @@ namespace ObjectiveCSEL { static SEL initWithRectSel = nullptr; // Touch event selectors - static SEL cgEventSel = nullptr; + static SEL setAcceptsTouchEventsSel = nullptr; + static SEL setWantsRestingTouchesSel = nullptr; + static SEL cgEventSel = nullptr; static SEL touchesBeganSel = nullptr; static SEL touchesMovedSel = nullptr; static SEL touchesEndedSel = nullptr; @@ -303,6 +311,8 @@ namespace ObjectiveCSEL { static SEL penIsEnteringProximitySel = nullptr; static SEL pointingDeviceTypeSel = nullptr; static SEL penTangentialPressureSel = nullptr; + static SEL pressureChangeSel = nullptr; + static SEL stageSel = nullptr; // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; @@ -330,6 +340,7 @@ namespace ObjectiveCSEL { static SEL displaySel = nullptr; static SEL CGLContextObjSel = nullptr; static SEL setValuesSel = nullptr; + static SEL backingScaleFactorSel = nullptr; // Extracting data from NSEvent objects selectors static SEL keyCodeSel = nullptr; @@ -425,6 +436,8 @@ namespace ObjectiveCSEL { 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); @@ -447,6 +460,8 @@ namespace ObjectiveCSEL { penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); + pressureChangeSel = sel_registerName(kPressureChangeSel); + stageSel = sel_registerName(kStageSel); // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); @@ -474,6 +489,7 @@ namespace ObjectiveCSEL { displaySel = sel_registerName(kDisplaySel); CGLContextObjSel = sel_registerName(kCGLContextObjSel); setValuesSel = sel_registerName(kSetValuesSel); + backingScaleFactorSel = sel_registerName(kBackingScaleFactorSel); // Extracting data from NSEvent objects selectors keyCodeSel = sel_registerName(kKeyCodeSel); @@ -758,12 +774,12 @@ struct Application { // 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 + 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 // Event callback function pointers with nullptr initialization void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; @@ -1079,7 +1095,7 @@ StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { StylusEventData data; id cgEvent = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); data.nTabletDeviceID = (uint32_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventDeviceID); data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); @@ -1108,6 +1124,240 @@ StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { return data; } +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + + +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; + +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; + +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; + +// 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); + + // 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; +}; + +// 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); + + // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) + data.nspLocation.x = normPos.x * screenFrame.width; + data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; + + // Get Device Size + data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); + // Approximate touch size based on pressure stage (macOS does not provide actual touch size) + data.nsTouchSize = cgsApproxTouchSize; + data.bIsActive = !bRemove; + + // Assign a stable sequential uint32_t IDs same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + data.touchID = tid; + sTouchIDEvents.emplace(key, data); + callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) + { + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); + } + + } + + } + + // A bit painful, but we need to check of any keys that have been removed as the touchleave event happened outside of our application + std::vector vFoundKeys; + std::vector vMissingKeys; + touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, NSTouchPhaseAny, nil); + + // If we have a touch collection and the phase is not cancelled, we can check for missing keys, otherwise we will just remove all keys from the map + if (touchCollection && phase != NSTouchPhaseCancelled) + { + count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + for (NSUInteger i = 0; i < count; ++i) { + + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + vFoundKeys.push_back(key); + + } + } + + // Find the missing keys and fire the touchEndedCallback for them + for(auto& [key, data] : sTouchIDEvents) { + + if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { + gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); + vMissingKeys.push_back(key); + } + + } + + // 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]] { + + // 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) + { + // stage returns: 0 = normal click, 1 = light press, 2 = force click + stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); + } + + // Map stage to normalized pressure values as requested + double pressure = toNormalizedPressure(ForceTouchPressure::Normal); + + switch (stage) { + case NSPressureStageLight: + pressure = toNormalizedPressure(ForceTouchPressure::Light); + break; + case NSPressureStageNormal: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + case NSPressureStageForce: + pressure = toNormalizedPressure(ForceTouchPressure::Force); + break; + default: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + } + + // now we need to work out the size + cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); + + for(auto& [key, data] : sTouchIDEvents) { + data.pressure = pressure; + data.nsTouchSize = cgsApproxTouchSize; + } + + } + } +} + // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings @@ -1124,7 +1374,7 @@ void view_mouseDown(id self, SEL _cmd, id event) { (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } } - else + else if (nPointerEventType == 1 || nPointerEventType == 2) { // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) gptrNSWindowEvents->bStylusInProximity = false; @@ -1156,7 +1406,7 @@ void view_mouseUp(id self, SEL _cmd, id event) { (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } } - else + else if(nPointerEventType == 1 || nPointerEventType == 2) { // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) gptrNSWindowEvents->bStylusInProximity = false; @@ -1187,7 +1437,7 @@ void view_mouseDragged(id self, SEL _cmd, id event) { gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } } - else + else if (nPointerEventType == 1 || nPointerEventType == 2) { // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) gptrNSWindowEvents->bStylusInProximity = false; @@ -1207,6 +1457,11 @@ void view_mouseDragged(id self, SEL _cmd, id event) { 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); @@ -1221,7 +1476,12 @@ 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); @@ -1231,7 +1491,12 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { // Handle right mouse up events void view_rightMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1242,6 +1507,11 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { 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]] { gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1252,7 +1522,12 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { // Handle other mouse button down events void view_otherMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1262,7 +1537,12 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { // Handle other mouse button up events void view_otherMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1272,6 +1552,11 @@ 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]] { gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1332,94 +1617,44 @@ void view_mouseExited(id self, SEL _cmd, id event) { } } -//====================================================================// -// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) -// NSTouchPhase bitmask values -enum NSTouchPhase : NSUInteger { - NSTouchPhaseBegan = 1 << 0, - NSTouchPhaseMoved = 1 << 1, - NSTouchPhaseStationary = 1 << 2, - NSTouchPhaseEnded = 1 << 3, - NSTouchPhaseCancelled = 1 << 4, - NSTouchPhaseAny = ULONG_MAX -}; - -// Stable sequential touch ID map — maps identity pointer → assigned uint32_t ID -static std::unordered_map sTouchIDMap; -static uint32_t sNextTouchID = 0; - -/* - Iterate through Touches, updates and fires the callback. - Pass bRemove = true for Ended/Cancelled to clean up the ID map entry. -*/ -static void updateTouchData(id event, NSUInteger phase, - void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) -{ - if (!callback) return; - - // Get the current collection of touches (nil view = window coords) - id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); - if (!touchCollection) return; - - NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - id touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - - for (NSUInteger i = 0; i < count; ++i) { - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - - // normalizedPosition is NSPoint in [0,1] range on the trackpad surface - NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); - // deviceSize gives the physical trackpad size in points - NXSize devSize = ((NXSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); - - // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) - double x = normPos.x * devSize.width; - double y = (1.0 - normPos.y) * devSize.height; - - // Assign a stable sequential uint32_t ID same as iOS does - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - - auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); // I LOVE C++20 structured bindings - if (inserted) ++sNextTouchID; - uint32_t tid = it->second; - - // This is a trackpad touch event, so we don't have a sizeX/sizeY like on iOS. We'll just pass the device size as a proxy. - callback(tid, x, y, 1.0f, 1.0f, userData); - - // Clean up map entry once the touch has ended or been cancelled - if (bRemove) sTouchIDMap.erase(key); - } -} - -void view_touchesBegan(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesBegan(id self, SEL _cmd, id event) { + (void)_cmd; + + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseBegan, gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); } } -void view_touchesMoved(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesMoved(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseMoved, gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); } } -void view_touchesEnded(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesEnded(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseEnded, gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); } } -void view_touchesCancelled(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesCancelled(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseCancelled, gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); @@ -1440,6 +1675,14 @@ void view_tabletProximity(id self, SEL _cmd, id event) { } } +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletPoint(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet point events here + } +} + void view_updateTrackingAreas(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -1539,9 +1782,11 @@ Class createCustomOpenGLViewClass() { class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); - + class_addMethod(CustomViewClass, ObjectiveCSEL::pressureChangeSel, (IMP)view_pressureChange, kEventHandlerMethodTypeEncoding); + // Tablet / stylus event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); // Area tracking for Mouse entered/exited event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); @@ -2129,6 +2374,13 @@ extern "C" { unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); + // Enable touch events (NSTouch) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + + // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); + + } // Set up OpenGL context and make view first responder diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 41478828..ece47aec 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -9040,6 +9040,7 @@ namespace olc::host { // 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"; @@ -9131,11 +9132,13 @@ static constexpr const char* kInitWithRectSel = "initWithRect: // 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 = "touchesBegan:withEvent:"; -static constexpr const char* kTouchesMovedSel = "touchesMoved:withEvent:"; -static constexpr const char* kTouchesEndedSel = "touchesEnded:withEvent:"; -static constexpr const char* kTouchesCancelledSel = "touchesCancelled:withEvent:"; +static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; // NSTouch / NSSet data extraction selectors static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; @@ -9149,6 +9152,8 @@ static constexpr const char* kTouchCountSel = "count"; // 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"; // NSEvent tablet data extraction selectors static constexpr const char* kPenPressureSel = "pressure"; @@ -9178,6 +9183,7 @@ static constexpr const char* kConvertRectToBackingSel = "convertRectTo 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:"; @@ -9232,8 +9238,8 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; -// Type encoding for touch event methods: two id params (touches set + event) → "v@:@@" -static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@@"; +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; namespace ObjectiveCSEL { @@ -9314,7 +9320,9 @@ namespace ObjectiveCSEL { static SEL initWithRectSel = nullptr; // Touch event selectors - static SEL cgEventSel = nullptr; + static SEL setAcceptsTouchEventsSel = nullptr; + static SEL setWantsRestingTouchesSel = nullptr; + static SEL cgEventSel = nullptr; static SEL touchesBeganSel = nullptr; static SEL touchesMovedSel = nullptr; static SEL touchesEndedSel = nullptr; @@ -9336,6 +9344,8 @@ namespace ObjectiveCSEL { static SEL penIsEnteringProximitySel = nullptr; static SEL pointingDeviceTypeSel = nullptr; static SEL penTangentialPressureSel = nullptr; + static SEL pressureChangeSel = nullptr; + static SEL stageSel = nullptr; // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; @@ -9363,6 +9373,7 @@ namespace ObjectiveCSEL { static SEL displaySel = nullptr; static SEL CGLContextObjSel = nullptr; static SEL setValuesSel = nullptr; + static SEL backingScaleFactorSel = nullptr; // Extracting data from NSEvent objects selectors static SEL keyCodeSel = nullptr; @@ -9458,6 +9469,8 @@ namespace ObjectiveCSEL { 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); @@ -9480,6 +9493,8 @@ namespace ObjectiveCSEL { penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); + pressureChangeSel = sel_registerName(kPressureChangeSel); + stageSel = sel_registerName(kStageSel); // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); @@ -9507,6 +9522,7 @@ namespace ObjectiveCSEL { displaySel = sel_registerName(kDisplaySel); CGLContextObjSel = sel_registerName(kCGLContextObjSel); setValuesSel = sel_registerName(kSetValuesSel); + backingScaleFactorSel = sel_registerName(kBackingScaleFactorSel); // Extracting data from NSEvent objects selectors keyCodeSel = sel_registerName(kKeyCodeSel); @@ -9791,12 +9807,12 @@ struct Application { // 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 + 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 // Event callback function pointers with nullptr initialization void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; @@ -10112,7 +10128,7 @@ StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { StylusEventData data; id cgEvent = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); data.nTabletDeviceID = (uint32_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventDeviceID); data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); @@ -10141,6 +10157,240 @@ StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { return data; } +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + + +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; + +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; + +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; + +// 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); + + // 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; +}; + +// 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); + + // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) + data.nspLocation.x = normPos.x * screenFrame.width; + data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; + + // Get Device Size + data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); + // Approximate touch size based on pressure stage (macOS does not provide actual touch size) + data.nsTouchSize = cgsApproxTouchSize; + data.bIsActive = !bRemove; + + // Assign a stable sequential uint32_t IDs same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + data.touchID = tid; + sTouchIDEvents.emplace(key, data); + callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) + { + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); + } + + } + + } + + // A bit painful, but we need to check of any keys that have been removed as the touchleave event happened outside of our application + std::vector vFoundKeys; + std::vector vMissingKeys; + touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, NSTouchPhaseAny, nil); + + // If we have a touch collection and the phase is not cancelled, we can check for missing keys, otherwise we will just remove all keys from the map + if (touchCollection && phase != NSTouchPhaseCancelled) + { + count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + for (NSUInteger i = 0; i < count; ++i) { + + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + vFoundKeys.push_back(key); + + } + } + + // Find the missing keys and fire the touchEndedCallback for them + for(auto& [key, data] : sTouchIDEvents) { + + if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { + gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); + vMissingKeys.push_back(key); + } + + } + + // 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]] { + + // 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) + { + // stage returns: 0 = normal click, 1 = light press, 2 = force click + stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); + } + + // Map stage to normalized pressure values as requested + double pressure = toNormalizedPressure(ForceTouchPressure::Normal); + + switch (stage) { + case NSPressureStageLight: + pressure = toNormalizedPressure(ForceTouchPressure::Light); + break; + case NSPressureStageNormal: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + case NSPressureStageForce: + pressure = toNormalizedPressure(ForceTouchPressure::Force); + break; + default: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + } + + // now we need to work out the size + cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); + + for(auto& [key, data] : sTouchIDEvents) { + data.pressure = pressure; + data.nsTouchSize = cgsApproxTouchSize; + } + + } + } +} + // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings @@ -10157,7 +10407,7 @@ void view_mouseDown(id self, SEL _cmd, id event) { (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } } - else + else if (nPointerEventType == 1 || nPointerEventType == 2) { // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) gptrNSWindowEvents->bStylusInProximity = false; @@ -10189,7 +10439,7 @@ void view_mouseUp(id self, SEL _cmd, id event) { (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } } - else + else if(nPointerEventType == 1 || nPointerEventType == 2) { // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) gptrNSWindowEvents->bStylusInProximity = false; @@ -10220,7 +10470,7 @@ void view_mouseDragged(id self, SEL _cmd, id event) { gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } } - else + else if (nPointerEventType == 1 || nPointerEventType == 2) { // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) gptrNSWindowEvents->bStylusInProximity = false; @@ -10240,6 +10490,11 @@ void view_mouseDragged(id self, SEL _cmd, id event) { 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); @@ -10254,7 +10509,12 @@ 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); @@ -10264,7 +10524,12 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { // Handle right mouse up events void view_rightMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -10275,6 +10540,11 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { 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]] { gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -10285,7 +10555,12 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { // Handle other mouse button down events void view_otherMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -10295,7 +10570,12 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { // Handle other mouse button up events void view_otherMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -10305,6 +10585,11 @@ 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]] { gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -10365,94 +10650,44 @@ void view_mouseExited(id self, SEL _cmd, id event) { } } -//====================================================================// -// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) - -// NSTouchPhase bitmask values -enum NSTouchPhase : NSUInteger { - NSTouchPhaseBegan = 1 << 0, - NSTouchPhaseMoved = 1 << 1, - NSTouchPhaseStationary = 1 << 2, - NSTouchPhaseEnded = 1 << 3, - NSTouchPhaseCancelled = 1 << 4, - NSTouchPhaseAny = ULONG_MAX -}; - -// Stable sequential touch ID map — maps identity pointer → assigned uint32_t ID -static std::unordered_map sTouchIDMap; -static uint32_t sNextTouchID = 0; - -/* - Iterate through Touches, updates and fires the callback. - Pass bRemove = true for Ended/Cancelled to clean up the ID map entry. -*/ -static void updateTouchData(id event, NSUInteger phase, - void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) -{ - if (!callback) return; - - // Get the current collection of touches (nil view = window coords) - id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); - if (!touchCollection) return; - - NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - id touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - - for (NSUInteger i = 0; i < count; ++i) { - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - - // normalizedPosition is NSPoint in [0,1] range on the trackpad surface - NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); - // deviceSize gives the physical trackpad size in points - NXSize devSize = ((NXSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); - - // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) - double x = normPos.x * devSize.width; - double y = (1.0 - normPos.y) * devSize.height; - - // Assign a stable sequential uint32_t ID same as iOS does - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - - auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); // I LOVE C++20 structured bindings - if (inserted) ++sNextTouchID; - uint32_t tid = it->second; - - // This is a trackpad touch event, so we don't have a sizeX/sizeY like on iOS. We'll just pass the device size as a proxy. - callback(tid, x, y, 1.0f, 1.0f, userData); - - // Clean up map entry once the touch has ended or been cancelled - if (bRemove) sTouchIDMap.erase(key); - } -} -void view_touchesBegan(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesBegan(id self, SEL _cmd, id event) { + (void)_cmd; + + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseBegan, gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); } } -void view_touchesMoved(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesMoved(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseMoved, gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); } } -void view_touchesEnded(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesEnded(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseEnded, gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); } } -void view_touchesCancelled(id self, SEL _cmd, id touches, id event) { - (void)self;(void)_cmd;(void)touches; +void view_touchesCancelled(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { updateTouchData(event, NSTouchPhaseCancelled, gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); @@ -10478,15 +10713,6 @@ void view_tabletPoint(id self, SEL _cmd, id event) { (void)self;(void)_cmd;(void)event; if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { // Handle tablet point events here - //StylusEventData data = extractStylusEventData(event, 1); // 1 = Pen Event - //bool bPressed = data.buttonMask & 1; // Check if the first button is pressed - //bool bReleased = !bPressed; - //gptrNSWindowEvents->stylusCallback( - // data.nTabletDeviceID, - // data.nspLocation.x, data.nspLocation.y, - // data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - // bPressed, bReleased, true, - // gptrNSWindowEvents->stylusUserData); } } @@ -10589,7 +10815,8 @@ Class createCustomOpenGLViewClass() { class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); - + class_addMethod(CustomViewClass, ObjectiveCSEL::pressureChangeSel, (IMP)view_pressureChange, kEventHandlerMethodTypeEncoding); + // Tablet / stylus event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); @@ -11180,6 +11407,13 @@ extern "C" { unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); + // Enable touch events (NSTouch) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + + // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); + + } // Set up OpenGL context and make view first responder From f603e15d955bea07dafa406cb5bb7806d9aa8957 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 3 Jul 2026 08:09:57 -0500 Subject: [PATCH 21/28] Fix crash if no touch device. --- dev/src/host_lin_wayland.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 4c016d66..831cef3d 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -197,7 +197,9 @@ namespace olc::host { wp_pointer_warp_v1_destroy(pointer_warp); } - wl_touch_destroy(touch); + if(touch) { + wl_touch_destroy(touch); + } wl_keyboard_destroy(keyboard); wl_pointer_destroy(pointer); wl_seat_destroy(seat); From 40a5c8498f302842a66acb79b6a72ffbabdfb52e Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:11:38 -0500 Subject: [PATCH 22/28] Guess we're doing x11 here too. --- dev/src/host_lin_x11.cpp | 96 +++++++++++++++++++++++++++++++++++ dev/src/host_lin_x11.h | 5 ++ examples/CMakeLists.txt | 1 + olcPixelGameEngine3.h | 105 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 206 insertions(+), 1 deletion(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 6b1b715e..cb6866c5 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -10,6 +10,9 @@ namespace olc::host olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + // If supported, enable receiving touch events + enableTouch(olc_Display, olc_WindowRoot); + if(XkbQueryExtension(olc_Display, nullptr, &xkbEventBase, &xkbErrorBase, nullptr, nullptr)) { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); @@ -283,6 +286,16 @@ namespace olc::host pge_window->olc_OnWindowClose(); } } + else if (xev.type == GenericEvent) + { + X11::XGenericEventCookie& xgec = xev.xcookie; + + if(xgec.extension == xinput_extension_code && X11::XGetEventData(olc_Display, &xgec)) { + // Should be one of our touch events + handleTouchEvent(static_cast(xgec.data)); + X11::XFreeEventData(olc_Display, &xgec); + } + } } } @@ -544,5 +557,88 @@ namespace olc::host return true; } + + void Host_Linux_X11::enableTouch(X11::Display* display, X11::Window window) + { + // Check if we have the XInput extension + int extension_code; + int ev; + int err; + + // If we have the extension, check for version 2.3 or higher + if (X11::XQueryExtension(display, "XInputExtension", &extension_code, &ev, &err)) { + int major{2}; + int minor{3}; + + if(X11::XIQueryVersion(display, &major, &minor) != Success) { + // Version is incorrect, do not select touch events + return; + } + } else { + // Do not have the extension, do not select touch events + return; + } + + // If we get down here, we have the extension and version is correct + xinput_extension_code = extension_code; + + X11::XIEventMask mask{}; + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = static_cast(std::calloc(mask.mask_len, sizeof(char))); + + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + X11::XISelectEvents(display, window, &mask, 1); + + X11::XSync(display, False); + std::free(mask.mask); + } + + void Host_Linux_X11::handleTouchEvent(X11::XIDeviceEvent* event) + { + auto itr_child = mapX11Window2PTR.find(event->child); + if(itr_child == mapX11Window2PTR.end()) { + + return; + } + + olc::Window* pge_window = itr_child->second; + + switch(event->evtype) { + case XI_TouchBegin: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + true, + false, + olc::vf2d{1.0f, .0f} + ); + break; + case XI_TouchUpdate: + { + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + false, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + case XI_TouchEnd: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + true, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + } + } //! END IMPLEMENTATION diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 0f7c343a..832f268f 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -22,6 +22,7 @@ namespace X11 #include #include #include +#include #include #undef None constexpr int None = 0L; @@ -37,6 +38,8 @@ namespace olc::host X11::XVisualInfo* olc_VisualInfo; X11::Colormap olc_ColourMap; X11::XSetWindowAttributes olc_SetWindowAttribs; + + int xinput_extension_code{}; public: Host_Linux_X11(); @@ -68,6 +71,8 @@ namespace olc::host bool OnApplicationEnd() override; private: + void enableTouch(X11::Display* display, X11::Window window); + void handleTouchEvent(X11::XIDeviceEvent* event); std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; std::atomic systemActive {true}; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ca84eda0..9e351a6e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -124,6 +124,7 @@ foreach(source ${SOURCES}) target_link_libraries(${EXE_NAME} PRIVATE GL) target_link_libraries(${EXE_NAME} PRIVATE pthread) target_link_libraries(${EXE_NAME} PRIVATE X11) + target_link_libraries(${EXE_NAME} PRIVATE Xi) endif() endif() diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 765568b5..13458260 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5782,6 +5782,7 @@ namespace X11 #include #include #include +#include #include #undef None constexpr int None = 0L; @@ -5797,6 +5798,8 @@ namespace olc::host X11::XVisualInfo* olc_VisualInfo; X11::Colormap olc_ColourMap; X11::XSetWindowAttributes olc_SetWindowAttribs; + + int xinput_extension_code{}; public: Host_Linux_X11(); @@ -5828,6 +5831,8 @@ namespace olc::host bool OnApplicationEnd() override; private: + void enableTouch(X11::Display* display, X11::Window window); + void handleTouchEvent(X11::XIDeviceEvent* event); std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; std::atomic systemActive {true}; @@ -11284,6 +11289,9 @@ namespace olc::host olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + // If supported, enable receiving touch events + enableTouch(olc_Display, olc_WindowRoot); + if(XkbQueryExtension(olc_Display, nullptr, &xkbEventBase, &xkbErrorBase, nullptr, nullptr)) { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); @@ -11557,6 +11565,16 @@ namespace olc::host pge_window->olc_OnWindowClose(); } } + else if (xev.type == GenericEvent) + { + X11::XGenericEventCookie& xgec = xev.xcookie; + + if(xgec.extension == xinput_extension_code && X11::XGetEventData(olc_Display, &xgec)) { + // Should be one of our touch events + handleTouchEvent(static_cast(xgec.data)); + X11::XFreeEventData(olc_Display, &xgec); + } + } } } @@ -11818,6 +11836,89 @@ namespace olc::host return true; } + + void Host_Linux_X11::enableTouch(X11::Display* display, X11::Window window) + { + // Check if we have the XInput extension + int extension_code; + int ev; + int err; + + // If we have the extension, check for version 2.3 or higher + if (X11::XQueryExtension(display, "XInputExtension", &extension_code, &ev, &err)) { + int major{2}; + int minor{3}; + + if(X11::XIQueryVersion(display, &major, &minor) != Success) { + // Version is incorrect, do not select touch events + return; + } + } else { + // Do not have the extension, do not select touch events + return; + } + + // If we get down here, we have the extension and version is correct + xinput_extension_code = extension_code; + + X11::XIEventMask mask{}; + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = static_cast(std::calloc(mask.mask_len, sizeof(char))); + + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + X11::XISelectEvents(display, window, &mask, 1); + + X11::XSync(display, False); + std::free(mask.mask); + } + + void Host_Linux_X11::handleTouchEvent(X11::XIDeviceEvent* event) + { + auto itr_child = mapX11Window2PTR.find(event->child); + if(itr_child == mapX11Window2PTR.end()) { + + return; + } + + olc::Window* pge_window = itr_child->second; + + switch(event->evtype) { + case XI_TouchBegin: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + true, + false, + olc::vf2d{1.0f, .0f} + ); + break; + case XI_TouchUpdate: + { + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + false, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + case XI_TouchEnd: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + true, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + } + } #endif @@ -12017,7 +12118,9 @@ namespace olc::host { wp_pointer_warp_v1_destroy(pointer_warp); } - wl_touch_destroy(touch); + if(touch) { + wl_touch_destroy(touch); + } wl_keyboard_destroy(keyboard); wl_pointer_destroy(pointer); wl_seat_destroy(seat); From f5cfa0a4dbe561241c1a416186bf0096981113d3 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:17:35 -0500 Subject: [PATCH 23/28] Touch for emscripten too I guess. --- dev/src/host_web_emscripten.cpp | 47 ++++++++++++++++++++------------- olcPixelGameEngine3.h | 47 ++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 98d37119..ab20ff6f 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -209,6 +209,7 @@ namespace olc::host emscripten_set_touchstart_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchmove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchend_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + emscripten_set_touchcancel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); // Canvas Focus Callbacks emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); @@ -495,31 +496,41 @@ namespace olc::host return EM_TRUE; } - //TY Bispoo EM_BOOL Host_Web_Emscripten::touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) { - // TODO: Implement touch more effectively. - // For now, emulate single pointer mouse. - CallbackData* pCallbackData = reinterpret_cast(userData); - // Move - if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) - { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - } - - // Start - if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + for(int i = 0; i < e->numTouches; ++i) { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - olc_OnMouseButton(pCallbackData->pWindow, 0, true); + const EmscriptenTouchPoint& touch = e->touches[i]; + + if(touch.isChanged) { + bool is_start = (eventType == EMSCRIPTEN_EVENT_TOUCHSTART); + bool is_end = ((eventType == EMSCRIPTEN_EVENT_TOUCHEND) || (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)); + pCallbackData->pWindow->olc_OnTouch( + touch.identifier, + olc::vf2d(touch.targetX, touch.targetY), + is_start, + is_end, + olc::vf2d(1.0f, 1.0f) + ); + } } - // End - if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) - { - olc_OnMouseButton(pCallbackData->pWindow, 0, false); + // Emscripten seems to sometimes "leak" touches and not report them as ending. Iterate through the touches in the system and end + // any touches that are no longer being reported + for(const auto touch_id : pCallbackData->pWindow->touch.GetTouchIDs()) { + // if this touch is not reported anymore + if (std::find_if(e->touches, e->touches + e->numTouches, [=](const EmscriptenTouchPoint& x){return x.identifier == touch_id;}) == e->touches + e->numTouches) { + const auto& touch = pCallbackData->pWindow->touch.GetTouch(touch_id); + pCallbackData->pWindow->olc_OnTouch( + touch_id, + touch.position, + false, + true, + touch.size + ); + } } return EM_TRUE; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 13458260..8852896b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -13212,6 +13212,7 @@ namespace olc::host emscripten_set_touchstart_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchmove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchend_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + emscripten_set_touchcancel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); // Canvas Focus Callbacks emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); @@ -13498,31 +13499,41 @@ namespace olc::host return EM_TRUE; } - //TY Bispoo EM_BOOL Host_Web_Emscripten::touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) { - // TODO: Implement touch more effectively. - // For now, emulate single pointer mouse. - CallbackData* pCallbackData = reinterpret_cast(userData); - // Move - if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) - { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - } - - // Start - if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + for(int i = 0; i < e->numTouches; ++i) { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - olc_OnMouseButton(pCallbackData->pWindow, 0, true); + const EmscriptenTouchPoint& touch = e->touches[i]; + + if(touch.isChanged) { + bool is_start = (eventType == EMSCRIPTEN_EVENT_TOUCHSTART); + bool is_end = ((eventType == EMSCRIPTEN_EVENT_TOUCHEND) || (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)); + pCallbackData->pWindow->olc_OnTouch( + touch.identifier, + olc::vf2d(touch.targetX, touch.targetY), + is_start, + is_end, + olc::vf2d(1.0f, 1.0f) + ); + } } - // End - if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) - { - olc_OnMouseButton(pCallbackData->pWindow, 0, false); + // Emscripten seems to sometimes "leak" touches and not report them as ending. Iterate through the touches in the system and end + // any touches that are no longer being reported + for(const auto touch_id : pCallbackData->pWindow->touch.GetTouchIDs()) { + // if this touch is not reported anymore + if (std::find_if(e->touches, e->touches + e->numTouches, [=](const EmscriptenTouchPoint& x){return x.identifier == touch_id;}) == e->touches + e->numTouches) { + const auto& touch = pCallbackData->pWindow->touch.GetTouch(touch_id); + pCallbackData->pWindow->olc_OnTouch( + touch_id, + touch.position, + false, + true, + touch.size + ); + } } return EM_TRUE; From 419b9921fb0e982055fe046d0c3c976c633d0de3 Mon Sep 17 00:00:00 2001 From: DCubix Date: Tue, 7 Jul 2026 00:04:11 -0400 Subject: [PATCH 24/28] Implmement stylus/eraser and finger touch events (multitouch) for Android and improve mouse handling --- dev/src/host_android.cpp | 212 ++++++++++++++++++++++++++++++++----- dev/src/host_android.h | 4 + olcPixelGameEngine3.h | 221 ++++++++++++++++++++++++++++++++++----- 3 files changed, 379 insertions(+), 58 deletions(-) diff --git a/dev/src/host_android.cpp b/dev/src/host_android.cpp index 45e55e5b..1e2c7acc 100644 --- a/dev/src/host_android.cpp +++ b/dev/src/host_android.cpp @@ -279,33 +279,20 @@ namespace olc::host auto type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) { - pgeWindow->olc_OnMouseMove({ - static_cast(AMotionEvent_getX(event, 0)), - static_cast(AMotionEvent_getY(event, 0)), - }); - - auto action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; - - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - pgeWindow->olc_OnMouseButton(0, true); // Left button - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - pgeWindow->olc_OnMouseButton(0, false); // Left button - break; - case AMOTION_EVENT_AXIS_WHEEL: - // Handle mouse wheel - { - float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, 0); - if (vScroll != 0.0f) { - pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); - } - } - break; - default: - break; + auto actionRaw = AMotionEvent_getAction(event); + auto actionMasked = actionRaw & AMOTION_EVENT_ACTION_MASK; + auto pointerIndex = (actionRaw & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + auto pointerCount = AMotionEvent_getPointerCount(event); + + for (size_t pi = 0; pi < pointerCount; pi++) { + auto toolType = AMotionEvent_getToolType(event, pi); + switch (toolType) { + case AMOTION_EVENT_TOOL_TYPE_MOUSE: handleMouse(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_ERASER: + case AMOTION_EVENT_TOOL_TYPE_STYLUS: handleStylus(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_FINGER: handleFinger(pi, actionMasked, event); break; + default: break; + } } return 1; @@ -595,8 +582,175 @@ namespace olc::host return content; } - - void Host_Android::PollEvents(const std::function& funcContinue, bool bBlocking) + + void Host_Android::handleMouse(size_t id, int32_t action, AInputEvent *event) + { + auto fnGetButton = [&event]() { + int32_t buttons = AMotionEvent_getButtonState(event); + if (buttons & AMOTION_EVENT_BUTTON_PRIMARY) return 0; + if (buttons & AMOTION_EVENT_BUTTON_SECONDARY) return 1; + if (buttons & AMOTION_EVENT_BUTTON_TERTIARY) return 2; + if (buttons & AMOTION_EVENT_BUTTON_BACK) return 3; + if (buttons & AMOTION_EVENT_BUTTON_FORWARD) return 4; + return -1; + }; + + pgeWindow->olc_OnMouseMove({ + static_cast(AMotionEvent_getX(event, id)), + static_cast(AMotionEvent_getY(event, id)), + }); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnMouseButton(fnGetButton(), true); + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnMouseButton(fnGetButton(), false); + break; + case AMOTION_EVENT_AXIS_WHEEL: + { + float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, id); + if (vScroll != 0.0f) { + pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); + } + } + break; + default: + break; + } + } + + void Host_Android::handleStylus(size_t id, int32_t action, AInputEvent *event) + { + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + float ellipseX = AMotionEvent_getToolMajor(event, id); + float ellipseY = AMotionEvent_getToolMinor(event, id); + + // radians, 0 = vertical, positive = clockwise tilt in the plane of the screen + float orientation = AMotionEvent_getOrientation(event, id); + + // angle of the stylus away from perpendicular to the screen, radians, 0 = straight up + float axisTilt = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, id); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + default: + break; + } + } + + void Host_Android::handleFinger(size_t id, int32_t action, AInputEvent *event) + { + float size = AMotionEvent_getSize(event, id); + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { size, size }, + false, + pressure + ); + break; + default: + break; + } + } + + void Host_Android::PollEvents(const std::function &funcContinue, bool bBlocking) { while (funcContinue()) { int events; diff --git a/dev/src/host_android.h b/dev/src/host_android.h index 19a08cac..779c5a1a 100644 --- a/dev/src/host_android.h +++ b/dev/src/host_android.h @@ -82,6 +82,10 @@ namespace olc::host std::atomic initialized{false}, systemActive{false}; bool shiftOn = false; + void handleMouse(size_t id, int32_t action, AInputEvent* event); + void handleStylus(size_t id, int32_t action, AInputEvent* event); + void handleFinger(size_t id, int32_t action, AInputEvent* event); + void PollEvents( const std::function& funcContinue, bool bBlocking = false diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1a64b652..59055fd4 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6247,6 +6247,10 @@ namespace olc::host std::atomic initialized{false}, systemActive{false}; bool shiftOn = false; + void handleMouse(size_t id, int32_t action, AInputEvent* event); + void handleStylus(size_t id, int32_t action, AInputEvent* event); + void handleFinger(size_t id, int32_t action, AInputEvent* event); + void PollEvents( const std::function& funcContinue, bool bBlocking = false @@ -13623,33 +13627,20 @@ namespace olc::host auto type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) { - pgeWindow->olc_OnMouseMove({ - static_cast(AMotionEvent_getX(event, 0)), - static_cast(AMotionEvent_getY(event, 0)), - }); - - auto action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; - - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - pgeWindow->olc_OnMouseButton(0, true); // Left button - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - pgeWindow->olc_OnMouseButton(0, false); // Left button - break; - case AMOTION_EVENT_AXIS_WHEEL: - // Handle mouse wheel - { - float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, 0); - if (vScroll != 0.0f) { - pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); - } - } - break; - default: - break; + auto actionRaw = AMotionEvent_getAction(event); + auto actionMasked = actionRaw & AMOTION_EVENT_ACTION_MASK; + auto pointerIndex = (actionRaw & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + auto pointerCount = AMotionEvent_getPointerCount(event); + + for (size_t pi = 0; pi < pointerCount; pi++) { + auto toolType = AMotionEvent_getToolType(event, pi); + switch (toolType) { + case AMOTION_EVENT_TOOL_TYPE_MOUSE: handleMouse(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_ERASER: + case AMOTION_EVENT_TOOL_TYPE_STYLUS: handleStylus(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_FINGER: handleFinger(pi, actionMasked, event); break; + default: break; + } } return 1; @@ -13939,8 +13930,175 @@ namespace olc::host return content; } - - void Host_Android::PollEvents(const std::function& funcContinue, bool bBlocking) + + void Host_Android::handleMouse(size_t id, int32_t action, AInputEvent *event) + { + auto fnGetButton = [&event]() { + int32_t buttons = AMotionEvent_getButtonState(event); + if (buttons & AMOTION_EVENT_BUTTON_PRIMARY) return 0; + if (buttons & AMOTION_EVENT_BUTTON_SECONDARY) return 1; + if (buttons & AMOTION_EVENT_BUTTON_TERTIARY) return 2; + if (buttons & AMOTION_EVENT_BUTTON_BACK) return 3; + if (buttons & AMOTION_EVENT_BUTTON_FORWARD) return 4; + return -1; + }; + + pgeWindow->olc_OnMouseMove({ + static_cast(AMotionEvent_getX(event, id)), + static_cast(AMotionEvent_getY(event, id)), + }); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnMouseButton(fnGetButton(), true); + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnMouseButton(fnGetButton(), false); + break; + case AMOTION_EVENT_AXIS_WHEEL: + { + float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, id); + if (vScroll != 0.0f) { + pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); + } + } + break; + default: + break; + } + } + + void Host_Android::handleStylus(size_t id, int32_t action, AInputEvent *event) + { + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + float ellipseX = AMotionEvent_getToolMajor(event, id); + float ellipseY = AMotionEvent_getToolMinor(event, id); + + // radians, 0 = vertical, positive = clockwise tilt in the plane of the screen + float orientation = AMotionEvent_getOrientation(event, id); + + // angle of the stylus away from perpendicular to the screen, radians, 0 = straight up + float axisTilt = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, id); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + default: + break; + } + } + + void Host_Android::handleFinger(size_t id, int32_t action, AInputEvent *event) + { + float size = AMotionEvent_getSize(event, id); + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { size, size }, + false, + pressure + ); + break; + default: + break; + } + } + + void Host_Android::PollEvents(const std::function &funcContinue, bool bBlocking) { while (funcContinue()) { int events; @@ -15930,6 +16088,11 @@ void main() gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); } + else if (task.blendmode == olc::BlendMode::None) + { + gl.glDisable(GL_BLEND); + } + //gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); From 3c6abf3701c91107075ed32b478e4bfe15a10b3e Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:44:34 +0100 Subject: [PATCH 25/28] Explored private method and friendship, but lack of friendship inheritance just makes it messy. Went with more obscure method name and some hand holding comments --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +-- dev/src/core.cpp | 10 ++-- dev/src/core.h | 3 + dev/src/draw.cpp | 2 + dev/src/gpu_opengl33.cpp | 2 +- dev/src/image.cpp | 2 +- dev/src/image.h | 14 ++++- dev/src/imload_android.cpp | 4 +- dev/src/imload_lib_png.cpp | 2 +- dev/src/imload_macos.cpp | 4 +- dev/src/imload_stb_image.cpp | 6 +- dev/src/imload_wingdi.cpp | 2 +- olcPixelGameEngine3.h | 56 +++++++++++++------ 13 files changed, 77 insertions(+), 38 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 0a32e182..2e2a6919 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -22,8 +22,8 @@ true true - true - true + false + false true @@ -44,8 +44,8 @@ true - false - false + true + true true diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 092d8bdb..3710d69f 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -223,14 +223,14 @@ namespace olc bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image - if (!image.Create(size, cfg)) + if (!image.CreateNoGPU(size, cfg)) return false; // Create GPU Image auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -249,7 +249,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -271,7 +271,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -300,7 +300,7 @@ namespace olc } // Free any cpu memory associated with image - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); } void PGEWindow::LinkToRenderer(olc::gpu::Renderer* gpu) diff --git a/dev/src/core.h b/dev/src/core.h index 809a37de..231a0d4b 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -93,6 +93,9 @@ namespace olc public: // olc::Image Handling + + // These are the preferred methods to create olc::Image objects + // Create an image resource bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); // Create an image resource based on an image file asset on disk diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 4cf05a53..56fb76cc 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -295,11 +295,13 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const olc GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { + olc_IgnoreUnused(structure); return TaskDrawLine(vPoints, vColours, tint, false, true); } GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { + olc_IgnoreUnused(structure); return TaskDrawLine(vPoints, colour, tint, false, true); } diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 8877890a..66bc14bc 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -478,7 +478,7 @@ void main() // Create a null-texture so sampler doesnt fail. We don't have some of the core's helper // functions here, so we construct it manually - imgBlank.Create({ 1,1 }); + imgBlank.CreateNoGPU({ 1,1 }); imgBlank.SetGPUID(CreateTexture(imgBlank.Size())); imgBlank.BindCPU(); imgBlank.Pixel({ 0,0 }) = olc::Colour::WHITE; diff --git a/dev/src/image.cpp b/dev/src/image.cpp index b701c502..beadcc5e 100644 --- a/dev/src/image.cpp +++ b/dev/src/image.cpp @@ -11,7 +11,7 @@ namespace olc { }*/ - bool Image::Create(const olc::vi2d& size, const ImageConfig& cfg) + bool Image::CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg) { dimensions = size; config = cfg; diff --git a/dev/src/image.h b/dev/src/image.h index d03181b5..cab7215b 100644 --- a/dev/src/image.h +++ b/dev/src/image.h @@ -31,6 +31,13 @@ namespace olc struct ImageRegion; + class PGEWindow; + + namespace imload + { + class ImageLoader; + } + class Image { public: @@ -39,7 +46,12 @@ namespace olc virtual ~Image() = default; public: - bool Create(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + // Creates nothing but an array of pixels in system memory. Normal users + // should never need to call this method. If you want to construct an + // olc::Image object, use factory methods in olc::PGEWindow + // CreateImage(...) + bool CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + public: // Returns size (x, y) in pixels diff --git a/dev/src/imload_android.cpp b/dev/src/imload_android.cpp index 3226115c..ee40b34e 100644 --- a/dev/src/imload_android.cpp +++ b/dev/src/imload_android.cpp @@ -39,7 +39,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); @@ -95,7 +95,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); diff --git a/dev/src/imload_lib_png.cpp b/dev/src/imload_lib_png.cpp index 5fadfefa..8bb10f4f 100644 --- a/dev/src/imload_lib_png.cpp +++ b/dev/src/imload_lib_png.cpp @@ -94,7 +94,7 @@ namespace olc::imload png_read_info(png, info); png_byte color_type; png_byte bit_depth; - image.Create( + image.CreateNoGPU( { static_cast(png_get_image_width(png, info)), static_cast(png_get_image_height(png, info)) diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index 3136bc73..6fb551a1 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -33,7 +33,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -73,7 +73,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } diff --git a/dev/src/imload_stb_image.cpp b/dev/src/imload_stb_image.cpp index 39ff2baa..85e61c9e 100644 --- a/dev/src/imload_stb_image.cpp +++ b/dev/src/imload_stb_image.cpp @@ -31,7 +31,7 @@ namespace olc::imload return false; } - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), bytes, width * height * 4); delete[] bytes; @@ -48,7 +48,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; @@ -65,7 +65,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; diff --git a/dev/src/imload_wingdi.cpp b/dev/src/imload_wingdi.cpp index b947721c..e6c57512 100644 --- a/dev/src/imload_wingdi.cpp +++ b/dev/src/imload_wingdi.cpp @@ -95,7 +95,7 @@ namespace olc::imload bool ImageLoader_WinGDI::DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp) { // Need to swizzle each pixel... - image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); + image.CreateNoGPU(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); for (int y = 0; y < image.Size().y; y++) for (int x = 0; x < image.Size().x; x++) { diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1a64b652..19e53c7b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2251,6 +2251,13 @@ namespace olc struct ImageRegion; + class PGEWindow; + + namespace imload + { + class ImageLoader; + } + class Image { public: @@ -2259,7 +2266,12 @@ namespace olc virtual ~Image() = default; public: - bool Create(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + // Creates nothing but an array of pixels in system memory. Normal users + // should never need to call this method. If you want to construct an + // olc::Image object, use factory methods in olc::PGEWindow + // CreateImage(...) + bool CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + public: // Returns size (x, y) in pixels @@ -4207,6 +4219,9 @@ namespace olc public: // olc::Image Handling + + // These are the preferred methods to create olc::Image objects + // Create an image resource bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); // Create an image resource based on an image file asset on disk @@ -15227,7 +15242,7 @@ void main() // Create a null-texture so sampler doesnt fail. We don't have some of the core's helper // functions here, so we construct it manually - imgBlank.Create({ 1,1 }); + imgBlank.CreateNoGPU({ 1,1 }); imgBlank.SetGPUID(CreateTexture(imgBlank.Size())); imgBlank.BindCPU(); imgBlank.Pixel({ 0,0 }) = olc::Colour::WHITE; @@ -15930,6 +15945,11 @@ void main() gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); } + else if (task.blendmode == olc::BlendMode::None) + { + gl.glDisable(GL_BLEND); + } + //gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -16416,11 +16436,13 @@ GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const olc GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { + olc_IgnoreUnused(structure); return TaskDrawLine(vPoints, vColours, tint, false, true); } GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { + olc_IgnoreUnused(structure); return TaskDrawLine(vPoints, colour, tint, false, true); } @@ -18111,14 +18133,14 @@ namespace olc bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image - if (!image.Create(size, cfg)) + if (!image.CreateNoGPU(size, cfg)) return false; // Create GPU Image auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -18137,7 +18159,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -18159,7 +18181,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -18188,7 +18210,7 @@ namespace olc } // Free any cpu memory associated with image - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); } void PGEWindow::LinkToRenderer(olc::gpu::Renderer* gpu) @@ -18606,7 +18628,7 @@ namespace olc { }*/ - bool Image::Create(const olc::vi2d& size, const ImageConfig& cfg) + bool Image::CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg) { dimensions = size; config = cfg; @@ -19425,7 +19447,7 @@ namespace olc::imload bool ImageLoader_WinGDI::DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp) { // Need to swizzle each pixel... - image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); + image.CreateNoGPU(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); for (int y = 0; y < image.Size().y; y++) for (int x = 0; x < image.Size().x; x++) { @@ -19470,7 +19492,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -19510,7 +19532,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -19636,7 +19658,7 @@ namespace olc::imload png_read_info(png, info); png_byte color_type; png_byte bit_depth; - image.Create( + image.CreateNoGPU( { static_cast(png_get_image_width(png, info)), static_cast(png_get_image_height(png, info)) @@ -19716,7 +19738,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); @@ -19772,7 +19794,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); @@ -19856,7 +19878,7 @@ namespace olc::imload return false; } - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), bytes, width * height * 4); delete[] bytes; @@ -19873,7 +19895,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; @@ -19890,7 +19912,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; From 28338927ff928300f41dd5d8fb14b506b7b49c6c Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 23 Aug 2026 10:46:56 +0100 Subject: [PATCH 26/28] Cosmetic fix, but good enough --- dev/src/core.cpp | 1 + dev/src/host_win_winapi.cpp | 6 +++++- dev/src/host_win_winapi.h | 6 +++++- olcPixelGameEngine3.h | 19 +++++++++++++------ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 3710d69f..8b39c45a 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -175,6 +175,7 @@ namespace olc // fit within the window client area float fAspectScreen = float(GetScreen().Size().x) / float(GetScreen().Size().y); + vViewSize.x = (int32_t)vWindowSize.x; vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 3b84336f..8340cc54 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -306,7 +306,11 @@ namespace olc::host // Keep client size as requested RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - int width = rWndRect.right - rWndRect.left; + + // +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; // Create the actual OS window, return a handle diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 8a94d8c4..cbd8c299 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -33,7 +33,11 @@ #ifdef HAVE_MSMF #define _WIN32_WINNT 0x0600 // Windows Vista #else - #define _WIN32_WINNT 0x0603 // Windows 8.1 + //#define _WIN32_WINNT 0x0603 // Windows 8.1 + + // Windows 10 Minimum + #define WINVER 0x0A00 + #define _WIN32_WINNT 0x0A00 #endif #endif diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 04e0e7cc..9c0d5024 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4439,7 +4439,11 @@ namespace olc::host #ifdef HAVE_MSMF #define _WIN32_WINNT 0x0600 // Windows Vista #else - #define _WIN32_WINNT 0x0603 // Windows 8.1 + //#define _WIN32_WINNT 0x0603 // Windows 8.1 + + // Windows 10 Minimum + #define WINVER 0x0A00 + #define _WIN32_WINNT 0x0A00 #endif #endif @@ -4625,8 +4629,7 @@ extern "C" { typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); - typedef void (*StylusEventCallback) (uint32_t touchID, double x, double y, - float pressure, float rotation, + typedef void (*StylusEventCallback) (uint32_t touchID, double x, double y,float pressure, float rotation, float tiltX, float tiltY, bool bPress, bool bRelease, bool bIsStylus, void* userData); @@ -5627,8 +5630,7 @@ namespace olc { eventHandler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); } - static void stylusCallback(uint32_t touchID, double x, double y, - float pressure, float rotation, float tiltX, float tiltY, + static 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) { auto* eventHandler = static_cast(userData); @@ -7761,7 +7763,11 @@ namespace olc::host // Keep client size as requested RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - int width = rWndRect.right - rWndRect.left; + + // +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; // Create the actual OS window, return a handle @@ -19350,6 +19356,7 @@ namespace olc // fit within the window client area float fAspectScreen = float(GetScreen().Size().x) / float(GetScreen().Size().y); + vViewSize.x = (int32_t)vWindowSize.x; vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); From 383c752d29157efde5e8f381f06b40ca3110e0be Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:50:19 +0100 Subject: [PATCH 27/28] getting things ready for going public --- LICENCE.md | 31 +++++++++ README.md | 109 +++++++++++++++++++++++++++++++ dev/msvc/olcPGE3.vcxproj | 7 ++ dev/msvc/olcPGE3.vcxproj.filters | 16 +++++ dev/src/sh_template.h | 14 ++-- 5 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 LICENCE.md diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 00000000..1d5c073a --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,31 @@ +# License (OLC-3) + +Copyright 2018-2026 OneLoneCoder.com + +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. \ No newline at end of file diff --git a/README.md b/README.md index 643a3d9e..c7363ef7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,113 @@ +

+ +

+ +# Pre-Launch BETA Testing +`olcPixelGameEngine v3.00 Beta B` is currently in a Beta testing phase, and has been made available to those that may want to experiment during olc::CodeJam2026. As such, this repo does not represent the final form of olcPixelGameEngine3, and presumably feedback from the Jam may change a few things prior to the official launch later this year. + +# olcPixelGameEngine 3 (olcPGE3) +This is the official distribution of olcPixelGameEngine3, the successor to the popular [olcPixelGameEngine2](https://github.com/OneLoneCoder/olcPixelGameEngine/tree/master). + +Purposes + * Game Development + * Tool Development + * Visualisation + * Real-Time Interactive Applications + * Rapid Idea Prototyping + * Kiosks and Browsers + * Cross Platform Development + * Cohesive User Experience + * Algorithm Study + * Classroom Exercises + * Having Fun! + +Easy To Use + * Single Header File (as always) + * CMAKE optional but provided + * Entire project source for the curious + +Capabilities + * 2D Rendering + * 3D Rendering + * Batch Rendering + * Custom GPU Shaders + * Vector / Matrix Types + * Polygon Construction + * 2D Animation Package + * 2D Camera Control + * 2D Geometry Analysis & Interrogation + * 2D Quad-Tree Data Structure + * Flexible Game Creation + +Tested On Platforms + * Windows + * Linux X11/Wayland + * MacOS + * Emscripten + * Android + +Input Features + * Regional Keyboard + * Mouse + * Touch + * Stylus / Pen + +Extendable + * Window Hooks + * System Hooks + + +# Err.. I have questions! +* Is it compatible with olcPGE2? I've done loads of stuff in that. + * No. PGE3 has built upon the years of development and community contributions and has outgrown the interfaces that olcPGE2 provided. They are however very similar, so if you have familiarity with PGE2, you should easily get going with PGE3. +* Are there ports to Rust, C# or Java? + * No. Over the past years PGE2 was ported to other environments only by fantatsic community contributors. The official release of PGE3 targets C++20. +* Have you used AI to make this? + * No. Well, not knowingly or intentionally. This project has been developed for fun by enthusiasts who enjoy coding as a rewarding challenge. That said, no guarantees can be made that an IDE hasn't presented a useful auto-complete, or a contributer has used AI in pursuit of understanding a bug. The philosophy of OneLoneCoder is coding for fun, not prompt engineering. +* Can I use olcPixelGameEngine3 commercially? + * Yes. The licence below simply requires that your end users know they are using a product that uses OneLoneCoder's olcPixelGameEngine. +* Can I contribute? + * Yes. A primary goal of PGE3 was to be more open as a project. However, anonymous and/or AI created PRs will be ignored until you have introduced yourself and your ideas to the development team on our Discord server. + + +# Licence (OLC-3) +Copyright 2018 - 2026 OneLoneCoder.com + +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. + +# Examples +As part of the development and testing process we have many examples that illustrate individual features. You can find interactive examples hosted here. + # Building +By far the easiest way to use olcPixelGameEngine3 is to simply include the header file provided in the root of this repository - `olcPixelGameEngine3.h`. As much as possible it will self configure for your chosen environment. On Microsoft platforms, using Visual Studio is our recommended way forward. For all other platforms, a CMake instruction is available. + ## CMake Configure with: @@ -46,6 +154,7 @@ emcc olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads.html -sASYNCIFY -sALLOW_MEMORY ``` # Using STB Image +Image loading is complex. For all the supported platforms we have ensured that as a minimum you can load a ".png" file as an asset. For a wider variety of formats its much better to use existing libraries, and we have decided to support "stb_image.h" which is pretty much the go-to image loader in C++. olcPGE3 can be instructed to use "stb_image" by declaring `OLC_USE_STB_IMAGE=1` before including the header file, or better yet, as a compiler predefined directive. ## Get the headers Aquire [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) and [stb_image_write.h](https://github.com/nothings/stb/blob/master/stb_image_write.h). Place them in the root directory of the repo. diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index c6fd61d1..598c8157 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -352,6 +352,13 @@ false
+ + + + + + + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index c323ce4d..2323331a 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -46,6 +46,9 @@ {b5b02d3b-7b70-4ef1-a2e2-516cbaa9da51} + + {76f094ac-5176-4402-9395-6187374c5d64} + @@ -255,4 +258,17 @@ Hardware
+ + + Extras + + + Extras + + + + + Extras + + \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index d25012f2..21767228 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -8,13 +8,13 @@ olcPixelGameEngine3.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v3.00 Beta A | + | OneLoneCoder Pixel Game Engine v3.00 Beta B | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ What is this? ~~~~~~~~~~~~~ - olc::PixelGameEngine is a single file, cross platform graphics and userinput + olc::PixelGameEngine is a single file, cross platform graphics and user input framework used for games, visualisations, algorithm exploration and learning. It was developed by YouTuber "javidx9" as an assistive tool for many of his videos. The goal of this project is to provide high speed graphics with @@ -83,13 +83,15 @@ AI Disclosure ~~~~~~~~~~~~~ Parts of this code may have been generated with the assistance of AI tools. Instances - of such usage have typically been restricted to the tedious and repetitious through + of such usage would have typically been restricted to the tedious and repetitious through the use of auto-completion and other small code generation helpers. This community driven project has been developed on numerous platforms, across countless tools and environments, by different people over a number of years. As such, it is impossible - to categorically state which sections of code may have had AI assistance. Regardless, - the entire codebase has been architected, reviewed and tested by human developers - mostly for fun and learning purposes, and is intended to be used in that spirit. + to know which sections of code may have had AI assistance. Regardless, the entire codebase + has been architected, implemented, reviewed and tested by human developers mostly for fun + and learning purposes, and it is intended to be used in that spirit. + + "You know you don't have to use AI if you like coding right?" - javidx9 Primary Contributors ~~~~~~~~~~~~~~~~~~~~ From adf2d90919c0bd47d8708d0ab1f7ceca439cae59 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:56:22 +0100 Subject: [PATCH 28/28] synced header --- olcPixelGameEngine3.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 9c0d5024..a8a4da8b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8,13 +8,13 @@ olcPixelGameEngine3.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v3.00 Beta A | + | OneLoneCoder Pixel Game Engine v3.00 Beta B | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ What is this? ~~~~~~~~~~~~~ - olc::PixelGameEngine is a single file, cross platform graphics and userinput + olc::PixelGameEngine is a single file, cross platform graphics and user input framework used for games, visualisations, algorithm exploration and learning. It was developed by YouTuber "javidx9" as an assistive tool for many of his videos. The goal of this project is to provide high speed graphics with @@ -83,13 +83,15 @@ AI Disclosure ~~~~~~~~~~~~~ Parts of this code may have been generated with the assistance of AI tools. Instances - of such usage have typically been restricted to the tedious and repetitious through + of such usage would have typically been restricted to the tedious and repetitious through the use of auto-completion and other small code generation helpers. This community driven project has been developed on numerous platforms, across countless tools and environments, by different people over a number of years. As such, it is impossible - to categorically state which sections of code may have had AI assistance. Regardless, - the entire codebase has been architected, reviewed and tested by human developers - mostly for fun and learning purposes, and is intended to be used in that spirit. + to know which sections of code may have had AI assistance. Regardless, the entire codebase + has been architected, implemented, reviewed and tested by human developers mostly for fun + and learning purposes, and it is intended to be used in that spirit. + + "You know you don't have to use AI if you like coding right?" - javidx9 Primary Contributors ~~~~~~~~~~~~~~~~~~~~