From 36f0efd4fb45954af56d245a0f98611896770d29 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 23 Jan 2026 04:25:35 +0000 Subject: [PATCH 01/56] stupid rebase command i hate git so much --- dev/src/core.cpp | 5 +++++ dev/src/core.h | 2 ++ olcPixelGameEngine3.h | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 3d4cbc1e..e7747940 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -206,6 +206,11 @@ namespace olc return mouse; } + const olc::vi2d& PGEWindow::ScreenSize() + { + return GetDefaultImage().Size(); + } + bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) { mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize()) * GetDefaultImage().Size()); diff --git a/dev/src/core.h b/dev/src/core.h index 6d1aa1b7..5c656e8d 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -97,6 +97,8 @@ namespace olc // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); + // Returns the current size of the "screen" in pixels + const olc::vi2d& ScreenSize(); protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e32b8351..ca587a12 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -1204,6 +1204,13 @@ namespace olc } // Transform a vector by this transform in place + template + inline constexpr auto forwardX(std::vector>&& v) const + { + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i); }); + return v; + } + template inline constexpr auto forwardRoundX(std::vector>&& v) const { @@ -1227,6 +1234,14 @@ namespace olc return o; } + // Transform a vector by this transform in place + template + inline constexpr auto inverseX(std::vector>&& v) const + { + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + return v; + } + // Set scaling component of this transformation template @@ -2896,6 +2911,8 @@ namespace olc // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); + // Returns the current size of the "screen" in pixels + const olc::vi2d& ScreenSize(); protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; @@ -12547,6 +12564,11 @@ namespace olc return mouse; } + const olc::vi2d& PGEWindow::ScreenSize() + { + return GetDefaultImage().Size(); + } + bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) { mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize()) * GetDefaultImage().Size()); From 1ea5dedd51815a1e57eeb1c0ef2ca3ef683c2082 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:36:01 +0000 Subject: [PATCH 02/56] Fixed a bug with ellipse routines Changed background to black (now configurable) Maintains aspect ratio and scales mouse Found issue with Vector2D -= and += ops --- dev/src/core.cpp | 60 ++++++++++++++++++++++++++++---- dev/src/core.h | 11 ++++-- dev/src/draw2d.cpp | 9 +++++ olcPixelGameEngine3.h | 80 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 142 insertions(+), 18 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index e7747940..ca20abeb 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -38,6 +38,10 @@ namespace olc pRenderer->PrepareWindowTarget(pHost->GetHostWindowDescriptor(this)); CreateImage(GetDefaultImage(), vScreenSize); SetWindowSize(vScreenSize * vPixelSize); + + // Assume 1:1 Relationship for now + vViewPos = { 0,0 }; + vViewSize = vScreenSize * vPixelSize; return true; } @@ -97,15 +101,44 @@ namespace olc } draw.ResetShader(); + draw.WorldReset(); // Take the window's completed "screen" and draw it as a textured quad to the backbuffer pRenderer->AssignTextureTarget(0, 0); - pRenderer->SetViewport({ 0,0 }, GetWindowSize()); - pRenderer->ClearViewport(olc::Colour::MAGENTA, true, true); - - - draw.WorldReset(); - draw.ImageRect(GetDefaultImage().flipV(), {0.0,0.0}, GetWindowSize()); + + + // === Viewport Handling === + if (config.bRetainAspectRatio) + { + // Set the viewport to maintain the aspect ratio of GetDefaultImage() and maximise to + // fit within the window client area + float fAspectScreen = float(GetDefaultImage().Size().x) / float(GetDefaultImage().Size().y); + + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * fAspectScreen); + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + else + { + // Stretch to fit window (or 1:1 pixel mapping) + vViewPos = { 0,0 }; + vViewSize = vWindowSize; + + // Note: Important to keep "View" updated as mouse will need to + // know for scaling + } + + // Present final composite + pRenderer->SetViewport(vViewPos, vViewSize); + pRenderer->ClearViewport(config.colClear, true, true); + draw.ImageRect(GetDefaultImage().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); // Update Window's primary surface @@ -213,7 +246,17 @@ namespace olc bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) { - mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize()) * GetDefaultImage().Size()); + olc::vi2d pos = vMousePos; + + // TODO: Concept in v2d prevents this from being cleaner + // Full screen windows may have different scaling + pos.x -= vViewPos.x; + pos.y -= vViewPos.y; + + // Scale mouse into view coordinates + mouse.SetPosition( + (olc::vf2d(pos) / olc::vf2d(vWindowSize - (vViewPos * 2)) * GetDefaultImage().Size()) + .clamp({ 0.0f, 0.0f }, olc::vf2d(GetDefaultImage().Size()-1))); return true; } @@ -306,6 +349,8 @@ namespace olc // Link this olc::Window to a host resource if (host) { + // TODO: Connect to config + host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); window->LinkToHost(host.get()); window->LinkToRenderer(gpu.get()); @@ -313,6 +358,7 @@ namespace olc //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); window->Create(vScreenSize, vPixelSize); + deqChildWindows.push_back(window); } return true; diff --git a/dev/src/core.h b/dev/src/core.h index 5c656e8d..b4875da6 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -54,6 +54,8 @@ namespace olc bool bAllowChildWindows = true; // Creates a "DefaultImage" with anti-aliased properties bool bAntiAliasMainScreen = false; + // Default clear colour for the primary drawing surface + olc::Pixel colClear = olc::Colour::BLACK; }; // A PGE Window is a window with drawing and input capabilities a la olc::PixelGameEngine @@ -114,6 +116,12 @@ namespace olc olc::Image imgPrimary; olc::gpu::Renderer* pRenderer = nullptr; olc::imload::ImageLoader* pImageLoader = nullptr; + olc::vi2d vViewPos = { 0,0 }; + olc::vi2d vViewSize = { 0,0 }; + + protected: + // PGE Configuration + PGEConfig config; }; // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices @@ -158,8 +166,7 @@ namespace olc std::chrono::duration durationTotalElapsed{ 0 }; size_t frameCount = 0; - // PGE Configuration - PGEConfig config; + // Core Thread std::thread coreThread; diff --git a/dev/src/draw2d.cpp b/dev/src/draw2d.cpp index a732d898..270e2df9 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw2d.cpp @@ -512,6 +512,9 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const { PrepareTargetForHW(); + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + buffPoints.reserve(nFacets + 1); buffColours.reserve(nFacets + 1); @@ -533,6 +536,9 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); buffPoints.reserve(nFacets + 2); buffColours.reserve(nFacets + 2); @@ -557,6 +563,9 @@ const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); buffPoints.reserve(nFacets + 2); buffColours.reserve(nFacets + 2); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index ca587a12..a3594dd7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2868,6 +2868,8 @@ namespace olc bool bAllowChildWindows = true; // Creates a "DefaultImage" with anti-aliased properties bool bAntiAliasMainScreen = false; + // Default clear colour for the primary drawing surface + olc::Pixel colClear = olc::Colour::BLACK; }; // A PGE Window is a window with drawing and input capabilities a la olc::PixelGameEngine @@ -2928,6 +2930,12 @@ namespace olc olc::Image imgPrimary; olc::gpu::Renderer* pRenderer = nullptr; olc::imload::ImageLoader* pImageLoader = nullptr; + olc::vi2d vViewPos = { 0,0 }; + olc::vi2d vViewSize = { 0,0 }; + + protected: + // PGE Configuration + PGEConfig config; }; // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices @@ -2972,8 +2980,7 @@ namespace olc std::chrono::duration durationTotalElapsed{ 0 }; size_t frameCount = 0; - // PGE Configuration - PGEConfig config; + // Core Thread std::thread coreThread; @@ -11188,6 +11195,9 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const { PrepareTargetForHW(); + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + buffPoints.reserve(nFacets + 1); buffColours.reserve(nFacets + 1); @@ -11209,6 +11219,9 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); buffPoints.reserve(nFacets + 2); buffColours.reserve(nFacets + 2); @@ -11233,6 +11246,9 @@ const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); buffPoints.reserve(nFacets + 2); buffColours.reserve(nFacets + 2); @@ -12396,6 +12412,10 @@ namespace olc pRenderer->PrepareWindowTarget(pHost->GetHostWindowDescriptor(this)); CreateImage(GetDefaultImage(), vScreenSize); SetWindowSize(vScreenSize * vPixelSize); + + // Assume 1:1 Relationship for now + vViewPos = { 0,0 }; + vViewSize = vScreenSize * vPixelSize; return true; } @@ -12455,15 +12475,44 @@ namespace olc } draw.ResetShader(); + draw.WorldReset(); // Take the window's completed "screen" and draw it as a textured quad to the backbuffer pRenderer->AssignTextureTarget(0, 0); - pRenderer->SetViewport({ 0,0 }, GetWindowSize()); - pRenderer->ClearViewport(olc::Colour::MAGENTA, true, true); - - - draw.WorldReset(); - draw.ImageRect(GetDefaultImage().flipV(), {0.0,0.0}, GetWindowSize()); + + + // === Viewport Handling === + if (config.bRetainAspectRatio) + { + // Set the viewport to maintain the aspect ratio of GetDefaultImage() and maximise to + // fit within the window client area + float fAspectScreen = float(GetDefaultImage().Size().x) / float(GetDefaultImage().Size().y); + + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * fAspectScreen); + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + else + { + // Stretch to fit window (or 1:1 pixel mapping) + vViewPos = { 0,0 }; + vViewSize = vWindowSize; + + // Note: Important to keep "View" updated as mouse will need to + // know for scaling + } + + // Present final composite + pRenderer->SetViewport(vViewPos, vViewSize); + pRenderer->ClearViewport(config.colClear, true, true); + draw.ImageRect(GetDefaultImage().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); // Update Window's primary surface @@ -12571,7 +12620,17 @@ namespace olc bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) { - mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize()) * GetDefaultImage().Size()); + olc::vi2d pos = vMousePos; + + // TODO: Concept in v2d prevents this from being cleaner + // Full screen windows may have different scaling + pos.x -= vViewPos.x; + pos.y -= vViewPos.y; + + // Scale mouse into view coordinates + mouse.SetPosition( + (olc::vf2d(pos) / olc::vf2d(vWindowSize - (vViewPos * 2)) * GetDefaultImage().Size()) + .clamp({ 0.0f, 0.0f }, olc::vf2d(GetDefaultImage().Size()-1))); return true; } @@ -12664,6 +12723,8 @@ namespace olc // Link this olc::Window to a host resource if (host) { + // TODO: Connect to config + host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); window->LinkToHost(host.get()); window->LinkToRenderer(gpu.get()); @@ -12671,6 +12732,7 @@ namespace olc //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); window->Create(vScreenSize, vPixelSize); + deqChildWindows.push_back(window); } return true; From 533c069607c8699b9dc95876491ce97bbc67cc2b Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 23 Jan 2026 06:54:13 +0000 Subject: [PATCH 03/56] Added "Batches Of Lines" example Also added line segments as a prim --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 9 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/draw2d.cpp | 168 ++++++++++- dev/src/draw2d.h | 98 ++++++- dev/src/gpu_opengl33.cpp | 4 + dev/src/gputask.h | 6 +- examples/olcPGE3_BatchesOfLines.cpp | 145 +++++++++ olcPixelGameEngine3.h | 276 ++++++++++++++++-- 8 files changed, 669 insertions(+), 40 deletions(-) create mode 100644 examples/olcPGE3_BatchesOfLines.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index a35eb329..5a763c50 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -25,6 +25,7 @@ true true + true true @@ -62,10 +63,10 @@ true - false - false - false - false + true + 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 053792ab..1a1d7f27 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -57,6 +57,9 @@ Source Files + + Source Files + diff --git a/dev/src/draw2d.cpp b/dev/src/draw2d.cpp index 270e2df9..8d74f399 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw2d.cpp @@ -333,6 +333,12 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc: ))); } +const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +{ + + return Line(batch, p1, col, p2, col); +} + const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { PrepareTargetForHW(); @@ -346,6 +352,17 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc: ))); } +const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); + size_t idx = batch.task.vertexBuffer.size() - 2; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { // Right a big, hearty, F&^% you to OpenGL's Diamond Exit Strategy. It makes line drawing @@ -367,6 +384,11 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } +const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + return Rect(batch, pos, size, col, col, col, col); +} + const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); @@ -385,6 +407,22 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } +const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); + const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); + const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); + const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); + + Line(batch, pTL, colTL, pTR, colTR); + Line(batch, pTR, colTR, pBR, colBR); + Line(batch, pBR, colBR, pBL, colBL); + Line(batch, pBL, colBL, pTL, colTL); + + return batch; + +} + const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { @@ -454,6 +492,19 @@ const GPUTask& olc::Draw2D::Circle(const olc::vf2d& pos, const float& radius, co ))); } +const LineBatch& olc::Draw2D::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + Line(batch, buffUnitCirclePoints.data[i] * radius + pos, col, buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()] * radius + pos, col); + } + + return batch; +} + const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -533,6 +584,23 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const ))); } +const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + const olc::vf2d a1 = { buffUnitCirclePoints.data[i].x * rx + pos.x, buffUnitCirclePoints.data[i].y * ry + pos.y }; + const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, + buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; + + Line(batch, a1, col, a2, col); + } + + return batch; +} + const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -637,6 +705,52 @@ const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& s ))); } +const LineBatch& olc::Draw2D::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + buffPoints.reserve((nFacets + 1) * 4 + 1); + buffPoints.data.clear(); + + olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); + olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); + + // Top Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (1.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Top Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Bottom Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + // Bottom Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); + + for (size_t i = 0; i < buffPoints.data.size() - 1; i++) + { + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); + } + + return batch; +} + const GPUTask& olc::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -694,6 +808,11 @@ const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, c return Triangle(p1, p2, p3, col, col, col, tint); } +const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return Triangle(batch, p1, p2, p3, col, col, col); +} + const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -707,6 +826,14 @@ const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, c ))); } +const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + Line(batch, p1, c1, p2, c2); + Line(batch, p2, c2, p3, c3); + Line(batch, p3, c3, p1, c1); + return batch; +} + const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return FilledTriangle(p1, p2, p3, col, col, col, tint); @@ -746,11 +873,31 @@ const GPUTask& olc::Draw2D::Polygon(const std::vector& vecPoints, con return Polygon(olc::Structure::LineLoop, vecPoints, std::vector(vecPoints.size(), col), tint); } +const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); + } + + return batch; +} + const GPUTask& olc::Draw2D::Polygon(const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, vecColours, tint); } +const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +{ + for(size_t i = 0; i& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return Polygon(structure, vecPoints, std::vector(vecPoints.size(), col), tint); @@ -908,8 +1055,9 @@ ImageBatch olc::Draw2D::CreateImageBatch(olc::Image &image) return b; } -const GPUTask& olc::Draw2D::Batch(const ImageBatch& batch) +const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) { + batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } @@ -918,18 +1066,22 @@ FilledBatch olc::Draw2D::CreateFilledBatch() return FilledBatch(); } -const GPUTask& olc::Draw2D::Batch(const olc::FilledBatch& batch) +const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) { + batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } LineBatch olc::Draw2D::CreateLineBatch() { - return LineBatch(); + LineBatch b; + b.task.structure = olc::Structure::LineList; + return b; } -const GPUTask& olc::Draw2D::Batch(const olc::LineBatch& batch) +const GPUTask& olc::Draw2D::Batch(olc::LineBatch& batch, const olc::Pixel tint) { + batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } @@ -950,9 +1102,6 @@ const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - - batch.task.tint = tint; - return batch; } @@ -1042,9 +1191,6 @@ const ImageBatch& olc::Draw2D::ImageRotated(olc::ImageBatch& batch, olc::ImageRe batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - - batch.task.tint = tint; - return batch; } @@ -1142,8 +1288,6 @@ const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegio batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0}}); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0}}); batch.task.vertexBuffer.push_back({ {p3.x, p3.y, q[3], 1.0f}, tint, {q[3] * image.coords[3].x, q[3] * image.coords[3].y}, {0, 0}, {0, 0}, {0, 0}}); - - batch.task.tint = tint; return batch; } diff --git a/dev/src/draw2d.h b/dev/src/draw2d.h index a55e786c..4f811da0 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw2d.h @@ -104,6 +104,13 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a single pixel wide line into a batch + const LineBatch& Line( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a single pixel wide line with a gradient const GPUTask& Line( const olc::vf2d& p1, @@ -112,6 +119,14 @@ namespace olc const olc::Pixel c2, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a single pixel wide line with a gradient into a batch + const LineBatch& Line( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::Pixel c1, + const olc::vf2d& p2, + const olc::Pixel c2); + // === Rectangles === // Draws a rectangle outline @@ -121,6 +136,13 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a rectangle outline into a batch + const LineBatch& Rect( + olc::LineBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a multiple colour rectangle, with linear colour interpolation const GPUTask& Rect( const olc::vf2d& pos, @@ -131,6 +153,16 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a multiple colour rectangle, with linear colour interpolation, into a batch + const LineBatch& Rect( + olc::LineBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + // Draws a filled, single colour rectangle const GPUTask& FilledRect( const olc::vf2d& pos, @@ -158,6 +190,14 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a circle outline with a single colour into a batch + const LineBatch& Circle( + olc::LineBatch& batch, + const olc::vf2d& pos, + const float& radius, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a filled circle with a single colour const GPUTask& FilledCircle( const olc::vf2d& pos, @@ -186,6 +226,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws an ellipse outline with a single colour into a batch + const LineBatch& Ellipse( + olc::LineBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a filled ellipse with a single colour const GPUTask& FilledEllipse( const olc::vf2d& pos, @@ -216,6 +265,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 8); + // Draws a rounded rectangle outline with a single colour into a batch + const LineBatch& RoundedRect( + olc::LineBatch& batch, + const olc::vf2d& pos, // Top left of bounding rectangle + const olc::vf2d& size, // Size of bounding rectangle + const float& radius, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 8); + // Draws a filled rounded rectangle with a single colour const GPUTask& FilledRoundedRect( const olc::vf2d& pos, // Top left of bounding rectangle @@ -236,6 +294,14 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a triangle outline with a single colour into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a multiple colour triangle outline const GPUTask& Triangle( const olc::vf2d& p1, @@ -246,6 +312,16 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a multiple colour triangle outline into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3); + // Draws a filled, single colour triangle const GPUTask& FilledTriangle( const olc::vf2d& p1, @@ -278,7 +354,7 @@ namespace olc olc::Image& texture, const olc::Pixel tint = olc::Colour::WHITE); -// === Polygons === +// === Polygon Outlines === // Draws a polygon outline with a single colour const GPUTask& Polygon( @@ -286,12 +362,26 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with a single colour into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, + const std::vector& vecPoints, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a polygon outline with multiple colours const GPUTask& Polygon( const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with multiple colours into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, + const std::vector& vecPoints, + const std::vector& vecColours); + + // === Structured Polygons (Outlines & Fills) === + // Draws a polygon outline with a single colour const GPUTask& Polygon( const olc::Structure structure, @@ -445,21 +535,21 @@ namespace olc ImageBatch CreateImageBatch(olc::Image& image); // Draws an image batch to the current target - const GPUTask& Batch(const olc::ImageBatch& batch); + const GPUTask& Batch(olc::ImageBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); // Create a filled shape batch for efficient repeated drawing // of primitive filled shapes FilledBatch CreateFilledBatch(); // Draws a filled shape batch to the current target - const GPUTask& Batch(const olc::FilledBatch& batch); + const GPUTask& Batch(olc::FilledBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); // Create a line shape batch for efficient repeated drawing // of primitive line shapes LineBatch CreateLineBatch(); // Draws a line shape batch to the current target - const GPUTask& Batch(const olc::LineBatch& batch); + const GPUTask& Batch(olc::LineBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); public: // GPU Task Creator Functions (not normally called by user) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index aefc8873..f566e130 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -999,6 +999,8 @@ void main() 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); @@ -1010,6 +1012,8 @@ void main() gl.glDrawArrays(GL_TRIANGLES, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == olc::Structure::Line) gl.glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)task.vertexBuffer.size()); + else if (task.structure == olc::Structure::LineList) + gl.glDrawArrays(GL_LINES, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == olc::Structure::LineLoop) gl.glDrawArrays(GL_LINE_LOOP, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == olc::Structure::Point) diff --git a/dev/src/gputask.h b/dev/src/gputask.h index 39378688..ef5adf9b 100644 --- a/dev/src/gputask.h +++ b/dev/src/gputask.h @@ -24,10 +24,12 @@ namespace olc { // Vertex buffer is a series of points Point = 0, - // Vertex buffer is a series of line segments + // Vertex buffer is a series of connected line segments Line, - // Vertex buffer is a series of line segments, that close to form a loop + // Vertex buffer is a series of connected line segments, that close to form a loop LineLoop, + // Vertex buffer is a series of individual line segments + LineList, // Vertex buffer is a fan of triangles Fan, // Vertex buffer is a strip of adjacent triangles diff --git a/examples/olcPGE3_BatchesOfLines.cpp b/examples/olcPGE3_BatchesOfLines.cpp new file mode 100644 index 00000000..7ed23c51 --- /dev/null +++ b/examples/olcPGE3_BatchesOfLines.cpp @@ -0,0 +1,145 @@ +/* + olc::PixelGameEngine3 Example - Batchs of Lines + + Draws all shapes that have an outline into a single line batch for efficiency + + 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 batched line drawing. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_BatchesOfLines : public olc::PixelGameEngine +{ +public: + Example_BatchesOfLines() + { + + } + +protected: + // We accumulate total time for some animation + float fTotalTime = 0.0f; + +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); + + // Create a line batch + auto batch = draw.CreateLineBatch(); + + // === Stolen from olcPGE3_Lines.cpp === + // + // Simple white line + draw.Line(batch, { 10.0f, 10.0f }, { 246.0f, 40.0f }, olc::Colour::WHITE); + + // Gradient line + draw.Line(batch, { 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); + + // Rotating gradient lines + fTotalTime += fElapsedTime; + olc::vf2d p1 = { 64.0f, 160.0f }; + olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; + + draw.Line(batch, p1, olc::Colour::CYAN, p1 + p2, olc::Colour::MAGENTA); + draw.Line(batch, p1, olc::Colour::CYAN, p1 - p2, olc::Colour::MAGENTA); + draw.Line(batch, p1, olc::Colour::CYAN, p1 + olc::vf2d{ -p2.y, p2.x }, olc::Colour::MAGENTA); + draw.Line(batch, p1, olc::Colour::CYAN, p1 + olc::vf2d{ p2.y, -p2.x }, olc::Colour::MAGENTA); + + // Line to mouse pointer with tint + float alpha = (std::sin(fTotalTime * 4.0f) + 1.0f) / 2.0f; + draw.Line(batch, p1, mouse.GetPosition(), olc::PixelF(1.0f, 0.0f, 0.0f, alpha)); // Note! Can't tint a single line in a batch + + // Boundary box (with fixed coordinates) + draw.Line(batch, { 0.0f, 0.0f }, { 255.0f, 0.0f }, olc::Colour::DARK_GREY); + draw.Line(batch, { 255.0f, 0.0f }, { 255.0f, 239.0f }, olc::Colour::DARK_GREY); + draw.Line(batch, { 255.0f, 239.0f }, { 0.0f, 239.0f }, olc::Colour::DARK_GREY); + draw.Line(batch, { 0.0f, 239.0f }, { 0.0f, 0.0f }, olc::Colour::DARK_GREY); + + // ====================================== + + + // Add Rectangle with single colour + draw.Rect(batch, { 10.0f, 200.0f }, { 100.0f, 30.0f }, olc::Colour::DARK_GREEN); + + // Add Rectangle with gradient colours + draw.Rect(batch, { 14.0f, 204.0f }, { 92.0f, 22.0f }, olc::Colour::GREEN, olc::Colour::RED, olc::Colour::BLUE, olc::Colour::WHITE); + + // Add Circle with single colour + draw.Circle(batch, ScreenSize() * 0.5f, 10.0f, olc::Colour::DARK_CYAN); + + // Add Ellipse with dynamic size + draw.Ellipse(batch, ScreenSize() * 0.5f, 100.0f * ((cos(fTotalTime) + 1.0f) * 0.5f), 100.0f * ((sin(fTotalTime) + 1.0f) * 0.5f), olc::Colour::DARK_YELLOW); + + // Add Polygon with single colour + draw.Polygon(batch, { + { 200.0f, 150.0f }, + { 250.0f, 180.0f }, + { 220.0f, 220.0f }, + { 180.0f, 200.0f }, + { 160.0f, 160.0f } + }, olc::Colour::DARK_MAGENTA); + + // Add Polygon with gradient colours + draw.Polygon(batch, { + { 200.0f, 50.0f }, + { 250.0f, 80.0f }, + { 220.0f, 120.0f }, + { 180.0f, 100.0f }, + { 160.0f, 60.0f } + }, { + olc::Colour::MAGENTA, + olc::Colour::RED, + olc::Colour::YELLOW, + olc::Colour::GREEN, + olc::Colour::CYAN }); + + // Add Rounded Rectangle + draw.RoundedRect(batch, { 150.0f, 200.0f }, { 80.0f, 30.0f }, 5.0f, olc::Colour::TANGERINE); + + + + // Single batch draw with optional tinting + if(mouse.GetButton(0).bHeld) + draw.Batch(batch, olc::PixelF(1.0f, 0.0f, 0.0f, 0.5f)); + else + draw.Batch(batch, olc::Colour::WHITE); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_BatchesOfLines 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 a3594dd7..09601996 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -1638,10 +1638,12 @@ namespace olc { // Vertex buffer is a series of points Point = 0, - // Vertex buffer is a series of line segments + // Vertex buffer is a series of connected line segments Line, - // Vertex buffer is a series of line segments, that close to form a loop + // Vertex buffer is a series of connected line segments, that close to form a loop LineLoop, + // Vertex buffer is a series of individual line segments + LineList, // Vertex buffer is a fan of triangles Fan, // Vertex buffer is a strip of adjacent triangles @@ -1942,6 +1944,13 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a single pixel wide line into a batch + const LineBatch& Line( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a single pixel wide line with a gradient const GPUTask& Line( const olc::vf2d& p1, @@ -1950,6 +1959,14 @@ namespace olc const olc::Pixel c2, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a single pixel wide line with a gradient into a batch + const LineBatch& Line( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::Pixel c1, + const olc::vf2d& p2, + const olc::Pixel c2); + // === Rectangles === // Draws a rectangle outline @@ -1959,6 +1976,13 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a rectangle outline into a batch + const LineBatch& Rect( + olc::LineBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a multiple colour rectangle, with linear colour interpolation const GPUTask& Rect( const olc::vf2d& pos, @@ -1969,6 +1993,16 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a multiple colour rectangle, with linear colour interpolation, into a batch + const LineBatch& Rect( + olc::LineBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + // Draws a filled, single colour rectangle const GPUTask& FilledRect( const olc::vf2d& pos, @@ -1996,6 +2030,14 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a circle outline with a single colour into a batch + const LineBatch& Circle( + olc::LineBatch& batch, + const olc::vf2d& pos, + const float& radius, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a filled circle with a single colour const GPUTask& FilledCircle( const olc::vf2d& pos, @@ -2024,6 +2066,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws an ellipse outline with a single colour into a batch + const LineBatch& Ellipse( + olc::LineBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a filled ellipse with a single colour const GPUTask& FilledEllipse( const olc::vf2d& pos, @@ -2054,6 +2105,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 8); + // Draws a rounded rectangle outline with a single colour into a batch + const LineBatch& RoundedRect( + olc::LineBatch& batch, + const olc::vf2d& pos, // Top left of bounding rectangle + const olc::vf2d& size, // Size of bounding rectangle + const float& radius, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 8); + // Draws a filled rounded rectangle with a single colour const GPUTask& FilledRoundedRect( const olc::vf2d& pos, // Top left of bounding rectangle @@ -2074,6 +2134,14 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a triangle outline with a single colour into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a multiple colour triangle outline const GPUTask& Triangle( const olc::vf2d& p1, @@ -2084,6 +2152,16 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a multiple colour triangle outline into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3); + // Draws a filled, single colour triangle const GPUTask& FilledTriangle( const olc::vf2d& p1, @@ -2116,7 +2194,7 @@ namespace olc olc::Image& texture, const olc::Pixel tint = olc::Colour::WHITE); -// === Polygons === +// === Polygon Outlines === // Draws a polygon outline with a single colour const GPUTask& Polygon( @@ -2124,12 +2202,26 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with a single colour into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, + const std::vector& vecPoints, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a polygon outline with multiple colours const GPUTask& Polygon( const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with multiple colours into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, + const std::vector& vecPoints, + const std::vector& vecColours); + + // === Structured Polygons (Outlines & Fills) === + // Draws a polygon outline with a single colour const GPUTask& Polygon( const olc::Structure structure, @@ -2283,21 +2375,21 @@ namespace olc ImageBatch CreateImageBatch(olc::Image& image); // Draws an image batch to the current target - const GPUTask& Batch(const olc::ImageBatch& batch); + const GPUTask& Batch(olc::ImageBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); // Create a filled shape batch for efficient repeated drawing // of primitive filled shapes FilledBatch CreateFilledBatch(); // Draws a filled shape batch to the current target - const GPUTask& Batch(const olc::FilledBatch& batch); + const GPUTask& Batch(olc::FilledBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); // Create a line shape batch for efficient repeated drawing // of primitive line shapes LineBatch CreateLineBatch(); // Draws a line shape batch to the current target - const GPUTask& Batch(const olc::LineBatch& batch); + const GPUTask& Batch(olc::LineBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); public: // GPU Task Creator Functions (not normally called by user) @@ -10580,6 +10672,8 @@ void main() 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); @@ -10591,6 +10685,8 @@ void main() gl.glDrawArrays(GL_TRIANGLES, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == olc::Structure::Line) gl.glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)task.vertexBuffer.size()); + else if (task.structure == olc::Structure::LineList) + gl.glDrawArrays(GL_LINES, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == olc::Structure::LineLoop) gl.glDrawArrays(GL_LINE_LOOP, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == olc::Structure::Point) @@ -11016,6 +11112,12 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc: ))); } +const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +{ + + return Line(batch, p1, col, p2, col); +} + const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { PrepareTargetForHW(); @@ -11029,6 +11131,17 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc: ))); } +const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); + size_t idx = batch.task.vertexBuffer.size() - 2; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { // Right a big, hearty, F&^% you to OpenGL's Diamond Exit Strategy. It makes line drawing @@ -11050,6 +11163,11 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } +const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + return Rect(batch, pos, size, col, col, col, col); +} + const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); @@ -11068,6 +11186,22 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } +const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); + const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); + const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); + const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); + + Line(batch, pTL, colTL, pTR, colTR); + Line(batch, pTR, colTR, pBR, colBR); + Line(batch, pBR, colBR, pBL, colBL); + Line(batch, pBL, colBL, pTL, colTL); + + return batch; + +} + const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { @@ -11137,6 +11271,19 @@ const GPUTask& olc::Draw2D::Circle(const olc::vf2d& pos, const float& radius, co ))); } +const LineBatch& olc::Draw2D::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + Line(batch, buffUnitCirclePoints.data[i] * radius + pos, col, buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()] * radius + pos, col); + } + + return batch; +} + const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -11216,6 +11363,23 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const ))); } +const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + const olc::vf2d a1 = { buffUnitCirclePoints.data[i].x * rx + pos.x, buffUnitCirclePoints.data[i].y * ry + pos.y }; + const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, + buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; + + Line(batch, a1, col, a2, col); + } + + return batch; +} + const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -11320,6 +11484,52 @@ const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& s ))); } +const LineBatch& olc::Draw2D::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + buffPoints.reserve((nFacets + 1) * 4 + 1); + buffPoints.data.clear(); + + olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); + olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); + + // Top Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (1.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Top Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Bottom Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + // Bottom Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); + + for (size_t i = 0; i < buffPoints.data.size() - 1; i++) + { + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); + } + + return batch; +} + const GPUTask& olc::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -11377,6 +11587,11 @@ const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, c return Triangle(p1, p2, p3, col, col, col, tint); } +const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return Triangle(batch, p1, p2, p3, col, col, col); +} + const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -11390,6 +11605,14 @@ const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, c ))); } +const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + Line(batch, p1, c1, p2, c2); + Line(batch, p2, c2, p3, c3); + Line(batch, p3, c3, p1, c1); + return batch; +} + const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return FilledTriangle(p1, p2, p3, col, col, col, tint); @@ -11429,11 +11652,31 @@ const GPUTask& olc::Draw2D::Polygon(const std::vector& vecPoints, con return Polygon(olc::Structure::LineLoop, vecPoints, std::vector(vecPoints.size(), col), tint); } +const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); + } + + return batch; +} + const GPUTask& olc::Draw2D::Polygon(const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, vecColours, tint); } +const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +{ + for(size_t i = 0; i& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return Polygon(structure, vecPoints, std::vector(vecPoints.size(), col), tint); @@ -11591,8 +11834,9 @@ ImageBatch olc::Draw2D::CreateImageBatch(olc::Image &image) return b; } -const GPUTask& olc::Draw2D::Batch(const ImageBatch& batch) +const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) { + batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } @@ -11601,18 +11845,22 @@ FilledBatch olc::Draw2D::CreateFilledBatch() return FilledBatch(); } -const GPUTask& olc::Draw2D::Batch(const olc::FilledBatch& batch) +const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) { + batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } LineBatch olc::Draw2D::CreateLineBatch() { - return LineBatch(); + LineBatch b; + b.task.structure = olc::Structure::LineList; + return b; } -const GPUTask& olc::Draw2D::Batch(const olc::LineBatch& batch) +const GPUTask& olc::Draw2D::Batch(olc::LineBatch& batch, const olc::Pixel tint) { + batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } @@ -11633,9 +11881,6 @@ const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - - batch.task.tint = tint; - return batch; } @@ -11725,9 +11970,6 @@ const ImageBatch& olc::Draw2D::ImageRotated(olc::ImageBatch& batch, olc::ImageRe batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - - batch.task.tint = tint; - return batch; } @@ -11825,8 +12067,6 @@ const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegio batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0}}); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0}}); batch.task.vertexBuffer.push_back({ {p3.x, p3.y, q[3], 1.0f}, tint, {q[3] * image.coords[3].x, q[3] * image.coords[3].y}, {0, 0}, {0, 0}, {0, 0}}); - - batch.task.tint = tint; return batch; } From ee45b49150135884c139c8ef9d8bf5ce8d807b3d Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Sun, 25 Jan 2026 01:32:37 +0000 Subject: [PATCH 04/56] Correct EGL context version EGL_CONTEXT_MAJOR_VERSION is the newer equivalent flag to EGL_CONTEXT_CLIENT_VERSION. I think we were getting away with this because of the '-sMIN_WEBGL_VERSION=2' compilation flag, however better to be explicit and match the 'version 300 es' in the shaders. This change allows running with assertions enabled to debug other issues. --- dev/src/gpu_opengl33.cpp | 2 +- olcPixelGameEngine3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index f566e130..fa60512b 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -295,7 +295,7 @@ void main() #endif EGLint const attribute_list[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_SAMPLE_BUFFERS, 1, EGL_SAMPLES, OLC_MSAA_SAMPLES, EGL_NONE}; - EGLint const context_config[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; EGLint num_config; glRenderContext.display = eglGetDisplay(display); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 09601996..a660a071 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -9968,7 +9968,7 @@ void main() #endif EGLint const attribute_list[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_SAMPLE_BUFFERS, 1, EGL_SAMPLES, OLC_MSAA_SAMPLES, EGL_NONE}; - EGLint const context_config[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; EGLint num_config; glRenderContext.display = eglGetDisplay(display); From 10dc8cdc6af78b0f7908751be311f5ee7e3a56d9 Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Sun, 25 Jan 2026 02:21:16 +0000 Subject: [PATCH 05/56] Use helper uniform methods, add debug logs --- dev/src/gpu_opengl33.cpp | 41 ++++++++++++++++++++++++---------------- olcPixelGameEngine3.h | 41 ++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index f566e130..e84674db 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -885,6 +885,13 @@ void main() { auto& gl = olc::apis::opengl::gl::Get(); int loc = pCurrentShader->GetUniform(name); + if (loc == -1) + { + #if OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning: Uniform '" << name << "' not found in current shader\n"; + #endif + return false; + } gl.glUniform1f(loc, value); return true; } @@ -893,6 +900,13 @@ void main() { auto& gl = olc::apis::opengl::gl::Get(); int loc = pCurrentShader->GetUniform(name); + if (loc == -1) + { + #if OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning: Uniform '" << name << "' not found in current shader\n"; + #endif + return false; + } gl.glUniform2fv(loc, 1, value.a().data()); return true; } @@ -908,6 +922,13 @@ void main() auto& gl = olc::apis::opengl::gl::Get(); int loc = pCurrentShader->GetUniform(name); + if (loc == -1) + { + #if OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning: Uniform '" << name << "' not found in current shader\n"; + #endif + return false; + } gl.glUniform4fv(loc, 1, f); return true; } @@ -947,23 +968,11 @@ void main() //gl.glUniformMatrix4fv(shaderDefault.GetUniform("mvp"), 1, true, task.mvpMatrix.data()); // Shader: Apply Global Tint - float f[4] = { - float(task.tint.r) / 255.0f, - float(task.tint.g) / 255.0f, - float(task.tint.b) / 255.0f, - float(task.tint.a) / 255.0f - }; - - gl.glUniform4fv(pCurrentShader->GetUniform("pgeGlobalTint"), 1, f); + SetUniform("pgeGlobalTint", task.tint); - f[0] = 64.0f; - f[1] = 64.0f; - gl.glUniform2fv(pCurrentShader->GetUniform("pgeTargetSizeInPixels"), 1, vTargetSize.a().data()); - gl.glUniform2fv(pCurrentShader->GetUniform("pgeInverseTargetSizeInPixels"), 1, ((1.0f / vTargetSize)).a().data()); - gl.glUniform1f(pCurrentShader->GetUniform("pgeTotalTimeElapsed"), fTotalTime); - - - + SetUniform("pgeTileSizeInPixels", vTargetSize); + SetUniform("pgeInverseTargetSizeInPixels", (1.0f / vTargetSize)); + SetUniform("pgeTotalTimeElapsed", fTotalTime); // Apply Culling modes //if (task.cullmode == GPUTask::CullMode::None) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 09601996..dc198701 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10558,6 +10558,13 @@ void main() { auto& gl = olc::apis::opengl::gl::Get(); int loc = pCurrentShader->GetUniform(name); + if (loc == -1) + { + #if OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning: Uniform '" << name << "' not found in current shader\n"; + #endif + return false; + } gl.glUniform1f(loc, value); return true; } @@ -10566,6 +10573,13 @@ void main() { auto& gl = olc::apis::opengl::gl::Get(); int loc = pCurrentShader->GetUniform(name); + if (loc == -1) + { + #if OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning: Uniform '" << name << "' not found in current shader\n"; + #endif + return false; + } gl.glUniform2fv(loc, 1, value.a().data()); return true; } @@ -10581,6 +10595,13 @@ void main() auto& gl = olc::apis::opengl::gl::Get(); int loc = pCurrentShader->GetUniform(name); + if (loc == -1) + { + #if OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning: Uniform '" << name << "' not found in current shader\n"; + #endif + return false; + } gl.glUniform4fv(loc, 1, f); return true; } @@ -10620,23 +10641,11 @@ void main() //gl.glUniformMatrix4fv(shaderDefault.GetUniform("mvp"), 1, true, task.mvpMatrix.data()); // Shader: Apply Global Tint - float f[4] = { - float(task.tint.r) / 255.0f, - float(task.tint.g) / 255.0f, - float(task.tint.b) / 255.0f, - float(task.tint.a) / 255.0f - }; - - gl.glUniform4fv(pCurrentShader->GetUniform("pgeGlobalTint"), 1, f); + SetUniform("pgeGlobalTint", task.tint); - f[0] = 64.0f; - f[1] = 64.0f; - gl.glUniform2fv(pCurrentShader->GetUniform("pgeTargetSizeInPixels"), 1, vTargetSize.a().data()); - gl.glUniform2fv(pCurrentShader->GetUniform("pgeInverseTargetSizeInPixels"), 1, ((1.0f / vTargetSize)).a().data()); - gl.glUniform1f(pCurrentShader->GetUniform("pgeTotalTimeElapsed"), fTotalTime); - - - + SetUniform("pgeTileSizeInPixels", vTargetSize); + SetUniform("pgeInverseTargetSizeInPixels", (1.0f / vTargetSize)); + SetUniform("pgeTotalTimeElapsed", fTotalTime); // Apply Culling modes //if (task.cullmode == GPUTask::CullMode::None) From 70274902bc01be24165edc85b1618703e36bebe4 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 25 Jan 2026 10:17:15 +0000 Subject: [PATCH 06/56] Fixed typo in #92 Related Work Items: #9 --- dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 13 +++++++++---- dev/src/gpu_opengl33.cpp | 2 +- olcPixelGameEngine3.h | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 5a763c50..716cb9d1 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -25,25 +25,30 @@ true true - - + true true true true - + true true true true - + true true true true + + false + false + false + false + true true diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 9c2506b4..6544c4a7 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -970,7 +970,7 @@ void main() // Shader: Apply Global Tint SetUniform("pgeGlobalTint", task.tint); - SetUniform("pgeTileSizeInPixels", vTargetSize); + SetUniform("pgeTargetSizeInPixels", vTargetSize); SetUniform("pgeInverseTargetSizeInPixels", (1.0f / vTargetSize)); SetUniform("pgeTotalTimeElapsed", fTotalTime); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 21ecbfbc..2feb5598 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10643,7 +10643,7 @@ void main() // Shader: Apply Global Tint SetUniform("pgeGlobalTint", task.tint); - SetUniform("pgeTileSizeInPixels", vTargetSize); + SetUniform("pgeTargetSizeInPixels", vTargetSize); SetUniform("pgeInverseTargetSizeInPixels", (1.0f / vTargetSize)); SetUniform("pgeTotalTimeElapsed", fTotalTime); From 43ee4e3c6b6bb29aa6459a1b429460734e13d318 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 25 Jan 2026 10:58:18 +0000 Subject: [PATCH 07/56] degenerated circle/ellipse funcs --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 14 +- dev/src/draw2d.cpp | 176 ++++++------- dev/src/draw2d.h | 71 +++++ olcPixelGameEngine3.h | 246 +++++++++++------- 4 files changed, 305 insertions(+), 202 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 716cb9d1..762093d9 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -32,23 +32,23 @@ true + false + false + false + false + + true true true true - + true true true true - - false - false - false - false - true true diff --git a/dev/src/draw2d.cpp b/dev/src/draw2d.cpp index 8d74f399..9622016a 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw2d.cpp @@ -335,7 +335,6 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc: const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) { - return Line(batch, p1, col, p2, col); } @@ -441,6 +440,13 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } +const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); + return batch; +} + const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); @@ -457,6 +463,13 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } +const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); + return batch; +} + void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) { buffUnitCirclePoints.reserve(nFacets + 1); @@ -469,98 +482,37 @@ void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) const GPUTask& olc::Draw2D::Circle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 1); - buffColours.reserve(nFacets + 1); - - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i] = transformAffine.forwardRound(buffUnitCirclePoints.data[i] * radius + pos); - buffColours.data[i] = col; - } - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - olc::Structure::Line, - buffPoints.data, - buffColours.data, - tint - ))); + return Ellipse(pos, radius, radius, col, tint, nFacets); } const LineBatch& olc::Draw2D::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) { - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - for (int32_t i = 0; i <= nFacets; i++) - { - Line(batch, buffUnitCirclePoints.data[i] * radius + pos, col, buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()] * radius + pos, col); - } - - return batch; + return Ellipse(batch, pos, radius, radius, col, nFacets); } const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); - - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = col; - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i + 1] = transformAffine.forwardRound(buffUnitCirclePoints.data[i] * radius + pos); - buffColours.data[i + 1] = col; - } + return FilledEllipse(pos, radius, radius, col, tint, nFacets); +} - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); +const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, col, nFacets); } const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); + return FilledEllipse(pos, radius, radius, colInner, colOuter, tint, nFacets); +} - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = colInner; - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i + 1] = transformAffine.forwardRound(buffUnitCirclePoints.data[i] * radius + pos); - buffColours.data[i + 1] = colOuter; - } - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); +const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); } const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { + // Fundamental for outline circle/ellipse with colour solid/gradient PrepareTargetForHW(); if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) @@ -586,6 +538,8 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) { + // Fundamental for batched outline circle/ellipse with colour solid/gradient + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) RedefineUnitCircleBuffer(nFacets); @@ -603,33 +557,18 @@ const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& po const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); - - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = col; - 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}); - buffColours.data[i + 1] = col; - } + return FilledEllipse(pos, rx, ry, col, col, tint, nFacets); +} - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); +const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); } const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { + // Fundamental for filled circle/ellipse with colour solid/gradient + PrepareTargetForHW(); if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) @@ -655,6 +594,27 @@ const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, ))); } +const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + // Fundamental for batch filled circle/ellipse with colour solid/gradient + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + olc::vf2d p1 = { pos.x + rx * buffUnitCirclePoints.data[i].x, + pos.y + ry * buffUnitCirclePoints.data[i].y }; + + olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, + pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; + + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); + } + + return batch; +} + const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -839,6 +799,11 @@ const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& return FilledTriangle(p1, p2, p3, col, col, col, tint); } +const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return FilledTriangle(batch, p1, p2, p3, col, col, col); +} + const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -852,6 +817,19 @@ const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& ))); } +const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); + size_t idx = batch.task.vertexBuffer.size() - 3; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + const olc::vf2d a3 = transformAffine.forwardRound(p3); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + const GPUTask& olc::Draw2D::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); @@ -1063,7 +1041,9 @@ const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) FilledBatch olc::Draw2D::CreateFilledBatch() { - return FilledBatch(); + FilledBatch b; + b.task.structure = olc::Structure::List; + return b; } const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) diff --git a/dev/src/draw2d.h b/dev/src/draw2d.h index 4f811da0..fe6656bb 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw2d.h @@ -170,6 +170,13 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, single colour rectangle into a batch + const FilledBatch& FilledRect( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a filled, multiple colour rectangle, with linear colour interpolation const GPUTask& FilledRect( const olc::vf2d& pos, @@ -180,6 +187,16 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, multiple colour rectangle, with linear colour interpolation, into a batch + const FilledBatch& FilledRect( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + // === Circles === // Draws a circle outline with a single colour @@ -206,6 +223,14 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a filled circle with a single colour into a batch + const FilledBatch& FilledCircle( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& radius, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a shaded circle with a radial gradient const GPUTask& FilledCircle( const olc::vf2d& pos, @@ -215,6 +240,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a shaded circle with a radial gradient into a batch + const FilledBatch& FilledCircle( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& radius, + const olc::Pixel colInner, + const olc::Pixel colOuter, + int32_t nFacets = 32); + // === Ellipses === // Draws an ellipse outline with a single colour @@ -244,6 +278,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a filled ellipse with a single colour into a batch + const FilledBatch& FilledEllipse( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a shaded ellipse with a radial gradient const GPUTask& FilledEllipse( const olc::vf2d& pos, @@ -254,6 +297,16 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a shaded ellipse with a radial gradient into a batch + const FilledBatch& FilledEllipse( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel colInner, + const olc::Pixel colOuter, + int32_t nFacets = 32); + // === Rounded Rectangles === // Draws a rounded rectangle outline with a single colour @@ -330,6 +383,14 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, single colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a filled, multiple colour triangle const GPUTask& FilledTriangle( const olc::vf2d& p1, @@ -340,6 +401,16 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, multiple colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3); + // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( const olc::vf2d& p1, diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 2feb5598..d96410c0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2010,6 +2010,13 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, single colour rectangle into a batch + const FilledBatch& FilledRect( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a filled, multiple colour rectangle, with linear colour interpolation const GPUTask& FilledRect( const olc::vf2d& pos, @@ -2020,6 +2027,16 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, multiple colour rectangle, with linear colour interpolation, into a batch + const FilledBatch& FilledRect( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + // === Circles === // Draws a circle outline with a single colour @@ -2046,6 +2063,14 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a filled circle with a single colour into a batch + const FilledBatch& FilledCircle( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& radius, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a shaded circle with a radial gradient const GPUTask& FilledCircle( const olc::vf2d& pos, @@ -2055,6 +2080,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a shaded circle with a radial gradient into a batch + const FilledBatch& FilledCircle( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& radius, + const olc::Pixel colInner, + const olc::Pixel colOuter, + int32_t nFacets = 32); + // === Ellipses === // Draws an ellipse outline with a single colour @@ -2084,6 +2118,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a filled ellipse with a single colour into a batch + const FilledBatch& FilledEllipse( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel col = olc::Colour::WHITE, + int32_t nFacets = 32); + // Draws a shaded ellipse with a radial gradient const GPUTask& FilledEllipse( const olc::vf2d& pos, @@ -2094,6 +2137,16 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = 32); + // Draws a shaded ellipse with a radial gradient into a batch + const FilledBatch& FilledEllipse( + olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel colInner, + const olc::Pixel colOuter, + int32_t nFacets = 32); + // === Rounded Rectangles === // Draws a rounded rectangle outline with a single colour @@ -2170,6 +2223,14 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, single colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a filled, multiple colour triangle const GPUTask& FilledTriangle( const olc::vf2d& p1, @@ -2180,6 +2241,16 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, multiple colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3); + // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( const olc::vf2d& p1, @@ -11229,6 +11300,13 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } +const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); + return batch; +} + const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); @@ -11245,6 +11323,13 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } +const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); + return batch; +} + void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) { buffUnitCirclePoints.reserve(nFacets + 1); @@ -11257,98 +11342,37 @@ void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) const GPUTask& olc::Draw2D::Circle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 1); - buffColours.reserve(nFacets + 1); - - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i] = transformAffine.forwardRound(buffUnitCirclePoints.data[i] * radius + pos); - buffColours.data[i] = col; - } - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - olc::Structure::Line, - buffPoints.data, - buffColours.data, - tint - ))); + return Ellipse(pos, radius, radius, col, tint, nFacets); } const LineBatch& olc::Draw2D::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) { - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - for (int32_t i = 0; i <= nFacets; i++) - { - Line(batch, buffUnitCirclePoints.data[i] * radius + pos, col, buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()] * radius + pos, col); - } - - return batch; + return Ellipse(batch, pos, radius, radius, col, nFacets); } const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); - - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = col; - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i + 1] = transformAffine.forwardRound(buffUnitCirclePoints.data[i] * radius + pos); - buffColours.data[i + 1] = col; - } + return FilledEllipse(pos, radius, radius, col, tint, nFacets); +} - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); +const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, col, nFacets); } const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); + return FilledEllipse(pos, radius, radius, colInner, colOuter, tint, nFacets); +} - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = colInner; - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i + 1] = transformAffine.forwardRound(buffUnitCirclePoints.data[i] * radius + pos); - buffColours.data[i + 1] = colOuter; - } - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); +const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); } const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { + // Fundamental for outline circle/ellipse with colour solid/gradient PrepareTargetForHW(); if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) @@ -11374,6 +11398,8 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) { + // Fundamental for batched outline circle/ellipse with colour solid/gradient + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) RedefineUnitCircleBuffer(nFacets); @@ -11391,33 +11417,18 @@ const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& po const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); - - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = col; - 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}); - buffColours.data[i + 1] = col; - } + return FilledEllipse(pos, rx, ry, col, col, tint, nFacets); +} - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); +const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); } const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { + // Fundamental for filled circle/ellipse with colour solid/gradient + PrepareTargetForHW(); if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) @@ -11443,6 +11454,27 @@ const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, ))); } +const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + // Fundamental for batch filled circle/ellipse with colour solid/gradient + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + olc::vf2d p1 = { pos.x + rx * buffUnitCirclePoints.data[i].x, + pos.y + ry * buffUnitCirclePoints.data[i].y }; + + olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, + pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; + + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); + } + + return batch; +} + const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -11627,6 +11659,11 @@ const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& return FilledTriangle(p1, p2, p3, col, col, col, tint); } +const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return FilledTriangle(batch, p1, p2, p3, col, col, col); +} + const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -11640,6 +11677,19 @@ const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& ))); } +const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); + size_t idx = batch.task.vertexBuffer.size() - 3; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + const olc::vf2d a3 = transformAffine.forwardRound(p3); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + const GPUTask& olc::Draw2D::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); @@ -11851,7 +11901,9 @@ const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) FilledBatch olc::Draw2D::CreateFilledBatch() { - return FilledBatch(); + FilledBatch b; + b.task.structure = olc::Structure::List; + return b; } const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) From c44e02a34d55c24ebbf8b12d408d7e76161cd1d1 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 25 Jan 2026 12:19:24 +0000 Subject: [PATCH 08/56] Added "BatchesOfFills" example and imp-table --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 9 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/config.h | 1 + dev/src/draw2d.h | 162 ++++++++++++++-- examples/olcPGE3_BatchesOfFills.cpp | 182 ++++++++++++++++++ olcPixelGameEngine3.h | 32 +-- 6 files changed, 354 insertions(+), 35 deletions(-) create mode 100644 examples/olcPGE3_BatchesOfFills.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 762093d9..fc6ae101 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -25,6 +25,7 @@ true true + true true @@ -32,10 +33,10 @@ true - false - false - false - false + true + 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 1a1d7f27..b7a060ce 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -60,6 +60,9 @@ Source Files + + Source Files + diff --git a/dev/src/config.h b/dev/src/config.h index f40f43cf..0f68626c 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -111,6 +111,7 @@ #define OLC_GPU_MAX_VERTICES 8192 #define OLC_GPU_ERRORCHECK 0 #define OLC_MSAA_SAMPLES 4 +#define OLC_DEFAULT_CIRCLE_FACETS 32 #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " diff --git a/dev/src/draw2d.h b/dev/src/draw2d.h index fe6656bb..1a8079f2 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw2d.h @@ -19,6 +19,138 @@ #include "font.h" //! END CUSTOMHEADER + +/* + Draw Function Implementation Status Table + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Note 0: [#] = Implemented, [ ] = Not Implemented + + Note 1: Functions that accept a 'batch' parameter add the drawing command to the batch + and return the batch reference for convenience. + + Note 2: Functions that accept a 'tint' parameter apply the tint to the entire shape. + + Note 3: Functions that accept 'colInner' and 'colOuter' parameters create a radial gradient + from the center to the edge of the shape. + + Note 4: Functions that accept a 'structure' parameter define how the polygon is constructed + (e.g., TriangleFan, TriangleStrip, etc.) + + Note 5: Functions that accept 'facets' parameter define the number of segments used to + approximate curves (circles, ellipses, rounded rectangles). + + Note 6: Functions that accept 'uv' parameters define texture coordinates for textured shapes. + These are typically in the range [0.0, 1.0]. + + Note 7: Functions that accept an 'image' parameter use the specified image for texturing. By + deafult this is the whole image, but can be a sub-region defined by an ImageRegion. + ImageRegions are defined in pixel coordinates relative to the top-left of the image. + + Note 8: Batched images retain individual tints, but use image supplied by CreateImageBatch(). The + image supplied to these functions is actually an image region structure, allowing + sub-image rendering. When the batch is drawn, a global tint can be supplied. + + Note 9: A batch object must be constructed before use with the appropriate CreateXXXBatch() + + Note A: TexturedXXX batch functions use an image batch, naturally restricting all shapes in the + batch to use the same texture source. + + + [#] Clear(col) + [#] Pixel(pos, col) + + [#] Line(p1, p2, col, [tint]) + [#] Line(p1, c1, p2, c2, [tint]) + [#] Line(batch, p1, p2, col) + [#] Line(batch, p1, c1, p2, c2) + + [#] Rect(pos, size, col, [tint]) + [#] Rect(pos, size, colTL, colTR, colBL, colBR, [tint]) + [#] Rect(batch, pos, size, col) + [#] Rect(batch, pos, size, colTL, colTR, colBL, colBR) + + [#] FilledRect(pos, size, col, [tint]) + [#] FilledRect(pos, size, colTL, colTR, colBL, colBR, [tint]) + [#] FilledRect(batch, pos, size, col) + [#] FilledRect(batch, pos, size, colTL, colTR, colBL, colBR) + + [#] Circle(pos, radius, col, [tint], [facets]) + [#] Circle(pos, radius, colInner, colOuter, [tint], [facets]) + [#] Circle(batch, pos, radius, col, [facets]) + [#] Circle(batch, pos, radius, colInner, colOuter, [facets]) + + [#] FilledCircle(pos, radius, col, [tint], [facets]) + [#] FilledCircle(pos, radius, colInner, colOuter, [tint], [facets]) + [#] FilledCircle(batch, pos, radius, col, [facets]) + [#] FilledCircle(batch, pos, radius, colInner, colOuter, [facets]) + + [#] Ellipse(pos, radiusX, radiusY, col, [tint], [facets]) + [#] Ellipse(pos, radiusX, radiusY, colInner, colOuter, [tint], [facets]) + [#] Ellipse(batch, pos, radiusX, radiusY, col, [facets]) + [#] Ellipse(batch, pos, radiusX, radiusY, colInner, colOuter, [facets]) + + [#] FilledEllipse(pos, radiusX, radiusY, col, [tint], [facets]) + [#] FilledEllipse(pos, radiusX, radiusY, colInner, colOuter, [tint], [facets]) + [#] FilledEllipse(batch, pos, radiusX, radiusY, col, [facets]) + [#] FilledEllipse(batch, pos, radiusX, radiusY, colInner, colOuter, [facets]) + + [#] RoundedRect(pos, size, radius, col, [tint], [facets/4]) + [#] FilledRoundedRect(pos, size, radius, col, [tint], [facets/4]) + + [ ] RoundedRect(batch, pos, size, radius, col, [facets/4]) + [ ] FilledRoundedRect(batch, pos, size, radius, col, [facets/4]) + + [#] Triangle(p1, p2, p3, col, [tint]) + [#] Triangle(p1, c1, p2, c2, p3, c3, [tint]) + [#] Triangle(batch, p1, p2, p3, col) + [#] Triangle(batch, p1, c1, p2, c2, p3, c3) + + [#] FilledTriangle(p1, p2, p3, col, [tint]) + [#] FilledTriangle(p1, c1, p2, c2, p3, c3, [tint]) + [#] FilledTriangle(batch, p1, p2, p3, col) + [#] FilledTriangle(batch, p1, c1, p2, c2, p3, c3) + + [#] Polygon(structure, points[], col, [tint]) + [#] Polygon(structure, points[], colours[], [tint]) + [ ] Polygon(batch, structure, points[], col) + [ ] Polygon(batch, structure, points[], colours[]) + + [#] FilledPolygon(structure, points[], col, [tint]) + [#] FilledPolygon(structure, points[], colours[], [tint]) + [ ] FilledPolygon(batch, structure, points[], col) + [ ] FilledPolygon(batch, structure, points[], colours[]) + + [#] TexturedTriangle(p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image, [tint]) + [ ] TexturedTriangle(batch, p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image) + + [#] TexturedPolygon(structure, points[], colours[], uvs[], image, [tint]) + [ ] TexturedPolygon(batch, structure, points[], colours[], uvs[], image) + + [#] String(pos, text, col, [scale], [font]) + [#] StringProp(pos, text, col, [scale], [font]) + [ ] String(batch, pos, text, col, [scale], [font]) + [ ] StringProp(batch, pos, text, col, [scale], [font]) + + [#] Image(image, pos, [scale], [tint]) + [#] ImageRect(image, pos, size, [tint]) + [#] ImageQuad(image, vTL, vTR, vBL, vBR, [tint]) + [#] ImageQuad(image, points[4], [tint]) + [#] ImageRotated(image, pos, angle, [offset], [scale], [tint]) + + [#] Image(batch, image, pos, [scale], [tint]) + [#] ImageRect(batch, image, pos, size, [tint]) + [#] ImageQuad(batch, image, vTL, vTR, vBL, vBR, [tint]) + [#] ImageQuad(batch, image, points[4], [tint]) + [#] ImageRotated(batch, image, pos, angle, [offset], [scale], [tint]) + + [#] Batch(LineBtach, [tint]) + [#] Batch(FilledBatch, [tint]) + [#] Batch(ImageBatch, [tint]) + +*/ + + //! START DECLARATION #if !defined(PGE_DRAW2D_DECLARED) namespace olc @@ -205,7 +337,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a circle outline with a single colour into a batch const LineBatch& Circle( @@ -213,7 +345,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled circle with a single colour const GPUTask& FilledCircle( @@ -221,7 +353,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled circle with a single colour into a batch const FilledBatch& FilledCircle( @@ -229,7 +361,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded circle with a radial gradient const GPUTask& FilledCircle( @@ -238,7 +370,7 @@ namespace olc const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded circle with a radial gradient into a batch const FilledBatch& FilledCircle( @@ -247,7 +379,7 @@ namespace olc const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Ellipses === @@ -258,7 +390,7 @@ namespace olc const float& ry, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws an ellipse outline with a single colour into a batch const LineBatch& Ellipse( @@ -267,7 +399,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled ellipse with a single colour const GPUTask& FilledEllipse( @@ -276,7 +408,7 @@ namespace olc const float& ry, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled ellipse with a single colour into a batch const FilledBatch& FilledEllipse( @@ -285,7 +417,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded ellipse with a radial gradient const GPUTask& FilledEllipse( @@ -295,7 +427,7 @@ namespace olc const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded ellipse with a radial gradient into a batch const FilledBatch& FilledEllipse( @@ -305,7 +437,7 @@ namespace olc const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Rounded Rectangles === @@ -316,7 +448,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 8); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // Draws a rounded rectangle outline with a single colour into a batch const LineBatch& RoundedRect( @@ -325,7 +457,7 @@ namespace olc const olc::vf2d& size, // Size of bounding rectangle const float& radius, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 8); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // Draws a filled rounded rectangle with a single colour const GPUTask& FilledRoundedRect( @@ -334,7 +466,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 8); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // === Triangles === diff --git a/examples/olcPGE3_BatchesOfFills.cpp b/examples/olcPGE3_BatchesOfFills.cpp new file mode 100644 index 00000000..a12add2e --- /dev/null +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -0,0 +1,182 @@ +/* + olc::PixelGameEngine3 Example - Circles & Ellipses + + Demonstrates circle and ellipse drawing techniques + + 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 circle & ellipse drawing. +// This class overrides the olc::PixelGameEngine base class by +// implementing the OnUserCreate() and OnUserUpdate() functions +class Example_RoundThings : public olc::PixelGameEngine +{ +public: + Example_RoundThings() + { + + } + +protected: + // We accumulate total time for some animation + float fTotalTime = 0.0f; + + std::vector vecBubblePos; + std::vector vecBubbleVel; + std::vector vecBubbleCol; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Prepare bubble positions and velocities + for (int i = 0; i < 20; i++) + { + vecBubblePos.push_back({ + float((rand() % (GetDefaultImage().Size().x - 64)) + 32), + float((rand() % (GetDefaultImage().Size().y - 64)) + 32) }); + + vecBubbleVel.push_back({ + (float(rand() % 2000) - 1000.0f) / 100.0f, + (float(rand() % 2000) - 1000.0f) / 100.0f }); + + vecBubbleCol.push_back(olc::Pixel(rand() % 256, rand() % 256, rand() % 256, 128)); + } + + + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear screen to dark blue + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + auto batch = draw.CreateFilledBatch(); + + draw.WorldRotate(float(TotalTimeElapsed()) * 0.5f, ScreenSize() * 0.5f); + + // Draw a circle outline + //draw.Circle({ 32.0f, 32.0f }, 20.0f, olc::Colour::WHITE); + + // Draw a filled circle + draw.FilledCircle(batch, { 96.0f, 32.0f }, 20.0f, olc::Colour::GREEN); + + // Draw a filled circle with a white outline + draw.FilledCircle(batch, { 160.0f, 32.0f }, 20.0f, olc::Colour::DARK_RED); + //draw.Circle({ 160.0f, 32.0f }, 20.0f, olc::Colour::WHITE); + + // Draw a gradient circle + draw.FilledCircle(batch, { 224.0f, 32.0f }, 20.0f, + olc::Colour::RED, olc::Colour::YELLOW); + + + + // Draw an ellipse outline + //draw.Ellipse({ 32.0f, 96.0f }, 20, 10, olc::Colour::WHITE); + + // Draw a filled ellipse + draw.FilledEllipse(batch, { 96.0f, 96.0f }, 10, 20, olc::Colour::CYAN); + + // Draw a filled ellipse with a white outline + draw.FilledEllipse(batch, { 160.0f, 96.0f }, 20, 10, olc::Colour::DARK_GREEN); + //draw.Ellipse({ 160.0f, 96.0f }, 20, 10, olc::Colour::WHITE); + + // Draw a gradient ellipse + draw.FilledEllipse(batch, { 224.0f, 96.0f }, 10, 20, + olc::Colour::BLUE, olc::Colour::CYAN); + + + // Draw a circle outline with fewer facets + //draw.Circle({ 32.0f, 160.0f }, 20.0f, olc::Colour::WHITE, olc::Colour::WHITE, 5); + + // Draw a filled circle with fewer facets + draw.FilledCircle(batch, { 96.0f, 160.0f }, 20.0f, olc::Colour::MAGENTA, olc::Colour::WHITE, 6); + + // Draw an ellipse outline with fewer facets + //draw.Ellipse({ 160.0f, 160.0f }, 20, 10, olc::Colour::WHITE, olc::Colour::WHITE, 5); + + // Draw a filled ellipse with fewer facets + draw.FilledEllipse(batch, { 224.0f, 160.0f }, 10, 20, olc::Colour::YELLOW, olc::Colour::MAGENTA, 6); + + + // Bubbles! + for (int i = 0; i < vecBubblePos.size(); i++) + { + // Update position + vecBubblePos[i] += vecBubbleVel[i] * fElapsedTime * 10.0f; + + // Bounce off walls + if (vecBubblePos[i].x < 32.0f || vecBubblePos[i].x > float(GetDefaultImage().Size().x - 32)) + { + if (vecBubbleVel[i].x < 0) + vecBubblePos[i].x = 32.0f; + else + vecBubblePos[i].x = float(GetDefaultImage().Size().x - 32); + + vecBubbleVel[i].x = -vecBubbleVel[i].x; + } + + if (vecBubblePos[i].y < 32.0f || vecBubblePos[i].y > float(GetDefaultImage().Size().y - 32)) + { + if (vecBubbleVel[i].y < 0) + vecBubblePos[i].y = 32.0f; + else + vecBubblePos[i].y = float(GetDefaultImage().Size().y - 32); + + vecBubbleVel[i].y = -vecBubbleVel[i].y; + } + + // Draw bubble (filled circles with outline) + + // Note the round() - This is to demonstrate that sub-pixel positions + // can affect the appearance of the shapes due to the way they are rasterised + draw.FilledCircle(batch, vecBubblePos[i].round(), 16, vecBubbleCol[i] * 0.5f); + //draw.Circle(vecBubblePos[i].round(), 16, vecBubbleCol[i]); + } + + draw.FilledRect(batch, { 10.0f, 200.0f }, { 236.0f, 20.0f }, olc::Colour::DARK_GREY); + + olc::vf2d vMouse = draw.ScreenToWorld(mouse.GetPosition()); + draw.FilledTriangle(batch, + { vMouse.x - 0.0f, vMouse.y - 50.0f }, + { vMouse.x - 30.0f, vMouse.y + 30.0f }, + { vMouse.x + 30.0f, vMouse.y + 30.0f }, + olc::Colour::WHITE); + + + if(mouse.GetButton(0).bHeld) + draw.Batch(batch, olc::Colour::GREEN); + else + draw.Batch(batch); + + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_RoundThings 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 d96410c0..ba750da7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -246,6 +246,7 @@ #define OLC_GPU_MAX_VERTICES 8192 #define OLC_GPU_ERRORCHECK 0 #define OLC_MSAA_SAMPLES 4 +#define OLC_DEFAULT_CIRCLE_FACETS 32 #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " @@ -2045,7 +2046,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a circle outline with a single colour into a batch const LineBatch& Circle( @@ -2053,7 +2054,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled circle with a single colour const GPUTask& FilledCircle( @@ -2061,7 +2062,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled circle with a single colour into a batch const FilledBatch& FilledCircle( @@ -2069,7 +2070,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded circle with a radial gradient const GPUTask& FilledCircle( @@ -2078,7 +2079,7 @@ namespace olc const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded circle with a radial gradient into a batch const FilledBatch& FilledCircle( @@ -2087,7 +2088,7 @@ namespace olc const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Ellipses === @@ -2098,7 +2099,7 @@ namespace olc const float& ry, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws an ellipse outline with a single colour into a batch const LineBatch& Ellipse( @@ -2107,7 +2108,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled ellipse with a single colour const GPUTask& FilledEllipse( @@ -2116,7 +2117,7 @@ namespace olc const float& ry, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled ellipse with a single colour into a batch const FilledBatch& FilledEllipse( @@ -2125,7 +2126,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded ellipse with a radial gradient const GPUTask& FilledEllipse( @@ -2135,7 +2136,7 @@ namespace olc const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded ellipse with a radial gradient into a batch const FilledBatch& FilledEllipse( @@ -2145,7 +2146,7 @@ namespace olc const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, - int32_t nFacets = 32); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Rounded Rectangles === @@ -2156,7 +2157,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 8); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // Draws a rounded rectangle outline with a single colour into a batch const LineBatch& RoundedRect( @@ -2165,7 +2166,7 @@ namespace olc const olc::vf2d& size, // Size of bounding rectangle const float& radius, const olc::Pixel col = olc::Colour::WHITE, - int32_t nFacets = 8); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // Draws a filled rounded rectangle with a single colour const GPUTask& FilledRoundedRect( @@ -2174,7 +2175,7 @@ namespace olc const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = 8); + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // === Triangles === @@ -11194,7 +11195,6 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc: const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) { - return Line(batch, p1, col, p2, col); } From 3c307f06c181dce2fa28309f6bf0281a58f07d12 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 25 Jan 2026 12:23:38 +0000 Subject: [PATCH 09/56] clarified example --- examples/olcPGE3_BatchesOfFills.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/olcPGE3_BatchesOfFills.cpp b/examples/olcPGE3_BatchesOfFills.cpp index a12add2e..4762d00b 100644 --- a/examples/olcPGE3_BatchesOfFills.cpp +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -1,7 +1,7 @@ /* - olc::PixelGameEngine3 Example - Circles & Ellipses + olc::PixelGameEngine3 Example - Batches of Filled Primitives - Demonstrates circle and ellipse drawing techniques + Demonstrates filled shapes drawn using batches for efficiency Licenced under the OLC-3 License */ @@ -12,13 +12,13 @@ #define OLC_PGE3_APPLICATION #include "../olcPixelGameEngine3.h" -// Example application demonstrating circle & ellipse drawing. +// Example application demonstrating batches of fills drawing. // This class overrides the olc::PixelGameEngine base class by // implementing the OnUserCreate() and OnUserUpdate() functions -class Example_RoundThings : public olc::PixelGameEngine +class Example_BatchesOfFills : public olc::PixelGameEngine { public: - Example_RoundThings() + Example_BatchesOfFills() { } @@ -59,11 +59,15 @@ class Example_RoundThings : public olc::PixelGameEngine // Clear screen to dark blue draw.Clear(olc::Colour::VERY_DARK_BLUE); + // Create the batch for filled shapes auto batch = draw.CreateFilledBatch(); + // For additiona ltest, spin the world around the center of the screen draw.WorldRotate(float(TotalTimeElapsed()) * 0.5f, ScreenSize() * 0.5f); - // Draw a circle outline + // === Code mostly stolen form olcPGE3_CirclesEllipses.cpp === + + // Draw a circle outline - CANT DO THIS WITH FILLED BATCHES!!! //draw.Circle({ 32.0f, 32.0f }, 20.0f, olc::Colour::WHITE); // Draw a filled circle @@ -142,8 +146,12 @@ class Example_RoundThings : public olc::PixelGameEngine //draw.Circle(vecBubblePos[i].round(), 16, vecBubbleCol[i]); } + + // Test a filled rectangle draw.FilledRect(batch, { 10.0f, 200.0f }, { 236.0f, 20.0f }, olc::Colour::DARK_GREY); + // Test a filled triangle that follows the mouse. Notice use of ScreenToWorld to + // convert from screen space to world space coordinates olc::vf2d vMouse = draw.ScreenToWorld(mouse.GetPosition()); draw.FilledTriangle(batch, { vMouse.x - 0.0f, vMouse.y - 50.0f }, @@ -151,7 +159,7 @@ class Example_RoundThings : public olc::PixelGameEngine { vMouse.x + 30.0f, vMouse.y + 30.0f }, olc::Colour::WHITE); - + // If mouse button held, change batch colour to green if(mouse.GetButton(0).bHeld) draw.Batch(batch, olc::Colour::GREEN); else @@ -168,7 +176,7 @@ class Example_RoundThings : public olc::PixelGameEngine int main() { // Construct demo application - Example_RoundThings demo; + Example_BatchesOfFills demo; // Create "screen" of 256x240 "pixels" // with a pixel size of 4x4 actual screen pixels From 41acdf431a6da7550944d7566869c7d83725a6d6 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 25 Jan 2026 14:24:38 +0000 Subject: [PATCH 10/56] added "Shockwave" example --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + examples/assets/gamescene.png | Bin 0 -> 44101 bytes examples/olcPGE3_Shockwave.cpp | 191 ++++++++++++++++++ 4 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 examples/assets/gamescene.png create mode 100644 examples/olcPGE3_Shockwave.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index fc6ae101..062eb59e 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -25,7 +25,12 @@ true true - + + true + true + true + true + true true @@ -50,6 +55,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 b7a060ce..68337fe7 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -63,6 +63,9 @@ Source Files + + Source Files + diff --git a/examples/assets/gamescene.png b/examples/assets/gamescene.png new file mode 100644 index 0000000000000000000000000000000000000000..b1aaa7370be7482763df96fff7c1ffa2a3ead38f GIT binary patch literal 44101 zcmagF1z1~MvoMMiC>Ef&26xw@CAdqm0tEsT3GPtb-3k3AA^y1(ja>; zFE>d(K3`v7USAg_58ve&Lei=NqKiBPP;O}P3r)TTw>g@rsRrIlS@nZT%7RcEo6+CRMyllb9uQW)IUr>~XUw}tY z#6UnuQb<@*NRW#k`S~woO;;NSJOBSLWD!A0L4MPJBO)!s#>&g;{|EKI#4Pz2@n5VU z4f6lW$G^uYxI$e$kSrlN7y4)Be}hyN6m&dX?Hr&;n5Ulda|TsK1yKPBQBfX2UV(pb z|7uH8$HC7QY7BO8wsrADS{8C<5z+q_)a1WIh5sdrf5!eZyFSvTkW>7Nr8!QH(4J%O*M|4=Hzeb!NO0V9A*I8Vwc; zd?PaUlNadmyCXrR=_wQ)w=}8o`6HS$rjxu5seIihy&0vS`DbW2)^>}Ft|8Ba4kyFe zat*y|TL{lJ<`Wa6?mW+)1g|}L+NP)dtoNNYrM^&^?w6&}0&v30xj5|0=)DsRXHKuU z)YQ*d;A`DU$H}(oyqn+44inW+#Di6XE0tzC*Yh)5yt2>oy>7;>gs5;Ls&wH++#7y4 zTpar3S=rx9s*ExRgYvUyYjPGTO_pujt$=|oUd9oAn z+nk&D9`Cp_m=&{Jbd4&yIBQwEIGHpj_a4`JO_${Q>n+H`-?J?TA7HGJz1elXJ)3Ut zOXT%GIs6sgxShE-BkU&~_Q&78D>_&4 z!|N!|bcZFH>vQvVbjMRJ14JS>H?Jus;X@XzV_@UB3#91|~{WdTwukN*~wz^z8 z&FJv3OtM6%l7uz=?ZjzLZPJRiH{)Mj-H^vwM>CEotVyeWf z+?i@CE(=#dQ)e`zI73Yc8+`enyZL=TxzP; zuy$x8b@!&=Q+U3x<`6#=W}2zSqM9zODLwUMXxcsBqH*^&m{idr_w-V!Z06vih1-*6 z)SU*X`dwHxU9BSBHs_$rA$P=`CfbTr(Tbwdfl5)g(`=qzddkyWEWVThAT_nsxZ*bh2f*;R(VWNc_Z%913m7AI%rf?TizMohwAvSbq!|fe&T&zLu zoUo?(g15O?^45T5|5q+9t`!$wTU%QQq|AWxH$O_Nqz_LrWbI15La%W_Xr#$KVxZsK z94Ig{dvJl2{H@F^+DYNAfNE}KrW$5st1)oHTdo%ddr@z=CC;0v7BNt4#@pSnd~hK( zrN19eN{L3bxVYhF%$d0X=dj;#D92o+i-v-&rgB(E~|ilgLACQzpG0i$dL;;ri zg!!PWL4XxcfxX|IX2H9A#f2go^li^eY6@M;&hl7#O8vXArv9~rYPxAXR-Zgq;Vx1e zKEikkpU9tmX$bXw!1-S3;FyRLUcBO>AXm7H)aQnqed3-^w~k)fOenUTAE{!*00SW0 zq7l1+W;DzYE(BJh87@ic7V?rz5S0yWG%X{rhB<(l72C6Au3RB_Bab}Mrd~Qp=4`kEDe;4-tfp`qO#GE|itAA}W ze{k`xYR3M@h1{_^AzSb9vxW@TxZRtn-szx`5&#rwl(&73NB(Z9CD> zc5X%}&iczd){KZp-G|-3F+Wvq_g}tf=&Ezyi*g}Vyvk4P^%EopPOW9dHf#yXzCAyD zvXYPj<-`5Rn3+o2a1c%cU;%_{Rq59oTA`2zk}3{u5ZMAo5(OM`Wv(NAkB6ECcD_C7 zNl0e4p9$!3N$fl9?0y%89dnQ(0bo5(Ip|a= zdR@q8MDxU(BuRj<;MBFd5h2;|m)45w$B&E0R}1vMeSU%-W=NkFK-+tkTyAoSPoB*e z5>1He)xCn0tjmXkAJ<1ix}+RzH@$1fy9>#{AIDcOg4vUvv-chwab`|TXfAk{Kvg39 z1`Cq6g!3kZUQb%i$>pl3LB7tFz2n)fl9Z{$jk17`xWa=4Rd6xfX8y4GjL{ z`-#=DQDVy5yX~t+nccy6SGE&kI)mPB}|_6 z9zTVJJOEC)t}s(u%c}1K9UAF2&d&JjCEk}POkb{oz_gcLv6l5# z&9evIV3ZLost|AQr)31dLT1%;=6b_~q>$~zH`iM|87VAmTKW9lvZE>Vj|f?(q7EKK z{D+(RnaAt%lk4AGOeqE8?wciSE!2+21Cq3Ss z-BV7nTZTN`$k)yh19fKYg~1pc$yg(#c;Ur*HKA5MMBe*dNBjHx?F`!K87XXir+w{o z0a7p4Q|M(M#`|qFCt;1O^tbR)*2aUl!x7oN)vL_y?JW|wcQH349|+#8 z_3e(=uRYjEg`bch`F1>U4q z5x$2OF#yqUNvtufwdbtsH?2qVNVM;!+q!so5isZYG%2!PmI9A|RmWa6y*N91Xk%xG zbzVYLZ2t)!(v@T%oX&K1DaC%c-8Vx<>E4_SP`iHA1t-%!yS)6+cmL<0QPzDEt}J`H zaqQpeyT-K0=0Y2Mb+8(IckK?mnU0ZZ>+R(QExZc(JKUvX0}Ctp>k!-HSKZsK#Md@o zg#MVkdev7w7EV+5f@XSm^Y{^_ z>j2mFueG*;DbK>Qd`kYnE17@+#^VucS1I>Rj1bJ2uK0_Ki-g=GH}Si3^Bm9{8*|pH zpf`wvgM;11&MB!iAdVXXnXb6Jxw87D0TyO283+sRTeBB-b5q3lgdNm$s#S$fR_waX zg+-|MgCfJ0(va*3gMj9NG962A#Bcy_t8qBQk2M+MO_C#pH&kEGEnfDrAfo*K)fNZK0UtiMz^(_m{ zi;UhtE`CmV^2tmoFZN!Eb+LI?j%(ldFFhrt81ROJzv~a6`KPP{5b32F6 zeSA9wTd=tObzc;)keB=Ta1|KC7rY3NQsrcV`yGa9zw&QB+v{%mUatX8H%O*miyCqb z(Y@H`DtL8k{8+*!c%KpMxAO%uFauf3q8Dj7Ntn7?dmK{!CXBcUz-TnPF1Bbi;(Q83 zKGuzUh)jqusQ>xIDpj{GBS252r-l+4Jvc!W2D+phVou<>t9zu;?yvW$jW#81iC+X1 z_&w1Q-4XeXJ?aklYL(CKRFje*O^$&&Rd%XlAKNfsS)5ieU>RCpF)8WFEH!wre<8`M z*=aTs3Go2xQWHwAcTBPe#pR2;qum7DUH?u<4hRVzN11ywA+qqp>$_(1780~My4n;l zqkDyvxl~QMwyf+~7R>iv&iT#h-EP$?MepI4Z-=iBM}cWWnpejm%4hYB+_Lvg3gsKU zjm&JmdA#(GW-&U{HvBXMIH^r>U>+kQqo7-N^Q+}o6fN6GWvZ@`UkB$_E=a9SLC{?c zpudKY@%&V=BIG>x%Xks};MDYQqzVH6vNv_rx22fb*D-Dh+Amu7xvwT<0xcl*-htzF z+PrP_V1E{-j5TeMTN(UeIjXLeo2eXE82+L-bG|{Ta%@Jn=ST%Y!6cctvorJYda4qt=-bHwdtC7y73+XtrSDvy}xYB(r$ZPuB z$6#qy<)H2&fAzCP?F{n!lQcdl@s8br&u2Z|eNr@k_X>7)Eedr-Q$I2apyuK;bVO-! zCvVP~=pT~FX<5zMk-02y45%k944F`y@(j!7CFHercE)wa(lNN`a%T4KdEDfD=y*I8 z^Og9uj)+f~(C!nEx{16RHtg#4AGla}MQAD23R#^+S`C{^f^rtRq3oYD!jQY2qdz;C z{v_@}K|wZiyNx%8pTs#&cMSjBG=BV%Ymu8RXna9+cCq-jyWCVLQ@py*I7zYDj<#eFfWDF!P-|#cfZ!8JcjVaXk>%$ za-ACdrvg_qMsGF6R=l>7#AE`LCPU^M?oXtr&VDef7J7%BB`PEzcl$P`9OVGfSMA=o zbUFLoT{ov?@9(Co^X34iXs=*Jp6({#+=-n(TV+-(b+msSj#H+epV4QG#RfjitV%

^-tE|rI8EizjPA5Ve0uO1D1{icg$B&R@Ehp9;zLEU{f62IDi$40ezUkrlsGqII-I^{j|A3 z5$*47Ybms&zojWZh&Q_@2{7fFf?fVByAVqd7UDh}?i|%*xzp^nRCO~=z5B{g$Qu04 z6h~dv_M_xt z?{Sf&Hz%G#5XbPBvsu#AAu^!&$M1V}_4UnU!YPuYk?*hhUbz)U4_Q^{4e;Nd)9wt^ zSKS}Vc3Xhw7Zx6`31!b)xX^(&Lw$*-D)a&wE+2o798Sg@->aSOFZ9p~h|)E59s{?! z^tU$Oq5gelvt*lr3_pb&GW7*lOZ?fUex)m*v}r@V0rFVxLA*9+$W*JCbBwYQr>Mlk zPJDSBbnQRN32K4CD&}yh#D*ZueiYGGjSK4O+uIzE3541x7m%U^`sG;qRob>00Q}FN zKflQ%okY&!UdFJ!BZUAWr7{$S?zXffgzlqH-wtPxbLcfX#2WV>5ps;?8 zuh|zT05W@#Mk#E&EMm!hX{lRA(8;{%SaB&+00a#R!Rx3*Hv8Pxk;$rz@a0nheV{wk z-JsJQJ_4PDrtn|W!`(30Kbw-k+yknx>$zR-{;A&WMqOFjc@qlIv?A(K?N3T zBrckc> zXZ_~Y0< z^QdAXse$uCp$)mGK!jrcTr^dQ6suDaiEO@jY0M{H<2ag; zSOzXNt>0v6^$FGOBjYnkc;=RU!v!s>cm(OmQUxzS^Xv~%H^VZp9h8_7@0ojbli<*^!xk=sR` zezx7Xza+??k<(D^t1m1p@Huob3yV<1PqBhb3AsSAoe3#_kV!0wl?9-1iw|&YL9g}l z7BlAKhu}cBxz>B1?pO7OY1Ya{zE)e|3aesL(l=GE=J)GfG=+UTy@+Q+Tty~o&Mb9j z1HKK!h%soAKr+*e+-TNCeM3XLDNk&KAgUvN+d3X)jXy7N0N(FW?3Zb;VW+MqVoG?% zP?aWpO>NAlQ(|hS5|DWh3I~l)TLHYZaj1APlI!f|2l)?ux^+oK@-&SN!0TTFdbBK2 zxB%Z1qEMK@a+dKw!Mf))&nDO&ML}^Hk2H?r3uaG0Zu*DI_@6XiYr7uZ&zZl^rUiO- z>{)nbKAv(_STu$lwyxy4XcxjY>gj)_u1WpydUaF!rIq)yjJNU^Hzjj_ZYlSd=X=k5 zpg(Ji=id>gy|nY?^tnwkdSxOwayPf~8xQMxo-5p`;Z(bjGw80h zK)>E=yD=v3sMbO+Wbfzi5`~y0gy_=h*+=BwQTqqo}X~$D)wVi@2BA zTY>Igb0MG3dXDcZr>}zUB-gGA2zxzW44ox!5p9Bf?Hh3SbS4R-bokrg>rc^GqRgVTe%N4pxMKgfTF1;i1=K7>Yzsgc}X8;}%BhQ5+GG`=L zZCl@zfh8#zXpOpyjgDTgUQST~=BBtL;fjONL=Y2Sq-lc(>hh4F0 zJ*erMu(^o|Tn%}d*%Ecds$@;4t-lIC5 zgl?~p%_XCY8pK+%ML1iQ+2vsHa%p;A)LUod5#e2*ub!MzxqFqyYkYaBh#{kY1eP4k zHJvP`0JjnSid2FbgzbwN9xMd}up>*>$>Pyxaqn&$SQ5+BrHDtQqZQivpMbU()trhuS z#`vGbNd*U!Yb=RV4|_I#F4xGY5TPM_x>E__wB9$YxiQ+KJ5%%66C5rwLaHAMZhrmd zB9)ga-s&9-%vAG2P<@bdI#Ccl>xj0lKUYUau!&7DQ$I(+GEYr`;Y1~*ND#(d{+24i z@v~K)KwtDtbFy$5c1*_OrVfFqfcHAPw55&<_U%osVN&Yg)JH&9WYmuzj5~94FDL|? zMh~uOw{U6E9(4OYYrW%#v3c{eoqXmv((y?*^XJYXc}LUM-@i>cmz%WpE)p*n*RI$m zwXa1qT-zXh!Z;DQWd z2q;{z7X}5kDr6)T!XV z`mAn3o`QKgr$XwXqAE{ItU_dnJ@bB((cU74Tba6e4K2{vWUGBY2qYqq&)&(%i1|S- zikW`V!w-*P3{w1)U1IMd0RB{JZ&?d1cXe<~_r0XoFQVu81mr+zx=?)dv8uOF)q^)5oyu0IRgjP-0z=K0}!TMe)!cc1YJ<6XQ?dhET4j4@2V^)c@dGfU~LCqnmW-34r zHv)T3$)6RAkQ+4t!m!t=wor&`L?6x4zp)Q*F3`!u4ZgVZN-43dQ~jl#nHj%2V2J{= z+P5^WRUd8NnAu^%y^BpW_+1&bxj#4vu$nl zYu~`Oi(VEux-gG@Ei9yzuq7VJZr~mUswP4$FEuA&-A`}M`-o|J6;#wL7s~-ioz(=b z>qOYse=QU?c#DSeP?9A<^-^JMa? zST|DK#KdIgByI2ut31YMVo~%Og>BskW;rtW5us|j<>iU?^T`o9Y~+aZx8{=_0z4Lf;UN)EB!*#OeP)b{nn1P|9qXedLV)>w2WqSKn|tC1}cn>{>18& zuIQ86jsxOuJ5P!7Me&H)DfV1E_5dRD=*yn~m;eF+31RAq zZ|Tfd5wv+uqwadUxFn=%;pRE`UB980nqXs(dc)-(Kfrzi`BZ`O==LZUzH804u|zqh zlS`hMV}l-v(l_NweIR;u!W_gB#uEz|mH|NTH`}2!Z0^YI1H}INsq}5ze7zLRDaK~Smi^}SbwM^v zZcac+CvuC@xZq#l)J@NxAlZJ&0YF1wR{o~fFkF$}&UNj6PqZJVOzKV|tQ{&J6+1=hX1Mf2DCV!zu#iDEpe6`WHdiAQiRDku))UdQpzlEhpF3!TR z+}GIa1g2i3zT3t2wGB0ZlVm|^>ynO^G z$qd!(fA_)(fj-W?=%Y|ao|%+vm((Qu#XKm;mS02)1&3nRRKItylfA#|K zF~3g~$bDgpsL;dV{OvKOEMk!Q15d+~HiIGPp~i%BG`h)~Yp#6wtzaiF1gtW9J)^?g zm5i_Go_196hc+?-+gn9=3BAxCXpk@-IXiu=?YOMR?`GMpNC_!6ZNryDCHV~ZG5?BU zI&HphvqSrQ%JBTU_qgZsS-T0}GE%$SP08@Lk z;ZtD+P@ow>Qlz~GG(vcFkolKFEX71wGifuT!tY5qk(T~0A56rE(?p&?{M@-#2qly# z^C(V!D=(m3C>?Ii<|h~QYU&c~4n0!zhWS`W!YH&TLaTw-1~EiTO>4&x?rO!*U!S~C zYgO!FZ)Sf{EVcUGiL|N)h*y2zJHZtpox3z-GNE<4cp*$VSn0RstEm71zL$BkChNJA z==ha+AswSZFDetBl?*T!>6XrWABo1m%C-<_(8`(HkJd?r5h|xkih*lTL`8jWppKrP z&N!nH14(ErQJGk3$_`l1Fs0hIb)0&@YA@n=PTwpI zX1MZXHa~REpBz>Gg=Uzu%{E*^NQwWp6hE7I88-txh>CCmimf-nTfV9L>6DJMgnY`)cn&~M;!QO?W#xYV<~};X+4?o(M15eJ09_Sv=`<6q8JTd z`9H&~A_#<08*I`1Z{lTa-!lNJqwH$suvKMz$PdEJ84rW5PplBc%7hYLh`Ayb(D736tX|wDz+@7xs4deIMD9<$1tA3U26IE;ib=24H zov3y!*5H|YQ>BZq6^r;;Jc`R+Q%k&KoI4^onxG?NR9BTd=Q_{vCQ3a_c^D$wS;vce(aW`b+?1LVfe2$UOex~sox=@78#Ly9YeGHjn=R2 zxYL^M==qC=h6YX}oAyXDj^Z6YQ5=F=h|qVh@Kb2?OWUlL7^Zrx#P_Q-5NDLE|laD~0bf;(MG9Rc%Zh-ZBDJ#*WcZ_{c>9!LL13W`HCSIJ1w=YSonkyXN${cZgA#)vW z^`~pD$WE3<+gLPkx0#yeoQmaP<60)RZKnO&JWoVo&C@S#)N{D#5tlCQpYWAx+mymu zi>Y#vd@ZY#p%LlP>=#0vhP<)56RwHvo7aC{Z3c5wNMOKoWk zn#x^G6cyZukFzR*Vf};%Oc~9{;RzASjpNxTb9$|CU`0Xfk=Q1e^hThHxO zDSN*26v{TR1{N#A$J3FjyUo1*b#{Z)d#z?CYR>Q=>>LNAK^B{=Q1;qk`cUV z9*11wAvwi<&izD*_6-L+E?)DO8F@<$ek%Hwp_MV0ZsGIrfk^Rl+hs=-mA3-b<-?2` z&QG+OD{6izOx*ch8sHq|tC2twVx{xu=4YvMV<0#*y8NP1*^B!k?_w?-OAXIl-qVFW z9#C0+8I2XNPGk^ac1gyd#=j}P1C{9>G-UkbP!iEUzl4zw5iIyD0c#XaYHTKG z_J*jrf#-o|9RWJTu@qDr$2(@ zf{wUCFB5bAwisbtBbyo;qgMGZ4?$+8&G?zf<`Jy+8IV)moa5W>4TIt|Ki4t3>u(7R z22ZQnrxhlI@MJA5&Ame6oj(a&?k`YUUX6b15qPtf^|DafcL$!lZ#myQ8asX|M=9pa zI0RTYO^gqjTQXk$D4Gn>JdnJ^py0C&tK3cmZt;Yz`vwS`;WwhDTcl;aUdC`>F8>#^FU@gDQVH63`oPB+(oiaEN@wJcUp8L zD^~CMl$4qk#vl7l{xGs~BQSnh3_a_W=7}qL zRb$}x{LR|hj~~eP63aX^;waC<8Ux^sB>Ew15Tvzsnac8+S8X5!3Ivv#yONa^q9tii zO~NKR&0dCfwzQN|^3D!^`GV{F*&RE^E?Iaua&Fn>0|#J;y^SCgs-%RIVMDRf-Swck2bN;E3i0JXx0qHx(1m%iuv?x0i_Jl6)n} zCkh;o+qpRJNRLfTRLI;YP}bd+jU&A-58`(gFH1QQ=v+g{N(^g@A$y1_J^{|{QTvu= z0Ki%!&Y2t1z2tS}aRnkOlG&axP|T$Uzh2V4&!K?zp+h*o38<~eXEr&u&gXbjg;g8MhRP5PVyihvZ)CCS>|-B< z%CZs$p+yl;(x{Y;gA<8da?N4nNKeTh5+6j#iOA!%%$WA3G*tC!j+3;}@H8Qahb>k| z%*CgP(&)X=?jl;l9MPT+^P9!|j4)cU7iLcg2Ux$P)E;O(=z5DMU)JE)00V-mgrf_@}u>1zUVQesDSM%XSun;!m~WS7{gVE>|L0 zeK+Xil%P%!K6kZ4pwNOhM+d-J*(rI}o_(nG!wIWuX$&;AfC#nj-6C1_xy}IDLJI&W ze+!{yT!Jy44IwFSA$H)id? zzYp$Pgc4(lw);^pkHP;uDb?-T^J=N_f+Z~k zEvN)RLjwo7NmqQi`{e>G0ib0*rwy|ko!;|{xz0{i$vx27>1t?iiHHHFCQA>qgAhJ> z6jC%?hQj3z@xF!dC7#Ur;?!{eSULo_I*nOwbu?6#X*mr?!0K=Vdd&m7-vDDgOI`Rm z1l!NIr=mNE1*tRO9qKabggGVa^gHO*4DZ<(Y%cJkb2}nz%F3sFM}5A1LRCpGUxYfU zs*YW<{zkY5ikQZpC&aSSj>V#y+h{+=#u_f2X!E?)p_i*QX-aEvZwE)3GC{QcEOiDv zh+Y+n6UEpJ3;!t3dG-URS+tFnvN{KOp0#g9LjH|iEz1_!i8#6Ni_Iwp2R+a$BgzfN ze-JJm&|}fMNJ9`)TU(pFRs0KW8JD416UgXv7dSZ(`r#{`;%8-VRaVQL5-lJ}9eL&R zBr&R_MhUD?pH_)x+S?%IZP&-%3F1FrFPq(`EPj`7GB+FflTglrYnm6HmYtWem%7O| zR}`oAM)wOXClCvTrtJC$NEx}>t!smGPF3j_3ekQ;NpNYb!Qz>7g*^2G3M@B@+%yWY zs;Oy6TUbW8I6D`}1TDcw{0i&MS#atv&cJIorCvB`9C?`S%l-Wpq>)1L$)$w6-W}z& zaq=wf?>Y@Qkq1Uj}2k$ZXp!mgI0LBJ!1jO#dN#G8w(; zOg4iiv|#2>kT{LZRg_5J>;TRXQdlEz%@>kNtvbzOnuxZ0>4~hz1()2jBKoC+Bsnvg zlQWdczU|=|8V<5k)m^0Pja>x6mDE@=DyT~bKwn_1x!-_zh><+DBHT84tk`|ei`-(+`|^I0#~RbIw2nOcDP*Cakz z9dgj)ne6w&jeH7P-=uQ5L129@B;(%8L> z?}K}$pxSq#-6H(uuQ#vp>Cp-KK&pTC&EgTum^YyIPfDDswCgz474lz6%_E;2`O~fP z7xI1q=aJ}hDi1`N>eTd{nL&4!TkpYWfk!}G*6+`k-ZkY}(4zG*e)|1sy#0w6w6RJ~ zT5qugRXCgkgBgsWQxwiRy;@hGn+2!rL2;NY2%&-o&Z}gsb;Fk~Fc`3+cRD+4V;a(p zx4ojdma(81Z`zJBy@(GZMH$HF5k9JIB{bq!iR;Sh$R6=S1)f*mX-eTEul?XnEHMl3 zq(V0Eb+IRMEa%v%A!L=8O9|r15=`&0zgQaen7p;FM=D=dk-~D#!O3g#c^ho|gvJ=# z8lhpM-jIa@?GwB6hSbWG7+r{|wZFidu?UYlAtZ~9HfsweksL8}ht>+7!a7LOq^~{? z(E2Hq_2aPxDjxp&nj`eq)Lxux!kkH`#koEdyOFi^%2@p| zsK3)nb&B`~b9XHRmhtL#HCo|_ndm%#V3V_)mEEcARH9vZ1)089s-(irbRRxBSae*fJ$E=Hq!2@XaCG0+k@wuTZcQ0L-^D=(D?lc#~Q|Ee`WA`q7yMiW*!>?m6wQ0DP^{2hK^JE^ko4W}KNwxTo z-%KS6CJqh_+u2Ma0OuMWX)3>JwGF>?a=?aP$Ye_r&{UD5bHOD*C78e zL&L&BfflxF=HLrx99;A((`fcmw*X9vL8e zsE+4G@bX)CFM0WjL(@b-NPw9W>bI#Lfp}2z18`?;Y01>wd=3v?2IU*@nLOcq^}Qx# z6xDRVz}wZXxZO~d9j196rAN2i#A=qFI0L)U&ea$4N6}__EEwT!ZPb?R9p91%8xHjL zRL797d(67%LSxtviwY$my=JijR#guI{{GFZ^T?i8HrImD*XLK0`AE<3+%1~O!i29P zx5gdLAjr8V9m;`OAhwm=blzv!LXHDM#s$6La-9gs=>L@x%U%pmp(Fgetj$w<>32U( zLmXpJzrA3`Ut7J8&{Lu5ovXp3MoF0f^y z62!LKfbe;s2#vguu?d&f91x1j?=7Fq!TsG}Q!uP0@ ziLT+Cpi=1vfgtwIiBM54 zpyA+W*JesLeOEk6%+TG^Q!&`gw#5${%Po<66R6K)U3f-ZO}e()`3v4o{tFk8D$+*Y zD1LdVbw?DeefZQBu5!J|5;!U(5-z8nBX+2~n!V%Glr*mi-Id#h2DhV=Q`N!a5E{7F zhZx%J<3gSlxsqlc3O*rIpDa9vJg2@UBDS}*%8qICLeTIa%>^^p-{`hXpA_>7jl}Ny zzm3AW9Q3V!A2Tl$I864s`RxI!Beb}{^Ic?6bvpkg!TPX8(;j^5bAWa7lWcy`Hb!^5 z<&uE*y-YH@q32c<`p2TPkJq2-sc8Em{Tk6S%=20dQ>S=@z?9GdN5;&wgcdITv zIQaOAM(iuT*y8o-@$|gSpU=`W!vHom ziOwgQZkPGu3}Ly$7QyH>KoR9X@y;R*_rfph$%bI4bfk&dsy+CbUk#W@_?CYBV8)Yd z^QCF1t8)t_P~2E_qGMV%M&Wh*u-ue%(AClIH;3%4H5N?k`Hs>KZWGJXtn_ex&AX}P zH-{|aR?3-V(SF1+FtgpQ$GojtnUBgj?f&&hS`d;eA%J{0s_&Re`t`%&U-w!%*;{uP z7uQ@Kwn8@IBfZNma1ms}mHB0U-mu34xhcf!X*r9*24g@tT70igpKCC{QN}sjX=6^y z6K5KE3~S8Tm%9x(GjW?k%3ET$mobaw*5$%mzu>@yMktOFbQ%kWR86wHB@4=wQ;@v^ zsN#fe6za~dvFNo}6RvvlO9ky$e~wqE`?gO?%O!jt9MrkQABEvNld^#bpThmJ<7eLc zq}Xbyzs1YISd7a1+YD>HJ>GmxS8VHeI%%x!E1SqYC)x|HLlH8JS~p`2&nNF__8qAq zZUfC4r#Pnh-am+?wwP6q`qYI>o;Pi z@5I>LmD2XY?ZKhx=gupSYinpHc8wLA@|R6_5Wxje)4Z~M*4r}ymOzt&LUtlxX+=Yi ztno`}eLG0vz&tt7>v`is1g{k}&)rw36MwkW9S+ZiKV8LAfYNhhi_7u{AM}(gg)`>4 z$^g5hnrWVDZfT%eN!pTm@9}XhmiYMmq>HX^yF=~z-MGoK@0Kr-^XqM`XWrn=(xw+_ zr*9ANxx8|JIc9ET)*_}<_7)6=)R?~cGRF7*AhAaTZwnW*g`R4CthGFwM&?fsgC8

aD>Ea}&=XId>%=u{Ta*6 zCw~lU8D6d9t;K*@$8;oc#WeCYstNKgD7;+5PrEj?5 zV$Kf8;EI)3kPOhPC)*rxd#GG-`K}2Ri|*i5-utx5_7xliP%P zPNgH)LswtG;^4&ijXuQg=nlIvDVxTE#+wHDt`F@#1E9)dKOH2Yg_pqdq{1;44S%&v z)oIX7c#^?;`uOUJQ2zqYo~jd4!!Au%kzL_uMNUf{O%)3V7syJ+P`j#R%uHKx?2rIx z(8|^Y!hacA?s_ad6CoII$%>OBuKJ6hkzWVj~MC8xi z6UQwkSv+i`g$@gO+14B^IQKy-|f$7KHWXd&GOBTb}w5t(NnW_(kZN!-9CF^ z`RyBj+iin+x)^`ME&FFMtYv-lsMMSNSOqdF@gN}rf&>O7Q=+}cf*03 zDG~K920v|DTbX3Zzz+_^(&5#e;>gW|B7Xko5{kHt(d3>sR#=cHByd+Vx3bM)V>==V zP5Odok0nOic**Rf5)m|TQ#F9p|6=t^d#4dABs;=v@7vQ89)1v0HGlogbf$duNdBASt;%u*4K$st52@BS{^? z$baPe6KAv^)*BYdDCU1+yjGY3^?+U;UPR{fB*-~wje+z7z>KM6+c(yi@x}fh1C#$Q z&u;Q@wi)R&!AHi=NUXNLhWf-#Go{U5>JpWROyxJ1v#`Ue^Hbz`+)(OQTs(aeyX4kv zgiy@meaS29r5RPS=q-podQPNU{A)!a0?r?|Pdcm1j73xvMaMvW2_o7zoUkGdjW)Km zki0?dQ(FUh&#a{4)DX9iTt3%fAbUY%cCqHV^rfVhfA9Zc>MP@_+Pbglke2R{66x+v zK^mk}3F+=SARQ9Y-67rGC0&AaAG+btyqkOP^ZUOa-~(~?-fPVmbF4YXjuBDAZ0l#* z7k_=C_?)PmYx%53AKx{CB30xW&STe*f;i;fZ_O!*WVDbz7qpF$;`!l`_RIM|8^%ASHS3wX2q6PT-T|Aj(^KN{Xtw9g#- ztc!RnlXyU4I`Rz4q9;TUU4U3oQzz5-{P!kZw^rHho7Ds45~Cd$wVq!Qp3T~|<({IQ zQ`b0K!8gs+dsH+I%SF;jY%TI3G<*@4wp;pi+#t91pqaCV)Rhr3HBz8jY;f^~^uLnxVU8%&LBKG!^*XL>UO zca-YEj`DG#lhrV0llSBd?@H$mDg^x*C3L;|?K0hpeqB=hUpyqA*Kxe%br*ry`RzwQ z$|Ig69WDa=Jbw;}N*egB9Ef^eSBR{2C zw&HUXM(6wTxzc?Y~ZJy*LnOaXL*O6X8F?* z;&UkuiO*ak-Oun6^~f#ji38pqmY?BwJTJ{xYNvP8FE{O#?T-sIU=a78jSpi=8Vo<9 z=tzb#T&2@#C6S(`_GoA@WIiUxUT88 zC7ARn>GV0w^SZ~b?IwqJRqDxs@iVE2cA4N)yU3F%K8X-JqRaP6k+il)p=-)QN`Y#{ z_UMbN{Ta?;QzUTYn5!wHX~9zbSZnK=fD?dy08ls&VS9yOqzwhZ|0ZgG9+U>T%ZE%i zV8X}mw#*P6X4nrT%FMgibU@6EA!wI&3;a1+!|)+P+s*r4KpmlR>F39&8s%|mz_<1^ zc&Nm==zZ!Ndb1;_EQoBF_`G}WA(8)!Qy*ko{|QznYi@Z~VkK-0Vh!ZCX%lPD?Unpg z>^QM3tK^}vU_GLUA&B?P?}7r0<=?&!o14 z=OjJ1H5Na5oY=y((PCmyz?6&^;ZH8-%LypS1%4dD>?BQ6Ak8$_2ng0P4CVZa$QU7+%%QUB(nv z`nBF2>XLawM4m&aE;9WtiOC=OrVL;F;!s$$njbn4GyVR4jmW70@JBDV({n84O~d-} z+ibI1Ihyy96UH9ZQewaeUi0A;Mzo^N57#2mD#%9syvNyX#^_tq%TsKds2;$%EUS)Z z&alpW!7;*rhy~r}lg3HB?rB7>VC{+%ep5{!V)$M|p4YeEVF*K}>%X}VLkuCMcfgH* zN;3RnHIS7e{KAp-B+PHD0-f_zB^iVbL)C-KZd>hka({pb@z2ghS>OO&?VwOu09Ea| zZfL`sxaU`}?{~JyD>w7|f)&s8I4d*_9#l+l*Sf&ZqznCv13$tpGTJ z$p#`gn0bAf?!56EwZdTTLCYA*6cw?rbgtA1{LYkUU2bzFP<(RS3nRT@oYKl1=O|-p z#rDPUWg3xm&;uOvs9dg^0>89;N7sF`uWNgkwq)l!a5v$8S@yC@HZSb->_Cu6*l`R; z{4O_h@&@vF`uGxE7K^e0&U(MTy(6)REtE5C>b8c_7*ck9!L~m)2bAV|xX$F%$%^E3T!=8G)6}8+V-=S3It&yxc$8c05(^6_0#pHnSPCoz1viHebE) ziBj_2<#0$i%C;T&6B$1Sst!?^jT(S5Q>hpDsLNsD#tmZUIESZg^q*Q|a!6i&jE7

d^<2-R3ib6I zr$Ip2%H)5sehmm{4Sp#wy?8l3w|OcfteuWU(k%SdbM#v-LbY?uK#CzFRIPd(|jKC zw7W88KG1j2r1v)o^>LGJzg-u=A7;l2x9tm3Jz1TXC-{1IbrM#8Vr}i_ldkrbkaA+uKgjq z;soF$bR2V4`t4sDR6!j`Cw9^wys?gA`Kx-nLcR!`&AsJC-e=)<9x5FjenUxa&*Xt_Z7xL}FSU&czI$}@0xJgXsu}RQw$HjJ z9S^uH&9|SQeYDEW9{VBr220Prms#a#!B+e)Xs2!>UL*QzjyouLW)H)hLM=D#XprY~ zr8ZX*hnv3z$*0*KZeN}rBy>??pLt(NulAeQ;(>`r$rteSGUMy4ryhVYojNq&%jxLU zVS9Y(eC}T_@;i~YYdJX;df1c?&pRQMnRrQCI(dj8e?0h5Y0!N1SFYZqr^dE30dzdv z$h|WEWOVvRpvBJT`mk*0U~l@3?O3;kDfrLB%c1w9BEpsR)Spbuy6v-lqnI%f>`o~; zKavJeK45vv=`n)OeJs9vJj^XskFA}mt?O;o_s@e*-yX`X`x<$7 z1-5%$5qa8jd&(PmzH5C4=wGbL{1&;Wc<9{ujo)#S`uQ%?t>V2CaNXy_xZ4#J8{C1@ zFAL*TpaDry$_Ah`+%^a9EFUu9aH0Eh86)gBPm1w!{Qd3?m^ZP=Z|faP%jTWk^GPSZ z`zk;ZimiNB5<1WF1J4^?X2|&MuNA~SEZ-9EkUxMS&u5U1yWi&eu6WeDWWnGTafS4+ ze2Wa6-azg8@=u}OfaF^=d6lXB`8EEIrTcj|oq9}k9$i!lHV7N6`8~(pU$V50LIgRW zkE#nkrM!T8WyS@ZZ>LQ=hL{nW4m<{&Rz5@vd9^}%0Dzijn*$`SmHa#8!03LoB*CjM zS!_#d<_JX>4!tSJyU3eH6J1|yabd-qTdA)Uvkc1xdM4wZh(ZH5j- z7+{vqjzC1b9eEb`X2%v%&kK&5n^v~hTCIHD*nOF7-dm1SJ)N&b^ye2b8%-cBe$vIh zu>+7s!>GC7z}ry{3;AyDOIatp1KJFBrl1!6u;by&BDwp!))U`{G!Cr00z;I}yOxIz z;LCS^UL33Wd2IL6Z7rj3pUmr`;cd&n@mIAJ0NdGyFCNm2*y|JI6;&Z+A9!7s7rM z8F;%X#Y-4A)_9D=!{nia9LJzwvk{zltOv+vs&rl)46<~bZTg*W*)b*krn*=qe+t9!*~X}By5DvJy!ksKS<|L= z5}wY(!lU%44@RZmT~37X*^4hw7o9KKHE+UGIj%I4_>D_VJpc8(Ui#d5E9g+8mPRb2 z`3LWHLz(Khwu;l(J1?7CpGW9@=_+ekZI~qQ;m>q&#N?;{Ez2!|vYf@g#p8AF9{g^K z@X3Tuzp{8wRhUaYU;NSSxbMTRY`qZ>)LrX@Iyl0P$>MvG@w*F~_g(rB^NZ~F#2WdP z-%DXk`z_yRec$t1+Ad9dE80#s23vfh6YrC8!^}|N~RtJDnCy%u%JZ{hLtM=gu5o5AdL?M#8K#{n8_EhI8z1B)M zJC^aXJToAVp4IpEyUJ&K-*;5(r_ohZ08H^s$}bmsXK~>LiE7wI{jWqjJ<^%JRB4?2 z6Dh*s_Pk=nA}qLuZvoWT2$+cB0GzcFdyMwYdSxSkvKwnr#|TI(nd)DCCl?72w4gf8 zV;XKEv!4h$+#a7Z>hkHvUlNpen+1BtzdlA!UxAQx;$F*Pn^S!94w2wnaa|5{0H#cQ zlCPw2zk#H<1>tIUDZnxDJ?ixVbQ-`@?$n*f^%gM$kBOK!iocW!UtdiYI-Cdo^==O+ zeP5GEBi-Y;%*yf|1^~6jJDXmKKry+|;nT4d?6!$p{kYfrnSz{zHSCTX`ZwILx4mXS zP0Ti*BA}%EcLC>xeV(a$p8};{%RP2^od2K73(P6c{g+n&U>UI3ucXei<<`DikWfim zt059lgl?PPDCok2Mds_yH|x0IkA!k6{>id-&!lwj+N&AReZE_6BL5Kx<`dZ~@$)H#1@MRo zg_Jczgib{9*Uxo+y+f2AyUTY)O#h?Z04fJy^T=ILY{tGME+7YzQCpr6b_)2PAd7*6 zY1+5XiXvZ&XD=PE?sCEqApe7U-QF4lEZ!#4BKay4(kj4LR;qmMI9=q$M-X~IN_|{~ zw=5$7C6aWQKPu|$h)u7#E$RRiR91?>i#6Ss?v1=PFsQMKoxBo3AHCDCF`sTs#Qc8{ z2P~~+J?#al1Cl#FapnXZIqzkL#-r~_+-mRtSfX(no1+MAy#F&;p2rh!U{yF50Q*F1 zXuc%Jv;hc<)w4j&-RBy8d2=X-uC}z^Y3*0D0s8{!Ga2qLwha(2;$Le@r-|t$w40!f z_!eb^K+(KtPYu+D*wOW% z6Nv%--7zk-;U@PAwdgwC!d~6;jkVOj-1aBke~2A`nFO+Ln%`zXvJj;O&Llunf5!qs z#jyMm$Ez&?6ikG!XKOG^UG0|qhgZtxI5VDgulwvoyYJNZzu@Nt1iuNHmCRRrR{CTk zXzDJEt!D%c*hO4#7UQF@jrO!WI!pL#bo%G2ui2Us>jtOR)z!ii8t`rJiQA0lRp8q2 z04)x9zj>vPNbo7V3;b?lSd~+-+nUG21E9A;GN_#hh>V>=q^OZ#=3Nj21K0)tLBFtE zdL)=NhzD=9>AcGfXSq6qTwGH{+E?vny%X2g=q+IM;=vafh2Mfo0DJr+e@88s{QI(v zKK$U?vusWWP^v>k$OMPUcCP)aS_kycXqKTNzz#Z0bJlmm*4EU-QHPyAIFPut>O-CR z07Iv&s7uGLzyzM)1OfJ-L%RW(aL@s02#TXV6?)7RirWRESVuWv_`*ct|2PrWNswbrKPKT2zmJVv7bPVIVp-wE)Gyy1bRY|+BM4nrsyB3 zwmkX^pEZ{i>AdPP4nwg;eNY1}hM|S|pdXM9_)4Ol_%JejR&m(|+HM4DDb9MT*htVH zo-_%9#V~=%a1wOA`Rg`E95mo2O4NY^Jsd34^l$alRF(&YC1G0z^*S!;(?+btnj>=T z3NxJ1TNFR2<@DyUEfK{$cma{fO^;03Jr6*P2hd4lCCInL1LPtZD-OfIR2;hT2Y&3A zj3t*+!L;U!#vZgWRVYyh$!#{h+oY9y3nvyQS)ls<(-WgpnFd{S%Ioy3=`!ahscF^W z$8MQg-smVEco!##-OVZxy&hzBU+Jz}@6r#l_syrz&B1_lU299r0k19rG9VB5*IHU- z_EmRL0vR==_gj4`wHzQ0WRG>HMe9HUXxagv8x&Fv2n{;u&lB{>tZ+GekMV9{HEavN{73XXq3*bc8^9CaK0t1x9C1yZj)D?w8Qp!(SO zBqunb3*`deXy$#2znEG}jF*P7B>si(V{Yqi7O&tuuE_)t@9%ce0a?!{UqD=F4f5gV z#}akGhK&jMqu)^}(>+54^`BN8-gvfus}FZc_^SOX{Rt>UVj=fpO0>-%auK10vWHWV zKzRo{HtR7b0qpv<=Z<4afFLqm!p8#FR%2{8zi7{p}v4dHuMx-ig(ijbmR_;RN2CTuhqLA!&;j z&^3*c?j;C>Rx6v6%%9n(P#)OKVO7{|r7|gPzjdC{ICsjV=O|DCk+}H=yEL z!;6d!bc9Zn3+5hN)elJ{<(eZdKsReuLms_&g@m6NrgN*lMcLY^xi+2@795dOEFjL= z3%E*&dbXuPHseSQqZN0Wc4D{eifz(u%a=adB? z5!6#S$?q!Px#lM;Yy|~MAzIYDh>f< zOQ6L@IXMs4Df;9 z2R%N>IMgd!Q@Dp1<;{GeyNEUnmL(?<73c{YPONAqAUh>|0ZqPlXJK)XifHK+GW=&F z8t7iGF_R2O2<$#M01lseNkD6Y6QAPxc*BoqfSUD{UII$lS5^lRXFgS7Mr~f0Ul39h zhW`~n7Gng_%9ZL2+cC4y%5EIdrWYTnVE%3-7vAKVHHK!3{@B@Z{OgcTAI=F1a?oH- z3X2C+N&&?+ACL%X@X}oXf@KZmKEj# zI9a_>3)4}1Uew+nka##uthZgE9%v31k%ujEaqIKqHMh7;i-M>NfFABrp*jO_=7IL? zo;m0S1$dt5_pw?)Vw2&gPI=-MuNLoi&w6#Yk*FjRpmAdiEMhx!>&(7v3Df~_p5>Dd zAR_z`Ocp+G4=Zyj)XNhj0YGJ-xX-}^CN@Z0R>UI{kdh@DWFrx;qmzr{jjk@GR4yg9 zk8sdHURU;q@>)<&C<@48xRPoeweEntmYnA|D3HM z8I<()r9_Izk0`@ZNBN!gtI~7Ii1VT*>~y;TNyy@D^OrW1KcObw>RZQvqpI(R4Y?9@pC>pDTo5K3w5GrNx0m!v)epd76+Er z+7JvxOOnx_;w1yyVf2e!*qc}GErWe7JrH67f`K`O$PiJ~qPcHkzmlmeQ{xJyqApGV*D6TaOeum01482_n$%sL+rE#32U0HzhF2EbCfF(eF4mS6+0 zBr%2a)635X=*Oeok24PGKrjVxnG`T1m&%n#5#9*J%2?EpVuot0qbX)|0LTVrht^~F zP&u5m*p{e^Gb_I&ef=xqdrMaoXC^i+;`WQw=kDsxjXYF4&W#R+5_Bn35iY?5cMYhO z${F@C;elw9{E^yEAHMZ%J@mlJ8de84-Pau8MSNu_&)t%`EoqBLMRGLwD1C-ph*FO` zdmj(;?liIRy_Cz`w`-tpv`d=stx3mQq!6r}4)9X_A5*bkmhrb5TNsRp}uSl`?+nq<)b08~JQoTV{qr!3Ni zefSO0y48VAmE7c|4OjAL=n+kfIvXDt7>Io*1Bf-+^TNIDYxr$+HGEj%w8>i|wHV0h zA~?}>!aBkp>&&ZwPR=}7od#qagePBB=2pvt)(T>&lF+2Wc8cOA+jMF|L2Ri&AlOk$ z+%~II!Eb~)`-sS@q5npUy%O_x{RRNUV^<-T`zH}aA3vpblcGsYm74cJ8&`u~5aVEo zU5FS)^xcB{8h(%8-WmlGs|shkRB`4cTB)IsI@*~UY1HiOq=0!1fGst-1|1^1IBu+= z9DL?*CtLnJnNnC*`?jtKJMT>bGe!vjzyT~tjIIvP1FDa&{%t}AciN}OEFj@mS5H}R zE*{_P?(Y8n{rkmmB?bPktHJft(hOmXQkGMTZcCi?7c_1MLCWEtL>i0*MGaPEXhCBV zVpIpzYD#J;>o=>|rExeOvY`=FKa)@~;6T_Z%H910U`qG~b!qn>h@753Xs(YpmW6%B zS05fO&IDdwRv@b#s*t&#YWt4EkQ<@i)ir}&zw&ECt0{p2nhk7aBShFBPCbJBmg{R5 zu*OnDe}GXfTGtWgpFe+^>IElAqx_rBC`HGBT4QZ~Y6|N;ySGl_S3R&(2JWGoVM@QC z0gWP~OOf0AyYI0V95k4>of@{_%{{N{7|MbOUyLn^AS0CD@#vJw;_Ocdhtd8Qc%!8+ zk>7;{1@qG-DOwvFWi%7q6JXBld6!pLr8UqoFi6?3&3-IPLIXEYH3&eZ0?8rhVkAna zE^cn_ND{M5h0_1^0{r-S=WV;=oE%{((?B2Y5bLVu&u>a8g9Rl>f>f|GWziP_zT}gJ z#^VWvXGcN+Jy2axD)l>%l9Ha|jiMP~agEnl`}mOhBHB8!5+v*5CQxj?FTzn(m`Goe z%UG6g;p_~O=QFmIxufY6a$fZ%96plGa1;Q)JX)(}gy4uPyf47X`gFze^mHIT1CQ^p zVMVeFs3!r1sI|t&u*l!e{5!)~VT(c77UST{-d+h*YK5nkilUkKtM3YeD5w%FhE7fZ zHC{bjZHX@Avyd7_40QB$$sgBr=AC-X!;Ms-lk>%~v0Ea%y#9FM`|u7q+(T@sqP}=x zh&TBOvJ!l<@aaFEPp>@_1dx8S-S`*pILd*c8pCYk^&FE=TFXkmSy@GRA|c|66GTZT zaQ*Pj^_cF~BK(D62iul!oUfpK5{?zw0-Yl9KR>} zoRscLAn7uUlKOiQczi5OCT!KBd~?}GhV3SiH32$RSUhZFyV4&`5}Fa3kx~bK6Fa{r zPr3%sXA=_u@+~Qp(Ilhvda^3N+X+c!(M;EsB`_&;@WufN+OV(Rw0RgTnhfhNrX@c2 zo;3tBn-a@I?VG>w*!PXXGt^91=u$D=(9>gLHfCi(WyNV!`l<*|GwB?=nSC7Gu}iWi zRe9l;WUD=@!XH^5-LeReou9o8?l30-aTsU>5n-~JeT@8N6k*pM zvQbguH;((WXo}>AX{uDvZ%VswW9Q%b8yFhOu%97^3NvQ-c49=-&L7%XTl4aKjQf50 zS>XxLDUMWx?rf$;1@jef5}?w?hj||8ZersEJ92TSEi2-SVSPTU4BFb$(4SAh`QeM> z0=&scknGarNazHHKyD!lT{(jSP3i`9{+j_dsi0sqpvRZlq?VU4%XT~k^c=SjdPG(@ zyR1GRSGcmiEAf1;PcWu|HLEdw|5an$m%fwIY6Qk0Sxp!z25D@qiQfQgkonrVF zN^jtofsu*Y$M??ux4?lIU;J9Zww^*NZ@_RTDoxRcxjT5Cv*pS0>LYZ9yjk`zm=b4% zuS`Rf{N14D{(B;j#0*%QjD$q%nZ%ZVLm~~=L*t4%sOv+<05rl6*?#w4QkZ01&xIn^O-LIbQm6&Rx%l4N%_GH3!*A4mQO$*eHd#@>^G%Hzt>uvQV#F$H zwix2y_og_t<_^RRc%Lx=-lMb_#0_h27h-1rMo&slA`&=Vn4l~Y^z(z*kiBg&+S+uf ze*6z3_LS_Xi2lv>b$=W>mi5%BGr$~(tU@0EqsNcvzq#SodrRBlpK*6?jOW@_FB zH8HO-IyM+6M#KufV|4fPp}2jOy>GM5m^3kXL!~HP)GErvQg~j@d`c}kU?)*GhD z)r2>c02&oZRL(j+!kO?VV(=MOqw$E^$7Cl~o|D!&3_BSQdLk%0e=6@^h}XO;L6B2g z>;Ut&VE2`vPZw!=BC0)3As%)*2s`z5;hl4CUp z(p2}n^!Uj(YES`9SP0^(7~L5cOx!|0zE7T+=MU(eeaRvXCI7%TC_DQ#|3q8ld@YHKkYBPTg_2GTSnr=B;*?wA4J7Ok7S4zinj)0B z!|Uo`_u^|(P8^`$=eSV9#g%?Xh>Yt;EH@5(ALPK+H)ug|9~}JSQVjR_7dkq+>c{Q* zSUv-PWD&0$#&d+qZG^H?5Ek5GcZHL%x(yl%DLj@zFNI^Ml7f^3M`-kt7&elJ+Q-p( z3RfzM>f^R9Z(;Mg_-PM{%;?w(?CcD1kt}W>f)AMs{@osg1GUzwLP5^|R0^F~R+NDw zNOS@Cj*XQ?h~|4{G43rKQY1=MuX5>?6D$|frsbe|q)c`M68+jY>2Fq}x{e3stoj+h zr!T{d2q#Ik^e~nCuHVD~P!m2mI)d6$aoMfGkvdiYC7hy)v*%d|`stsvb>P^&BN1(3WD- zRf@$ifwTp=xyfb-=&oTjjU&M$9W>dw68z?#M*<4Nu<$CMa@M2!zO8_*$!k+F84$8` z7LmIi|DMtR4`?~5R-p_b;{)E9DGhi#*F$`&bGbf4`HH2j;UcN?ej>Ej=qwl$YEV`I zsr#4DZ9AMjHIK~_lAHvRmIaja6d@_DxJDvPZNUu$;?SY+8~n4JcI3iYe?eTDk|2xE z$qaby15_`(=`P#j(3`%Arzf8#Q9-jwq|hua5Q`MOOdi%Yhc7JW8=a5-jh0J^BfG6g zBx)ZEQyo!)Fp>0142+?IBl;${Zi{Oeg{(mwK@$otk=s6nlsNIjLGJi#7I&^C(D4DE zsi%(EVIcj*G|WHaBM)fHOTXDLMXo>5tQvsiybjKCH(v-xMkm#@c{uu>FCA4e^I z5A<{njYKKRc3+oDxk7A9d?_j{H063D{_6%1LkeF z&64nK{jd4M_Q&eahCX$9jfRwq=jK^|3ex8bwnRkom7f@e1!?gjlF_h1Y0Ik z(b9J&Dlvoc?d|Q1y6}Zx2^YDomVQ|-5}3a)=%x_h*HJ8*0e%-1>s;`vI$?CYinWs#^FC_vNd<=wI5)*5S>d*kyGjS!RYs3 z@Y0z4hNUG`|1?Ng^7ew7!0{aw3BI;U@sNs0bFftWzFPpO(lj|KPqmTUKh|FF7Vko& zO7FY{2l^}sZdo+7;=!aWs%*vG^kB0ORqRWAgN@up4?Xg}08F0}&HZQOV-m1|>iKVS z0~-T28_cbb(UF6J7W^MhkKB1ulCAnu$zi!GXE85z;uo4&e{hP=d`lDw!+Idpk_ZYG zW&c&l0woIbtMQXm9;P#OjIJmRrgNP5VL0B$md=+a0 zgeoy<6@rAZ6g7${0Xltg+V$O1cOm5;3G^{$PEPSh!gQL6GzFrW;z-bD-`@Ec)}cU5dz}8Zv<$49romc8iMo>ii)IP`VWcGerG5arAX(-lWp`M zX|WB<%)=szJZziA#U!@k3<(X0M?bTG!~|dQrcdvo+%^? zsBCdow+*`7fKlc)ch4Izamo}lQ4=462NU`JjxXhG0UZWu?zvFX(;=8tYY9HOciJp1 zb$HBs{N>NtGaTQ}Qg*GRT4d7{sFr`fdeO~nLZkU04#zv-y?S=>^qe}qw~mE>LirhrXyv-9w zla#=qukU#&xpsz*vK3V8mb*wVIE9cC<#N&VI2&RpQZlk`( zOh#~{cE$y6z85*`=byOzEO^uYS-v5YutIRK(J)M=<+erwDaZXuDUuhYpi5~a0OBm( zqFAGZ`vPBU^9>fs5dbNkKIL;dH(P=|`I(>9VIoeblB$2JnhH7KWETCfvN4?=lH?$| z{Tzdu<3@IVt%>qQC~RlqQ+FVwj(k}^C+D*n3C_3rYGfoL2SrOfQP~+GF{Bb!P;n;h zLmbOyC3Ru$eO%x-8xr0(!NLM&>#jzeyqk_Kzf|MRXE?Xcc`Y1SizJ(2i~3Y}Io(um zBMigX20jZtFP_pb@);B=Szc6j73@VsEUm@H3*_zhk-z~Xp?w67qI_bw7-W6>?#_Z_ zX+y(Bru{Z;(DjcWVBoHLPA;|t85NhOqz)K6{XK`xL9s2=YT=T~~%yOp%O#ZZo9g-$rsx~$|D=$c&u!N(Cp+qv--#_wum znK~(h=tthV2R3g|6QTtjyKxYONSrmy4rKhbzB$_pB-r{u?sx1)7HUXZ=OhKcyO{~8 zd?&Zyl~3|Uc!FzyH$`Dc2a18Qra?4HnhM+EVwECBi~=POVIP^27kjbPloXmYN}7NH z;uN6WZ4`~&6#qR~DK^dVi9k!C4>hX?|42}SM_SULnc6_xf~4-a-A~O@f_11IkhdEr z-0YfU#sDEunXuTifAMNbGfUpWG|kVNX?v%q$>TvJZhy4F&mSE-$z39`1}{L8QL8mR zjTE=pT!Bco5KB_x16~0MSeD0r+-WBQo}$ zuL_qxM~?Q{4ik6O>0E5d`$HJMHJP}PSr7`$9#mD+(gld$_<|#fZ(V{YiacniwYJtY z0e4ow#6j#_&sJjX+ow1xAyCeaVVJ8jnMr283cWWY{;&pCr>l+4yTYO+GJAg&Mp`hx zSNrPtIC>GHAttdBcL}5X#OT)9ok4nFgoRiRQVN1~K$vt8wxXO`DL6>~4G=YLRB$TY zue>S~tw}4n=@M4Jo#LY-;XJ-Zs?G>+sOJpBUb=Sjg?SC(kQj*ZgWwMqWqmPVq&;)_ zlNriYRHLhsUXtB z48r(AX<@@O%jOCdeZ|r6Vozx@0|fw6&M@9i#zC0D2H65LG*Hy^9AD7DN)s6ZgKg?` zTFu=pGpq?N7WUq=Cnpt3F=V8J+*KTBpGkyY?iYCGF9rSf4cE>yBn1A1z0kq$Vv2w4 zxAOD$FX?J|N0VH}I5KpMiE5uP6cE2ho7nz-N!I_ zBRR%#C5seNHLhfobq6hs{5xSl9?aVy_ElQ8}KEX-+&%G!YaRBhrm z$6|>>)0Xi)(N1@!X}mZS5pFh}3gqtRt{Q?i>}7eq7wpYa=@8tFF+oWAi!m#hQxr-X2@o~GwD|bs{~4~DQGOCB6HaT0Q1F(P`A@H?kk~V6|Cl9uy6X@iE=5;6SiHSZ0C(#3jBP z`|#*&Ui}OSFD*5=nJi;TEdL-akdz%glam3~q7?21hhT=ct~Kf)ep&M8+gKMximcoW z0|`}~a}QId+j+|o6-5eJapk2Kh1Nlb4;|)wzG2v&k5HU8)g+E$2OAR` zHd7%a4$V4aWLiqONjMD=1Y=HqNXPY1;J%@3m1pwp<2s+GFbBb;;eSc_&=*#PK^d`r z06qSL(xYvj%M>-Bj9gE!s9Vff+uc>zepTLVbUtPFb|4G+<|EdW+DAjLeBh09mEPN2 z6U_WCYdIMs;CjagXMXMTpW8IyxqF4|3YA<2Je!RrWauHQyPyC&t~xr`8_8{t4nU<~ zkK2M5@!OwzgFZ?z)3<12hb^>awUY?U;2z?sE0y^g4z2NPu6Neb^NI(AUsnP9oz_DZ{O;{N$_eBoUWjX`&?ZZ@_XI={^K(t9l62nYPO$lJ6k~n zI4o!!95U|pbTjGUB|Bk^H|fAZUW4&6uab8{Daf*GQ>`V+pm@N5O1ZGGE-Hq;u%)o< z`L^&&lq1t<&p4ly#FT&gfs)Ym?9I&$;CvjVd$4$^v<{JV9QR0gi#>sB3k$`ifNIoW z;wm4Nn>(qcY$x`MKQ>4)k~z#3m3x;0JFwi6XfNfLildQeT10H}h!)D22%Jl{l)nDg zd?`y3B$gZOQ$=6>Kl;vzGPu%FPEvS1vFvIe7kClO7vNc<{t5tb!T zGTaH16eoV33b_K9-xi}48dW1?K5~DqZZ|}}mAKqqg8`b0Z5=kJwp4+FK0zHuq}V{UmSVhm{MomiGIHok!TsF`DXIbT!~Nq_ zZxCBsmZC*O9Y`%`cpJM=lo7-tuDbA(n5Qod%~{$i$A)7*k|vAot1CNHurXmqrK{L4 z@_!?h!}*$rgFQ$<(Enk?k&|e}`vs!MROe)kS)+p=vXkxkd(NqG`-s(UC6e+xheFc1 zPmG^2?6K9fkrgo=3Mz;`)1EW;D}UPNqRcWl=5N!7nVW?z&6Rm zZeJ6y9Fd>8^i3aLD-=>F%av|A6xIHXbcRvUO#J%AlrRpw{}xWF*`kVf)$Juic%T?- zu8}^RU;-_qmjK_xfEh1hqslN57WaGbE!%k8yzba2tne4@hK^OWnzH4!%7|Q2)L^y& z-_GT=!H~Wwi!M>Luclss-RozpyB@x$MK!-#tG0r2{hBE@-cEsw+{e7`vt1=nik6n& zYg%;iFjIvtuAV})HTqHt$2m>I#qtZPMTKqqDsW#A=R2F5DyK{Ge883d%-V-Fa6r?oi(xOqtOv{HH=$lOjj_=Dn~2 z=@x-}S9j3`|Nbl0ZJpUl)zP-%so0-pWKQ^@l7*h!DWZ!If80#z-kIi?fZjq&gNv?= zq@NF$g-;25Gvi7Q^Io6jFl2t@^AZv%ym_lKGJ6n^1{j99Ibv>chFRG#m14^2=_%c( z^`Sbp`F<4vwLGPh?;&7=CFu+==WS}-$9QStAN)NC=`3qf$r1L-ym_I) z3)h}P-zZXU^LIU)z770DEH8#m-bqynQHEL4`|XlCqHT)*e9NZ)JE6!{Pd#EX+{^h# z)a@UW-MQ5FiB=%z3i8pQ8ADCviU2UoTUbwxRORDHa&j+scZ=!`#M$#( zaU>F#g1<0wLe-DhI&ko}BYqjUQVxgU6W-Wik@mexob^v4TsvA!u6a{KzkC$SrwC~E zY?RkemeCP6vdd?$K9J#Jk3V)HA-p+;?9Aqd{uWF-=;3ln*+czqF;~tld4}|XtIc#C zc@ph7mq^lokuh*xx=IdxrNs}bx%r2J7{1CDeB{GUsb?zV*kNFc`7Pe24ooS)lJt6d zrxF_;$%pBp9*{IO9(yEDjVVdGz^s!1tu*xLM#CfYIk&8NyLoz&tPV(h5i3Gl>{wxr z9~9;d1HxO?MaC&DBhl`7c;P%ksFh3>#c*^SUSKbQA-R#KM#GGR!>_*KpF6GQUdY6| zS}rNr6lLk=62%Y4QE&~T5W5iiYG}&{VABQMqJ>wOk?AZraS=x4pd!mu9f!+dl4&|X zee%--h=`RWY#22fe*FBXg7)U9b-q=I2EM!`<7ONvRA1P`k$(nX@0vKs*u?A!?wXT!aK`vCUM$ z1ZPtRz7Gs8HfaOx22wN4r&wW-r}`I>qK8CAP@@^KDtzf*Qo%Ta83IisV&P0-P1~u> zokGZ;2C8h9bWd7^^l0jw+F+XATEi-QtgaOPiKz)|D^yM~u$LhIRz5|Ga4Fb-*F*6% zE)9-X`{7Aiy@CEWEGB(;^q+5Cl1dy^HH?=Hf**93QY4@i*q7;o*0WkXFr=jjiYY3E z(2%v+O9wlAXj(eJ=1EROS}ox#*`~Qr$Y&M&*HL>lRcWL!5p2*`#)flg*1#EtO96+UMsH9f*1 zYSh>W^F&-fNzmny%&~2<_|vA#GHGPvq{PJo*++i6MsA^caROySjUdN-r@%hO4^K1Iv6jg_oemhd6qii`3`>Pr!lhX8M$B7#f5X|ytj97cfXCB&@Ys_ys`kh z!#5ot6MD$O-Ycxc+is2IDtkinz2-JdO~hv$2#wp4G9UUm$qf% z6dTeOU#A`Xb`>KeA*F)AoIEdCAEj#!r7n1#T_;3$5`JPotBeJGL zRJ}QI6!R!~G@L_43372{=M6E*Pse6D3gyTjeh$#XUn^$oFZd23$TrLi660fir=o@m zC@t+c+gWHhju3GJ_g_3MD?InVISbBOUdKsXBt`r1VZzC_Mq?huf{gcYn$|Vd(X?zB zUd*gUV}$P{E|9)|od)FW-gSD%(kvwk&kC!uD_6xK@<$)i~>dT^`Qmx$!^2eRm$VIGt^`ugb!u6n&0(5G$1Z;lh{ zHZBR)H&~5o;dLWR9(d77aSVKNA}T@`8XD!5m4R*LDk+6{lVQN?>&7#MMrPP*b^;n~ zT01&yFT3Xw@fAsLe4rgC@3OmP3f+S+F+JM|{rc^__seohOVdQ0mM*F9KfpIV_*iY{ zS3%OH%2KE<#|uf(Lpuh=C4!e43QE7eOwP!f3lE|PIdM{SquOK1F)eAEYsMtK@IuQF zF*qNT6bJZlav^S^Wn%K|tgp(~lSqI|cVWyWLO{EjEVHzhLWHX^2!5*;(P+e+=@y2i z^6^%(zeNhi9bLOH9G&u~^!L)Xb|(oxaL&RMR8;S2!p6N}*OcN&Ju1%7Ic8{ve%m=n zp(@s^v(^5DoDy6sAf;>RNR}LxFdQrWq%9P>g3Le^bvr&M8LldHr6m$%zm?T_kPLG+ zhxzmqX8C#*dV0pfn~nMBk&JT78!@@m7Ky1kVo06NIjGE~9806SjD&>bOBvUWMh=-& zhs!bR{{MObzLucNMPkZGv+7%Zr8?sO-Sw09ppFVRFxcrlB>oTEIa58YQvJd2qKn@y z2wV@l2gQzc_?!cXbuE+0tjkGD8GN65JAFGRPTIdG%I)TM5D26=s+B$b-52j@#9{m6 z*^`wq*L}>$sZ)llMv5eMjsGZ3=4xsR>%SqJ#Q7P&>gy&9mYhHy5l&QgfJ$EYt*aqx zOb4DwqiUJ-WaxCZ6q?FASynbdQnEL~8z_d+mv@hU9!}d@XkSd=o3=8(rwHB(gVf;~ zrzwIBidd?TXPraMSlhqUf2?L`K_%z$N8~DJ;VkUtS1T3YSLZZ=z7E%MncA1#CR8Yz z(cGCD_X3E!mPsL!?@rzE!D z7_TFAUMOdR@5Zv4k3HT3JKT1!Ps8^A0*oVb+$b!(Rui|QMmCTRVkk5*P1#wr#-EpJ zA0q;DNzdV9N{R9ZbfDPb2$*H$Lj)$0HOn;e%|!-IxzZ#+_sSp>Y(v6KqD5q!2tS-_ zYc!65=Us`~h5hDTd=Y=;HAoC40#i6MwQ*vh5h;UXbikPau9kF%GgsWmQscu~%^$#5 z#Lgx^>KmnsXqg7j@QmOlqJ)m5LuZpnUn6bsP9UO6Bj`3ZHoE$mYvKkcMoBKH&A^Mb zYc29X<McU=*`F$hM!C4 za%q!4I5@!T!E3N#b1pyY@ETarj=|<+c?}N(?iz+4KTvB*iaWMQ6u6RKXslwRc`cQu zBEzA_gVE=T110d{c^i`PRLl}+gbz7;J6ZcCPBPHmGX zbvK!zj*HnTCI0H95QPXEBgCELCLNoGmxxoN^~@ZU-EXVim|eB!eBAho0IX6M&!_Z< z5K8a>Vmg^jn6%16>h*g2`}^*V%*B>7wShl-H)Gd-Ic%_y@k9%Cj>9rwy95ovh~|7g zACJd-dwW6LckD6vwbVK=BMk=|$Q;=QXaao=ECHw0>P`+J5R-J<&nt~f02)mulet!Q z7aCjCYUsX`jA&h}Y81N1ljCNIA7-=J_V)JEPe0x7_Z>DkEyLqSSM5R_>e%ZN6mh(L zO{Q6D<7%!trmM~dCj_!b8YCQq!WUZ7Gti!nO62fX9IB-{?+TIgIESU!yZB5F4Op1e zP7Mlu@XPg@^&NNNXsS9gb zrs8AMGyn}l03yE20FMl_`^Ua2X~QPp1pC%{jkJ`&E5Q?ijRJDY%!POCww6|q5C zUCU@IvLN0js_34y@Hkf|UFZz7402GIdF)H6bspePXRf1Y6j7x~WFr@++E|}+CIw95 zkvM@Ql0haaN*c7$&KsVr1CL6R@VJ03P$ZO%TAPW&xSD}B`o^;l{nj88XUl0Ef;@g7 z)p>+v^}3;?S+h~|{fKTsFUT2A){v>EQRYN)6c#X?zI2Wr8cCxG5#S;&ov9usvM-m* z>2%60s#+r+T)wHb?}(7P)T|1(q>-RJd-f~?Ae@P6)GWsJG#V?EY4oh;WsnK!*1Q~q z0QUG-E}LiIno(cVtqF$?4-Y4k2~TuBpFt#0;K^?kMBfl6JE|NmK~% zq|qIB*L2orEfx!s@Av_SQpi1S%4tUE1!O5Ph^fs_AR;J!P*k`oVKi59fK%3$`F(}!QN1##jrt>;`Pqu+j?apBx4AprWayCyXQq1>hdk+`b+~q z*x^ph-H039>nyD1*|aKA#m=(TA=aw3+9wl*$<}14ndArw7q-w~ z7+;1lyxCTHr)z>!s=rj*Lg*z{7RTvCvTsXZQWztt?dLUU6fKR4>5O_~CR5k*#bX3iA5QgNhx?oN{8 zScpu_(zP+8BOa6w8pPZn0ptTfaY-X8G7Ww$RRXU?Sp$PX_pjAiA2kCiSKkb}%I^U) zS(cIiC{;goQjUv681T}zJE#-K?g%Yvp1b48{r!DEP$w-B!Xr(E;Xxc}y#vlL@06;* zOS$H$tvt`|@5pgvByb!|#7_U0OR7$j`-!ryC#a#*EiC*Mq=CZ<>Knv>1(Eo<8Gss3 zy4AiW7nof#M2OKhVlrB7sMwR0$H{_FGtI$lM802W&N#~ErEz6hGML0(*eZdl0J<C*KvIs|1Phg#FYkNdLr6J*pDQu# z$pvw3ph2X9lu4tST~bZm$O4;)#5+}8YDr-wf;XQ~-2J|_QUi1N8Z)1<(?zH0F6*pe zGnmP>GmFzQ`Vxsh7uw>N^tU z{z=ALG6sVI^r1@A`khkD)TCzV?5hpmm=(B;MalHzBm?mhD1snoph**s{wd<}{5VDs zJU^IWVs~R@o?@de0t0l8j*iDngz&khfAS$13;w_h@G}@MXHm$~5)g7pl~#D$z^rs$j09lMJXVA>K%>?3QM+4O_3|Qum>>Lp*2$~zJPjH@$CNlSEWyfQHqFtix{x~|9L@q9i%(oE?f>Jk5xX?KLf z#Y`)^0UmH?@FzVrbr6giRC9EkVX3M=FGkt;G{-_%M%1RNDpx#vBGR^P%Rm!woqyw4 z#Q>hD5(|QHY*b0i{?RA5oCYP(=33ixgFpQBy}gISE!X60Rr#5Rj;?=M`GyZxd7k~* zZ!VtrQhWQ0%4G~+d$QSnN%@8k_O@R*th8M?&-14qKm6bSzTWv-bL+L`u*mLvczNC5 zEZ+1p{i^Ey@yBQ1`ciw_cja}+zxqVI^^)RsA1JQ7X@qJHhr_+Sy)<9@qmLi#J```g zrs(D2bB{LHzpQ-gM@Gx#@_+s2(YL=G@3^UV)Wokn*>1f!f8*aDY~HYHCPp9txb8x9 z+(`_SYinK906et9$pqB679B9gxW2xQ4J%aZby~C4joHmbFe|j8(}|FSg9B_LUZT4U_P9|(RU3GGBM#C6F2nS{Q z`4`MT@yI-c@Q#~?|N0xlag&7*&K2n&+;I5&Pc7f`uh#Q&Wug}I=C^+F+z*}U`|bN> z`iD0h{_Z35kY(?@dF@|+eZbofi}bTEIQ;!57eDrE>sb*o+eOys7cfsJiCdL*jK{MT-`aymJ7p=*y&^*GjEEz%RKo%PWy5nGRp`KL9g@KJp0sETky1wR`YaH)3>L;nH z${Akr?5pJ1FWkG?WgnmkpZT4|{LkypJ~as!{=Dsm{glEVd~1Bs@9)^&?cEV>`_Tdk zc;Mc&_-wrN?LY9sSxWJLe&d+8_g|ge`L5M?oWHy`)%T_Qo}OOxvhTlPKgIZ|CocZ| z9oxIr?dh%`Ur$=j$6;Z}2_I`sgEAc4-rhcY_ADsKqb@8xDW&mv%oHQ1oLvs1v<#6( zWy_U)YDA5UYV7Q5*i5FbL}9jHHRyzuO(`UAWR-^ z0bB%@z|6S}SBd0CnaD3QCs#AM2Q8f;)4I5fxfWm$IXEltx9 zjxRJqk5A?{TP@^lRoo>-OOl|_qO^sE_sli=DHE+dbr!Ar9=q+A=vYoUvk}P|qxjID zFF#tYg-d=u`Na75zJ0Oyh7f+KT7T^RSZcT7!=G*bNin$eW%qt_^vO$TO1m~JHj>CaVN=ERXC2(Xy9q$%)Gb=J_?W5dhxbE%kfq%?FEH(BAl-SKp zBNKpf)?0`LHj&K*fUJuCHE$ko$J++?u-a!N? zIZ+B)3ebY|o);PcQfr$-Q4~)<*4(pKTq z%HT3`?26NiJo^+wnX+SK?J_@A$YWg9zhaCbg(~EyczX&du9)bXo13Tp_LPSdxc|vy z5>>F4e5j;mB^~I*=)I7o;!Dr4s7D-y}dou zzNaq4zQ&dA?d>t>^W3>}&@Kjp!TS0-mUS|j7%LBlLxxcB)9-X?5*9N>h718fhbxAuKvw!lp$g@l`_zuAK#?yvWb;d;SSi?PULQxg=j?2hPD(ofQqd5=S(f&1!9q z!=-RLv|i*M-n(2b3Aba6lk4-(j+>K<;9rdD@%5|fbe3h~8}iR?oaXHt&Gd%c#qjA3 z#b-C_lfV5l<7VSbz^3l(>=3rrbxmC5aF$rD6mM^-u5;Rykt(=f^Z9(Kkq2ls!6--x zpqb}6vPc-Wxw)CClVNjn6Ys(HF%V9!8DV|xd*q{BYQ?zPcqi?PcTo}zr_{LAf)bK5 zNDp_BtNvYd1{yfk1AH&kq)HqD_xAAckWyez-Ec`GJPH+YgE|CVWpnlF@bHj~(9zKm zJ99$_Tu;xBa-HUsF*jpYiZpZ!n$*{TR=7QL(s;cX5?R~0ToaXKA_RZ&4C!M*q z`lI0~^yiOy%QLt3XwQYXPj1I)L5`d39(Dfdbc&!H930H&a{$cF&d!ncRhbBpkaY{KT?G&5;$pWC;KR{ z-F#2{?Qfq#g8$367BBx>1LMWN@#gY(o;=Olzd3*Dn^)uftE#&74dJ)Hb#$6Q7#(u=-lH4SCCTwe2%W|6`P-~OMjU(Qo};Va6di5)L^c|MQn#NWO=3h9NfSXCA| zi&9OSoNv%}N**3inBU;p8}(jdgn%v}4RXerMz=d>*J$H6FXo?!t)U6v^!ZZLgzy#K zMVV>b238ik?zA1i31RJtat8*$C*m(K|N! z*VNAx&OW#vfAq<{H~q|T^Sf5{&cR^N#&+XO_VGVHeCI}g`&r)J{^*l?cl}gx=9U#d zdp4W3vAy=j!LR?}?428f?fRLb;Rn~_+n?Nf!-odr8&(W9>QHzy=jjlL5Q1a1QnS{0 zTI5oA_L(Mq4>bLbkK!~jLN25?+MPqRYWqYd4MkHTMHB~IwKLbD1tVYK4x!tC zHxCwpq~3W$D3(ChlO|X?cx5OK!7dk5YOZ6{R1uVa(OG3gHn|kJH7I+r)L2MF z)Zcz|bX45){t@1=5`mV7di||yi=+1Aj~xDN8dR6b$;^s$S9bQL-#>iG_hk%U$h2JJ zx?9&`oBi8|4?h$Kz01G-o?`!&w-&E@+xc}sh{)EB)qGz6#$!j%{Pw%^-J9N8eE0W- z)R|iW&cn11H2o^-$F%La#&but2z#NbSyrTj4h{}Ly#VM|t6WUEt29ABQ*Si6a}&j) zhKLX>Ml})@qKE>@O@uDXk{k;#*PMt(gnoYM)PAm>>7a}u;Sj*drJq&{KKzzUOF=Pw zs9dW7TOiV)SeCQ$aZRUF^c=Bq2oyqaJs#7J^7GV+qTm>#IPkd!#$bc&2dEWhD&cWG zrPj%quj!gYr4FiEbY^lyjaJHLYAECJ@ukI`?=CBKHZVGdw9$M%zxnpHN50Vhw_^Fz zv;MIn_|`KA%?pax|8&17&xeUYh0+^e*?;)+%_p+O-#d3qQ7nY;mTUHwH)L=AnRPes za3|c=i(WN&=&$Negv-ADt=H}^uFvlNKsj6+7MjaxT8Qt^oGOe*P~%@vM|KodE>lMm zV(09H>l;qwV~W5|6m7Qv#bPm=&5jmFZ~jQ{!K3C)*In}MWqR`s z2VYsVZ~gf}3ae?-7(9+XgaC)px1H2<0yxG(kQs1}h%6S*k3ZNJ`o+Nd?xIz)=0W|Y z-2m5y%?eqlhEGF`3!EH&C2oBy>N2q@0UvLH<^_e*IiJJBLlUoGcO(+IUur&KtBGCi z(IcZ;i60LgwbEV>a1@aVXwVb`ZltH-)u=Z*Oo)k|cX@selsPCO0WRZ6KecxM0O!!> zs;Z{bY10`cPu3w52hO%8?{vGDnjx^%aH>+>9j%&427^Jb*DLOR-)hAR2W=kt3NQ?N zH{BJ#^kDoKPoBGHv;U%b9zyu^R{O@qZn`P|!CxMQ;{2ib$t|^^wvF}6-!lCC7wdoi zjV7bt6f*N^kC;&_0XVZ15HOPMOj&BgMob-sh&nHtsw-USF6PnwT5ND^~f}v7RE$R zJyt|J=tG2txj2_W+YlY3 zHl0b#q|`GN*K3WO>1oe79)p@A?>*80heqAfSO=;=K10$H6u*l*-(8|ExGGlz(CiRG z$l@)pDZ()P>Zi{wZ^{06qyF$C2d{o_b=P|bA&-RKu!TL7j0!whB4zC@uPuwU;#WU? zu(&1r(`ozRNA_Ry{^6T`dXnXeNH8q6UzFM&KR? z?H$7C+C^8IwdxVMj<6RR9_(ZQl8tV+>e52tv69G%-y1cV&;0O0O@g)di#lbTG_nAb z+;sXsiLHSoMnv3cUDqDYbR!4Jy12510$nS3Csto6%eFKPVbr2`@F9SWPYa(Qm$t2eVze0Rt$By{fQu~XR z+8T?AVuL51&V1UWo_ab;=kqycp2GD&OQ~EO;p_JI_qVsVYjrk;5Ej~Q?`MNo!C=E; zC_8J9nJz(Nl%p}LmNRrn*lafA^_51mID_o4k5(0oWvh9KNdp87yFI_w&V(cn(JJo+ ztWy=jedn6%i1ylM;UzHhX|+{lHPZ!4k5TvXVDmwo60j~cn&k?soL5E-XXktxalB2= zvv1)WDP`cVCX>nT?rvA-(JQ3F0WJzg8SbdlQ{HNyCt}IJ=9)ALQe|jBmk(cxcZem* z)Oc+*E?)l=8(3l>X*bFyS4g%YbXtu2YRWWhFc5FFK$majk+hmTY(raWRdDP~hN(41 z%4wEL0~M3zp%>cloMjoHi@6NB=2U`bYPC3GOOh6l;2h`kIlKw}$%`LLjhb_A(V4hf z!w>nEQaeEkacN8S_!*Y)QN|KP)#1YVAJ*ih1~mA+z@ep@$huU=rTFv!9?k=!SH}PV z1sh33K~x#Z#_bTP^SlZzqC?zUjjabvJLpU*Z=oBRm^LXTnhc#&?v}S4iKdiT+F&oS zTrO#mX|+?1%-Ga2VETI(()v$$gCpb6bnQ8vUVhFVo!1 zl|lQtCQoiX&>=0JP6YBA67%peUe8_!v&R~u8c=Tgd<)~ z&X}#~7YB707S1U_Rwp;Wd4i*y zFLr^_uF1Lkg+>8T8!0>M68*63`Uv6y&#NTwYk$0tG?ZF`S*kV(0xmiww@k!oFrw>} z+)pVEeP26#l7@S6*14Kfkwu;#))5Md>Ap>8$y`!GH>wSHsl|zPXYWI<8DJJ=zV7Jg zXgZx@z<7#14G2owAz<1T+G4y^Y4@w~PwEuT2=Z)@wzjs=_Ncy=sL?WLMJb}cB@az@ z_`#=bw5iniOlNPG8dHasBEtmMJaj(X{$v$$-1+V*gaEA^ zySvmNUv3Xk;3FWnppjG@h8m5J7P-jMh=z*gF2!aEol$Mhnb@jvtVzTh;slp1qXt2e zrP~PtwU`zF(gUSpkxpB}uF}KHYrq0suz9wH_!bp!G zg{@dawkECUcX&B6s$IheBDWYAd6MC&#f?Lyml_Gr)8!Q$JI{gtsCm{@w@;onI^Pbgm=AI@`1TD8Cy-FGf=AiKmkBa0# zPpcrE?Ltgl<3LmGv+%_T2RM~RX@gN>_FSXL_|o}&PR_B=%<)EK*C4h+uDPm9b+T4! z8X|v>=-8sSn%fn%FdeN1c(k2~DOg!|lhj&iTx$$%DK>#CUpyfLBr3bGg&mipqN&p= z$$OC}e;tR`Xk2DbBeh9I5MLVLI{gqd7@bQp8E&9S4g8r@N1;KEt;XC(HBuu6eN6|n zleZ@!{DZs3!*wE Date: Sun, 25 Jan 2026 09:49:45 -0500 Subject: [PATCH 11/56] make integers into floats to solve error in shader compilation --- examples/olcPGE3_Shockwave.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/olcPGE3_Shockwave.cpp b/examples/olcPGE3_Shockwave.cpp index eb609a6b..255dfc58 100644 --- a/examples/olcPGE3_Shockwave.cpp +++ b/examples/olcPGE3_Shockwave.cpp @@ -57,7 +57,7 @@ class Example_Shockwave : public olc::PixelGameEngine float wave(vec2 p) { - float waveAmplitude = 1; + float waveAmplitude = 1.; float waveThickness = 0.025; float waveRadius = sw_radius * 0.5; @@ -71,7 +71,7 @@ class Example_Shockwave : public olc::PixelGameEngine } else { - return 0; + return 0.; } } From 4588abf054e267c249d6df1191a743676baebdf1 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:13:42 +0000 Subject: [PATCH 12/56] 101-macos-code-refactoring --- dev/src/api_macos.cpp | 205 ++++------------------------------ dev/src/api_macos.h | 26 +---- dev/src/api_macos_wrapper.hpp | 91 ++------------- dev/src/host_apple_macos.cpp | 46 ++------ dev/src/host_apple_macos.h | 14 +-- dev/src/imload_macos.cpp | 73 ++++++------ 6 files changed, 82 insertions(+), 373 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index b8a3b5ee..f9e4b812 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -149,111 +149,40 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; - namespace ObjectiveCSEL { // Application memory management selectors - static SEL allocSel = nullptr; - static SEL initSel = nullptr; - static SEL setDelegateSel = nullptr; - static SEL releaseSel = nullptr; - static SEL isKindOfClassSel = nullptr; + static SEL allocSel, initSel, setDelegateSel, releaseSel, isKindOfClassSel = nullptr; // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel = nullptr; - static SEL activateIgnoringOtherAppsSel = nullptr; - static SEL setActivationPolicySel = nullptr; - static SEL runSel = nullptr; + static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel = nullptr; // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel = nullptr; - static SEL applicationDidFinishLaunchingSel = nullptr; - static SEL applicationWillTerminateSel = nullptr; - static SEL applicationDidBecomeActiveSel = nullptr; - static SEL applicationWillResignActiveSel = nullptr; + static SEL applicationWillFinishLaunchingSel, applicationDidFinishLaunchingSel, applicationWillTerminateSel, applicationDidBecomeActiveSel, applicationWillResignActiveSel = nullptr; // NSWindow creation, display, and management selectors - static SEL initWithContentRectSel = nullptr; - static SEL stringWithUTF8StringSel = nullptr; - static SEL setTitleSel = nullptr; - static SEL orderFrontRegardlessSel = nullptr; - static SEL setAcceptsMouseMovedEventsSel = nullptr; - static SEL makeFirstResponderSel = nullptr; - static SEL makeKeyAndOrderFrontSel = nullptr; - static SEL makeKeyWindowSel = nullptr; - static SEL frameSel = nullptr; - static SEL setFrameDisplaySel = nullptr; - static SEL setFrameSel = nullptr; + static SEL initWithContentRectSel, stringWithUTF8StringSel, setTitleSel, orderFrontRegardlessSel, setAcceptsMouseMovedEventsSel, makeKeyAndOrderFrontSel = nullptr; + static SEL makeKeyWindowSel, frameSel, setFrameDisplaySel, setFrameSel, makeFirstResponderSel = nullptr; // NSWindowDelegate lifecycle and event methods selectors - static SEL windowDidResizeSel = nullptr; - static SEL windowWillCloseSel = nullptr; - static SEL windowDidBecomeKeySel = nullptr; - static SEL windowDidResignKeySel = nullptr; - static SEL windowDidMiniaturizeSel = nullptr; - static SEL windowDidDeminiaturizeSel = nullptr; + static SEL windowDidResizeSel, windowWillCloseSel, windowDidBecomeKeySel, windowDidResignKeySel, windowDidMiniaturizeSel, windowDidDeminiaturizeSel = nullptr; // NSResponder keyboard and mouse event methods selectors - static SEL keyDownSel = nullptr; - static SEL keyUpSel = nullptr; - static SEL mouseDownSel = nullptr; - static SEL mouseUpSel = nullptr; - static SEL mouseDraggedSel = nullptr; - static SEL mouseMovedSel = nullptr; - static SEL rightMouseDownSel = nullptr; - static SEL rightMouseUpSel = nullptr; - static SEL rightMouseDraggedSel = nullptr; - static SEL otherMouseDownSel = nullptr; - static SEL otherMouseUpSel = nullptr; - static SEL otherMouseDraggedSel = nullptr; - static SEL scrollWheelSel = nullptr; - static SEL deltaXSel = nullptr; - static SEL deltaYSel = nullptr; + static SEL keyDownSel, keyUpSel, mouseDownSel, mouseUpSel, mouseDraggedSel, mouseMovedSel, rightMouseDownSel, rightMouseUpSel, rightMouseDraggedSel, otherMouseDownSel = nullptr; + static SEL otherMouseUpSel, otherMouseDraggedSel, scrollWheelSel, deltaXSel, deltaYSel = nullptr; // Managing first responder status and keyboard focus selectors - static SEL acceptsFirstResponderSel = nullptr; - static SEL becomeFirstResponderSel = nullptr; - static SEL canBecomeKeyViewSel = nullptr; - static SEL needsPanelToBecomeKeySel = nullptr; - static SEL drawRectSel = nullptr; - static SEL reshapeSel = nullptr; - static SEL updateSel = nullptr; + static SEL acceptsFirstResponderSel, becomeFirstResponderSel, canBecomeKeyViewSel, needsPanelToBecomeKeySel, drawRectSel, reshapeSel, updateSel = nullptr; // NSOpenGL pixel format, view, and context management selectors - static SEL initWithAttributesSel = nullptr; - static SEL initWithFramePixelFormatSel = nullptr; - static SEL setContentViewSel = nullptr; - static SEL contentViewSel = nullptr; - static SEL boundsSel = nullptr; - static SEL convertPointFromViewSel = nullptr; - static SEL openGLContextSel = nullptr; - static SEL makeCurrentContextSel = nullptr; - static SEL setAutoresizingMaskSel = nullptr; - static SEL flushBufferSel = nullptr; - static SEL displaySel = nullptr; - static SEL CGLContextObjSel = nullptr; - static SEL setValuesSel = nullptr; + static SEL initWithAttributesSel, initWithFramePixelFormatSel, setContentViewSel, contentViewSel, boundsSel, convertPointFromViewSel, openGLContextSel, makeCurrentContextSel = nullptr; + static SEL setAutoresizingMaskSel, flushBufferSel, displaySel, CGLContextObjSel, setValuesSel = nullptr; // Extracting data from NSEvent objects selectors - static SEL keyCodeSel = nullptr; - static SEL charactersSel = nullptr; - static SEL locationInWindowSel = nullptr; - static SEL buttonNumberSel = nullptr; - static SEL clickCountSel = nullptr; - static SEL modifierFlagsSel = nullptr; - static SEL utf8StringSel = nullptr; + static SEL keyCodeSel, charactersSel, locationInWindowSel, buttonNumberSel, clickCountSel, modifierFlagsSel, utf8StringSel = nullptr; // NSImage, NSBitmapImageRep, and image data access selectors - static SEL initWithContentsOfFileSel = nullptr; - static SEL representationsSel = nullptr; - static SEL countSel = nullptr; - static SEL objectAtIndexSel = nullptr; - static SEL pixelsWideSel = nullptr; - static SEL pixelsHighSel = nullptr; - static SEL bitsPerPixelSel = nullptr; - static SEL bytesPerRowSel = nullptr; - static SEL hasAlphaSel = nullptr; - static SEL bitmapDataSel = nullptr; + static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; // Initialize all selectors - called once at startup void initializeSelectors() { @@ -427,7 +356,6 @@ struct CustomOpenGLView; struct WindowDelegate; struct ImageLoader; - // enums for OpenGL pixel format and rendering capabilities enum class NSOpenGLPixelFormatAttribute : uint16_t { // Renderer selection @@ -572,7 +500,6 @@ static Window* gptrNSWindowEvents = nullptr; static Application* gptrApplicationDelegate = nullptr; static Window* gptrWindowDelegate = nullptr; - // Handles NSApplication delegate methods and lifecycle events struct ApplicationDelegate { Class isa; // Objective-C class pointer (required) @@ -625,8 +552,6 @@ struct Application { // Delete copy constructor and assignment Application(const Application&) = delete; Application& operator=(const Application&) = delete; - - // Allow move semantics Application(Application&&) = default; Application& operator=(Application&&) = default; }; @@ -734,7 +659,6 @@ struct ImageLoader { unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; }; - // Called when application is about to finish launching void applicationWillFinishLaunching(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; // Remove unused parameter warnings @@ -874,7 +798,6 @@ void convertToContentViewCoordinates(NSPoint& location) { } } -// Structure to hold common mouse event data struct MouseEventData { NSPoint location = {0.0, 0.0}; NSUInteger modifierFlags = 0; @@ -894,8 +817,6 @@ MouseEventData extractMouseEventData(id event) { return data; } - - // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings @@ -914,12 +835,10 @@ 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); } - } // Handle mouse drag events @@ -927,7 +846,6 @@ 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); } @@ -937,12 +855,6 @@ void view_mouseDragged(id self, SEL _cmd, id event) { void view_mouseMoved(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - // Note: mouseMoved events do not have buttonNumber except in the case of Touch screens - // When using a touch screen, mouseMoved events may include a buttonNumber corresponding to the touch point - // However, for standard mouse movement, buttonNumber is typically not applicable, but if you call it, it returns 0 - // Hence, we will use kNoButton constant to indicate no button is pressed during mouse movement and avoid - // using extractMouseEventData which retrieves buttonNumber - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); @@ -958,7 +870,6 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -969,7 +880,6 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -980,7 +890,6 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -992,7 +901,6 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -1003,7 +911,6 @@ void view_otherMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -1013,7 +920,6 @@ void view_otherMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -1035,7 +941,6 @@ void view_scrollWheel(id self, SEL _cmd, id event) { } } - // Determine if view can accept first responder status id view_acceptsFirstResponder(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -1063,25 +968,20 @@ id view_needsPanelToBecomeKey(id self, SEL _cmd) { // Empty drawRect method - prevents automatic drawing on main thread void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { (void)self;(void)_cmd;(void)dirtyRect; - - // Block automatic drawing - PGE engine thread will handle OpenGL rendering } // Empty reshape method - prevents automatic context reshaping on main thread void view_reshape(id self, SEL _cmd) { (void)self;(void)_cmd; - // Block automatic reshape - PGE engine thread will handle OpenGL updates } // Empty update method - prevents automatic context updates on main thread void view_update(id self, SEL _cmd) { (void)self;(void)_cmd; - // Block automatic update - PGE engine thread will handle OpenGL updates } // Create custom OpenGL view class Class createCustomOpenGLViewClass() { - // Check if class already exists Class existingClass = objc_getClass(kCustomOpenGLViewClass); if (existingClass) { return existingClass; @@ -1141,7 +1041,8 @@ void windowDidResize(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { - // Safely update frame data only - no OpenGL operations + + // Update frame data only window_updateFrameFromOSX(gptrWindowDelegate); // Get the new content view size @@ -1171,6 +1072,7 @@ void windowDidBecomeKey(id self, SEL _cmd, id notification) { } } +// handle window did resign key events void windowDidResignKey(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; if (gptrWindowDelegate && gptrWindowDelegate->windowDidResignKeyCallback) { @@ -1178,6 +1080,7 @@ void windowDidResignKey(id self, SEL _cmd, id notification) { } } +// Handle window did miniaturize events void windowDidMiniaturize(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; if (gptrWindowDelegate && gptrWindowDelegate->windowDidMiniaturizeCallback) { @@ -1258,7 +1161,6 @@ id createWindowDelegateForWindow(void) { return delegate; } - // Forward declarations for C API functions used by internal helpers extern "C" void window_setDelegate(Window* self, id delegate); @@ -1366,7 +1268,6 @@ extern "C" { window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, &self->contentViewFrame.width, &self->contentViewFrame.height); - // Update internal frame representation from actual NSWindow } // Show the window and set up event handling @@ -1390,8 +1291,8 @@ extern "C" { // Destroy the window void window_destroy(Window* self) { - // NSWindow will be cleaned up by autorelease pool (void)self; + // NSWindow will be cleaned up by autorelease pool } // Window delegate setter @@ -1429,11 +1330,7 @@ extern "C" { // Refresh internal frame representation from actual NSWindow (OSX) void window_updateFrameFromOSX(Window* self) { - - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); - - self->windowFrame = screenFrame; // Update internal frame representation - + self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); } // Get the content view size (excludes title bar and borders) @@ -1486,7 +1383,6 @@ extern "C" { } } - // Get Window frame (x, y, width, height) void window_getWindowFrame(const Window* self, double* x, double* y, double* width, double* height) { @@ -1611,20 +1507,10 @@ extern "C" { ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); // Make the OpenGL view the first responder so it can receive keyboard and mouse events - BOOL result = ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); - - if(result == NO) { - // Handle error if needed - //printf("Warning: Failed to make OpenGL view the first responder.\n"); - } + ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); // Additional debug: check if view can become key view - BOOL canBecomeKey = ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); - if(canBecomeKey == NO) { - // Handle error if needed - //printf("Warning: OpenGL view cannot become key view.\n"); - } - + ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); } // Implementation function @@ -1651,7 +1537,6 @@ extern "C" { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); return true; - } @@ -1662,11 +1547,8 @@ extern "C" { // Get the underlying CGLContextObj from the OpenGL renderer void* opengl_getCGLContextObj(struct OpenGLRenderer* self) { - if (!self || !self->glContext) { - return nullptr; - } + if (!self || !self->glContext) { return nullptr;} - // Get the CGLContextObj from the NSOpenGLContext return (void*)((void*(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::CGLContextObjSel); } @@ -1684,9 +1566,7 @@ extern "C" { // Enable or disable vertical synchronization (vsync) void opengl_setVsync(OpenGLRenderer* self, BOOL enabled) { - if (!self || !self->glContext) { - return; - } + if (!self || !self->glContext) { return; } // Make context current first ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); @@ -1695,7 +1575,6 @@ extern "C" { GLint swapInterval = enabled ? 1 : 0; // Use NSOpenGLContext setValues:forParameter: to set swap interval - // NSOpenGLContextParameterSwapInterval = 222 const GLint parameter = 222; // NSOpenGLContextParameterSwapInterval ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } @@ -1890,36 +1769,7 @@ extern "C" { self->bytesPerRow = kZeroRows; self->hasAlpha = NO; } - - // Create OpenGL texture from ImageLoader - unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* loader) { - if (!loader->isLoaded(const_cast(loader))) { - return 0; - } - - unsigned int textureID; - glGenTextures(1, &textureID); - glBindTexture(GL_TEXTURE_2D, textureID); - - // Set texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Determine format - GLenum format = (loader->bytesPerPixel == kRGBABytesPerPixel) ? GL_RGBA : GL_RGB; - - // Upload texture data - glTexImage2D(GL_TEXTURE_2D, 0, format, loader->width, loader->height, - 0, format, GL_UNSIGNED_BYTE, loader->pixelData); - - // Generate mipmaps for better quality at different scales - glGenerateMipmap(GL_TEXTURE_2D); - - return textureID; - } - + // ImageLoader constructor struct ImageLoader* imageloader_init() { struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); @@ -1940,7 +1790,6 @@ extern "C" { loader->getDetailedInfo = imageloader_getDetailedInfo; loader->isLoaded = imageloader_isLoaded; loader->getPixel = imageloader_getPixel; - loader->createOpenGLTexture = imageloader_createOpenGLTexture; return loader; } @@ -2102,38 +1951,32 @@ extern "C" { void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidResizeCallback = callback; self->windowDidResizeUserData = userData; - ensureWindowDelegate(self); // Ensure delegate exists when callbacks are set } void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { self->windowWillCloseCallback = callback; self->windowWillCloseUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidBecomeKeyCallback = callback; self->windowDidBecomeKeyUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidResignKeyCallback = callback; self->windowDidResignKeyUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidMiniaturizeCallback = callback; self->windowDidMiniaturizeUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidDeminiaturizeCallback = callback; self->windowDidDeminiaturizeUserData = userData; - ensureWindowDelegate(self); } // Application delegate callback setter functions diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index e83d8dc4..a7b4b621 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -26,7 +26,6 @@ extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) - // NSRect structure for window and view frames struct NSRect { double x{0.0}, y{0.0}; double width{800.0}, height{600.0}; @@ -69,16 +68,15 @@ extern "C" { void window_getContentViewFrame (const struct Window* self, double* x, double* y, double* width, double* height); void window_setContentViewFrame (struct Window* self, double* x, double* y, double* width, double* height); - // OpenGL Renderer API - as implemented in api_macos.c struct OpenGLRenderer* opengl_init (void); void opengl_initialize (struct OpenGLRenderer* self, struct Window* window); void opengl_setupContext (struct OpenGLRenderer* self); - void* opengl_getOpenGLContext (const struct OpenGLRenderer* self); // Returns id, cast to void* - void* opengl_getCGLContextObj (struct OpenGLRenderer* self); // Returns CGLContextObj, cast to void* - void* opengl_getCGLContextObjPtr (struct OpenGLRenderer* self); // Returns CGLContextObj as void* - void opengl_makeCurrentContext (struct OpenGLRenderer* self); // Make OpenGL context current - void opengl_setVsync (struct OpenGLRenderer* self, BOOL enabled); // Enable/disable vsync + void* opengl_getOpenGLContext (const struct OpenGLRenderer* self); // Returns id, cast to void* + void* opengl_getCGLContextObj (struct OpenGLRenderer* self); // Returns CGLContextObj, cast to void* + void* opengl_getCGLContextObjPtr (struct OpenGLRenderer* self); // Returns CGLContextObj as void* + void opengl_makeCurrentContext (struct OpenGLRenderer* self); // Make OpenGL context current + void opengl_setVsync (struct OpenGLRenderer* self, BOOL enabled); // Enable/disable vsync void opengl_destroy (struct OpenGLRenderer* self); bool opengl_resetContextForSize (struct OpenGLRenderer* self, double width, double height); @@ -91,7 +89,6 @@ extern "C" { void imageloader_getDetailedInfo (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha); BOOL imageloader_isLoaded (const struct ImageLoader* self); BOOL imageloader_getPixel (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha); - unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* self); // Autorelease pool management void* objc_autoreleasePoolPush (void); @@ -100,14 +97,9 @@ extern "C" { // OpenGL functions we might need void glDeleteTextures(int n, const unsigned int* textures); - // =========================================================================== - // EVENT HANDLING API - // =========================================================================== - // Event callback function types typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); - //typedef void (*MouseMoveEventCallback) (double x, double y, unsigned int modifierFlags, void* userData); // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); @@ -128,10 +120,6 @@ extern "C" { void window_enableEventHandling (struct Window* self); void window_disableEventHandling (struct Window* self); - // =========================================================================== - // APPLICATION DELEGATE EVENT HANDLING API - // =========================================================================== - // Application delegate callback function type typedef void (*ApplicationDelegateCallback)(void* userData); @@ -141,10 +129,6 @@ extern "C" { void application_setWillTerminateCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); void application_setDidBecomeActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); void application_setWillResignActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); - - // =========================================================================== - // WINDOW DELEGATE EVENT HANDLING API - // =========================================================================== // Window delegate callback function type typedef void (*WindowDelegateCallback)(void* userData); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index 6edf411b..705dd961 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -40,57 +40,6 @@ namespace olc { AutoreleasePool& operator=(AutoreleasePool&&) = delete; }; - // RAII wrapper for OpenGL texture management - class OpenGLTexture { - private: - unsigned int textureID_; - - public: - explicit constexpr OpenGLTexture(unsigned int id = 0) noexcept : textureID_(id) {} - - ~OpenGLTexture() noexcept { - if (textureID_ != 0) { - glDeleteTextures(1, &textureID_); - } - } - - constexpr unsigned int get() const noexcept { return textureID_; } - constexpr bool isValid() const noexcept { return textureID_ != 0; } - - // Release ownership - unsigned int release() noexcept { - unsigned int id = textureID_; - textureID_ = 0; - return id; - } - - // Reset with new texture ID - void reset(unsigned int id = 0) noexcept { - if (textureID_ != 0) { - glDeleteTextures(1, &textureID_); - } - textureID_ = id; - } - - // Move semantics - OpenGLTexture(OpenGLTexture&& other) noexcept : textureID_(other.textureID_) { - other.textureID_ = 0; - } - - OpenGLTexture& operator=(OpenGLTexture&& other) noexcept { - if (this != &other) { - reset(); - textureID_ = other.textureID_; - other.textureID_ = 0; - } - return *this; - } - - // Non-copyable - OpenGLTexture(const OpenGLTexture&) = delete; - OpenGLTexture& operator=(const OpenGLTexture&) = delete; - }; - // Exception class for framework errors class FrameworkException : public std::runtime_error { public: @@ -149,8 +98,7 @@ namespace olc { // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } - - + // Set callback for application will finish launching event void setWillFinishLaunchingCallback(std::function callback) { setCallback(application_setWillFinishLaunchingCallback, std::move(callback)); @@ -238,7 +186,6 @@ namespace olc { if (!window_) { throw FrameworkException("Failed to create window"); } - } ~Window() { @@ -286,20 +233,21 @@ namespace olc { return NSRect{0, 0, 0, 0}; } + // Set the window frame size void setFrameSize(int32_t width, int32_t height) noexcept { setFrameSize(static_cast(width), static_cast(height)); } + void setFrameSize(float width, float height) noexcept { setFrameSize(static_cast(width), static_cast(height)); } - // Set the window frame size + void setFrameSize(double width, double height) noexcept { if (window_) { double x = 0.0, y = 0.0; window_getWindowFrame(window_, &x, &y, nullptr, nullptr); window_setWindowFrame(window_, x, y, width, height); } - } void getFrameSize(int32_t& width, int32_t& height) const noexcept { @@ -324,13 +272,15 @@ namespace olc { } } + // Set the window position void setPosition(int32_t x, int32_t y) noexcept { setPosition(static_cast(x), static_cast(y)); } + void setPosition(float x, float y) noexcept { setPosition(static_cast(x), static_cast(y)); } - // Set the window position + void setPosition(double x, double y) noexcept { if (window_) { window_setWindowPosition(window_, x, y); @@ -345,6 +295,7 @@ namespace olc { } } + // Set the window size void setWindowSize(int32_t width, int32_t height) noexcept { setWindowSize(static_cast(width), static_cast(height)); } @@ -352,7 +303,7 @@ namespace olc { void setWindowSize(float width, float height) noexcept { setWindowSize(static_cast(width), static_cast(height)); } - // Set the window size + void setWindowSize(double width, double height) noexcept { if (window_) { window_setWindowSize(window_, width, height); @@ -382,7 +333,6 @@ namespace olc { } } - // Context view frame getters and setters void setContentViewPosition(int32_t x, int32_t y) noexcept { setContentViewPosition(static_cast(x), static_cast(y)); @@ -458,8 +408,6 @@ namespace olc { } } - - // Set callback for window resize events void setWindowDidResizeCallback(std::function callback) { setCallback(window_setWindowDidResizeCallback, std::move(callback)); @@ -528,7 +476,6 @@ namespace olc { ~OpenGLRenderer() { if (renderer_) { opengl_destroy(renderer_); - // Note: opengl_destroy already calls free() on the renderer } } @@ -645,22 +592,7 @@ namespace olc { bool isLoaded() const noexcept { return loaded_ && loader_ && imageloader_isLoaded(loader_); } - - OpenGLTexture createOpenGLTexture() const noexcept { - if (loader_ && loaded_) { - return OpenGLTexture(imageloader_createOpenGLTexture(loader_)); - } - return OpenGLTexture{}; - } - - // Legacy method for backwards compatibility - unsigned int createOpenGLTextureID() const noexcept { - if (loader_ && loaded_) { - return imageloader_createOpenGLTexture(loader_); - } - return 0; - } - + void getImageInfo(int& width, int& height, int& bytesPerPixel) const noexcept { if (loader_ && loaded_) { imageloader_getImageInfo(loader_, &width, &height, &bytesPerPixel); @@ -696,7 +628,6 @@ namespace olc { } }; - // Keyboard event data structure struct KeyEvent { unsigned short keyCode; @@ -937,8 +868,6 @@ namespace olc { EventHandler& operator=(EventHandler&&) = delete; }; - - } // namespace macos } // namespace apis } // namespace olc diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index fa90d4df..f5adfe45 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -6,10 +6,8 @@ //! START IMPLEMENTATION namespace olc::host { - bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible) - { + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning - //TODO: Johnngy63 find out what bBlockIfPossible is supposed to do // Create MacOS Application instance pMacApplication = std::make_unique(); @@ -45,10 +43,7 @@ namespace olc::host { return true; } - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - // Update the PGE window with the actual window size given by MacOS - + bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ pPGEwindow = pWindow; pPGEwindow->SetWindowPosition(vWindowPos); pPGEwindow->SetWindowSize(vWindowSize); // Temporary small size to avoid large window on creation @@ -63,17 +58,14 @@ namespace olc::host { } - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow) - { - // TODO: Gracefully close the window and clean up resources + bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ if (!pMacOSWindow) return false; if (!pWindow) return false; pWindow->olc_OnWindowClose(); return true; } - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow) - { + bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ if (!pMacOSWindow) return false; dispatch_async(dispatch_get_main_queue(), ^{ pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); @@ -81,9 +73,7 @@ namespace olc::host { return true; } - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow) - { - + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ // While the PGE is running, if there are pending main thread tasks, process them, this causes PGE to wait bSkipFrame = ExecutePendingMainThreadTasks(); @@ -244,7 +234,6 @@ namespace olc::host { } vPendingMainThreadTasks.clear(); - // Release any locks on the PGE // 4. Main Thread unlocks PGE Thread { std::lock_guard lock(pgeThreadPendingTasksMutex); @@ -252,49 +241,34 @@ namespace olc::host { isMainThreadResetting = false; // Reset main thread flag } pgeThreadResetCondition.notify_all(); // Wake up PGE thread - return res; - } //------ Events Handlers ----- void Host_Apple_MacOS::MacApplicationEventsHandler() { - // Application event handling code here - // Set application delegate event handlers - pMacApplication->setWillFinishLaunchingCallback([]() { - //std::cout << "--> Application delegate: Will finish launching" << std::endl; - }); + pMacApplication->setWillFinishLaunchingCallback([]() { }); pMacApplication->setDidFinishLaunchingCallback([&]() { - //std::cout << "--> Application delegate: Did finish launching" << std::endl; // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); }); pMacApplication->setWillTerminateCallback([&]() { - //std::cout << "--> Application delegate: Will terminate" << std::endl; // TODO: Johnngy63 - Implement olc_OnDestory in window.h/cpp - }); - pMacApplication->setDidBecomeActiveCallback([]() { - //std::cout << "--> Application delegate: Did become active" << std::endl; - }); + pMacApplication->setDidBecomeActiveCallback([]() { }); - pMacApplication->setWillResignActiveCallback([]() { - //std::cout << "--> Application delegate: Will resign active" << std::endl; - }); + pMacApplication->setWillResignActiveCallback([]() { }); } void Host_Apple_MacOS::MacWindowEventsHandler() { - // Window event handling code here pMacOSWindow->setWindowDidResizeCallback([&]() { AddPendingMainThreadTask(RESIZE_WINDOW); - }); pMacOSWindow->setWindowWillCloseCallback([&]() { @@ -310,16 +284,13 @@ namespace olc::host { pMacOSWindow->setWindowDidResignKeyCallback([&]() { //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp - }); pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowMinimize in window.h/cpp if needed AddPendingMainThreadTask(MINIMIZE_WINDOW); }); pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp if needed AddPendingMainThreadTask(DEMINIMIZE_WINDOW); }); @@ -424,7 +395,6 @@ namespace olc::host { } - } //! END IMPLEMENTATION #endif /* OLC_HOST == OLC_HOST_MACOS */ diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 9a2696d0..566d08af 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -34,24 +34,19 @@ namespace olc { namespace host { - // Manages our MacOS Host class Host_Apple_MacOS : public olc::host::Host { public: - olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window - //GLint glSwapInterval = 0; // VSync disbaled by default public: Host_Apple_MacOS() = default; virtual ~Host_Apple_MacOS() {}; public: - // Check/Get last error HostError GetLastError() const { return lastError; } - public: virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) override; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; @@ -60,7 +55,6 @@ namespace olc virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - virtual bool ConnectHostResourceToRenderer() override; // Wait for entire host desktop refresh (for smooooth vsync), @@ -70,7 +64,6 @@ namespace olc HostError lastError = HostError::None; public: - // MacOS Application and Window pointers std::unique_ptr pMacApplication = nullptr; std::unique_ptr pMacOSWindow = nullptr; @@ -80,9 +73,7 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; - - private: - + private: enum MAINTASKS{ NONE, CREATE_OPENGL_RENDERER, @@ -102,7 +93,7 @@ namespace olc std::vector vMacOSWindowDescriptors; // Vector to hold window descriptors bool enableVSync = false; // VSync enabled flag - bool bSkipFrame = false; // Flag to indicate if frame should be skipped + bool bSkipFrame = false; // Flag to indicate if frame should be skipped // Thread synchronization for PGE Thread V Main thread mutable std::mutex mainThreadPendingTasksMutex; // Mutex for main thread pending tasks @@ -121,7 +112,6 @@ namespace olc double height = 600.0; } frameBounds; - // TODO: Should these be private? void MacApplicationEventsHandler(); void MacWindowEventsHandler(); void MacEventsHandler(); diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index 39af66e6..c144beb1 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -11,48 +11,41 @@ namespace olc::imload return false; // File does not exist } - try { - // Create macOS API wrapper image loader - olc::apis::macos::ImageLoader loader; - - // Load the image file - if (!loader.loadFromFile(sFileName) || !loader.isLoaded()) { - return false; // Failed to load file - } - - // Get image dimensions and info - int width, height, bytesPerPixel; - loader.getImageInfo(width, height, bytesPerPixel); - - if (width <= 0 || height <= 0) { - return false; // Invalid dimensions - } - - // Get raw pixel data from the loader - unsigned char* pixelData = imageloader_getPixelData(loader.getCHandle()); - if (!pixelData) { - return false; // Failed to get pixel data - } - - // Create our olc::Image - if (!image.Create({width, height})) { - return false; // Failed to create image - } - - // Clear and resize the pixel vector - image.GetPixels().clear(); - image.GetPixels().resize(width * height); - - // Copy pixel data - assuming the loader provides RGBA data - // The api_macos should provide RGBA format with 4 bytes per pixel - std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); - - return true; + // Create macOS API wrapper image loader + olc::apis::macos::ImageLoader loader; + + if (!loader.loadFromFile(sFileName) || !loader.isLoaded()) { + return false; // Failed to load file } - catch (const std::exception& e) { - // Handle any exceptions from the wrapper - return false; + + // Get image dimensions and info + int width, height, bytesPerPixel; + loader.getImageInfo(width, height, bytesPerPixel); + + if (width <= 0 || height <= 0) { + return false; // Invalid dimensions } + + // Get raw pixel data from the loader + unsigned char* pixelData = imageloader_getPixelData(loader.getCHandle()); + if (!pixelData) { + return false; // Failed to get pixel data + } + + // Create our olc::Image + if (!image.Create({width, height})) { + return false; // Failed to create image + } + + // Clear and resize the pixel vector + image.GetPixels().clear(); + image.GetPixels().resize(width * height); + + // The api_macos will provide RGBA format with 4 bytes per pixel + std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + + return true; + } bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) From cacbc08b56ac4617465e988607f90325077e0a06 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Tue, 27 Jan 2026 18:09:08 +0000 Subject: [PATCH 13/56] added "Keyboard Input" example - BREAKS HOSTS - FIXES NEEDED EVEN IF JUST RETURN QWERTY FOR NOW --- dev/msvc/olcPGE3.vcxproj | 1 + dev/msvc/olcPGE3.vcxproj.filters | 3 + .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 16 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/config.h | 4 +- dev/src/core.cpp | 6 + dev/src/core.h | 1 + dev/src/host_iface.h | 2 + dev/src/host_win_winapi.cpp | 203 +++++- dev/src/host_win_winapi.h | 7 +- dev/src/hw_keyboard.cpp | 258 ++++++++ dev/src/hw_keyboard.h | 121 ++++ dev/src/sh_template.h | 7 + dev/src/window.cpp | 8 + dev/src/window.h | 5 +- examples/olcPGE3_Keyboard.cpp | 114 ++++ olcPixelGameEngine3.h | 593 +++++++++++++++++- 17 files changed, 1293 insertions(+), 59 deletions(-) create mode 100644 dev/src/hw_keyboard.cpp create mode 100644 examples/olcPGE3_Keyboard.cpp diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index c63e0da6..a203cebd 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -291,6 +291,7 @@ true + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index a6248680..09e92632 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -218,5 +218,8 @@ Hosts\Wayland Specific + + Source Files + \ 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 062eb59e..09326a02 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -49,13 +49,19 @@ true true + true true true true - + + true + true + true + true + true true @@ -198,7 +204,7 @@ true - make_pge3.bat + $(ProjectDir)make_pge3.bat Create single header library @@ -221,7 +227,7 @@ true - make_pge3.bat + $(ProjectDir)make_pge3.bat Create single header library @@ -240,7 +246,7 @@ true - make_pge3.bat + $(ProjectDir)make_pge3.bat Create single header library @@ -263,7 +269,7 @@ true - make_pge3.bat + $(ProjectDir)make_pge3.bat Create single header library diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 68337fe7..5907981e 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -66,6 +66,9 @@ Source Files + + Source Files + diff --git a/dev/src/config.h b/dev/src/config.h index 0f68626c..41eb893a 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -108,10 +108,12 @@ #define OLC_MOUSE_BUTTONS 5 +#define OLC_DEFAULT_KEYBOARD_LAYOUT olc::KeyboardLayout::QWERTY_UK + #define OLC_GPU_MAX_VERTICES 8192 #define OLC_GPU_ERRORCHECK 0 #define OLC_MSAA_SAMPLES 4 -#define OLC_DEFAULT_CIRCLE_FACETS 32 +#define OLC_DEFAULT_CIRCLE_FACETS 16 #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " diff --git a/dev/src/core.cpp b/dev/src/core.cpp index ca20abeb..7af45394 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -65,6 +65,7 @@ namespace olc { // Input Changes mouse.UpdateState(); + keyboard.UpdateState(); draw.SetGPU(pRenderer); draw.SetTarget(GetDefaultImage()); @@ -239,6 +240,11 @@ namespace olc return mouse; } + olc::hw::Keyboard& PGEWindow::GetKeyboard() + { + return keyboard; + } + const olc::vi2d& PGEWindow::ScreenSize() { return GetDefaultImage().Size(); diff --git a/dev/src/core.h b/dev/src/core.h index b4875da6..dc1e4d61 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -98,6 +98,7 @@ namespace olc // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); + olc::hw::Keyboard& GetKeyboard(); // Returns the current size of the "screen" in pixels const olc::vi2d& ScreenSize(); diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index dfb1c2e1..b70799df 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -94,6 +94,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; + protected: HostError lastError = HostError::None; }; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 060ea376..22f9a01b 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -6,31 +6,9 @@ namespace olc::host // Forward Declaration static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - // Windows app needs an event loop somewhere. This is blocking of course. This loop handles - // all windows created for this host. - bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) - { - if (bBlockIfPossible) - { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - else - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - return true; - } + + // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get // access to the actual host instance, which can more conveninetly process @@ -76,6 +54,147 @@ namespace olc::host return w; } + Host_Windows_WinAPI::Host_Windows_WinAPI() + { + // Map Windows Defined VK_ Codes to olc::KeyCodes + mapKeys[0x00] = Key::NONE; + + // Windows doesn't define A-Z + mapKeys[0x41] = Key::A; + mapKeys[0x42] = Key::B; + mapKeys[0x43] = Key::C; + mapKeys[0x44] = Key::D; + mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; + mapKeys[0x47] = Key::G; + mapKeys[0x48] = Key::H; + mapKeys[0x49] = Key::I; + mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; + mapKeys[0x4C] = Key::L; + mapKeys[0x4D] = Key::M; + mapKeys[0x4E] = Key::N; + mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; + mapKeys[0x51] = Key::Q; + mapKeys[0x52] = Key::R; + mapKeys[0x53] = Key::S; + mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; + mapKeys[0x56] = Key::V; + mapKeys[0x57] = Key::W; + mapKeys[0x58] = Key::X; + mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + // Windows doesnt define numeric keys + mapKeys[0x30] = Key::K0; + mapKeys[0x31] = Key::K1; + mapKeys[0x32] = Key::K2; + mapKeys[0x33] = Key::K3; + mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; + mapKeys[0x36] = Key::K6; + mapKeys[0x37] = Key::K7; + mapKeys[0x38] = Key::K8; + mapKeys[0x39] = Key::K9; + + // Function Keys + mapKeys[VK_F1] = Key::F1; + mapKeys[VK_F2] = Key::F2; + mapKeys[VK_F3] = Key::F3; + mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; + mapKeys[VK_F6] = Key::F6; + mapKeys[VK_F7] = Key::F7; + mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; + mapKeys[VK_F10] = Key::F10; + mapKeys[VK_F11] = Key::F11; + mapKeys[VK_F12] = Key::F12; + + // Arrow Keys + mapKeys[VK_DOWN] = Key::DOWN; + mapKeys[VK_LEFT] = Key::LEFT; + mapKeys[VK_RIGHT] = Key::RIGHT; + mapKeys[VK_UP] = Key::UP; + + // Other Keys + mapKeys[VK_BACK] = Key::BACK; + mapKeys[VK_ESCAPE] = Key::ESCAPE; + mapKeys[VK_RETURN] = Key::ENTER; + mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; + mapKeys[VK_TAB] = Key::TAB; + mapKeys[VK_DELETE] = Key::DEL; + mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; + mapKeys[VK_PRIOR] = Key::PGUP; + mapKeys[VK_NEXT] = Key::PGDN; + mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; + mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + + // Numpad + mapKeys[VK_NUMPAD0] = Key::NP0; + mapKeys[VK_NUMPAD1] = Key::NP1; + mapKeys[VK_NUMPAD2] = Key::NP2; + mapKeys[VK_NUMPAD3] = Key::NP3; + mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; + mapKeys[VK_NUMPAD6] = Key::NP6; + mapKeys[VK_NUMPAD7] = Key::NP7; + mapKeys[VK_NUMPAD8] = Key::NP8; + mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; + mapKeys[VK_ADD] = Key::NP_ADD; + mapKeys[VK_DIVIDE] = Key::NP_DIV; + mapKeys[VK_SUBTRACT] = Key::NP_SUB; + mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + // OEM Keys + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + } + + // Windows app needs an event loop somewhere. This is blocking of course. This loop handles + // all windows created for this host. + bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) + { + if (bBlockIfPossible) + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else + { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return true; + } + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { olc_IgnoreUnused(bFullScreen); @@ -241,6 +360,25 @@ namespace olc::host // case WM_KEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; // case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(int32_t(wParam), true); return 0; // case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; + + case WM_KEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } + + case WM_KEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } + case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); @@ -325,6 +463,23 @@ namespace olc::host return DefWindowProc(hWnd, uMsg, wParam, lParam); } + olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const + { + HKL kbl = ::GetKeyboardLayout(0); + size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; + + if (highWord == 0x00000409) // US + return olc::KeyboardLayout::QWERTY_US; + else if(highWord == 0x00000809) // UK + return olc::KeyboardLayout::QWERTY_UK; + else if(highWord == 0x00000407) // DE + return olc::KeyboardLayout::QWERTZ; + else if(highWord == 0x0000040C) // FR + return olc::KeyboardLayout::AZERTY; + + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } + }; //! END IMPLEMENTATION diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 0da6a964..a7e8b3fe 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -59,7 +59,7 @@ namespace olc public: - Host_Windows_WinAPI() = default; + Host_Windows_WinAPI(); virtual ~Host_Windows_WinAPI() {}; @@ -78,6 +78,8 @@ namespace olc LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + olc::KeyboardLayout GetKeyboardLayout() const override; + std::string test; private: @@ -85,6 +87,9 @@ namespace olc std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + }; } } diff --git a/dev/src/hw_keyboard.cpp b/dev/src/hw_keyboard.cpp new file mode 100644 index 00000000..f6a11dc6 --- /dev/null +++ b/dev/src/hw_keyboard.cpp @@ -0,0 +1,258 @@ +#include "hw_keyboard.h" + +#if OLC_HOST == OLC_HOST_WINDOWS +#include "host_win_winapi.h" +#include +#endif + + +//! START IMPLEMENTATION +namespace olc::hw +{ + + void Keyboard::UseKeyboardLayout(const olc::KeyboardLayout kbl) + { + mapKeyGlyphs.clear(); + + // Define appropriate glyphs for each key for the selected keyboard layout + if (kbl == olc::KeyboardLayout::QWERTY_UK) + { + mapKeyGlyphs = + { + // PGE Key, no mods, shift mod, ctrl mod, alt mod + {olc::Key::A, {"a", "A", "a", "a"}}, + {olc::Key::B, {"b", "B", "b", "b"}}, + {olc::Key::C, {"c", "C", "c", "c"}}, + {olc::Key::D, {"d", "D", "d", "d"}}, + {olc::Key::E, {"e", "E", "e", "e"}}, + {olc::Key::F, {"f", "F", "f", "f"}}, + {olc::Key::G, {"g", "G", "g", "g"}}, + {olc::Key::H, {"h", "H", "h", "h"}}, + {olc::Key::I, {"i", "I", "i", "i"}}, + {olc::Key::J, {"j", "J", "j", "j"}}, + {olc::Key::K, {"k", "K", "k", "k"}}, + {olc::Key::L, {"l", "L", "l", "l"}}, + {olc::Key::M, {"m", "M", "m", "m"}}, + {olc::Key::N, {"n", "N", "n", "n"}}, + {olc::Key::O, {"o", "O", "o", "o"}}, + {olc::Key::P, {"p", "P", "p", "p"}}, + {olc::Key::Q, {"q", "Q", "q", "q"}}, + {olc::Key::R, {"r", "R", "r", "r"}}, + {olc::Key::S, {"s", "S", "s", "s"}}, + {olc::Key::T, {"t", "T", "t", "t"}}, + {olc::Key::U, {"u", "U", "u", "u"}}, + {olc::Key::V, {"v", "V", "v", "v"}}, + {olc::Key::W, {"w", "W", "w", "w"}}, + {olc::Key::X, {"x", "X", "x", "x"}}, + {olc::Key::Y, {"y", "Y", "y", "y"}}, + {olc::Key::Z, {"z", "Z", "z", "z"}}, + + {olc::Key::K0, {"0", ")", "0", "0"}}, + {olc::Key::K1, {"1", "!", "1", "1"}}, + {olc::Key::K2, {"2", "\"","2", "2"}}, + {olc::Key::K3, {"3", "#", "3", "3"}}, + {olc::Key::K4, {"4", "$", "4", "4"}}, + {olc::Key::K5, {"5", "%", "5", "5"}}, + {olc::Key::K6, {"6", "^", "6", "6"}}, + {olc::Key::K7, {"7", "&", "7", "7"}}, + {olc::Key::K8, {"8", "*", "8", "8"}}, + {olc::Key::K9, {"9", "(", "9", "9"}}, + + {olc::Key::NP0, {"0", "0", "0", "0"}}, + {olc::Key::NP1, {"1", "1", "1", "1"}}, + {olc::Key::NP2, {"2", "2", "2", "2"}}, + {olc::Key::NP3, {"3", "3", "3", "3"}}, + {olc::Key::NP4, {"4", "4", "4", "4"}}, + {olc::Key::NP5, {"5", "5", "5", "5"}}, + {olc::Key::NP6, {"6", "6", "6", "6"}}, + {olc::Key::NP7, {"7", "7", "7", "7"}}, + {olc::Key::NP8, {"8", "8", "8", "8"}}, + {olc::Key::NP9, {"9", "9", "9", "9"}}, + {olc::Key::NP_MUL, {"*", "*", "*", "*"}}, + {olc::Key::NP_DIV, {"/", "/", "/", "/"}}, + {olc::Key::NP_ADD, {"+", "+", "+", "+"}}, + {olc::Key::NP_SUB, {"-", "-", "-", "-"}}, + {olc::Key::NP_DECIMAL, {".", ".", ".", "."}}, + + {olc::Key::PERIOD, {".", ">", ".", "."}}, + {olc::Key::EQUALS, {"=", "+", "=", "="}}, + {olc::Key::COMMA, {",", "<", ",", ","}}, + {olc::Key::MINUS, {"-", "_", "-", "-"}}, + {olc::Key::SPACE, {" ", " ", " ", " "}}, + {olc::Key::ENTER, {"\n", "\n ", "\n", "\n"}}, + + {olc::Key::OEM_1, {";", ":", ";", ";"}}, + {olc::Key::OEM_2, {"/", "?", "/", "/"}}, + {olc::Key::OEM_3, {"\'","@", "\'", "\'"}}, + {olc::Key::OEM_4, {"[", "{", "[", "["}}, + {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, + {olc::Key::OEM_6, {"]", "}", "]", "]"}}, + {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + + // Give these keys glyphs so they can be interpreted in text editing + {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, + {olc::Key::BACK, {"\b", "\b", "\b", "\b"}}, + {olc::Key::DEL, {"_X", "_X", "_X", "_X"}}, + {olc::Key::LEFT, {"_L", "_L", "_L", "_L"}}, + {olc::Key::RIGHT, {"_R", "_R", "_R", "_R"}}, + {olc::Key::UP, {"_U", "_U", "_U", "_U"}}, + {olc::Key::DOWN, {"_D", "_D", "_D", "_D"}}, + }; + } + + // Define appropriate glyphs for each key for the selected keyboard layout + if (kbl == olc::KeyboardLayout::QWERTY_US) + { + mapKeyGlyphs = + { + // PGE Key, no mods, shift mod, ctrl mod, alt mod + {olc::Key::A, {"a", "A", "a", "a"}}, + {olc::Key::B, {"b", "B", "b", "b"}}, + {olc::Key::C, {"c", "C", "c", "c"}}, + {olc::Key::D, {"d", "D", "d", "d"}}, + {olc::Key::E, {"e", "E", "e", "e"}}, + {olc::Key::F, {"f", "F", "f", "f"}}, + {olc::Key::G, {"g", "G", "g", "g"}}, + {olc::Key::H, {"h", "H", "h", "h"}}, + {olc::Key::I, {"i", "I", "i", "i"}}, + {olc::Key::J, {"j", "J", "j", "j"}}, + {olc::Key::K, {"k", "K", "k", "k"}}, + {olc::Key::L, {"l", "L", "l", "l"}}, + {olc::Key::M, {"m", "M", "m", "m"}}, + {olc::Key::N, {"n", "N", "n", "n"}}, + {olc::Key::O, {"o", "O", "o", "o"}}, + {olc::Key::P, {"p", "P", "p", "p"}}, + {olc::Key::Q, {"q", "Q", "q", "q"}}, + {olc::Key::R, {"r", "R", "r", "r"}}, + {olc::Key::S, {"s", "S", "s", "s"}}, + {olc::Key::T, {"t", "T", "t", "t"}}, + {olc::Key::U, {"u", "U", "u", "u"}}, + {olc::Key::V, {"v", "V", "v", "v"}}, + {olc::Key::W, {"w", "W", "w", "w"}}, + {olc::Key::X, {"x", "X", "x", "x"}}, + {olc::Key::Y, {"y", "Y", "y", "y"}}, + {olc::Key::Z, {"z", "Z", "z", "z"}}, + + {olc::Key::K0, {"0", ")", "0", "0"}}, + {olc::Key::K1, {"1", "!", "1", "1"}}, + {olc::Key::K2, {"2", "@","2", "2"}}, + {olc::Key::K3, {"3", "#", "3", "3"}}, + {olc::Key::K4, {"4", "$", "4", "4"}}, + {olc::Key::K5, {"5", "%", "5", "5"}}, + {olc::Key::K6, {"6", "^", "6", "6"}}, + {olc::Key::K7, {"7", "&", "7", "7"}}, + {olc::Key::K8, {"8", "*", "8", "8"}}, + {olc::Key::K9, {"9", "(", "9", "9"}}, + + {olc::Key::NP0, {"0", "0", "0", "0"}}, + {olc::Key::NP1, {"1", "1", "1", "1"}}, + {olc::Key::NP2, {"2", "2", "2", "2"}}, + {olc::Key::NP3, {"3", "3", "3", "3"}}, + {olc::Key::NP4, {"4", "4", "4", "4"}}, + {olc::Key::NP5, {"5", "5", "5", "5"}}, + {olc::Key::NP6, {"6", "6", "6", "6"}}, + {olc::Key::NP7, {"7", "7", "7", "7"}}, + {olc::Key::NP8, {"8", "8", "8", "8"}}, + {olc::Key::NP9, {"9", "9", "9", "9"}}, + {olc::Key::NP_MUL, {"*", "*", "*", "*"}}, + {olc::Key::NP_DIV, {"/", "/", "/", "/"}}, + {olc::Key::NP_ADD, {"+", "+", "+", "+"}}, + {olc::Key::NP_SUB, {"-", "-", "-", "-"}}, + {olc::Key::NP_DECIMAL, {".", ".", ".", "."}}, + + {olc::Key::PERIOD, {".", ">", ".", "."}}, + {olc::Key::EQUALS, {"=", "+", "=", "="}}, + {olc::Key::COMMA, {",", "<", ",", ","}}, + {olc::Key::MINUS, {"-", "_", "-", "-"}}, + {olc::Key::SPACE, {" ", " ", " ", " "}}, + {olc::Key::ENTER, {"\n", "\n ", "\n", "\n"}}, + + {olc::Key::OEM_1, {";", ":", ";", ";"}}, + {olc::Key::OEM_2, {"/", "?", "/", "/"}}, + {olc::Key::OEM_3, {"\'","\"", "\'", "\'"}}, + {olc::Key::OEM_4, {"[", "{", "[", "["}}, + {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, + {olc::Key::OEM_6, {"]", "}", "]", "]"}}, + {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + + // Give these keys glyphs so they can be interpreted in text editing + {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, + {olc::Key::BACK, {"\b", "\b", "\b", "\b"}}, + {olc::Key::DEL, {"_X", "_X", "_X", "_X"}}, + {olc::Key::LEFT, {"_L", "_L", "_L", "_L"}}, + {olc::Key::RIGHT, {"_R", "_R", "_R", "_R"}}, + {olc::Key::UP, {"_U", "_U", "_U", "_U"}}, + {olc::Key::DOWN, {"_D", "_D", "_D", "_D"}}, + }; + } + } + + const Button& Keyboard::GetKey(const olc::Key key) const + { + return keys.at(size_t(key)); + } + + const std::vector& Keyboard::GetKeyCache() const + { + return keyCache[nKeyCacheIndex ^ 0x01]; // Return the inactive cache + } + + const std::string Keyboard::GetKeyGlyph(const olc::Key key, const bool shift, const bool ctrl, const bool alt) const + { + if (mapKeyGlyphs.contains(key)) + { + if(shift) + return mapKeyGlyphs.at(key).modShift; + else if(ctrl) + return mapKeyGlyphs.at(key).modCtrl; + else if(alt) + return mapKeyGlyphs.at(key).modAlt; + else + return mapKeyGlyphs.at(key).modNone; + } + + return ""; + } + + void Keyboard::SetKey(const olc::Key key, bool state) + { + keys_new[size_t(key)] = state; + + // Update key cache + if (state) + { + keyCache[nKeyCacheIndex].push_back(key); + } + } + + + void olc::hw::Keyboard::UpdateState() + { + for (size_t i = 0; i < keys.size(); i++) + { + keys[i].bPressed = false; + keys[i].bReleased = false; + if (keys_new[i] != keys_old[i]) + { + if (keys_new[i]) + { + keys[i].bPressed = !keys[i].bHeld; + keys[i].bHeld = true; + } + else + { + keys[i].bReleased = true; + keys[i].bHeld = false; + } + } + keys_old[i] = keys_new[i]; + } + + // Clear the inactive cache + keyCache[nKeyCacheIndex ^ 0x01].clear(); + + // Swap active cache index + nKeyCacheIndex ^= 0x01; + } +} +//! END IMPLEMENTATION diff --git a/dev/src/hw_keyboard.h b/dev/src/hw_keyboard.h index 6f70f09b..a44343d0 100644 --- a/dev/src/hw_keyboard.h +++ b/dev/src/hw_keyboard.h @@ -1 +1,122 @@ #pragma once + +//! START STDHEADER +#include +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "config.h" +#include "hw_input.h" +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_HW_KEYBOARD_DECLARED) +namespace olc +{ + // Forward declare for friendship + class Window; + class PGEWindow; + + enum class KeyboardLayout : uint8_t + { + QWERTY_UK, + QWERTY_US, + QWERTZ, + AZERTY + }; + + // Officially recognised OLC key codes + enum class Key : uint8_t + { + NONE, + + // Alphanumeric keys + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + + // Numeric keys + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + + // Numpad keys + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD, + + // Function keys + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + + // Arrow keys + UP, DOWN, LEFT, RIGHT, + + // Other keys + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, CAPS_LOCK, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, EQUALS, COMMA, MINUS, + + // OEM specific keys + OEM_1, OEM_2, OEM_3, OEM_4, OEM_5, OEM_6, OEM_7, OEM_8, + + // Special case for iteration limits + ENUM_END + }; + + + namespace hw + { + class Keyboard + { + friend class olc::Window; + friend class olc::PGEWindow; + + public: + Keyboard() = default; + + public: + // Get the state of a specific key + const Button& GetKey(const olc::Key nOLCKeyCode) const; + // Get a list of keys that have been pressed/typed since last update + const std::vector& GetKeyCache() const; + // Get printable glyph for a key based on current modifier state + const std::string GetKeyGlyph(const olc::Key key, const bool shift = false, const bool ctrl = false, const bool alt = false) const; + // Converts System keycode to olc::Key + olc::Key SystemKeyCodeToOLCKey(const int32_t nSystemKeyCode) const; + // Set keyboard layout for glyph mapping + void UseKeyboardLayout(const olc::KeyboardLayout kbl); + + private: + // Set key state from system keycode + void SetKey(const olc::Key key, bool state); + // Update internal state, called once per frame + void UpdateState(); + + protected: + std::array keys{}; + std::array keys_new{}; + std::array keys_old{}; + + private: + // We use two buffers to cache key presses so we can + // read from one while writing to the other, we dont + // want to miss any key presses that are typed, or + // autotyped by the OS repeat rate or peripheral + std::array, 2> keyCache{}; + size_t nKeyCacheIndex = 0; + + // Map of olc::Keycodes to printable glyphs + struct sKeyGlyph + { + std::string modNone; + std::string modShift; + std::string modCtrl; + std::string modAlt; + }; + std::unordered_map mapKeyGlyphs; + + + }; + } +} +#define PGE_HW_KEYBOARD_DECLARED 1 +#endif diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index edc872db..08e901c4 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -136,6 +136,8 @@ //! GRAB hw_mouse.h DECLARATION +//! GRAB hw_keyboard.h DECLARATION + //! GRAB window.h DECLARATION //! GRAB host_iface.h DECLARATION @@ -274,6 +276,11 @@ #define PGE_HW_MOUSE_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_KEYBOARD_IMPLEMENTED) +//! GRAB hw_keyboard.cpp IMPLEMENTATION +#define PGE_HW_KEYBOARD_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 aa26e3db..2145b829 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -10,6 +10,7 @@ namespace olc Window::Window() { nUniqueID = pgeguts::CreateUID(); + } Window::~Window() @@ -19,6 +20,7 @@ namespace olc void Window::LinkToHost(olc::host::Host* host) { pHost = host; + keyboard.UseKeyboardLayout(pHost->GetKeyboardLayout()); sFrameTitle = "OneLoneCoder.com - Pixel Game Engine 3"; pHost->UpdateWindowFrameTitle(this); } @@ -65,6 +67,12 @@ namespace olc return true; } + bool Window::olc_OnKeyPress(const olc::Key key, const bool bPressed) + { + keyboard.SetKey(key, bPressed); + return true; + } + bool Window::olc_ShouldRemove() const { return bShouldRemove; diff --git a/dev/src/window.h b/dev/src/window.h index 130d4ede..2bcf2121 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -7,7 +7,6 @@ #include #include #include - //! END STDHEADER //! START CUSTOMHEADER GLOBAL @@ -16,6 +15,7 @@ #include "vector2d.h" #include "draw2d.h" #include "hw_mouse.h" +#include "hw_keyboard.h" //! END CUSTOMHEADER //! START DECLARATION @@ -62,6 +62,7 @@ namespace olc namespace hw { class Mouse; + class Keyboard; } class Window @@ -90,6 +91,7 @@ namespace olc virtual bool olc_OnWindowClose(); // Set Keyboard State + virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); @@ -124,6 +126,7 @@ namespace olc protected: olc::hw::Mouse mouse; + olc::hw::Keyboard keyboard; }; } diff --git a/examples/olcPGE3_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp new file mode 100644 index 00000000..e3499100 --- /dev/null +++ b/examples/olcPGE3_Keyboard.cpp @@ -0,0 +1,114 @@ +/* + olc::PixelGameEngine3 Example - Keybaord (sic :P) + + Demonstrates using the keyboard input system + + 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 keyboard input. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_Keyboard : public olc::PixelGameEngine +{ +public: + Example_Keyboard() + { + + } + +protected: + + olc::vf2d vPosition; + std::string sText; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + vPosition = ScreenSize() / 2; + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // Move position with arrow keys + if (keyboard.GetKey(olc::Key::UP).bHeld) + vPosition.y -= 50.0f * fElapsedTime; + + if (keyboard.GetKey(olc::Key::DOWN).bHeld) + vPosition.y += 50.0f * fElapsedTime; + + if (keyboard.GetKey(olc::Key::LEFT).bHeld) + vPosition.x -= 50.0f * fElapsedTime; + + if (keyboard.GetKey(olc::Key::RIGHT).bHeld) + vPosition.x += 50.0f * fElapsedTime; + + draw.FilledCircle(vPosition.round(), 10.0f); + + + // Capture text input from keyboard + // Note: this is specifically for text entry and shouldnt + // be used for key polling. + for (const auto& key : keyboard.GetKeyCache()) + { + // Each olc::Key has a glyph associated with it + // depending on the current keyboard layout, which + // can be queried via GetKeyGlyph() + if (key == olc::Key::BACK && !sText.empty()) + sText.pop_back(); + else + sText += keyboard.GetKeyGlyph(key, keyboard.GetKey(olc::Key::SHIFT).bHeld); + } + + // Display current keyboard layout + std::string sKeyboardType; + auto kbl = pHost->GetKeyboardLayout(); + if(kbl == olc::KeyboardLayout::QWERTY_UK) + sKeyboardType = "QWERTY UK"; + else if (kbl == olc::KeyboardLayout::QWERTY_US) + sKeyboardType = "QWERTY US"; + else if (kbl == olc::KeyboardLayout::AZERTY) + sKeyboardType = "AZERTY"; + else if (kbl == olc::KeyboardLayout::QWERTZ) + sKeyboardType = "QWERTZ"; + else + sKeyboardType = "UNKNOWN"; + + draw.StringProp({ 10, 5 }, "Keyboard Layout: " + sKeyboardType, olc::Colour::YELLOW); + draw.StringProp({ 10,20 }, sText, olc::Colour::GREEN); + + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Keyboard 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 ba750da7..e1153be0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -116,7 +116,6 @@ #include #include #include - #include #include #include @@ -243,10 +242,12 @@ #define OLC_MOUSE_BUTTONS 5 +#define OLC_DEFAULT_KEYBOARD_LAYOUT olc::KeyboardLayout::QWERTY_UK + #define OLC_GPU_MAX_VERTICES 8192 #define OLC_GPU_ERRORCHECK 0 #define OLC_MSAA_SAMPLES 4 -#define OLC_DEFAULT_CIRCLE_FACETS 32 +#define OLC_DEFAULT_CIRCLE_FACETS 16 #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " @@ -2803,6 +2804,112 @@ namespace olc #define PGE_HW_MOUSE_DECLARED 1 #endif +#if !defined(PGE_HW_KEYBOARD_DECLARED) +namespace olc +{ + // Forward declare for friendship + class Window; + class PGEWindow; + + enum class KeyboardLayout : uint8_t + { + QWERTY_UK, + QWERTY_US, + QWERTZ, + AZERTY + }; + + // Officially recognised OLC key codes + enum class Key : uint8_t + { + NONE, + + // Alphanumeric keys + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + + // Numeric keys + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + + // Numpad keys + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD, + + // Function keys + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + + // Arrow keys + UP, DOWN, LEFT, RIGHT, + + // Other keys + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, CAPS_LOCK, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, EQUALS, COMMA, MINUS, + + // OEM specific keys + OEM_1, OEM_2, OEM_3, OEM_4, OEM_5, OEM_6, OEM_7, OEM_8, + + // Special case for iteration limits + ENUM_END + }; + + + namespace hw + { + class Keyboard + { + friend class olc::Window; + friend class olc::PGEWindow; + + public: + Keyboard() = default; + + public: + // Get the state of a specific key + const Button& GetKey(const olc::Key nOLCKeyCode) const; + // Get a list of keys that have been pressed/typed since last update + const std::vector& GetKeyCache() const; + // Get printable glyph for a key based on current modifier state + const std::string GetKeyGlyph(const olc::Key key, const bool shift = false, const bool ctrl = false, const bool alt = false) const; + // Converts System keycode to olc::Key + olc::Key SystemKeyCodeToOLCKey(const int32_t nSystemKeyCode) const; + // Set keyboard layout for glyph mapping + void UseKeyboardLayout(const olc::KeyboardLayout kbl); + + private: + // Set key state from system keycode + void SetKey(const olc::Key key, bool state); + // Update internal state, called once per frame + void UpdateState(); + + protected: + std::array keys{}; + std::array keys_new{}; + std::array keys_old{}; + + private: + // We use two buffers to cache key presses so we can + // read from one while writing to the other, we dont + // want to miss any key presses that are typed, or + // autotyped by the OS repeat rate or peripheral + std::array, 2> keyCache{}; + size_t nKeyCacheIndex = 0; + + // Map of olc::Keycodes to printable glyphs + struct sKeyGlyph + { + std::string modNone; + std::string modShift; + std::string modCtrl; + std::string modAlt; + }; + std::unordered_map mapKeyGlyphs; + + + }; + } +} +#define PGE_HW_KEYBOARD_DECLARED 1 +#endif + #if !defined(PGE_WINDOW_DECLARED) #if OLC_HOST == OLC_HOST_WINDOWS @@ -2846,6 +2953,7 @@ namespace olc namespace hw { class Mouse; + class Keyboard; } class Window @@ -2874,6 +2982,7 @@ namespace olc virtual bool olc_OnWindowClose(); // Set Keyboard State + virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); @@ -2908,6 +3017,7 @@ namespace olc protected: olc::hw::Mouse mouse; + olc::hw::Keyboard keyboard; }; } @@ -2994,6 +3104,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; + protected: HostError lastError = HostError::None; }; @@ -3076,6 +3188,7 @@ namespace olc // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); + olc::hw::Keyboard& GetKeyboard(); // Returns the current size of the "screen" in pixels const olc::vi2d& ScreenSize(); @@ -3217,7 +3330,7 @@ namespace olc public: - Host_Windows_WinAPI() = default; + Host_Windows_WinAPI(); virtual ~Host_Windows_WinAPI() {}; @@ -3236,6 +3349,8 @@ namespace olc LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + olc::KeyboardLayout GetKeyboardLayout() const override; + std::string test; private: @@ -3243,6 +3358,9 @@ namespace olc std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + }; } } @@ -5227,31 +5345,9 @@ namespace olc::host // Forward Declaration static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - // Windows app needs an event loop somewhere. This is blocking of course. This loop handles - // all windows created for this host. - bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) - { - if (bBlockIfPossible) - { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - else - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - return true; - } + + // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get // access to the actual host instance, which can more conveninetly process @@ -5297,6 +5393,147 @@ namespace olc::host return w; } + Host_Windows_WinAPI::Host_Windows_WinAPI() + { + // Map Windows Defined VK_ Codes to olc::KeyCodes + mapKeys[0x00] = Key::NONE; + + // Windows doesn't define A-Z + mapKeys[0x41] = Key::A; + mapKeys[0x42] = Key::B; + mapKeys[0x43] = Key::C; + mapKeys[0x44] = Key::D; + mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; + mapKeys[0x47] = Key::G; + mapKeys[0x48] = Key::H; + mapKeys[0x49] = Key::I; + mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; + mapKeys[0x4C] = Key::L; + mapKeys[0x4D] = Key::M; + mapKeys[0x4E] = Key::N; + mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; + mapKeys[0x51] = Key::Q; + mapKeys[0x52] = Key::R; + mapKeys[0x53] = Key::S; + mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; + mapKeys[0x56] = Key::V; + mapKeys[0x57] = Key::W; + mapKeys[0x58] = Key::X; + mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + // Windows doesnt define numeric keys + mapKeys[0x30] = Key::K0; + mapKeys[0x31] = Key::K1; + mapKeys[0x32] = Key::K2; + mapKeys[0x33] = Key::K3; + mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; + mapKeys[0x36] = Key::K6; + mapKeys[0x37] = Key::K7; + mapKeys[0x38] = Key::K8; + mapKeys[0x39] = Key::K9; + + // Function Keys + mapKeys[VK_F1] = Key::F1; + mapKeys[VK_F2] = Key::F2; + mapKeys[VK_F3] = Key::F3; + mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; + mapKeys[VK_F6] = Key::F6; + mapKeys[VK_F7] = Key::F7; + mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; + mapKeys[VK_F10] = Key::F10; + mapKeys[VK_F11] = Key::F11; + mapKeys[VK_F12] = Key::F12; + + // Arrow Keys + mapKeys[VK_DOWN] = Key::DOWN; + mapKeys[VK_LEFT] = Key::LEFT; + mapKeys[VK_RIGHT] = Key::RIGHT; + mapKeys[VK_UP] = Key::UP; + + // Other Keys + mapKeys[VK_BACK] = Key::BACK; + mapKeys[VK_ESCAPE] = Key::ESCAPE; + mapKeys[VK_RETURN] = Key::ENTER; + mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; + mapKeys[VK_TAB] = Key::TAB; + mapKeys[VK_DELETE] = Key::DEL; + mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; + mapKeys[VK_PRIOR] = Key::PGUP; + mapKeys[VK_NEXT] = Key::PGDN; + mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; + mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + + // Numpad + mapKeys[VK_NUMPAD0] = Key::NP0; + mapKeys[VK_NUMPAD1] = Key::NP1; + mapKeys[VK_NUMPAD2] = Key::NP2; + mapKeys[VK_NUMPAD3] = Key::NP3; + mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; + mapKeys[VK_NUMPAD6] = Key::NP6; + mapKeys[VK_NUMPAD7] = Key::NP7; + mapKeys[VK_NUMPAD8] = Key::NP8; + mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; + mapKeys[VK_ADD] = Key::NP_ADD; + mapKeys[VK_DIVIDE] = Key::NP_DIV; + mapKeys[VK_SUBTRACT] = Key::NP_SUB; + mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + // OEM Keys + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + } + + // Windows app needs an event loop somewhere. This is blocking of course. This loop handles + // all windows created for this host. + bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) + { + if (bBlockIfPossible) + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else + { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return true; + } + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { olc_IgnoreUnused(bFullScreen); @@ -5462,6 +5699,25 @@ namespace olc::host // case WM_KEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; // case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(int32_t(wParam), true); return 0; // case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; + + case WM_KEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } + + case WM_KEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } + case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); @@ -5546,6 +5802,23 @@ namespace olc::host return DefWindowProc(hWnd, uMsg, wParam, lParam); } + olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const + { + HKL kbl = ::GetKeyboardLayout(0); + size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; + + if (highWord == 0x00000409) // US + return olc::KeyboardLayout::QWERTY_US; + else if(highWord == 0x00000809) // UK + return olc::KeyboardLayout::QWERTY_UK; + else if(highWord == 0x00000407) // DE + return olc::KeyboardLayout::QWERTZ; + else if(highWord == 0x0000040C) // FR + return olc::KeyboardLayout::AZERTY; + + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } + }; #endif #if OLC_HOST == OLC_HOST_MACOS @@ -12740,6 +13013,7 @@ namespace olc { // Input Changes mouse.UpdateState(); + keyboard.UpdateState(); draw.SetGPU(pRenderer); draw.SetTarget(GetDefaultImage()); @@ -12914,6 +13188,11 @@ namespace olc return mouse; } + olc::hw::Keyboard& PGEWindow::GetKeyboard() + { + return keyboard; + } + const olc::vi2d& PGEWindow::ScreenSize() { return GetDefaultImage().Size(); @@ -13508,12 +13787,265 @@ namespace olc::hw #define PGE_HW_MOUSE_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_KEYBOARD_IMPLEMENTED) +namespace olc::hw +{ + + void Keyboard::UseKeyboardLayout(const olc::KeyboardLayout kbl) + { + mapKeyGlyphs.clear(); + + // Define appropriate glyphs for each key for the selected keyboard layout + if (kbl == olc::KeyboardLayout::QWERTY_UK) + { + mapKeyGlyphs = + { + // PGE Key, no mods, shift mod, ctrl mod, alt mod + {olc::Key::A, {"a", "A", "a", "a"}}, + {olc::Key::B, {"b", "B", "b", "b"}}, + {olc::Key::C, {"c", "C", "c", "c"}}, + {olc::Key::D, {"d", "D", "d", "d"}}, + {olc::Key::E, {"e", "E", "e", "e"}}, + {olc::Key::F, {"f", "F", "f", "f"}}, + {olc::Key::G, {"g", "G", "g", "g"}}, + {olc::Key::H, {"h", "H", "h", "h"}}, + {olc::Key::I, {"i", "I", "i", "i"}}, + {olc::Key::J, {"j", "J", "j", "j"}}, + {olc::Key::K, {"k", "K", "k", "k"}}, + {olc::Key::L, {"l", "L", "l", "l"}}, + {olc::Key::M, {"m", "M", "m", "m"}}, + {olc::Key::N, {"n", "N", "n", "n"}}, + {olc::Key::O, {"o", "O", "o", "o"}}, + {olc::Key::P, {"p", "P", "p", "p"}}, + {olc::Key::Q, {"q", "Q", "q", "q"}}, + {olc::Key::R, {"r", "R", "r", "r"}}, + {olc::Key::S, {"s", "S", "s", "s"}}, + {olc::Key::T, {"t", "T", "t", "t"}}, + {olc::Key::U, {"u", "U", "u", "u"}}, + {olc::Key::V, {"v", "V", "v", "v"}}, + {olc::Key::W, {"w", "W", "w", "w"}}, + {olc::Key::X, {"x", "X", "x", "x"}}, + {olc::Key::Y, {"y", "Y", "y", "y"}}, + {olc::Key::Z, {"z", "Z", "z", "z"}}, + + {olc::Key::K0, {"0", ")", "0", "0"}}, + {olc::Key::K1, {"1", "!", "1", "1"}}, + {olc::Key::K2, {"2", "\"","2", "2"}}, + {olc::Key::K3, {"3", "#", "3", "3"}}, + {olc::Key::K4, {"4", "$", "4", "4"}}, + {olc::Key::K5, {"5", "%", "5", "5"}}, + {olc::Key::K6, {"6", "^", "6", "6"}}, + {olc::Key::K7, {"7", "&", "7", "7"}}, + {olc::Key::K8, {"8", "*", "8", "8"}}, + {olc::Key::K9, {"9", "(", "9", "9"}}, + + {olc::Key::NP0, {"0", "0", "0", "0"}}, + {olc::Key::NP1, {"1", "1", "1", "1"}}, + {olc::Key::NP2, {"2", "2", "2", "2"}}, + {olc::Key::NP3, {"3", "3", "3", "3"}}, + {olc::Key::NP4, {"4", "4", "4", "4"}}, + {olc::Key::NP5, {"5", "5", "5", "5"}}, + {olc::Key::NP6, {"6", "6", "6", "6"}}, + {olc::Key::NP7, {"7", "7", "7", "7"}}, + {olc::Key::NP8, {"8", "8", "8", "8"}}, + {olc::Key::NP9, {"9", "9", "9", "9"}}, + {olc::Key::NP_MUL, {"*", "*", "*", "*"}}, + {olc::Key::NP_DIV, {"/", "/", "/", "/"}}, + {olc::Key::NP_ADD, {"+", "+", "+", "+"}}, + {olc::Key::NP_SUB, {"-", "-", "-", "-"}}, + {olc::Key::NP_DECIMAL, {".", ".", ".", "."}}, + + {olc::Key::PERIOD, {".", ">", ".", "."}}, + {olc::Key::EQUALS, {"=", "+", "=", "="}}, + {olc::Key::COMMA, {",", "<", ",", ","}}, + {olc::Key::MINUS, {"-", "_", "-", "-"}}, + {olc::Key::SPACE, {" ", " ", " ", " "}}, + {olc::Key::ENTER, {"\n", "\n ", "\n", "\n"}}, + + {olc::Key::OEM_1, {";", ":", ";", ";"}}, + {olc::Key::OEM_2, {"/", "?", "/", "/"}}, + {olc::Key::OEM_3, {"\'","@", "\'", "\'"}}, + {olc::Key::OEM_4, {"[", "{", "[", "["}}, + {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, + {olc::Key::OEM_6, {"]", "}", "]", "]"}}, + {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + + // Give these keys glyphs so they can be interpreted in text editing + {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, + {olc::Key::BACK, {"\b", "\b", "\b", "\b"}}, + {olc::Key::DEL, {"_X", "_X", "_X", "_X"}}, + {olc::Key::LEFT, {"_L", "_L", "_L", "_L"}}, + {olc::Key::RIGHT, {"_R", "_R", "_R", "_R"}}, + {olc::Key::UP, {"_U", "_U", "_U", "_U"}}, + {olc::Key::DOWN, {"_D", "_D", "_D", "_D"}}, + }; + } + + // Define appropriate glyphs for each key for the selected keyboard layout + if (kbl == olc::KeyboardLayout::QWERTY_US) + { + mapKeyGlyphs = + { + // PGE Key, no mods, shift mod, ctrl mod, alt mod + {olc::Key::A, {"a", "A", "a", "a"}}, + {olc::Key::B, {"b", "B", "b", "b"}}, + {olc::Key::C, {"c", "C", "c", "c"}}, + {olc::Key::D, {"d", "D", "d", "d"}}, + {olc::Key::E, {"e", "E", "e", "e"}}, + {olc::Key::F, {"f", "F", "f", "f"}}, + {olc::Key::G, {"g", "G", "g", "g"}}, + {olc::Key::H, {"h", "H", "h", "h"}}, + {olc::Key::I, {"i", "I", "i", "i"}}, + {olc::Key::J, {"j", "J", "j", "j"}}, + {olc::Key::K, {"k", "K", "k", "k"}}, + {olc::Key::L, {"l", "L", "l", "l"}}, + {olc::Key::M, {"m", "M", "m", "m"}}, + {olc::Key::N, {"n", "N", "n", "n"}}, + {olc::Key::O, {"o", "O", "o", "o"}}, + {olc::Key::P, {"p", "P", "p", "p"}}, + {olc::Key::Q, {"q", "Q", "q", "q"}}, + {olc::Key::R, {"r", "R", "r", "r"}}, + {olc::Key::S, {"s", "S", "s", "s"}}, + {olc::Key::T, {"t", "T", "t", "t"}}, + {olc::Key::U, {"u", "U", "u", "u"}}, + {olc::Key::V, {"v", "V", "v", "v"}}, + {olc::Key::W, {"w", "W", "w", "w"}}, + {olc::Key::X, {"x", "X", "x", "x"}}, + {olc::Key::Y, {"y", "Y", "y", "y"}}, + {olc::Key::Z, {"z", "Z", "z", "z"}}, + + {olc::Key::K0, {"0", ")", "0", "0"}}, + {olc::Key::K1, {"1", "!", "1", "1"}}, + {olc::Key::K2, {"2", "@","2", "2"}}, + {olc::Key::K3, {"3", "#", "3", "3"}}, + {olc::Key::K4, {"4", "$", "4", "4"}}, + {olc::Key::K5, {"5", "%", "5", "5"}}, + {olc::Key::K6, {"6", "^", "6", "6"}}, + {olc::Key::K7, {"7", "&", "7", "7"}}, + {olc::Key::K8, {"8", "*", "8", "8"}}, + {olc::Key::K9, {"9", "(", "9", "9"}}, + + {olc::Key::NP0, {"0", "0", "0", "0"}}, + {olc::Key::NP1, {"1", "1", "1", "1"}}, + {olc::Key::NP2, {"2", "2", "2", "2"}}, + {olc::Key::NP3, {"3", "3", "3", "3"}}, + {olc::Key::NP4, {"4", "4", "4", "4"}}, + {olc::Key::NP5, {"5", "5", "5", "5"}}, + {olc::Key::NP6, {"6", "6", "6", "6"}}, + {olc::Key::NP7, {"7", "7", "7", "7"}}, + {olc::Key::NP8, {"8", "8", "8", "8"}}, + {olc::Key::NP9, {"9", "9", "9", "9"}}, + {olc::Key::NP_MUL, {"*", "*", "*", "*"}}, + {olc::Key::NP_DIV, {"/", "/", "/", "/"}}, + {olc::Key::NP_ADD, {"+", "+", "+", "+"}}, + {olc::Key::NP_SUB, {"-", "-", "-", "-"}}, + {olc::Key::NP_DECIMAL, {".", ".", ".", "."}}, + + {olc::Key::PERIOD, {".", ">", ".", "."}}, + {olc::Key::EQUALS, {"=", "+", "=", "="}}, + {olc::Key::COMMA, {",", "<", ",", ","}}, + {olc::Key::MINUS, {"-", "_", "-", "-"}}, + {olc::Key::SPACE, {" ", " ", " ", " "}}, + {olc::Key::ENTER, {"\n", "\n ", "\n", "\n"}}, + + {olc::Key::OEM_1, {";", ":", ";", ";"}}, + {olc::Key::OEM_2, {"/", "?", "/", "/"}}, + {olc::Key::OEM_3, {"\'","\"", "\'", "\'"}}, + {olc::Key::OEM_4, {"[", "{", "[", "["}}, + {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, + {olc::Key::OEM_6, {"]", "}", "]", "]"}}, + {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + + // Give these keys glyphs so they can be interpreted in text editing + {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, + {olc::Key::BACK, {"\b", "\b", "\b", "\b"}}, + {olc::Key::DEL, {"_X", "_X", "_X", "_X"}}, + {olc::Key::LEFT, {"_L", "_L", "_L", "_L"}}, + {olc::Key::RIGHT, {"_R", "_R", "_R", "_R"}}, + {olc::Key::UP, {"_U", "_U", "_U", "_U"}}, + {olc::Key::DOWN, {"_D", "_D", "_D", "_D"}}, + }; + } + } + + const Button& Keyboard::GetKey(const olc::Key key) const + { + return keys.at(size_t(key)); + } + + const std::vector& Keyboard::GetKeyCache() const + { + return keyCache[nKeyCacheIndex ^ 0x01]; // Return the inactive cache + } + + const std::string Keyboard::GetKeyGlyph(const olc::Key key, const bool shift, const bool ctrl, const bool alt) const + { + if (mapKeyGlyphs.contains(key)) + { + if(shift) + return mapKeyGlyphs.at(key).modShift; + else if(ctrl) + return mapKeyGlyphs.at(key).modCtrl; + else if(alt) + return mapKeyGlyphs.at(key).modAlt; + else + return mapKeyGlyphs.at(key).modNone; + } + + return ""; + } + + void Keyboard::SetKey(const olc::Key key, bool state) + { + keys_new[size_t(key)] = state; + + // Update key cache + if (state) + { + keyCache[nKeyCacheIndex].push_back(key); + } + } + + + void olc::hw::Keyboard::UpdateState() + { + for (size_t i = 0; i < keys.size(); i++) + { + keys[i].bPressed = false; + keys[i].bReleased = false; + if (keys_new[i] != keys_old[i]) + { + if (keys_new[i]) + { + keys[i].bPressed = !keys[i].bHeld; + keys[i].bHeld = true; + } + else + { + keys[i].bReleased = true; + keys[i].bHeld = false; + } + } + keys_old[i] = keys_new[i]; + } + + // Clear the inactive cache + keyCache[nKeyCacheIndex ^ 0x01].clear(); + + // Swap active cache index + nKeyCacheIndex ^= 0x01; + } +} +#define PGE_HW_KEYBOARD_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) namespace olc { Window::Window() { nUniqueID = pgeguts::CreateUID(); + } Window::~Window() @@ -13523,6 +14055,7 @@ namespace olc void Window::LinkToHost(olc::host::Host* host) { pHost = host; + keyboard.UseKeyboardLayout(pHost->GetKeyboardLayout()); sFrameTitle = "OneLoneCoder.com - Pixel Game Engine 3"; pHost->UpdateWindowFrameTitle(this); } @@ -13569,6 +14102,12 @@ namespace olc return true; } + bool Window::olc_OnKeyPress(const olc::Key key, const bool bPressed) + { + keyboard.SetKey(key, bPressed); + return true; + } + bool Window::olc_ShouldRemove() const { return bShouldRemove; From 9a197a69404ee876695a4c4a31d17740faa6b385 Mon Sep 17 00:00:00 2001 From: dandistine <> Date: Tue, 27 Jan 2026 20:49:12 -0600 Subject: [PATCH 14/56] Implement the keyboard for X11 --- dev/src/host_lin_x11.cpp | 111 +++++++++++++++++++++++++++++------- dev/src/host_lin_x11.h | 5 ++ dev/src/hw_keyboard.cpp | 4 +- olcPixelGameEngine3.h | 120 ++++++++++++++++++++++++++++++++------- 4 files changed, 196 insertions(+), 44 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 0ed649ec..79e6b1d9 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -9,6 +9,64 @@ namespace olc::host XInitThreads(); olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + + mapKeys[NoSymbol] = Key::NONE; + + int keyTracker = static_cast(Key::A); + uint32_t uppercase = static_cast(XK_A); + uint32_t lowercase = static_cast(XK_a); + + for (; uppercase <= static_cast(XK_Z); ++uppercase, ++lowercase) + { + mapKeys[uppercase] = (Key)keyTracker; + mapKeys[lowercase] = (Key)keyTracker; + ++keyTracker; + } + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + // These map the keypad when NUMLOCK is off + mapKeys[XK_KP_Home] = Key::HOME; mapKeys[XK_KP_End] = Key::END; mapKeys[XK_KP_Up] = Key::UP; + mapKeys[XK_KP_Down] = Key::DOWN; mapKeys[XK_KP_Left] = Key::LEFT; mapKeys[XK_KP_Right] = Key::RIGHT; + mapKeys[XK_KP_Page_Up] = Key::PGUP; mapKeys[XK_KP_Page_Down] = Key::PGDN; mapKeys[XK_KP_Insert] = Key::INS; + mapKeys[XK_KP_Delete] = Key::DEL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XK_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XK_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XK_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XK_grave] = Key::OEM_3; // On US keyboard this is the '`' key + mapKeys[XK_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XK_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XK_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XK_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XK_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XK_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XK_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; } bool Host_Linux_X11::StartSystemEventLoop(bool bBlockIfPossible) @@ -49,24 +107,36 @@ namespace olc::host pge_window->olc_OnWindowSize(olc::vi2d{xce.width, xce.height}); } } - // else if (xev.type == KeyPress) - // { - // KeySym ks; + else if (xev.type == KeyPress) + { + KeySym ks; - // // DragonEye still loves numpads, but this is a better way - // XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + // Unset the "shift" bit so that Key and Shift-Key will be mapped to the same olc::key + // since the system kind of assumes this + xev.xkey.state &= ~(1); - // if (ks != NoSymbol) - // ptrPGE->olc_UpdateKeyState(ks, true); - // } - // else if (xev.type == KeyRelease) - // { - // KeySym ks; - // XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, true); + } + } + } + else if (xev.type == KeyRelease) + { + KeySym ks; - // if (ks != NoSymbol) - // ptrPGE->olc_UpdateKeyState(ks, false); - // } + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, false); + } + } + } else if (xev.type == ButtonPress) { if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { @@ -126,11 +196,6 @@ namespace olc::host bool Host_Linux_X11::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { - // The user created olc::Window object is the SSoT for what a window - // should look like, so get that sort of thing from there - olc::vi2d vWinPos = vWindowPos; - olc::vi2d vWinSize = vWindowSize; - // Based on the display capabilities, configure the appearance of the window // to do this namespacing, both x11 and glx have to be included in the x11 namespace GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; @@ -198,6 +263,12 @@ namespace olc::host return true; } + olc::KeyboardLayout Host_Linux_X11::GetKeyboardLayout() const { + // The objectively correct keyboard layout + return olc::KeyboardLayout::QWERTY_US; + } + + // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Linux_X11::SyncWithDesktopComposite() { diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 55d6d530..da753b04 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -17,6 +17,7 @@ namespace X11 { #include #include +#include #include } @@ -42,6 +43,8 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; + + olc::KeyboardLayout GetKeyboardLayout() const override; // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -49,6 +52,8 @@ namespace olc::host std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; std::atomic terminate {false}; + + std::unordered_map mapKeys; }; } diff --git a/dev/src/hw_keyboard.cpp b/dev/src/hw_keyboard.cpp index f6a11dc6..1c01291f 100644 --- a/dev/src/hw_keyboard.cpp +++ b/dev/src/hw_keyboard.cpp @@ -169,11 +169,11 @@ namespace olc::hw {olc::Key::OEM_1, {";", ":", ";", ";"}}, {olc::Key::OEM_2, {"/", "?", "/", "/"}}, - {olc::Key::OEM_3, {"\'","\"", "\'", "\'"}}, + {olc::Key::OEM_3, {"`","~", "`", "`"}}, {olc::Key::OEM_4, {"[", "{", "[", "["}}, {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, {olc::Key::OEM_6, {"]", "}", "]", "]"}}, - {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + {olc::Key::OEM_7, {"'", "\"", "'", "'"}}, // Give these keys glyphs so they can be interpreted in text editing {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e1153be0..d2930f6a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4552,6 +4552,7 @@ namespace X11 { #include #include +#include #include } @@ -4577,6 +4578,8 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; + + olc::KeyboardLayout GetKeyboardLayout() const override; // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -4584,6 +4587,8 @@ namespace olc::host std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; std::atomic terminate {false}; + + std::unordered_map mapKeys; }; } @@ -8419,6 +8424,64 @@ namespace olc::host XInitThreads(); olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + + mapKeys[NoSymbol] = Key::NONE; + + int keyTracker = static_cast(Key::A); + uint32_t uppercase = static_cast(XK_A); + uint32_t lowercase = static_cast(XK_a); + + for (; uppercase <= static_cast(XK_Z); ++uppercase, ++lowercase) + { + mapKeys[uppercase] = (Key)keyTracker; + mapKeys[lowercase] = (Key)keyTracker; + ++keyTracker; + } + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + // These map the keypad when NUMLOCK is off + mapKeys[XK_KP_Home] = Key::HOME; mapKeys[XK_KP_End] = Key::END; mapKeys[XK_KP_Up] = Key::UP; + mapKeys[XK_KP_Down] = Key::DOWN; mapKeys[XK_KP_Left] = Key::LEFT; mapKeys[XK_KP_Right] = Key::RIGHT; + mapKeys[XK_KP_Page_Up] = Key::PGUP; mapKeys[XK_KP_Page_Down] = Key::PGDN; mapKeys[XK_KP_Insert] = Key::INS; + mapKeys[XK_KP_Delete] = Key::DEL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XK_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XK_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XK_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XK_grave] = Key::OEM_3; // On US keyboard this is the '`' key + mapKeys[XK_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XK_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XK_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XK_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XK_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XK_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XK_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; } bool Host_Linux_X11::StartSystemEventLoop(bool bBlockIfPossible) @@ -8459,24 +8522,36 @@ namespace olc::host pge_window->olc_OnWindowSize(olc::vi2d{xce.width, xce.height}); } } - // else if (xev.type == KeyPress) - // { - // KeySym ks; + else if (xev.type == KeyPress) + { + KeySym ks; - // // DragonEye still loves numpads, but this is a better way - // XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + // Unset the "shift" bit so that Key and Shift-Key will be mapped to the same olc::key + // since the system kind of assumes this + xev.xkey.state &= ~(1); - // if (ks != NoSymbol) - // ptrPGE->olc_UpdateKeyState(ks, true); - // } - // else if (xev.type == KeyRelease) - // { - // KeySym ks; - // XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, true); + } + } + } + else if (xev.type == KeyRelease) + { + KeySym ks; - // if (ks != NoSymbol) - // ptrPGE->olc_UpdateKeyState(ks, false); - // } + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, false); + } + } + } else if (xev.type == ButtonPress) { if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { @@ -8536,11 +8611,6 @@ namespace olc::host bool Host_Linux_X11::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { - // The user created olc::Window object is the SSoT for what a window - // should look like, so get that sort of thing from there - olc::vi2d vWinPos = vWindowPos; - olc::vi2d vWinSize = vWindowSize; - // Based on the display capabilities, configure the appearance of the window // to do this namespacing, both x11 and glx have to be included in the x11 namespace GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; @@ -8608,6 +8678,12 @@ namespace olc::host return true; } + olc::KeyboardLayout Host_Linux_X11::GetKeyboardLayout() const { + // The objectively correct keyboard layout + return olc::KeyboardLayout::QWERTY_US; + } + + // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Linux_X11::SyncWithDesktopComposite() { @@ -13950,11 +14026,11 @@ namespace olc::hw {olc::Key::OEM_1, {";", ":", ";", ";"}}, {olc::Key::OEM_2, {"/", "?", "/", "/"}}, - {olc::Key::OEM_3, {"\'","\"", "\'", "\'"}}, + {olc::Key::OEM_3, {"`","~", "`", "`"}}, {olc::Key::OEM_4, {"[", "{", "[", "["}}, {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, {olc::Key::OEM_6, {"]", "}", "]", "]"}}, - {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + {olc::Key::OEM_7, {"'", "\"", "'", "'"}}, // Give these keys glyphs so they can be interpreted in text editing {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, From 7e7fdb3e05bb54584ddae73682c548e5033fe9bc Mon Sep 17 00:00:00 2001 From: dandistine <> Date: Tue, 27 Jan 2026 22:40:56 -0600 Subject: [PATCH 15/56] Implement wayland keyboard --- dev/src/host_lin_wayland.cpp | 179 ++++++++++++++++++++++++++++++ dev/src/host_lin_wayland.h | 24 ++++ dev/src/hw_keyboard.cpp | 4 +- examples/compile_wayland.sh | 8 +- olcPixelGameEngine3.h | 207 ++++++++++++++++++++++++++++++++++- 5 files changed, 417 insertions(+), 5 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 2bfd9260..afbed611 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -28,6 +28,15 @@ namespace olc::host .axis_value120 = Host_Linux_Wayland::pointer_axis_value120_callback, .axis_relative_direction = Host_Linux_Wayland::pointer_axis_relative_direction_callback }; + + static const wl_keyboard_listener keyboard_listener { + .keymap = Host_Linux_Wayland::keyboard_keymap_callback, + .enter = Host_Linux_Wayland::keyboard_enter_callback, + .leave = Host_Linux_Wayland::keyboard_leave_callback, + .key = Host_Linux_Wayland::keyboard_key_callback, + .modifiers = Host_Linux_Wayland::keyboard_modifiers_callback, + .repeat_info = Host_Linux_Wayland::keyboard_repeat_info_callback + }; } namespace xdg { @@ -66,6 +75,64 @@ namespace olc::host xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); wl_seat_add_listener(seat, &wayland::seat_listener, this); + + kb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + // Setup the keymap with XKB codes, which are basically the same as the X11 codes + mapKeys[XKB_KEY_NoSymbol] = Key::NONE; + + int keyTracker = static_cast(Key::A); + uint32_t uppercase = static_cast(XKB_KEY_A); + uint32_t lowercase = static_cast(XKB_KEY_a); + + for (; uppercase <= static_cast(XKB_KEY_Z); ++uppercase, ++lowercase) + { + mapKeys[uppercase] = (Key)keyTracker; + mapKeys[lowercase] = (Key)keyTracker; + ++keyTracker; + } + + mapKeys[XKB_KEY_F1] = Key::F1; mapKeys[XKB_KEY_F2] = Key::F2; mapKeys[XKB_KEY_F3] = Key::F3; mapKeys[XKB_KEY_F4] = Key::F4; + mapKeys[XKB_KEY_F5] = Key::F5; mapKeys[XKB_KEY_F6] = Key::F6; mapKeys[XKB_KEY_F7] = Key::F7; mapKeys[XKB_KEY_F8] = Key::F8; + mapKeys[XKB_KEY_F9] = Key::F9; mapKeys[XKB_KEY_F10] = Key::F10; mapKeys[XKB_KEY_F11] = Key::F11; mapKeys[XKB_KEY_F12] = Key::F12; + + mapKeys[XKB_KEY_Down] = Key::DOWN; mapKeys[XKB_KEY_Left] = Key::LEFT; mapKeys[XKB_KEY_Right] = Key::RIGHT; mapKeys[XKB_KEY_Up] = Key::UP; + mapKeys[XKB_KEY_KP_Enter] = Key::ENTER; mapKeys[XKB_KEY_Return] = Key::ENTER; + + mapKeys[XKB_KEY_BackSpace] = Key::BACK; mapKeys[XKB_KEY_Escape] = Key::ESCAPE; mapKeys[XKB_KEY_Linefeed] = Key::ENTER; mapKeys[XKB_KEY_Pause] = Key::PAUSE; + mapKeys[XKB_KEY_Scroll_Lock] = Key::SCROLL; mapKeys[XKB_KEY_Tab] = Key::TAB; mapKeys[XKB_KEY_Delete] = Key::DEL; mapKeys[XKB_KEY_Home] = Key::HOME; + mapKeys[XKB_KEY_End] = Key::END; mapKeys[XKB_KEY_Page_Up] = Key::PGUP; mapKeys[XKB_KEY_Page_Down] = Key::PGDN; mapKeys[XKB_KEY_Insert] = Key::INS; + mapKeys[XKB_KEY_Shift_L] = Key::SHIFT; mapKeys[XKB_KEY_Shift_R] = Key::SHIFT; mapKeys[XKB_KEY_Control_L] = Key::CTRL; mapKeys[XKB_KEY_Control_R] = Key::CTRL; + mapKeys[XKB_KEY_space] = Key::SPACE; mapKeys[XKB_KEY_period] = Key::PERIOD; + + mapKeys[XKB_KEY_0] = Key::K0; mapKeys[XKB_KEY_1] = Key::K1; mapKeys[XKB_KEY_2] = Key::K2; mapKeys[XKB_KEY_3] = Key::K3; mapKeys[XKB_KEY_4] = Key::K4; + mapKeys[XKB_KEY_5] = Key::K5; mapKeys[XKB_KEY_6] = Key::K6; mapKeys[XKB_KEY_7] = Key::K7; mapKeys[XKB_KEY_8] = Key::K8; mapKeys[XKB_KEY_9] = Key::K9; + + mapKeys[XKB_KEY_KP_0] = Key::NP0; mapKeys[XKB_KEY_KP_1] = Key::NP1; mapKeys[XKB_KEY_KP_2] = Key::NP2; mapKeys[XKB_KEY_KP_3] = Key::NP3; mapKeys[XKB_KEY_KP_4] = Key::NP4; + mapKeys[XKB_KEY_KP_5] = Key::NP5; mapKeys[XKB_KEY_KP_6] = Key::NP6; mapKeys[XKB_KEY_KP_7] = Key::NP7; mapKeys[XKB_KEY_KP_8] = Key::NP8; mapKeys[XKB_KEY_KP_9] = Key::NP9; + mapKeys[XKB_KEY_KP_Multiply] = Key::NP_MUL; mapKeys[XKB_KEY_KP_Add] = Key::NP_ADD; mapKeys[XKB_KEY_KP_Divide] = Key::NP_DIV; mapKeys[XKB_KEY_KP_Subtract] = Key::NP_SUB; mapKeys[XKB_KEY_KP_Decimal] = Key::NP_DECIMAL; + + // These map the keypad when NUMLOCK is off + mapKeys[XKB_KEY_KP_Home] = Key::HOME; mapKeys[XKB_KEY_KP_End] = Key::END; mapKeys[XKB_KEY_KP_Up] = Key::UP; + mapKeys[XKB_KEY_KP_Down] = Key::DOWN; mapKeys[XKB_KEY_KP_Left] = Key::LEFT; mapKeys[XKB_KEY_KP_Right] = Key::RIGHT; + mapKeys[XKB_KEY_KP_Page_Up] = Key::PGUP; mapKeys[XKB_KEY_KP_Page_Down] = Key::PGDN; mapKeys[XKB_KEY_KP_Insert] = Key::INS; + mapKeys[XKB_KEY_KP_Delete] = Key::DEL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XKB_KEY_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XKB_KEY_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XKB_KEY_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XKB_KEY_grave] = Key::OEM_3; // On US keyboard this is the '`' key + mapKeys[XKB_KEY_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XKB_KEY_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XKB_KEY_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XKB_KEY_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XKB_KEY_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XKB_KEY_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XKB_KEY_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XKB_KEY_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XKB_KEY_Caps_Lock] = Key::CAPS_LOCK; } Host_Linux_Wayland::~Host_Linux_Wayland() @@ -78,6 +145,9 @@ namespace olc::host wl_surface_destroy(wayland_window.surface); } + xkb_state_unref(kb_state); + xkb_context_unref(kb_context); + wl_display_disconnect(display); } @@ -150,6 +220,11 @@ namespace olc::host return true; } + olc::KeyboardLayout Host_Linux_Wayland::GetKeyboardLayout() const + { + return olc::KeyboardLayout::QWERTY_US; + } + void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { if(std::strcmp(interface, wl_compositor_interface.name) == 0) { @@ -164,6 +239,9 @@ namespace olc::host 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)); } + if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { + keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); + } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -177,6 +255,11 @@ namespace olc::host pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } + + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { + keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); + } } void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) @@ -412,6 +495,102 @@ namespace olc::host //host->pointer_axis_relative_direction(pointer, axis, direction); } + // Keyboard Callbacks + void Host_Linux_Wayland::keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size) + { + auto* host = reinterpret_cast(data); + host->keyboard_keymap(keyboard, format, fd, size); + } + + void Host_Linux_Wayland::keyboard_keymap(wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size) + { + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + char* keymap_string = static_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); + if (keymap_string == MAP_FAILED) { + close(fd); + return; + } + + xkb_keymap* keymap = xkb_keymap_new_from_string(kb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(keymap_string, size); + close(fd); + + if (!keymap) { + return; + } + + // Unreference the previous state if it exists and we got a new keymap from the server + xkb_state_unref(kb_state); + kb_state = xkb_state_new(keymap); + xkb_keymap_unref(keymap); + } + + void Host_Linux_Wayland::keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) + { + auto* host = reinterpret_cast(data); + host->keyboard_enter(keyboard, serial, surface, keys); + } + + void Host_Linux_Wayland::keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) + { + // Currently do nothing + } + + void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) + { + auto* host = reinterpret_cast(data); + host->keyboard_leave(keyboard, serial, surface); + } + + void Host_Linux_Wayland::keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) + { + // Currently do nothing + } + + void Host_Linux_Wayland::keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) + { + auto* host = reinterpret_cast(data); + host->keyboard_key(keyboard, serial, time, key, state); + } + + void Host_Linux_Wayland::keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) + { + const auto sym = xkb_state_key_get_one_sym(kb_state, key + 8); // XKB keys are offset by 8 + auto itr = mapKeys.find(sym); + if(itr != mapKeys.end()) { + auto olc_key = itr->second; + auto* pge_window = mapUID2OlcWindow[active_window_id]; + pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); + } + } + + void Host_Linux_Wayland::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) + { + auto* host = reinterpret_cast(data); + host->keyboard_modifiers(keyboard, serial, mods_depressed, mods_latched, mods_locked, group); + } + + void Host_Linux_Wayland::keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) + { + xkb_state_update_mask(kb_state, + mods_depressed & ~(1), // Just like X11, ignore the shift key + mods_latched, + mods_locked, + 0, + 0, + group); + } + + void Host_Linux_Wayland::keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay) + { + // auto* host = reinterpret_cast(data); + // host->keyboard_repeat_info(keyboard, rate, delay); + } + // 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 f3df087f..f50b62f9 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -18,6 +18,9 @@ #include "xdg-shell.h" #include "xdg-decoration.h" #include +#include +#include +#include #include #include @@ -76,6 +79,9 @@ namespace olc::host wl_compositor* compositor{nullptr}; wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; + wl_keyboard* keyboard{nullptr}; + xkb_context* kb_context{nullptr}; + xkb_state* kb_state{nullptr}; xdg_wm_base* xdg_wm{nullptr}; zxdg_decoration_manager_v1* decoration_manager{nullptr}; @@ -97,6 +103,8 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; + olc::KeyboardLayout GetKeyboardLayout() const override; + // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -120,6 +128,14 @@ namespace olc::host static void pointer_axis_value120_callback(void* data, wl_pointer* pointer, uint32_t axis, int32_t value120); static void pointer_axis_relative_direction_callback(void* data, wl_pointer* pointer, uint32_t axis, uint32_t direction); + // Keyboard callbacks + static void keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); + static void keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); + static void keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); + static void keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); + 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); + // 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); @@ -147,6 +163,13 @@ namespace olc::host void pointer_axis_value120(wl_pointer* pointer, uint32_t axis, int32_t value120); void pointer_axis_relative_direction(wl_pointer* pointer, uint32_t axis, uint32_t direction); + // Keyboard Callback Functions + void keyboard_keymap(wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); + void keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); + void keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); + 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); @@ -157,6 +180,7 @@ namespace olc::host std::unordered_map mapUID2Window; std::unordered_map mapUID2OlcWindow; std::atomic terminate {false}; + std::unordered_map mapKeys; }; } diff --git a/dev/src/hw_keyboard.cpp b/dev/src/hw_keyboard.cpp index f6a11dc6..1c01291f 100644 --- a/dev/src/hw_keyboard.cpp +++ b/dev/src/hw_keyboard.cpp @@ -169,11 +169,11 @@ namespace olc::hw {olc::Key::OEM_1, {";", ":", ";", ";"}}, {olc::Key::OEM_2, {"/", "?", "/", "/"}}, - {olc::Key::OEM_3, {"\'","\"", "\'", "\'"}}, + {olc::Key::OEM_3, {"`","~", "`", "`"}}, {olc::Key::OEM_4, {"[", "{", "[", "["}}, {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, {olc::Key::OEM_6, {"]", "}", "]", "]"}}, - {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + {olc::Key::OEM_7, {"'", "\"", "'", "'"}}, // Give these keys glyphs so they can be interpreted in text editing {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, diff --git a/examples/compile_wayland.sh b/examples/compile_wayland.sh index 70636e82..681c0766 100755 --- a/examples/compile_wayland.sh +++ b/examples/compile_wayland.sh @@ -15,6 +15,12 @@ then exit 1 fi +if [ $(pkg-config --exists xkbcommon; echo $?) -eq 1 ] +then + echo "You may need to install libxkbcommon-dev via your package manager" + exit 1 +fi + # May need to install wayland-protocols to get the xdg-decoration to build wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell.c wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell.h @@ -24,4 +30,4 @@ wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decorati gcc -c xdg-shell.c gcc -c xdg-decoration.c -g++ -ggdb -fmax-errors=5 -std=c++20 -DOLC_HOST=3 -I. -lpng -lGL -lwayland-client -lwayland-egl -lEGL -Wall -lpthread $1.cpp xdg-shell.o xdg-decoration.o -o $1 +g++ -ggdb -fmax-errors=5 -std=c++20 -DOLC_HOST=3 -I. -o $1 $1.cpp $(pkg-config --libs xkbcommon) -lpng -lGL -lwayland-client -lwayland-egl -lEGL -Wall -lpthread xdg-shell.o xdg-decoration.o diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e1153be0..5c51cfba 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4596,6 +4596,9 @@ namespace olc::host #include "xdg-shell.h" #include "xdg-decoration.h" #include +#include +#include +#include #include #include @@ -4654,6 +4657,9 @@ namespace olc::host wl_compositor* compositor{nullptr}; wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; + wl_keyboard* keyboard{nullptr}; + xkb_context* kb_context{nullptr}; + xkb_state* kb_state{nullptr}; xdg_wm_base* xdg_wm{nullptr}; zxdg_decoration_manager_v1* decoration_manager{nullptr}; @@ -4675,6 +4681,8 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; + olc::KeyboardLayout GetKeyboardLayout() const override; + // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -4698,6 +4706,14 @@ namespace olc::host static void pointer_axis_value120_callback(void* data, wl_pointer* pointer, uint32_t axis, int32_t value120); static void pointer_axis_relative_direction_callback(void* data, wl_pointer* pointer, uint32_t axis, uint32_t direction); + // Keyboard callbacks + static void keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); + static void keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); + static void keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); + static void keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); + 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); + // 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); @@ -4725,6 +4741,13 @@ namespace olc::host void pointer_axis_value120(wl_pointer* pointer, uint32_t axis, int32_t value120); void pointer_axis_relative_direction(wl_pointer* pointer, uint32_t axis, uint32_t direction); + // Keyboard Callback Functions + void keyboard_keymap(wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); + void keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); + void keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); + 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); @@ -4735,6 +4758,7 @@ namespace olc::host std::unordered_map mapUID2Window; std::unordered_map mapUID2OlcWindow; std::atomic terminate {false}; + std::unordered_map mapKeys; }; } @@ -8643,6 +8667,15 @@ namespace olc::host .axis_value120 = Host_Linux_Wayland::pointer_axis_value120_callback, .axis_relative_direction = Host_Linux_Wayland::pointer_axis_relative_direction_callback }; + + static const wl_keyboard_listener keyboard_listener { + .keymap = Host_Linux_Wayland::keyboard_keymap_callback, + .enter = Host_Linux_Wayland::keyboard_enter_callback, + .leave = Host_Linux_Wayland::keyboard_leave_callback, + .key = Host_Linux_Wayland::keyboard_key_callback, + .modifiers = Host_Linux_Wayland::keyboard_modifiers_callback, + .repeat_info = Host_Linux_Wayland::keyboard_repeat_info_callback + }; } namespace xdg { @@ -8681,6 +8714,64 @@ namespace olc::host xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); wl_seat_add_listener(seat, &wayland::seat_listener, this); + + kb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + // Setup the keymap with XKB codes, which are basically the same as the X11 codes + mapKeys[XKB_KEY_NoSymbol] = Key::NONE; + + int keyTracker = static_cast(Key::A); + uint32_t uppercase = static_cast(XKB_KEY_A); + uint32_t lowercase = static_cast(XKB_KEY_a); + + for (; uppercase <= static_cast(XKB_KEY_Z); ++uppercase, ++lowercase) + { + mapKeys[uppercase] = (Key)keyTracker; + mapKeys[lowercase] = (Key)keyTracker; + ++keyTracker; + } + + mapKeys[XKB_KEY_F1] = Key::F1; mapKeys[XKB_KEY_F2] = Key::F2; mapKeys[XKB_KEY_F3] = Key::F3; mapKeys[XKB_KEY_F4] = Key::F4; + mapKeys[XKB_KEY_F5] = Key::F5; mapKeys[XKB_KEY_F6] = Key::F6; mapKeys[XKB_KEY_F7] = Key::F7; mapKeys[XKB_KEY_F8] = Key::F8; + mapKeys[XKB_KEY_F9] = Key::F9; mapKeys[XKB_KEY_F10] = Key::F10; mapKeys[XKB_KEY_F11] = Key::F11; mapKeys[XKB_KEY_F12] = Key::F12; + + mapKeys[XKB_KEY_Down] = Key::DOWN; mapKeys[XKB_KEY_Left] = Key::LEFT; mapKeys[XKB_KEY_Right] = Key::RIGHT; mapKeys[XKB_KEY_Up] = Key::UP; + mapKeys[XKB_KEY_KP_Enter] = Key::ENTER; mapKeys[XKB_KEY_Return] = Key::ENTER; + + mapKeys[XKB_KEY_BackSpace] = Key::BACK; mapKeys[XKB_KEY_Escape] = Key::ESCAPE; mapKeys[XKB_KEY_Linefeed] = Key::ENTER; mapKeys[XKB_KEY_Pause] = Key::PAUSE; + mapKeys[XKB_KEY_Scroll_Lock] = Key::SCROLL; mapKeys[XKB_KEY_Tab] = Key::TAB; mapKeys[XKB_KEY_Delete] = Key::DEL; mapKeys[XKB_KEY_Home] = Key::HOME; + mapKeys[XKB_KEY_End] = Key::END; mapKeys[XKB_KEY_Page_Up] = Key::PGUP; mapKeys[XKB_KEY_Page_Down] = Key::PGDN; mapKeys[XKB_KEY_Insert] = Key::INS; + mapKeys[XKB_KEY_Shift_L] = Key::SHIFT; mapKeys[XKB_KEY_Shift_R] = Key::SHIFT; mapKeys[XKB_KEY_Control_L] = Key::CTRL; mapKeys[XKB_KEY_Control_R] = Key::CTRL; + mapKeys[XKB_KEY_space] = Key::SPACE; mapKeys[XKB_KEY_period] = Key::PERIOD; + + mapKeys[XKB_KEY_0] = Key::K0; mapKeys[XKB_KEY_1] = Key::K1; mapKeys[XKB_KEY_2] = Key::K2; mapKeys[XKB_KEY_3] = Key::K3; mapKeys[XKB_KEY_4] = Key::K4; + mapKeys[XKB_KEY_5] = Key::K5; mapKeys[XKB_KEY_6] = Key::K6; mapKeys[XKB_KEY_7] = Key::K7; mapKeys[XKB_KEY_8] = Key::K8; mapKeys[XKB_KEY_9] = Key::K9; + + mapKeys[XKB_KEY_KP_0] = Key::NP0; mapKeys[XKB_KEY_KP_1] = Key::NP1; mapKeys[XKB_KEY_KP_2] = Key::NP2; mapKeys[XKB_KEY_KP_3] = Key::NP3; mapKeys[XKB_KEY_KP_4] = Key::NP4; + mapKeys[XKB_KEY_KP_5] = Key::NP5; mapKeys[XKB_KEY_KP_6] = Key::NP6; mapKeys[XKB_KEY_KP_7] = Key::NP7; mapKeys[XKB_KEY_KP_8] = Key::NP8; mapKeys[XKB_KEY_KP_9] = Key::NP9; + mapKeys[XKB_KEY_KP_Multiply] = Key::NP_MUL; mapKeys[XKB_KEY_KP_Add] = Key::NP_ADD; mapKeys[XKB_KEY_KP_Divide] = Key::NP_DIV; mapKeys[XKB_KEY_KP_Subtract] = Key::NP_SUB; mapKeys[XKB_KEY_KP_Decimal] = Key::NP_DECIMAL; + + // These map the keypad when NUMLOCK is off + mapKeys[XKB_KEY_KP_Home] = Key::HOME; mapKeys[XKB_KEY_KP_End] = Key::END; mapKeys[XKB_KEY_KP_Up] = Key::UP; + mapKeys[XKB_KEY_KP_Down] = Key::DOWN; mapKeys[XKB_KEY_KP_Left] = Key::LEFT; mapKeys[XKB_KEY_KP_Right] = Key::RIGHT; + mapKeys[XKB_KEY_KP_Page_Up] = Key::PGUP; mapKeys[XKB_KEY_KP_Page_Down] = Key::PGDN; mapKeys[XKB_KEY_KP_Insert] = Key::INS; + mapKeys[XKB_KEY_KP_Delete] = Key::DEL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XKB_KEY_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XKB_KEY_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XKB_KEY_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XKB_KEY_grave] = Key::OEM_3; // On US keyboard this is the '`' key + mapKeys[XKB_KEY_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XKB_KEY_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XKB_KEY_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XKB_KEY_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XKB_KEY_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XKB_KEY_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XKB_KEY_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XKB_KEY_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XKB_KEY_Caps_Lock] = Key::CAPS_LOCK; } Host_Linux_Wayland::~Host_Linux_Wayland() @@ -8693,6 +8784,9 @@ namespace olc::host wl_surface_destroy(wayland_window.surface); } + xkb_state_unref(kb_state); + xkb_context_unref(kb_context); + wl_display_disconnect(display); } @@ -8765,6 +8859,11 @@ namespace olc::host return true; } + olc::KeyboardLayout Host_Linux_Wayland::GetKeyboardLayout() const + { + return olc::KeyboardLayout::QWERTY_US; + } + void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { if(std::strcmp(interface, wl_compositor_interface.name) == 0) { @@ -8779,6 +8878,9 @@ namespace olc::host 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)); } + if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { + keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); + } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -8792,6 +8894,11 @@ namespace olc::host pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } + + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { + keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); + } } void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) @@ -9027,6 +9134,102 @@ namespace olc::host //host->pointer_axis_relative_direction(pointer, axis, direction); } + // Keyboard Callbacks + void Host_Linux_Wayland::keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size) + { + auto* host = reinterpret_cast(data); + host->keyboard_keymap(keyboard, format, fd, size); + } + + void Host_Linux_Wayland::keyboard_keymap(wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size) + { + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + char* keymap_string = static_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); + if (keymap_string == MAP_FAILED) { + close(fd); + return; + } + + xkb_keymap* keymap = xkb_keymap_new_from_string(kb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(keymap_string, size); + close(fd); + + if (!keymap) { + return; + } + + // Unreference the previous state if it exists and we got a new keymap from the server + xkb_state_unref(kb_state); + kb_state = xkb_state_new(keymap); + xkb_keymap_unref(keymap); + } + + void Host_Linux_Wayland::keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) + { + auto* host = reinterpret_cast(data); + host->keyboard_enter(keyboard, serial, surface, keys); + } + + void Host_Linux_Wayland::keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) + { + // Currently do nothing + } + + void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) + { + auto* host = reinterpret_cast(data); + host->keyboard_leave(keyboard, serial, surface); + } + + void Host_Linux_Wayland::keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) + { + // Currently do nothing + } + + void Host_Linux_Wayland::keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) + { + auto* host = reinterpret_cast(data); + host->keyboard_key(keyboard, serial, time, key, state); + } + + void Host_Linux_Wayland::keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) + { + const auto sym = xkb_state_key_get_one_sym(kb_state, key + 8); // XKB keys are offset by 8 + auto itr = mapKeys.find(sym); + if(itr != mapKeys.end()) { + auto olc_key = itr->second; + auto* pge_window = mapUID2OlcWindow[active_window_id]; + pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); + } + } + + void Host_Linux_Wayland::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) + { + auto* host = reinterpret_cast(data); + host->keyboard_modifiers(keyboard, serial, mods_depressed, mods_latched, mods_locked, group); + } + + void Host_Linux_Wayland::keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) + { + xkb_state_update_mask(kb_state, + mods_depressed & ~(1), // Just like X11, ignore the shift key + mods_latched, + mods_locked, + 0, + 0, + group); + } + + void Host_Linux_Wayland::keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay) + { + // auto* host = reinterpret_cast(data); + // host->keyboard_repeat_info(keyboard, rate, delay); + } + // 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); @@ -13950,11 +14153,11 @@ namespace olc::hw {olc::Key::OEM_1, {";", ":", ";", ";"}}, {olc::Key::OEM_2, {"/", "?", "/", "/"}}, - {olc::Key::OEM_3, {"\'","\"", "\'", "\'"}}, + {olc::Key::OEM_3, {"`","~", "`", "`"}}, {olc::Key::OEM_4, {"[", "{", "[", "["}}, {olc::Key::OEM_5, {"\\", "|", "\\", "\\"}}, {olc::Key::OEM_6, {"]", "}", "]", "]"}}, - {olc::Key::OEM_7, {"#", "~", "#", "#"}}, + {olc::Key::OEM_7, {"'", "\"", "'", "'"}}, // Give these keys glyphs so they can be interpreted in text editing {olc::Key::TAB, {"\t", "\t", "\t", "\t"}}, From a860615783bdc8ea63ba30244de7dc9804681884 Mon Sep 17 00:00:00 2001 From: dandistine <> Date: Tue, 27 Jan 2026 22:43:27 -0600 Subject: [PATCH 16/56] Remove accidentally duplicated keys --- dev/src/host_lin_x11.cpp | 3 --- olcPixelGameEngine3.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 79e6b1d9..22941c9a 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -39,9 +39,6 @@ namespace olc::host mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; - mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; - mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; - mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d2930f6a..8d04db8a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8454,9 +8454,6 @@ namespace olc::host mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; - mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; - mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; - mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; From 3e9aa0991915aae9e0e9636ff4525982ab2d5da8 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Wed, 28 Jan 2026 06:36:47 +0000 Subject: [PATCH 17/56] sneakily boosted keyboard example to display metrics --- dev/src/api_opengl.cpp | 11 + dev/src/api_opengl.h | 8 +- dev/src/config.h | 1 + dev/src/core.h | 2 +- dev/src/draw2d.cpp | 25 +- dev/src/draw2d.h | 16 + dev/src/gpu_opengl33.cpp | 20 +- examples/olcPGE3_Keyboard.cpp | 17 +- olcPixelGameEngine3.h | 538 ++++++++++------------------------ 9 files changed, 246 insertions(+), 392 deletions(-) diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index a2c64d8d..eb807bf8 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -66,6 +66,10 @@ namespace olc::apis::opengl bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; +#if OLC_HOST == OLC_HOST_WINDOWS + bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; +#endif + return bLoaded; } @@ -229,6 +233,13 @@ namespace olc::apis::opengl #endif } + void gl::glSwapInterval(GLsizei n) + { +#if OLC_HOST == OLC_HOST_WINDOWS + _wglSwapIntervalEXT(n); +#endif + } + GLuint gl::glCreateShader(GLenum type) { return _glCreateShader(type); diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 56df07ed..820f5ade 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -159,7 +159,7 @@ namespace olc typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); #if OLC_HOST == OLC_HOST_WINDOWS - typedef void CALLSTYLE glSwapInterval_t(GLsizei n); + typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); #endif // A little GL class (singleton) @@ -217,6 +217,9 @@ namespace olc glDeleteRenderbuffers_t* _glDeleteRenderbuffers = nullptr; glGetInternalformativ_t* _glGetInternalformativ = nullptr; glGetShaderiv_t* _glGetShaderiv = nullptr; +#if OLC_HOST == OLC_HOST_WINDOWS + wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; +#endif public: @@ -288,6 +291,9 @@ namespace olc void glHint(GLenum target, GLenum mode); void glPolygonMode(GLenum face, GLenum mode); + + void glSwapInterval(GLsizei n); + // Constants static constexpr GLenum GL_FRAMEBUFFER_COMPLETE_X = 0x8CD5; static constexpr GLenum GL_TEXTURE_2D_MULTISAMPLE_X = 0x9100; diff --git a/dev/src/config.h b/dev/src/config.h index 41eb893a..3072cbd8 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -114,6 +114,7 @@ #define OLC_GPU_ERRORCHECK 0 #define OLC_MSAA_SAMPLES 4 #define OLC_DEFAULT_CIRCLE_FACETS 16 +#define OLC_DEFAULT_VSYNC true #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " diff --git a/dev/src/core.h b/dev/src/core.h index dc1e4d61..e18a9be7 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -43,7 +43,7 @@ namespace olc // Allow the window to be resized by user bool bResizeable = true; // Synchronise rendering with monitor - bool bVSync = false; + bool bVSync = OLC_DEFAULT_VSYNC; // Behave like a host window, resizing the screen in response to window resize bool bRealWindow = false; // Ensure aspect ratio of "screen" is mainatined regardless of window size diff --git a/dev/src/draw2d.cpp b/dev/src/draw2d.cpp index 9622016a..9842c1bd 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw2d.cpp @@ -61,6 +61,8 @@ void olc::Draw2D::ProcessGPUTasks() for (const auto& task : vecGPUTasks.data) pRenderer->DoGPUTask(task); + drawMetrics.nGPUTasks += uint32_t(vecGPUTasks.data.size()); + vecGPUTasks.data.clear(); } @@ -77,6 +79,8 @@ void Draw2D::PrepareTargetForSW() // Image is now CPU bound pTarget->BindCPU(); + drawMetrics.nGPUtoCPUTransfers++; + // Create a scanline buffer the height of this target vScanlines.resize(size_t(pTarget->Size().y), {}); } @@ -91,6 +95,8 @@ void Draw2D::PrepareTargetForHW() // Image is now GPU bound pTarget->BindGPU(); + + drawMetrics.nCPUtoGPUTransfers++; } } @@ -106,6 +112,8 @@ void Draw2D::PrepareImageForSW(olc::Image& image) // Image is now CPU bound image.BindCPU(); + + drawMetrics.nGPUtoCPUTransfers++; } } @@ -118,6 +126,8 @@ void Draw2D::PrepareImageForHW(olc::Image& image) // Image is now GPU bound image.BindGPU(); + + drawMetrics.nCPUtoGPUTransfers++; } // Only resolve MSAA if this image is NOT the current render target @@ -132,6 +142,9 @@ bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) { // Finish all drawing with current shader ProcessGPUTasks(); + + drawMetrics.nShaderChanges++; + // Set new shader return pRenderer->ApplyShader(shader); } @@ -139,12 +152,12 @@ bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) bool olc::Draw2D::ResetShader() { ProcessGPUTasks(); + drawMetrics.nShaderChanges++; return pRenderer->ApplyDefaultShader(); } bool olc::Draw2D::SetShaderUniform(const std::string& name, const float value) { - return pRenderer->SetUniform(name, value); } @@ -164,6 +177,16 @@ bool olc::Draw2D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) return pRenderer->AssignTextureSource(nSlot, image.GetGPUID()); } +void olc::Draw2D::ResetDrawMetrics() +{ + drawMetrics = sDrawMetrics(); +} + +olc::Draw2D::sDrawMetrics olc::Draw2D::GetDrawMetrics() const +{ + return drawMetrics; +} + void olc::Draw2D::WorldReset() { transformAffine = olc::tf2d(); diff --git a/dev/src/draw2d.h b/dev/src/draw2d.h index 1a8079f2..285d747f 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw2d.h @@ -965,6 +965,22 @@ namespace olc // Assign an image to a texture slot for subsequent GPU drawing tasks bool SetShaderTexture(const uint32_t nSlot, olc::Image& image); + public: + struct sDrawMetrics + { + uint32_t nGPUTasks = 0; + uint32_t nGPUtoCPUTransfers = 0; + uint32_t nCPUtoGPUTransfers = 0; + uint32_t nShaderChanges = 0; + }; + + void ResetDrawMetrics(); + sDrawMetrics GetDrawMetrics() const; + + private: + sDrawMetrics drawMetrics; + + protected: diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 6544c4a7..458eb2b8 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -251,12 +251,6 @@ void main() lastError = RendererError::FailedToSwitchRenderContext; return false; } - - //// Set Vertical Sync - //glSwapInterval = OGL_LOAD(glSwapInterval); - //if (locSwapInterval && !bVSYNC) locSwapInterval(0); - //bSync = bVSYNC; - #endif #if OLC_HOST == OLC_HOST_LINUX_X11 @@ -326,6 +320,20 @@ void main() lastError = RendererError::NoError; return false; } + + // Configure Swap Interval (VSync) + if (config.VerticalSync) + { + // Enable VSync - lock to display refresh + // May also be governed by OS / driver settings + // and desktop compositor settings + gl.glSwapInterval(1); + } + else + { + // Disable VSync - run like the clappers! + gl.glSwapInterval(0); + } // Create "Default" Shader diff --git a/examples/olcPGE3_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp index e3499100..a3c70d0d 100644 --- a/examples/olcPGE3_Keyboard.cpp +++ b/examples/olcPGE3_Keyboard.cpp @@ -39,6 +39,9 @@ class Example_Keyboard : public olc::PixelGameEngine // Called every frame, so update things here bool OnUserUpdate(float fElapsedTime) override { + auto metrics = draw.GetDrawMetrics(); + draw.ResetDrawMetrics(); + // Clear whole screen draw.Clear(olc::Colour::VERY_DARK_BLUE); @@ -90,6 +93,13 @@ class Example_Keyboard : public olc::PixelGameEngine draw.StringProp({ 10,20 }, sText, olc::Colour::GREEN); + // Display frame time and draw metrics + draw.String({ 10, 200 }, + "GPU Tasks: " + std::to_string(metrics.nGPUTasks) + "\n" + + "CPU->GPU : " + std::to_string(metrics.nCPUtoGPUTransfers) + "\n" + + "GPU->CPU : " + std::to_string(metrics.nGPUtoCPUTransfers) + "\n" + + "Shaders : " + std::to_string(metrics.nShaderChanges)); + // Successful frame return true; } @@ -104,7 +114,12 @@ int main() // Create "screen" of 256x240 "pixels" // with a pixel size of 4x4 actual screen pixels - if (demo.Construct({ 256, 240 }, { 4, 4 })) + PGEConfig config; + config.bVSync = true; + config.vPixelSize = { 4,4 }; + config.vScreenSize = { 256,240 }; + + if (demo.Construct(config)) { // Start the application demo.Start(); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index f189d492..1c4c5c76 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -248,6 +248,7 @@ #define OLC_GPU_ERRORCHECK 0 #define OLC_MSAA_SAMPLES 4 #define OLC_DEFAULT_CIRCLE_FACETS 16 +#define OLC_DEFAULT_VSYNC true #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " @@ -2675,6 +2676,22 @@ namespace olc // Assign an image to a texture slot for subsequent GPU drawing tasks bool SetShaderTexture(const uint32_t nSlot, olc::Image& image); + public: + struct sDrawMetrics + { + uint32_t nGPUTasks = 0; + uint32_t nGPUtoCPUTransfers = 0; + uint32_t nCPUtoGPUTransfers = 0; + uint32_t nShaderChanges = 0; + }; + + void ResetDrawMetrics(); + sDrawMetrics GetDrawMetrics() const; + + private: + sDrawMetrics drawMetrics; + + protected: @@ -3133,7 +3150,7 @@ namespace olc // Allow the window to be resized by user bool bResizeable = true; // Synchronise rendering with monitor - bool bVSync = false; + bool bVSync = OLC_DEFAULT_VSYNC; // Behave like a host window, resizing the screen in response to window resize bool bRealWindow = false; // Ensure aspect ratio of "screen" is mainatined regardless of window size @@ -3376,7 +3393,6 @@ namespace olc extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) - // NSRect structure for window and view frames struct NSRect { double x{0.0}, y{0.0}; double width{800.0}, height{600.0}; @@ -3419,16 +3435,15 @@ extern "C" { void window_getContentViewFrame (const struct Window* self, double* x, double* y, double* width, double* height); void window_setContentViewFrame (struct Window* self, double* x, double* y, double* width, double* height); - // OpenGL Renderer API - as implemented in api_macos.c struct OpenGLRenderer* opengl_init (void); void opengl_initialize (struct OpenGLRenderer* self, struct Window* window); void opengl_setupContext (struct OpenGLRenderer* self); - void* opengl_getOpenGLContext (const struct OpenGLRenderer* self); // Returns id, cast to void* - void* opengl_getCGLContextObj (struct OpenGLRenderer* self); // Returns CGLContextObj, cast to void* - void* opengl_getCGLContextObjPtr (struct OpenGLRenderer* self); // Returns CGLContextObj as void* - void opengl_makeCurrentContext (struct OpenGLRenderer* self); // Make OpenGL context current - void opengl_setVsync (struct OpenGLRenderer* self, BOOL enabled); // Enable/disable vsync + void* opengl_getOpenGLContext (const struct OpenGLRenderer* self); // Returns id, cast to void* + void* opengl_getCGLContextObj (struct OpenGLRenderer* self); // Returns CGLContextObj, cast to void* + void* opengl_getCGLContextObjPtr (struct OpenGLRenderer* self); // Returns CGLContextObj as void* + void opengl_makeCurrentContext (struct OpenGLRenderer* self); // Make OpenGL context current + void opengl_setVsync (struct OpenGLRenderer* self, BOOL enabled); // Enable/disable vsync void opengl_destroy (struct OpenGLRenderer* self); bool opengl_resetContextForSize (struct OpenGLRenderer* self, double width, double height); @@ -3441,7 +3456,6 @@ extern "C" { void imageloader_getDetailedInfo (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha); BOOL imageloader_isLoaded (const struct ImageLoader* self); BOOL imageloader_getPixel (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha); - unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* self); // Autorelease pool management void* objc_autoreleasePoolPush (void); @@ -3450,14 +3464,9 @@ extern "C" { // OpenGL functions we might need void glDeleteTextures(int n, const unsigned int* textures); - // =========================================================================== - // EVENT HANDLING API - // =========================================================================== - // Event callback function types typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); - //typedef void (*MouseMoveEventCallback) (double x, double y, unsigned int modifierFlags, void* userData); // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); @@ -3478,10 +3487,6 @@ extern "C" { void window_enableEventHandling (struct Window* self); void window_disableEventHandling (struct Window* self); - // =========================================================================== - // APPLICATION DELEGATE EVENT HANDLING API - // =========================================================================== - // Application delegate callback function type typedef void (*ApplicationDelegateCallback)(void* userData); @@ -3491,10 +3496,6 @@ extern "C" { void application_setWillTerminateCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); void application_setDidBecomeActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); void application_setWillResignActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); - - // =========================================================================== - // WINDOW DELEGATE EVENT HANDLING API - // =========================================================================== // Window delegate callback function type typedef void (*WindowDelegateCallback)(void* userData); @@ -3534,57 +3535,6 @@ namespace olc { AutoreleasePool& operator=(AutoreleasePool&&) = delete; }; - // RAII wrapper for OpenGL texture management - class OpenGLTexture { - private: - unsigned int textureID_; - - public: - explicit constexpr OpenGLTexture(unsigned int id = 0) noexcept : textureID_(id) {} - - ~OpenGLTexture() noexcept { - if (textureID_ != 0) { - glDeleteTextures(1, &textureID_); - } - } - - constexpr unsigned int get() const noexcept { return textureID_; } - constexpr bool isValid() const noexcept { return textureID_ != 0; } - - // Release ownership - unsigned int release() noexcept { - unsigned int id = textureID_; - textureID_ = 0; - return id; - } - - // Reset with new texture ID - void reset(unsigned int id = 0) noexcept { - if (textureID_ != 0) { - glDeleteTextures(1, &textureID_); - } - textureID_ = id; - } - - // Move semantics - OpenGLTexture(OpenGLTexture&& other) noexcept : textureID_(other.textureID_) { - other.textureID_ = 0; - } - - OpenGLTexture& operator=(OpenGLTexture&& other) noexcept { - if (this != &other) { - reset(); - textureID_ = other.textureID_; - other.textureID_ = 0; - } - return *this; - } - - // Non-copyable - OpenGLTexture(const OpenGLTexture&) = delete; - OpenGLTexture& operator=(const OpenGLTexture&) = delete; - }; - // Exception class for framework errors class FrameworkException : public std::runtime_error { public: @@ -3643,8 +3593,7 @@ namespace olc { // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } - - + // Set callback for application will finish launching event void setWillFinishLaunchingCallback(std::function callback) { setCallback(application_setWillFinishLaunchingCallback, std::move(callback)); @@ -3732,7 +3681,6 @@ namespace olc { if (!window_) { throw FrameworkException("Failed to create window"); } - } ~Window() { @@ -3780,20 +3728,21 @@ namespace olc { return NSRect{0, 0, 0, 0}; } + // Set the window frame size void setFrameSize(int32_t width, int32_t height) noexcept { setFrameSize(static_cast(width), static_cast(height)); } + void setFrameSize(float width, float height) noexcept { setFrameSize(static_cast(width), static_cast(height)); } - // Set the window frame size + void setFrameSize(double width, double height) noexcept { if (window_) { double x = 0.0, y = 0.0; window_getWindowFrame(window_, &x, &y, nullptr, nullptr); window_setWindowFrame(window_, x, y, width, height); } - } void getFrameSize(int32_t& width, int32_t& height) const noexcept { @@ -3818,13 +3767,15 @@ namespace olc { } } + // Set the window position void setPosition(int32_t x, int32_t y) noexcept { setPosition(static_cast(x), static_cast(y)); } + void setPosition(float x, float y) noexcept { setPosition(static_cast(x), static_cast(y)); } - // Set the window position + void setPosition(double x, double y) noexcept { if (window_) { window_setWindowPosition(window_, x, y); @@ -3839,6 +3790,7 @@ namespace olc { } } + // Set the window size void setWindowSize(int32_t width, int32_t height) noexcept { setWindowSize(static_cast(width), static_cast(height)); } @@ -3846,7 +3798,7 @@ namespace olc { void setWindowSize(float width, float height) noexcept { setWindowSize(static_cast(width), static_cast(height)); } - // Set the window size + void setWindowSize(double width, double height) noexcept { if (window_) { window_setWindowSize(window_, width, height); @@ -3876,7 +3828,6 @@ namespace olc { } } - // Context view frame getters and setters void setContentViewPosition(int32_t x, int32_t y) noexcept { setContentViewPosition(static_cast(x), static_cast(y)); @@ -3952,8 +3903,6 @@ namespace olc { } } - - // Set callback for window resize events void setWindowDidResizeCallback(std::function callback) { setCallback(window_setWindowDidResizeCallback, std::move(callback)); @@ -4022,7 +3971,6 @@ namespace olc { ~OpenGLRenderer() { if (renderer_) { opengl_destroy(renderer_); - // Note: opengl_destroy already calls free() on the renderer } } @@ -4139,22 +4087,7 @@ namespace olc { bool isLoaded() const noexcept { return loaded_ && loader_ && imageloader_isLoaded(loader_); } - - OpenGLTexture createOpenGLTexture() const noexcept { - if (loader_ && loaded_) { - return OpenGLTexture(imageloader_createOpenGLTexture(loader_)); - } - return OpenGLTexture{}; - } - - // Legacy method for backwards compatibility - unsigned int createOpenGLTextureID() const noexcept { - if (loader_ && loaded_) { - return imageloader_createOpenGLTexture(loader_); - } - return 0; - } - + void getImageInfo(int& width, int& height, int& bytesPerPixel) const noexcept { if (loader_ && loaded_) { imageloader_getImageInfo(loader_, &width, &height, &bytesPerPixel); @@ -4190,7 +4123,6 @@ namespace olc { } }; - // Keyboard event data structure struct KeyEvent { unsigned short keyCode; @@ -4431,8 +4363,6 @@ namespace olc { EventHandler& operator=(EventHandler&&) = delete; }; - - } // namespace macos } // namespace apis } // namespace olc @@ -4446,24 +4376,19 @@ namespace olc { namespace host { - // Manages our MacOS Host class Host_Apple_MacOS : public olc::host::Host { public: - olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window - //GLint glSwapInterval = 0; // VSync disbaled by default public: Host_Apple_MacOS() = default; virtual ~Host_Apple_MacOS() {}; public: - // Check/Get last error HostError GetLastError() const { return lastError; } - public: virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) override; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; @@ -4472,7 +4397,6 @@ namespace olc virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - virtual bool ConnectHostResourceToRenderer() override; // Wait for entire host desktop refresh (for smooooth vsync), @@ -4482,7 +4406,6 @@ namespace olc HostError lastError = HostError::None; public: - // MacOS Application and Window pointers std::unique_ptr pMacApplication = nullptr; std::unique_ptr pMacOSWindow = nullptr; @@ -4492,9 +4415,7 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; - - private: - + private: enum MAINTASKS{ NONE, CREATE_OPENGL_RENDERER, @@ -4514,7 +4435,7 @@ namespace olc std::vector vMacOSWindowDescriptors; // Vector to hold window descriptors bool enableVSync = false; // VSync enabled flag - bool bSkipFrame = false; // Flag to indicate if frame should be skipped + bool bSkipFrame = false; // Flag to indicate if frame should be skipped // Thread synchronization for PGE Thread V Main thread mutable std::mutex mainThreadPendingTasksMutex; // Mutex for main thread pending tasks @@ -4533,7 +4454,6 @@ namespace olc double height = 600.0; } frameBounds; - // TODO: Should these be private? void MacApplicationEventsHandler(); void MacWindowEventsHandler(); void MacEventsHandler(); @@ -4972,7 +4892,7 @@ namespace olc typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); #if OLC_HOST == OLC_HOST_WINDOWS - typedef void CALLSTYLE glSwapInterval_t(GLsizei n); + typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); #endif // A little GL class (singleton) @@ -5030,6 +4950,9 @@ namespace olc glDeleteRenderbuffers_t* _glDeleteRenderbuffers = nullptr; glGetInternalformativ_t* _glGetInternalformativ = nullptr; glGetShaderiv_t* _glGetShaderiv = nullptr; +#if OLC_HOST == OLC_HOST_WINDOWS + wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; +#endif public: @@ -5101,6 +5024,9 @@ namespace olc void glHint(GLenum target, GLenum mode); void glPolygonMode(GLenum face, GLenum mode); + + void glSwapInterval(GLsizei n); + // Constants static constexpr GLenum GL_FRAMEBUFFER_COMPLETE_X = 0x8CD5; static constexpr GLenum GL_TEXTURE_2D_MULTISAMPLE_X = 0x9100; @@ -5853,10 +5779,8 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { - bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible) - { + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning - //TODO: Johnngy63 find out what bBlockIfPossible is supposed to do // Create MacOS Application instance pMacApplication = std::make_unique(); @@ -5892,10 +5816,7 @@ namespace olc::host { return true; } - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - // Update the PGE window with the actual window size given by MacOS - + bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ pPGEwindow = pWindow; pPGEwindow->SetWindowPosition(vWindowPos); pPGEwindow->SetWindowSize(vWindowSize); // Temporary small size to avoid large window on creation @@ -5910,17 +5831,14 @@ namespace olc::host { } - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow) - { - // TODO: Gracefully close the window and clean up resources + bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ if (!pMacOSWindow) return false; if (!pWindow) return false; pWindow->olc_OnWindowClose(); return true; } - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow) - { + bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ if (!pMacOSWindow) return false; dispatch_async(dispatch_get_main_queue(), ^{ pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); @@ -5928,9 +5846,7 @@ namespace olc::host { return true; } - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow) - { - + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ // While the PGE is running, if there are pending main thread tasks, process them, this causes PGE to wait bSkipFrame = ExecutePendingMainThreadTasks(); @@ -6091,7 +6007,6 @@ namespace olc::host { } vPendingMainThreadTasks.clear(); - // Release any locks on the PGE // 4. Main Thread unlocks PGE Thread { std::lock_guard lock(pgeThreadPendingTasksMutex); @@ -6099,49 +6014,34 @@ namespace olc::host { isMainThreadResetting = false; // Reset main thread flag } pgeThreadResetCondition.notify_all(); // Wake up PGE thread - return res; - } //------ Events Handlers ----- void Host_Apple_MacOS::MacApplicationEventsHandler() { - // Application event handling code here - // Set application delegate event handlers - pMacApplication->setWillFinishLaunchingCallback([]() { - //std::cout << "--> Application delegate: Will finish launching" << std::endl; - }); + pMacApplication->setWillFinishLaunchingCallback([]() { }); pMacApplication->setDidFinishLaunchingCallback([&]() { - //std::cout << "--> Application delegate: Did finish launching" << std::endl; // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); }); pMacApplication->setWillTerminateCallback([&]() { - //std::cout << "--> Application delegate: Will terminate" << std::endl; // TODO: Johnngy63 - Implement olc_OnDestory in window.h/cpp - }); - pMacApplication->setDidBecomeActiveCallback([]() { - //std::cout << "--> Application delegate: Did become active" << std::endl; - }); + pMacApplication->setDidBecomeActiveCallback([]() { }); - pMacApplication->setWillResignActiveCallback([]() { - //std::cout << "--> Application delegate: Will resign active" << std::endl; - }); + pMacApplication->setWillResignActiveCallback([]() { }); } void Host_Apple_MacOS::MacWindowEventsHandler() { - // Window event handling code here pMacOSWindow->setWindowDidResizeCallback([&]() { AddPendingMainThreadTask(RESIZE_WINDOW); - }); pMacOSWindow->setWindowWillCloseCallback([&]() { @@ -6157,16 +6057,13 @@ namespace olc::host { pMacOSWindow->setWindowDidResignKeyCallback([&]() { //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp - }); pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowMinimize in window.h/cpp if needed AddPendingMainThreadTask(MINIMIZE_WINDOW); }); pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp if needed AddPendingMainThreadTask(DEMINIMIZE_WINDOW); }); @@ -6271,7 +6168,6 @@ namespace olc::host { } - } @@ -6421,111 +6317,40 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; - namespace ObjectiveCSEL { // Application memory management selectors - static SEL allocSel = nullptr; - static SEL initSel = nullptr; - static SEL setDelegateSel = nullptr; - static SEL releaseSel = nullptr; - static SEL isKindOfClassSel = nullptr; + static SEL allocSel, initSel, setDelegateSel, releaseSel, isKindOfClassSel = nullptr; // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel = nullptr; - static SEL activateIgnoringOtherAppsSel = nullptr; - static SEL setActivationPolicySel = nullptr; - static SEL runSel = nullptr; + static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel = nullptr; // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel = nullptr; - static SEL applicationDidFinishLaunchingSel = nullptr; - static SEL applicationWillTerminateSel = nullptr; - static SEL applicationDidBecomeActiveSel = nullptr; - static SEL applicationWillResignActiveSel = nullptr; + static SEL applicationWillFinishLaunchingSel, applicationDidFinishLaunchingSel, applicationWillTerminateSel, applicationDidBecomeActiveSel, applicationWillResignActiveSel = nullptr; // NSWindow creation, display, and management selectors - static SEL initWithContentRectSel = nullptr; - static SEL stringWithUTF8StringSel = nullptr; - static SEL setTitleSel = nullptr; - static SEL orderFrontRegardlessSel = nullptr; - static SEL setAcceptsMouseMovedEventsSel = nullptr; - static SEL makeFirstResponderSel = nullptr; - static SEL makeKeyAndOrderFrontSel = nullptr; - static SEL makeKeyWindowSel = nullptr; - static SEL frameSel = nullptr; - static SEL setFrameDisplaySel = nullptr; - static SEL setFrameSel = nullptr; + static SEL initWithContentRectSel, stringWithUTF8StringSel, setTitleSel, orderFrontRegardlessSel, setAcceptsMouseMovedEventsSel, makeKeyAndOrderFrontSel = nullptr; + static SEL makeKeyWindowSel, frameSel, setFrameDisplaySel, setFrameSel, makeFirstResponderSel = nullptr; // NSWindowDelegate lifecycle and event methods selectors - static SEL windowDidResizeSel = nullptr; - static SEL windowWillCloseSel = nullptr; - static SEL windowDidBecomeKeySel = nullptr; - static SEL windowDidResignKeySel = nullptr; - static SEL windowDidMiniaturizeSel = nullptr; - static SEL windowDidDeminiaturizeSel = nullptr; + static SEL windowDidResizeSel, windowWillCloseSel, windowDidBecomeKeySel, windowDidResignKeySel, windowDidMiniaturizeSel, windowDidDeminiaturizeSel = nullptr; // NSResponder keyboard and mouse event methods selectors - static SEL keyDownSel = nullptr; - static SEL keyUpSel = nullptr; - static SEL mouseDownSel = nullptr; - static SEL mouseUpSel = nullptr; - static SEL mouseDraggedSel = nullptr; - static SEL mouseMovedSel = nullptr; - static SEL rightMouseDownSel = nullptr; - static SEL rightMouseUpSel = nullptr; - static SEL rightMouseDraggedSel = nullptr; - static SEL otherMouseDownSel = nullptr; - static SEL otherMouseUpSel = nullptr; - static SEL otherMouseDraggedSel = nullptr; - static SEL scrollWheelSel = nullptr; - static SEL deltaXSel = nullptr; - static SEL deltaYSel = nullptr; + static SEL keyDownSel, keyUpSel, mouseDownSel, mouseUpSel, mouseDraggedSel, mouseMovedSel, rightMouseDownSel, rightMouseUpSel, rightMouseDraggedSel, otherMouseDownSel = nullptr; + static SEL otherMouseUpSel, otherMouseDraggedSel, scrollWheelSel, deltaXSel, deltaYSel = nullptr; // Managing first responder status and keyboard focus selectors - static SEL acceptsFirstResponderSel = nullptr; - static SEL becomeFirstResponderSel = nullptr; - static SEL canBecomeKeyViewSel = nullptr; - static SEL needsPanelToBecomeKeySel = nullptr; - static SEL drawRectSel = nullptr; - static SEL reshapeSel = nullptr; - static SEL updateSel = nullptr; + static SEL acceptsFirstResponderSel, becomeFirstResponderSel, canBecomeKeyViewSel, needsPanelToBecomeKeySel, drawRectSel, reshapeSel, updateSel = nullptr; // NSOpenGL pixel format, view, and context management selectors - static SEL initWithAttributesSel = nullptr; - static SEL initWithFramePixelFormatSel = nullptr; - static SEL setContentViewSel = nullptr; - static SEL contentViewSel = nullptr; - static SEL boundsSel = nullptr; - static SEL convertPointFromViewSel = nullptr; - static SEL openGLContextSel = nullptr; - static SEL makeCurrentContextSel = nullptr; - static SEL setAutoresizingMaskSel = nullptr; - static SEL flushBufferSel = nullptr; - static SEL displaySel = nullptr; - static SEL CGLContextObjSel = nullptr; - static SEL setValuesSel = nullptr; + static SEL initWithAttributesSel, initWithFramePixelFormatSel, setContentViewSel, contentViewSel, boundsSel, convertPointFromViewSel, openGLContextSel, makeCurrentContextSel = nullptr; + static SEL setAutoresizingMaskSel, flushBufferSel, displaySel, CGLContextObjSel, setValuesSel = nullptr; // Extracting data from NSEvent objects selectors - static SEL keyCodeSel = nullptr; - static SEL charactersSel = nullptr; - static SEL locationInWindowSel = nullptr; - static SEL buttonNumberSel = nullptr; - static SEL clickCountSel = nullptr; - static SEL modifierFlagsSel = nullptr; - static SEL utf8StringSel = nullptr; + static SEL keyCodeSel, charactersSel, locationInWindowSel, buttonNumberSel, clickCountSel, modifierFlagsSel, utf8StringSel = nullptr; // NSImage, NSBitmapImageRep, and image data access selectors - static SEL initWithContentsOfFileSel = nullptr; - static SEL representationsSel = nullptr; - static SEL countSel = nullptr; - static SEL objectAtIndexSel = nullptr; - static SEL pixelsWideSel = nullptr; - static SEL pixelsHighSel = nullptr; - static SEL bitsPerPixelSel = nullptr; - static SEL bytesPerRowSel = nullptr; - static SEL hasAlphaSel = nullptr; - static SEL bitmapDataSel = nullptr; + static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; // Initialize all selectors - called once at startup void initializeSelectors() { @@ -6699,7 +6524,6 @@ struct CustomOpenGLView; struct WindowDelegate; struct ImageLoader; - // enums for OpenGL pixel format and rendering capabilities enum class NSOpenGLPixelFormatAttribute : uint16_t { // Renderer selection @@ -6844,7 +6668,6 @@ static Window* gptrNSWindowEvents = nullptr; static Application* gptrApplicationDelegate = nullptr; static Window* gptrWindowDelegate = nullptr; - // Handles NSApplication delegate methods and lifecycle events struct ApplicationDelegate { Class isa; // Objective-C class pointer (required) @@ -6897,8 +6720,6 @@ struct Application { // Delete copy constructor and assignment Application(const Application&) = delete; Application& operator=(const Application&) = delete; - - // Allow move semantics Application(Application&&) = default; Application& operator=(Application&&) = default; }; @@ -7006,7 +6827,6 @@ struct ImageLoader { unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; }; - // Called when application is about to finish launching void applicationWillFinishLaunching(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; // Remove unused parameter warnings @@ -7146,7 +6966,6 @@ void convertToContentViewCoordinates(NSPoint& location) { } } -// Structure to hold common mouse event data struct MouseEventData { NSPoint location = {0.0, 0.0}; NSUInteger modifierFlags = 0; @@ -7166,8 +6985,6 @@ MouseEventData extractMouseEventData(id event) { return data; } - - // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings @@ -7186,12 +7003,10 @@ 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); } - } // Handle mouse drag events @@ -7199,7 +7014,6 @@ 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); } @@ -7209,12 +7023,6 @@ void view_mouseDragged(id self, SEL _cmd, id event) { void view_mouseMoved(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - // Note: mouseMoved events do not have buttonNumber except in the case of Touch screens - // When using a touch screen, mouseMoved events may include a buttonNumber corresponding to the touch point - // However, for standard mouse movement, buttonNumber is typically not applicable, but if you call it, it returns 0 - // Hence, we will use kNoButton constant to indicate no button is pressed during mouse movement and avoid - // using extractMouseEventData which retrieves buttonNumber - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); @@ -7230,7 +7038,6 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -7241,7 +7048,6 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -7252,7 +7058,6 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -7264,7 +7069,6 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -7275,7 +7079,6 @@ void view_otherMouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -7285,7 +7088,6 @@ void view_otherMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); } @@ -7307,7 +7109,6 @@ void view_scrollWheel(id self, SEL _cmd, id event) { } } - // Determine if view can accept first responder status id view_acceptsFirstResponder(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -7335,25 +7136,20 @@ id view_needsPanelToBecomeKey(id self, SEL _cmd) { // Empty drawRect method - prevents automatic drawing on main thread void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { (void)self;(void)_cmd;(void)dirtyRect; - - // Block automatic drawing - PGE engine thread will handle OpenGL rendering } // Empty reshape method - prevents automatic context reshaping on main thread void view_reshape(id self, SEL _cmd) { (void)self;(void)_cmd; - // Block automatic reshape - PGE engine thread will handle OpenGL updates } // Empty update method - prevents automatic context updates on main thread void view_update(id self, SEL _cmd) { (void)self;(void)_cmd; - // Block automatic update - PGE engine thread will handle OpenGL updates } // Create custom OpenGL view class Class createCustomOpenGLViewClass() { - // Check if class already exists Class existingClass = objc_getClass(kCustomOpenGLViewClass); if (existingClass) { return existingClass; @@ -7413,7 +7209,8 @@ void windowDidResize(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { - // Safely update frame data only - no OpenGL operations + + // Update frame data only window_updateFrameFromOSX(gptrWindowDelegate); // Get the new content view size @@ -7443,6 +7240,7 @@ void windowDidBecomeKey(id self, SEL _cmd, id notification) { } } +// handle window did resign key events void windowDidResignKey(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; if (gptrWindowDelegate && gptrWindowDelegate->windowDidResignKeyCallback) { @@ -7450,6 +7248,7 @@ void windowDidResignKey(id self, SEL _cmd, id notification) { } } +// Handle window did miniaturize events void windowDidMiniaturize(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; if (gptrWindowDelegate && gptrWindowDelegate->windowDidMiniaturizeCallback) { @@ -7530,7 +7329,6 @@ id createWindowDelegateForWindow(void) { return delegate; } - // Forward declarations for C API functions used by internal helpers extern "C" void window_setDelegate(Window* self, id delegate); @@ -7638,7 +7436,6 @@ extern "C" { window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, &self->contentViewFrame.width, &self->contentViewFrame.height); - // Update internal frame representation from actual NSWindow } // Show the window and set up event handling @@ -7662,8 +7459,8 @@ extern "C" { // Destroy the window void window_destroy(Window* self) { - // NSWindow will be cleaned up by autorelease pool (void)self; + // NSWindow will be cleaned up by autorelease pool } // Window delegate setter @@ -7701,11 +7498,7 @@ extern "C" { // Refresh internal frame representation from actual NSWindow (OSX) void window_updateFrameFromOSX(Window* self) { - - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); - - self->windowFrame = screenFrame; // Update internal frame representation - + self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); } // Get the content view size (excludes title bar and borders) @@ -7758,7 +7551,6 @@ extern "C" { } } - // Get Window frame (x, y, width, height) void window_getWindowFrame(const Window* self, double* x, double* y, double* width, double* height) { @@ -7883,20 +7675,10 @@ extern "C" { ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); // Make the OpenGL view the first responder so it can receive keyboard and mouse events - BOOL result = ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); - - if(result == NO) { - // Handle error if needed - //printf("Warning: Failed to make OpenGL view the first responder.\n"); - } + ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); // Additional debug: check if view can become key view - BOOL canBecomeKey = ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); - if(canBecomeKey == NO) { - // Handle error if needed - //printf("Warning: OpenGL view cannot become key view.\n"); - } - + ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); } // Implementation function @@ -7923,7 +7705,6 @@ extern "C" { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); return true; - } @@ -7934,11 +7715,8 @@ extern "C" { // Get the underlying CGLContextObj from the OpenGL renderer void* opengl_getCGLContextObj(struct OpenGLRenderer* self) { - if (!self || !self->glContext) { - return nullptr; - } + if (!self || !self->glContext) { return nullptr;} - // Get the CGLContextObj from the NSOpenGLContext return (void*)((void*(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::CGLContextObjSel); } @@ -7956,9 +7734,7 @@ extern "C" { // Enable or disable vertical synchronization (vsync) void opengl_setVsync(OpenGLRenderer* self, BOOL enabled) { - if (!self || !self->glContext) { - return; - } + if (!self || !self->glContext) { return; } // Make context current first ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); @@ -7967,7 +7743,6 @@ extern "C" { GLint swapInterval = enabled ? 1 : 0; // Use NSOpenGLContext setValues:forParameter: to set swap interval - // NSOpenGLContextParameterSwapInterval = 222 const GLint parameter = 222; // NSOpenGLContextParameterSwapInterval ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } @@ -8162,36 +7937,7 @@ extern "C" { self->bytesPerRow = kZeroRows; self->hasAlpha = NO; } - - // Create OpenGL texture from ImageLoader - unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* loader) { - if (!loader->isLoaded(const_cast(loader))) { - return 0; - } - - unsigned int textureID; - glGenTextures(1, &textureID); - glBindTexture(GL_TEXTURE_2D, textureID); - - // Set texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Determine format - GLenum format = (loader->bytesPerPixel == kRGBABytesPerPixel) ? GL_RGBA : GL_RGB; - - // Upload texture data - glTexImage2D(GL_TEXTURE_2D, 0, format, loader->width, loader->height, - 0, format, GL_UNSIGNED_BYTE, loader->pixelData); - - // Generate mipmaps for better quality at different scales - glGenerateMipmap(GL_TEXTURE_2D); - - return textureID; - } - + // ImageLoader constructor struct ImageLoader* imageloader_init() { struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); @@ -8212,7 +7958,6 @@ extern "C" { loader->getDetailedInfo = imageloader_getDetailedInfo; loader->isLoaded = imageloader_isLoaded; loader->getPixel = imageloader_getPixel; - loader->createOpenGLTexture = imageloader_createOpenGLTexture; return loader; } @@ -8374,38 +8119,32 @@ extern "C" { void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidResizeCallback = callback; self->windowDidResizeUserData = userData; - ensureWindowDelegate(self); // Ensure delegate exists when callbacks are set } void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { self->windowWillCloseCallback = callback; self->windowWillCloseUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidBecomeKeyCallback = callback; self->windowDidBecomeKeyUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidResignKeyCallback = callback; self->windowDidResignKeyUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidMiniaturizeCallback = callback; self->windowDidMiniaturizeUserData = userData; - ensureWindowDelegate(self); } void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { self->windowDidDeminiaturizeCallback = callback; self->windowDidDeminiaturizeUserData = userData; - ensureWindowDelegate(self); } // Application delegate callback setter functions @@ -9894,6 +9633,10 @@ namespace olc::apis::opengl bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; +#if OLC_HOST == OLC_HOST_WINDOWS + bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; +#endif + return bLoaded; } @@ -10057,6 +9800,13 @@ namespace olc::apis::opengl #endif } + void gl::glSwapInterval(GLsizei n) + { +#if OLC_HOST == OLC_HOST_WINDOWS + _wglSwapIntervalEXT(n); +#endif + } + GLuint gl::glCreateShader(GLenum type) { return _glCreateShader(type); @@ -10545,12 +10295,6 @@ void main() lastError = RendererError::FailedToSwitchRenderContext; return false; } - - //// Set Vertical Sync - //glSwapInterval = OGL_LOAD(glSwapInterval); - //if (locSwapInterval && !bVSYNC) locSwapInterval(0); - //bSync = bVSYNC; - #endif #if OLC_HOST == OLC_HOST_LINUX_X11 @@ -10620,6 +10364,20 @@ void main() lastError = RendererError::NoError; return false; } + + // Configure Swap Interval (VSync) + if (config.VerticalSync) + { + // Enable VSync - lock to display refresh + // May also be governed by OS / driver settings + // and desktop compositor settings + gl.glSwapInterval(1); + } + else + { + // Disable VSync - run like the clappers! + gl.glSwapInterval(0); + } // Create "Default" Shader @@ -11470,6 +11228,8 @@ void olc::Draw2D::ProcessGPUTasks() for (const auto& task : vecGPUTasks.data) pRenderer->DoGPUTask(task); + drawMetrics.nGPUTasks += uint32_t(vecGPUTasks.data.size()); + vecGPUTasks.data.clear(); } @@ -11486,6 +11246,8 @@ void Draw2D::PrepareTargetForSW() // Image is now CPU bound pTarget->BindCPU(); + drawMetrics.nGPUtoCPUTransfers++; + // Create a scanline buffer the height of this target vScanlines.resize(size_t(pTarget->Size().y), {}); } @@ -11500,6 +11262,8 @@ void Draw2D::PrepareTargetForHW() // Image is now GPU bound pTarget->BindGPU(); + + drawMetrics.nCPUtoGPUTransfers++; } } @@ -11515,6 +11279,8 @@ void Draw2D::PrepareImageForSW(olc::Image& image) // Image is now CPU bound image.BindCPU(); + + drawMetrics.nGPUtoCPUTransfers++; } } @@ -11527,6 +11293,8 @@ void Draw2D::PrepareImageForHW(olc::Image& image) // Image is now GPU bound image.BindGPU(); + + drawMetrics.nCPUtoGPUTransfers++; } // Only resolve MSAA if this image is NOT the current render target @@ -11541,6 +11309,9 @@ bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) { // Finish all drawing with current shader ProcessGPUTasks(); + + drawMetrics.nShaderChanges++; + // Set new shader return pRenderer->ApplyShader(shader); } @@ -11548,12 +11319,12 @@ bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) bool olc::Draw2D::ResetShader() { ProcessGPUTasks(); + drawMetrics.nShaderChanges++; return pRenderer->ApplyDefaultShader(); } bool olc::Draw2D::SetShaderUniform(const std::string& name, const float value) { - return pRenderer->SetUniform(name, value); } @@ -11573,6 +11344,16 @@ bool olc::Draw2D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) return pRenderer->AssignTextureSource(nSlot, image.GetGPUID()); } +void olc::Draw2D::ResetDrawMetrics() +{ + drawMetrics = sDrawMetrics(); +} + +olc::Draw2D::sDrawMetrics olc::Draw2D::GetDrawMetrics() const +{ + return drawMetrics; +} + void olc::Draw2D::WorldReset() { transformAffine = olc::tf2d(); @@ -14540,48 +14321,41 @@ namespace olc::imload return false; // File does not exist } - try { - // Create macOS API wrapper image loader - olc::apis::macos::ImageLoader loader; - - // Load the image file - if (!loader.loadFromFile(sFileName) || !loader.isLoaded()) { - return false; // Failed to load file - } - - // Get image dimensions and info - int width, height, bytesPerPixel; - loader.getImageInfo(width, height, bytesPerPixel); - - if (width <= 0 || height <= 0) { - return false; // Invalid dimensions - } - - // Get raw pixel data from the loader - unsigned char* pixelData = imageloader_getPixelData(loader.getCHandle()); - if (!pixelData) { - return false; // Failed to get pixel data - } - - // Create our olc::Image - if (!image.Create({width, height})) { - return false; // Failed to create image - } - - // Clear and resize the pixel vector - image.GetPixels().clear(); - image.GetPixels().resize(width * height); - - // Copy pixel data - assuming the loader provides RGBA data - // The api_macos should provide RGBA format with 4 bytes per pixel - std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); - - return true; + // Create macOS API wrapper image loader + olc::apis::macos::ImageLoader loader; + + if (!loader.loadFromFile(sFileName) || !loader.isLoaded()) { + return false; // Failed to load file } - catch (const std::exception& e) { - // Handle any exceptions from the wrapper - return false; + + // Get image dimensions and info + int width, height, bytesPerPixel; + loader.getImageInfo(width, height, bytesPerPixel); + + if (width <= 0 || height <= 0) { + return false; // Invalid dimensions + } + + // Get raw pixel data from the loader + unsigned char* pixelData = imageloader_getPixelData(loader.getCHandle()); + if (!pixelData) { + return false; // Failed to get pixel data + } + + // Create our olc::Image + if (!image.Create({width, height})) { + return false; // Failed to create image } + + // Clear and resize the pixel vector + image.GetPixels().clear(); + image.GetPixels().resize(width * height); + + // The api_macos will provide RGBA format with 4 bytes per pixel + std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + + return true; + } bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) From d69932717d43922514dffd223c1f2ba2c9e91c6a Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 27 Jan 2026 15:59:16 -0500 Subject: [PATCH 18/56] [emscripten] revamp callback with custom struct userData, implement keyboard --- dev/src/host_web_emscripten.cpp | 276 ++++++++++++++++++++++---------- dev/src/host_web_emscripten.h | 18 ++- 2 files changed, 205 insertions(+), 89 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index ce6870e7..fa579674 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -5,50 +5,126 @@ namespace olc::host { std::unordered_map Host_Web_Emscripten::mapUID2CanvasId; std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; + std::unordered_map Host_Web_Emscripten::mapUID2CallbackData; Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; - // mapKeys[DOM_PK_UNKNOWN] = Key::NONE; - // mapKeys[DOM_PK_A] = Key::A; mapKeys[DOM_PK_B] = Key::B; mapKeys[DOM_PK_C] = Key::C; mapKeys[DOM_PK_D] = Key::D; - // mapKeys[DOM_PK_E] = Key::E; mapKeys[DOM_PK_F] = Key::F; mapKeys[DOM_PK_G] = Key::G; mapKeys[DOM_PK_H] = Key::H; - // mapKeys[DOM_PK_I] = Key::I; mapKeys[DOM_PK_J] = Key::J; mapKeys[DOM_PK_K] = Key::K; mapKeys[DOM_PK_L] = Key::L; - // mapKeys[DOM_PK_M] = Key::M; mapKeys[DOM_PK_N] = Key::N; mapKeys[DOM_PK_O] = Key::O; mapKeys[DOM_PK_P] = Key::P; - // mapKeys[DOM_PK_Q] = Key::Q; mapKeys[DOM_PK_R] = Key::R; mapKeys[DOM_PK_S] = Key::S; mapKeys[DOM_PK_T] = Key::T; - // mapKeys[DOM_PK_U] = Key::U; mapKeys[DOM_PK_V] = Key::V; mapKeys[DOM_PK_W] = Key::W; mapKeys[DOM_PK_X] = Key::X; - // mapKeys[DOM_PK_Y] = Key::Y; mapKeys[DOM_PK_Z] = Key::Z; - // mapKeys[DOM_PK_0] = Key::K0; mapKeys[DOM_PK_1] = Key::K1; mapKeys[DOM_PK_2] = Key::K2; - // mapKeys[DOM_PK_3] = Key::K3; mapKeys[DOM_PK_4] = Key::K4; mapKeys[DOM_PK_5] = Key::K5; - // mapKeys[DOM_PK_6] = Key::K6; mapKeys[DOM_PK_7] = Key::K7; mapKeys[DOM_PK_8] = Key::K8; - // mapKeys[DOM_PK_9] = Key::K9; - // mapKeys[DOM_PK_F1] = Key::F1; mapKeys[DOM_PK_F2] = Key::F2; mapKeys[DOM_PK_F3] = Key::F3; mapKeys[DOM_PK_F4] = Key::F4; - // mapKeys[DOM_PK_F5] = Key::F5; mapKeys[DOM_PK_F6] = Key::F6; mapKeys[DOM_PK_F7] = Key::F7; mapKeys[DOM_PK_F8] = Key::F8; - // mapKeys[DOM_PK_F9] = Key::F9; mapKeys[DOM_PK_F10] = Key::F10; mapKeys[DOM_PK_F11] = Key::F11; mapKeys[DOM_PK_F12] = Key::F12; - // mapKeys[DOM_PK_ARROW_UP] = Key::UP; mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; - // mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; - // mapKeys[DOM_PK_SPACE] = Key::SPACE; mapKeys[DOM_PK_TAB] = Key::TAB; - // mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; - // mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; - // mapKeys[DOM_PK_INSERT] = Key::INS; mapKeys[DOM_PK_DELETE] = Key::DEL; mapKeys[DOM_PK_HOME] = Key::HOME; - // mapKeys[DOM_PK_END] = Key::END; mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; - // mapKeys[DOM_PK_BACKSPACE] = Key::BACK; mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; - // mapKeys[DOM_PK_ENTER] = Key::ENTER; mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; - // mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; mapKeys[DOM_PK_PAUSE] = Key::PAUSE; - // mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; - // mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; - // mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; - // mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; - // mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; - // mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; - // mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; - // mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; - // mapKeys[DOM_PK_PERIOD] = Key::PERIOD; mapKeys[DOM_PK_EQUAL] = Key::EQUALS; - // mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; - // mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; - // mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; mapKeys[DOM_PK_SLASH] = Key::OEM_2; mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; - // mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; - // mapKeys[DOM_PK_QUOTE] = Key::OEM_7; + // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes + mapKeys[DOM_PK_UNKNOWN] = Key::NONE; + + // A-Z + mapKeys[DOM_PK_A] = Key::A; + mapKeys[DOM_PK_B] = Key::B; + mapKeys[DOM_PK_C] = Key::C; + mapKeys[DOM_PK_D] = Key::D; + mapKeys[DOM_PK_E] = Key::E; + mapKeys[DOM_PK_F] = Key::F; + mapKeys[DOM_PK_G] = Key::G; + mapKeys[DOM_PK_H] = Key::H; + mapKeys[DOM_PK_I] = Key::I; + mapKeys[DOM_PK_J] = Key::J; + mapKeys[DOM_PK_K] = Key::K; + mapKeys[DOM_PK_L] = Key::L; + mapKeys[DOM_PK_M] = Key::M; + mapKeys[DOM_PK_N] = Key::N; + mapKeys[DOM_PK_O] = Key::O; + mapKeys[DOM_PK_P] = Key::P; + mapKeys[DOM_PK_Q] = Key::Q; + mapKeys[DOM_PK_R] = Key::R; + mapKeys[DOM_PK_S] = Key::S; + mapKeys[DOM_PK_T] = Key::T; + mapKeys[DOM_PK_U] = Key::U; + mapKeys[DOM_PK_V] = Key::V; + mapKeys[DOM_PK_W] = Key::W; + mapKeys[DOM_PK_X] = Key::X; + mapKeys[DOM_PK_Y] = Key::Y; + mapKeys[DOM_PK_Z] = Key::Z; + + // Numeric Keys + mapKeys[DOM_PK_0] = Key::K0; + mapKeys[DOM_PK_1] = Key::K1; + mapKeys[DOM_PK_2] = Key::K2; + mapKeys[DOM_PK_3] = Key::K3; + mapKeys[DOM_PK_4] = Key::K4; + mapKeys[DOM_PK_5] = Key::K5; + mapKeys[DOM_PK_6] = Key::K6; + mapKeys[DOM_PK_7] = Key::K7; + mapKeys[DOM_PK_8] = Key::K8; + mapKeys[DOM_PK_9] = Key::K9; + + // Function Keys + mapKeys[DOM_PK_F1] = Key::F1; + mapKeys[DOM_PK_F2] = Key::F2; + mapKeys[DOM_PK_F3] = Key::F3; + mapKeys[DOM_PK_F4] = Key::F4; + mapKeys[DOM_PK_F5] = Key::F5; + mapKeys[DOM_PK_F6] = Key::F6; + mapKeys[DOM_PK_F7] = Key::F7; + mapKeys[DOM_PK_F8] = Key::F8; + mapKeys[DOM_PK_F9] = Key::F9; + mapKeys[DOM_PK_F10] = Key::F10; + mapKeys[DOM_PK_F11] = Key::F11; + mapKeys[DOM_PK_F12] = Key::F12; + + // Arrow Keys + mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; + mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; + mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; + mapKeys[DOM_PK_ARROW_UP] = Key::UP; + + // Other Keys + mapKeys[DOM_PK_BACKSPACE] = Key::BACK; + mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; + mapKeys[DOM_PK_ENTER] = Key::ENTER; + mapKeys[DOM_PK_PAUSE] = Key::PAUSE; + mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; + mapKeys[DOM_PK_TAB] = Key::TAB; + mapKeys[DOM_PK_DELETE] = Key::DEL; + mapKeys[DOM_PK_HOME] = Key::HOME; + mapKeys[DOM_PK_END] = Key::END; + mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; + mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; + mapKeys[DOM_PK_INSERT] = Key::INS; + mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; + mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; + mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; + mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; + mapKeys[DOM_PK_SPACE] = Key::SPACE; + mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; + + // Numpad + mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; + mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; + mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; + mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; + mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; + mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; + mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; + mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; + mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; + mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; + mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; + mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; + mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; + mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; + mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; + mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; + + mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; + mapKeys[DOM_PK_SLASH] = Key::OEM_2; + mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; + mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; + mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; + mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; + mapKeys[DOM_PK_QUOTE] = Key::OEM_7; + + mapKeys[DOM_PK_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_COMMA] = Key::COMMA; + mapKeys[DOM_PK_MINUS] = Key::MINUS; + mapKeys[DOM_PK_PERIOD] = Key::PERIOD; } bool Host_Web_Emscripten::StartSystemEventLoop(bool bBlockIfPossible) @@ -67,40 +143,45 @@ namespace olc::host olc::vi2d vWinSize = vWindowSize; // TODO: multi-window solutions - canvasId = "#canvas"; // + std::to_string(pWindow->GetUID()); - - mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), canvasId); - mapCanvasId2PTR.insert_or_assign(canvasId, pWindow); + CallbackData* cbData = new CallbackData{ + this, + pWindow, + std::string{"#canvas"} + }; + + mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), cbData->canvasId); + mapUID2CallbackData.insert_or_assign(pWindow->GetUID(), cbData); + mapCanvasId2PTR.insert_or_assign(cbData->canvasId, pWindow); - emscripten_set_canvas_element_size(canvasId.c_str(), vWinSize.x, vWinSize.y); + emscripten_set_canvas_element_size(cbData->canvasId.c_str(), vWinSize.x, vWinSize.y); pWindow->SetWindowSize(vWinSize); // Keyboard Callbacks - emscripten_set_keydown_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, keyboard_callback); - emscripten_set_keyup_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, keyboard_callback); + emscripten_set_keydown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); + emscripten_set_keyup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); // Mouse Callbacks - emscripten_set_wheel_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, wheel_callback); - emscripten_set_mousedown_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, mouse_callback); - emscripten_set_mouseup_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, mouse_callback); - emscripten_set_mousemove_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, mouse_callback); + emscripten_set_wheel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, wheel_callback); + emscripten_set_mousedown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + emscripten_set_mouseup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + emscripten_set_mousemove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); // Touch Callbacks - emscripten_set_touchstart_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, touch_callback); - emscripten_set_touchmove_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, touch_callback); - emscripten_set_touchend_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, touch_callback); + 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); // Canvas Focus Callbacks - emscripten_set_blur_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, focus_callback); - emscripten_set_focus_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, focus_callback); + emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); + emscripten_set_focus_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); // Canvas Resize Callbacks - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, (void*)(canvasId.c_str()), 1, resize_callback); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, (void*)(canvasId.c_str()), 1, fullscreen_change_callback); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, resize_callback); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, fullscreen_change_callback); // trigger resize after a short pause emscripten_sleep(50); - resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, (void*)(canvasId.c_str())); + resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, reinterpret_cast(cbData)); return true; } @@ -108,6 +189,8 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { + CallbackData* pCallbackData = reinterpret_cast(userData); + // we maintain our own state for teh number pad, default true static bool numPadActive = true; @@ -145,11 +228,23 @@ namespace olc::host numPadActive = !numPadActive; } - // if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) - // ptrPGE->olc_UpdateKeyState(pk_code, true); + if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) + { + if(pCallbackData->pHost->mapKeys.contains(pk_code)) + { + olc_OnKeyPress(pCallbackData->pWindow, pCallbackData->pHost->mapKeys[pk_code], true); + } + } + - // if (eventType == EMSCRIPTEN_EVENT_KEYUP) - // ptrPGE->olc_UpdateKeyState(pk_code, false); + if (eventType == EMSCRIPTEN_EVENT_KEYUP) + { + if(pCallbackData->pHost->mapKeys.contains(pk_code)) + { + olc_OnKeyPress(pCallbackData->pWindow, pCallbackData->pHost->mapKeys[pk_code], false); + } + } + //Consume keyboard events so that keys like F1 and F5 don't do weird things return EM_TRUE; @@ -158,10 +253,10 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); if (eventType == EMSCRIPTEN_EVENT_WHEEL) - olc_OnMouseWheel(pWindow, -1 * e->deltaY); + olc_OnMouseWheel(pCallbackData->pWindow, -1 * e->deltaY); return EM_TRUE; } @@ -181,36 +276,36 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); //Mouse Movement if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) - olc_OnMouseMove(pWindow, {e->targetX, e->targetY}); + olc_OnMouseMove(pCallbackData->pWindow, {e->targetX, e->targetY}); //Mouse button press if (e->button == 0) // left click { if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pWindow, 0, true); + olc_OnMouseButton(pCallbackData->pWindow, 0, true); else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pWindow, 0, false); + olc_OnMouseButton(pCallbackData->pWindow, 0, false); } if (e->button == 2) // right click { if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pWindow, 1, true); + olc_OnMouseButton(pCallbackData->pWindow, 1, true); else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pWindow, 1, false); + olc_OnMouseButton(pCallbackData->pWindow, 1, false); } if (e->button == 1) // middle click { if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pWindow, 2, true); + olc_OnMouseButton(pCallbackData->pWindow, 2, true); else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pWindow, 2, false); + olc_OnMouseButton(pCallbackData->pWindow, 2, false); //at the moment only middle mouse needs to consume events. return EM_TRUE; @@ -225,25 +320,25 @@ namespace olc::host // TODO: Implement touch more effectively. // For now, emulate single pointer mouse. - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); // Move if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) { - olc_OnMouseMove(pWindow, {e->touches->targetX, e->touches->targetY}); + olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); } // Start if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) { - olc_OnMouseMove(pWindow, {e->touches->targetX, e->touches->targetY}); - olc_OnMouseButton(pWindow, 0, true); + olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); + olc_OnMouseButton(pCallbackData->pWindow, 0, true); } // End if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) { - olc_OnMouseButton(pWindow, 0, false); + olc_OnMouseButton(pCallbackData->pWindow, 0, false); } return EM_TRUE; @@ -251,17 +346,17 @@ namespace olc::host //TY Gorbit EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); if (eventType == EMSCRIPTEN_EVENT_BLUR) { // ptrPGE->olc_UpdateKeyFocus(false); - olc_OnMouseFocus(pWindow, false); + olc_OnMouseFocus(pCallbackData->pWindow, false); } else if (eventType == EMSCRIPTEN_EVENT_FOCUS) { // ptrPGE->olc_UpdateKeyFocus(true); - olc_OnMouseFocus(pWindow, true); + olc_OnMouseFocus(pCallbackData->pWindow, true); } return 0; @@ -270,8 +365,6 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); - // trigger resize after a short pause emscripten_sleep(50); resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, userData); @@ -281,8 +374,7 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData) { - char* canvasId = reinterpret_cast(userData); - olc::Window* pWindow = GetWindowFromCanvasId(canvasId); + CallbackData* pCallbackData = reinterpret_cast(userData); // HACK ALERT! // @@ -314,8 +406,8 @@ namespace olc::host } // resize the canvas - emscripten_set_canvas_element_size(canvasId, static_cast(width), static_cast(height)); - pWindow->SetWindowSize(olc::vd2d{width, height}); + emscripten_set_canvas_element_size(pCallbackData->canvasId.c_str(), static_cast(width), static_cast(height)); + pCallbackData->pWindow->SetWindowSize(olc::vd2d{width, height}); return 0; } @@ -336,7 +428,7 @@ namespace olc::host const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); if(window_handle != mapUID2CanvasId.end()) { - return { reinterpret_cast(&canvasId) }; + return { (void*)(window_handle->second.c_str()) }; } return {}; @@ -354,6 +446,11 @@ namespace olc::host std::cout << "Emscripten: SyncWithDesktopComposite not implemented.\n"; return true; } + + olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const + { + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) { @@ -375,6 +472,11 @@ namespace olc::host return pWindow->olc_OnMouseFocus(bHasFocus); } + bool Host_Web_Emscripten::olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed) + { + return pWindow->olc_OnKeyPress(key, bPressed); + } + bool Host_Web_Emscripten::olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos) { return pWindow->olc_OnWindowPosition(vWindowPos); diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 69bab1ca..930b406b 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -20,8 +20,6 @@ namespace olc::host { class Host_Web_Emscripten : public olc::host::Host { - private: - std::string canvasId; public: Host_Web_Emscripten(); bool StartSystemEventLoop(bool bBlockIfPossible = false) override; @@ -35,6 +33,8 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + olc::KeyboardLayout GetKeyboardLayout() const override; + public: // event callbacks static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); @@ -51,6 +51,9 @@ namespace olc::host static bool olc_OnMouseWheel(olc::Window* pWindow, const int32_t nScroll); static bool olc_OnMouseFocus(olc::Window* pWindow, const bool bHasFocus); + // Set Keyboard Device State + static bool olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed); + // Set Window State static bool olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos); static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); @@ -58,10 +61,21 @@ namespace olc::host private: // helpers static olc::Window* GetWindowFromCanvasId(std::string canvasId); + public: // Callback data type + struct CallbackData { + Host_Web_Emscripten* pHost; + olc::Window* pWindow; + std::string canvasId; + }; + private: static std::unordered_map mapUID2CanvasId; + static std::unordered_map mapUID2CallbackData; static std::unordered_map mapCanvasId2PTR; std::atomic terminate {false}; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; }; From e8a4945983f80143dfb66480f1e810011c1f4666 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 Jan 2026 09:13:01 -0500 Subject: [PATCH 19/56] [emscripten] use smart pointer for callback data --- dev/src/host_web_emscripten.cpp | 9 +++++---- dev/src/host_web_emscripten.h | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index fa579674..e0bdb7ec 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -5,7 +5,7 @@ namespace olc::host { std::unordered_map Host_Web_Emscripten::mapUID2CanvasId; std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; - std::unordered_map Host_Web_Emscripten::mapUID2CallbackData; + std::unordered_map> Host_Web_Emscripten::mapUID2CallbackData; Host_Web_Emscripten::Host_Web_Emscripten() { @@ -143,14 +143,15 @@ namespace olc::host olc::vi2d vWinSize = vWindowSize; // TODO: multi-window solutions - CallbackData* cbData = new CallbackData{ + mapUID2CallbackData.insert_or_assign(pWindow->GetUID(), std::make_unique(CallbackData{ this, pWindow, std::string{"#canvas"} - }; + })); + + auto cbData = mapUID2CallbackData.at(pWindow->GetUID()).get(); mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), cbData->canvasId); - mapUID2CallbackData.insert_or_assign(pWindow->GetUID(), cbData); mapCanvasId2PTR.insert_or_assign(cbData->canvasId, pWindow); emscripten_set_canvas_element_size(cbData->canvasId.c_str(), vWinSize.x, vWinSize.y); diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 930b406b..06edc135 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -70,10 +70,10 @@ namespace olc::host private: static std::unordered_map mapUID2CanvasId; - static std::unordered_map mapUID2CallbackData; + static std::unordered_map> mapUID2CallbackData; static std::unordered_map mapCanvasId2PTR; std::atomic terminate {false}; - + // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; }; From 8882eb5c834071d0e2b51e98a2728ba67f53b0b5 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 Jan 2026 09:13:47 -0500 Subject: [PATCH 20/56] [emscripten] remove nagging log for SyncWithDesktopComposie, unused in emscripten --- dev/src/host_web_emscripten.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index e0bdb7ec..cc9b772e 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -444,7 +444,7 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Web_Emscripten::SyncWithDesktopComposite() { - std::cout << "Emscripten: SyncWithDesktopComposite not implemented.\n"; + // SyncWithDesktopComposite not implemented on this platform return true; } From 6d87ae39dfc6f7d00a9b3a940ef8464d4d560090 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 Jan 2026 09:14:22 -0500 Subject: [PATCH 21/56] [emscripten] detect and use keyboard layout --- dev/src/host_web_emscripten.cpp | 42 +++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index cc9b772e..c1c21555 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -11,6 +11,44 @@ namespace olc::host { std::cout << "Emscripten: host constructed.\n"; + // Detect and Store Keyboard Layout + EM_ASM({ + if (!navigator.keyboard || !navigator.keyboard.getLayoutMap) + return; + + navigator.keyboard.getLayoutMap().then(function(map) + { + const keys = [map.get("KeyQ"), map.get("KeyW"), map.get("KeyE"), map.get("KeyR"), map.get("KeyT"), map.get("KeyY"), map.get("Backslash")]; + + // QWERTY - UK/US + if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'y') { + if(keys[6] == '#' || keys[6] == '~') + { + Module.keyboardLayout = 0; + return; + } + else + { + Module.keyboardLayout = 1; + return; + } + } + + // QWERTZ - DE + if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { + Module.keyboardLayout = 2; + return; + } + + // AZERTY - FR + if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { + Module.keyboardLayout = 3; + return; + } + + }); + }); + // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes mapKeys[DOM_PK_UNKNOWN] = Key::NONE; @@ -348,7 +386,7 @@ namespace olc::host EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { CallbackData* pCallbackData = reinterpret_cast(userData); - + if (eventType == EMSCRIPTEN_EVENT_BLUR) { // ptrPGE->olc_UpdateKeyFocus(false); @@ -450,7 +488,7 @@ namespace olc::host olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { - return OLC_DEFAULT_KEYBOARD_LAYOUT; + return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); } bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) From 151926a1bc4b4935403ae0ea9d6899ff63f20673 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 Jan 2026 09:43:42 -0500 Subject: [PATCH 22/56] [wayland] add wayland executables and artifact source files --- .gitignore | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.gitignore b/.gitignore index bad33d78..dfcf3d95 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,28 @@ xcuserdata/ /dev/msvc/olcPGE3/x64 /dev/msvc/olcPGE3_BuildSH/x64 build/ + +# wayland artifacts +examples/olcPGE3_AntiAliasing +examples/olcPGE3_BatchesOfFills +examples/olcPGE3_BatchesOfLines +examples/olcPGE3_CirclesEllipses +examples/olcPGE3_ImageQuads +examples/olcPGE3_ImageRect +examples/olcPGE3_ImageRegion +examples/olcPGE3_ImageTiling +examples/olcPGE3_Keyboard +examples/olcPGE3_Lines +examples/olcPGE3_Pixels +examples/olcPGE3_PixelShaders +examples/olcPGE3_Polygons +examples/olcPGE3_Rectangles +examples/olcPGE3_Shockwave +examples/olcPGE3_TextBasics +examples/olcPGE3_TextFontMap +examples/xdg-decoration.c +examples/xdg-decoration.h +examples/xdg-decoration.o +examples/xdg-shell.c +examples/xdg-shell.h +examples/xdg-shell.o From 028b70b1632bd3e3b17a5a2f875345954b98ebb7 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 Jan 2026 11:39:16 -0500 Subject: [PATCH 23/56] [gpu_opengl33] merge emscripten and wayland eglSwapBuffers, identical on both platforms --- dev/src/gpu_opengl33.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 458eb2b8..db911f74 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -1105,17 +1105,10 @@ void main() X11::glXSwapBuffers(display, window_handle); #endif -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - const auto* wayland_window = reinterpret_cast(os_win_id[0]); - //auto* window_handle = wayland_window->window; - //auto* display = reinterpret_cast(os_win_id[1]); +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND eglSwapBuffers(glRenderContext.display, glRenderContext.surface); #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - eglSwapInterval(glRenderContext.display, bVerticalSyncNow ? 1 : 0); -#endif - return true; } From 45bbc70128cfcc8134b7a6afa65a8327d6dd678b Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 28 Jan 2026 11:39:55 -0500 Subject: [PATCH 24/56] [gpu_opengl33] use eglSwapInterval for emscripten and wayland --- dev/src/gpu_opengl33.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index db911f74..6332f8f7 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -327,12 +327,20 @@ void main() // Enable VSync - lock to display refresh // May also be governed by OS / driver settings // and desktop compositor settings +#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) gl.glSwapInterval(1); +#else + eglSwapInterval(glRenderContext.display, 1); +#endif } else { // Disable VSync - run like the clappers! +#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) gl.glSwapInterval(0); +#else + eglSwapInterval(glRenderContext.display, 0); +#endif } From 52762d71b0af243be32ddbfab69e0c6fb5c3bb48 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:55:59 +0000 Subject: [PATCH 25/56] 111-add-macos-keyboard-support --- dev/src/api_macos.cpp | 57 ++++- dev/src/api_macos.h | 3 +- dev/src/api_macos_wrapper.hpp | 29 ++- dev/src/host_apple_macos.cpp | 216 +++++++++++++++--- dev/src/host_apple_macos.h | 13 +- .../olcPGE3.xcodeproj/project.pbxproj | 4 + 6 files changed, 264 insertions(+), 58 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index f9e4b812..47fb2d9d 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -120,6 +120,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -184,6 +190,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -290,6 +299,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -540,6 +553,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -566,8 +580,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -732,6 +746,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -740,6 +755,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -750,7 +766,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -761,7 +777,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -1203,6 +1219,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -1225,10 +1261,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -1866,12 +1903,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index a7b4b621..af4adc8c 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -50,6 +50,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -98,7 +99,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index 705dd961..c2eceb67 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -95,6 +95,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -632,10 +638,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -689,10 +696,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -705,12 +712,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index f5adfe45..3fd9cefe 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -6,6 +6,135 @@ //! START IMPLEMENTATION namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -107,6 +236,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -253,6 +411,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -296,6 +456,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -303,43 +477,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 566d08af..fd245507 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -41,7 +41,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -60,6 +60,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -72,6 +74,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -117,6 +122,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index 1a7837b4..3e092141 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 46E85E0A2E39252200B74FA3 /* window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495F22E300E000047E223 /* window.cpp */; }; 46E85E0B2E39252B00B74FA3 /* gpu_opengl33.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495E12E300E000047E223 /* gpu_opengl33.cpp */; }; 46FB045A2F02F7DF00BFA86D /* draw2d_sw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */; }; + 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -88,6 +89,7 @@ 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 46E85DFC2E39137100B74FA3 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw2d_sw.cpp; sourceTree = ""; }; + 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_keyboard.cpp; sourceTree = ""; }; 8998CE202E057F4E007DDD96 /* olcPGE3 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = olcPGE3; sourceTree = BUILT_PRODUCTS_DIR; }; 8998CE2B2E057F85007DDD96 /* test_vector2d.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_vector2d.cpp; sourceTree = ""; }; 8998CE2C2E057F85007DDD96 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; @@ -164,6 +166,7 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( + 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */, 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */, 467318B82EEC213700714D4C /* font.h */, 467318B92EEC213700714D4C /* font.cpp */, @@ -280,6 +283,7 @@ 469A92132E79B0F1007BB460 /* hw_mouse.cpp in Sources */, 46E85E062E3924CC00B74FA3 /* api_opengl.cpp in Sources */, 46964CE82EE5C23300B7BCF1 /* api_macos.cpp in Sources */, + 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */, 46C8F5B92E7DA67900C11019 /* imload_macos.cpp in Sources */, 460496082E362F190047E223 /* test_mh.cpp in Sources */, ); From ee3f7672b853b712ff3ae8b68fe3e011a9e990ec Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Thu, 29 Jan 2026 18:28:40 +0000 Subject: [PATCH 26/56] tidy up some magic numbers --- dev/src/gpu_opengl33.cpp | 23 +++++++++++++++-------- olcPixelGameEngine3.h | 23 +++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 458eb2b8..d57b311d 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -405,18 +405,25 @@ void main() // Create a Frame Buffer Object for off-screen rendering things gl.glGenFramebuffers(1, (GLuint*)&nDefaultFBO); - gl.glBindFramebuffer(36160U, nDefaultFBO); // GL_FRAMEBUFFER + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); // GL_FRAMEBUFFER // Attach 4 colour buffers - std::array attachments = { {36064U, 36065U, 36066U, 36067U} }; + std::array attachments = + { { + gl.GL_COLOR_ATTACHMENT0_X, + gl.GL_COLOR_ATTACHMENT0_X + 1, + gl.GL_COLOR_ATTACHMENT0_X + 2, + gl.GL_COLOR_ATTACHMENT0_X + 3 + } }; + gl.glDrawBuffers(4, attachments.data()); // Unlink them from any existing image textures - //gl.glFramebufferTexture2D(36160U, attachments[0], GL_TEXTURE_2D, 0, 0); - //gl.glFramebufferTexture2D(36160U, attachments[1], GL_TEXTURE_2D, 0, 0); - //gl.glFramebufferTexture2D(36160U, attachments[2], GL_TEXTURE_2D, 0, 0); - //gl.glFramebufferTexture2D(36160U, attachments[3], GL_TEXTURE_2D, 0, 0); - // Unbind the FBO - gl.glBindFramebuffer(36160U, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[0], GL_TEXTURE_2D, 0, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[1], GL_TEXTURE_2D, 0, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[2], GL_TEXTURE_2D, 0, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[3], GL_TEXTURE_2D, 0, 0); + // Unbind the FBO + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); // Create FBOs for MSAA resolve operations gl.glGenFramebuffers(1, &nResolveFBO_Draw); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1c4c5c76..b8273300 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10449,18 +10449,25 @@ void main() // Create a Frame Buffer Object for off-screen rendering things gl.glGenFramebuffers(1, (GLuint*)&nDefaultFBO); - gl.glBindFramebuffer(36160U, nDefaultFBO); // GL_FRAMEBUFFER + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); // GL_FRAMEBUFFER // Attach 4 colour buffers - std::array attachments = { {36064U, 36065U, 36066U, 36067U} }; + std::array attachments = + { { + gl.GL_COLOR_ATTACHMENT0_X, + gl.GL_COLOR_ATTACHMENT0_X + 1, + gl.GL_COLOR_ATTACHMENT0_X + 2, + gl.GL_COLOR_ATTACHMENT0_X + 3 + } }; + gl.glDrawBuffers(4, attachments.data()); // Unlink them from any existing image textures - //gl.glFramebufferTexture2D(36160U, attachments[0], GL_TEXTURE_2D, 0, 0); - //gl.glFramebufferTexture2D(36160U, attachments[1], GL_TEXTURE_2D, 0, 0); - //gl.glFramebufferTexture2D(36160U, attachments[2], GL_TEXTURE_2D, 0, 0); - //gl.glFramebufferTexture2D(36160U, attachments[3], GL_TEXTURE_2D, 0, 0); - // Unbind the FBO - gl.glBindFramebuffer(36160U, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[0], GL_TEXTURE_2D, 0, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[1], GL_TEXTURE_2D, 0, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[2], GL_TEXTURE_2D, 0, 0); + //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[3], GL_TEXTURE_2D, 0, 0); + // Unbind the FBO + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); // Create FBOs for MSAA resolve operations gl.glGenFramebuffers(1, &nResolveFBO_Draw); From a933055facc08a66e8ae25a55e4724809e433c04 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Thu, 29 Jan 2026 21:50:39 +0000 Subject: [PATCH 27/56] ported geometry2d, forcing oinclusion order agnosticism --- dev/msvc/olcPGE3.vcxproj | 3 + dev/msvc/olcPGE3.vcxproj.filters | 9 + utilities/olcUTIL3_Animate2D.h | 238 +++ utilities/olcUTIL3_Camera2D.h | 271 ++++ utilities/olcUTIL3_Geometry2D.h | 2583 ++++++++++++++++++++++++++++++ 5 files changed, 3104 insertions(+) create mode 100644 utilities/olcUTIL3_Animate2D.h create mode 100644 utilities/olcUTIL3_Camera2D.h create mode 100644 utilities/olcUTIL3_Geometry2D.h diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index a203cebd..66c53e4e 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -176,7 +176,10 @@ + + + true true diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 09e92632..c01addef 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -144,6 +144,15 @@ Hosts\Wayland Specific + + Utilities + + + Utilities + + + Utilities + diff --git a/utilities/olcUTIL3_Animate2D.h b/utilities/olcUTIL3_Animate2D.h new file mode 100644 index 00000000..d9283af6 --- /dev/null +++ b/utilities/olcUTIL3_Animate2D.h @@ -0,0 +1,238 @@ +/* + OneLoneCoder - Animate2D v1.01 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Handles animated Sprites efficiently + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2024 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, 2020, 2021, 2022, 2023, 2024 + + + Versions + ~~~~~~~~ + 1.01 +PingPong Style Animation +*/ + +#pragma once + +#include "utilities/olcUTIL_Geometry2D.h" +#include "olcPixelGameEngine.h" +#include + +namespace olc::utils::Animate2D +{ + // This class rerpresents a valid "frame" of an animation. It could be from any image source, and + // any location withing that image source. Once it's constructed, it's advised not to change it, as + // this likely indicates a usage bug. + // + // "Sourceless" frames are valid too - this is useful if you have a particular animation set, but + // want to apply it to a variety of sources, for example sprite maps with common layouts. + class Frame + { + public: + inline Frame(const olc::Renderable* gfxSource, const geom2d::rect& rectSource = { {0,0},{0,0} }) + : gfxImageSource(gfxSource), rectFrameSource(rectSource) + { + // If no source rectangle specified then use whole image source. Ignore in the event + // that a frame is set up as source-less + if(gfxSource && rectFrameSource.size.x == 0) + rectFrameSource.size = gfxSource->Sprite()->Size(); + } + + inline const olc::Renderable* GetSourceImage() const + { + return gfxImageSource; + } + + inline const geom2d::rect& GetSourceRect() const + { + return rectFrameSource; + } + + private: + const olc::Renderable* gfxImageSource; + geom2d::rect rectFrameSource; + }; + + // Animation styles decide how the frames should be traversed in time + enum class Style : uint8_t + { + Repeat, // Cycle through, go back to beginning + OneShot, // Play once and suspend on final frame + PingPong, // Traverse through forwards, then backwards + Reverse, // Cycle through sequence backwards + }; + + class FrameSequence + { + public: + // Constructs a sequence of frames with a duration and a traversal style + inline FrameSequence(const float fFrameDuration = 0.1f, const Style nStyle = Style::Repeat) + { + SetFrameDuration(fFrameDuration); + m_nStyle = nStyle; + } + + inline void SetFrameDuration(const float fFrameDuration = 0.1f) + { + m_fFrameDuration = fFrameDuration; + m_fFrameRate = 1.0f / m_fFrameDuration; + } + + // Adds a frame to this sequence + inline void AddFrame(const Frame& frame) + { + m_vFrames.emplace_back(frame); + } + + // Returns a Frame Object for a given time into an animation + inline const Frame& GetFrame(const float fTime) const + { + size_t frame = ConvertTimeToFrame(fTime); + return m_vFrames[frame]; + } + + inline const bool Complete(const float fTime) const + { + return std::abs(fTime - (m_vFrames.size() * m_fFrameDuration)) < 0.01f; + } + + private: + Style m_nStyle; + std::vector m_vFrames; + float m_fFrameDuration = 0.1f; + float m_fFrameRate = 10.0f; + + inline const size_t ConvertTimeToFrame(const float fTime) const + { + switch (m_nStyle) + { + case Style::Repeat: + return size_t(fTime * m_fFrameRate) % m_vFrames.size(); + break; + case Style::OneShot: + { + size_t frame = std::clamp(size_t(fTime * m_fFrameRate), size_t(0), m_vFrames.size() - 1); + return frame; + } + break; + case Style::PingPong: + { + // Thanks @sigonasr2 (discord) + size_t frame = size_t(m_fFrameRate * fTime) % (m_vFrames.size() * 2 - 1); + return frame >= m_vFrames.size() ? m_vFrames.size() - frame % m_vFrames.size() - 1 : frame; + } + break; + case Style::Reverse: + return (m_vFrames.size() - 1) - (size_t(fTime * m_fFrameRate) % m_vFrames.size()); + break; + } + + return 0; + } + }; + + // A Animate2D::State is a lightweight token that can be attached to things + // that are animated. Under normal circumstances, it is never updated manually + struct AnimationState + { + private: + size_t nIndex = 0; + float fTime = 0.0f; + bool bComplete = false; + template + friend class Animation; + }; + + // Animation object holds a group of frame sequences and can mutate an AnimationState token + template + class Animation + { + public: + Animation() = default; + + // Change an animation state token to a new state + inline bool ChangeState(AnimationState& state, const StatesEnum& sStateName) const + { + size_t idx = m_mapStateIndices.at(sStateName); + if (state.nIndex != idx) + { + state.fTime = 0.0f; + state.nIndex = idx; + state.bComplete = false; + return true; + } + + return false; + } + + // Update an animation state token + inline void UpdateState(AnimationState& state, const float fElapsedTime) const + { + state.fTime += fElapsedTime; + state.bComplete = m_vSequences[state.nIndex].Complete(state.fTime); + } + + public: + // Retrieve the frame information for a given animation state + inline const Frame& GetFrame(const AnimationState& state) const + { + return m_vSequences[state.nIndex].GetFrame(state.fTime); + } + + public: + // Add a named Frame sequence as a state + inline void AddState(const StatesEnum& sStateName, const FrameSequence& sequence) + { + m_vSequences.emplace_back(sequence); + m_mapStateIndices[sStateName] = m_vSequences.size() - 1; + } + + private: + std::vector m_vSequences; + std::unordered_map m_mapStateIndices; + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h new file mode 100644 index 00000000..b9aed6b7 --- /dev/null +++ b/utilities/olcUTIL3_Camera2D.h @@ -0,0 +1,271 @@ +/* + OneLoneCoder - Camera2D v1.01 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A 2D world camera with various modes + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2025 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, 2020, 2021, 2022, 2023, 2024, 2025 + + Version + ~~~~~~~ + + 1.01 +SlideScreens - Prompted by AtomizerZero + +*/ + +#pragma once + +#include "olcPixelGameEngine.h" + +namespace olc::utils +{ + class Camera2D + { + public: + enum class Mode : uint8_t + { + Simple, // No motion, just directly settable + EdgeMove, // Moves as target crosses boundary + LazyFollow, // Lazily follows the target + FixedScreens, // Moves statically between "screens" + SlideScreens, // MOves statically between "screens" but with a fast transition + }; + + public: + inline Camera2D() : m_pTarget(&m_vLocalTarget) {} + + // Construct a camera with a viewable area size, and an optional starting position + inline Camera2D(const olc::vf2d& vViewSize, const olc::vf2d& vViewPos = { 0.0f, 0.0f }) : m_pTarget(&m_vLocalTarget) + { + m_vViewSize = vViewSize; + m_vViewPos = vViewPos; + } + + // Set the operational mode of this camera + inline void SetMode(const Mode t) + { + m_nMode = t; + } + + // Get the operational mode of this camera + inline Mode GetMode() const + { + return m_nMode; + } + + // Get the position of the target being tracked by this camera + inline const olc::vf2d& GetTarget() const + { + return *m_pTarget; + } + + // Get the position of the cameras focus point + inline const olc::vf2d& GetPosition() const + { + return m_vPosition; + } + + // Get the top left of teh cameras visible area in world space + inline const olc::vf2d& GetViewPosition() const + { + return m_vViewPos; + } + + // Get the camera's visible area + inline const olc::vf2d& GetViewSize() const + { + return m_vViewSize; + } + + // Set tracked point via pointer + inline void SetTarget(olc::vf2d& vTarget) + { + m_pTarget = &vTarget; + } + + // Set tracked point via const ref - {10, 35} for example + inline void SetTarget(const olc::vf2d&& vTarget) + { + m_vLocalTarget = vTarget; + m_pTarget = &m_vLocalTarget; + } + + // Set world boundary rectangle + inline void SetWorldBoundary(const olc::vf2d& vPos, const olc::vf2d& vSize) + { + m_vWorldBoundaryPos = vPos; + m_vWorldBoundarySize = vSize; + } + + // Instruct camera to respect world boundaries + inline void EnableWorldBoundary(const bool bEnable) + { + m_bWorldBoundary = bEnable; + } + + // Are we using a world boundary? + inline bool IsWorldBoundaryEnabled() const + { + return m_bWorldBoundary; + } + + // Get the world boundary rectangle position + inline const olc::vf2d& GetWorldBoundaryPosition() const + { + return m_vWorldBoundaryPos; + } + + // Get the world boundary rectangle size + inline const olc::vf2d& GetWorldBoundarySize() const + { + return m_vWorldBoundarySize; + } + + // Set the velocity at which the lazy follower reaches tracked point + inline void SetLazyFollowRate(const float fRate) + { + m_fLazyFollowRate = fRate; + } + + // Get the velocity at which the lazy follower reaches tracked point + inline float GetLazyFollowRate() const + { + return m_fLazyFollowRate; + } + + // Set distance from tracked point to start nudging screen + inline void SetEdgeTriggerDistance(const olc::vf2d& vEdge) + { + m_vEdgeTriggerDistance = vEdge; + } + + // Return disance from tracked point that screen will nudge + inline const olc::vf2d& GetEdgeTriggerDistance() const + { + return m_vEdgeTriggerDistance; + } + + // Update camera, animating if necessary, obeying world boundary rules + // returns true if target is visible + inline virtual bool Update(const float fElapsedTime) + { + switch (m_nMode) + { + case Mode::Simple: + { + m_vPosition = GetTarget(); + } + break; + + case Mode::EdgeMove: + { + olc::vf2d vOverlap = GetTarget() - m_vPosition; + if (vOverlap.x > m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x - m_vEdgeTriggerDistance.x; + if (vOverlap.x < -m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x + m_vEdgeTriggerDistance.x; + if (vOverlap.y > m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y - m_vEdgeTriggerDistance.y; + if (vOverlap.y < -m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y + m_vEdgeTriggerDistance.y; + } + break; + + case Mode::LazyFollow: + { + m_vPosition += (GetTarget() - m_vPosition) * m_fLazyFollowRate * fElapsedTime; + } + break; + + case Mode::FixedScreens: + { + m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (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); + m_vPosition += (vScreen - m_vPosition) * m_fLazyFollowRate * 2.0f * fElapsedTime; + } + break; + } + + // Make camera target the middle of the view + m_vViewPos = m_vPosition - (m_vViewSize * 0.5f); + + // Clamp to World Boundary (if in place) + if (m_bWorldBoundary) + { + m_vViewPos = m_vViewPos.max(m_vWorldBoundaryPos).min(m_vWorldBoundaryPos + m_vWorldBoundarySize - m_vViewSize); + } + + return GetTarget().x >= m_vViewPos.x && GetTarget().x < (m_vViewPos.x + m_vViewSize.x) && + GetTarget().y >= m_vViewPos.y && GetTarget().y < (m_vViewPos.y + m_vViewSize.y); + } + + protected: + // Position of camera focus point in the world + olc::vf2d m_vPosition; + // Rectangular size of camera viewing area + olc::vf2d m_vViewSize; + // Top left coordinate of camera viewing area + olc::vf2d m_vViewPos; + // Camera movement mode + Mode m_nMode = Mode::Simple; + + // Target Vector2D object camera should follow (either ref or ptr) + olc::vf2d* m_pTarget = nullptr; + olc::vf2d m_vLocalTarget; + + // World Boundary + bool m_bWorldBoundary = false; + olc::vf2d m_vWorldBoundaryPos = { 0.0f, 0.0f }; + olc::vf2d m_vWorldBoundarySize = { 256.0f, 240.0f }; + + // Mode specific + olc::vf2d m_vEdgeTriggerDistance = { 1.0f, 1.0f }; + float m_fLazyFollowRate = 4.0f; + olc::vi2d m_vScreenSize = { 16,15 }; + }; +} \ No newline at end of file diff --git a/utilities/olcUTIL3_Geometry2D.h b/utilities/olcUTIL3_Geometry2D.h new file mode 100644 index 00000000..ec22309e --- /dev/null +++ b/utilities/olcUTIL3_Geometry2D.h @@ -0,0 +1,2583 @@ +/* + OneLoneCoder - Geometry 2D v2.0 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A collection of 2D Geometric primitives and functions to work with + and between them. + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2024 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 + + Authors + ~~~~~~~ + David Barr, aka javidx9, (c) OneLoneCoder 2019, 2020, 2021, 2022, 2023, 2024 + + With significant contributions from: + + Piratux, Gusgo99, Gorbit99, MaGetzUb, Dandistine, Moros1138 + cstdint, sigonasr, bixxy, Qwerasd, starfreakclone, fux + + Changes: + v1.01: +Made constants inline + +Header guards (lol... sigh...) + v2.0: +Major file contribution and restructuring + +*/ + +/* + What Is This? + ~~~~~~~~~~~~~ + + I've worked with 2D stuff for decades and I'm tired of reapeatedly researching, deriving + and implementing geometric analysis routines, so wanted a "one-stop-shop" to collate all + these mathematics. You don't even need olc::PixelGameEngine, this file will run as a + standalone 2D vector/geometry utility. + + As well as a robust 2D Vector implementation, this file offers definitions of the following + shapes: + + point - A 2D (x,y) vector from (0,0) + line - A line segment defined by a start and end point + circle - A circle defined by a middle point and a radius + rectangle - An axis aligned quad defined by a top left point, and a size + triangle - A triangle defined by 3 points + ray - A special case "line" with an origin and a direction + + Functions have been provided that yield useful analyses for almost every combination + of shapes. The function groups all have the same names, and are differentiated via + argument type: + + point closest(a, b) + Returns closest point on Shape A to Shape B + + bool overlaps(a, b) + Returns true if any part of Shape A overlaps any part of Shape B, including boundaries + + bool contains(a, b) + Returns true if Shape A wholly contains Shape B within and including it's boundary + + vector intersects(a, b) + Returns a vector of points where Shape A boundary intersects with Shape B boundary + + optional project(a, b, ray) + Projects Shape A along a ray, until and if it contacts shape B. If it never contacts + then nothing is returned. If it does contact the closest position Shape A can be to + Shape B is returned without the shapes overlapping + + rect envelope_r(a) / bounding_box(a) + Returns the minimum area rectangle that fully encompasses Shape A + + rect envelope_c(a) / bounding_circle(a) + Returns the minimum area circle that fully encompasses Shape A + + ray reflect(ray, a) + Returns a ray that is a reflection of supplied incident ray against Shape A + + optional collision(ray, a) + Returns the point and normal where a ray collides with Shape A + +*/ + +/* + Quick Navigation + ~~~~~~~~~~~~~~~~ + + To jump to an implementation quickly, use your editor to search in the following way: + + f(a, b) + + where: + + f = overlaps, intersects, contains, closest, envelope_r, envelope_b, reflects, collision + a = p, l, r, c, t, q, pol (point, line, rect, circ, triangle, ray, polygon) + + example: + + "contains(r,c)" - takes you to implementation for contains(rect, circ) + - Does the rectangle contain the circle? +*/ + +/* + Function Matrix - Function(A, B) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A B>| POINT | LINE | RECT | CIRCLE | TRIANGLE | RAY | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + POINT | contains | contains | contains | contains | contains | | + | closest | closest | closest | closest | closest | closest | + | overlaps | overlaps | overlaps | overlaps | overlaps | | + | intersects | intersects | intersects | intersects | intersects | | + | | | | | | | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + LINE | contains | contains | contains | contains | contains | | + | closest | | | closest | | | + | overlaps | overlaps | overlaps | overlaps | overlaps | | + | intersects | intersects | intersects | intersects | intersects | | + | | | | | | | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + RECT | contains | contains | contains | contains | contains | | + | closest | | | | | | + | overlaps | overlaps | overlaps | overlaps | overlaps | | + | intersects | intersects | intersects | intersects | intersects | | + | | | | | | | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + CIRCLE | contains | contains | contains | contains | contains | | + | closest | closest | | | | | + | overlaps | overlaps | overlaps | overlaps | overlaps | | + | intersects | intersects | intersects | intersects | intersects | | + | project | project | | project | | | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + TRIANGLE | contains | contains | contains | contains | contains | | + | closest | | | | | | + | overlaps | overlaps | overlaps | overlaps | overlaps | | + | intersects | intersects | intersects | intersects | intersects | | + | | | | | | | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + RAY | contains | | | | | | + | | | | | | | + | | collision | collision | collision | collision | collision* | + | | intersects | intersects | intersects | intersects | intersects | + | | reflect | reflect | reflect | reflect | reflect* | + ---------+--------------+--------------+--------------+--------------+--------------+--------------+ + + * Exists but always fails +*/ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef PGE_VER +#error "olcUTIL_Geometry2D.h must be included BEFORE olcPixelGameEngine.h" +#else + + +#if !defined(PGE_VECTOR2D_DECLARED) +namespace olc +{ + template + concept numeric = std::integral || std::floating_point; + + + /* + A complete 2D geometric vector structure, with a variety + of useful utility functions and operator overloads + */ + template + struct v_2d + { + static_assert(std::is_arithmetic::value, "olc::v_2d must be numeric"); + + union + { +#pragma warning(disable:4201) // Top MSVC whinging about anonymous structs + struct + { + // x-axis component + T x; + // y-axis component + T y; + }; +#pragma warning(default:4201) + + std::array xy = { {0,0} }; + }; + + // Default constructor + inline constexpr v_2d() = default; + + // Specific constructor + inline constexpr v_2d(T _x, T _y) : x(_x), y(_y) + { + } + + // Copy constructor + inline constexpr v_2d(const v_2d& v) = default; + + // Assignment operator + inline constexpr v_2d& operator=(const v_2d& v) = default; + + + inline constexpr std::array a() const + { + return xy; + } + + // Returns rectangular area of vector + inline constexpr auto area() const + { + return x * y; + } + + // Returns magnitude of vector + inline constexpr auto mag() const + { + return std::sqrt(x * x + y * y); + } + + // Returns magnitude squared of vector (useful for fast comparisons) + inline constexpr T mag2() const + { + return x * x + y * y; + } + + // Returns normalised version of vector + inline constexpr v_2d norm() const + { + auto r = 1 / mag(); + return v_2d(x * r, y * r); + } + + // Returns vector at 90 degrees to this one + inline constexpr v_2d perp() const + { + return v_2d(-y, x); + } + + // Rounds both components down + inline constexpr v_2d floor() const + { + return v_2d(std::floor(x), std::floor(y)); + } + + // Rounds both components accurately + inline constexpr v_2d round() const + { + return v_2d(std::round(x), std::round(y)); + } + + // Rounds both components up + inline constexpr v_2d ceil() const + { + return v_2d(std::ceil(x), std::ceil(y)); + } + + // Returns 'element-wise' max of this and another vector + inline constexpr v_2d max(const v_2d& v) const + { + return v_2d(std::max(x, v.x), std::max(y, v.y)); + } + + // Returns 'element-wise' min of this and another vector + inline constexpr v_2d min(const v_2d& v) const + { + return v_2d(std::min(x, v.x), std::min(y, v.y)); + } + + // Returns 'element-wise' abs of this vector + inline constexpr v_2d abs() const + { + return v_2d(std::abs(x), std::abs(y)); + } + + // Calculates scalar dot product between this and another vector + inline constexpr auto dot(const v_2d& rhs) const + { + return this->x * rhs.x + this->y * rhs.y; + } + + // Calculates 'scalar' cross product between this and another vector (useful for winding orders) + inline constexpr auto cross(const v_2d& rhs) const + { + return this->x * rhs.y - this->y * rhs.x; + } + + // Treat this as polar coordinate (R, Theta), return cartesian equivalent (X, Y) + inline constexpr v_2d cart() const + { + return v_2d(std::cos(y) * x, std::sin(y) * x); + } + + // Treat this as cartesian coordinate (X, Y), return polar equivalent (R, Theta) + inline constexpr v_2d polar() const + { + return v_2d(mag(), std::atan2(y, x)); + } + + // Clamp the components of this vector in between the 'element-wise' minimum and maximum of 2 other vectors + inline constexpr v_2d clamp(const v_2d& v1, const v_2d& v2) const + { + return this->max(v1).min(v2); + } + + // Linearly interpolate between this vector, and another vector, given normalised parameter 't' + inline constexpr v_2d lerp(const v_2d& v1, const double t) const + { + return (*this) * (T(1.0 - t)) + (v1 * T(t)); + } + + // Compare if this vector is numerically equal to another + inline constexpr bool operator == (const v_2d& rhs) const + { + return (this->x == rhs.x && this->y == rhs.y); + } + + // Compare if this vector is not numerically equal to another + inline constexpr bool operator != (const v_2d& rhs) const + { + return (this->x != rhs.x || this->y != rhs.y); + } + + // Return this vector as a std::string, of the form "(x,y)" + inline std::string str() const + { + return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + ")"; + } + + // Assuming this vector is incident, given a normal, return the reflection + inline constexpr v_2d reflect(const v_2d& n) const + { + return (*this) - 2.0 * (this->dot(n) * n); + } + + // Allow 'casting' from other v_2d types + template + inline constexpr operator v_2d() const + { + return { static_cast(this->x), static_cast(this->y) }; + } + }; + + // Multiplication operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator * (const TL& lhs, const v_2d& rhs) + { + return v_2d(lhs * rhs.x, lhs * rhs.y); + } + + template + inline constexpr auto operator * (const v_2d& lhs, const TR& rhs) + { + return v_2d(lhs.x * rhs, lhs.y * rhs); + } + + template + inline constexpr auto operator * (const v_2d& lhs, const v_2d& rhs) + { + return v_2d(lhs.x * rhs.x, lhs.y * rhs.y); + } + + template + inline constexpr auto operator *= (v_2d& lhs, const TR& rhs) + { + lhs = lhs * rhs; + return lhs; + } + + // Division operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator / (const TL& lhs, const v_2d& rhs) + { + return v_2d(lhs / rhs.x, lhs / rhs.y); + } + + template + inline constexpr auto operator / (const v_2d& lhs, const TR& rhs) + { + return v_2d(lhs.x / rhs, lhs.y / rhs); + } + + template + inline constexpr auto operator / (const v_2d& lhs, const v_2d& rhs) + { + return v_2d(lhs.x / rhs.x, lhs.y / rhs.y); + } + + template + inline constexpr auto operator /= (v_2d& lhs, const TR& rhs) + { + lhs = lhs / rhs; + return lhs; + } + + // Unary Addition operator (pointless but i like the platinum trophies) + template + inline constexpr auto operator + (const v_2d& lhs) + { + return v_2d(+lhs.x, +lhs.y); + } + + // Addition operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator + (const TL& lhs, const v_2d& rhs) + { + return v_2d(lhs + rhs.x, lhs + rhs.y); + } + + template + inline constexpr auto operator + (const v_2d& lhs, const TR& rhs) + { + return v_2d(lhs.x + rhs, lhs.y + rhs); + } + + template + inline constexpr auto operator + (const v_2d& lhs, const v_2d& rhs) + { + return v_2d(lhs.x + rhs.x, lhs.y + rhs.y); + } + + template + inline constexpr auto operator += (v_2d& lhs, const TR& rhs) + { + lhs = lhs + rhs; + return lhs; + } + + template + inline constexpr auto operator += (v_2d& lhs, const v_2d& rhs) + { + lhs = lhs + rhs; + return lhs; + } + + // Unary negation operator overoad for inverting a vector + template + inline constexpr auto operator - (const v_2d& lhs) + { + return v_2d(-lhs.x, -lhs.y); + } + + // Subtraction operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator - (const TL& lhs, const v_2d& rhs) + { + return v_2d(lhs - rhs.x, lhs - rhs.y); + } + + template + inline constexpr auto operator - (const v_2d& lhs, const TR& rhs) + { + return v_2d(lhs.x - rhs, lhs.y - rhs); + } + + template + inline constexpr auto operator - (const v_2d& lhs, const v_2d& rhs) + { + return v_2d(lhs.x - rhs.x, lhs.y - rhs.y); + } + + template + inline constexpr auto operator -= (v_2d& lhs, const TR& rhs) + { + lhs = lhs - rhs; + return lhs; + } + + // Greater/Less-Than Operator overloads - mathematically useless, but handy for "sorted" container storage + template + inline constexpr bool operator < (const v_2d& lhs, const v_2d& rhs) + { + return (lhs.y < rhs.y) || (lhs.y == rhs.y && lhs.x < rhs.x); + } + + template + inline constexpr bool operator > (const v_2d& lhs, const v_2d& rhs) + { + return (lhs.y > rhs.y) || (lhs.y == rhs.y && lhs.x > rhs.x); + } + + // Allow olc::v_2d to play nicely with std::cout + template + inline std::ostream& operator << (std::ostream& os, const v_2d& rhs) + { + os << rhs.str(); + return os; + } + + // Convenient types ready-to-go + typedef v_2d vi2d; + typedef v_2d vu2d; + typedef v_2d vf2d; + typedef v_2d vd2d; +} +#define PGE_VECTOR2D_DECLARED 1 +#endif + + + +namespace olc::utils::geom2d +{ + // Lemon Meringue + inline const double pi = 3.141592653589793238462643383279502884; + + // Floating point error margin + inline const double epsilon = 0.001; + + namespace internal + { + template + inline std::vector> filter_duplicate_points(const std::vector>& points) { + std::vector> filtered_points; + + for (const auto& point : points) + { + bool is_duplicate = false; + + for (const auto& filtered_point : filtered_points) + { + if (std::abs(point.x - filtered_point.x) < epsilon && std::abs(point.y - filtered_point.y) < epsilon) + { + is_duplicate = true; + break; + } + } + + if (!is_duplicate) + { + filtered_points.push_back(point); + } + } + + return filtered_points; + } + }; + + //https://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c + template + constexpr int sgn(T val) { return (T(0) < val) - (val < T(0)); } + + // Defines a line segment + template + struct line + { + olc::v_2d start; + olc::v_2d end; + + inline line(const olc::v_2d& s = { T(0), T(0) }, + const olc::v_2d& e = { T(0), T(0) }) + : start(s), end(e) + { } + + // Get vector pointing from start to end + inline constexpr olc::v_2d vector() const + { + return (end - start); + } + + // Get length of line + inline constexpr T length() + { + return vector().mag(); + } + + // Get length of line^2 + inline constexpr T length2() + { + return vector().mag2(); + } + + + + // Given a real distance, get point along line + inline constexpr olc::v_2d rpoint(const T& distance) const + { + return start + vector().norm() * distance; + } + + // Given a unit distance, get point along line + inline constexpr olc::v_2d upoint(const T& distance) const + { + return start + vector() * distance; + } + + // Return which side of the line does a point lie + inline constexpr int32_t side(const olc::v_2d& point) const + { + double d = vector().cross(point - start); + if (d < 0) + return -1; + else + if (d > 0) + return 1; + else + return 0; + } + + // Returns line equation "mx + a" coefficients where: + // x: m + // y: a + // NOTE: Returns {inf, inf} if std::abs(end.x - start.x) < epsilon: + inline constexpr olc::vd2d coefficients() const + { + double x1 = start.x; + double x2 = end.x; + double y1 = start.y; + double y2 = end.y; + + // check if line is vertical or close to vertical + if (std::abs(x2 - x1) < epsilon) { + return olc::vd2d{ std::numeric_limits::infinity(), std::numeric_limits::infinity() }; + } + + double m = (y2 - y1) / (x2 - x1); + return olc::vd2d{ m, -m * x1 + y1 }; + } + }; + + template + struct ray + { + olc::v_2d origin; + olc::v_2d direction; + + inline ray(const olc::v_2d& o = { T(0), T(0) }, + const olc::v_2d& d = { T(0), T(0) }) + : origin(o), direction(d) + { } + }; + + template + struct rect + { + olc::v_2d pos; + olc::v_2d size; + + inline rect(const olc::v_2d& p = { T(0), T(0) }, + const olc::v_2d& s = { T(1), T(1) }) + : pos(p), size(s) + { } + + inline olc::v_2d middle() const + { + return pos + (size * double(0.5)); + } + + // Get line segment from top side of rectangle + inline line top() const + { + return { pos, {pos.x + size.x, pos.y } }; + } + + // Get line segment from bottom side of rectangle + inline line bottom() const + { + return { {pos.x, pos.y + size.y}, pos + size }; + } + + // Get line segment from left side of rectangle + inline line left() const + { + return { pos, {pos.x, pos.y + size.y} }; + } + + // Get line segment from right side of rectangle + inline line right() const + { + return { {pos.x + size.x, pos.y }, pos + size }; + } + + // Get a line from an indexed side, starting top, going clockwise + inline line side(const size_t i) const + { + if ((i & 0b11) == 0) return top(); + if ((i & 0b11) == 1) return right(); + if ((i & 0b11) == 2) return bottom(); + //if ((i & 0b11) == 3) return left(); // Dumb compilers cant fathom this + return left(); + } + + // Get area of rectangle + inline constexpr T area() const + { + return size.x * size.y; + } + + // Get perimeter of rectangle + inline constexpr T perimeter() const + { + return T(2) * (size.x + size.y); + } + + // Returns side count: 4 + inline constexpr size_t side_count() const { + return 4; + } + }; + + + template + struct circle + { + olc::v_2d pos; + T radius = T(0); + + inline circle(const olc::v_2d& p = { T(0), T(0) }, const T r = T(0)) + : pos(p), radius(r) + { } + + // Get area of circle + inline constexpr T area() const + { + return T(pi) * radius * radius; + } + + // Get circumference of circle + inline constexpr T perimeter() const + { + return T(2.0 * pi) * radius; + } + + // Get circumference of circle + inline constexpr T circumference() const + { + return perimeter(); + } + }; + + + template + struct triangle + { + std::array, 3> pos; + + inline triangle( + const olc::v_2d& p0 = { T(0), T(0) }, + const olc::v_2d& p1 = { T(0), T(0) }, + const olc::v_2d& p2 = { T(0), T(0) }) + : pos{ p0,p1,p2 } + { } + + // Get a line from an indexed side, starting top, going clockwise + inline line side(const size_t i) const + { + return line(pos[i % 3], pos[(i + 1) % 3]); + } + + // Get area of triangle + inline constexpr T area() const + { + return double(0.5) * std::abs( + (pos[0].x * (pos[1].y - pos[2].y)) + + (pos[1].x * (pos[2].y - pos[0].y)) + + (pos[2].x * (pos[0].y - pos[1].y))); + } + + // Get perimeter of triangle + inline constexpr T perimeter() const + { + return line(pos[0], pos[1]).length() + + line(pos[1], pos[2]).length() + + line(pos[2], pos[0]).length(); + } + + // Returns side count: 3 + inline constexpr size_t side_count() const { + return 3; + } + }; + + + template + struct polygon + { + std::vector> pos; + }; + + + // ========================================================================================================================= + // Closest(shape, point) =================================================================================================== + + // Closest location on [SHAPE] to Point + + // closest(p,p) + // Returns closest point on point to any shape (aka the original point) :P + template + inline olc::v_2d closest(const olc::v_2d& p, [[maybe_unused]] const T2& anything_who_cares) + { + return p; + } + + // closest(l,p) + // Returns closest point on line to point + template + inline olc::v_2d closest(const line& l, const olc::v_2d& p) + { + auto d = l.vector(); + double u = std::clamp(double(d.dot(p - l.start)) / d.mag2(), 0.0, 1.0); + return l.start + u * d; + } + + // closest(c,p) + // Returns closest point on circle to point + template + inline olc::v_2d closest(const circle& c, const olc::v_2d& p) + { + return c.pos + olc::vd2d(p - c.pos).norm() * c.radius; + } + + // closest(r,p) + // Returns closest point on rectangle to point + template + inline olc::v_2d closest(const rect& r, const olc::v_2d& p) + { + // Note: this algorithm can be reused for polygon + auto c1 = closest(r.top(), p); + auto c2 = closest(r.bottom(), p); + auto c3 = closest(r.left(), p); + auto c4 = closest(r.right(), p); + + auto d1 = (c1 - p).mag2(); + auto d2 = (c2 - p).mag2(); + auto d3 = (c3 - p).mag2(); + auto d4 = (c4 - p).mag2(); + + auto dmin = d1; + auto cmin = c1; + + if (d2 < dmin) { + dmin = d2; + cmin = c2; + } + if (d3 < dmin) { + dmin = d3; + cmin = c3; + } + if (d4 < dmin) { + dmin = d4; + cmin = c4; + } + + return cmin; + } + + // closest(t,p) + // Returns closest point on triangle to point + template + inline olc::v_2d closest(const triangle& t, const olc::v_2d& p) + { + olc::utils::geom2d::line l{ t.pos[0], t.pos[1] }; + auto p0 = closest(l, p); + auto d0 = (p0 - p).mag2(); + + l.end = t.pos[2]; + auto p1 = closest(l, p); + auto d1 = (p1 - p).mag2(); + + l.start = t.pos[1]; + auto p2 = closest(l, p); + auto d2 = (p2 - p).mag2(); + + if ((d0 <= d1) && (d0 <= d2)) { + return p0; + } + else if ((d1 <= d0) && (d1 <= d2)) { + return p1; + } + else { + return p2; + } + } + + // closest(ray,p) + // Returns closest point on ray to point + template + inline olc::v_2d closest(const ray& r, const olc::v_2d& p) + { + // TODO: implement + return p; + } + + + // Closest location on [SHAPE] to Line + + // closest(l,l) + // Returns closest point on line to line + template + inline olc::v_2d closest(const line& l1, const line& l2) + { + // TODO: + return {}; + } + + // closest(r,l) + // Returns closest point on rectangle to line + template + inline olc::v_2d closest(const rect& r, const line& l) + { + // TODO: + return {}; + } + + // closest(c,l) + // Returns closest point on circle to line + template + inline olc::v_2d closest(const circle& c, const line& l) + { + const auto p1 = closest(l, c.pos); + return c.pos + olc::vd2d(p1 - c.pos).norm() * c.radius; + } + + // closest(t,l) + // Returns closest point on triangle to line + template + inline olc::v_2d closest(const triangle& t, const line& l) + { + // TODO: + return {}; + } + + + // Closest location on [SHAPE] to Circle + + // closest(l,c) + // Returns closest point on line to circle + template + inline olc::v_2d closest(const line& l, const circle& c) + { + const auto p1 = closest(c, l); // Closest point on circle to line + return closest(l, p1); + } + + // closest(r,c) + // Returns closest point on rectangle to circle + template + inline olc::v_2d closest(const rect& r, const circle& l) + { + // TODO: + return {}; + } + + // closest(c,c) + // Returns closest point on circle to circle + template + inline olc::v_2d closest(const circle& c1, const circle& c2) + { + return closest(c1, c2.pos); + } + + // closest(t,c) + // Returns closest point on triangle to circle + template + inline olc::v_2d closest(const triangle& r, const circle& l) + { + // TODO: + return {}; + } + + + // Closest location on [SHAPE] to Triangle + + // closest(l,t) + // Returns closest point on line to triangle + template + inline olc::v_2d closest(const line& l, const triangle& c) + { + // TODO: + return {}; + } + + // closest(r,t) + // Returns closest point on rectangle to triangle + template + inline olc::v_2d closest(const rect& r, const triangle& l) + { + // TODO: + return {}; + } + + // closest(c,t) + // Returns closest point on circle to triangle + template + inline olc::v_2d closest(const circle& c, const triangle& l) + { + // TODO: + return {}; + } + + // closest(t,t) + // Returns closest point on triangle to triangle + template + inline olc::v_2d closest(const triangle& r, const triangle& l) + { + // TODO: + return {}; + } + + + + + + // ================================================================================================================ + // POINT ========================================================================================================== + + // contains(p,p) + // Checks if point contains point + template + inline constexpr bool contains(const olc::v_2d& p1, const olc::v_2d& p2) + { + return (p1 - p2).mag2() < epsilon; + } + + // contains(l,p) + // Checks if line contains point + template + inline constexpr bool contains(const line& l, const olc::v_2d& p) + { + double d = ((p.x - l.start.x) * (l.end.y - l.start.y) - (p.y - l.start.y) * (l.end.x - l.start.x)); + if (std::abs(d) < epsilon) + { + // point is on line + double u = l.vector().dot(p - l.start) / l.vector().mag2(); + return (u >= double(0.0) && u <= double(1.0)); + } + + return false; + } + + // contains(r,p) + // Checks if rectangle contains point + template + inline constexpr bool contains(const rect& r, const olc::v_2d& p) + { + return !(p.x < r.pos.x || p.y < r.pos.y || + p.x >(r.pos.x + r.size.x) || p.y >(r.pos.y + r.size.y)); + } + + // contains(c,p) + // Checks if circle contains a point + template + inline constexpr bool contains(const circle& c, const olc::v_2d& p) + { + return (c.pos - p).mag2() <= (c.radius * c.radius); + } + + // contains(t,p) + // Checks if triangle contains a point + template + inline constexpr bool contains(const triangle& t, const olc::v_2d& p) + { + // http://jsfiddle.net/PerroAZUL/zdaY8/1/ + T2 A = T2(0.5) * (-t.pos[1].y * t.pos[2].x + t.pos[0].y * (-t.pos[1].x + t.pos[2].x) + t.pos[0].x * (t.pos[1].y - t.pos[2].y) + t.pos[1].x * t.pos[2].y); + T2 sign = A < T2(0) ? T2(-1) : T2(1); + T2 s = (t.pos[0].y * t.pos[2].x - t.pos[0].x * t.pos[2].y + (t.pos[2].y - t.pos[0].y) * p.x + (t.pos[0].x - t.pos[2].x) * p.y) * sign; + T2 v = (t.pos[0].x * t.pos[1].y - t.pos[0].y * t.pos[1].x + (t.pos[0].y - t.pos[1].y) * p.x + (t.pos[1].x - t.pos[0].x) * p.y) * sign; + return s >= T2(0) && v >= T2(0) && (s + v) <= T2(2) * A * sign; + } + + template + inline constexpr bool contains(const ray& r, const olc::v_2d& p) + { + // Calculate the vector from the ray's origin to point p + olc::v_2d op = p - r.origin; + + // Calculate the dot product between op and the ray's direction + // This checks if p is in the direction of the ray and not behind the origin + T2 dotProduct = op.dot(r.direction); + + if (dotProduct < 0) { + // p is behind the ray's origin + return false; + } + + // Project op onto the ray's direction (which is already normalized) + olc::v_2d projection = { r.direction.x * dotProduct, r.direction.y * dotProduct }; + + // Check if the projection of op onto the ray's direction is equivalent to op + // This is true if p lies on the ray + + T2 distance = std::sqrt((projection.x - op.x) * (projection.x - op.x) + (projection.y - op.y) * (projection.y - op.y)); + + // Assuming a small threshold for floating point arithmetic issues + return distance < epsilon; + } + + // overlaps(p,p) + // Check if point overlaps with point (analogous to contains()) + template + inline constexpr bool overlaps(const olc::v_2d& p1, const olc::v_2d& p2) + { + return contains(p1, p2); + } + + // overlaps(l,p) + // Checks if line segment overlaps with point + template + inline constexpr bool overlaps(const line& l, const olc::v_2d& p) + { + return contains(l, p); + } + + // overlaps(r,p) + // Checks if rectangle overlaps with point + template + inline constexpr bool overlaps(const rect& r, const olc::v_2d& p) + { + return contains(r, p); + } + + // overlaps(c,p) + // Checks if circle overlaps with point + template + inline constexpr bool overlaps(const circle& c, const olc::v_2d& p) + { + return contains(c, p); + } + + // overlaps(t,p) + // Checks if triangle overlaps with point + template + inline constexpr bool overlaps(const triangle& t, const olc::v_2d& p) + { + return contains(t, p); + } + + + + + // intersects(p,p) + // Get intersection points where point intersects with point + template + inline std::vector> intersects(const olc::v_2d& p1, const olc::v_2d& p2) + { + if (contains(p1, p2)) + return { p1 }; + + return {}; + } + + // intersects(l,p) + // Get intersection points where line segment intersects with point + template + inline std::vector> intersects(const line& l, const olc::v_2d& p) + { + if (contains(l, p)) + return { p }; + + return {}; + } + + // intersects(r,p) + // Get intersection points where rectangle intersects with point + template + inline std::vector> intersects(const rect& r, const olc::v_2d& p) + { + for (size_t i = 0; i < r.side_count(); i++) + if (contains(r.side(i), p)) + return { p }; + + return {}; + } + + // intersects(c,p) + // Get intersection points where circle intersects with point + template + inline std::vector> intersects(const circle& c, const olc::v_2d& p) + { + if (std::abs((p - c.pos).mag2() - (c.radius * c.radius)) <= epsilon) + return { p }; + + return {}; + } + + // intersects(t,p) + // Get intersection points where triangle intersects with point + template + inline std::vector> intersects(const triangle& t, const olc::v_2d& p) + { + for (size_t i = 0; i < t.side_count(); i++) + if (contains(t.side(i), p)) + return { p }; + + return {}; + + } + + + + + + + + + + + + + // ================================================================================================================ + // LINE =========================================================================================================== + + // contains(p,l) + // Check if point contains line segment + template + inline constexpr bool contains([[maybe_unused]] const olc::v_2d& p, [[maybe_unused]] const line& l) + { + return false; // It can't! + } + + // contains(l,l) + // Check if line segment contains line segment + template + inline constexpr bool contains(const line& l1, const line& l2) + { + return overlaps(l1, l2.start) && overlaps(l1, l2.end); + } + + // contains(r,l) + // Check if rectangle contains line segment + template + inline constexpr bool contains(const rect& r, const line& l) + { + return contains(r, l.start) && contains(r, l.end); + } + + // contains(c,l) + // Check if circle contains line segment + template + inline constexpr bool contains(const circle& c1, const line& l) + { + return contains(c1, l.start) && contains(c1, l.end); + } + + // contains(t,l) + // Check if triangle contains line segment + template + inline constexpr bool contains(const triangle& t, const line& l) + { + return contains(t, l.start) && contains(t, l.end); + } + + + + // overlaps(p,l) + // Check if point overlaps line segment + template + inline constexpr bool overlaps(const olc::v_2d& p, const line& l) + { + return contains(l, p); + } + + // overlaps(l,l) + // Check if line segment overlaps line segment + template + inline constexpr bool overlaps(const line& l1, const line& l2) + { + double D = ((l2.end.y - l2.start.y) * (l1.end.x - l1.start.x) - (l2.end.x - l2.start.x) * (l1.end.y - l1.start.y)); + double uA = ((l2.end.x - l2.start.x) * (l1.start.y - l2.start.y) - (l2.end.y - l2.start.y) * (l1.start.x - l2.start.x)) / D; + double uB = ((l1.end.x - l1.start.x) * (l1.start.y - l2.start.y) - (l1.end.y - l1.start.y) * (l1.start.x - l2.start.x)) / D; + return uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1; + } + + // overlaps(r,l) + // Check if rectangle overlaps line segment + template + inline constexpr bool overlaps(const rect& r, const line& l) + { + return contains(r, l.start) + || overlaps(r.top(), l) + || overlaps(r.bottom(), l) + || overlaps(r.left(), l) + || overlaps(r.right(), l); + } + + // overlaps(c,l) + // Check if circle overlaps line segment + template + inline constexpr bool overlaps(const circle& c, const line& l) + { + auto vClosest = closest(l, c.pos); + return ((c.pos - vClosest).mag2() <= (c.radius * c.radius)); + } + + // overlaps(t,l) + // Check if triangle overlaps line segment + template + inline constexpr bool overlaps(const triangle& t, const line& l) + { + return overlaps(t, l.start) || overlaps(t.side(0), l) || overlaps(t.side(1), l) || overlaps(t.side(2), l); + } + + + + // intersects(p,l) + // Get intersection points where point intersects with line segment + template + inline std::vector> intersects(const olc::v_2d& p, const line& l) + { + return intersects(l, p); + } + + // intersects(l,l) + // Get intersection points where line segment intersects with line segment + template + inline std::vector> intersects(const line& l1, const line& l2, bool infinite = false) + { + if (infinite) + { + float rd = l1.vector().cross(l2.vector()); + if (rd == 0) return {}; // Parallel or Colinear TODO: Return two points + + //Inverse rd product + rd = 1.f / rd; + + //Cross products: + //rn = (b1b2 x b1a1) + float rn = ((l2.end.x - l2.start.x) * (l1.start.y - l2.start.y) - (l2.end.y - l2.start.y) * (l1.start.x - l2.start.x)) * rd; + //sn = (a1a2 x b1a1) + float sn = ((l1.end.x - l1.start.x) * (l1.start.y - l2.start.y) - (l1.end.y - l1.start.y) * (l1.start.x - l2.start.x)) * rd; + + //Return the intersection depth + //if (d) *d = rn; + + return { l1.start + rn * l1.vector() }; + } + else + { + float rd = l1.vector().cross(l2.vector()); + if (rd == 0) return {}; // Parallel or Colinear TODO: Return two points + + //Inverse rd product + rd = 1.f / rd; + + //Cross products: + //rn = (b1b2 x b1a1) + float rn = ((l2.end.x - l2.start.x) * (l1.start.y - l2.start.y) - (l2.end.y - l2.start.y) * (l1.start.x - l2.start.x)) * rd; + //sn = (a1a2 x b1a1) + float sn = ((l1.end.x - l1.start.x) * (l1.start.y - l2.start.y) - (l1.end.y - l1.start.y) * (l1.start.x - l2.start.x)) * rd; + + //Return the intersection depth + //if (d) *d = rn; + + if (rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f) + return {}; // Intersection not within line segment + + return { l1.start + rn * l1.vector() }; + } + } + + // intersects(r,l) + // Get intersection points where rectangle intersects with line segment + template + inline std::vector> intersects(const rect& r, const line& l) + { + std::vector> intersections; + + for (size_t i = 0; i < r.side_count(); i++) + { + auto v = intersects(r.side(i), l); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + // intersects(c,l) + // Get intersection points where circle intersects with line segment + template + inline std::vector> intersects(const circle& c, const line& l) + { + const auto closestPointToSegment = closest(l, c.pos); + if (!overlaps(c, closestPointToSegment)) + { + // Circle is too far away + return {}; + } + + // Compute point closest to the circle on the line + const auto d = l.vector(); + const auto uLine = d.dot(c.pos - l.start) / d.mag2(); + const auto closestPointToLine = l.start + uLine * d; + const auto distToLine = (c.pos - closestPointToLine).mag2(); + + if (std::abs(distToLine - c.radius * c.radius) < epsilon) + { + // Circle "kisses" the line + return { closestPointToLine }; + } + + // Circle intersects the line + const auto length = std::sqrt(c.radius * c.radius - distToLine); + const auto p1 = closestPointToLine + l.vector().norm() * length; + const auto p2 = closestPointToLine - l.vector().norm() * length; + + std::vector> intersections; + intersections.reserve(2); + + if ((p1 - closest(l, p1)).mag2() < epsilon * epsilon) + intersections.push_back(p1); + if ((p2 - closest(l, p2)).mag2() < epsilon * epsilon) + intersections.push_back(p2); + + return internal::filter_duplicate_points(intersections); + } + + // intersects(t,l) + // Get intersection points where triangle intersects with line segment + template + inline std::vector> intersects(const triangle& t, const line& l) + { + std::vector> intersections; + + for (size_t i = 0; i < t.side_count(); i++) + { + auto v = intersects(t.side(i), l); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + + + + + + + + + + + + // ================================================================================================================ + // RECTANGLE ====================================================================================================== + + // contains(p,r) + // Check if point contains rectangle + template + inline constexpr bool contains([[maybe_unused]] const olc::v_2d& p, [[maybe_unused]] const rect& r) + { + return false; // It can't! + } + + // contains(l,r) + // Check if line segment contains rectangle + template + inline constexpr bool contains([[maybe_unused]] const line& l, [[maybe_unused]] const rect& r) + { + return false; // It can't + } + + // contains(r,r) + // Check if rectangle contains rectangle + template + inline constexpr bool contains(const rect& r1, const rect& r2) + { + return (r2.pos.x >= r1.pos.x) && (r2.pos.x + r2.size.x <= r1.pos.x + r1.size.x) && + (r2.pos.y >= r1.pos.y) && (r2.pos.y + r2.size.y <= r1.pos.y + r1.size.y); + } + + // contains(c,r) + // Check if circle contains rectangle + template + inline constexpr bool contains(const circle& c, const rect& r) + { + return contains(c, r.pos) + && contains(c, olc::v_2d{ r.pos.x + r.size.x, r.pos.y }) + && contains(c, olc::v_2d{ r.pos.x, r.pos.y + r.size.y }) + && contains(c, r.pos + r.size); + } + + // contains(t,r) + // Check if triangle contains rectangle + template + inline constexpr bool contains(const triangle& t, const rect& r) + { + return contains(t, r.pos) + && contains(t, r.pos + r.size) + && contains(t, olc::v_2d{ r.pos.x + r.size.x, r.pos.y }) + && contains(t, olc::v_2d{ r.pos.x, r.pos.y + r.size.y }); + } + + + + // overlaps(p,r) + // Check if point overlaps rectangle + template + inline constexpr bool overlaps(const olc::v_2d& p, const rect& r) + { + return overlaps(r, p); + } + + // overlaps(l,r) + // Check if line segment overlaps rectangle + template + inline constexpr bool overlaps(const line& l, const rect& r) + { + return overlaps(r, l); + } + + // overlaps(r,r) + // Check if rectangle overlaps rectangle + template + inline constexpr bool overlaps(const rect& r1, const rect& r2) + { + return (r1.pos.x <= r2.pos.x + r2.size.x && r1.pos.x + r1.size.x >= r2.pos.x && + r1.pos.y <= r2.pos.y + r2.size.y && r1.pos.y + r1.size.y >= r2.pos.y); + } + + // overlaps(c,r) + // Check if circle overlaps rectangle + template + inline constexpr bool overlaps(const circle& c, const rect& r) + { + // Inspired by this (very clever btw) + // https://stackoverflow.com/questions/45370692/circle-rectangle-collision-response + // But modified to work :P + double overlap = (olc::v_2d{ std::clamp(c.pos.x, r.pos.x, r.pos.x + r.size.x), std::clamp(c.pos.y, r.pos.y, r.pos.y + r.size.y) } - c.pos).mag2(); + if (std::isnan(overlap)) overlap = 0; + return (overlap - (c.radius * c.radius)) < 0; + } + + // overlaps(t,r) + // Check if triangle overlaps rectangle + template + inline constexpr bool overlaps(const triangle& t, const rect& r) + { + return overlaps(t, r.top()) + || overlaps(t, r.bottom()) + || overlaps(t, r.left()) + || overlaps(t, r.right()) + || contains(r, t.pos[0]); + } + + + + // intersects(p,r) + // Get intersection points where point intersects with rectangle + template + inline std::vector> intersects(const olc::v_2d& p, const rect& r) + { + return intersects(r, p); + } + + // intersects(l,r) + // Get intersection points where line segment intersects with rectangle + template + inline std::vector> intersects(const line& l, const rect& r) + { + return intersects(r, l); + } + + // intersects(r,r) + // Get intersection points where rectangle intersects with rectangle + template + inline std::vector> intersects(const rect& r1, const rect& r2) + { + std::vector> intersections; + + for (size_t i = 0; i < r2.side_count(); i++) { + auto v = intersects(r1, r2.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + // intersects(c,r) + // Get intersection points where circle intersects with rectangle + template + inline std::vector> intersects(const circle& c, const rect& r) + { + std::vector> intersections; + + for (size_t i = 0; i < r.side_count(); i++) + { + auto v = intersects(c, r.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + // intersects(t,r) + // Get intersection points where triangle intersects with rectangle + template + inline std::vector> intersects(const triangle& t, const rect& r) + { + std::vector> intersections; + + for (size_t i = 0; i < r.side_count(); i++) { + auto v = intersects(t, r.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + + + + + + + + + + + + + // ================================================================================================================ + // CIRCLE ========================================================================================================= + + // contains(p,c) + // Check if point contains circle + template + inline constexpr bool contains([[maybe_unused]] const olc::v_2d& p, [[maybe_unused]] const circle& c) + { + return false; // It can't! + } + + // contains(l,c) + // Check if line segment contains circle + template + inline constexpr bool contains([[maybe_unused]] const line& l, [[maybe_unused]] const circle& c) + { + return false; // It can't! + } + + // contains(r,c) + // Check if rectangle contains circle + template + inline constexpr bool contains(const rect& r, const circle& c) + { + return r.pos.x + c.radius <= c.pos.x + && c.pos.x <= r.pos.x + r.size.x - c.radius + && r.pos.y + c.radius <= c.pos.y + && c.pos.y <= r.pos.y + r.size.y - c.radius; + } + + // contains(c,c) + // Check if circle contains circle + template + inline constexpr bool contains(const circle& c1, const circle& c2) + { + return (std::sqrt(std::pow(c2.pos.x - c1.pos.x, 2) + std::pow(c2.pos.y - c1.pos.y, 2)) + c2.radius) <= c1.radius; + + } + + // contains(t,c) + // Check if triangle contains circle + template + inline constexpr bool contains(const triangle& t, const circle& c) + { + return contains(t, c.pos) && (c.pos - closest(t, c.pos)).mag2() >= c.radius * c.radius; + } + + + + // overlaps(p,c) + // Check if point overlaps circle + template + inline constexpr bool overlaps(const olc::v_2d& p, const circle& c) + { + return overlaps(c, p); + } + + // overlaps(l,c) + // Check if line segment overlaps circle + template + inline constexpr bool overlaps(const line& l, const circle& c) + { + return overlaps(c, l); + } + + // overlaps(r,c) + // Check if rectangle overlaps circle + template + inline constexpr bool overlaps(const rect& r, const circle& c) + { + return overlaps(c, r); + } + + // overlaps(c,c) + // Check if circle overlaps circle + template + inline constexpr bool overlaps(const circle& c1, const circle& c2) + { + return (c1.pos - c2.pos).mag2() <= (c1.radius + c2.radius) * (c1.radius + c2.radius); + } + + // overlaps(t,c) + // Check if triangle overlaps circle + template + inline constexpr bool overlaps(const triangle& t, const circle& c) + { + return contains(t, c.pos) || (c.pos - closest(t, c.pos)).mag2() <= c.radius * c.radius; + } + + + + // intersects(p,c) + // Get intersection points where point intersects with circle + template + inline std::vector> intersects(const olc::v_2d& p, const circle& c) + { + return intersects(c, p); + } + + // intersects(l,c) + // Get intersection points where line segment intersects with circle + template + inline std::vector> intersects(const line& l, const circle& c) + { + return intersects(c, l); + } + + // intersects(r,c) + // Get intersection points where rectangle intersects with circle + template + inline std::vector> intersects(const rect& r, const circle& c) + { + return intersects(c, r); + } + + // intersects(c,c) + // Get intersection points where circle intersects with circle + template + inline std::vector> intersects(const circle& c1, const circle& c2) + { + if (c1.pos == c2.pos) return {}; // circles are either within one another so cannot intersect, or are + // identical so share all points which there's no good way to represent in return value. + v_2d between = c2.pos - c1.pos; + float dist2 = between.mag2(); + float radiusSum = c1.radius + c2.radius; + if (dist2 > radiusSum * radiusSum) return {}; // circles are too far apart to be touching. + if (contains(c1, c2) || contains(c2, c1)) return {}; // one circle is inside of the other, they can't be intersecting. + if (dist2 == radiusSum) return { c1.pos + between.norm() * c1.radius }; // circles are touching at exactly 1 point + // otherwise they're touching at 2 points. + // + // ______ ________ + // .'' ''.X' ''. X = intersections + // / / |\ \ O = chordCenter + // | | | | | In order to find the intersections we first find O. + // ( c1--(--O-)--c2 ) To do this, we find the distance c1->O by solving for the + // | | | | | two right triangles formed by the chord and c1->c2 (L). + // \ \ |/ / . Pythagorean theorem: + // '. .'X ,' .'|\ (L-x)^2 + h^2 = c1.r^2 + // ''----'' ''------'' c1.r / | \ c2.radius x^2 + h^2 = c2.r^2 + // .' h| \ Subtract 1 equation from the other and solve: + // /_____|___\ (L-x)^2 + h^2 - (x^2 + h^2) = c1.r^2 - c2.r^2 + // x L-x (L-x)^2 - x^2 = c1.r^2 - c2.r^2 + // L^2 - 2Lx = c1.r^2 - c2.r^2 + // 2Lx - L^2 = c2.r^2 - c1.r^2 + // v------------<-----------------<-----------------<-----. x = (L^2 + c2.r^2 - c1.r^2)/2 -. + // Next, we have to solve for the height h, and move '-------<--------------------<-----' + // that distance from O, perpendicular to c1->c2. + // + // Pythagorean theorem: x^2 + h^2 = c1.r^2 -> c1.r^2 - x^2 = h^2 -> h = sqrt(c1.r^2 - x^2) + // + // x is ccDist and h is halfChord. + // + float dist = std::sqrt(dist2); + float ccDist = (dist2 + c1.radius * c1.radius - c2.radius * c2.radius) / (2 * dist); + v_2d chordCenter = c1.pos + between.norm() * ccDist; + v_2d halfChord = between.norm().perp() * std::sqrt(c1.radius * c1.radius - ccDist * ccDist); + return { chordCenter + halfChord, chordCenter - halfChord }; + } + + // intersects(t,c) + // Get intersection points where triangle intersects with circle + template + inline std::vector> intersects(const triangle& t, const circle& c) + { + std::vector> intersections; + + for (size_t i = 0; i < t.side_count(); i++) { + auto v = intersects(c, t.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + + + + + + + + + + + + + // ================================================================================================================ + // TRIANGLE ======================================================================================================= + + // contains(p,t) + // Check if point contains triangle + template + inline constexpr bool contains([[maybe_unused]] const olc::v_2d& p, [[maybe_unused]] const triangle& t) + { + return false; // It can't! + } + + // contains(l,t) + // Check if line segment contains triangle + template + inline constexpr bool contains([[maybe_unused]] const line& l, [[maybe_unused]] const triangle& t) + { + return false; // It can't + } + + // contains(r,t) + // Check if rectangle contains triangle + template + inline constexpr bool contains(const rect& r, const triangle& t) + { + return contains(r, t.side(0)) + && contains(r, t.side(1)) + && contains(r, t.side(2)); + } + + // contains(c,t) + // Check if circle contains triangle + template + inline constexpr bool contains(const circle& c, const triangle& t) + { + return contains(c, t.pos[0]) + && contains(c, t.pos[1]) + && contains(c, t.pos[2]); + } + + // contains(t,t) + // Check if triangle contains triangle + template + inline constexpr bool contains(const triangle& t1, const triangle& t2) + { + return contains(t1, t2.pos[0]) + && contains(t1, t2.pos[1]) + && contains(t1, t2.pos[2]); + } + + + + // overlaps(p,t) + // Check if point overlaps triangle + template + inline constexpr bool overlaps(const olc::v_2d& p, const triangle& t) + { + return overlaps(t, p); + } + + // overlaps(l,t) + // Check if line segment overlaps triangle + template + inline constexpr bool overlaps(const line& l, const triangle& t) + { + return overlaps(t, l); + } + + // overlaps(r,t) + // Check if rectangle overlaps triangle + template + inline constexpr bool overlaps(const rect& r, const triangle& t) + { + return overlaps(t, r); + } + + // overlaps(c,t) + // Check if circle overlaps triangle + template + inline constexpr bool overlaps(const circle& c, const triangle& t) + { + return overlaps(t, c); + } + + // overlaps(t,t) + // Check if triangle overlaps triangle + template + inline constexpr bool overlaps(const triangle& t1, const triangle& t2) + { + return overlaps(t1, t2.side(0)) + || overlaps(t1, t2.side(1)) + || overlaps(t1, t2.side(2)) + || overlaps(t2, t1.pos[0]); + } + + + + // intersects(p,t) + // Get intersection points where point intersects with triangle + template + inline std::vector> intersects(const olc::v_2d& p, const triangle& t) + { + return intersects(t, p); + } + + // intersects(l,t) + // Get intersection points where line segment intersects with triangle + template + inline std::vector> intersects(const line& l, const triangle& t) + { + return intersects(t, l); + } + + // intersects(r,t) + // Get intersection points where rectangle intersects with triangle + template + inline std::vector> intersects(const rect& r, const triangle& t) + { + return intersects(t, r); + } + + // intersects(c,t) + // Get intersection points where circle intersects with triangle + template + inline std::vector> intersects(const circle& c, const triangle& t) + { + return intersects(t, c); + } + + // intersects(t,t) + // Get intersection points where triangle intersects with triangle + template + inline std::vector> intersects(const triangle& t1, const triangle& t2) + { + std::vector> intersections; + + for (size_t i = 0; i < t2.side_count(); i++) { + auto v = intersects(t1, t2.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + + // envelope_c(c) + // Return circle that fully encapsulates a point + template + inline constexpr circle envelope_c(const olc::v_2d& p) + { + return circle(p, 0); + } + + + + // envelope_c(l) + // Return circle that fully encapsulates a line + template + inline constexpr circle envelope_c(const line& l) + { + return { l.upoint(0.5),l.vector().mag() / 2 }; + } + + // envelope_c(r) + // Return circle that fully encapsulates a rectangle + template + inline constexpr circle envelope_c(const rect& r) + { + // Construct the circle from the rectangle's diagonal + return envelope_c(line(r.pos, r.pos + r.size)); + } + + // envelope_c(c) + // Return circle that fully encapsulates a circle + template + inline constexpr circle envelope_c(const circle& c) + { + return c; + } + + // envelope_c(t) + // Return circle that fully encapsulates a triangle + template + inline constexpr circle envelope_c(const triangle& t) + { + olc::v_2d circumcenter; + + double D = 2 * (t.pos[0].x * (t.pos[1].y - t.pos[2].y) + t.pos[1].x * (t.pos[2].y - t.pos[0].y) + t.pos[2].x * (t.pos[0].y - t.pos[1].y)); + circumcenter.x = T1(double( + (t.pos[0].x * t.pos[0].x + t.pos[0].y * t.pos[0].y) * (t.pos[1].y - t.pos[2].y) + + (t.pos[1].x * t.pos[1].x + t.pos[1].y * t.pos[1].y) * (t.pos[2].y - t.pos[0].y) + + (t.pos[2].x * t.pos[2].x + t.pos[2].y * t.pos[2].y) * (t.pos[0].y - t.pos[1].y) + ) / D); + circumcenter.y = T1(double( + (t.pos[0].x * t.pos[0].x + t.pos[0].y * t.pos[0].y) * (t.pos[2].x - t.pos[1].x) + + (t.pos[1].x * t.pos[1].x + t.pos[1].y * t.pos[1].y) * (t.pos[0].x - t.pos[2].x) + + (t.pos[2].x * t.pos[2].x + t.pos[2].y * t.pos[2].y) * (t.pos[1].x - t.pos[0].x) + ) / D); + + double r = 0; + for (auto& point : t.pos) { + r = std::max(r, double(std::hypot(circumcenter.x - point.x, circumcenter.y - point.y))); + } + + return { circumcenter, T1(r) }; + } + + + + + + + + + // envelope_r(p) + // Return rectangle that fully encapsulates a point + template + inline constexpr rect envelope_r(const olc::v_2d& p) + { + return rect(p, { 0,0 }); + } + + // envelope_r(l) + // Return rectangle that fully encapsulates a line + template + inline constexpr rect envelope_r(const line& l) + { + T1 min_x = std::min(l.start.x, l.end.x); + T1 min_y = std::min(l.start.y, l.end.y); + T1 size_x = std::abs(l.start.x - l.end.x); + T1 size_y = std::abs(l.start.y - l.end.y); + return { {min_x,min_y},{size_x,size_y} }; + } + + // envelope_r(r) + // Return rectangle that fully encapsulates a rectangle + template + inline constexpr rect envelope_r(const rect& r) + { + return r; + } + + // envelope_r(c) + // Return rectangle that fully encapsulates a circle + template + inline constexpr rect envelope_r(const circle& c) + { + return rect(c.pos - v_2d{c.radius, c.radius}, { c.radius * 2, c.radius * 2 }); + } + + // envelope_r(t) + // Return rectangle that fully encapsulates a triangle + template + inline constexpr rect envelope_r(const triangle& t) + { + auto vMin = t.pos[0].min(t.pos[1].min(t.pos[2])); + auto vMax = t.pos[0].max(t.pos[1].max(t.pos[2])); + return rect(vMin, vMax - vMin); + } + + template + inline constexpr auto bounding_box(T&& t) + { + return envelope_r(std::forward(t)); + } + + template + inline constexpr auto bounding_circle(T&& t) + { + return envelope_c(std::forward(t)); + } + + + // PROJECTIONS ========================================================================================================== + + // project(c,p) + // project a circle, onto a point, via a ray (i.e. how far along the ray can the circle travel until it contacts the point?) + template + inline std::optional> project(const circle& c, const olc::v_2d& p, const ray& q) + { + return project(c, circle(p, 0), q); + } + + + // project(c,c) + // project a circle, onto a circle, via a ray (i.e. how far along the ray can the circle travel until it contacts the other circle?) + template + inline std::optional> project(const circle& c1, const circle& c2, const ray& q) + { + // Inspired by https://math.stackexchange.com/a/929240 + + double A = q.direction.mag2(); + double B = 2.0 * (q.origin.dot(q.direction) - c2.pos.dot(q.direction)); + double C = c2.pos.mag2() + q.origin.mag2() - (2.0 * c2.pos.x * q.origin.x) - (2.0 * c2.pos.y * q.origin.y) - ((c1.radius + c2.radius) * (c1.radius + c2.radius)); + double D = B * B - 4.0 * A * C; + + if (D < 0.0) + return std::nullopt; + else + { + const auto sD = std::sqrt(D); + const auto s1 = (-B + sD) / (2.0 * A); + const auto s2 = (-B - sD) / (2.0 * A); + + if (s1 < 0 && s2 < 0) + return std::nullopt; + if (s1 < 0) + return q.origin + q.direction * s2; + if (s2 < 0) + return q.origin + q.direction * s1; + + return q.origin + q.direction * std::min(s1, s2); + } + } + + // project(c,l) + // project a circle, onto a line segment, via a ray + template + inline std::optional> project(const circle& c, const line& l, const ray& q) + { + // Treat line segment as capsule with radius that of the circle + // and treat the circle as a point + + // First do we hit ends of line segment, inflated to be circles + const auto vHitsStartCirc = intersects(q, circle(l.start, c.radius)); + const auto vHitsEndCirc = intersects(q, circle(l.end, c.radius)); + + // Now create two line segments in parallel to the original, that join + // up the end circles to form the sides of the capsule + const auto displace = l.vector().norm().perp() * c.radius; + const auto vHitsSide1 = intersects(q, line(l.start + displace, l.end + displace)); + const auto vHitsSide2 = intersects(q, line(l.start - displace, l.end - displace)); + + // Bring the multitude of points to one place + std::vector> vAllIntersections; + vAllIntersections.insert(vAllIntersections.end(), vHitsStartCirc.begin(), vHitsStartCirc.end()); + vAllIntersections.insert(vAllIntersections.end(), vHitsEndCirc.begin(), vHitsEndCirc.end()); + vAllIntersections.insert(vAllIntersections.end(), vHitsSide1.begin(), vHitsSide1.end()); + vAllIntersections.insert(vAllIntersections.end(), vHitsSide2.begin(), vHitsSide2.end()); + + if (vAllIntersections.size() == 0) + { + // No intersections at all, so + return std::nullopt; + } + + // Find closest + double dClosest = std::numeric_limits::max(); + olc::v_2d vClosest; + for (const auto& vContact : vAllIntersections) + { + double dDistance = (vContact - q.origin).mag2(); + if (dDistance < dClosest) + { + dClosest = dDistance; + vClosest = vContact; + } + } + + return vClosest; + } + + // project(c,r) + // project a circle, onto a rectangle, via a ray + template + inline std::optional> project(const circle& c, const rect& r, const ray& q) + { + const auto s1 = project(c, r.top(), q); + const auto s2 = project(c, r.bottom(), q); + const auto s3 = project(c, r.left(), q); + const auto s4 = project(c, r.right(), q); + + std::vector> vAllIntersections; + if (s1.has_value()) vAllIntersections.push_back(s1.value()); + if (s2.has_value()) vAllIntersections.push_back(s2.value()); + if (s3.has_value()) vAllIntersections.push_back(s3.value()); + if (s4.has_value()) vAllIntersections.push_back(s4.value()); + + if (vAllIntersections.size() == 0) + { + // No intersections at all, so + return std::nullopt; + } + + // Find closest + double dClosest = std::numeric_limits::max(); + olc::v_2d vClosest; + for (const auto& vContact : vAllIntersections) + { + double dDistance = (vContact - q.origin).mag2(); + if (dDistance < dClosest) + { + dClosest = dDistance; + vClosest = vContact; + } + } + + return vClosest; + } + + // project(c,t) + // project a circle, onto a triangle, via a ray + template + inline std::optional> project(const circle& c, const triangle& t, const ray& q) + { + const auto s1 = project(c, t.side(0), q); + const auto s2 = project(c, t.side(1), q); + const auto s3 = project(c, t.side(2), q); + + std::vector> vAllIntersections; + if (s1.has_value()) vAllIntersections.push_back(s1.value()); + if (s2.has_value()) vAllIntersections.push_back(s2.value()); + if (s3.has_value()) vAllIntersections.push_back(s3.value()); + + if (vAllIntersections.size() == 0) + { + // No intersections at all, so + return std::nullopt; + } + + // Find closest + double dClosest = std::numeric_limits::max(); + olc::v_2d vClosest; + for (const auto& vContact : vAllIntersections) + { + double dDistance = (vContact - q.origin).mag2(); + if (dDistance < dClosest) + { + dClosest = dDistance; + vClosest = vContact; + } + } + + return vClosest; + } + + + // RAYS ================================================================================================================= + + // intersects(q,q) + // return intersection point (if it exists) of a ray and a ray + template + inline std::vector> intersects(const ray& q1, const ray& q2) + { + const auto origin_diff = q2.origin - q1.origin; + const auto cp1 = q1.direction.cross(q2.direction); + const auto cp2 = origin_diff.cross(q2.direction); + + if (cp1 == 0) // Early rejection + { + if (cp2 == 0) + return { q1.origin }; // co-linear + else + return {}; // parallel + } + + const auto cp3 = origin_diff.cross(q1.direction); + const auto t1 = cp2 / cp1; // distance along q1 to intersection + const auto t2 = cp3 / cp1; // distance along q2 to intersection + + if (t1 >= 0 && t2 >= 0) + return { q1.origin + q1.direction * t1 }; // Intersection, both rays positive + else + return {}; // Intersection, but behind a rays origin, so not really an intersection in context + } + + // intersects(q,p) + // return intersection point (if it exists) of a ray and a point + template + inline std::vector> intersects(const ray& q, const v_2d& p) + { + const line l = { q.origin, q.origin + q.direction }; + + if (std::abs(l.side(p)) < epsilon) + return { p }; // Intersection + else + return {}; + } + + // intersects(q,l) + // return intersection point (if it exists) of a ray and a line segment + template + inline std::vector> intersects(const ray& q, const line& l) + { + const auto line_direction = l.vector(); + const auto origin_diff = l.start - q.origin; + const auto cp1 = q.direction.cross(line_direction); + const auto cp2 = origin_diff.cross(line_direction); + + if (cp1 == 0) // Early rejection + { + if (cp2 == 0) + return { q.origin }; // co-linear + else + return {}; // parallel + } + + const auto cp3 = origin_diff.cross(q.direction); + const auto t1 = cp2 / cp1; // distance along ray to intersection + const auto t2 = cp3 / cp1; // distance along line segment to intersection + + if (t1 >= 0 && t2 >= 0 && t2 <= 1) + return { q.origin + q.direction * t1 }; // Intersection, both rays positive + else + return {}; // Intersection, but behind a rays origin, or outside line segment bounds. + // so not really an intersection in context + } + + // collision(q,l) + // optionally returns collision point and collision normal of ray and a line segment, if it collides + template + inline std::optional, olc::v_2d>> collision(const ray& q, const line& l) + { + const auto vIntersection = intersects(q, l); + if (vIntersection.size() > 0) + { + return { {vIntersection[0], l.vector().perp().norm() * l.side(q.origin)} }; + } + + return std::nullopt; + } + + // reflect(q,l) + // optionally returns a ray reflected off a line segement if collision occurs + template + inline std::optional> reflect(const ray& q, const line& l) + { + const auto vCollision = collision(q, l); + if (vCollision.has_value()) + { + return { ray{vCollision.value().first, q.direction.reflect(vCollision.value().second)} }; + } + + return std::nullopt; + } + + // reflect(q,p) + // optionally returns a ray reflected off a point if collision occurs + template + inline std::optional> reflect(const ray& q, const olc::v_2d& p) + { + // TODO: + return std::nullopt; + } + + // collision(q,r) + // optionally returns collision point and collision normal of ray and a line segment, if it collides + template + inline std::optional, olc::v_2d>> collision(const ray& q, const rect& r) + { + olc::v_2d vClosestIntersection; + olc::v_2d vIntersectionNormal; + double dClosestDistance2 = std::numeric_limits::max(); + bool bCollide = false; + + for (size_t i = 0; i < r.side_count(); i++) + { + auto v = intersects(q, r.side(i)); + if (v.size() > 0) + { + bCollide = true; + double d = (v[0] - q.origin).mag2(); + if (d < dClosestDistance2) + { + dClosestDistance2 = d; + vClosestIntersection = v[0]; + vIntersectionNormal = r.side(i).vector().perp().norm(); + } + } + } + + if (bCollide) + { + return { {vClosestIntersection, vIntersectionNormal} }; + } + + return std::nullopt; + } + + // reflect(q,r) + // optionally returns a ray reflected off a rectangle if collision occurs + template + inline std::optional> reflect(const ray& q, const rect& r) + { + const auto vCollision = collision(q, r); + if (vCollision.has_value()) + { + return { ray{vCollision.value().first, q.direction.reflect(vCollision.value().second)} }; + } + + return std::nullopt; + } + + // collision(q,c) + // optionally returns collision point and collision normal of ray and a circle, if it collides + template + inline std::optional, olc::v_2d>> collision(const ray& q, const circle& c) + { + const auto vIntersection = intersects(q, c); + if (vIntersection.size() > 0) + { + return { {vIntersection[0], (vIntersection[0] - c.pos).norm()} }; + } + + return std::nullopt; + } + + // reflect(q,c) + // optionally returns a ray reflected off a circle if collision occurs + template + inline std::optional> reflect(const ray& q, const circle& c) + { + const auto vCollision = collision(q, c); + if (vCollision.has_value()) + { + return { ray{vCollision.value().first, q.direction.reflect(vCollision.value().second)} }; + } + + return std::nullopt; + } + + // collision(q,r) + // optionally returns collision point and collision normal of ray and a triangle, if it collides + template + inline std::optional, olc::v_2d>> collision(const ray& q, const triangle& t) + { + olc::v_2d vClosestIntersection; + olc::v_2d vIntersectionNormal; + double dClosestDistance2 = std::numeric_limits::max(); + bool bCollide = false; + + for (size_t i = 0; i < t.side_count(); i++) + { + auto v = intersects(q, t.side(i)); + if (v.size() > 0) + { + bCollide = true; + double d = (v[0] - q.origin).mag2(); + if (d < dClosestDistance2) + { + dClosestDistance2 = d; + vClosestIntersection = v[0]; + vIntersectionNormal = t.side(i).vector().perp().norm(); + } + } + } + + if (bCollide) + { + return { {vClosestIntersection, vIntersectionNormal} }; + } + + return std::nullopt; + } + + // reflect(q,t) + // optionally returns a ray reflected off a triangle if collision occurs + template + inline std::optional> reflect(const ray& q, const triangle& t) + { + const auto vCollision = collision(q, t); + if (vCollision.has_value()) + { + return { ray{vCollision.value().first, q.direction.reflect(vCollision.value().second)} }; + } + + return std::nullopt; + } + + // reflect(q,r) + // can't reflect a ray of a ray + template + inline std::optional> reflect(const ray& q1, const ray& q2) + { + // Can't reflect! + return std::nullopt; + } + + + // intersects(q,c) + // Get intersection points where a ray intersects a circle + template + inline std::vector> intersects(const ray& q, const circle& c) + { + // Look familiar? + double A = q.direction.mag2(); + double B = 2.0 * (q.origin.dot(q.direction) - c.pos.dot(q.direction)); + double C = c.pos.mag2() + q.origin.mag2() - (2.0 * c.pos.x * q.origin.x) - (2.0 * c.pos.y * q.origin.y) - (c.radius * c.radius); + double D = B * B - 4.0 * A * C; + + if (D < 0.0) + return {}; + else + { + const auto sD = std::sqrt(D); + const auto s1 = (-B + sD) / (2.0 * A); + const auto s2 = (-B - sD) / (2.0 * A); + + if (s1 < 0 && s2 < 0) + return {}; + if (s1 < 0) + return { q.origin + q.direction * s2 }; + if (s2 < 0) + return { q.origin + q.direction * s1 }; + + const auto [min_s, max_s] = std::minmax(s1, s2); + return { q.origin + q.direction * min_s, q.origin + q.direction * max_s }; + } + } + + // intersects(q,r) + // Get intersection points where a ray intersects a rectangle + template + inline std::vector> intersects(const ray& q, const rect& r) + { + std::vector> intersections; + + for (size_t i = 0; i < r.side_count(); i++) + { + auto v = intersects(q, r.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } + + // intersects(q,t) + // Get intersection points where a ray intersects a triangle + template + inline std::vector> intersects(const ray& q, const triangle& t) + { + std::vector> intersections; + + for (size_t i = 0; i < t.side_count(); i++) + { + auto v = intersects(q, t.side(i)); + intersections.insert(intersections.end(), v.begin(), v.end()); + } + + return internal::filter_duplicate_points(intersections); + } +} + +#endif // PGE_VER From 1102d945a5f759d6cdd9d69e5ec6a6f4149aae9d Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:48:43 +0000 Subject: [PATCH 28/56] ported camera and animate, absorbed concepts.h --- dev/msvc/olcPGE3.vcxproj | 1 - dev/msvc/olcPGE3.vcxproj.filters | 3 -- dev/src/concepts.h | 15 ------- dev/src/sh_template.h | 2 - dev/src/vector2d.h | 4 +- olcPixelGameEngine3.h | 12 ++---- utilities/olcUTIL3_Animate2D.h | 72 ++++++++++++++++---------------- utilities/olcUTIL3_Camera2D.h | 10 ++--- utilities/olcUTIL3_GameMode.h | 68 +++++++++++++++++++++++++++--- utilities/olcUTIL3_Geometry2D.h | 11 ++--- 10 files changed, 113 insertions(+), 85 deletions(-) delete mode 100644 dev/src/concepts.h diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 66c53e4e..c083f5ad 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -193,7 +193,6 @@ true - diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index c01addef..37257044 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -126,9 +126,6 @@ Hosts\Linux X11 Specific - - Header Files - Utilities diff --git a/dev/src/concepts.h b/dev/src/concepts.h deleted file mode 100644 index 77a1a867..00000000 --- a/dev/src/concepts.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -//! START STDHEADER GLOBAL -#include -//! END STDHEADER - - -//! START DECLARATION -namespace olc -{ - template - concept numeric = std::integral || std::floating_point; -} - -//! END DECLARATION \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 08e901c4..74c2639d 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -110,8 +110,6 @@ //! GRAB config.h CONFIGURATION -//! GRAB concepts.h DECLARATION - //! GRAB pixel.h DECLARATION //! GRAB vector2d.h DECLARATION diff --git a/dev/src/vector2d.h b/dev/src/vector2d.h index 565076de..a626d67c 100644 --- a/dev/src/vector2d.h +++ b/dev/src/vector2d.h @@ -10,13 +10,15 @@ //! START CUSTOMHEADER #include "config.h" -#include "concepts.h" //! END CUSTOMHEADER //! START DECLARATION #if !defined(PGE_VECTOR2D_DECLARED) namespace olc { + template + concept numeric = std::integral || std::floating_point; + /* A complete 2D geometric vector structure, with a variety of useful utility functions and operator overloads diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1c4c5c76..6ccc171e 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -106,13 +106,13 @@ */ -#include #include #include #include #include #include #include +#include #include #include #include @@ -261,13 +261,6 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} -namespace olc -{ - template - concept numeric = std::integral || std::floating_point; -} - - #if !defined(PGE_PIXEL_DECLARED) namespace olc { @@ -582,6 +575,9 @@ namespace olc #if !defined(PGE_VECTOR2D_DECLARED) namespace olc { + template + concept numeric = std::integral || std::floating_point; + /* A complete 2D geometric vector structure, with a variety of useful utility functions and operator overloads diff --git a/utilities/olcUTIL3_Animate2D.h b/utilities/olcUTIL3_Animate2D.h index d9283af6..6b169ba4 100644 --- a/utilities/olcUTIL3_Animate2D.h +++ b/utilities/olcUTIL3_Animate2D.h @@ -1,5 +1,5 @@ /* - OneLoneCoder - Animate2D v1.01 + OneLoneCoder - Animate2D v3.0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Handles animated Sprites efficiently @@ -7,7 +7,7 @@ License (OLC-3) ~~~~~~~~~~~~~~~ - Copyright 2018 - 2024 OneLoneCoder.com + Copyright 2018 - 2026 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -48,18 +48,18 @@ Author ~~~~~~ - David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022, 2023, 2024 + David Barr, aka javidx9, ©OneLoneCoder 2018 - 2026 Versions ~~~~~~~~ - 1.01 +PingPong Style Animation + 3.0 +Initial Release */ #pragma once -#include "utilities/olcUTIL_Geometry2D.h" -#include "olcPixelGameEngine.h" +#include +#include #include namespace olc::utils::Animate2D @@ -70,32 +70,32 @@ namespace olc::utils::Animate2D // // "Sourceless" frames are valid too - this is useful if you have a particular animation set, but // want to apply it to a variety of sources, for example sprite maps with common layouts. - class Frame - { - public: - inline Frame(const olc::Renderable* gfxSource, const geom2d::rect& rectSource = { {0,0},{0,0} }) - : gfxImageSource(gfxSource), rectFrameSource(rectSource) - { - // If no source rectangle specified then use whole image source. Ignore in the event - // that a frame is set up as source-less - if(gfxSource && rectFrameSource.size.x == 0) - rectFrameSource.size = gfxSource->Sprite()->Size(); - } - - inline const olc::Renderable* GetSourceImage() const - { - return gfxImageSource; - } - - inline const geom2d::rect& GetSourceRect() const - { - return rectFrameSource; - } - - private: - const olc::Renderable* gfxImageSource; - geom2d::rect rectFrameSource; - }; + //class Frame + //{ + //public: + // inline Frame(const olc::Renderable* gfxSource, const geom2d::rect& rectSource = { {0,0},{0,0} }) + // : gfxImageSource(gfxSource), rectFrameSource(rectSource) + // { + // // If no source rectangle specified then use whole image source. Ignore in the event + // // that a frame is set up as source-less + // if(gfxSource && rectFrameSource.size.x == 0) + // rectFrameSource.size = gfxSource->Sprite()->Size(); + // } + + // inline const olc::Renderable* GetSourceImage() const + // { + // return gfxImageSource; + // } + + // inline const geom2d::rect& GetSourceRect() const + // { + // return rectFrameSource; + // } + + //private: + // const olc::Renderable* gfxImageSource; + // geom2d::rect rectFrameSource; + //}; // Animation styles decide how the frames should be traversed in time enum class Style : uint8_t @@ -123,13 +123,13 @@ namespace olc::utils::Animate2D } // Adds a frame to this sequence - inline void AddFrame(const Frame& frame) + inline void AddFrame(const olc::ImageRegion& frame) { m_vFrames.emplace_back(frame); } // Returns a Frame Object for a given time into an animation - inline const Frame& GetFrame(const float fTime) const + inline const olc::ImageRegion& GetFrame(const float fTime) const { size_t frame = ConvertTimeToFrame(fTime); return m_vFrames[frame]; @@ -142,7 +142,7 @@ namespace olc::utils::Animate2D private: Style m_nStyle; - std::vector m_vFrames; + std::vector m_vFrames; float m_fFrameDuration = 0.1f; float m_fFrameRate = 10.0f; @@ -218,7 +218,7 @@ namespace olc::utils::Animate2D public: // Retrieve the frame information for a given animation state - inline const Frame& GetFrame(const AnimationState& state) const + inline const olc::ImageRegion& GetFrame(const AnimationState& state) const { return m_vSequences[state.nIndex].GetFrame(state.fTime); } diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index b9aed6b7..1ff9ca9e 100644 --- a/utilities/olcUTIL3_Camera2D.h +++ b/utilities/olcUTIL3_Camera2D.h @@ -1,5 +1,5 @@ /* - OneLoneCoder - Camera2D v1.01 + OneLoneCoder - Camera2D v3.0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A 2D world camera with various modes @@ -7,7 +7,7 @@ License (OLC-3) ~~~~~~~~~~~~~~~ - Copyright 2018 - 2025 OneLoneCoder.com + Copyright 2018 - 2026 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -48,18 +48,18 @@ Author ~~~~~~ - David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022, 2023, 2024, 2025 + David Barr, aka javidx9, ©OneLoneCoder 2019 - 2026 Version ~~~~~~~ - 1.01 +SlideScreens - Prompted by AtomizerZero + 3.0 +Initial Release */ #pragma once -#include "olcPixelGameEngine.h" +#include namespace olc::utils { diff --git a/utilities/olcUTIL3_GameMode.h b/utilities/olcUTIL3_GameMode.h index ac024266..d8ff544b 100644 --- a/utilities/olcUTIL3_GameMode.h +++ b/utilities/olcUTIL3_GameMode.h @@ -1,12 +1,68 @@ +/* + OneLoneCoder - GameMode v3.0 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A very simple state machine base class for game states/modes that + enforces a pattern javidx9 likes :D + + + 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 + + Version + ~~~~~~~ + + 3.0 +Initial Release + +*/ + #pragma once -#if defined(OLC_PGE3_MULTIFILE) - -#else - #include -#endif +#include -namespace olc::util3::gsm +namespace olc::utils::gsm { template class Mode diff --git a/utilities/olcUTIL3_Geometry2D.h b/utilities/olcUTIL3_Geometry2D.h index ec22309e..c2ed8443 100644 --- a/utilities/olcUTIL3_Geometry2D.h +++ b/utilities/olcUTIL3_Geometry2D.h @@ -1,5 +1,5 @@ /* - OneLoneCoder - Geometry 2D v2.0 + OneLoneCoder - Geometry 2D v3.0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A collection of 2D Geometric primitives and functions to work with and between them. @@ -8,7 +8,7 @@ License (OLC-3) ~~~~~~~~~~~~~~~ - Copyright 2018 - 2024 OneLoneCoder.com + Copyright 2018 - 2026 OneLoneCoder.com Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -60,6 +60,7 @@ v1.01: +Made constants inline +Header guards (lol... sigh...) v2.0: +Major file contribution and restructuring + v3.0: +Rewritten for olc::PixelGameEngine3 */ @@ -194,11 +195,6 @@ #include -#ifdef PGE_VER -#error "olcUTIL_Geometry2D.h must be included BEFORE olcPixelGameEngine.h" -#else - - #if !defined(PGE_VECTOR2D_DECLARED) namespace olc { @@ -2580,4 +2576,3 @@ namespace olc::utils::geom2d } } -#endif // PGE_VER From c13d93b57a3ed566635d8433f3634f823e4072f8 Mon Sep 17 00:00:00 2001 From: Diego Lopes Date: Thu, 29 Jan 2026 19:25:41 -0400 Subject: [PATCH 29/56] Android host (#114) * Android host implementation WIP * include gradle directory (important) and remove olcPixelGameEngine3.h from android dir (autogenerated) * small tweaks on input handling and intialization and added shockwave example as the android example * use NativeActivity for Android host, improve engine integration * Keyboard input support * Add ShowKeyboard to Android host and add a simple JNI wrapper for convenience * temp file handling and 3d monkey * keymap fixes * Add a function to find the best EGL config for a desired MSAA sample count w/ fallback * Remove android stuff (project files) * update header --- .gitignore | 7 + CMakeLists.txt | 8 +- dev/src/api_opengl.cpp | 32 +- dev/src/api_opengl.h | 16 +- dev/src/config.h | 8 +- dev/src/core.cpp | 187 ++-- dev/src/core.h | 3 +- dev/src/gpu_opengl33.cpp | 105 ++- dev/src/gpu_opengl33.h | 6 +- dev/src/host_android.cpp | 556 ++++++++++++ dev/src/host_android.h | 231 +++++ dev/src/host_iface.h | 2 +- dev/src/imload_android.cpp | 148 +++ dev/src/imload_android.h | 45 + dev/src/sh_template.h | 18 +- dev/src/window.h | 4 + olcPixelGameEngine3.h | 1767 ++++++++++++++++++++++++++++++------ 17 files changed, 2762 insertions(+), 381 deletions(-) create mode 100644 dev/src/host_android.cpp create mode 100644 dev/src/host_android.h create mode 100644 dev/src/imload_android.cpp create mode 100644 dev/src/imload_android.h diff --git a/.gitignore b/.gitignore index dfcf3d95..30693aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,13 @@ xcuserdata/ /dev/msvc/olcPGE3_BuildSH/x64 build/ +# Android +/dev/android +.idea/ +*.iml +/local.properties +/.gradle + # wayland artifacts examples/olcPGE3_AntiAliasing examples/olcPGE3_BatchesOfFills diff --git a/CMakeLists.txt b/CMakeLists.txt index 935b0ba1..d6fb91ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,8 +46,14 @@ add_custom_target(CopyHeader DEPENDS olcPixelGameEngine3.h ) +# copy to Android project as well +add_custom_target(CopyHeader_Android + COMMAND ${CMAKE_COMMAND} -E copy olcPixelGameEngine3.h ${CMAKE_CURRENT_SOURCE_DIR}/dev/android/app/src/main/cpp/olcPixelGameEngine3.h + DEPENDS olcPixelGameEngine3.h +) + add_library(olcPixelGameEngine3 INTERFACE olcPixelGameEngine3.h) -add_dependencies(olcPixelGameEngine3 CopyHeader) +add_dependencies(olcPixelGameEngine3 CopyHeader CopyHeader_Android) if(BUILD_EXAMPLES) add_subdirectory(examples) diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index eb807bf8..61696b9c 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -31,18 +31,24 @@ namespace olc::apis::opengl bLoaded &= (_glVertexAttribPointer = OGL_LOAD(glVertexAttribPointer)) != nullptr; bLoaded &= (_glEnableVertexAttribArray = OGL_LOAD(glEnableVertexAttribArray)) != nullptr; bLoaded &= (_glUseProgram = OGL_LOAD(glUseProgram)) != nullptr; -#if OLC_HOST != OLC_HOST_EMSCRIPTEN - bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; - bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; - bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; - bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; - bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; -#else - bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArrayOES)) != nullptr; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArrayOES)) != nullptr; bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArraysOES)) != nullptr; bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffersEXT)) != nullptr; // bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; +#elif OLC_HOST == OLC_HOST_ANDROID + bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; + bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; + bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; + // bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; + bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; +#else + bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; + bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; + bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; + bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; + bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; #endif bLoaded &= (_glGetShaderInfoLog = OGL_LOAD(glGetShaderInfoLog)) != nullptr; bLoaded &= (_glGetUniformLocation = OGL_LOAD(glGetUniformLocation)) != nullptr; @@ -99,10 +105,12 @@ namespace olc::apis::opengl sLocation << "OGL33 Error: GL_INVALID_FRAMEBUFFER_OPERATION\n"; break; case GL_OUT_OF_MEMORY: sLocation << "OGL33 Error: GL_OUT_OF_MEMORY\n"; break; +#if OLC_HOST != OLC_HOST_ANDROID case GL_STACK_UNDERFLOW: sLocation << "OGL33 Error: GL_STACK_UNDERFLOW\n"; break; case GL_STACK_OVERFLOW: sLocation << "OGL33 Error: GL_STACK_OVERFLOW\n"; break; +#endif } bWasError = true; @@ -135,8 +143,10 @@ namespace olc::apis::opengl void gl::glTexEnvf(GLenum target, GLenum pname, GLfloat param) { +#if OLC_HOST != OLC_HOST_ANDROID ::glTexEnvf(target, pname, param); CheckError(); +#endif } void gl::glDeleteTextures(GLsizei n, const GLuint* textures) @@ -213,7 +223,7 @@ namespace olc::apis::opengl void gl::glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID ::glGetTexImage(target, level, format, type, pixels); CheckError(); #endif @@ -227,7 +237,7 @@ namespace olc::apis::opengl void gl::glPolygonMode(GLenum face, GLenum mode) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID ::glPolygonMode(face, mode); CheckError(); #endif @@ -424,7 +434,7 @@ namespace olc::apis::opengl void gl::glTexImage2DMultisample(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID _glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); CheckError(); #endif diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 820f5ade..498c1f98 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -37,7 +37,6 @@ #include #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) - #endif #if OLC_HOST == OLC_HOST_MACOS @@ -68,6 +67,19 @@ #define OGL_LOAD(t) ::t #endif +#if OLC_HOST == OLC_HOST_ANDROID + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #define CALLSTYLE + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE + #define GL_LINE 0 + #define GL_FILL 0 + #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) +#endif + #if !defined(CALLSTYLE) #define CALLSTYLE #endif @@ -105,7 +117,7 @@ namespace olc typedef X11::GLXContext glRenderContext_t; #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei size, const GLchar *const * string, const GLint * length); typedef void glDeviceContext_t; typedef struct diff --git a/dev/src/config.h b/dev/src/config.h index 3072cbd8..cb92f1d2 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -39,7 +39,7 @@ #define OLC_HOST OLC_HOST_WINDOWS #endif - #if defined(__linux__) || defined(__FreeBSD__) + #if (defined(__linux__) || defined(__FreeBSD__)) && !defined(__ANDROID__) // Note: Assumes X11 atm #define OLC_HOST OLC_HOST_LINUX_X11 #endif @@ -77,6 +77,7 @@ #define OLC_IMAGELOADER_WINGDI 2 #define OLC_IMAGELOADER_MACOS 3 #define OLC_IMAGELOADER_LIB_PNG 4 +#define OLC_IMAGELOADER_NDK_IMAGEDECODER 5 #if OLC_HOST == OLC_HOST_MACOS #undef OLC_IMAGELOADER @@ -93,6 +94,11 @@ #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG #endif +#if OLC_HOST == OLC_HOST_ANDROID + #undef OLC_IMAGELOADER + #define OLC_IMAGELOADER OLC_IMAGELOADER_NDK_IMAGEDECODER +#endif + #if !defined(OLC_IMAGELOADER) #define OLC_IMAGELOADER OLC_IMAGELOADER_WINGDI #endif diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 7af45394..6aa13d0f 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -23,12 +23,16 @@ #include "imload_lib_png.h" #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN #include "host_web_emscripten.h" #include "imload_lib_png.h" #endif +#if OLC_HOST == OLC_HOST_ANDROID +#include "host_android.h" +#include "imload_android.h" +#endif + //! START IMPLEMENTATION namespace olc { @@ -284,7 +288,6 @@ namespace olc config.vScreenSize = vScreenSize; config.vPixelSize = vPixelSize; config.bFullScreen = bFullScreen; - return true; } @@ -312,7 +315,11 @@ namespace olc #endif #if OLC_HOST == OLC_HOST_EMSCRIPTEN host = std::make_unique(); - #endif + #endif + #if OLC_HOST == OLC_HOST_ANDROID + host = std::make_unique(); + auto hostPtr = (dynamic_cast(host.get())); + #endif #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO // Create OS window on this thread host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); @@ -320,7 +327,8 @@ namespace olc // at least to initialise teh rendering subsystem... sigh. coreActive = true; -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID + // Create EngineThread coreThread = std::thread(&PixelGameEngine::EngineThread, this); // Handle window events on this thread (and block) host->StartSystemEventLoop(true); @@ -328,8 +336,18 @@ namespace olc coreActive = false; // Wait for engine thread to terminate coreThread.join(); +#elif OLC_HOST == OLC_HOST_ANDROID + // We need to wait for the APP_CMD_INIT_WINDOW command before starting the loop + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initializing..."); + while (!hostPtr->IsInitialized()) { + host->StartSystemEventLoop(false); + } + + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initialized Successfully"); + + EngineThread(); #else - EngineThread(); + EngineThread(); #endif #else @@ -466,103 +484,112 @@ namespace olc } } - } - - void PixelGameEngine::EngineThread() - { - using namespace std::chrono_literals; - timeFrame2 = std::chrono::steady_clock::now(); - timeFrame1 = std::chrono::steady_clock::now(); + } + + void PixelGameEngine::EngineThread() + { + using namespace std::chrono_literals; + timeFrame2 = std::chrono::steady_clock::now(); + timeFrame1 = std::chrono::steady_clock::now(); #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Create Primary Window on EngineThread, event loop also exists for all windows - // on this thread, and all windows will be created on this thread - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); + // Create Primary Window on EngineThread, event loop also exists for all windows + // on this thread, and all windows will be created on this thread + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); #endif - - // Initialise ImageLoader Interface - #if OLC_HOST == OLC_HOST_WINDOWS - imageloader = std::make_unique(); - #endif - // Initialise ImageLoader Interface - #if OLC_HOST == OLC_HOST_MACOS - imageloader = std::make_unique(); - #endif + // Initialise ImageLoader Interface +#if OLC_HOST == OLC_HOST_WINDOWS + imageloader = std::make_unique(); +#endif - #if OLC_HOST == OLC_HOST_LINUX_X11 - imageloader = std::make_unique(); - #endif + // Initialise ImageLoader Interface +#if OLC_HOST == OLC_HOST_MACOS + imageloader = std::make_unique(); +#endif - #if OLC_HOST == OLC_HOST_LINUX_WAYLAND - imageloader = std::make_unique(); - #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + imageloader = std::make_unique(); +#endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN - imageloader = std::make_unique(); - #endif +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + imageloader = std::make_unique(); +#endif - // Initialise GPU Interface - This thread is the context - olc::gpu::RendererConfig cfgRenderer; - cfgRenderer.VerticalSync = config.bVSync; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + imageloader = std::make_unique(); +#endif - gpu = std::make_unique(); +#if OLC_HOST == OLC_HOST_ANDROID + imageloader = std::make_unique( + olc::host::Host_Android::androidApp->activity->assetManager + ); +#endif - // Link this windows devices - LinkToHost(host.get()); - LinkToRenderer(gpu.get()); - LinkToImageLoader(imageloader.get()); + // Initialise GPU Interface - This thread is the context + olc::gpu::RendererConfig cfgRenderer; + cfgRenderer.VerticalSync = config.bVSync; - // The GPU device can be based upon the primary window configuration. This - // gives us completed gpu and host objects to pass to other windows as and - // when required - gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); - if (gpu->GetLastError() != olc::gpu::RendererError::NoError) - { - //const auto e = gpu->GetLastError(); // For debug visibility - std::cout << "Error: Could not create Renderer\n"; - return; - } + gpu = std::make_unique(); + // Link this windows devices + LinkToHost(host.get()); + LinkToRenderer(gpu.get()); + LinkToImageLoader(imageloader.get()); - - olc::ImageConfig cfg; - cfg.MSAA = config.bAntiAliasMainScreen; - CreateImage(GetDefaultImage(), config.vScreenSize, cfg); - + // The GPU device can be based upon the primary window configuration. This + // gives us completed gpu and host objects to pass to other windows as and + // when required + gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); + if (gpu->GetLastError() != olc::gpu::RendererError::NoError) + { + //const auto e = gpu->GetLastError(); // For debug visibility + std::cout << "Error: Could not create Renderer\n"; + return; + } - // Initialise Font System - olc::pgeguts::CreateClassicFont(this); - draw.SetGPU(gpu.get()); - gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + olc::ImageConfig cfg; + cfg.MSAA = config.bAntiAliasMainScreen; + CreateImage(GetDefaultImage(), config.vScreenSize, cfg); - if (!OnUserCreate()) - { - // Creation process signalled abort - return; - } + // Initialise Font System + olc::pgeguts::CreateClassicFont(this); - - draw.ProcessGPUTasks(); - draw.SetTarget(GetDefaultImage()); - // Initialise Input Devices + draw.SetGPU(gpu.get()); + gpu->ApplyDefaultShader(); + draw.SetTarget(GetDefaultImage()); + if (!OnUserCreate()) + { + // Creation process signalled abort + return; + } - durationFrameCount = 0s; + draw.ProcessGPUTasks(); + draw.SetTarget(GetDefaultImage()); - #if OLC_HOST == OLC_HOST_EMSCRIPTEN - emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); - #else - while (coreActive) - { - PixelGameEngine::CoreUpdate(this); - } - #endif - } + // Initialise Input Devices + + + durationFrameCount = 0s; + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); +#else + while (coreActive) + { +#if OLC_HOST == OLC_HOST_ANDROID + if (!host->StartSystemEventLoop(false)) { + coreActive = false; + } +#endif + PixelGameEngine::CoreUpdate(this); + } +#endif + } } //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/core.h b/dev/src/core.h index e18a9be7..c44f4de7 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -25,6 +25,7 @@ //! START DECLARATION #if !defined(PGE_CORE_DECLARED) + namespace olc { // A grouping of all settable PGE properties @@ -167,8 +168,6 @@ namespace olc std::chrono::duration durationTotalElapsed{ 0 }; size_t frameCount = 0; - - // Core Thread std::thread coreThread; std::atomic coreActive; diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 1b607d48..77cd0916 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -6,7 +6,7 @@ namespace olc::gpu // === PIXEL SHADER PGE DEFAULTS === std::string Shader::static_PS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID R"(#version 330 core )" #else @@ -45,7 +45,7 @@ void main() // === VERTEX SHADER PGE DEFAULTS === std::string Shader::static_VS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID R"(#version 330 core )" #else @@ -278,9 +278,13 @@ void main() #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - EGLNativeWindowType window_handle = NULL; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID + #if OLC_HOST == OLC_HOST_ANDROID + EGLNativeWindowType window_handle = reinterpret_cast(os_win_id[0]); + #else + EGLNativeWindowType window_handle = NULL; + #endif EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY; #else const auto wayland_window = reinterpret_cast(os_win_id[0]); @@ -288,23 +292,39 @@ void main() EGLNativeDisplayType display = reinterpret_cast(os_win_id[1]); #endif - EGLint const attribute_list[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_SAMPLE_BUFFERS, 1, EGL_SAMPLES, OLC_MSAA_SAMPLES, EGL_NONE}; - EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; - EGLint num_config; - glRenderContext.display = eglGetDisplay(display); if(glRenderContext.display == EGL_NO_DISPLAY) { std::cout << "Could not create EGL Display" << std::endl; } + eglInitialize(glRenderContext.display, nullptr, nullptr); + +#if OLC_HOST == OLC_HOST_ANDROID + glRenderContext.config = FindBestConfig(glRenderContext.display, OLC_MSAA_SAMPLES); +#else + EGLint num_config; + EGLint const attribute_list[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_SAMPLE_BUFFERS, 1, + EGL_SAMPLES, OLC_MSAA_SAMPLES, + EGL_NONE + }; eglChooseConfig(glRenderContext.display, attribute_list, &glRenderContext.config, 1, &num_config); - +#endif + + EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; + /* create an EGL rendering context */ 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) { std::cout << "Could not create EGL Surface" << std::endl; } + if(!eglMakeCurrent(glRenderContext.display, glRenderContext.surface, glRenderContext.surface, glRenderContext.context)) { lastError = RendererError::FailedToCreateRenderContext; @@ -437,7 +457,7 @@ void main() gl.glGenFramebuffers(1, &nResolveFBO_Draw); gl.glGenFramebuffers(1, &nResolveFBO_Read); -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID gl.glEnable(GL_TEXTURE_2D); // Turn on texturing gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); #endif @@ -463,7 +483,7 @@ void main() X11::glXMakeCurrent(display, 0, NULL); X11::glXDestroyContext(display, glRenderContext); #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID eglMakeCurrent(glRenderContext.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(glRenderContext.display, glRenderContext.context); eglDestroySurface(glRenderContext.display, glRenderContext.surface); @@ -504,13 +524,13 @@ void main() return false; } #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID if(!eglMakeCurrent(glRenderContext.display, glRenderContext.surface, glRenderContext.surface, glRenderContext.context)) { lastError = RendererError::FailedToSwitchRenderContext; return false; } -#endif +#endif return true; } @@ -610,10 +630,8 @@ void main() mapTextureToRenderbuffer[id] = rboId; } -#if OLC_HOST != OLC_HOST_EMSCRIPTEN -#if OLC_HOST != OLC_HOST_MACOS +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_MACOS && OLC_HOST != OLC_HOST_ANDROID gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); -#endif #endif mapTextureSizes[id] = vSize; @@ -668,7 +686,7 @@ void main() // which has been blitted to via ResolveMSAA if its an MSAA texture gl.glBindTexture(GL_TEXTURE_2D, image.GetGPUID()); -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID gl.glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); #else gl.glReadPixels(0, 0, image.Size().x, image.Size().y, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); @@ -1124,9 +1142,60 @@ void main() eglSwapBuffers(glRenderContext.display, glRenderContext.surface); #endif +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID + eglSwapBuffers(glRenderContext.display, glRenderContext.surface); +#endif + return true; } +#if OLC_HOST == OLC_HOST_ANDROID + EGLConfig Renderer_OGL33::FindBestConfig(EGLDisplay display, int desiredMultisamples) + { + EGLint numConfigs; + EGLConfig bestConfig = nullptr; + EGLConfig fallbackConfig = nullptr; + + // Define attribute list for desired configuration + EGLint const attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_NONE + }; + + // Get all matching configurations + eglChooseConfig(display, attribs, nullptr, 0, &numConfigs); + + std::vector configs(numConfigs); + eglChooseConfig(display, attribs, configs.data(), numConfigs, &numConfigs); + + // Evaluate configurations to find the best that matches desired multisampling + for (const auto& config : configs) { + EGLint sampleBuffers = 0; + EGLint samples = 0; + + eglGetConfigAttrib(display, config, EGL_SAMPLE_BUFFERS, &sampleBuffers); + eglGetConfigAttrib(display, config, EGL_SAMPLES, &samples); + + if (sampleBuffers > 0 && samples == desiredMultisamples) { + bestConfig = config; + break; // Found the best match + } + + // Keep track of a fallback configuration + if (fallbackConfig == nullptr) { + fallbackConfig = config; + } + } + + return bestConfig ? bestConfig : fallbackConfig; + } +#endif } //! END IMPLEMENTATION diff --git a/dev/src/gpu_opengl33.h b/dev/src/gpu_opengl33.h index 2c018c1a..c908de5c 100644 --- a/dev/src/gpu_opengl33.h +++ b/dev/src/gpu_opengl33.h @@ -73,7 +73,7 @@ namespace olc protected: // These may need some thinking about re multiple window //olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID olc::apis::opengl::glRenderContext_t glRenderContext; #else olc::apis::opengl::glRenderContext_t glRenderContext = 0; @@ -99,6 +99,10 @@ namespace olc const Shader* pCurrentShader = nullptr; +#if OLC_HOST == OLC_HOST_ANDROID + EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); +#endif + }; } } diff --git a/dev/src/host_android.cpp b/dev/src/host_android.cpp new file mode 100644 index 00000000..b5f38f48 --- /dev/null +++ b/dev/src/host_android.cpp @@ -0,0 +1,556 @@ +#include "host_android.h" + +//! START IMPLEMENTATION +#include +#include + +namespace olc::host +{ + constexpr size_t ANDROID_KEY_MAX = 163; + + + struct KeyEntry + { + olc::Key key; + bool shiftOn; + }; + + static const std::array AndroidKeyMap = [] { + std::array map{}; + map.fill({Key::NONE, false}); + + // Letters + map[AKEYCODE_A] = {Key::A, false}; + map[AKEYCODE_B] = {Key::B, false}; + map[AKEYCODE_C] = {Key::C, false}; + map[AKEYCODE_D] = {Key::D, false}; + map[AKEYCODE_E] = {Key::E, false}; + map[AKEYCODE_F] = {Key::F, false}; + map[AKEYCODE_G] = {Key::G, false}; + map[AKEYCODE_H] = {Key::H, false}; + map[AKEYCODE_I] = {Key::I, false}; + map[AKEYCODE_J] = {Key::J, false}; + map[AKEYCODE_K] = {Key::K, false}; + map[AKEYCODE_L] = {Key::L, false}; + map[AKEYCODE_M] = {Key::M, false}; + map[AKEYCODE_N] = {Key::N, false}; + map[AKEYCODE_O] = {Key::O, false}; + map[AKEYCODE_P] = {Key::P, false}; + map[AKEYCODE_Q] = {Key::Q, false}; + map[AKEYCODE_R] = {Key::R, false}; + map[AKEYCODE_S] = {Key::S, false}; + map[AKEYCODE_T] = {Key::T, false}; + map[AKEYCODE_U] = {Key::U, false}; + map[AKEYCODE_V] = {Key::V, false}; + map[AKEYCODE_W] = {Key::W, false}; + map[AKEYCODE_X] = {Key::X, false}; + map[AKEYCODE_Y] = {Key::Y, false}; + map[AKEYCODE_Z] = {Key::Z, false}; + + // Numbers + map[AKEYCODE_0] = {Key::K0, false}; + map[AKEYCODE_1] = {Key::K1, false}; + map[AKEYCODE_2] = {Key::K2, false}; + map[AKEYCODE_3] = {Key::K3, false}; + map[AKEYCODE_4] = {Key::K4, false}; + map[AKEYCODE_5] = {Key::K5, false}; + map[AKEYCODE_6] = {Key::K6, false}; + map[AKEYCODE_7] = {Key::K7, false}; + map[AKEYCODE_8] = {Key::K8, false}; + map[AKEYCODE_9] = {Key::K9, false}; + + // Numpad + map[AKEYCODE_NUMPAD_0] = {Key::NP0, false}; + map[AKEYCODE_NUMPAD_1] = {Key::NP1, false}; + map[AKEYCODE_NUMPAD_2] = {Key::NP2, false}; + map[AKEYCODE_NUMPAD_3] = {Key::NP3, false}; + map[AKEYCODE_NUMPAD_4] = {Key::NP4, false}; + map[AKEYCODE_NUMPAD_5] = {Key::NP5, false}; + map[AKEYCODE_NUMPAD_6] = {Key::NP6, false}; + map[AKEYCODE_NUMPAD_7] = {Key::NP7, false}; + map[AKEYCODE_NUMPAD_8] = {Key::NP8, false}; + map[AKEYCODE_NUMPAD_9] = {Key::NP9, false}; + map[AKEYCODE_NUMPAD_ADD] = {Key::NP_ADD, false}; + map[AKEYCODE_NUMPAD_SUBTRACT] = {Key::NP_SUB, false}; + map[AKEYCODE_NUMPAD_MULTIPLY] = {Key::NP_MUL, false}; + map[AKEYCODE_NUMPAD_DIVIDE] = {Key::NP_DIV, false}; + map[AKEYCODE_NUMPAD_DOT] = {Key::NP_DECIMAL, false}; + + // Function keys + map[AKEYCODE_F1] = {Key::F1, false}; + map[AKEYCODE_F2] = {Key::F2, false}; + map[AKEYCODE_F3] = {Key::F3, false}; + map[AKEYCODE_F4] = {Key::F4, false}; + map[AKEYCODE_F5] = {Key::F5, false}; + map[AKEYCODE_F6] = {Key::F6, false}; + map[AKEYCODE_F7] = {Key::F7, false}; + map[AKEYCODE_F8] = {Key::F8, false}; + map[AKEYCODE_F9] = {Key::F9, false}; + map[AKEYCODE_F10] = {Key::F10, false}; + map[AKEYCODE_F11] = {Key::F11, false}; + map[AKEYCODE_F12] = {Key::F12, false}; + + // Arrows + map[AKEYCODE_DPAD_UP] = {Key::UP, false}; + map[AKEYCODE_DPAD_DOWN] = {Key::DOWN, false}; + map[AKEYCODE_DPAD_LEFT] = {Key::LEFT, false}; + map[AKEYCODE_DPAD_RIGHT] = {Key::RIGHT, false}; + + // Common keys + map[AKEYCODE_SPACE] = {Key::SPACE, false}; + map[AKEYCODE_TAB] = {Key::TAB, false}; + map[AKEYCODE_ENTER] = {Key::ENTER, false}; + map[AKEYCODE_ESCAPE] = {Key::ESCAPE, false}; + map[AKEYCODE_DEL] = {Key::BACK, false}; + map[AKEYCODE_FORWARD_DEL] = {Key::DEL, false}; + + // Modifiers + map[AKEYCODE_SHIFT_LEFT] = {Key::SHIFT, false}; + map[AKEYCODE_SHIFT_RIGHT] = {Key::SHIFT, false}; + map[AKEYCODE_CTRL_LEFT] = {Key::CTRL, false}; + map[AKEYCODE_CTRL_RIGHT] = {Key::CTRL, false}; + map[AKEYCODE_CAPS_LOCK] = {Key::CAPS_LOCK, false}; + + // OEM-style punctuation + map[AKEYCODE_SEMICOLON] = {Key::OEM_1, false}; + map[AKEYCODE_SLASH] = {Key::OEM_2, false}; + map[AKEYCODE_GRAVE] = {Key::OEM_7, false}; + map[AKEYCODE_LEFT_BRACKET] = {Key::OEM_4, false}; + map[AKEYCODE_BACKSLASH] = {Key::OEM_5, false}; + map[AKEYCODE_RIGHT_BRACKET] = {Key::OEM_6, false}; + map[AKEYCODE_APOSTROPHE] = {Key::OEM_3, false}; + map[AKEYCODE_EQUALS] = {Key::EQUALS, false}; + map[AKEYCODE_COMMA] = {Key::COMMA, false}; + map[AKEYCODE_MINUS] = {Key::MINUS, false}; + map[AKEYCODE_PERIOD] = {Key::PERIOD, false}; + map[AKEYCODE_PLUS] = {Key::EQUALS, true}; + map[AKEYCODE_AT] = {Key::K2, true}; + map[AKEYCODE_POUND] = {Key::K3, true}; + map[AKEYCODE_STAR] = {Key::NP_MUL, false}; + + return map; + }(); + + AndroidApp* Host_Android::androidApp = nullptr; + JavaVM* JNI::jvm = nullptr; + + static void Android_onAppCmd(struct android_app* app, int32_t cmd) + { + auto host = reinterpret_cast(app->userData); + host->OnAppCmd(app, cmd); + } + + static int32_t Android_onInputEvent(struct android_app* app, AInputEvent* event) + { + auto host = reinterpret_cast(app->userData); + return host->OnInputEvent(app, event); + } + + bool Host_Android::StartSystemEventLoop(bool bBlockIfPossible) + { + int events; + struct android_poll_source* source = nullptr; + + if (ALooper_pollOnce( + bBlockIfPossible || !initialized ? -1 : 0, + nullptr, + &events, + (void**)&source + ) >= 0) { + if (source) source->process(androidApp, source); + } + + if (!androidApp || !initialized) return true; + + if (androidApp->destroyRequested != 0) return false; + + return true; + } + + void Host_Android::OnAppCmd(struct android_app *app, int32_t cmd) + { + auto host = reinterpret_cast(app->userData); + switch (cmd) { + case APP_CMD_WINDOW_RESIZED: + host->pgeWindow->olc_OnWindowSize({ + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window) + }); + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", + "APP_CMD_WINDOW_RESIZED received: %dx%d", + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); + break; + case APP_CMD_INIT_WINDOW: + if (app->window) { + host->initialized = true; + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", + "APP_CMD_INIT_WINDOW received with Window"); + } + break; + case APP_CMD_TERM_WINDOW: { + host->pgeWindow->olc_OnWindowClose(); + } break; + case APP_CMD_GAINED_FOCUS: { + host->pgeWindow->olc_OnMouseFocus(true); + } break; + case APP_CMD_LOST_FOCUS: { + host->pgeWindow->olc_OnMouseFocus(false); + } break; + default: break; + } + } + + int32_t Host_Android::OnInputEvent(AndroidApp *app, AInputEvent *event) + { + auto host = reinterpret_cast(app->userData); + 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; + } + + return 1; + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + int32_t keyCode = AKeyEvent_getKeyCode(event); + int32_t action = AKeyEvent_getAction(event); + int32_t meta = AKeyEvent_getMetaState(event); + + auto entry = ((keyCode > 0) && keyCode < ANDROID_KEY_MAX) ? AndroidKeyMap[keyCode] : KeyEntry{Key::NONE, false}; + + shiftOn = + (meta & AMETA_SHIFT_ON) != 0 || + (meta & AMETA_SHIFT_LEFT_ON) != 0 || + (meta & AMETA_SHIFT_RIGHT_ON) != 0 || + (meta & AMETA_CAPS_LOCK_ON) != 0 || + entry.shiftOn; + + if (entry.key != Key::NONE) + { + pgeWindow->olc_OnKeyPress(Key::SHIFT, shiftOn); + if (action == AKEY_EVENT_ACTION_DOWN) + { + pgeWindow->olc_OnKeyPress(entry.key, true); + } + else if (action == AKEY_EVENT_ACTION_UP) + { + pgeWindow->olc_OnKeyPress(entry.key, false); + } + } + + if (keyCode == AKEYCODE_POWER) + { + // Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS + // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS + return 0; + } + else if ((keyCode == AKEYCODE_BACK) || (keyCode == AKEYCODE_MENU)) + { + return 1; + } + else if ((keyCode == AKEYCODE_VOLUME_UP) || (keyCode == AKEYCODE_VOLUME_DOWN)) + { + return 0; + } + + return 0; + } + + return 0; + } + + bool Host_Android::AddWindowFrame(olc::Window *pWindow, const vi2d &vWindowPos, + const vi2d &vWindowSize, const bool bFullScreen) + { + pgeWindow = pWindow; + return true; + } + + bool Host_Android::CloseWindowFrame(olc::Window *pWindow) + { + return true; + } + + bool Host_Android::UpdateWindowFrameTitle(olc::Window *pWindow) + { + return true; + } + + std::vector Host_Android::GetHostWindowDescriptor(olc::Window *pWindow) + { + return { reinterpret_cast(androidApp->window) }; + } + + bool Host_Android::ConnectHostResourceToRenderer() + { + return true; + } + + bool Host_Android::SyncWithDesktopComposite() + { + return true; + } + + olc::KeyboardLayout Host_Android::GetKeyboardLayout() const + { + JNIObject activity(androidApp->activity->clazz); + JNIEnv* env = JNI::GetEnv(); + + auto param = JNIString("input_method"); + JNIObject imm = activity.Call( + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + *param + ); + + std::string result = ""; + + if (*imm) { + JNIObject subType = imm.Call( + "getCurrentInputMethodSubtype", + "()Landroid/view/inputmethod/InputMethodSubtype;" + ); + if (*subType) { + JNIObject localeStr = subType.Call( + "getLocale", + "()Ljava/lang/String;" + ); + + if (*localeStr) { + JNIString localeStrObj(static_cast(*localeStr)); + result = localeStrObj; + } + } + } + + JNI::Detach(); + + auto countryCodePos = result.substr(result.find('_') + 1); + if (countryCodePos.find("US") != std::string::npos) + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (countryCodePos.find("UK") != std::string::npos || + countryCodePos.find("GB") != std::string::npos) + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (countryCodePos.find("DE") != std::string::npos) + { + return olc::KeyboardLayout::QWERTZ; + } + else if (countryCodePos.find("FR") != std::string::npos) + { + return olc::KeyboardLayout::AZERTY; + } + +#ifdef PGE_SPELL_CORRECTLY + return olc::KeyboardLayout::QWERTY_UK; +#else + return olc::KeyboardLayout::QWERTY_US; +#endif + } + + void Host_Android::ShowKeyboard(bool bShow) + { + JNIObject activity(androidApp->activity->clazz); + + auto param = JNIString("input_method"); + auto imm = activity.Call( + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + *param + ); + if (!imm) return; + + auto window = activity.Call( + "getWindow", + "()Landroid/view/Window;" + ); + if (!window) return; + + auto decor = window.Call( + "getDecorView", + "()Landroid/view/View;" + ); + if (!decor) return; + + if (bShow) + { + decor.Call( + "requestFocus", + "()Z" + ); + imm.Call( + "showSoftInput", + "(Landroid/view/View;I)Z", + *decor, + 0 + ); + } + else + { + auto token = decor.Call( + "getWindowToken", + "()Landroid/os/IBinder;" + ); + if (!token) return; + imm.Call( + "hideSoftInputFromWindow", + "(Landroid/os/IBinder;I)Z", + *token, + 0 + ); + } + + JNI::Detach(); + } + + Host_Android::Host_Android() + { + androidApp->onAppCmd = Android_onAppCmd; + androidApp->onInputEvent = Android_onInputEvent; + androidApp->userData = this; + } + + void JNI::Init(AndroidApp *app) + { + jvm = app->activity->vm; + } + + void JNI::Detach() + { + jvm->DetachCurrentThread(); + } + + JNIEnv* JNI::GetEnv() + { + JNIEnv* env = nullptr; + if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) + { + jvm->AttachCurrentThread(&env, nullptr); + } + return env; + } + + JNIObject::JNIObject(jobject obj) + { + this->obj = JNI::GetEnv()->NewGlobalRef(obj); + } + + JNIObject::~JNIObject() + { + if (obj) + { + JNI::GetEnv()->DeleteGlobalRef(obj); + } + obj = nullptr; + } + + JNIObject::JNIObject(JNIObject&& other) noexcept + { + obj = other.obj; + other.obj = nullptr; + } + + JNIObject &JNIObject::operator=(JNIObject&& other) noexcept + { + if (this != &other) { + if (obj) JNI::GetEnv()->DeleteGlobalRef(obj); + obj = other.obj; + other.obj = nullptr; + } + return *this; + } + + std::vector Host_Android::OpenFile(const std::string &sFileName) + { + AAsset* asset = AAssetManager_open( + androidApp->activity->assetManager, + sFileName.c_str(), + AASSET_MODE_STREAMING + ); + if (!asset) { + return {}; + } + + off_t size = AAsset_getLength(asset); + std::vector buffer(size); + AAsset_read(asset, buffer.data(), size); + AAsset_close(asset); + + return buffer; + } + + std::string Host_Android::OpenTextFile(const std::string &sFileName) + { + AAsset* asset = AAssetManager_open( + androidApp->activity->assetManager, + sFileName.c_str(), + AASSET_MODE_STREAMING + ); + if (!asset) { + return {}; + } + + off_t size = AAsset_getLength(asset); + std::string content(size, '\0'); + AAsset_read(asset, content.data(), size); + AAsset_close(asset); + + return content; + } +} + +void android_main(struct android_app* app) +{ + char arg0[] = "olcPixelGameEngine 3.0"; + char* argv[] = { arg0, nullptr }; + + olc::host::JNI::Init(app); + olc::host::Host_Android::androidApp = app; + + (void)main(1, argv); + + ANativeActivity_finish(app->activity); + + while (!app->destroyRequested) { + int events; + struct android_poll_source* source; + + while (ALooper_pollOnce(0, nullptr, &events, (void**)&source) > ALOOPER_POLL_TIMEOUT) { + if (source) { + source->process(app, source); + } + } + } +} +//! END IMPLEMENTATION diff --git a/dev/src/host_android.h b/dev/src/host_android.h new file mode 100644 index 00000000..62bdcfed --- /dev/null +++ b/dev/src/host_android.h @@ -0,0 +1,231 @@ +#pragma once + +//! START CUSTOMHEADER +#include "host_iface.h" +//! END CUSTOMHEADER + +//! START STDHEADER GLOBAL +#include +#include +#include +//! END STDHEADER + +//! START DECLARATION +#include +#include +#include + +// We allow users to create a normal main function for android apps +extern int main(int argc, char** argv); + +namespace olc::host +{ + using AndroidApp = struct android_app; + class Host_Android : public olc::host::Host + { + public: + Host_Android(); + bool StartSystemEventLoop(bool bBlockIfPossible) override; + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + bool CloseWindowFrame(olc::Window* pWindow) override; + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + bool ConnectHostResourceToRenderer() override; + + // Wait for entire host desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + olc::KeyboardLayout GetKeyboardLayout() const override; + + void OnAppCmd(AndroidApp* app, int32_t cmd); + int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); + + bool IsInitialized() const { return initialized.load(); } + + void ShowKeyboard(bool bShow); + + // TODO: file loading support (temporaru) + std::vector OpenFile(const std::string& sFileName); + std::string OpenTextFile(const std::string& sFileName); + + static AndroidApp* androidApp; + protected: + olc::Window* pgeWindow = nullptr; + std::atomic initialized{false}; + bool shiftOn = false; + }; + + class JNI + { + public: + static void Init(AndroidApp* app); + static void Detach(); + + static JNIEnv* GetEnv(); + + private: + static JavaVM* jvm; + }; + + class JNIObject + { + public: + JNIObject() = default; + JNIObject(jobject obj); + + ~JNIObject(); + + JNIObject(const JNIObject&) = delete; + JNIObject& operator=(const JNIObject&) = delete; + + JNIObject(JNIObject&& other) noexcept; + + JNIObject& operator=(JNIObject&& other) noexcept; + + jobject Get() const { return obj; } + jobject operator *() const { return obj; } + operator bool() const { return obj != nullptr; } + + template + R Call( + const std::string& methodName, + const std::string& methodSig, + Args&&... args + ) + { + JNIEnv* env = JNI::GetEnv(); + jclass objClass = env->GetObjectClass(obj); + jmethodID methodID = env->GetMethodID( + objClass, + methodName.c_str(), + methodSig.c_str() + ); + env->DeleteLocalRef(objClass); + + R result{}; + + if constexpr (std::is_same_v) + { + env->CallVoidMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallIntMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallBooleanMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallByteMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallCharMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallShortMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallLongMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallFloatMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallDoubleMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + jobject callResult = env->CallObjectMethod(obj, methodID, std::forward(args)...); + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + return {}; + } + if constexpr (std::is_same_v) + { + result = callResult; + } + else // JNIObject + { + result = JNIObject(callResult); + } + } + else + { + static_assert(sizeof(R) == 0, "Unsupported return type in JNIObject::Call"); + } + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + return {}; + } + return result; + } + + private: + jobject obj = nullptr; + }; + + class JNIString + { + public: + JNIString(const std::string& str) + { + JNIEnv* env = JNI::GetEnv(); + jstring local = env->NewStringUTF(str.c_str()); + jstr = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + } + + JNIString(jstring jstr) + { + JNIEnv* env = JNI::GetEnv(); + const char* chars = env->GetStringUTFChars(jstr, nullptr); + std::string str(chars); + env->ReleaseStringUTFChars(jstr, chars); + jstring local = env->NewStringUTF(str.c_str()); + this->jstr = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + } + + ~JNIString() + { + if (jstr) + { + JNI::GetEnv()->DeleteGlobalRef(jstr); + } + jstr = nullptr; + } + + operator std::string() const + { + JNIEnv* env = JNI::GetEnv(); + const char* chars = env->GetStringUTFChars(jstr, nullptr); + std::string result(chars); + env->ReleaseStringUTFChars(jstr, chars); + return result; + } + + jstring operator*() const + { + return jstr; + } + + private: + jstring jstr = nullptr; + }; + +} +//! END DECLARATION diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index b70799df..e8d6b7ac 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -46,7 +46,7 @@ namespace olc return uuid++; } #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN + #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID inline size_t CreateUID() { return uuid++; diff --git a/dev/src/imload_android.cpp b/dev/src/imload_android.cpp new file mode 100644 index 00000000..155ef8dd --- /dev/null +++ b/dev/src/imload_android.cpp @@ -0,0 +1,148 @@ +#include "imload_android.h" + +//! START IMPLEMENTATION +#include +#include +#include + +namespace olc::imload +{ + bool ImageLoader_NDKImageDecoder::CreateImageFromFile(olc::Image& image, const std::string& sFileName) + { + AAsset* asset = AAssetManager_open( + assetManager, + sFileName.c_str(), + AASSET_MODE_BUFFER + ); + if (!asset) { + return false; + } + + AImageDecoder* decoder = nullptr; + int status = AImageDecoder_createFromAAsset( + asset, + &decoder + ); + + if (status != ANDROID_IMAGE_DECODER_SUCCESS || !decoder) { + AAsset_close(asset); + return false; + } + + const AImageDecoderHeaderInfo* info = + AImageDecoder_getHeaderInfo(decoder); + if (!info) { + AImageDecoder_delete(decoder); + AAsset_close(asset); + return false; + } + + image.Create({ + AImageDecoderHeaderInfo_getWidth(info), + AImageDecoderHeaderInfo_getHeight(info) + }); + + AImageDecoder_setAndroidBitmapFormat( + decoder, + ANDROID_BITMAP_FORMAT_RGBA_8888 + ); + AImageDecoder_setUnpremultipliedRequired(decoder, true); + + std::vector pixels(image.Size().x * image.Size().y * 4); + + status = AImageDecoder_decodeImage( + decoder, + pixels.data(), + image.Size().x * 4, + pixels.size() + ); + + AImageDecoder_delete(decoder); + AAsset_close(asset); + + for (size_t i = 0; i < pixels.size(); i += 4) { + int32_t x = static_cast(i / 4) % image.Size().x; + int32_t y = static_cast(i / 4) / image.Size().x; + uint8_t r = pixels[i]; + uint8_t g = pixels[i + 1]; + uint8_t b = pixels[i + 2]; + uint8_t a = pixels[i + 3]; + image.Pixel({x, y}) = olc::Pixel(r, g, b, a); + } + + return status == ANDROID_IMAGE_DECODER_SUCCESS; + } + + bool ImageLoader_NDKImageDecoder::CreateImageFromMemory(Image &image, const uint8_t *data, const size_t bytes) + { + AImageDecoder* decoder = nullptr; + int status = AImageDecoder_createFromBuffer( + data, + bytes, + &decoder + ); + + if (status != ANDROID_IMAGE_DECODER_SUCCESS || !decoder) { + return false; + } + + const AImageDecoderHeaderInfo* info = + AImageDecoder_getHeaderInfo(decoder); + if (!info) { + AImageDecoder_delete(decoder); + return false; + } + + image.Create({ + AImageDecoderHeaderInfo_getWidth(info), + AImageDecoderHeaderInfo_getHeight(info) + }); + + AImageDecoder_setAndroidBitmapFormat( + decoder, + ANDROID_BITMAP_FORMAT_RGBA_8888 + ); + AImageDecoder_setUnpremultipliedRequired(decoder, true); + + std::vector pixels(image.Size().x * image.Size().y * 4); + + status = AImageDecoder_decodeImage( + decoder, + pixels.data(), + image.Size().x * 4, + pixels.size() + ); + + AImageDecoder_delete(decoder); + + for (size_t i = 0; i < pixels.size(); i += 4) { + int32_t x = static_cast(i / 4) % image.Size().x; + int32_t y = static_cast(i / 4) / image.Size().x; + uint8_t r = pixels[i]; + uint8_t g = pixels[i + 1]; + uint8_t b = pixels[i + 2]; + uint8_t a = pixels[i + 3]; + image.Pixel({x, y}) = olc::Pixel(r, g, b, a); + } + + return status == ANDROID_IMAGE_DECODER_SUCCESS; + } + + bool ImageLoader_NDKImageDecoder::CreateImageFromMemory(Image &image, const std::vector &data) + { + return CreateImageFromMemory(image, data.data(), data.size()); + } + + bool ImageLoader_NDKImageDecoder::WriteImageToFile(const Image &image, const std::string &sFileName) + { + // No solution for now + return false; + } + + bool ImageLoader_NDKImageDecoder::WriteImageToMemoryFile(Image &image, const std::vector &data) + { + // No solution for now + return false; + } +} +//! END IMPLEMENTATION diff --git a/dev/src/imload_android.h b/dev/src/imload_android.h new file mode 100644 index 00000000..43b0919b --- /dev/null +++ b/dev/src/imload_android.h @@ -0,0 +1,45 @@ +#pragma once + +//! START CUSTOMHEADER +#include "imload_iface.h" +//! END CUSTOMHEADER + +//! START STDHEADER GLOBAL +//! END STDHEADER + +//! START DECLARATION +#include + +#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) +namespace olc::imload +{ + + // Create an image resource based on an image file asset + class ImageLoader_NDKImageDecoder : public ImageLoader + { + public: + ImageLoader_NDKImageDecoder() = default; + ImageLoader_NDKImageDecoder(AAssetManager* assetManager) : assetManager(assetManager) {} + + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + + protected: + AAssetManager* assetManager = nullptr; + }; +} +#define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 +#endif +//! END DECLARATION diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 74c2639d..40ca4410 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -185,6 +185,12 @@ //! GRAB host_web_emscripten.h DECLARATION #endif +#if OLC_HOST == OLC_HOST_ANDROID +//! GRAB host_android.h ANDROID_CONFIG + +//! GRAB host_android.h DECLARATION +#endif + #if OLC_GPU == OLC_GPU_OPENGL33 //! GRAB api_opengl.h OPENGL_CONFIG @@ -207,6 +213,10 @@ //! GRAB imload_lib_png.h DECLARATION #endif +#if OLC_HOST == OLC_HOST_ANDROID +//! GRAB imload_android.h DECLARATION +#endif + @@ -236,6 +246,10 @@ //! GRAB host_web_emscripten.cpp IMPLEMENTATION #endif +#if OLC_HOST == OLC_HOST_ANDROID +//! GRAB host_android.cpp IMPLEMENTATION +#endif + #define PGE_HOST_IMPLEMENTED 1 #endif @@ -294,6 +308,9 @@ #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG //! GRAB imload_lib_png.cpp IMPLEMENTATION #endif +#if OLC_HOST == OLC_HOST_ANDROID +//! GRAB imload_android.cpp IMPLEMENTATION +#endif #define PGE_IMAGELOADER_IMPLEMENTED 1 #endif @@ -314,4 +331,3 @@ So you scrolled all this way huh ? In that case: */ // Thank you for using olcPixelGameEngine! :) - diff --git a/dev/src/window.h b/dev/src/window.h index 2bcf2121..74495a4d 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -41,6 +41,10 @@ #define FRIENDLY_HOST Host_Web_Emscripten #endif +#if OLC_HOST == OLC_HOST_ANDROID + #define FRIENDLY_HOST Host_Android +#endif + namespace olc { namespace host diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1f21904d..d731753f 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -173,7 +173,7 @@ #define OLC_HOST OLC_HOST_WINDOWS #endif - #if defined(__linux__) || defined(__FreeBSD__) + #if (defined(__linux__) || defined(__FreeBSD__)) && !defined(__ANDROID__) // Note: Assumes X11 atm #define OLC_HOST OLC_HOST_LINUX_X11 #endif @@ -211,6 +211,7 @@ #define OLC_IMAGELOADER_WINGDI 2 #define OLC_IMAGELOADER_MACOS 3 #define OLC_IMAGELOADER_LIB_PNG 4 +#define OLC_IMAGELOADER_NDK_IMAGEDECODER 5 #if OLC_HOST == OLC_HOST_MACOS #undef OLC_IMAGELOADER @@ -227,6 +228,11 @@ #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG #endif +#if OLC_HOST == OLC_HOST_ANDROID + #undef OLC_IMAGELOADER + #define OLC_IMAGELOADER OLC_IMAGELOADER_NDK_IMAGEDECODER +#endif + #if !defined(OLC_IMAGELOADER) #define OLC_IMAGELOADER OLC_IMAGELOADER_WINGDI #endif @@ -2945,6 +2951,10 @@ namespace olc #define FRIENDLY_HOST Host_Web_Emscripten #endif +#if OLC_HOST == OLC_HOST_ANDROID + #define FRIENDLY_HOST Host_Android +#endif + namespace olc { namespace host @@ -3069,7 +3079,7 @@ namespace olc return uuid++; } #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN + #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID inline size_t CreateUID() { return uuid++; @@ -3128,6 +3138,7 @@ namespace olc #endif #if !defined(PGE_CORE_DECLARED) + namespace olc { // A grouping of all settable PGE properties @@ -3270,8 +3281,6 @@ namespace olc std::chrono::duration durationTotalElapsed{ 0 }; size_t frameCount = 0; - - // Core Thread std::thread coreThread; std::atomic coreActive; @@ -4695,8 +4704,6 @@ namespace olc::host { class Host_Web_Emscripten : public olc::host::Host { - private: - std::string canvasId; public: Host_Web_Emscripten(); bool StartSystemEventLoop(bool bBlockIfPossible = false) override; @@ -4710,6 +4717,8 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + olc::KeyboardLayout GetKeyboardLayout() const override; + public: // event callbacks static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); @@ -4726,6 +4735,9 @@ namespace olc::host static bool olc_OnMouseWheel(olc::Window* pWindow, const int32_t nScroll); static bool olc_OnMouseFocus(olc::Window* pWindow, const bool bHasFocus); + // Set Keyboard Device State + static bool olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed); + // Set Window State static bool olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos); static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); @@ -4733,10 +4745,21 @@ namespace olc::host private: // helpers static olc::Window* GetWindowFromCanvasId(std::string canvasId); + public: // Callback data type + struct CallbackData { + Host_Web_Emscripten* pHost; + olc::Window* pWindow; + std::string canvasId; + }; + private: static std::unordered_map mapUID2CanvasId; + static std::unordered_map> mapUID2CallbackData; static std::unordered_map mapCanvasId2PTR; std::atomic terminate {false}; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; }; @@ -4744,6 +4767,227 @@ namespace olc::host #endif +#if OLC_HOST == OLC_HOST_ANDROID + +#include +#include +#include + +// We allow users to create a normal main function for android apps +extern int main(int argc, char** argv); + +namespace olc::host +{ + using AndroidApp = struct android_app; + class Host_Android : public olc::host::Host + { + public: + Host_Android(); + bool StartSystemEventLoop(bool bBlockIfPossible) override; + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + bool CloseWindowFrame(olc::Window* pWindow) override; + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + bool ConnectHostResourceToRenderer() override; + + // Wait for entire host desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + olc::KeyboardLayout GetKeyboardLayout() const override; + + void OnAppCmd(AndroidApp* app, int32_t cmd); + int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); + + bool IsInitialized() const { return initialized.load(); } + + void ShowKeyboard(bool bShow); + + // TODO: file loading support (temporaru) + std::vector OpenFile(const std::string& sFileName); + std::string OpenTextFile(const std::string& sFileName); + + static AndroidApp* androidApp; + protected: + olc::Window* pgeWindow = nullptr; + std::atomic initialized{false}; + bool shiftOn = false; + }; + + class JNI + { + public: + static void Init(AndroidApp* app); + static void Detach(); + + static JNIEnv* GetEnv(); + + private: + static JavaVM* jvm; + }; + + class JNIObject + { + public: + JNIObject() = default; + JNIObject(jobject obj); + + ~JNIObject(); + + JNIObject(const JNIObject&) = delete; + JNIObject& operator=(const JNIObject&) = delete; + + JNIObject(JNIObject&& other) noexcept; + + JNIObject& operator=(JNIObject&& other) noexcept; + + jobject Get() const { return obj; } + jobject operator *() const { return obj; } + operator bool() const { return obj != nullptr; } + + template + R Call( + const std::string& methodName, + const std::string& methodSig, + Args&&... args + ) + { + JNIEnv* env = JNI::GetEnv(); + jclass objClass = env->GetObjectClass(obj); + jmethodID methodID = env->GetMethodID( + objClass, + methodName.c_str(), + methodSig.c_str() + ); + env->DeleteLocalRef(objClass); + + R result{}; + + if constexpr (std::is_same_v) + { + env->CallVoidMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallIntMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallBooleanMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallByteMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallCharMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallShortMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallLongMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallFloatMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallDoubleMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + jobject callResult = env->CallObjectMethod(obj, methodID, std::forward(args)...); + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + return {}; + } + if constexpr (std::is_same_v) + { + result = callResult; + } + else // JNIObject + { + result = JNIObject(callResult); + } + } + else + { + static_assert(sizeof(R) == 0, "Unsupported return type in JNIObject::Call"); + } + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + return {}; + } + return result; + } + + private: + jobject obj = nullptr; + }; + + class JNIString + { + public: + JNIString(const std::string& str) + { + JNIEnv* env = JNI::GetEnv(); + jstring local = env->NewStringUTF(str.c_str()); + jstr = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + } + + JNIString(jstring jstr) + { + JNIEnv* env = JNI::GetEnv(); + const char* chars = env->GetStringUTFChars(jstr, nullptr); + std::string str(chars); + env->ReleaseStringUTFChars(jstr, chars); + jstring local = env->NewStringUTF(str.c_str()); + this->jstr = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + } + + ~JNIString() + { + if (jstr) + { + JNI::GetEnv()->DeleteGlobalRef(jstr); + } + jstr = nullptr; + } + + operator std::string() const + { + JNIEnv* env = JNI::GetEnv(); + const char* chars = env->GetStringUTFChars(jstr, nullptr); + std::string result(chars); + env->ReleaseStringUTFChars(jstr, chars); + return result; + } + + jstring operator*() const + { + return jstr; + } + + private: + jstring jstr = nullptr; + }; + +} +#endif + #if OLC_GPU == OLC_GPU_OPENGL33 #if OLC_HOST == OLC_HOST_WINDOWS @@ -4768,7 +5012,6 @@ namespace olc::host #include #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) - #endif #if OLC_HOST == OLC_HOST_MACOS @@ -4799,6 +5042,19 @@ namespace olc::host #define OGL_LOAD(t) ::t #endif +#if OLC_HOST == OLC_HOST_ANDROID + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #define CALLSTYLE + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE + #define GL_LINE 0 + #define GL_FILL 0 + #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) +#endif + #if !defined(CALLSTYLE) #define CALLSTYLE #endif @@ -4834,7 +5090,7 @@ namespace olc typedef X11::GLXContext glRenderContext_t; #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei size, const GLchar *const * string, const GLint * length); typedef void glDeviceContext_t; typedef struct @@ -5121,7 +5377,7 @@ namespace olc protected: // These may need some thinking about re multiple window //olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID olc::apis::opengl::glRenderContext_t glRenderContext; #else olc::apis::opengl::glRenderContext_t glRenderContext = 0; @@ -5147,6 +5403,10 @@ namespace olc const Shader* pCurrentShader = nullptr; +#if OLC_HOST == OLC_HOST_ANDROID + EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); +#endif + }; } } @@ -5283,6 +5543,43 @@ namespace olc::imload #endif #endif +#if OLC_HOST == OLC_HOST_ANDROID +#include + +#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) +namespace olc::imload +{ + + // Create an image resource based on an image file asset + class ImageLoader_NDKImageDecoder : public ImageLoader + { + public: + ImageLoader_NDKImageDecoder() = default; + ImageLoader_NDKImageDecoder(AAssetManager* assetManager) : assetManager(assetManager) {} + + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + + protected: + AAssetManager* assetManager = nullptr; + }; +} +#define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 +#endif +#endif + @@ -9106,50 +9403,164 @@ namespace olc::host { std::unordered_map Host_Web_Emscripten::mapUID2CanvasId; std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; + std::unordered_map> Host_Web_Emscripten::mapUID2CallbackData; Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; - // mapKeys[DOM_PK_UNKNOWN] = Key::NONE; - // mapKeys[DOM_PK_A] = Key::A; mapKeys[DOM_PK_B] = Key::B; mapKeys[DOM_PK_C] = Key::C; mapKeys[DOM_PK_D] = Key::D; - // mapKeys[DOM_PK_E] = Key::E; mapKeys[DOM_PK_F] = Key::F; mapKeys[DOM_PK_G] = Key::G; mapKeys[DOM_PK_H] = Key::H; - // mapKeys[DOM_PK_I] = Key::I; mapKeys[DOM_PK_J] = Key::J; mapKeys[DOM_PK_K] = Key::K; mapKeys[DOM_PK_L] = Key::L; - // mapKeys[DOM_PK_M] = Key::M; mapKeys[DOM_PK_N] = Key::N; mapKeys[DOM_PK_O] = Key::O; mapKeys[DOM_PK_P] = Key::P; - // mapKeys[DOM_PK_Q] = Key::Q; mapKeys[DOM_PK_R] = Key::R; mapKeys[DOM_PK_S] = Key::S; mapKeys[DOM_PK_T] = Key::T; - // mapKeys[DOM_PK_U] = Key::U; mapKeys[DOM_PK_V] = Key::V; mapKeys[DOM_PK_W] = Key::W; mapKeys[DOM_PK_X] = Key::X; - // mapKeys[DOM_PK_Y] = Key::Y; mapKeys[DOM_PK_Z] = Key::Z; - // mapKeys[DOM_PK_0] = Key::K0; mapKeys[DOM_PK_1] = Key::K1; mapKeys[DOM_PK_2] = Key::K2; - // mapKeys[DOM_PK_3] = Key::K3; mapKeys[DOM_PK_4] = Key::K4; mapKeys[DOM_PK_5] = Key::K5; - // mapKeys[DOM_PK_6] = Key::K6; mapKeys[DOM_PK_7] = Key::K7; mapKeys[DOM_PK_8] = Key::K8; - // mapKeys[DOM_PK_9] = Key::K9; - // mapKeys[DOM_PK_F1] = Key::F1; mapKeys[DOM_PK_F2] = Key::F2; mapKeys[DOM_PK_F3] = Key::F3; mapKeys[DOM_PK_F4] = Key::F4; - // mapKeys[DOM_PK_F5] = Key::F5; mapKeys[DOM_PK_F6] = Key::F6; mapKeys[DOM_PK_F7] = Key::F7; mapKeys[DOM_PK_F8] = Key::F8; - // mapKeys[DOM_PK_F9] = Key::F9; mapKeys[DOM_PK_F10] = Key::F10; mapKeys[DOM_PK_F11] = Key::F11; mapKeys[DOM_PK_F12] = Key::F12; - // mapKeys[DOM_PK_ARROW_UP] = Key::UP; mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; - // mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; - // mapKeys[DOM_PK_SPACE] = Key::SPACE; mapKeys[DOM_PK_TAB] = Key::TAB; - // mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; - // mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; - // mapKeys[DOM_PK_INSERT] = Key::INS; mapKeys[DOM_PK_DELETE] = Key::DEL; mapKeys[DOM_PK_HOME] = Key::HOME; - // mapKeys[DOM_PK_END] = Key::END; mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; - // mapKeys[DOM_PK_BACKSPACE] = Key::BACK; mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; - // mapKeys[DOM_PK_ENTER] = Key::ENTER; mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; - // mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; mapKeys[DOM_PK_PAUSE] = Key::PAUSE; - // mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; - // mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; - // mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; - // mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; - // mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; - // mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; - // mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; - // mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; - // mapKeys[DOM_PK_PERIOD] = Key::PERIOD; mapKeys[DOM_PK_EQUAL] = Key::EQUALS; - // mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; - // mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; - // mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; mapKeys[DOM_PK_SLASH] = Key::OEM_2; mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; - // mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; - // mapKeys[DOM_PK_QUOTE] = Key::OEM_7; + // Detect and Store Keyboard Layout + EM_ASM({ + if (!navigator.keyboard || !navigator.keyboard.getLayoutMap) + return; + + navigator.keyboard.getLayoutMap().then(function(map) + { + const keys = [map.get("KeyQ"), map.get("KeyW"), map.get("KeyE"), map.get("KeyR"), map.get("KeyT"), map.get("KeyY"), map.get("Backslash")]; + + // QWERTY - UK/US + if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'y') { + if(keys[6] == '#' || keys[6] == '~') + { + Module.keyboardLayout = 0; + return; + } + else + { + Module.keyboardLayout = 1; + return; + } + } + + // QWERTZ - DE + if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { + Module.keyboardLayout = 2; + return; + } + + // AZERTY - FR + if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { + Module.keyboardLayout = 3; + return; + } + + }); + }); + + // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes + mapKeys[DOM_PK_UNKNOWN] = Key::NONE; + + // A-Z + mapKeys[DOM_PK_A] = Key::A; + mapKeys[DOM_PK_B] = Key::B; + mapKeys[DOM_PK_C] = Key::C; + mapKeys[DOM_PK_D] = Key::D; + mapKeys[DOM_PK_E] = Key::E; + mapKeys[DOM_PK_F] = Key::F; + mapKeys[DOM_PK_G] = Key::G; + mapKeys[DOM_PK_H] = Key::H; + mapKeys[DOM_PK_I] = Key::I; + mapKeys[DOM_PK_J] = Key::J; + mapKeys[DOM_PK_K] = Key::K; + mapKeys[DOM_PK_L] = Key::L; + mapKeys[DOM_PK_M] = Key::M; + mapKeys[DOM_PK_N] = Key::N; + mapKeys[DOM_PK_O] = Key::O; + mapKeys[DOM_PK_P] = Key::P; + mapKeys[DOM_PK_Q] = Key::Q; + mapKeys[DOM_PK_R] = Key::R; + mapKeys[DOM_PK_S] = Key::S; + mapKeys[DOM_PK_T] = Key::T; + mapKeys[DOM_PK_U] = Key::U; + mapKeys[DOM_PK_V] = Key::V; + mapKeys[DOM_PK_W] = Key::W; + mapKeys[DOM_PK_X] = Key::X; + mapKeys[DOM_PK_Y] = Key::Y; + mapKeys[DOM_PK_Z] = Key::Z; + + // Numeric Keys + mapKeys[DOM_PK_0] = Key::K0; + mapKeys[DOM_PK_1] = Key::K1; + mapKeys[DOM_PK_2] = Key::K2; + mapKeys[DOM_PK_3] = Key::K3; + mapKeys[DOM_PK_4] = Key::K4; + mapKeys[DOM_PK_5] = Key::K5; + mapKeys[DOM_PK_6] = Key::K6; + mapKeys[DOM_PK_7] = Key::K7; + mapKeys[DOM_PK_8] = Key::K8; + mapKeys[DOM_PK_9] = Key::K9; + + // Function Keys + mapKeys[DOM_PK_F1] = Key::F1; + mapKeys[DOM_PK_F2] = Key::F2; + mapKeys[DOM_PK_F3] = Key::F3; + mapKeys[DOM_PK_F4] = Key::F4; + mapKeys[DOM_PK_F5] = Key::F5; + mapKeys[DOM_PK_F6] = Key::F6; + mapKeys[DOM_PK_F7] = Key::F7; + mapKeys[DOM_PK_F8] = Key::F8; + mapKeys[DOM_PK_F9] = Key::F9; + mapKeys[DOM_PK_F10] = Key::F10; + mapKeys[DOM_PK_F11] = Key::F11; + mapKeys[DOM_PK_F12] = Key::F12; + + // Arrow Keys + mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; + mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; + mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; + mapKeys[DOM_PK_ARROW_UP] = Key::UP; + + // Other Keys + mapKeys[DOM_PK_BACKSPACE] = Key::BACK; + mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; + mapKeys[DOM_PK_ENTER] = Key::ENTER; + mapKeys[DOM_PK_PAUSE] = Key::PAUSE; + mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; + mapKeys[DOM_PK_TAB] = Key::TAB; + mapKeys[DOM_PK_DELETE] = Key::DEL; + mapKeys[DOM_PK_HOME] = Key::HOME; + mapKeys[DOM_PK_END] = Key::END; + mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; + mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; + mapKeys[DOM_PK_INSERT] = Key::INS; + mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; + mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; + mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; + mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; + mapKeys[DOM_PK_SPACE] = Key::SPACE; + mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; + + // Numpad + mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; + mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; + mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; + mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; + mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; + mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; + mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; + mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; + mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; + mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; + mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; + mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; + mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; + mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; + mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; + mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; + + mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; + mapKeys[DOM_PK_SLASH] = Key::OEM_2; + mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; + mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; + mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; + mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; + mapKeys[DOM_PK_QUOTE] = Key::OEM_7; + + mapKeys[DOM_PK_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_COMMA] = Key::COMMA; + mapKeys[DOM_PK_MINUS] = Key::MINUS; + mapKeys[DOM_PK_PERIOD] = Key::PERIOD; } bool Host_Web_Emscripten::StartSystemEventLoop(bool bBlockIfPossible) @@ -9168,40 +9579,46 @@ namespace olc::host olc::vi2d vWinSize = vWindowSize; // TODO: multi-window solutions - canvasId = "#canvas"; // + std::to_string(pWindow->GetUID()); + mapUID2CallbackData.insert_or_assign(pWindow->GetUID(), std::make_unique(CallbackData{ + this, + pWindow, + std::string{"#canvas"} + })); + + auto cbData = mapUID2CallbackData.at(pWindow->GetUID()).get(); - mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), canvasId); - mapCanvasId2PTR.insert_or_assign(canvasId, pWindow); + mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), cbData->canvasId); + mapCanvasId2PTR.insert_or_assign(cbData->canvasId, pWindow); - emscripten_set_canvas_element_size(canvasId.c_str(), vWinSize.x, vWinSize.y); + emscripten_set_canvas_element_size(cbData->canvasId.c_str(), vWinSize.x, vWinSize.y); pWindow->SetWindowSize(vWinSize); // Keyboard Callbacks - emscripten_set_keydown_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, keyboard_callback); - emscripten_set_keyup_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, keyboard_callback); + emscripten_set_keydown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); + emscripten_set_keyup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); // Mouse Callbacks - emscripten_set_wheel_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, wheel_callback); - emscripten_set_mousedown_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, mouse_callback); - emscripten_set_mouseup_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, mouse_callback); - emscripten_set_mousemove_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, mouse_callback); + emscripten_set_wheel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, wheel_callback); + emscripten_set_mousedown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + emscripten_set_mouseup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + emscripten_set_mousemove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); // Touch Callbacks - emscripten_set_touchstart_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, touch_callback); - emscripten_set_touchmove_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, touch_callback); - emscripten_set_touchend_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, touch_callback); + 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); // Canvas Focus Callbacks - emscripten_set_blur_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, focus_callback); - emscripten_set_focus_callback(canvasId.c_str(), (void*)(canvasId.c_str()), 1, focus_callback); + emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); + emscripten_set_focus_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); // Canvas Resize Callbacks - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, (void*)(canvasId.c_str()), 1, resize_callback); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, (void*)(canvasId.c_str()), 1, fullscreen_change_callback); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, resize_callback); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, fullscreen_change_callback); // trigger resize after a short pause emscripten_sleep(50); - resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, (void*)(canvasId.c_str())); + resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, reinterpret_cast(cbData)); return true; } @@ -9209,6 +9626,8 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { + CallbackData* pCallbackData = reinterpret_cast(userData); + // we maintain our own state for teh number pad, default true static bool numPadActive = true; @@ -9246,11 +9665,23 @@ namespace olc::host numPadActive = !numPadActive; } - // if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) - // ptrPGE->olc_UpdateKeyState(pk_code, true); + if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) + { + if(pCallbackData->pHost->mapKeys.contains(pk_code)) + { + olc_OnKeyPress(pCallbackData->pWindow, pCallbackData->pHost->mapKeys[pk_code], true); + } + } + - // if (eventType == EMSCRIPTEN_EVENT_KEYUP) - // ptrPGE->olc_UpdateKeyState(pk_code, false); + if (eventType == EMSCRIPTEN_EVENT_KEYUP) + { + if(pCallbackData->pHost->mapKeys.contains(pk_code)) + { + olc_OnKeyPress(pCallbackData->pWindow, pCallbackData->pHost->mapKeys[pk_code], false); + } + } + //Consume keyboard events so that keys like F1 and F5 don't do weird things return EM_TRUE; @@ -9259,10 +9690,10 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); if (eventType == EMSCRIPTEN_EVENT_WHEEL) - olc_OnMouseWheel(pWindow, -1 * e->deltaY); + olc_OnMouseWheel(pCallbackData->pWindow, -1 * e->deltaY); return EM_TRUE; } @@ -9282,36 +9713,36 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); //Mouse Movement if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) - olc_OnMouseMove(pWindow, {e->targetX, e->targetY}); + olc_OnMouseMove(pCallbackData->pWindow, {e->targetX, e->targetY}); //Mouse button press if (e->button == 0) // left click { if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pWindow, 0, true); + olc_OnMouseButton(pCallbackData->pWindow, 0, true); else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pWindow, 0, false); + olc_OnMouseButton(pCallbackData->pWindow, 0, false); } if (e->button == 2) // right click { if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pWindow, 1, true); + olc_OnMouseButton(pCallbackData->pWindow, 1, true); else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pWindow, 1, false); + olc_OnMouseButton(pCallbackData->pWindow, 1, false); } if (e->button == 1) // middle click { if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pWindow, 2, true); + olc_OnMouseButton(pCallbackData->pWindow, 2, true); else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pWindow, 2, false); + olc_OnMouseButton(pCallbackData->pWindow, 2, false); //at the moment only middle mouse needs to consume events. return EM_TRUE; @@ -9326,25 +9757,25 @@ namespace olc::host // TODO: Implement touch more effectively. // For now, emulate single pointer mouse. - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); + CallbackData* pCallbackData = reinterpret_cast(userData); // Move if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) { - olc_OnMouseMove(pWindow, {e->touches->targetX, e->touches->targetY}); + olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); } // Start if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) { - olc_OnMouseMove(pWindow, {e->touches->targetX, e->touches->targetY}); - olc_OnMouseButton(pWindow, 0, true); + olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); + olc_OnMouseButton(pCallbackData->pWindow, 0, true); } // End if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) { - olc_OnMouseButton(pWindow, 0, false); + olc_OnMouseButton(pCallbackData->pWindow, 0, false); } return EM_TRUE; @@ -9352,17 +9783,17 @@ namespace olc::host //TY Gorbit EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); - + CallbackData* pCallbackData = reinterpret_cast(userData); + if (eventType == EMSCRIPTEN_EVENT_BLUR) { // ptrPGE->olc_UpdateKeyFocus(false); - olc_OnMouseFocus(pWindow, false); + olc_OnMouseFocus(pCallbackData->pWindow, false); } else if (eventType == EMSCRIPTEN_EVENT_FOCUS) { // ptrPGE->olc_UpdateKeyFocus(true); - olc_OnMouseFocus(pWindow, true); + olc_OnMouseFocus(pCallbackData->pWindow, true); } return 0; @@ -9371,8 +9802,6 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) { - olc::Window* pWindow = GetWindowFromCanvasId(reinterpret_cast(userData)); - // trigger resize after a short pause emscripten_sleep(50); resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, userData); @@ -9382,8 +9811,7 @@ namespace olc::host //TY Moros EM_BOOL Host_Web_Emscripten::resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData) { - char* canvasId = reinterpret_cast(userData); - olc::Window* pWindow = GetWindowFromCanvasId(canvasId); + CallbackData* pCallbackData = reinterpret_cast(userData); // HACK ALERT! // @@ -9415,8 +9843,8 @@ namespace olc::host } // resize the canvas - emscripten_set_canvas_element_size(canvasId, static_cast(width), static_cast(height)); - pWindow->SetWindowSize(olc::vd2d{width, height}); + emscripten_set_canvas_element_size(pCallbackData->canvasId.c_str(), static_cast(width), static_cast(height)); + pCallbackData->pWindow->SetWindowSize(olc::vd2d{width, height}); return 0; } @@ -9437,7 +9865,7 @@ namespace olc::host const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); if(window_handle != mapUID2CanvasId.end()) { - return { reinterpret_cast(&canvasId) }; + return { (void*)(window_handle->second.c_str()) }; } return {}; @@ -9452,9 +9880,14 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Web_Emscripten::SyncWithDesktopComposite() { - std::cout << "Emscripten: SyncWithDesktopComposite not implemented.\n"; + // SyncWithDesktopComposite not implemented on this platform return true; } + + olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const + { + return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + } bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) { @@ -9476,6 +9909,11 @@ namespace olc::host return pWindow->olc_OnMouseFocus(bHasFocus); } + bool Host_Web_Emscripten::olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed) + { + return pWindow->olc_OnKeyPress(key, bPressed); + } + bool Host_Web_Emscripten::olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos) { return pWindow->olc_OnWindowPosition(vWindowPos); @@ -9495,74 +9933,629 @@ namespace olc::host } #endif -#define PGE_HOST_IMPLEMENTED 1 -#endif +#if OLC_HOST == OLC_HOST_ANDROID +#include +#include -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) -#if OLC_GPU == OLC_GPU_OPENGL33 -namespace olc +namespace olc::host { - gpu::Shader::~Shader() - { - } - - void gpu::Shader::SetPixelShaderSource(const std::string& src) - { - srcPixelShader = src; - } + constexpr size_t ANDROID_KEY_MAX = 163; - void gpu::Shader::SetVertexShaderSource(const std::string& src) - { - srcVertexShader = src; - } - void gpu::Shader::SetGeometryShaderSource(const std::string& src) + struct KeyEntry { - srcGeometryShader = src; - } + olc::Key key; + bool shiftOn; + }; - int32_t gpu::Shader::GetUniform(const std::string& name) const - { - if (mapUniforms.contains(name)) - return int32_t(mapUniforms.at(name)); - else - return -1; - } + static const std::array AndroidKeyMap = [] { + std::array map{}; + map.fill({Key::NONE, false}); + + // Letters + map[AKEYCODE_A] = {Key::A, false}; + map[AKEYCODE_B] = {Key::B, false}; + map[AKEYCODE_C] = {Key::C, false}; + map[AKEYCODE_D] = {Key::D, false}; + map[AKEYCODE_E] = {Key::E, false}; + map[AKEYCODE_F] = {Key::F, false}; + map[AKEYCODE_G] = {Key::G, false}; + map[AKEYCODE_H] = {Key::H, false}; + map[AKEYCODE_I] = {Key::I, false}; + map[AKEYCODE_J] = {Key::J, false}; + map[AKEYCODE_K] = {Key::K, false}; + map[AKEYCODE_L] = {Key::L, false}; + map[AKEYCODE_M] = {Key::M, false}; + map[AKEYCODE_N] = {Key::N, false}; + map[AKEYCODE_O] = {Key::O, false}; + map[AKEYCODE_P] = {Key::P, false}; + map[AKEYCODE_Q] = {Key::Q, false}; + map[AKEYCODE_R] = {Key::R, false}; + map[AKEYCODE_S] = {Key::S, false}; + map[AKEYCODE_T] = {Key::T, false}; + map[AKEYCODE_U] = {Key::U, false}; + map[AKEYCODE_V] = {Key::V, false}; + map[AKEYCODE_W] = {Key::W, false}; + map[AKEYCODE_X] = {Key::X, false}; + map[AKEYCODE_Y] = {Key::Y, false}; + map[AKEYCODE_Z] = {Key::Z, false}; + + // Numbers + map[AKEYCODE_0] = {Key::K0, false}; + map[AKEYCODE_1] = {Key::K1, false}; + map[AKEYCODE_2] = {Key::K2, false}; + map[AKEYCODE_3] = {Key::K3, false}; + map[AKEYCODE_4] = {Key::K4, false}; + map[AKEYCODE_5] = {Key::K5, false}; + map[AKEYCODE_6] = {Key::K6, false}; + map[AKEYCODE_7] = {Key::K7, false}; + map[AKEYCODE_8] = {Key::K8, false}; + map[AKEYCODE_9] = {Key::K9, false}; + + // Numpad + map[AKEYCODE_NUMPAD_0] = {Key::NP0, false}; + map[AKEYCODE_NUMPAD_1] = {Key::NP1, false}; + map[AKEYCODE_NUMPAD_2] = {Key::NP2, false}; + map[AKEYCODE_NUMPAD_3] = {Key::NP3, false}; + map[AKEYCODE_NUMPAD_4] = {Key::NP4, false}; + map[AKEYCODE_NUMPAD_5] = {Key::NP5, false}; + map[AKEYCODE_NUMPAD_6] = {Key::NP6, false}; + map[AKEYCODE_NUMPAD_7] = {Key::NP7, false}; + map[AKEYCODE_NUMPAD_8] = {Key::NP8, false}; + map[AKEYCODE_NUMPAD_9] = {Key::NP9, false}; + map[AKEYCODE_NUMPAD_ADD] = {Key::NP_ADD, false}; + map[AKEYCODE_NUMPAD_SUBTRACT] = {Key::NP_SUB, false}; + map[AKEYCODE_NUMPAD_MULTIPLY] = {Key::NP_MUL, false}; + map[AKEYCODE_NUMPAD_DIVIDE] = {Key::NP_DIV, false}; + map[AKEYCODE_NUMPAD_DOT] = {Key::NP_DECIMAL, false}; + + // Function keys + map[AKEYCODE_F1] = {Key::F1, false}; + map[AKEYCODE_F2] = {Key::F2, false}; + map[AKEYCODE_F3] = {Key::F3, false}; + map[AKEYCODE_F4] = {Key::F4, false}; + map[AKEYCODE_F5] = {Key::F5, false}; + map[AKEYCODE_F6] = {Key::F6, false}; + map[AKEYCODE_F7] = {Key::F7, false}; + map[AKEYCODE_F8] = {Key::F8, false}; + map[AKEYCODE_F9] = {Key::F9, false}; + map[AKEYCODE_F10] = {Key::F10, false}; + map[AKEYCODE_F11] = {Key::F11, false}; + map[AKEYCODE_F12] = {Key::F12, false}; + + // Arrows + map[AKEYCODE_DPAD_UP] = {Key::UP, false}; + map[AKEYCODE_DPAD_DOWN] = {Key::DOWN, false}; + map[AKEYCODE_DPAD_LEFT] = {Key::LEFT, false}; + map[AKEYCODE_DPAD_RIGHT] = {Key::RIGHT, false}; + + // Common keys + map[AKEYCODE_SPACE] = {Key::SPACE, false}; + map[AKEYCODE_TAB] = {Key::TAB, false}; + map[AKEYCODE_ENTER] = {Key::ENTER, false}; + map[AKEYCODE_ESCAPE] = {Key::ESCAPE, false}; + map[AKEYCODE_DEL] = {Key::BACK, false}; + map[AKEYCODE_FORWARD_DEL] = {Key::DEL, false}; + + // Modifiers + map[AKEYCODE_SHIFT_LEFT] = {Key::SHIFT, false}; + map[AKEYCODE_SHIFT_RIGHT] = {Key::SHIFT, false}; + map[AKEYCODE_CTRL_LEFT] = {Key::CTRL, false}; + map[AKEYCODE_CTRL_RIGHT] = {Key::CTRL, false}; + map[AKEYCODE_CAPS_LOCK] = {Key::CAPS_LOCK, false}; + + // OEM-style punctuation + map[AKEYCODE_SEMICOLON] = {Key::OEM_1, false}; + map[AKEYCODE_SLASH] = {Key::OEM_2, false}; + map[AKEYCODE_GRAVE] = {Key::OEM_7, false}; + map[AKEYCODE_LEFT_BRACKET] = {Key::OEM_4, false}; + map[AKEYCODE_BACKSLASH] = {Key::OEM_5, false}; + map[AKEYCODE_RIGHT_BRACKET] = {Key::OEM_6, false}; + map[AKEYCODE_APOSTROPHE] = {Key::OEM_3, false}; + map[AKEYCODE_EQUALS] = {Key::EQUALS, false}; + map[AKEYCODE_COMMA] = {Key::COMMA, false}; + map[AKEYCODE_MINUS] = {Key::MINUS, false}; + map[AKEYCODE_PERIOD] = {Key::PERIOD, false}; + map[AKEYCODE_PLUS] = {Key::EQUALS, true}; + map[AKEYCODE_AT] = {Key::K2, true}; + map[AKEYCODE_POUND] = {Key::K3, true}; + map[AKEYCODE_STAR] = {Key::NP_MUL, false}; + + return map; + }(); + + AndroidApp* Host_Android::androidApp = nullptr; + JavaVM* JNI::jvm = nullptr; + + static void Android_onAppCmd(struct android_app* app, int32_t cmd) + { + auto host = reinterpret_cast(app->userData); + host->OnAppCmd(app, cmd); + } + + static int32_t Android_onInputEvent(struct android_app* app, AInputEvent* event) + { + auto host = reinterpret_cast(app->userData); + return host->OnInputEvent(app, event); + } + + bool Host_Android::StartSystemEventLoop(bool bBlockIfPossible) + { + int events; + struct android_poll_source* source = nullptr; + + if (ALooper_pollOnce( + bBlockIfPossible || !initialized ? -1 : 0, + nullptr, + &events, + (void**)&source + ) >= 0) { + if (source) source->process(androidApp, source); + } - uint32_t gpu::Shader::GetShaderID() const - { - return nCompiledShaderID; - } + if (!androidApp || !initialized) return true; - std::string gpu::Shader::PS_DefaultHeader() - { - return static_PS_DefaultHeader; - } + if (androidApp->destroyRequested != 0) return false; - std::string gpu::Shader::PS_DefaultMain() - { - return static_PS_DefaultMain; + return true; } - std::string gpu::Shader::VS_DefaultHeader() + void Host_Android::OnAppCmd(struct android_app *app, int32_t cmd) { - return static_VS_DefaultHeader; + auto host = reinterpret_cast(app->userData); + switch (cmd) { + case APP_CMD_WINDOW_RESIZED: + host->pgeWindow->olc_OnWindowSize({ + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window) + }); + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", + "APP_CMD_WINDOW_RESIZED received: %dx%d", + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); + break; + case APP_CMD_INIT_WINDOW: + if (app->window) { + host->initialized = true; + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", + "APP_CMD_INIT_WINDOW received with Window"); + } + break; + case APP_CMD_TERM_WINDOW: { + host->pgeWindow->olc_OnWindowClose(); + } break; + case APP_CMD_GAINED_FOCUS: { + host->pgeWindow->olc_OnMouseFocus(true); + } break; + case APP_CMD_LOST_FOCUS: { + host->pgeWindow->olc_OnMouseFocus(false); + } break; + default: break; + } } - std::string gpu::Shader::VS_DefaultMain() + int32_t Host_Android::OnInputEvent(AndroidApp *app, AInputEvent *event) { - return static_VS_DefaultMain; - } + auto host = reinterpret_cast(app->userData); + 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)), + }); - std::string gpu::Shader::GS_DefaultHeader() - { - return static_GS_DefaultHeader; - } + auto action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; - std::string gpu::Shader::GS_DefaultMain() - { - return static_GS_DefaultMain; - } + 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; + } + + return 1; + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + int32_t keyCode = AKeyEvent_getKeyCode(event); + int32_t action = AKeyEvent_getAction(event); + int32_t meta = AKeyEvent_getMetaState(event); + + auto entry = ((keyCode > 0) && keyCode < ANDROID_KEY_MAX) ? AndroidKeyMap[keyCode] : KeyEntry{Key::NONE, false}; + + shiftOn = + (meta & AMETA_SHIFT_ON) != 0 || + (meta & AMETA_SHIFT_LEFT_ON) != 0 || + (meta & AMETA_SHIFT_RIGHT_ON) != 0 || + (meta & AMETA_CAPS_LOCK_ON) != 0 || + entry.shiftOn; + + if (entry.key != Key::NONE) + { + pgeWindow->olc_OnKeyPress(Key::SHIFT, shiftOn); + if (action == AKEY_EVENT_ACTION_DOWN) + { + pgeWindow->olc_OnKeyPress(entry.key, true); + } + else if (action == AKEY_EVENT_ACTION_UP) + { + pgeWindow->olc_OnKeyPress(entry.key, false); + } + } + + if (keyCode == AKEYCODE_POWER) + { + // Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS + // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS + return 0; + } + else if ((keyCode == AKEYCODE_BACK) || (keyCode == AKEYCODE_MENU)) + { + return 1; + } + else if ((keyCode == AKEYCODE_VOLUME_UP) || (keyCode == AKEYCODE_VOLUME_DOWN)) + { + return 0; + } + + return 0; + } + + return 0; + } + + bool Host_Android::AddWindowFrame(olc::Window *pWindow, const vi2d &vWindowPos, + const vi2d &vWindowSize, const bool bFullScreen) + { + pgeWindow = pWindow; + return true; + } + + bool Host_Android::CloseWindowFrame(olc::Window *pWindow) + { + return true; + } + + bool Host_Android::UpdateWindowFrameTitle(olc::Window *pWindow) + { + return true; + } + + std::vector Host_Android::GetHostWindowDescriptor(olc::Window *pWindow) + { + return { reinterpret_cast(androidApp->window) }; + } + + bool Host_Android::ConnectHostResourceToRenderer() + { + return true; + } + + bool Host_Android::SyncWithDesktopComposite() + { + return true; + } + + olc::KeyboardLayout Host_Android::GetKeyboardLayout() const + { + JNIObject activity(androidApp->activity->clazz); + JNIEnv* env = JNI::GetEnv(); + + auto param = JNIString("input_method"); + JNIObject imm = activity.Call( + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + *param + ); + + std::string result = ""; + + if (*imm) { + JNIObject subType = imm.Call( + "getCurrentInputMethodSubtype", + "()Landroid/view/inputmethod/InputMethodSubtype;" + ); + if (*subType) { + JNIObject localeStr = subType.Call( + "getLocale", + "()Ljava/lang/String;" + ); + + if (*localeStr) { + JNIString localeStrObj(static_cast(*localeStr)); + result = localeStrObj; + } + } + } + + JNI::Detach(); + + auto countryCodePos = result.substr(result.find('_') + 1); + if (countryCodePos.find("US") != std::string::npos) + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (countryCodePos.find("UK") != std::string::npos || + countryCodePos.find("GB") != std::string::npos) + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (countryCodePos.find("DE") != std::string::npos) + { + return olc::KeyboardLayout::QWERTZ; + } + else if (countryCodePos.find("FR") != std::string::npos) + { + return olc::KeyboardLayout::AZERTY; + } + +#ifdef PGE_SPELL_CORRECTLY + return olc::KeyboardLayout::QWERTY_UK; +#else + return olc::KeyboardLayout::QWERTY_US; +#endif + } + + void Host_Android::ShowKeyboard(bool bShow) + { + JNIObject activity(androidApp->activity->clazz); + + auto param = JNIString("input_method"); + auto imm = activity.Call( + "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + *param + ); + if (!imm) return; + + auto window = activity.Call( + "getWindow", + "()Landroid/view/Window;" + ); + if (!window) return; + + auto decor = window.Call( + "getDecorView", + "()Landroid/view/View;" + ); + if (!decor) return; + + if (bShow) + { + decor.Call( + "requestFocus", + "()Z" + ); + imm.Call( + "showSoftInput", + "(Landroid/view/View;I)Z", + *decor, + 0 + ); + } + else + { + auto token = decor.Call( + "getWindowToken", + "()Landroid/os/IBinder;" + ); + if (!token) return; + imm.Call( + "hideSoftInputFromWindow", + "(Landroid/os/IBinder;I)Z", + *token, + 0 + ); + } + + JNI::Detach(); + } + + Host_Android::Host_Android() + { + androidApp->onAppCmd = Android_onAppCmd; + androidApp->onInputEvent = Android_onInputEvent; + androidApp->userData = this; + } + + void JNI::Init(AndroidApp *app) + { + jvm = app->activity->vm; + } + + void JNI::Detach() + { + jvm->DetachCurrentThread(); + } + + JNIEnv* JNI::GetEnv() + { + JNIEnv* env = nullptr; + if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) + { + jvm->AttachCurrentThread(&env, nullptr); + } + return env; + } + + JNIObject::JNIObject(jobject obj) + { + this->obj = JNI::GetEnv()->NewGlobalRef(obj); + } + + JNIObject::~JNIObject() + { + if (obj) + { + JNI::GetEnv()->DeleteGlobalRef(obj); + } + obj = nullptr; + } + + JNIObject::JNIObject(JNIObject&& other) noexcept + { + obj = other.obj; + other.obj = nullptr; + } + + JNIObject &JNIObject::operator=(JNIObject&& other) noexcept + { + if (this != &other) { + if (obj) JNI::GetEnv()->DeleteGlobalRef(obj); + obj = other.obj; + other.obj = nullptr; + } + return *this; + } + + std::vector Host_Android::OpenFile(const std::string &sFileName) + { + AAsset* asset = AAssetManager_open( + androidApp->activity->assetManager, + sFileName.c_str(), + AASSET_MODE_STREAMING + ); + if (!asset) { + return {}; + } + + off_t size = AAsset_getLength(asset); + std::vector buffer(size); + AAsset_read(asset, buffer.data(), size); + AAsset_close(asset); + + return buffer; + } + + std::string Host_Android::OpenTextFile(const std::string &sFileName) + { + AAsset* asset = AAssetManager_open( + androidApp->activity->assetManager, + sFileName.c_str(), + AASSET_MODE_STREAMING + ); + if (!asset) { + return {}; + } + + off_t size = AAsset_getLength(asset); + std::string content(size, '\0'); + AAsset_read(asset, content.data(), size); + AAsset_close(asset); + + return content; + } +} + +void android_main(struct android_app* app) +{ + char arg0[] = "olcPixelGameEngine 3.0"; + char* argv[] = { arg0, nullptr }; + + olc::host::JNI::Init(app); + olc::host::Host_Android::androidApp = app; + + (void)main(1, argv); + + ANativeActivity_finish(app->activity); + + while (!app->destroyRequested) { + int events; + struct android_poll_source* source; + + while (ALooper_pollOnce(0, nullptr, &events, (void**)&source) > ALOOPER_POLL_TIMEOUT) { + if (source) { + source->process(app, source); + } + } + } +} +#endif + +#define PGE_HOST_IMPLEMENTED 1 +#endif + +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) +#if OLC_GPU == OLC_GPU_OPENGL33 +namespace olc +{ + gpu::Shader::~Shader() + { + } + + void gpu::Shader::SetPixelShaderSource(const std::string& src) + { + srcPixelShader = src; + } + + void gpu::Shader::SetVertexShaderSource(const std::string& src) + { + srcVertexShader = src; + } + + void gpu::Shader::SetGeometryShaderSource(const std::string& src) + { + srcGeometryShader = src; + } + + int32_t gpu::Shader::GetUniform(const std::string& name) const + { + if (mapUniforms.contains(name)) + return int32_t(mapUniforms.at(name)); + else + return -1; + } + + uint32_t gpu::Shader::GetShaderID() const + { + return nCompiledShaderID; + } + + std::string gpu::Shader::PS_DefaultHeader() + { + return static_PS_DefaultHeader; + } + + std::string gpu::Shader::PS_DefaultMain() + { + return static_PS_DefaultMain; + } + + std::string gpu::Shader::VS_DefaultHeader() + { + return static_VS_DefaultHeader; + } + + std::string gpu::Shader::VS_DefaultMain() + { + return static_VS_DefaultMain; + } + + std::string gpu::Shader::GS_DefaultHeader() + { + return static_GS_DefaultHeader; + } + + std::string gpu::Shader::GS_DefaultMain() + { + return static_GS_DefaultMain; + } } namespace olc::apis::opengl @@ -9594,18 +10587,24 @@ namespace olc::apis::opengl bLoaded &= (_glVertexAttribPointer = OGL_LOAD(glVertexAttribPointer)) != nullptr; bLoaded &= (_glEnableVertexAttribArray = OGL_LOAD(glEnableVertexAttribArray)) != nullptr; bLoaded &= (_glUseProgram = OGL_LOAD(glUseProgram)) != nullptr; -#if OLC_HOST != OLC_HOST_EMSCRIPTEN - bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; - bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; - bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; - bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; - bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; -#else - bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArrayOES)) != nullptr; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArrayOES)) != nullptr; bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArraysOES)) != nullptr; bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffersEXT)) != nullptr; // bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; +#elif OLC_HOST == OLC_HOST_ANDROID + bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; + bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; + bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; + // bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; + bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; +#else + bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; + bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; + bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; + bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; + bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; #endif bLoaded &= (_glGetShaderInfoLog = OGL_LOAD(glGetShaderInfoLog)) != nullptr; bLoaded &= (_glGetUniformLocation = OGL_LOAD(glGetUniformLocation)) != nullptr; @@ -9662,10 +10661,12 @@ namespace olc::apis::opengl sLocation << "OGL33 Error: GL_INVALID_FRAMEBUFFER_OPERATION\n"; break; case GL_OUT_OF_MEMORY: sLocation << "OGL33 Error: GL_OUT_OF_MEMORY\n"; break; +#if OLC_HOST != OLC_HOST_ANDROID case GL_STACK_UNDERFLOW: sLocation << "OGL33 Error: GL_STACK_UNDERFLOW\n"; break; case GL_STACK_OVERFLOW: sLocation << "OGL33 Error: GL_STACK_OVERFLOW\n"; break; +#endif } bWasError = true; @@ -9698,8 +10699,10 @@ namespace olc::apis::opengl void gl::glTexEnvf(GLenum target, GLenum pname, GLfloat param) { +#if OLC_HOST != OLC_HOST_ANDROID ::glTexEnvf(target, pname, param); CheckError(); +#endif } void gl::glDeleteTextures(GLsizei n, const GLuint* textures) @@ -9776,7 +10779,7 @@ namespace olc::apis::opengl void gl::glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID ::glGetTexImage(target, level, format, type, pixels); CheckError(); #endif @@ -9790,7 +10793,7 @@ namespace olc::apis::opengl void gl::glPolygonMode(GLenum face, GLenum mode) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID ::glPolygonMode(face, mode); CheckError(); #endif @@ -9987,7 +10990,7 @@ namespace olc::apis::opengl void gl::glTexImage2DMultisample(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID _glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); CheckError(); #endif @@ -10046,7 +11049,7 @@ namespace olc::gpu // === PIXEL SHADER PGE DEFAULTS === std::string Shader::static_PS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID R"(#version 330 core )" #else @@ -10085,7 +11088,7 @@ void main() // === VERTEX SHADER PGE DEFAULTS === std::string Shader::static_VS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID R"(#version 330 core )" #else @@ -10318,9 +11321,13 @@ void main() #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - EGLNativeWindowType window_handle = NULL; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID + #if OLC_HOST == OLC_HOST_ANDROID + EGLNativeWindowType window_handle = reinterpret_cast(os_win_id[0]); + #else + EGLNativeWindowType window_handle = NULL; + #endif EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY; #else const auto wayland_window = reinterpret_cast(os_win_id[0]); @@ -10328,23 +11335,39 @@ void main() EGLNativeDisplayType display = reinterpret_cast(os_win_id[1]); #endif - EGLint const attribute_list[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 16, EGL_SAMPLE_BUFFERS, 1, EGL_SAMPLES, OLC_MSAA_SAMPLES, EGL_NONE}; - EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; - EGLint num_config; - glRenderContext.display = eglGetDisplay(display); if(glRenderContext.display == EGL_NO_DISPLAY) { std::cout << "Could not create EGL Display" << std::endl; } + eglInitialize(glRenderContext.display, nullptr, nullptr); + +#if OLC_HOST == OLC_HOST_ANDROID + glRenderContext.config = FindBestConfig(glRenderContext.display, OLC_MSAA_SAMPLES); +#else + EGLint num_config; + EGLint const attribute_list[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_SAMPLE_BUFFERS, 1, + EGL_SAMPLES, OLC_MSAA_SAMPLES, + EGL_NONE + }; eglChooseConfig(glRenderContext.display, attribute_list, &glRenderContext.config, 1, &num_config); - +#endif + + EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; + /* create an EGL rendering context */ 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) { std::cout << "Could not create EGL Surface" << std::endl; } + if(!eglMakeCurrent(glRenderContext.display, glRenderContext.surface, glRenderContext.surface, glRenderContext.context)) { lastError = RendererError::FailedToCreateRenderContext; @@ -10367,12 +11390,20 @@ void main() // Enable VSync - lock to display refresh // May also be governed by OS / driver settings // and desktop compositor settings +#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) gl.glSwapInterval(1); +#else + eglSwapInterval(glRenderContext.display, 1); +#endif } else { // Disable VSync - run like the clappers! +#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) gl.glSwapInterval(0); +#else + eglSwapInterval(glRenderContext.display, 0); +#endif } @@ -10469,7 +11500,7 @@ void main() gl.glGenFramebuffers(1, &nResolveFBO_Draw); gl.glGenFramebuffers(1, &nResolveFBO_Read); -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID gl.glEnable(GL_TEXTURE_2D); // Turn on texturing gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); #endif @@ -10495,7 +11526,7 @@ void main() X11::glXMakeCurrent(display, 0, NULL); X11::glXDestroyContext(display, glRenderContext); #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID eglMakeCurrent(glRenderContext.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(glRenderContext.display, glRenderContext.context); eglDestroySurface(glRenderContext.display, glRenderContext.surface); @@ -10536,13 +11567,13 @@ void main() return false; } #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID if(!eglMakeCurrent(glRenderContext.display, glRenderContext.surface, glRenderContext.surface, glRenderContext.context)) { lastError = RendererError::FailedToSwitchRenderContext; return false; } -#endif +#endif return true; } @@ -10642,10 +11673,8 @@ void main() mapTextureToRenderbuffer[id] = rboId; } -#if OLC_HOST != OLC_HOST_EMSCRIPTEN -#if OLC_HOST != OLC_HOST_MACOS +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_MACOS && OLC_HOST != OLC_HOST_ANDROID gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); -#endif #endif mapTextureSizes[id] = vSize; @@ -10700,7 +11729,7 @@ void main() // which has been blitted to via ResolveMSAA if its an MSAA texture gl.glBindTexture(GL_TEXTURE_2D, image.GetGPUID()); -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID gl.glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); #else gl.glReadPixels(0, 0, image.Size().x, image.Size().y, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); @@ -11152,20 +12181,64 @@ void main() X11::glXSwapBuffers(display, window_handle); #endif -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - const auto* wayland_window = reinterpret_cast(os_win_id[0]); - //auto* window_handle = wayland_window->window; - //auto* display = reinterpret_cast(os_win_id[1]); +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND eglSwapBuffers(glRenderContext.display, glRenderContext.surface); #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - eglSwapInterval(glRenderContext.display, bVerticalSyncNow ? 1 : 0); +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID + eglSwapBuffers(glRenderContext.display, glRenderContext.surface); #endif return true; } +#if OLC_HOST == OLC_HOST_ANDROID + EGLConfig Renderer_OGL33::FindBestConfig(EGLDisplay display, int desiredMultisamples) + { + EGLint numConfigs; + EGLConfig bestConfig = nullptr; + EGLConfig fallbackConfig = nullptr; + + // Define attribute list for desired configuration + EGLint const attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + EGL_NONE + }; + + // Get all matching configurations + eglChooseConfig(display, attribs, nullptr, 0, &numConfigs); + + std::vector configs(numConfigs); + eglChooseConfig(display, attribs, configs.data(), numConfigs, &numConfigs); + + // Evaluate configurations to find the best that matches desired multisampling + for (const auto& config : configs) { + EGLint sampleBuffers = 0; + EGLint samples = 0; + + eglGetConfigAttrib(display, config, EGL_SAMPLE_BUFFERS, &sampleBuffers); + eglGetConfigAttrib(display, config, EGL_SAMPLES, &samples); + + if (sampleBuffers > 0 && samples == desiredMultisamples) { + bestConfig = config; + break; // Found the best match + } + + // Keep track of a fallback configuration + if (fallbackConfig == nullptr) { + fallbackConfig = config; + } + } + + return bestConfig ? bestConfig : fallbackConfig; + } +#endif } #endif @@ -13292,7 +14365,6 @@ namespace olc config.vScreenSize = vScreenSize; config.vPixelSize = vPixelSize; config.bFullScreen = bFullScreen; - return true; } @@ -13320,7 +14392,11 @@ namespace olc #endif #if OLC_HOST == OLC_HOST_EMSCRIPTEN host = std::make_unique(); - #endif + #endif + #if OLC_HOST == OLC_HOST_ANDROID + host = std::make_unique(); + auto hostPtr = (dynamic_cast(host.get())); + #endif #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO // Create OS window on this thread host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); @@ -13328,7 +14404,8 @@ namespace olc // at least to initialise teh rendering subsystem... sigh. coreActive = true; -#if OLC_HOST != OLC_HOST_EMSCRIPTEN +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID + // Create EngineThread coreThread = std::thread(&PixelGameEngine::EngineThread, this); // Handle window events on this thread (and block) host->StartSystemEventLoop(true); @@ -13336,8 +14413,18 @@ namespace olc coreActive = false; // Wait for engine thread to terminate coreThread.join(); +#elif OLC_HOST == OLC_HOST_ANDROID + // We need to wait for the APP_CMD_INIT_WINDOW command before starting the loop + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initializing..."); + while (!hostPtr->IsInitialized()) { + host->StartSystemEventLoop(false); + } + + __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initialized Successfully"); + + EngineThread(); #else - EngineThread(); + EngineThread(); #endif #else @@ -13474,104 +14561,113 @@ namespace olc } } - } - - void PixelGameEngine::EngineThread() - { - using namespace std::chrono_literals; - timeFrame2 = std::chrono::steady_clock::now(); - timeFrame1 = std::chrono::steady_clock::now(); + } + + void PixelGameEngine::EngineThread() + { + using namespace std::chrono_literals; + timeFrame2 = std::chrono::steady_clock::now(); + timeFrame1 = std::chrono::steady_clock::now(); #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Create Primary Window on EngineThread, event loop also exists for all windows - // on this thread, and all windows will be created on this thread - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); + // Create Primary Window on EngineThread, event loop also exists for all windows + // on this thread, and all windows will be created on this thread + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); #endif - - // Initialise ImageLoader Interface - #if OLC_HOST == OLC_HOST_WINDOWS - imageloader = std::make_unique(); - #endif - // Initialise ImageLoader Interface - #if OLC_HOST == OLC_HOST_MACOS - imageloader = std::make_unique(); - #endif + // Initialise ImageLoader Interface +#if OLC_HOST == OLC_HOST_WINDOWS + imageloader = std::make_unique(); +#endif - #if OLC_HOST == OLC_HOST_LINUX_X11 - imageloader = std::make_unique(); - #endif + // Initialise ImageLoader Interface +#if OLC_HOST == OLC_HOST_MACOS + imageloader = std::make_unique(); +#endif - #if OLC_HOST == OLC_HOST_LINUX_WAYLAND - imageloader = std::make_unique(); - #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + imageloader = std::make_unique(); +#endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN - imageloader = std::make_unique(); - #endif +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + imageloader = std::make_unique(); +#endif - // Initialise GPU Interface - This thread is the context - olc::gpu::RendererConfig cfgRenderer; - cfgRenderer.VerticalSync = config.bVSync; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + imageloader = std::make_unique(); +#endif - gpu = std::make_unique(); +#if OLC_HOST == OLC_HOST_ANDROID + imageloader = std::make_unique( + olc::host::Host_Android::androidApp->activity->assetManager + ); +#endif - // Link this windows devices - LinkToHost(host.get()); - LinkToRenderer(gpu.get()); - LinkToImageLoader(imageloader.get()); + // Initialise GPU Interface - This thread is the context + olc::gpu::RendererConfig cfgRenderer; + cfgRenderer.VerticalSync = config.bVSync; - // The GPU device can be based upon the primary window configuration. This - // gives us completed gpu and host objects to pass to other windows as and - // when required - gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); - if (gpu->GetLastError() != olc::gpu::RendererError::NoError) - { - //const auto e = gpu->GetLastError(); // For debug visibility - std::cout << "Error: Could not create Renderer\n"; - return; - } + gpu = std::make_unique(); + // Link this windows devices + LinkToHost(host.get()); + LinkToRenderer(gpu.get()); + LinkToImageLoader(imageloader.get()); - - olc::ImageConfig cfg; - cfg.MSAA = config.bAntiAliasMainScreen; - CreateImage(GetDefaultImage(), config.vScreenSize, cfg); - + // The GPU device can be based upon the primary window configuration. This + // gives us completed gpu and host objects to pass to other windows as and + // when required + gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); + if (gpu->GetLastError() != olc::gpu::RendererError::NoError) + { + //const auto e = gpu->GetLastError(); // For debug visibility + std::cout << "Error: Could not create Renderer\n"; + return; + } - // Initialise Font System - olc::pgeguts::CreateClassicFont(this); - draw.SetGPU(gpu.get()); - gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + olc::ImageConfig cfg; + cfg.MSAA = config.bAntiAliasMainScreen; + CreateImage(GetDefaultImage(), config.vScreenSize, cfg); - if (!OnUserCreate()) - { - // Creation process signalled abort - return; - } + // Initialise Font System + olc::pgeguts::CreateClassicFont(this); - - draw.ProcessGPUTasks(); - draw.SetTarget(GetDefaultImage()); - // Initialise Input Devices + draw.SetGPU(gpu.get()); + gpu->ApplyDefaultShader(); + draw.SetTarget(GetDefaultImage()); + if (!OnUserCreate()) + { + // Creation process signalled abort + return; + } - durationFrameCount = 0s; + draw.ProcessGPUTasks(); + draw.SetTarget(GetDefaultImage()); - #if OLC_HOST == OLC_HOST_EMSCRIPTEN - emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); - #else - while (coreActive) - { - PixelGameEngine::CoreUpdate(this); - } - #endif - } + // Initialise Input Devices + + + durationFrameCount = 0s; + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); +#else + while (coreActive) + { +#if OLC_HOST == OLC_HOST_ANDROID + if (!host->StartSystemEventLoop(false)) { + coreActive = false; + } +#endif + PixelGameEngine::CoreUpdate(this); + } +#endif + } } #define PGE_CORE_IMPLEMENTED 1 #endif @@ -14493,6 +15589,152 @@ namespace olc::imload } #endif +#if OLC_HOST == OLC_HOST_ANDROID +#include +#include +#include + +namespace olc::imload +{ + bool ImageLoader_NDKImageDecoder::CreateImageFromFile(olc::Image& image, const std::string& sFileName) + { + AAsset* asset = AAssetManager_open( + assetManager, + sFileName.c_str(), + AASSET_MODE_BUFFER + ); + if (!asset) { + return false; + } + + AImageDecoder* decoder = nullptr; + int status = AImageDecoder_createFromAAsset( + asset, + &decoder + ); + + if (status != ANDROID_IMAGE_DECODER_SUCCESS || !decoder) { + AAsset_close(asset); + return false; + } + + const AImageDecoderHeaderInfo* info = + AImageDecoder_getHeaderInfo(decoder); + if (!info) { + AImageDecoder_delete(decoder); + AAsset_close(asset); + return false; + } + + image.Create({ + AImageDecoderHeaderInfo_getWidth(info), + AImageDecoderHeaderInfo_getHeight(info) + }); + + AImageDecoder_setAndroidBitmapFormat( + decoder, + ANDROID_BITMAP_FORMAT_RGBA_8888 + ); + AImageDecoder_setUnpremultipliedRequired(decoder, true); + + std::vector pixels(image.Size().x * image.Size().y * 4); + + status = AImageDecoder_decodeImage( + decoder, + pixels.data(), + image.Size().x * 4, + pixels.size() + ); + + AImageDecoder_delete(decoder); + AAsset_close(asset); + + for (size_t i = 0; i < pixels.size(); i += 4) { + int32_t x = static_cast(i / 4) % image.Size().x; + int32_t y = static_cast(i / 4) / image.Size().x; + uint8_t r = pixels[i]; + uint8_t g = pixels[i + 1]; + uint8_t b = pixels[i + 2]; + uint8_t a = pixels[i + 3]; + image.Pixel({x, y}) = olc::Pixel(r, g, b, a); + } + + return status == ANDROID_IMAGE_DECODER_SUCCESS; + } + + bool ImageLoader_NDKImageDecoder::CreateImageFromMemory(Image &image, const uint8_t *data, const size_t bytes) + { + AImageDecoder* decoder = nullptr; + int status = AImageDecoder_createFromBuffer( + data, + bytes, + &decoder + ); + + if (status != ANDROID_IMAGE_DECODER_SUCCESS || !decoder) { + return false; + } + + const AImageDecoderHeaderInfo* info = + AImageDecoder_getHeaderInfo(decoder); + if (!info) { + AImageDecoder_delete(decoder); + return false; + } + + image.Create({ + AImageDecoderHeaderInfo_getWidth(info), + AImageDecoderHeaderInfo_getHeight(info) + }); + + AImageDecoder_setAndroidBitmapFormat( + decoder, + ANDROID_BITMAP_FORMAT_RGBA_8888 + ); + AImageDecoder_setUnpremultipliedRequired(decoder, true); + + std::vector pixels(image.Size().x * image.Size().y * 4); + + status = AImageDecoder_decodeImage( + decoder, + pixels.data(), + image.Size().x * 4, + pixels.size() + ); + + AImageDecoder_delete(decoder); + + for (size_t i = 0; i < pixels.size(); i += 4) { + int32_t x = static_cast(i / 4) % image.Size().x; + int32_t y = static_cast(i / 4) / image.Size().x; + uint8_t r = pixels[i]; + uint8_t g = pixels[i + 1]; + uint8_t b = pixels[i + 2]; + uint8_t a = pixels[i + 3]; + image.Pixel({x, y}) = olc::Pixel(r, g, b, a); + } + + return status == ANDROID_IMAGE_DECODER_SUCCESS; + } + + bool ImageLoader_NDKImageDecoder::CreateImageFromMemory(Image &image, const std::vector &data) + { + return CreateImageFromMemory(image, data.data(), data.size()); + } + + bool ImageLoader_NDKImageDecoder::WriteImageToFile(const Image &image, const std::string &sFileName) + { + // No solution for now + return false; + } + + bool ImageLoader_NDKImageDecoder::WriteImageToMemoryFile(Image &image, const std::vector &data) + { + // No solution for now + return false; + } +} +#endif #define PGE_IMAGELOADER_IMPLEMENTED 1 #endif @@ -14513,4 +15755,3 @@ So you scrolled all this way huh ? In that case: */ // Thank you for using olcPixelGameEngine! :) - From fb8cac2527736fdb3db8110e06b7df1acdfaa6b6 Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Thu, 29 Jan 2026 19:30:54 +0000 Subject: [PATCH 30/56] Query and use screen fbo --- dev/src/api_opengl.cpp | 7 +++++++ dev/src/api_opengl.h | 5 +++-- dev/src/gpu_opengl33.cpp | 8 +++++--- dev/src/gpu_opengl33.h | 1 + olcPixelGameEngine3.h | 21 ++++++++++++++++----- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 61696b9c..64bb3cca 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -71,6 +71,7 @@ namespace olc::apis::opengl bLoaded &= (_glDeleteRenderbuffers = OGL_LOAD(glDeleteRenderbuffers)) != nullptr; bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; + bLoaded &= (_glGetIntegerv = OGL_LOAD(glGetIntegerv)) != nullptr; #if OLC_HOST == OLC_HOST_WINDOWS bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; @@ -487,5 +488,11 @@ namespace olc::apis::opengl _glGetShaderiv(shader, pname, params); CheckError(); } + + void gl::glGetIntegerv(GLenum pname, GLint *data) + { + _glGetIntegerv(pname, data); + CheckError(); + } } //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 498c1f98..b466eef2 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -169,6 +169,7 @@ namespace olc typedef void CALLSTYLE glDeleteRenderbuffers_t(GLsizei n, const GLuint* renderbuffers); typedef void CALLSTYLE glGetInternalformativ_t(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); + typedef void CALLSTYLE glGetIntegerv_t(GLenum pname, GLint *data); #if OLC_HOST == OLC_HOST_WINDOWS typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); @@ -229,6 +230,7 @@ namespace olc glDeleteRenderbuffers_t* _glDeleteRenderbuffers = nullptr; glGetInternalformativ_t* _glGetInternalformativ = nullptr; glGetShaderiv_t* _glGetShaderiv = nullptr; + glGetIntegerv_t *_glGetIntegerv = nullptr; #if OLC_HOST == OLC_HOST_WINDOWS wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; #endif @@ -279,8 +281,7 @@ namespace olc void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); - - + void glGetIntegerv(GLenum pname, GLint *data); // OpenGL1.2 Proxies (just keeps things tidy imo) void glGenTextures(GLsizei n, GLuint* textures); diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 77cd0916..114a78a5 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -341,6 +341,8 @@ void main() return false; } + gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING_X, (GLint *)&nScreenFBO); + // Configure Swap Interval (VSync) if (config.VerticalSync) { @@ -451,7 +453,7 @@ void main() //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[3], GL_TEXTURE_2D, 0, 0); // Unbind the FBO - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nScreenFBO); // Create FBOs for MSAA resolve operations gl.glGenFramebuffers(1, &nResolveFBO_Draw); @@ -734,7 +736,7 @@ void main() #if defined(OLC_GPU_ERRORCHECK) && OLC_GPU_ERRORCHECK == 1 std::cout << "Warning ATS: Requested source is currently attached as target (" << actualTexId << ") - unbinding FBO\n"; #endif - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nScreenFBO); nCurrentTextureTarget = 0; } @@ -781,7 +783,7 @@ void main() if (texid == 0) { // Unbind the FBO (bind default framebuffer) - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nScreenFBO); return true; } diff --git a/dev/src/gpu_opengl33.h b/dev/src/gpu_opengl33.h index c908de5c..4cef8eea 100644 --- a/dev/src/gpu_opengl33.h +++ b/dev/src/gpu_opengl33.h @@ -83,6 +83,7 @@ namespace olc uint32_t nDefaultVB = 0; uint32_t nDefaultVA = 0; uint32_t nDefaultFBO = 0; + uint32_t nScreenFBO = 0; olc::Image imgBlank; olc::vf2d vTargetSize; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d731753f..e7bea250 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5142,6 +5142,7 @@ namespace olc typedef void CALLSTYLE glDeleteRenderbuffers_t(GLsizei n, const GLuint* renderbuffers); typedef void CALLSTYLE glGetInternalformativ_t(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); + typedef void CALLSTYLE glGetIntegerv_t(GLenum pname, GLint *data); #if OLC_HOST == OLC_HOST_WINDOWS typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); @@ -5202,6 +5203,7 @@ namespace olc glDeleteRenderbuffers_t* _glDeleteRenderbuffers = nullptr; glGetInternalformativ_t* _glGetInternalformativ = nullptr; glGetShaderiv_t* _glGetShaderiv = nullptr; + glGetIntegerv_t *_glGetIntegerv = nullptr; #if OLC_HOST == OLC_HOST_WINDOWS wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; #endif @@ -5252,8 +5254,7 @@ namespace olc void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); - - + void glGetIntegerv(GLenum pname, GLint *data); // OpenGL1.2 Proxies (just keeps things tidy imo) void glGenTextures(GLsizei n, GLuint* textures); @@ -5387,6 +5388,7 @@ namespace olc uint32_t nDefaultVB = 0; uint32_t nDefaultVA = 0; uint32_t nDefaultFBO = 0; + uint32_t nScreenFBO = 0; olc::Image imgBlank; olc::vf2d vTargetSize; @@ -10627,6 +10629,7 @@ namespace olc::apis::opengl bLoaded &= (_glDeleteRenderbuffers = OGL_LOAD(glDeleteRenderbuffers)) != nullptr; bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; + bLoaded &= (_glGetIntegerv = OGL_LOAD(glGetIntegerv)) != nullptr; #if OLC_HOST == OLC_HOST_WINDOWS bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; @@ -11043,6 +11046,12 @@ namespace olc::apis::opengl _glGetShaderiv(shader, pname, params); CheckError(); } + + void gl::glGetIntegerv(GLenum pname, GLint *data) + { + _glGetIntegerv(pname, data); + CheckError(); + } } namespace olc::gpu { @@ -11384,6 +11393,8 @@ void main() return false; } + gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING_X, (GLint *)&nScreenFBO); + // Configure Swap Interval (VSync) if (config.VerticalSync) { @@ -11494,7 +11505,7 @@ void main() //gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER_X, attachments[3], GL_TEXTURE_2D, 0, 0); // Unbind the FBO - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nScreenFBO); // Create FBOs for MSAA resolve operations gl.glGenFramebuffers(1, &nResolveFBO_Draw); @@ -11777,7 +11788,7 @@ void main() #if defined(OLC_GPU_ERRORCHECK) && OLC_GPU_ERRORCHECK == 1 std::cout << "Warning ATS: Requested source is currently attached as target (" << actualTexId << ") - unbinding FBO\n"; #endif - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nScreenFBO); nCurrentTextureTarget = 0; } @@ -11824,7 +11835,7 @@ void main() if (texid == 0) { // Unbind the FBO (bind default framebuffer) - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nScreenFBO); return true; } From 2ec1d137f99a9d59c228807e358ffcf1bfdeda59 Mon Sep 17 00:00:00 2001 From: DCubix Date: Thu, 29 Jan 2026 19:50:55 -0400 Subject: [PATCH 31/56] Remove CopyHeader_Android (no longer needed) --- CMakeLists.txt | 8 +- olcPixelGameEngine3.h | 318 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 261 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6fb91ea..935b0ba1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,14 +46,8 @@ add_custom_target(CopyHeader DEPENDS olcPixelGameEngine3.h ) -# copy to Android project as well -add_custom_target(CopyHeader_Android - COMMAND ${CMAKE_COMMAND} -E copy olcPixelGameEngine3.h ${CMAKE_CURRENT_SOURCE_DIR}/dev/android/app/src/main/cpp/olcPixelGameEngine3.h - DEPENDS olcPixelGameEngine3.h -) - add_library(olcPixelGameEngine3 INTERFACE olcPixelGameEngine3.h) -add_dependencies(olcPixelGameEngine3 CopyHeader CopyHeader_Android) +add_dependencies(olcPixelGameEngine3 CopyHeader) if(BUILD_EXAMPLES) add_subdirectory(examples) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d731753f..8f20b594 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3422,6 +3422,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -3470,7 +3471,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup @@ -3595,6 +3596,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -4132,10 +4139,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -4189,10 +4197,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -4205,12 +4213,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { @@ -4388,7 +4396,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -4407,6 +4415,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -4419,6 +4429,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -4464,6 +4477,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } @@ -6072,6 +6091,135 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -6173,6 +6321,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -6319,6 +6496,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -6362,6 +6541,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -6369,43 +6562,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers @@ -6581,6 +6746,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -6645,6 +6816,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -6751,6 +6925,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -7001,6 +7179,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -7027,8 +7206,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -7193,6 +7372,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -7201,6 +7381,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -7211,7 +7392,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7222,7 +7403,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7664,6 +7845,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -7686,10 +7887,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -8327,12 +8529,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } From b0ef6b77aab605be550760188106f27a90ed3655 Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Fri, 30 Jan 2026 00:05:08 +0000 Subject: [PATCH 32/56] Add comment --- dev/src/gpu_opengl33.cpp | 3 + olcPixelGameEngine3.h | 321 ++++++++++++++++++++++++++++++++------- 2 files changed, 266 insertions(+), 58 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 114a78a5..492a735a 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -341,6 +341,9 @@ void main() return false; } + // Store the initial (screen) framebuffer binding + // On most platforms the system provides a default framebuffer of 0 + // However on iOS there is no system buffer and instead a GLKit creates an FBO to use gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING_X, (GLint *)&nScreenFBO); // Configure Swap Interval (VSync) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e7bea250..0b7d1002 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3422,6 +3422,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -3470,7 +3471,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup @@ -3595,6 +3596,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -4132,10 +4139,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -4189,10 +4197,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -4205,12 +4213,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { @@ -4388,7 +4396,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -4407,6 +4415,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -4419,6 +4429,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -4464,6 +4477,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } @@ -6074,6 +6093,135 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -6175,6 +6323,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -6321,6 +6498,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -6364,6 +6543,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -6371,43 +6564,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers @@ -6583,6 +6748,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -6647,6 +6818,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -6753,6 +6927,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -7003,6 +7181,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -7029,8 +7208,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -7195,6 +7374,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -7203,6 +7383,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -7213,7 +7394,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7224,7 +7405,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7666,6 +7847,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -7688,10 +7889,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -8329,12 +8531,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } @@ -11393,6 +11595,9 @@ void main() return false; } + // Store the initial (screen) framebuffer binding + // On most platforms the system provides a default framebuffer of 0 + // However on iOS there is no system buffer and instead a GLKit creates an FBO to use gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING_X, (GLint *)&nScreenFBO); // Configure Swap Interval (VSync) From 1467f91a32b48ca29356ee09fb5b35a6290d6201 Mon Sep 17 00:00:00 2001 From: dandistine <> Date: Thu, 29 Jan 2026 21:21:29 -0600 Subject: [PATCH 33/56] Allow the event loop to end --- dev/src/core.cpp | 1 + dev/src/host_iface.h | 1 + dev/src/host_lin_wayland.cpp | 5 + dev/src/host_lin_wayland.h | 1 + dev/src/host_lin_x11.cpp | 5 + dev/src/host_lin_x11.h | 1 + examples/olcPGE3_Keyboard.cpp | 3 + olcPixelGameEngine3.h | 332 ++++++++++++++++++++++++++++------ 8 files changed, 291 insertions(+), 58 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 6aa13d0f..8517bbfa 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -589,6 +589,7 @@ namespace olc #endif PixelGameEngine::CoreUpdate(this); } + host->TerminateSystemEventLoop(); #endif } } diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index e8d6b7ac..cf44846a 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -82,6 +82,7 @@ namespace olc public: virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) = 0; + virtual void TerminateSystemEventLoop() = 0; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; virtual bool CloseWindowFrame(olc::Window* pWindow) = 0; virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) = 0; diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index afbed611..26aeab6d 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -162,6 +162,11 @@ namespace olc::host return true; } + void Host_Linux_Wayland::TerminateSystemEventLoop() + { + terminate = true; + } + bool Host_Linux_Wayland::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { // Create a window diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index f50b62f9..01b71fdb 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -94,6 +94,7 @@ namespace olc::host ~Host_Linux_Wayland(); bool StartSystemEventLoop(bool bBlockIfPossible = false) override; + void TerminateSystemEventLoop() override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 22941c9a..7aee54af 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -191,6 +191,11 @@ namespace olc::host return true; } + void Host_Linux_X11::TerminateSystemEventLoop() + { + terminate = true; + } + bool Host_Linux_X11::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { // Based on the display capabilities, configure the appearance of the window diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index da753b04..faa73e84 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -35,6 +35,7 @@ namespace olc::host public: Host_Linux_X11(); bool StartSystemEventLoop(bool bBlockIfPossible = false) override; + void TerminateSystemEventLoop() override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; diff --git a/examples/olcPGE3_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp index a3c70d0d..d8467dfb 100644 --- a/examples/olcPGE3_Keyboard.cpp +++ b/examples/olcPGE3_Keyboard.cpp @@ -100,6 +100,9 @@ class Example_Keyboard : public olc::PixelGameEngine "GPU->CPU : " + std::to_string(metrics.nGPUtoCPUTransfers) + "\n" + "Shaders : " + std::to_string(metrics.nShaderChanges)); + if(keyboard.GetKey(olc::Key::ESCAPE).bPressed) + return false; // End application + // Successful frame return true; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d731753f..3c986cb6 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3115,6 +3115,7 @@ namespace olc public: virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) = 0; + virtual void TerminateSystemEventLoop() = 0; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; virtual bool CloseWindowFrame(olc::Window* pWindow) = 0; virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) = 0; @@ -3422,6 +3423,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -3470,7 +3472,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup @@ -3595,6 +3597,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -4132,10 +4140,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -4189,10 +4198,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -4205,12 +4214,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { @@ -4388,7 +4397,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -4407,6 +4416,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -4419,6 +4430,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -4464,6 +4478,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } @@ -4495,6 +4515,7 @@ namespace olc::host public: Host_Linux_X11(); bool StartSystemEventLoop(bool bBlockIfPossible = false) override; + void TerminateSystemEventLoop() override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; @@ -4602,6 +4623,7 @@ namespace olc::host ~Host_Linux_Wayland(); bool StartSystemEventLoop(bool bBlockIfPossible = false) override; + void TerminateSystemEventLoop() override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; @@ -6072,6 +6094,135 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -6173,6 +6324,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -6319,6 +6499,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -6362,6 +6544,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -6369,43 +6565,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers @@ -6581,6 +6749,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -6645,6 +6819,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -6751,6 +6928,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -7001,6 +7182,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -7027,8 +7209,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -7193,6 +7375,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -7201,6 +7384,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -7211,7 +7395,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7222,7 +7406,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7664,6 +7848,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -7686,10 +7890,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -8327,12 +8532,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } @@ -8662,6 +8867,11 @@ namespace olc::host return true; } + void Host_Linux_X11::TerminateSystemEventLoop() + { + terminate = true; + } + bool Host_Linux_X11::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { // Based on the display capabilities, configure the appearance of the window @@ -8906,6 +9116,11 @@ namespace olc::host return true; } + void Host_Linux_Wayland::TerminateSystemEventLoop() + { + terminate = true; + } + bool Host_Linux_Wayland::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { // Create a window @@ -14666,6 +14881,7 @@ namespace olc #endif PixelGameEngine::CoreUpdate(this); } + host->TerminateSystemEventLoop(); #endif } } From 2d3c7a9748a3c1584a83ef1eea0e613bf91c6b1e Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 00:24:16 -0500 Subject: [PATCH 34/56] [single header] update single header --- olcPixelGameEngine3.h | 318 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 260 insertions(+), 58 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d731753f..8f20b594 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3422,6 +3422,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -3470,7 +3471,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup @@ -3595,6 +3596,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -4132,10 +4139,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -4189,10 +4197,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -4205,12 +4213,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { @@ -4388,7 +4396,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -4407,6 +4415,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -4419,6 +4429,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -4464,6 +4477,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } @@ -6072,6 +6091,135 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -6173,6 +6321,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -6319,6 +6496,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -6362,6 +6541,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -6369,43 +6562,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers @@ -6581,6 +6746,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -6645,6 +6816,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -6751,6 +6925,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -7001,6 +7179,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -7027,8 +7206,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -7193,6 +7372,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -7201,6 +7381,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -7211,7 +7392,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7222,7 +7403,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7664,6 +7845,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -7686,10 +7887,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -8327,12 +8529,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } From 06679277b6a3651e24a28691911df7897cbda11b Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 00:26:56 -0500 Subject: [PATCH 35/56] [x11] detect and update keyboard layout --- dev/src/host_lin_x11.cpp | 94 +++++++++++++++++++++++++++++++++++- dev/src/host_lin_x11.h | 7 +++ olcPixelGameEngine3.h | 101 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 198 insertions(+), 4 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 22941c9a..d1aefa99 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -10,6 +10,14 @@ namespace olc::host olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + if(XkbQueryExtension(olc_Display, nullptr, &xkbEventBase, &xkbErrorBase, nullptr, nullptr)) + { + XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); + kbExtensionsFound = true; + UpdateKeyboardLayout(); + } + + mapKeys[NoSymbol] = Key::NONE; int keyTracker = static_cast(Key::A); @@ -86,6 +94,12 @@ namespace olc::host while (XPending(olc_Display)) { XNextEvent(olc_Display, &xev); + + // If there's an update to the keyboard, update it's layout. + if (xev.type == xkbEventBase + XkbEventCode && kbExtensionsFound) + { + UpdateKeyboardLayout(); + } if (xev.type == Expose) { @@ -261,10 +275,86 @@ namespace olc::host } olc::KeyboardLayout Host_Linux_X11::GetKeyboardLayout() const { - // The objectively correct keyboard layout - return olc::KeyboardLayout::QWERTY_US; + return keyboardLayout; } + void Host_Linux_X11::UpdateKeyboardLayout() + { + // Get active group + X11::XkbStateRec state; + X11::XkbGetState(olc_Display, XkbUseCoreKbd, &state); + unsigned int group = state.group; + + // Get symbol name + X11::XkbDescPtr desc = X11::XkbAllocKeyboard(); + desc->dpy = olc_Display; + + if (X11::XkbGetNames(olc_Display, XkbSymbolsNameMask, desc) != Success) + { + X11::XkbFreeKeyboard(desc, 0, True); + return; + } + + const char* symbols = X11::XGetAtomName(olc_Display, desc->names->symbols); + if (!symbols) + { + X11::XkbFreeKeyboard(desc, 0, True); + return; + } + + // Extract layouts in order + // Example: pc_us_gb_2_fr_3_de_4_inet(evdev) + std::vector layouts; + std::string s(symbols); + + size_t start = s.find('_') + 1; + size_t end = s.find("_inet"); + + std::string layoutPart = s.substr(start, end - start); + + size_t pos = 0; + while (pos < layoutPart.size()) + { + size_t next = layoutPart.find('_', pos); + std::string token = layoutPart.substr(pos, next - pos); + + // Remove numeric suffixes (_2, _3, _4) + size_t num = token.find_first_of("0123456789"); + if (num != std::string::npos) + token = token.substr(0, num); + + if(token.size() > 0) + layouts.push_back(token); + + if (next == std::string::npos) + break; + pos = next + 1; + } + + X11::XFree((void*)symbols); + X11::XkbFreeKeyboard(desc, 0, True); + + if (group >= layouts.size()) + return; + + const std::string& layout = layouts[group]; + + std::unordered_map mapLayouts; + mapLayouts["us"] = olc::KeyboardLayout::QWERTY_US; + mapLayouts["gb"] = olc::KeyboardLayout::QWERTY_UK; + mapLayouts["de"] = olc::KeyboardLayout::QWERTZ; + mapLayouts["fr"] = olc::KeyboardLayout::AZERTY; + + auto it = mapLayouts.find(layout); + + if(it != mapLayouts.end()) + { + keyboardLayout = it->second; + return; + } + + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + } // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Linux_X11::SyncWithDesktopComposite() diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index da753b04..d0ff25b4 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -45,6 +45,7 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; olc::KeyboardLayout GetKeyboardLayout() const override; + void UpdateKeyboardLayout(); // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -54,6 +55,12 @@ namespace olc::host std::atomic terminate {false}; std::unordered_map mapKeys; + + // Keyboard Layout Variables + olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + bool kbExtensionsFound = false; + int xkbEventBase = 0; + int xkbErrorBase = 0; }; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 8f20b594..43095df7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4524,6 +4524,7 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; olc::KeyboardLayout GetKeyboardLayout() const override; + void UpdateKeyboardLayout(); // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -4533,6 +4534,12 @@ namespace olc::host std::atomic terminate {false}; std::unordered_map mapKeys; + + // Keyboard Layout Variables + olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + bool kbExtensionsFound = false; + int xkbEventBase = 0; + int xkbErrorBase = 0; }; } @@ -8683,6 +8690,14 @@ namespace olc::host olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + if(XkbQueryExtension(olc_Display, nullptr, &xkbEventBase, &xkbErrorBase, nullptr, nullptr)) + { + XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); + kbExtensionsFound = true; + UpdateKeyboardLayout(); + } + + mapKeys[NoSymbol] = Key::NONE; int keyTracker = static_cast(Key::A); @@ -8759,6 +8774,12 @@ namespace olc::host while (XPending(olc_Display)) { XNextEvent(olc_Display, &xev); + + // If there's an update to the keyboard, update it's layout. + if (xev.type == xkbEventBase + XkbEventCode && kbExtensionsFound) + { + UpdateKeyboardLayout(); + } if (xev.type == Expose) { @@ -8934,10 +8955,86 @@ namespace olc::host } olc::KeyboardLayout Host_Linux_X11::GetKeyboardLayout() const { - // The objectively correct keyboard layout - return olc::KeyboardLayout::QWERTY_US; + return keyboardLayout; } + void Host_Linux_X11::UpdateKeyboardLayout() + { + // Get active group + X11::XkbStateRec state; + X11::XkbGetState(olc_Display, XkbUseCoreKbd, &state); + unsigned int group = state.group; + + // Get symbol name + X11::XkbDescPtr desc = X11::XkbAllocKeyboard(); + desc->dpy = olc_Display; + + if (X11::XkbGetNames(olc_Display, XkbSymbolsNameMask, desc) != Success) + { + X11::XkbFreeKeyboard(desc, 0, True); + return; + } + + const char* symbols = X11::XGetAtomName(olc_Display, desc->names->symbols); + if (!symbols) + { + X11::XkbFreeKeyboard(desc, 0, True); + return; + } + + // Extract layouts in order + // Example: pc_us_gb_2_fr_3_de_4_inet(evdev) + std::vector layouts; + std::string s(symbols); + + size_t start = s.find('_') + 1; + size_t end = s.find("_inet"); + + std::string layoutPart = s.substr(start, end - start); + + size_t pos = 0; + while (pos < layoutPart.size()) + { + size_t next = layoutPart.find('_', pos); + std::string token = layoutPart.substr(pos, next - pos); + + // Remove numeric suffixes (_2, _3, _4) + size_t num = token.find_first_of("0123456789"); + if (num != std::string::npos) + token = token.substr(0, num); + + if(token.size() > 0) + layouts.push_back(token); + + if (next == std::string::npos) + break; + pos = next + 1; + } + + X11::XFree((void*)symbols); + X11::XkbFreeKeyboard(desc, 0, True); + + if (group >= layouts.size()) + return; + + const std::string& layout = layouts[group]; + + std::unordered_map mapLayouts; + mapLayouts["us"] = olc::KeyboardLayout::QWERTY_US; + mapLayouts["gb"] = olc::KeyboardLayout::QWERTY_UK; + mapLayouts["de"] = olc::KeyboardLayout::QWERTZ; + mapLayouts["fr"] = olc::KeyboardLayout::AZERTY; + + auto it = mapLayouts.find(layout); + + if(it != mapLayouts.end()) + { + keyboardLayout = it->second; + return; + } + + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + } // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Linux_X11::SyncWithDesktopComposite() From 94cbccd26752d96fb9586c10953559506edbb519 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 00:33:26 -0500 Subject: [PATCH 36/56] [x11] refactor mapLayouts, code documentation --- dev/src/host_lin_x11.cpp | 20 +++++++++++++------- olcPixelGameEngine3.h | 20 +++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index d1aefa99..a87a1266 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -302,8 +302,10 @@ namespace olc::host return; } + // At this point we can have: pc_us_inet(evdev) + // or something more complex like: pc_us_gb_2_fr_3_de_4_inet(evdev) + // Extract layouts in order - // Example: pc_us_gb_2_fr_3_de_4_inet(evdev) std::vector layouts; std::string s(symbols); @@ -323,11 +325,13 @@ namespace olc::host if (num != std::string::npos) token = token.substr(0, num); + // don't push empty tokens if(token.size() > 0) layouts.push_back(token); if (next == std::string::npos) break; + pos = next + 1; } @@ -339,11 +343,12 @@ namespace olc::host const std::string& layout = layouts[group]; - std::unordered_map mapLayouts; - mapLayouts["us"] = olc::KeyboardLayout::QWERTY_US; - mapLayouts["gb"] = olc::KeyboardLayout::QWERTY_UK; - mapLayouts["de"] = olc::KeyboardLayout::QWERTZ; - mapLayouts["fr"] = olc::KeyboardLayout::AZERTY; + std::unordered_map mapLayouts = { + {"us", olc::KeyboardLayout::QWERTY_US }, + {"gb", olc::KeyboardLayout::QWERTY_UK}, + {"de", olc::KeyboardLayout::QWERTZ}, + {"fr", olc::KeyboardLayout::AZERTY}, + }; auto it = mapLayouts.find(layout); @@ -352,7 +357,8 @@ namespace olc::host keyboardLayout = it->second; return; } - + + // if we've made it here, no valid layout was detected, fallback on default keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 43095df7..3c9e56bc 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8982,8 +8982,10 @@ namespace olc::host return; } + // At this point we can have: pc_us_inet(evdev) + // or something more complex like: pc_us_gb_2_fr_3_de_4_inet(evdev) + // Extract layouts in order - // Example: pc_us_gb_2_fr_3_de_4_inet(evdev) std::vector layouts; std::string s(symbols); @@ -9003,11 +9005,13 @@ namespace olc::host if (num != std::string::npos) token = token.substr(0, num); + // don't push empty tokens if(token.size() > 0) layouts.push_back(token); if (next == std::string::npos) break; + pos = next + 1; } @@ -9019,11 +9023,12 @@ namespace olc::host const std::string& layout = layouts[group]; - std::unordered_map mapLayouts; - mapLayouts["us"] = olc::KeyboardLayout::QWERTY_US; - mapLayouts["gb"] = olc::KeyboardLayout::QWERTY_UK; - mapLayouts["de"] = olc::KeyboardLayout::QWERTZ; - mapLayouts["fr"] = olc::KeyboardLayout::AZERTY; + std::unordered_map mapLayouts = { + {"us", olc::KeyboardLayout::QWERTY_US }, + {"gb", olc::KeyboardLayout::QWERTY_UK}, + {"de", olc::KeyboardLayout::QWERTZ}, + {"fr", olc::KeyboardLayout::AZERTY}, + }; auto it = mapLayouts.find(layout); @@ -9032,7 +9037,8 @@ namespace olc::host keyboardLayout = it->second; return; } - + + // if we've made it here, no valid layout was detected, fallback on default keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; } From 0ec3e14ddf4a9b9e59579dbdf7dc1abffc9b981a Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 10:54:13 -0500 Subject: [PATCH 37/56] [x11] a cleaner, more reliable implementation --- dev/src/host_lin_x11.cpp | 104 ++++++++++++++------------------------- olcPixelGameEngine3.h | 104 ++++++++++++++------------------------- 2 files changed, 74 insertions(+), 134 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index a87a1266..d973f6a7 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -14,9 +14,10 @@ namespace olc::host { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); kbExtensionsFound = true; - UpdateKeyboardLayout(); + } + UpdateKeyboardLayout(); mapKeys[NoSymbol] = Key::NONE; @@ -280,86 +281,55 @@ namespace olc::host void Host_Linux_X11::UpdateKeyboardLayout() { - // Get active group - X11::XkbStateRec state; - X11::XkbGetState(olc_Display, XkbUseCoreKbd, &state); - unsigned int group = state.group; - - // Get symbol name - X11::XkbDescPtr desc = X11::XkbAllocKeyboard(); - desc->dpy = olc_Display; + using namespace X11; + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; - if (X11::XkbGetNames(olc_Display, XkbSymbolsNameMask, desc) != Success) + XkbStateRec state; + if (XkbGetState(olc_Display, XkbUseCoreKbd, &state) != Success) { - X11::XkbFreeKeyboard(desc, 0, True); return; } - const char* symbols = X11::XGetAtomName(olc_Display, desc->names->symbols); - if (!symbols) + // state.group contains the currently active layout group + unsigned int currentGroup = state.group; + + XkbDescPtr xkb = XkbGetMap(olc_Display, 0, XkbUseCoreKbd); + if (!xkb) { - X11::XkbFreeKeyboard(desc, 0, True); return; } - // At this point we can have: pc_us_inet(evdev) - // or something more complex like: pc_us_gb_2_fr_3_de_4_inet(evdev) - - // Extract layouts in order - std::vector layouts; - std::string s(symbols); - - size_t start = s.find('_') + 1; - size_t end = s.find("_inet"); - - std::string layoutPart = s.substr(start, end - start); - - size_t pos = 0; - while (pos < layoutPart.size()) + XkbGetNames(olc_Display, XkbGroupNamesMask, xkb); + + if (!xkb->names) { - size_t next = layoutPart.find('_', pos); - std::string token = layoutPart.substr(pos, next - pos); - - // Remove numeric suffixes (_2, _3, _4) - size_t num = token.find_first_of("0123456789"); - if (num != std::string::npos) - token = token.substr(0, num); - - // don't push empty tokens - if(token.size() > 0) - layouts.push_back(token); - - if (next == std::string::npos) - break; - - pos = next + 1; - } - - X11::XFree((void*)symbols); - X11::XkbFreeKeyboard(desc, 0, True); - - if (group >= layouts.size()) + XkbFreeKeyboard(xkb, 0, True); return; + } - const std::string& layout = layouts[group]; - - std::unordered_map mapLayouts = { - {"us", olc::KeyboardLayout::QWERTY_US }, - {"gb", olc::KeyboardLayout::QWERTY_UK}, - {"de", olc::KeyboardLayout::QWERTZ}, - {"fr", olc::KeyboardLayout::AZERTY}, - }; - - auto it = mapLayouts.find(layout); - - if(it != mapLayouts.end()) + Atom layoutAtom = xkb->names->groups[currentGroup]; + char* layoutName = layoutAtom ? XGetAtomName(olc_Display, layoutAtom) : nullptr; + + if (layoutName) { - keyboardLayout = it->second; - return; - } + std::string layout(layoutName); + + // Convert to lowercase for easier comparison + std::transform(layout.begin(), layout.end(), layout.begin(), ::tolower); - // if we've made it here, no valid layout was detected, fallback on default - keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + if (layout.find("us") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_US; + else if (layout.find("gb") != std::string::npos || layout.find("uk") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_UK; + else if (layout.find("de") != std::string::npos || layout.find("german") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTZ; + else if (layout.find("fr") != std::string::npos || layout.find("french") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::AZERTY; + + XFree(layoutName); + } + + XkbFreeKeyboard(xkb, 0, True); } // Wait for entire host desktop refresh (for smooooth vsync) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3c9e56bc..2263e283 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8694,9 +8694,10 @@ namespace olc::host { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); kbExtensionsFound = true; - UpdateKeyboardLayout(); + } + UpdateKeyboardLayout(); mapKeys[NoSymbol] = Key::NONE; @@ -8960,86 +8961,55 @@ namespace olc::host void Host_Linux_X11::UpdateKeyboardLayout() { - // Get active group - X11::XkbStateRec state; - X11::XkbGetState(olc_Display, XkbUseCoreKbd, &state); - unsigned int group = state.group; - - // Get symbol name - X11::XkbDescPtr desc = X11::XkbAllocKeyboard(); - desc->dpy = olc_Display; + using namespace X11; + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; - if (X11::XkbGetNames(olc_Display, XkbSymbolsNameMask, desc) != Success) + XkbStateRec state; + if (XkbGetState(olc_Display, XkbUseCoreKbd, &state) != Success) { - X11::XkbFreeKeyboard(desc, 0, True); return; } - const char* symbols = X11::XGetAtomName(olc_Display, desc->names->symbols); - if (!symbols) + // state.group contains the currently active layout group + unsigned int currentGroup = state.group; + + XkbDescPtr xkb = XkbGetMap(olc_Display, 0, XkbUseCoreKbd); + if (!xkb) { - X11::XkbFreeKeyboard(desc, 0, True); return; } - // At this point we can have: pc_us_inet(evdev) - // or something more complex like: pc_us_gb_2_fr_3_de_4_inet(evdev) - - // Extract layouts in order - std::vector layouts; - std::string s(symbols); - - size_t start = s.find('_') + 1; - size_t end = s.find("_inet"); - - std::string layoutPart = s.substr(start, end - start); - - size_t pos = 0; - while (pos < layoutPart.size()) + XkbGetNames(olc_Display, XkbGroupNamesMask, xkb); + + if (!xkb->names) { - size_t next = layoutPart.find('_', pos); - std::string token = layoutPart.substr(pos, next - pos); - - // Remove numeric suffixes (_2, _3, _4) - size_t num = token.find_first_of("0123456789"); - if (num != std::string::npos) - token = token.substr(0, num); - - // don't push empty tokens - if(token.size() > 0) - layouts.push_back(token); - - if (next == std::string::npos) - break; - - pos = next + 1; - } - - X11::XFree((void*)symbols); - X11::XkbFreeKeyboard(desc, 0, True); - - if (group >= layouts.size()) + XkbFreeKeyboard(xkb, 0, True); return; + } - const std::string& layout = layouts[group]; - - std::unordered_map mapLayouts = { - {"us", olc::KeyboardLayout::QWERTY_US }, - {"gb", olc::KeyboardLayout::QWERTY_UK}, - {"de", olc::KeyboardLayout::QWERTZ}, - {"fr", olc::KeyboardLayout::AZERTY}, - }; - - auto it = mapLayouts.find(layout); - - if(it != mapLayouts.end()) + Atom layoutAtom = xkb->names->groups[currentGroup]; + char* layoutName = layoutAtom ? XGetAtomName(olc_Display, layoutAtom) : nullptr; + + if (layoutName) { - keyboardLayout = it->second; - return; + std::string layout(layoutName); + + // Convert to lowercase for easier comparison + std::transform(layout.begin(), layout.end(), layout.begin(), ::tolower); + + if (layout.find("us") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_US; + else if (layout.find("gb") != std::string::npos || layout.find("uk") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_UK; + else if (layout.find("de") != std::string::npos || layout.find("german") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTZ; + else if (layout.find("fr") != std::string::npos || layout.find("french") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::AZERTY; + + XFree(layoutName); } - - // if we've made it here, no valid layout was detected, fallback on default - keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + + XkbFreeKeyboard(xkb, 0, True); } // Wait for entire host desktop refresh (for smooooth vsync) From bae60f3337b7a9beba1dd842b4f41f3b9d5989f6 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 11:19:29 -0500 Subject: [PATCH 38/56] [x11] only update keyboard layout if the extensions are available --- dev/src/host_lin_x11.cpp | 4 +--- olcPixelGameEngine3.h | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index d973f6a7..e06cd8f0 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -14,10 +14,8 @@ namespace olc::host { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); kbExtensionsFound = true; - + UpdateKeyboardLayout(); } - - UpdateKeyboardLayout(); mapKeys[NoSymbol] = Key::NONE; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 2263e283..b1bb01c4 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8694,10 +8694,8 @@ namespace olc::host { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); kbExtensionsFound = true; - + UpdateKeyboardLayout(); } - - UpdateKeyboardLayout(); mapKeys[NoSymbol] = Key::NONE; From 2dc49289ea100649423acf2ae7c1a3f5e00d0fb2 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 11:45:58 -0500 Subject: [PATCH 39/56] [examples] [cmake] add wayland to cmake -DBUILD_WAYLAND=ON --- examples/CMakeLists.txt | 102 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 6 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 10029e55..09920f29 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +option(BUILD_WAYLAND "Build example programs with Wayland" OFF) + ###################################################################### # Directories file(GLOB SOURCES "*.cpp") @@ -16,6 +18,79 @@ if(EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX .html) endif() +if(UNIX AND BUILD_WAYLAND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) + pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) + pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) + pkg_check_modules(EGL REQUIRED egl) + + find_package(OpenGL REQUIRED) + find_package(PNG REQUIRED) + find_package(Threads REQUIRED) + + # Find wayland-scanner + find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) + + # Check for wayland protocols + set(XDG_SHELL_PROTOCOL "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") + set(XDG_DECORATION_PROTOCOL "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") + + if(NOT EXISTS ${XDG_SHELL_PROTOCOL}) + message(FATAL_ERROR "xdg-shell protocol not found. You may need to install wayland-protocols via your package manager") + endif() + + if(NOT EXISTS ${XDG_DECORATION_PROTOCOL}) + message(FATAL_ERROR "xdg-decoration protocol not found. You may need to install wayland-protocols via your package manager") + endif() + + # Generate wayland protocol files + set(XDG_SHELL_C ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c) + set(XDG_SHELL_H ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.h) + set(XDG_DECORATION_C ${CMAKE_CURRENT_BINARY_DIR}/xdg-decoration.c) + set(XDG_DECORATION_H ${CMAKE_CURRENT_BINARY_DIR}/xdg-decoration.h) + + add_custom_command( + OUTPUT ${XDG_SHELL_C} + COMMAND ${WAYLAND_SCANNER} private-code ${XDG_SHELL_PROTOCOL} ${XDG_SHELL_C} + DEPENDS ${XDG_SHELL_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${XDG_SHELL_H} + COMMAND ${WAYLAND_SCANNER} client-header ${XDG_SHELL_PROTOCOL} ${XDG_SHELL_H} + DEPENDS ${XDG_SHELL_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${XDG_DECORATION_C} + COMMAND ${WAYLAND_SCANNER} private-code ${XDG_DECORATION_PROTOCOL} ${XDG_DECORATION_C} + DEPENDS ${XDG_DECORATION_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${XDG_DECORATION_H} + COMMAND ${WAYLAND_SCANNER} client-header ${XDG_DECORATION_PROTOCOL} ${XDG_DECORATION_H} + DEPENDS ${XDG_DECORATION_PROTOCOL} + VERBATIM + ) + + # Create a library for the wayland protocol files + add_library(wayland_protocols STATIC + ${XDG_SHELL_C} + ${XDG_SHELL_H} + ${XDG_DECORATION_C} + ${XDG_DECORATION_H} + ) + + target_include_directories(wayland_protocols PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ) +endif() + foreach(source ${SOURCES}) get_filename_component(EXE_NAME ${source} NAME_WE) add_executable(${EXE_NAME} ${source}) @@ -33,14 +108,29 @@ foreach(source ${SOURCES}) target_link_libraries(${EXE_NAME} PRIVATE "-framework Foundation") endif() endif() - + if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) - target_link_libraries(${EXE_NAME} PRIVATE png) - target_link_libraries(${EXE_NAME} PRIVATE GL) - target_link_libraries(${EXE_NAME} PRIVATE X11) - target_link_libraries(${EXE_NAME} PRIVATE pthread) + if(BUILD_WAYLAND) + target_compile_definitions(${EXE_NAME} PRIVATE OLC_HOST=3) + + target_link_libraries(${EXE_NAME} PRIVATE + wayland_protocols + ${XKBCOMMON_LIBRARIES} + ${WAYLAND_CLIENT_LIBRARIES} + ${WAYLAND_EGL_LIBRARIES} + ${EGL_LIBRARIES} + OpenGL::GL + PNG::PNG + Threads::Threads + ) + + else() # x11 + target_link_libraries(${EXE_NAME} PRIVATE png) + target_link_libraries(${EXE_NAME} PRIVATE GL) + target_link_libraries(${EXE_NAME} PRIVATE pthread) + target_link_libraries(${EXE_NAME} PRIVATE X11) + endif() - target_compile_options(${EXE_NAME} PRIVATE -fmax-errors=5) endif() if(WIN32 AND NOT MSVC AND NOT EMSCRIPTEN) From a886941102da2ac99902556e8f26696c165c3eb9 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 11:49:23 -0500 Subject: [PATCH 40/56] [repo] add wayland instructions to the linux cmake instructions --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index c22cf4bd..02c08010 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Linux build ## Cmake + +### Building X11 (Default) ```bash cmake -B build -DBUILD_EXAMPLES=ON cmake --build build @@ -10,6 +12,18 @@ Header file should now be located at `build/olcPixelGameEngine3.h`. Examples will be located at `build/examples` +### Building for Wayland + +```bash +cmake -B build -DBUILD_EXAMPLES=ON -DBUILD_WAYLAND=ON +cmake --build build +``` + +Header file should now be located at `build/olcPixelGameEngine3.h`. + +Examples will be located at `build/examples` + + ## Manually build a single example with GCC ```bash From a040fe71cde6053706db24ff7c1b46f78514f922 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 21:48:18 -0500 Subject: [PATCH 41/56] [single header] bring header up to date --- olcPixelGameEngine3.h | 318 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 260 insertions(+), 58 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d731753f..8f20b594 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3422,6 +3422,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -3470,7 +3471,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup @@ -3595,6 +3596,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -4132,10 +4139,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -4189,10 +4197,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -4205,12 +4213,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { @@ -4388,7 +4396,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -4407,6 +4415,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -4419,6 +4429,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -4464,6 +4477,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } @@ -6072,6 +6091,135 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -6173,6 +6321,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -6319,6 +6496,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -6362,6 +6541,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -6369,43 +6562,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers @@ -6581,6 +6746,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -6645,6 +6816,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -6751,6 +6925,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -7001,6 +7179,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -7027,8 +7206,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -7193,6 +7372,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -7201,6 +7381,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -7211,7 +7392,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7222,7 +7403,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7664,6 +7845,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -7686,10 +7887,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -8327,12 +8529,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } From 28264f1bc2365632a0db56dccfc396e97e96cd42 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 21:49:28 -0500 Subject: [PATCH 42/56] [wayland] implement keyboard layout detection --- dev/src/host_lin_wayland.cpp | 55 ++++++++++++++++++++++++++++++++---- dev/src/host_lin_wayland.h | 5 ++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index afbed611..2b939734 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -133,6 +133,8 @@ namespace olc::host mapKeys[XKB_KEY_minus] = Key::MINUS; // the minus key on any keyboard mapKeys[XKB_KEY_Caps_Lock] = Key::CAPS_LOCK; + + UpdateKeyboardLayout(); } Host_Linux_Wayland::~Host_Linux_Wayland() @@ -146,6 +148,7 @@ namespace olc::host } xkb_state_unref(kb_state); + xkb_keymap_unref(kb_keymap); xkb_context_unref(kb_context); wl_display_disconnect(display); @@ -220,11 +223,43 @@ namespace olc::host return true; } + void Host_Linux_Wayland::UpdateKeyboardLayout() + { + if(!kb_keymap) + { + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + return; + } + + const char* layoutName = xkb_keymap_layout_get_name(kb_keymap, kb_group); + + if (!layoutName) + { + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + return; + } + + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + + std::string layout(layoutName); + std::transform(layout.begin(), layout.end(), layout.begin(), ::tolower); + + if (layout.find("us") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_US; + else if (layout.find("gb") != std::string::npos || layout.find("uk") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_UK; + else if (layout.find("de") != std::string::npos || layout.find("german") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTZ; + else if (layout.find("fr") != std::string::npos || layout.find("french") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::AZERTY; + } + olc::KeyboardLayout Host_Linux_Wayland::GetKeyboardLayout() const { - return olc::KeyboardLayout::QWERTY_US; + return keyboardLayout; } + void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { if(std::strcmp(interface, wl_compositor_interface.name) == 0) { @@ -515,18 +550,21 @@ namespace olc::host return; } - xkb_keymap* keymap = xkb_keymap_new_from_string(kb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + if(kb_keymap) + xkb_keymap_unref(kb_keymap); + + kb_keymap = xkb_keymap_new_from_string(kb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(keymap_string, size); close(fd); - if (!keymap) { + if (!kb_keymap) { return; } // Unreference the previous state if it exists and we got a new keymap from the server xkb_state_unref(kb_state); - kb_state = xkb_state_new(keymap); - xkb_keymap_unref(keymap); + kb_state = xkb_state_new(kb_keymap); + UpdateKeyboardLayout(); } void Host_Linux_Wayland::keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) @@ -576,6 +614,13 @@ namespace olc::host void Host_Linux_Wayland::keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + // The 'group' parameter is the currently active layout index! + if(kb_group != group) + { + kb_group = group; + UpdateKeyboardLayout(); + } + xkb_state_update_mask(kb_state, mods_depressed & ~(1), // Just like X11, ignore the shift key mods_latched, diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index f50b62f9..951d2f52 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -82,6 +83,8 @@ namespace olc::host wl_keyboard* keyboard{nullptr}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; + xkb_keymap* kb_keymap; + uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; zxdg_decoration_manager_v1* decoration_manager{nullptr}; @@ -104,6 +107,7 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; olc::KeyboardLayout GetKeyboardLayout() const override; + void UpdateKeyboardLayout(); // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -181,6 +185,7 @@ namespace olc::host std::unordered_map mapUID2OlcWindow; std::atomic terminate {false}; std::unordered_map mapKeys; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; } From efc63baa6c9973f3b795ebb4528292d411cbcf27 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 30 Jan 2026 22:37:50 -0500 Subject: [PATCH 43/56] [single header] bring header up to date --- olcPixelGameEngine3.h | 60 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 8f20b594..b4febb99 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4548,6 +4548,7 @@ namespace olc::host #include #include #include +#include #include #include @@ -4609,6 +4610,8 @@ namespace olc::host wl_keyboard* keyboard{nullptr}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; + xkb_keymap* kb_keymap; + uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; zxdg_decoration_manager_v1* decoration_manager{nullptr}; @@ -4631,6 +4634,7 @@ namespace olc::host bool ConnectHostResourceToRenderer() override; olc::KeyboardLayout GetKeyboardLayout() const override; + void UpdateKeyboardLayout(); // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; @@ -4708,6 +4712,7 @@ namespace olc::host std::unordered_map mapUID2OlcWindow; std::atomic terminate {false}; std::unordered_map mapKeys; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; } @@ -9079,6 +9084,8 @@ namespace olc::host mapKeys[XKB_KEY_minus] = Key::MINUS; // the minus key on any keyboard mapKeys[XKB_KEY_Caps_Lock] = Key::CAPS_LOCK; + + UpdateKeyboardLayout(); } Host_Linux_Wayland::~Host_Linux_Wayland() @@ -9092,6 +9099,7 @@ namespace olc::host } xkb_state_unref(kb_state); + xkb_keymap_unref(kb_keymap); xkb_context_unref(kb_context); wl_display_disconnect(display); @@ -9166,11 +9174,43 @@ namespace olc::host return true; } + void Host_Linux_Wayland::UpdateKeyboardLayout() + { + if(!kb_keymap) + { + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + return; + } + + const char* layoutName = xkb_keymap_layout_get_name(kb_keymap, kb_group); + + if (!layoutName) + { + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + return; + } + + keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + + std::string layout(layoutName); + std::transform(layout.begin(), layout.end(), layout.begin(), ::tolower); + + if (layout.find("us") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_US; + else if (layout.find("gb") != std::string::npos || layout.find("uk") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTY_UK; + else if (layout.find("de") != std::string::npos || layout.find("german") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::QWERTZ; + else if (layout.find("fr") != std::string::npos || layout.find("french") != std::string::npos) + keyboardLayout = olc::KeyboardLayout::AZERTY; + } + olc::KeyboardLayout Host_Linux_Wayland::GetKeyboardLayout() const { - return olc::KeyboardLayout::QWERTY_US; + return keyboardLayout; } + void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { if(std::strcmp(interface, wl_compositor_interface.name) == 0) { @@ -9461,18 +9501,21 @@ namespace olc::host return; } - xkb_keymap* keymap = xkb_keymap_new_from_string(kb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + if(kb_keymap) + xkb_keymap_unref(kb_keymap); + + kb_keymap = xkb_keymap_new_from_string(kb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(keymap_string, size); close(fd); - if (!keymap) { + if (!kb_keymap) { return; } // Unreference the previous state if it exists and we got a new keymap from the server xkb_state_unref(kb_state); - kb_state = xkb_state_new(keymap); - xkb_keymap_unref(keymap); + kb_state = xkb_state_new(kb_keymap); + UpdateKeyboardLayout(); } void Host_Linux_Wayland::keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) @@ -9522,6 +9565,13 @@ namespace olc::host void Host_Linux_Wayland::keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + // The 'group' parameter is the currently active layout index! + if(kb_group != group) + { + kb_group = group; + UpdateKeyboardLayout(); + } + xkb_state_update_mask(kb_state, mods_depressed & ~(1), // Just like X11, ignore the shift key mods_latched, From d62cffa75ff5ec160030cf9b18692dff2e01de2d Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 10:30:02 +0000 Subject: [PATCH 44/56] quick fix --- dev/msvc/olcPGE3.vcxproj | 12 ++ dev/msvc/olcPGE3.vcxproj.filters | 15 ++ dev/src/core.cpp | 6 + dev/src/core.h | 2 + olcPixelGameEngine3.h | 326 +++++++++++++++++++++++++------ 5 files changed, 303 insertions(+), 58 deletions(-) diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index c083f5ad..2f7fcc46 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -200,6 +200,9 @@ + + true + true true @@ -231,6 +234,9 @@ + + true + true @@ -268,6 +274,9 @@ + + true + true true @@ -296,6 +305,9 @@ + + true + true true diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 37257044..df812651 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -37,6 +37,9 @@ {469b9ff0-8c4d-48fe-9034-fe6f877aab29} + + {0020e19e-b8ce-4218-a5ee-8a5716c9f01a} + @@ -150,6 +153,12 @@ Utilities + + Hosts\Android Specific + + + Hosts\Android Specific + @@ -227,5 +236,11 @@ Source Files + + Hosts\Android Specific + + + Hosts\Android Specific + \ No newline at end of file diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 6aa13d0f..eb71c582 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -367,6 +367,11 @@ namespace olc return durationTotalElapsed.count(); } + size_t PixelGameEngine::GetFPS() const + { + return fps; + } + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES @@ -433,6 +438,7 @@ namespace olc if (pge->durationFrameCount >= 1s) { pge->durationFrameCount -= 1s; + pge->fps = pge->frameCount; std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(pge->frameCount); pge->SetWindowTitle(sTitle); pge->frameCount = 0; diff --git a/dev/src/core.h b/dev/src/core.h index c44f4de7..4187644f 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -149,6 +149,7 @@ namespace olc public: float FrameTimeElapsed() const; double TotalTimeElapsed() const; + size_t GetFPS() const; public: // Child Windows bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); @@ -167,6 +168,7 @@ namespace olc std::chrono::duration durationFrameCount{ 0 }; std::chrono::duration durationTotalElapsed{ 0 }; size_t frameCount = 0; + size_t fps = 0; // Core Thread std::thread coreThread; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d731753f..603332a5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3262,6 +3262,7 @@ namespace olc public: float FrameTimeElapsed() const; double TotalTimeElapsed() const; + size_t GetFPS() const; public: // Child Windows bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); @@ -3280,6 +3281,7 @@ namespace olc std::chrono::duration durationFrameCount{ 0 }; std::chrono::duration durationTotalElapsed{ 0 }; size_t frameCount = 0; + size_t fps = 0; // Core Thread std::thread coreThread; @@ -3422,6 +3424,7 @@ extern "C" { void application_activate (struct Application* self); void application_run (struct Application* self); void application_destroy (struct Application* self); + const char* application_getSystemLocale (struct Application* self); // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); @@ -3470,7 +3473,7 @@ extern "C" { void glDeleteTextures(int n, const unsigned int* textures); // Event callback function types - typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, void* userData); + 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); // Event handler setup @@ -3595,6 +3598,12 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + // Add this method to get system locale + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } // Get underlying C handle struct ::Application* getCHandle() const noexcept { return app_; } @@ -4132,10 +4141,11 @@ namespace olc { struct KeyEvent { unsigned short keyCode; std::string characters; - - KeyEvent(unsigned short code, const char* chars) noexcept - : keyCode(code), characters(chars ? chars : "") {} - + unsigned int modifierFlags; + + KeyEvent(unsigned short code, const char* chars, unsigned int mods) noexcept + : keyCode(code), characters(chars ? chars : ""), modifierFlags(mods) {} + // Move constructor and assignment for better performance KeyEvent(KeyEvent&&) noexcept = default; KeyEvent& operator=(KeyEvent&&) noexcept = default; @@ -4189,10 +4199,10 @@ namespace olc { // Template helpers for static callbacks to reduce code duplication template - static void keyCallback(unsigned short keyCode, const char* characters, void* userData, HandlerType EventHandler::*handler) { + static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { auto* eventHandler = static_cast(userData); if (eventHandler && (eventHandler->*handler)) { - (eventHandler->*handler)(KeyEvent(keyCode, characters)); + (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } @@ -4205,12 +4215,12 @@ namespace olc { } // Static callback functions for C API - static void keyDownCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyDownHandler_); + static void keyDownCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyDownHandler_); } - - static void keyUpCallback(unsigned short keyCode, const char* characters, void* userData) { - keyCallback(keyCode, characters, userData, &EventHandler::keyUpHandler_); + + static void keyUpCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData) { + keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { @@ -4388,7 +4398,7 @@ namespace olc olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window public: - Host_Apple_MacOS() = default; + Host_Apple_MacOS(); virtual ~Host_Apple_MacOS() {}; public: @@ -4407,6 +4417,8 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + protected: HostError lastError = HostError::None; @@ -4419,6 +4431,9 @@ namespace olc void* pMacGLConextObj = nullptr; std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; private: enum MAINTASKS{ @@ -4464,6 +4479,12 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); + /* + Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. + This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. + */ + void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + }; } } @@ -6072,6 +6093,135 @@ namespace olc::host #if OLC_HOST == OLC_HOST_MACOS namespace olc::host { + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 + 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 + + + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; + + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; + + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; + + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; + + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . + + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + } + bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ (void)(bBlockIfPossible); // Remove unused variable warning @@ -6173,6 +6323,35 @@ namespace olc::host { return enableVSync; } + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + // ------- Priavate Main Thread Task Handling for MacOS Host ------- bool Host_Apple_MacOS::CreateCGLContextObj() @@ -6319,6 +6498,8 @@ namespace olc::host { pMacApplication->setDidFinishLaunchingCallback([&]() { // Queue the Create OpenGL context task vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); pMacApplication->setWillTerminateCallback([&]() { @@ -6362,6 +6543,20 @@ namespace olc::host { } + void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + if (event.modifierFlags & NSEventModifierFlagCapsLock) { + pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + } + if (event.modifierFlags & NSEventModifierFlagShift) { + pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + } + if (event.modifierFlags & NSEventModifierFlagControl) { + pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); + } + + } + void Host_Apple_MacOS::MacEventsHandler() { @@ -6369,43 +6564,15 @@ namespace olc::host { // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Down - Code: " << event.keyCode - << ", Chars: '" << event.characters << "'" << std::endl; - - // Handle special keys - switch (event.keyCode) { - case 53: // Escape - std::cout << "Escape key pressed!" << std::endl; - break; - case 36: // Return - std::cout << "Return key pressed!" << std::endl; - break; - case 49: // Space - std::cout << "Space key pressed!" << std::endl; - break; - case 123: // Left arrow - std::cout << "Left arrow pressed!" << std::endl; - break; - case 124: // Right arrow - std::cout << "Right arrow pressed!" << std::endl; - break; - case 125: // Down arrow - std::cout << "Down arrow pressed!" << std::endl; - break; - case 126: // Up arrow - std::cout << "Up arrow pressed!" << std::endl; - break; - default: - if (!event.characters.empty()) { - std::cout << "Character key: '" << event.characters << "'" << std::endl; - } - break; - } + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, true); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); - pMacOSEventHandler->onKeyUp([](const olc::apis::macos::KeyEvent& event) { - std::cout << "Key Up - Code: " << event.keyCode << std::endl; + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + ModifiersFlagsHandler(event, false); + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + }); // Set up mouse event handlers @@ -6581,6 +6748,12 @@ static constexpr const char* kBytesPerRowSel = "bytesPerRow"; static constexpr const char* kHasAlphaSel = "hasAlpha"; static constexpr const char* kBitmapDataSel = "bitmapData"; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + + // Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; @@ -6645,6 +6818,9 @@ namespace ObjectiveCSEL { // NSImage, NSBitmapImageRep, and image data access selectors static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; + // NSLocale selectors + static SEL currentLocaleSel, localeIdentifierSel = nullptr; + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized @@ -6751,6 +6927,10 @@ namespace ObjectiveCSEL { bytesPerRowSel = sel_registerName(kBytesPerRowSel); hasAlphaSel = sel_registerName(kHasAlphaSel); bitmapDataSel = sel_registerName(kBitmapDataSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); } // Ensures we only initialize selectors once (Thread-safe) @@ -7001,6 +7181,7 @@ struct Application { void (*activate) (struct Application* self){nullptr}; void (*run) (struct Application* self){nullptr}; void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; Application() = default; @@ -7027,8 +7208,8 @@ struct Window { const char* title{nullptr}; // Window title string // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, void* userData){nullptr}; + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -7193,6 +7374,7 @@ id createApplicationDelegate() { struct KeyEventData { unsigned short keyCode = 0; const char* characters = nullptr; + unsigned int modifierFlags = 0; }; // Extract common key event data from NSEvent @@ -7201,6 +7383,7 @@ KeyEventData extractKeyEventData(id event) { data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); return data; } @@ -7211,7 +7394,7 @@ void view_keyDown(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7222,7 +7405,7 @@ void view_keyUp(id self, SEL _cmd, id event) { KeyEventData data = extractKeyEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, gptrNSWindowEvents->eventUserData); + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } } @@ -7664,6 +7847,26 @@ extern "C" { } } + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } + + // Ensure window has a delegate for window events void ensureWindowDelegate(struct Window* self) { @@ -7686,10 +7889,11 @@ extern "C" { Application* app = new Application(); // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; return app; } @@ -8327,12 +8531,12 @@ extern "C" { } // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyDownCallback = callback; self->eventUserData = userData; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, void*), void* userData) { + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { self->keyUpCallback = callback; self->eventUserData = userData; } @@ -14444,6 +14648,11 @@ namespace olc return durationTotalElapsed.count(); } + size_t PixelGameEngine::GetFPS() const + { + return fps; + } + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES @@ -14510,6 +14719,7 @@ namespace olc if (pge->durationFrameCount >= 1s) { pge->durationFrameCount -= 1s; + pge->fps = pge->frameCount; std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(pge->frameCount); pge->SetWindowTitle(sTitle); pge->frameCount = 0; From 312a733a33dee0ea566d6de4279b4290d2bb5a96 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:57:35 +0000 Subject: [PATCH 45/56] glGetIntegerv() err.... Added Terminate func --- dev/src/api_opengl.cpp | 6 ++++++ dev/src/api_opengl.h | 3 ++- dev/src/host_win_winapi.cpp | 6 ++++++ dev/src/host_win_winapi.h | 1 + olcPixelGameEngine3.h | 16 +++++++++++++++- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 64bb3cca..7d7e465d 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -71,7 +71,13 @@ namespace olc::apis::opengl bLoaded &= (_glDeleteRenderbuffers = OGL_LOAD(glDeleteRenderbuffers)) != nullptr; bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; + + // Do we really need to do this? - jx9 +#if OLC_HOST != OLC_HOST_WINDOWS bLoaded &= (_glGetIntegerv = OGL_LOAD(glGetIntegerv)) != nullptr; +#else + _glGetIntegerv = ::glGetIntegerv; +#endif #if OLC_HOST == OLC_HOST_WINDOWS bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index b466eef2..b46fe1cc 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -281,7 +281,6 @@ namespace olc void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); - void glGetIntegerv(GLenum pname, GLint *data); // OpenGL1.2 Proxies (just keeps things tidy imo) void glGenTextures(GLsizei n, GLuint* textures); @@ -303,6 +302,8 @@ namespace olc void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); void glHint(GLenum target, GLenum mode); void glPolygonMode(GLenum face, GLenum mode); + + void glGetIntegerv(GLenum pname, GLint *data); void glSwapInterval(GLsizei n); diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 22f9a01b..a255097d 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -195,6 +195,12 @@ namespace olc::host return true; } + void Host_Windows_WinAPI::TerminateSystemEventLoop() + { + // Post windows WM_QUIT message + //PostQuitMessage(0); + } + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { olc_IgnoreUnused(bFullScreen); diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index a7e8b3fe..b532585d 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -65,6 +65,7 @@ namespace olc public: bool StartSystemEventLoop(bool bBlockIfPossible = false); + void TerminateSystemEventLoop(); bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen); bool CloseWindowFrame(olc::Window* pWindow); bool UpdateWindowFrameTitle(olc::Window* pWindow); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index a58b28eb..f08b9034 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3361,6 +3361,7 @@ namespace olc public: bool StartSystemEventLoop(bool bBlockIfPossible = false); + void TerminateSystemEventLoop(); bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen); bool CloseWindowFrame(olc::Window* pWindow); bool UpdateWindowFrameTitle(olc::Window* pWindow); @@ -5290,7 +5291,6 @@ namespace olc void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); - void glGetIntegerv(GLenum pname, GLint *data); // OpenGL1.2 Proxies (just keeps things tidy imo) void glGenTextures(GLsizei n, GLuint* textures); @@ -5312,6 +5312,8 @@ namespace olc void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); void glHint(GLenum target, GLenum mode); void glPolygonMode(GLenum face, GLenum mode); + + void glGetIntegerv(GLenum pname, GLint *data); void glSwapInterval(GLsizei n); @@ -5820,6 +5822,12 @@ namespace olc::host return true; } + void Host_Windows_WinAPI::TerminateSystemEventLoop() + { + // Post windows WM_QUIT message + //PostQuitMessage(0); + } + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { olc_IgnoreUnused(bFullScreen); @@ -10967,7 +10975,13 @@ namespace olc::apis::opengl bLoaded &= (_glDeleteRenderbuffers = OGL_LOAD(glDeleteRenderbuffers)) != nullptr; bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; + + // Do we really need to do this? - jx9 +#if OLC_HOST != OLC_HOST_WINDOWS bLoaded &= (_glGetIntegerv = OGL_LOAD(glGetIntegerv)) != nullptr; +#else + _glGetIntegerv = ::glGetIntegerv; +#endif #if OLC_HOST == OLC_HOST_WINDOWS bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; From a28b72e64f5f040148dc3beaf53314d104520a52 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:24:47 +0000 Subject: [PATCH 46/56] 131 Update MacOS to support both Mac and Non Mac Keyboards --- dev/src/host_apple_macos.cpp | 42 ++++++++++++++++++++++++++++++------ dev/src/host_apple_macos.h | 10 ++++----- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 3fd9cefe..02b4410a 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -79,7 +79,7 @@ namespace olc::host { mapKeys[111] = Key::F12; // Arrow Keys - mapKeys[125] = Key::DOWN; + mapKeys[125] = Key::DOWN; mapKeys[123] = Key::LEFT; mapKeys[124] = Key::RIGHT; mapKeys[126] = Key::UP; @@ -456,18 +456,48 @@ namespace olc::host { } - void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + bool Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + bool bisHandled = false; if (event.modifierFlags & NSEventModifierFlagCapsLock) { pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); } if (event.modifierFlags & NSEventModifierFlagShift) { pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + if(event.keyCode == 39) + { + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + pPGEwindow->olc_OnKeyPress(mapKeys[50], pressed); + return true; + } + } if (event.modifierFlags & NSEventModifierFlagControl) { pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); } + if(event.modifierFlags & NSEventModifierFlagNumericPad) { + if(event.keyCode == 71 && pressed) // NumLock keycode + { + // We only tottle the NumLock state on key press to minic the latching of the key + bNumLockActive = !bNumLockActive; + } + + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + if(event.keyCode == 84) pPGEwindow->olc_OnKeyPress(mapKeys[125], pressed); + if(event.keyCode == 86) pPGEwindow->olc_OnKeyPress(mapKeys[123], pressed); + if(event.keyCode == 88) pPGEwindow->olc_OnKeyPress(mapKeys[124], pressed); + if(event.keyCode == 91) pPGEwindow->olc_OnKeyPress(mapKeys[126], pressed); + return true; + } + + + } + + return bisHandled; + } @@ -478,13 +508,13 @@ namespace olc::host { // Set up keyboard event handlers pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - ModifiersFlagsHandler(event, true); - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); + if(!ModifiersFlagsHandler(event, true)) + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - ModifiersFlagsHandler(event, false); - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + if(!ModifiersFlagsHandler(event, false)) + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); }); diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index fd245507..70578d93 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -122,11 +122,11 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - /* - Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. - This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. - */ - void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + + // When modifier flag changes the keycode it will return true, else false + bool ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + + bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; } From fd544cc92026a242805c854860b292e0b49426fd Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:36:28 +0000 Subject: [PATCH 47/56] first pass --- dev/src/core.cpp | 593 ++++++++++++++++++++---------------- dev/src/core.h | 6 + dev/src/host_iface.h | 37 ++- dev/src/host_win_winapi.cpp | 117 ++++++- dev/src/host_win_winapi.h | 56 ++-- 5 files changed, 515 insertions(+), 294 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 0452ec3c..d13695f7 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -288,315 +288,394 @@ namespace olc config.vScreenSize = vScreenSize; config.vPixelSize = vPixelSize; config.bFullScreen = bFullScreen; - return true; + return Construct(config); } bool PixelGameEngine::Construct(const PGEConfig& cfg) { config = cfg; - return true; - } - bool PixelGameEngine::Start() - { + // This is the earliest point within familiar PGE ecosystem + // where we can instatiate the host interface. + // Initialise Host Interface - #if OLC_HOST == OLC_HOST_WINDOWS +#if OLC_HOST == OLC_HOST_WINDOWS host = std::make_unique(); - #endif - // Johnnyg63: Added MacOS Host Initialisation - #if OLC_HOST == OLC_HOST_MACOS - host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_LINUX_X11 - host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_LINUX_WAYLAND - host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN - host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_ANDROID - host = std::make_unique(); - auto hostPtr = (dynamic_cast(host.get())); - #endif -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO - // Create OS window on this thread - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); - // Create EngineThread - no more windows will be created now. We needed one window - // at least to initialise teh rendering subsystem... sigh. - coreActive = true; - -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID - // Create EngineThread - coreThread = std::thread(&PixelGameEngine::EngineThread, this); - // Handle window events on this thread (and block) - host->StartSystemEventLoop(true); - // Window has closed its event handler, so shut down gracefully - coreActive = false; - // Wait for engine thread to terminate - coreThread.join(); -#elif OLC_HOST == OLC_HOST_ANDROID - // We need to wait for the APP_CMD_INIT_WINDOW command before starting the loop - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initializing..."); - while (!hostPtr->IsInitialized()) { - host->StartSystemEventLoop(false); - } - - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initialized Successfully"); - - EngineThread(); -#else - EngineThread(); #endif -#else - +#if OLC_HOST == OLC_HOST_MACOS + host = std::make_unique(); #endif - - return true; - } - - float PixelGameEngine::FrameTimeElapsed() const - { - return durationFrame.count(); - } - - double PixelGameEngine::TotalTimeElapsed() const - { - return durationTotalElapsed.count(); - } - - size_t PixelGameEngine::GetFPS() const - { - return fps; - } - - bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) - { -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Link this olc::Window to a host resource - if (host) - { - // TODO: Connect to config - - host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); - window->LinkToHost(host.get()); - window->LinkToRenderer(gpu.get()); - window->LinkToImageLoader(imageloader.get()); - //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); - window->Create(vScreenSize, vPixelSize); - - deqChildWindows.push_back(window); - } - return true; -#else - // Can't create new windows - olc_IgnoreUnused(window, vScreenSize, vPixelSize); - return false; +#if OLC_HOST == OLC_HOST_LINUX_X11 + host = std::make_unique(); #endif - } - void PixelGameEngine::CoreUpdate(void* userdata) - { - using namespace std::chrono_literals; - auto pge = reinterpret_cast(userdata); -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Multiwindow system uses one event loop (non blocking) for all windows - pge->host->StartSystemEventLoop(false); +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + host = std::make_unique(); #endif - // Frame Delta Timing - "ElapsedTime" since last core update - // ~~~~~~~~~~~~~~~~~~ - // All timing is synchronous to the primary window, i.e child - // windows do not maintain their own frame timing. This is a - // deliberate decision as PGE will maintain sync between windows. - // Why? Multiple windows are not really the point of PGE and - // indeed could just be addional unnecessary complexity. However, - // by moving to a Window abstraction we kinda get it for free. - // This freedom comes at the expense of complexity. If we allowed - // windows to be wholly isolated from the core loop, then the - // user is expected to maintain thread safety, context sharing - // and resource management. That is not olc::PGE. - - pge->timeFrame1 = std::chrono::steady_clock::now(); - pge->durationFrame = pge->timeFrame1 - pge->timeFrame2; - pge->timeFrame2 = pge->timeFrame1; - - pge->durationTotalElapsed += pge->durationFrame; - - // Our time per frame coefficient - float fDT = pge->durationFrame.count(); - - // Our Total Time accumulator - float fTT = float(pge->durationTotalElapsed.count()); - - pge->frameCount++; - pge->durationFrameCount += pge->durationFrame; - - if (pge->durationFrameCount >= 1s) - { - pge->durationFrameCount -= 1s; - pge->fps = pge->frameCount; - std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(pge->frameCount); - pge->SetWindowTitle(sTitle); - pge->frameCount = 0; - } - - - - // Primary Window - if (pge->olc_ShouldRemove()) - { - // Application is to be terminated as primary window has closed - pge->coreActive = false; #if OLC_HOST == OLC_HOST_EMSCRIPTEN - emscripten_cancel_main_loop(); -#endif - } - else - { -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Update Child Windows (if any) - for (auto& winChild : deqChildWindows) - winChild->olc_WindowUpdate(fDT, fTT); - - // Remove child windows that have requested closure - if (!deqChildWindows.empty()) - { - deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), - [this](const std::shared_ptr& w) - { - if (w->olc_ShouldRemove()) - { - host->CloseWindowFrame(w.get()); - } - return w->olc_ShouldRemove(); - } - ), deqChildWindows.end()); - } + host = std::make_unique(); #endif - // Update Primary Window - pge->olc_WindowUpdate(fDT, fTT); - - // Wait for vertical sync if required. - // Note: Child windows will never vsync as waiting for each buffer swap with vsync - // divides up the frame rate budget across the windows. - if (pge->gpu->GetConfig().VerticalSync) - { - pge->host->SyncWithDesktopComposite(); - } - } - - } - - void PixelGameEngine::EngineThread() - { - using namespace std::chrono_literals; - timeFrame2 = std::chrono::steady_clock::now(); - timeFrame1 = std::chrono::steady_clock::now(); - -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Create Primary Window on EngineThread, event loop also exists for all windows - // on this thread, and all windows will be created on this thread - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); -#endif + // DEVS!! Please don't merge these just yet - // Initialise ImageLoader Interface + // Initialise ImageLoader Interface #if OLC_HOST == OLC_HOST_WINDOWS - imageloader = std::make_unique(); + imageloader = std::make_unique(); #endif - // Initialise ImageLoader Interface #if OLC_HOST == OLC_HOST_MACOS - imageloader = std::make_unique(); + imageloader = std::make_unique(); #endif #if OLC_HOST == OLC_HOST_LINUX_X11 - imageloader = std::make_unique(); + imageloader = std::make_unique(); #endif #if OLC_HOST == OLC_HOST_LINUX_WAYLAND - imageloader = std::make_unique(); + imageloader = std::make_unique(); #endif #if OLC_HOST == OLC_HOST_EMSCRIPTEN - imageloader = std::make_unique(); + imageloader = std::make_unique(); #endif -#if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( - olc::host::Host_Android::androidApp->activity->assetManager - ); -#endif + // Allow host to prepare itself + return host->OnApplicationStart(this); + } + + bool PixelGameEngine::Start() + { + bool bStartCheck = host->StartSystem(); // Must block until system is shutdown + if(!bStartCheck) + std::cout << "PGE Start() Error: Host failed to start\n"; + + bool bEndCheck = host->OnApplicationEnd(); + if (!bEndCheck) + std::cout << "PGE Start() Error: Host failed to shutdown cleanly\n"; + + return bStartCheck && bEndCheck; + + +// // Johnnyg63: Added MacOS Host Initialisation +// +//#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO +// // Create OS window on this thread +// host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); +// // Create EngineThread - no more windows will be created now. We needed one window +// // at least to initialise teh rendering subsystem... sigh. +// coreActive = true; +// +//#if OLC_HOST != OLC_HOST_EMSCRIPTEN +// coreThread = std::thread(&PixelGameEngine::EngineThread, this); +// // Handle window events on this thread (and block) +// host->StartSystemEventLoop(true); +// // Window has closed its event handler, so shut down gracefully +// coreActive = false; +// // Wait for engine thread to terminate +// coreThread.join(); +//#else +// EngineThread(); +//#endif +// +//#else +// +//#endif + // + //return true; + } + + bool PixelGameEngine::OnContextStart() + { + // The "context" refers to the thread (or execution pathway) + // that will be hosting the main rendering loop. Things like + // OpenGL contexts are thread specific, so all GPU initialisation + // must be performed here for example + + // If the host doesnt use a thread for its event loop, then + // this function will probably be called on the main application + // thread + + // Create the window! + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); + + // Initialise GPU Interface + olc::gpu::RendererConfig cfgRenderer; + cfgRenderer.VerticalSync = config.bVSync; + gpu = std::make_unique(); + + gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); + if (gpu->GetLastError() != olc::gpu::RendererError::NoError) + { + std::cout << "PGE OnContextStart() Error: Could not create Renderer\n"; + return false; + } - // Initialise GPU Interface - This thread is the context - olc::gpu::RendererConfig cfgRenderer; - cfgRenderer.VerticalSync = config.bVSync; + // Link this olc::Window to host and renderer resources + LinkToHost(host.get()); + // Link this olc::PGEWindow to renderer and imageloader + LinkToRenderer(gpu.get()); + LinkToImageLoader(imageloader.get()); - gpu = std::make_unique(); + // These things require a valid GPU context - // Link this windows devices - LinkToHost(host.get()); - LinkToRenderer(gpu.get()); - LinkToImageLoader(imageloader.get()); + // Create Primary olc::Image - aka "The Screen" + olc::ImageConfig cfg; + cfg.MSAA = config.bAntiAliasMainScreen; + CreateImage(GetDefaultImage(), config.vScreenSize, cfg); - // The GPU device can be based upon the primary window configuration. This - // gives us completed gpu and host objects to pass to other windows as and - // when required - gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); - if (gpu->GetLastError() != olc::gpu::RendererError::NoError) - { - //const auto e = gpu->GetLastError(); // For debug visibility - std::cout << "Error: Could not create Renderer\n"; - return; - } + // Initialise "Classic" Font System + olc::pgeguts::CreateClassicFont(this); + // Prepare Draw2D system + draw.SetGPU(gpu.get()); + gpu->ApplyDefaultShader(); + draw.SetTarget(GetDefaultImage()); + // User Create GOOOOOOOOO!!!! + if (!OnUserCreate()) + { + std::cout << "PGE OnContextStart(): User aborted OnUserCreate()\n"; + return false; + } - olc::ImageConfig cfg; - cfg.MSAA = config.bAntiAliasMainScreen; - CreateImage(GetDefaultImage(), config.vScreenSize, cfg); + // It's possible to draw things in create so flush any pending GPU tasks + draw.ProcessGPUTasks(); + // Set to known default state + draw.SetTarget(GetDefaultImage()); + draw.WorldReset(); + gpu->ApplyDefaultShader(); - // Initialise Font System - olc::pgeguts::CreateClassicFont(this); + // Fire up the engine! + using namespace std::chrono_literals; + durationFrameCount = 0s; + durationTotalElapsed = 0s; + timeFrame1 = std::chrono::steady_clock::now(); + timeFrame2 = std::chrono::steady_clock::now(); + frameCount = 0; + return true; + } + bool PixelGameEngine::OnContextTick() + { + // This is called once per frame from the host's system loop + + // Frame Delta Timing - "ElapsedTime" since last core update + // ~~~~~~~~~~~~~~~~~~ + // All timing is synchronous to the primary window, i.e child + // windows do not maintain their own frame timing. This is a + // deliberate decision as PGE will maintain sync between windows. + // Why? Multiple windows are not really the point of PGE and + // indeed could just be addional unnecessary complexity. However, + // by moving to a Window abstraction we kinda get it for free. + // This freedom comes at the expense of complexity. If we allowed + // windows to be wholly isolated from the core loop, then the + // user is expected to maintain thread safety, context sharing + // and resource management. That is not olc::PGE. - draw.SetGPU(gpu.get()); - gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + using namespace std::chrono_literals; + + // Calculate last frame time + timeFrame1 = std::chrono::steady_clock::now(); + durationFrame = timeFrame1 - timeFrame2; + timeFrame2 = timeFrame1; - if (!OnUserCreate()) - { - // Creation process signalled abort - return; - } + // Accumulate total time elapsed since application start + durationTotalElapsed += durationFrame; - draw.ProcessGPUTasks(); - draw.SetTarget(GetDefaultImage()); + // Our time per frame coefficient + float fDT = durationFrame.count(); - // Initialise Input Devices + // Our Total Time accumulator + float fTT = float(durationTotalElapsed.count()); + // Calculate FPS every second + frameCount++; + durationFrameCount += durationFrame; + if (durationFrameCount >= 1s) + { + durationFrameCount -= 1s; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); + SetWindowTitle(sTitle); + frameCount = 0; + } - durationFrameCount = 0s; + // If primary window is to be closed, signal application termination + if (olc_ShouldRemove()) + { + return false; // Terminate application + } + else + { + // Update Primary Window + olc_WindowUpdate(fDT, fTT); -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); + // Wait for vertical sync with desktop compositor if required. + + // Note: Child windows will never vsync as waiting for each buffer swap with vsync + // divides up the frame rate budget across the windows. + if (gpu->GetConfig().VerticalSync) + { + host->SyncWithDesktopComposite(); + } + } + + return true; // Keep going + } + + bool PixelGameEngine::OnContextEnd() + { + return true; + } + + float PixelGameEngine::FrameTimeElapsed() const + { + return durationFrame.count(); + } + + double PixelGameEngine::TotalTimeElapsed() const + { + return durationTotalElapsed.count(); + } + + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) + { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Link this olc::Window to a host resource + if (host) + { + host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); + window->LinkToHost(host.get()); + window->LinkToRenderer(gpu.get()); + window->LinkToImageLoader(imageloader.get()); + + //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); + window->Create(vScreenSize, vPixelSize); + deqChildWindows.push_back(window); + } + return true; #else - while (coreActive) - { -#if OLC_HOST == OLC_HOST_ANDROID - if (!host->StartSystemEventLoop(false)) { - coreActive = false; - } -#endif - PixelGameEngine::CoreUpdate(this); - } - host->TerminateSystemEventLoop(); + // Can't create new windows + olc_IgnoreUnused(window, vScreenSize, vPixelSize); + return false; #endif - } + } + +// void PixelGameEngine::CoreUpdate(void* userdata) +// { +// +// auto pge = reinterpret_cast(userdata); +//#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES +// // Multiwindow system uses one event loop (non blocking) for all windows +// pge->host->StartSystemEventLoop(false); +//#endif +// +// +// +// } + +// void PixelGameEngine::EngineThread() +// { +// using namespace std::chrono_literals; +// timeFrame2 = std::chrono::steady_clock::now(); +// timeFrame1 = std::chrono::steady_clock::now(); +// +//#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES +// // Create Primary Window on EngineThread, event loop also exists for all windows +// // on this thread, and all windows will be created on this thread +// host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); +//#endif +// +// +// +// // Initialise ImageLoader Interface +// +// +// +// +// // Link this windows devices +// LinkToHost(host.get()); +// LinkToRenderer(gpu.get()); +// LinkToImageLoader(imageloader.get()); +// +// // The GPU device can be based upon the primary window configuration. This +// // gives us completed gpu and host objects to pass to other windows as and +// // when required +// gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); +// if (gpu->GetLastError() != olc::gpu::RendererError::NoError) +// { +// //const auto e = gpu->GetLastError(); // For debug visibility +// std::cout << "Error: Could not create Renderer\n"; +// return; +// } +// +// +// +// olc::ImageConfig cfg; +// cfg.MSAA = config.bAntiAliasMainScreen; +// CreateImage(GetDefaultImage(), config.vScreenSize, cfg); +// +// +// // Initialise Font System +// olc::pgeguts::CreateClassicFont(this); +// +// +// draw.SetGPU(gpu.get()); +// gpu->ApplyDefaultShader(); +// draw.SetTarget(GetDefaultImage()); +// +// if (!OnUserCreate()) +// { +// // Creation process signalled abort +// return; +// } +// +// +// +// draw.ProcessGPUTasks(); +// draw.SetTarget(GetDefaultImage()); +// +// // Initialise Input Devices +// +// +// +// +// #if OLC_HOST == OLC_HOST_EMSCRIPTEN +// emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); +// #else +// while (coreActive) +// { +// PixelGameEngine::CoreUpdate(this); +// } +// #endif +// } } -//! END IMPLEMENTATION \ No newline at end of file +//! END IMPLEMENTATION + + + +// Javid dump for later + +// //#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES +// // Update Child Windows (if any) +// for (auto& winChild : deqChildWindows) +// winChild->olc_WindowUpdate(fDT, fTT); +// +// // Remove child windows that have requested closure +// if (!deqChildWindows.empty()) +// { +// deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), +// [this](const std::shared_ptr& w) +// { +// if (w->olc_ShouldRemove()) +// { +// host->CloseWindowFrame(w.get()); +// } +// return w->olc_ShouldRemove(); +// } +// ), deqChildWindows.end()); +// } +//#endif \ No newline at end of file diff --git a/dev/src/core.h b/dev/src/core.h index 4187644f..1b85f419 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -156,6 +156,12 @@ namespace olc public: // Core Update static void CoreUpdate(void* userdata); + + + public: // Called from Host + bool OnContextStart(); + bool OnContextTick(); + bool OnContextEnd(); private: // Window Management diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index cf44846a..b63baa19 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -54,6 +54,8 @@ namespace olc #endif } + class PixelGameEngine; + namespace host { @@ -80,25 +82,40 @@ namespace olc // Check/Get last error HostError GetLastError() const { return lastError; } - public: - virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) = 0; - virtual void TerminateSystemEventLoop() = 0; - virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; + // Make OS Close a window frame, associated with olc::Window virtual bool CloseWindowFrame(olc::Window* pWindow) = 0; + // Make OS Update a window frame title, associated with olc::Window virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) = 0; - + // Get OS-specific window descriptor(s) for given olc::Window virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) = 0; - - - virtual bool ConnectHostResourceToRenderer() = 0; - - // Wait for entire host desktop refresh (for smooooth vsync) + // Wait for OS desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) = 0; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() = 0; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() = 0; + // Called at start of system event loop + virtual bool OnSystemThreadStart() = 0; + // Called to perform primary window update + virtual bool OnSystemTick() = 0; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() = 0; + // Called at very end of application + virtual bool OnApplicationEnd() = 0; + virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; protected: HostError lastError = HostError::None; + olc::PixelGameEngine* pPrimaryPGE = nullptr; }; } } diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index a255097d..7b3b8a40 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -1,14 +1,109 @@ #include "host_win_winapi.h" +#include "core.h" //! START IMPLEMENTATION namespace olc::host { + bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Windows_WinAPI::StartSystem() + { + systemActive = true; + + // Create system thread + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); + + return SystemEventLoop(true); + } + + bool Host_Windows_WinAPI::StopSystem() + { + systemActive = false; + return true; + } + + bool Host_Windows_WinAPI::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Windows_WinAPI::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + bool Host_Windows_WinAPI::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Windows_WinAPI::OnApplicationEnd() + { + return true; + } + + + + + + // Forward Declaration static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + // Windows app needs an event loop somewhere. This is blocking of course. This loop handles + // all windows created for this host. + bool Host_Windows_WinAPI::SystemEventLoop(bool bBlockIfPossible) + { + if (bBlockIfPossible) + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else + { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } - - + return true; + } // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get // access to the actual host instance, which can more conveninetly process @@ -311,16 +406,24 @@ namespace olc::host return { mapUID2HWND[pWindow->GetUID()] }; } - bool Host_Windows_WinAPI::ConnectHostResourceToRenderer() - { - return false; - } - bool Host_Windows_WinAPI::SyncWithDesktopComposite() { return DwmFlush() == S_OK; } + + + + + + + + + + + + + LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!mapHWND2PTR.contains(hWnd)) diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index b532585d..3570cd3d 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -4,6 +4,7 @@ #include #include #include +#include //! END STDHEADER //! START CUSTOMHEADER @@ -63,30 +64,45 @@ namespace olc virtual ~Host_Windows_WinAPI() {}; - public: - bool StartSystemEventLoop(bool bBlockIfPossible = false); - void TerminateSystemEventLoop(); - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen); - bool CloseWindowFrame(olc::Window* pWindow); - bool UpdateWindowFrameTitle(olc::Window* pWindow); - - std::vector GetHostWindowDescriptor(olc::Window* pWindow); - bool ConnectHostResourceToRenderer(); - - - // Wait for entire host desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite(); - - LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; - olc::KeyboardLayout GetKeyboardLayout() const override; - - std::string test; - - private: + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + + + private: // Windows OS Specific Things std::unordered_map mapUID2HWND; std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); + std::atomic systemActive = false; + bool SystemEventLoop(const bool bBlockIfPossible = true); + + public: + LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; From c394877a7f110cccab1acb9dba9608f1157e0927 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:29:41 +0000 Subject: [PATCH 48/56] Reworked host_iface for better system abstraction. Decided single window and multiple windows may benefit from seperate hosts to keep core etc much simpler --- dev/src/config.h | 24 ++ dev/src/core.cpp | 94 +++-- dev/src/core.h | 21 +- dev/src/host_iface.h | 5 +- dev/src/host_win_winapi.cpp | 116 ++---- dev/src/host_win_winapi.h | 8 +- dev/src/window.h | 29 +- dev/tests/test_mh.cpp | 7 +- olcPixelGameEngine3.h | 735 ++++++++++++++++++++---------------- 9 files changed, 564 insertions(+), 475 deletions(-) diff --git a/dev/src/config.h b/dev/src/config.h index cb92f1d2..c6811db0 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -132,4 +132,28 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} +#if OLC_HOST == OLC_HOST_WINDOWS +#define OLC_FRIENDLY_HOST Host_Windows_WinAPI; +#endif + +#if OLC_HOST == OLC_HOST_MACOS +#define OLC_FRIENDLY_HOST Host_Apple_MacOS +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 +#define OLC_FRIENDLY_HOST Host_Linux_X11 +#endif + +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND +#define OLC_FRIENDLY_HOST Host_Linux_Wayland +#endif + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN +#define OLC_FRIENDLY_HOST Host_Web_Emscripten +#endif + +#if OLC_HOST == OLC_HOST_ANDROID +#define OLC_FRIENDLY_HOST Host_Android +#endif + //! END CONFIGURATION \ No newline at end of file diff --git a/dev/src/core.cpp b/dev/src/core.cpp index d13695f7..8124881f 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -319,6 +319,10 @@ namespace olc host = std::make_unique(); #endif +#if OLC_HOST == OLC_HOST_ANDROID + host = std::make_unique(); +#endif + // DEVS!! Please don't merge these just yet // Initialise ImageLoader Interface @@ -342,12 +346,20 @@ namespace olc imageloader = std::make_unique(); #endif +#if OLC_HOST == OLC_HOST_ANDROID + imageloader = std::make_unique( + olc::host::Host_Android::androidApp->activity->assetManager + ); +#endif + // Allow host to prepare itself return host->OnApplicationStart(this); } bool PixelGameEngine::Start() { + + bool bStartCheck = host->StartSystem(); // Must block until system is shutdown if(!bStartCheck) std::cout << "PGE Start() Error: Host failed to start\n"; @@ -357,34 +369,12 @@ namespace olc std::cout << "PGE Start() Error: Host failed to shutdown cleanly\n"; return bStartCheck && bEndCheck; + } - -// // Johnnyg63: Added MacOS Host Initialisation -// -//#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO -// // Create OS window on this thread -// host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); -// // Create EngineThread - no more windows will be created now. We needed one window -// // at least to initialise teh rendering subsystem... sigh. -// coreActive = true; -// -//#if OLC_HOST != OLC_HOST_EMSCRIPTEN -// coreThread = std::thread(&PixelGameEngine::EngineThread, this); -// // Handle window events on this thread (and block) -// host->StartSystemEventLoop(true); -// // Window has closed its event handler, so shut down gracefully -// coreActive = false; -// // Wait for engine thread to terminate -// coreThread.join(); -//#else -// EngineThread(); -//#endif -// -//#else -// -//#endif - // - //return true; + bool PixelGameEngine::OnPreContextStart() + { + // Create the window! + return host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); } bool PixelGameEngine::OnContextStart() @@ -398,8 +388,6 @@ namespace olc // this function will probably be called on the main application // thread - // Create the window! - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); // Initialise GPU Interface olc::gpu::RendererConfig cfgRenderer; @@ -500,6 +488,7 @@ namespace olc durationFrameCount -= 1s; std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); SetWindowTitle(sTitle); + fps = frameCount; frameCount = 0; } @@ -531,6 +520,11 @@ namespace olc return true; } + bool PixelGameEngine::OnPostContextEnd() + { + return true; + } + float PixelGameEngine::FrameTimeElapsed() const { return durationFrame.count(); @@ -541,6 +535,11 @@ namespace olc return durationTotalElapsed.count(); } + size_t PixelGameEngine::GetFPS() const + { + return fps; + } + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES @@ -564,6 +563,40 @@ namespace olc #endif } +} +//! END IMPLEMENTATION + +// DEVS!! All your old stuff is below here for reference, but will be removed later + +// // Johnnyg63: Added MacOS Host Initialisation +// +//#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO +// // Create OS window on this thread +// host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); +// // Create EngineThread - no more windows will be created now. We needed one window +// // at least to initialise teh rendering subsystem... sigh. +// coreActive = true; +// +//#if OLC_HOST != OLC_HOST_EMSCRIPTEN +// coreThread = std::thread(&PixelGameEngine::EngineThread, this); +// // Handle window events on this thread (and block) +// host->StartSystemEventLoop(true); +// // Window has closed its event handler, so shut down gracefully +// coreActive = false; +// // Wait for engine thread to terminate +// coreThread.join(); +//#else +// EngineThread(); +//#endif +// +//#else +// +//#endif + // + //return true; + + + // void PixelGameEngine::CoreUpdate(void* userdata) // { // @@ -652,8 +685,7 @@ namespace olc // } // #endif // } -} -//! END IMPLEMENTATION + diff --git a/dev/src/core.h b/dev/src/core.h index 1b85f419..ea46277c 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -129,6 +129,9 @@ namespace olc // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices class PixelGameEngine : public PGEWindow { + // Host needs access to private methods + friend class olc::host::OLC_FRIENDLY_HOST; + public: PixelGameEngine(); virtual ~PixelGameEngine(); @@ -154,14 +157,18 @@ namespace olc public: // Child Windows bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); - public: // Core Update - static void CoreUpdate(void* userdata); - - public: // Called from Host + private: // Called from Host + // Called before any context threads start + bool OnPreContextStart(); + // Called after context thread started, before anything else bool OnContextStart(); + // Called once per tick on context thread bool OnContextTick(); + // Called at end of context thread, after everything else bool OnContextEnd(); + // Called after all context threads ended + bool OnPostContextEnd(); private: // Window Management @@ -176,16 +183,12 @@ namespace olc size_t frameCount = 0; size_t fps = 0; - // Core Thread - std::thread coreThread; - std::atomic coreActive; - void EngineThread(); - // These interfaces are created dynamically by the PGE core // after the environment is understood (or specified by config) std::unique_ptr gpu; std::unique_ptr host; std::unique_ptr imageloader; + }; } #define PGE_CORE_DECLARED 1 diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index b63baa19..1461eaaf 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -94,6 +94,9 @@ namespace olc // Wait for OS desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; + public: // Platform Specific OS<->PGE Linkage // Called at very start of application virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) = 0; @@ -111,8 +114,6 @@ namespace olc // Called at very end of application virtual bool OnApplicationEnd() = 0; - virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; - protected: HostError lastError = HostError::None; olc::PixelGameEngine* pPrimaryPGE = nullptr; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 7b3b8a40..b233244b 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -12,9 +12,13 @@ namespace olc::host bool Host_Windows_WinAPI::StartSystem() { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Mark system as active systemActive = true; - // Create system thread + // Create system thread - handles gpu context std::thread threadSystem([this]() { // Notify start of system thread @@ -42,7 +46,20 @@ namespace olc::host } }); - return SystemEventLoop(true); + // Blocking event loop on this thread - handles windows + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); } bool Host_Windows_WinAPI::StopSystem() @@ -73,37 +90,12 @@ namespace olc::host - + // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE // Forward Declaration static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - // Windows app needs an event loop somewhere. This is blocking of course. This loop handles - // all windows created for this host. - bool Host_Windows_WinAPI::SystemEventLoop(bool bBlockIfPossible) - { - if (bBlockIfPossible) - { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - else - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - return true; - } // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get // access to the actual host instance, which can more conveninetly process @@ -264,38 +256,6 @@ namespace olc::host mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard } - // Windows app needs an event loop somewhere. This is blocking of course. This loop handles - // all windows created for this host. - bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) - { - if (bBlockIfPossible) - { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - else - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - return true; - } - - void Host_Windows_WinAPI::TerminateSystemEventLoop() - { - // Post windows WM_QUIT message - //PostQuitMessage(0); - } - bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { olc_IgnoreUnused(bFullScreen); @@ -411,19 +371,6 @@ namespace olc::host return DwmFlush() == S_OK; } - - - - - - - - - - - - - LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!mapHWND2PTR.contains(hWnd)) @@ -465,10 +412,6 @@ namespace olc::host // case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; // case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; // case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; - // case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(int32_t(wParam), true); return 0; - // case WM_KEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; - // case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(int32_t(wParam), true); return 0; - // case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; case WM_KEYDOWN: { @@ -488,6 +431,24 @@ namespace olc::host break; } + case WM_SYSKEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } + + case WM_SYSKEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } + case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); @@ -562,6 +523,7 @@ namespace olc::host break; //return DefWindowProc(hWnd, uMsg, wParam, lParam); } + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 3570cd3d..db55025f 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -52,9 +52,7 @@ namespace olc { namespace host { - - - + // Host for Windows OS - Single Window Only! class Host_Windows_WinAPI : public olc::host::Host { @@ -76,6 +74,9 @@ namespace olc // Wait for OS desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + public: // OS Specific Environment Information + olc::KeyboardLayout GetKeyboardLayout() const override; + public: // Called at very start of application bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -99,7 +100,6 @@ namespace olc std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); std::atomic systemActive = false; - bool SystemEventLoop(const bool bBlockIfPossible = true); public: LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/dev/src/window.h b/dev/src/window.h index 74495a4d..2e500546 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -20,36 +20,11 @@ //! START DECLARATION #if !defined(PGE_WINDOW_DECLARED) - -#if OLC_HOST == OLC_HOST_WINDOWS - #define FRIENDLY_HOST Host_Windows_WinAPI -#endif - -#if OLC_HOST == OLC_HOST_MACOS - #define FRIENDLY_HOST Host_Apple_MacOS -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - #define FRIENDLY_HOST Host_Linux_X11 -#endif - -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - #define FRIENDLY_HOST Host_Linux_Wayland -#endif - -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - #define FRIENDLY_HOST Host_Web_Emscripten -#endif - -#if OLC_HOST == OLC_HOST_ANDROID - #define FRIENDLY_HOST Host_Android -#endif - namespace olc { namespace host { - class FRIENDLY_HOST; + class OLC_FRIENDLY_HOST; class Host; } @@ -71,7 +46,7 @@ namespace olc class Window { - friend class olc::host::FRIENDLY_HOST; + friend class olc::host::OLC_FRIENDLY_HOST; public: diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 63d6066a..b1b4c299 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -158,7 +158,7 @@ class Example : public olc::PixelGameEngine //return true; - if (mouse.GetButton(1).bHeld) + //if (mouse.GetButton(1).bHeld) { fAngle += 0.5f * fElapsedTime; } @@ -308,7 +308,8 @@ class Example : public olc::PixelGameEngine draw.String({ 10, 10 }, mouse.GetPosition().str(), olc::Colour::BLACK); - + if (keyboard.GetKey(olc::Key::ESCAPE).bPressed) + return false; // End application return true; @@ -620,7 +621,7 @@ int main() cfg.vPixelSize = { 4,4 }; cfg.vScreenSize = { 256, 240 }; - //cfg.bVSync = true; + cfg.bVSync = false; //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) if(demo.Construct(cfg)) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index f08b9034..d423dba0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3087,6 +3087,8 @@ namespace olc #endif } + class PixelGameEngine; + namespace host { @@ -3113,25 +3115,41 @@ namespace olc // Check/Get last error HostError GetLastError() const { return lastError; } - public: - virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) = 0; - virtual void TerminateSystemEventLoop() = 0; - virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; + // Make OS Close a window frame, associated with olc::Window virtual bool CloseWindowFrame(olc::Window* pWindow) = 0; + // Make OS Update a window frame title, associated with olc::Window virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) = 0; - + // Get OS-specific window descriptor(s) for given olc::Window virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) = 0; - - - virtual bool ConnectHostResourceToRenderer() = 0; - - // Wait for entire host desktop refresh (for smooooth vsync) + // Wait for OS desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) = 0; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() = 0; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() = 0; + // Called at start of system event loop + virtual bool OnSystemThreadStart() = 0; + // Called to perform primary window update + virtual bool OnSystemTick() = 0; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() = 0; + // Called at very end of application + virtual bool OnApplicationEnd() = 0; + protected: HostError lastError = HostError::None; + olc::PixelGameEngine* pPrimaryPGE = nullptr; }; } } @@ -3270,6 +3288,19 @@ namespace olc public: // Core Update static void CoreUpdate(void* userdata); + + + public: // Called from Host + // Called before any context threads start + bool OnPreContextStart(); + // Called after context thread started, before anything else + bool OnContextStart(); + // Called once per tick on context thread + bool OnContextTick(); + // Called at end of context thread, after everything else + bool OnContextEnd(); + // Called after all context threads ended + bool OnPostContextEnd(); private: // Window Management @@ -3359,30 +3390,48 @@ namespace olc virtual ~Host_Windows_WinAPI() {}; - public: - bool StartSystemEventLoop(bool bBlockIfPossible = false); - void TerminateSystemEventLoop(); - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen); - bool CloseWindowFrame(olc::Window* pWindow); - bool UpdateWindowFrameTitle(olc::Window* pWindow); - - std::vector GetHostWindowDescriptor(olc::Window* pWindow); - bool ConnectHostResourceToRenderer(); - - - // Wait for entire host desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite(); - - LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; - std::string test; - - private: + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + + + private: // Windows OS Specific Things std::unordered_map mapUID2HWND; std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); + std::atomic systemActive = false; + bool SystemEventLoop(const bool bBlockIfPossible = true); + + public: + LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; @@ -5630,12 +5679,118 @@ namespace olc::imload #if OLC_HOST == OLC_HOST_WINDOWS namespace olc::host { + bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Windows_WinAPI::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Mark system as active + systemActive = true; + + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); + + // Blocking event loop on this thread - handles windows + SystemEventLoop(true); + + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); + } + + bool Host_Windows_WinAPI::StopSystem() + { + systemActive = false; + return true; + } + + bool Host_Windows_WinAPI::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Windows_WinAPI::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + bool Host_Windows_WinAPI::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Windows_WinAPI::OnApplicationEnd() + { + return true; + } + + + + // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE + + // Forward Declaration static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + // Windows app needs an event loop somewhere. This is blocking of course. This loop handles + // all windows created for this host. + bool Host_Windows_WinAPI::SystemEventLoop(bool bBlockIfPossible) + { + if (bBlockIfPossible) + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else + { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } - - + return true; + } // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get // access to the actual host instance, which can more conveninetly process @@ -5796,38 +5951,6 @@ namespace olc::host mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard } - // Windows app needs an event loop somewhere. This is blocking of course. This loop handles - // all windows created for this host. - bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) - { - if (bBlockIfPossible) - { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - else - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - return true; - } - - void Host_Windows_WinAPI::TerminateSystemEventLoop() - { - // Post windows WM_QUIT message - //PostQuitMessage(0); - } - bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { olc_IgnoreUnused(bFullScreen); @@ -5938,11 +6061,6 @@ namespace olc::host return { mapUID2HWND[pWindow->GetUID()] }; } - bool Host_Windows_WinAPI::ConnectHostResourceToRenderer() - { - return false; - } - bool Host_Windows_WinAPI::SyncWithDesktopComposite() { return DwmFlush() == S_OK; @@ -5989,10 +6107,6 @@ namespace olc::host // case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; // case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; // case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; - // case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(int32_t(wParam), true); return 0; - // case WM_KEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; - // case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(int32_t(wParam), true); return 0; - // case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(int32_t(wParam), false); return 0; case WM_KEYDOWN: { @@ -6012,6 +6126,24 @@ namespace olc::host break; } + case WM_SYSKEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } + + case WM_SYSKEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } + case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); @@ -14731,316 +14863,275 @@ namespace olc config.vScreenSize = vScreenSize; config.vPixelSize = vPixelSize; config.bFullScreen = bFullScreen; - return true; + return Construct(config); } bool PixelGameEngine::Construct(const PGEConfig& cfg) { config = cfg; - return true; - } - bool PixelGameEngine::Start() - { + // This is the earliest point within familiar PGE ecosystem + // where we can instatiate the host interface. + // Initialise Host Interface - #if OLC_HOST == OLC_HOST_WINDOWS +#if OLC_HOST == OLC_HOST_WINDOWS host = std::make_unique(); - #endif - // Johnnyg63: Added MacOS Host Initialisation - #if OLC_HOST == OLC_HOST_MACOS +#endif + +#if OLC_HOST == OLC_HOST_MACOS host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_LINUX_X11 +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_LINUX_WAYLAND +#endif + +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN +#endif + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN host = std::make_unique(); - #endif - #if OLC_HOST == OLC_HOST_ANDROID - host = std::make_unique(); - auto hostPtr = (dynamic_cast(host.get())); - #endif -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO - // Create OS window on this thread - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); - // Create EngineThread - no more windows will be created now. We needed one window - // at least to initialise teh rendering subsystem... sigh. - coreActive = true; +#endif -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID - // Create EngineThread - coreThread = std::thread(&PixelGameEngine::EngineThread, this); - // Handle window events on this thread (and block) - host->StartSystemEventLoop(true); - // Window has closed its event handler, so shut down gracefully - coreActive = false; - // Wait for engine thread to terminate - coreThread.join(); -#elif OLC_HOST == OLC_HOST_ANDROID - // We need to wait for the APP_CMD_INIT_WINDOW command before starting the loop - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initializing..."); - while (!hostPtr->IsInitialized()) { - host->StartSystemEventLoop(false); - } +#if OLC_HOST == OLC_HOST_ANDROID + host = std::make_unique(); +#endif - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", "Initialized Successfully"); + // DEVS!! Please don't merge these just yet - EngineThread(); -#else - EngineThread(); + // Initialise ImageLoader Interface +#if OLC_HOST == OLC_HOST_WINDOWS + imageloader = std::make_unique(); #endif -#else - +#if OLC_HOST == OLC_HOST_MACOS + imageloader = std::make_unique(); #endif - - return true; - } - float PixelGameEngine::FrameTimeElapsed() const - { - return durationFrame.count(); - } +#if OLC_HOST == OLC_HOST_LINUX_X11 + imageloader = std::make_unique(); +#endif - double PixelGameEngine::TotalTimeElapsed() const - { - return durationTotalElapsed.count(); - } +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + imageloader = std::make_unique(); +#endif - size_t PixelGameEngine::GetFPS() const - { - return fps; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + imageloader = std::make_unique(); +#endif + +#if OLC_HOST == OLC_HOST_ANDROID + imageloader = std::make_unique( + olc::host::Host_Android::androidApp->activity->assetManager + ); +#endif + + // Allow host to prepare itself + return host->OnApplicationStart(this); } - bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) - { -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Link this olc::Window to a host resource - if (host) - { - // TODO: Connect to config + bool PixelGameEngine::Start() + { + - host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); - window->LinkToHost(host.get()); - window->LinkToRenderer(gpu.get()); - window->LinkToImageLoader(imageloader.get()); + bool bStartCheck = host->StartSystem(); // Must block until system is shutdown + if(!bStartCheck) + std::cout << "PGE Start() Error: Host failed to start\n"; - //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); - window->Create(vScreenSize, vPixelSize); - - deqChildWindows.push_back(window); - } - return true; -#else - // Can't create new windows - olc_IgnoreUnused(window, vScreenSize, vPixelSize); - return false; -#endif + bool bEndCheck = host->OnApplicationEnd(); + if (!bEndCheck) + std::cout << "PGE Start() Error: Host failed to shutdown cleanly\n"; + + return bStartCheck && bEndCheck; } - void PixelGameEngine::CoreUpdate(void* userdata) + bool PixelGameEngine::OnPreContextStart() { - using namespace std::chrono_literals; - auto pge = reinterpret_cast(userdata); -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Multiwindow system uses one event loop (non blocking) for all windows - pge->host->StartSystemEventLoop(false); -#endif - - // Frame Delta Timing - "ElapsedTime" since last core update - // ~~~~~~~~~~~~~~~~~~ - // All timing is synchronous to the primary window, i.e child - // windows do not maintain their own frame timing. This is a - // deliberate decision as PGE will maintain sync between windows. - // Why? Multiple windows are not really the point of PGE and - // indeed could just be addional unnecessary complexity. However, - // by moving to a Window abstraction we kinda get it for free. - // This freedom comes at the expense of complexity. If we allowed - // windows to be wholly isolated from the core loop, then the - // user is expected to maintain thread safety, context sharing - // and resource management. That is not olc::PGE. - - pge->timeFrame1 = std::chrono::steady_clock::now(); - pge->durationFrame = pge->timeFrame1 - pge->timeFrame2; - pge->timeFrame2 = pge->timeFrame1; - - pge->durationTotalElapsed += pge->durationFrame; - - // Our time per frame coefficient - float fDT = pge->durationFrame.count(); - - // Our Total Time accumulator - float fTT = float(pge->durationTotalElapsed.count()); + // Create the window! + return host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); + } - pge->frameCount++; - pge->durationFrameCount += pge->durationFrame; - - if (pge->durationFrameCount >= 1s) - { - pge->durationFrameCount -= 1s; - pge->fps = pge->frameCount; - std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(pge->frameCount); - pge->SetWindowTitle(sTitle); - pge->frameCount = 0; - } - - - - // Primary Window - if (pge->olc_ShouldRemove()) - { - // Application is to be terminated as primary window has closed - pge->coreActive = false; -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - emscripten_cancel_main_loop(); -#endif - } - else - { -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Update Child Windows (if any) - for (auto& winChild : deqChildWindows) - winChild->olc_WindowUpdate(fDT, fTT); + bool PixelGameEngine::OnContextStart() + { + // The "context" refers to the thread (or execution pathway) + // that will be hosting the main rendering loop. Things like + // OpenGL contexts are thread specific, so all GPU initialisation + // must be performed here for example - // Remove child windows that have requested closure - if (!deqChildWindows.empty()) - { - deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), - [this](const std::shared_ptr& w) - { - if (w->olc_ShouldRemove()) - { - host->CloseWindowFrame(w.get()); - } - return w->olc_ShouldRemove(); - } - ), deqChildWindows.end()); - } -#endif + // If the host doesnt use a thread for its event loop, then + // this function will probably be called on the main application + // thread - // Update Primary Window - pge->olc_WindowUpdate(fDT, fTT); - // Wait for vertical sync if required. - // Note: Child windows will never vsync as waiting for each buffer swap with vsync - // divides up the frame rate budget across the windows. - if (pge->gpu->GetConfig().VerticalSync) - { - pge->host->SyncWithDesktopComposite(); - } - } - - } + // Initialise GPU Interface + olc::gpu::RendererConfig cfgRenderer; + cfgRenderer.VerticalSync = config.bVSync; + gpu = std::make_unique(); - void PixelGameEngine::EngineThread() - { - using namespace std::chrono_literals; - timeFrame2 = std::chrono::steady_clock::now(); - timeFrame1 = std::chrono::steady_clock::now(); + gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); + if (gpu->GetLastError() != olc::gpu::RendererError::NoError) + { + std::cout << "PGE OnContextStart() Error: Could not create Renderer\n"; + return false; + } -#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES - // Create Primary Window on EngineThread, event loop also exists for all windows - // on this thread, and all windows will be created on this thread - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); -#endif + // Link this olc::Window to host and renderer resources + LinkToHost(host.get()); + // Link this olc::PGEWindow to renderer and imageloader + LinkToRenderer(gpu.get()); + LinkToImageLoader(imageloader.get()); - // Initialise ImageLoader Interface -#if OLC_HOST == OLC_HOST_WINDOWS - imageloader = std::make_unique(); -#endif + // These things require a valid GPU context - // Initialise ImageLoader Interface -#if OLC_HOST == OLC_HOST_MACOS - imageloader = std::make_unique(); -#endif + // Create Primary olc::Image - aka "The Screen" + olc::ImageConfig cfg; + cfg.MSAA = config.bAntiAliasMainScreen; + CreateImage(GetDefaultImage(), config.vScreenSize, cfg); -#if OLC_HOST == OLC_HOST_LINUX_X11 - imageloader = std::make_unique(); -#endif + // Initialise "Classic" Font System + olc::pgeguts::CreateClassicFont(this); -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - imageloader = std::make_unique(); -#endif + // Prepare Draw2D system + draw.SetGPU(gpu.get()); + gpu->ApplyDefaultShader(); + draw.SetTarget(GetDefaultImage()); -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - imageloader = std::make_unique(); -#endif + // User Create GOOOOOOOOO!!!! + if (!OnUserCreate()) + { + std::cout << "PGE OnContextStart(): User aborted OnUserCreate()\n"; + return false; + } -#if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( - olc::host::Host_Android::androidApp->activity->assetManager - ); -#endif + // It's possible to draw things in create so flush any pending GPU tasks + draw.ProcessGPUTasks(); - // Initialise GPU Interface - This thread is the context - olc::gpu::RendererConfig cfgRenderer; - cfgRenderer.VerticalSync = config.bVSync; + // Set to known default state + draw.SetTarget(GetDefaultImage()); + draw.WorldReset(); + gpu->ApplyDefaultShader(); - gpu = std::make_unique(); + // Fire up the engine! + using namespace std::chrono_literals; + durationFrameCount = 0s; + durationTotalElapsed = 0s; + timeFrame1 = std::chrono::steady_clock::now(); + timeFrame2 = std::chrono::steady_clock::now(); + frameCount = 0; + return true; + } - // Link this windows devices - LinkToHost(host.get()); - LinkToRenderer(gpu.get()); - LinkToImageLoader(imageloader.get()); + bool PixelGameEngine::OnContextTick() + { + // This is called once per frame from the host's system loop - // The GPU device can be based upon the primary window configuration. This - // gives us completed gpu and host objects to pass to other windows as and - // when required - gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); - if (gpu->GetLastError() != olc::gpu::RendererError::NoError) - { - //const auto e = gpu->GetLastError(); // For debug visibility - std::cout << "Error: Could not create Renderer\n"; - return; - } + // Frame Delta Timing - "ElapsedTime" since last core update + // ~~~~~~~~~~~~~~~~~~ + // All timing is synchronous to the primary window, i.e child + // windows do not maintain their own frame timing. This is a + // deliberate decision as PGE will maintain sync between windows. + // Why? Multiple windows are not really the point of PGE and + // indeed could just be addional unnecessary complexity. However, + // by moving to a Window abstraction we kinda get it for free. + // This freedom comes at the expense of complexity. If we allowed + // windows to be wholly isolated from the core loop, then the + // user is expected to maintain thread safety, context sharing + // and resource management. That is not olc::PGE. + using namespace std::chrono_literals; + + // Calculate last frame time + timeFrame1 = std::chrono::steady_clock::now(); + durationFrame = timeFrame1 - timeFrame2; + timeFrame2 = timeFrame1; + // Accumulate total time elapsed since application start + durationTotalElapsed += durationFrame; - olc::ImageConfig cfg; - cfg.MSAA = config.bAntiAliasMainScreen; - CreateImage(GetDefaultImage(), config.vScreenSize, cfg); + // Our time per frame coefficient + float fDT = durationFrame.count(); + // Our Total Time accumulator + float fTT = float(durationTotalElapsed.count()); - // Initialise Font System - olc::pgeguts::CreateClassicFont(this); + // Calculate FPS every second + frameCount++; + durationFrameCount += durationFrame; + if (durationFrameCount >= 1s) + { + durationFrameCount -= 1s; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); + SetWindowTitle(sTitle); + frameCount = 0; + } + // If primary window is to be closed, signal application termination + if (olc_ShouldRemove()) + { + return false; // Terminate application + } + else + { + // Update Primary Window + olc_WindowUpdate(fDT, fTT); - draw.SetGPU(gpu.get()); - gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + // Wait for vertical sync with desktop compositor if required. + + // Note: Child windows will never vsync as waiting for each buffer swap with vsync + // divides up the frame rate budget across the windows. + if (gpu->GetConfig().VerticalSync) + { + host->SyncWithDesktopComposite(); + } + } - if (!OnUserCreate()) - { - // Creation process signalled abort - return; - } + return true; // Keep going + } - draw.ProcessGPUTasks(); - draw.SetTarget(GetDefaultImage()); + bool PixelGameEngine::OnContextEnd() + { + return true; + } - // Initialise Input Devices + bool PixelGameEngine::OnPostContextEnd() + { + return true; + } + float PixelGameEngine::FrameTimeElapsed() const + { + return durationFrame.count(); + } - durationFrameCount = 0s; + double PixelGameEngine::TotalTimeElapsed() const + { + return durationTotalElapsed.count(); + } -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) + { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Link this olc::Window to a host resource + if (host) + { + host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); + window->LinkToHost(host.get()); + window->LinkToRenderer(gpu.get()); + window->LinkToImageLoader(imageloader.get()); + + //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); + window->Create(vScreenSize, vPixelSize); + deqChildWindows.push_back(window); + } + return true; #else - while (coreActive) - { -#if OLC_HOST == OLC_HOST_ANDROID - if (!host->StartSystemEventLoop(false)) { - coreActive = false; - } -#endif - PixelGameEngine::CoreUpdate(this); - } - host->TerminateSystemEventLoop(); + // Can't create new windows + olc_IgnoreUnused(window, vScreenSize, vPixelSize); + return false; #endif - } + } + } #define PGE_CORE_IMPLEMENTED 1 #endif From 3be422c2f12818e1fac484bf3badaf8cf5f3d43c Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:31:25 +0000 Subject: [PATCH 49/56] headere sync --- olcPixelGameEngine3.h | 111 +++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 66 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d423dba0..c13c5823 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -266,6 +266,30 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} +#if OLC_HOST == OLC_HOST_WINDOWS +#define OLC_FRIENDLY_HOST Host_Windows_WinAPI; +#endif + +#if OLC_HOST == OLC_HOST_MACOS +#define OLC_FRIENDLY_HOST Host_Apple_MacOS +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 +#define OLC_FRIENDLY_HOST Host_Linux_X11 +#endif + +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND +#define OLC_FRIENDLY_HOST Host_Linux_Wayland +#endif + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN +#define OLC_FRIENDLY_HOST Host_Web_Emscripten +#endif + +#if OLC_HOST == OLC_HOST_ANDROID +#define OLC_FRIENDLY_HOST Host_Android +#endif + #if !defined(PGE_PIXEL_DECLARED) namespace olc @@ -2930,36 +2954,11 @@ namespace olc #endif #if !defined(PGE_WINDOW_DECLARED) - -#if OLC_HOST == OLC_HOST_WINDOWS - #define FRIENDLY_HOST Host_Windows_WinAPI -#endif - -#if OLC_HOST == OLC_HOST_MACOS - #define FRIENDLY_HOST Host_Apple_MacOS -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - #define FRIENDLY_HOST Host_Linux_X11 -#endif - -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - #define FRIENDLY_HOST Host_Linux_Wayland -#endif - -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - #define FRIENDLY_HOST Host_Web_Emscripten -#endif - -#if OLC_HOST == OLC_HOST_ANDROID - #define FRIENDLY_HOST Host_Android -#endif - namespace olc { namespace host { - class FRIENDLY_HOST; + class OLC_FRIENDLY_HOST; class Host; } @@ -2981,7 +2980,7 @@ namespace olc class Window { - friend class olc::host::FRIENDLY_HOST; + friend class olc::host::OLC_FRIENDLY_HOST; public: @@ -3261,6 +3260,9 @@ namespace olc // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices class PixelGameEngine : public PGEWindow { + // Host needs access to private methods + friend class olc::host::OLC_FRIENDLY_HOST; + public: PixelGameEngine(); virtual ~PixelGameEngine(); @@ -3286,11 +3288,8 @@ namespace olc public: // Child Windows bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); - public: // Core Update - static void CoreUpdate(void* userdata); - - public: // Called from Host + private: // Called from Host // Called before any context threads start bool OnPreContextStart(); // Called after context thread started, before anything else @@ -3315,16 +3314,12 @@ namespace olc size_t frameCount = 0; size_t fps = 0; - // Core Thread - std::thread coreThread; - std::atomic coreActive; - void EngineThread(); - // These interfaces are created dynamically by the PGE core // after the environment is understood (or specified by config) std::unique_ptr gpu; std::unique_ptr host; std::unique_ptr imageloader; + }; } #define PGE_CORE_DECLARED 1 @@ -3378,9 +3373,7 @@ namespace olc { namespace host { - - - + // Host for Windows OS - Single Window Only! class Host_Windows_WinAPI : public olc::host::Host { @@ -3428,7 +3421,6 @@ namespace olc std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); std::atomic systemActive = false; - bool SystemEventLoop(const bool bBlockIfPossible = true); public: LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -5722,7 +5714,12 @@ namespace olc::host }); // Blocking event loop on this thread - handles windows - SystemEventLoop(true); + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } systemActive = false; if(threadSystem.joinable()) @@ -5766,31 +5763,6 @@ namespace olc::host // Forward Declaration static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - // Windows app needs an event loop somewhere. This is blocking of course. This loop handles - // all windows created for this host. - bool Host_Windows_WinAPI::SystemEventLoop(bool bBlockIfPossible) - { - if (bBlockIfPossible) - { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - else - { - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - return true; - } // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get // access to the actual host instance, which can more conveninetly process @@ -6218,6 +6190,7 @@ namespace olc::host break; //return DefWindowProc(hWnd, uMsg, wParam, lParam); } + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); @@ -15063,6 +15036,7 @@ namespace olc durationFrameCount -= 1s; std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); SetWindowTitle(sTitle); + fps = frameCount; frameCount = 0; } @@ -15109,6 +15083,11 @@ namespace olc return durationTotalElapsed.count(); } + size_t PixelGameEngine::GetFPS() const + { + return fps; + } + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES From a91117cad365f3de93fd4ad8775521b52859dd2e Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:45:34 +0000 Subject: [PATCH 50/56] optimised host selection --- dev/src/config.h | 2 +- dev/src/core.cpp | 25 ++----------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/dev/src/config.h b/dev/src/config.h index c6811db0..9967d0a6 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -133,7 +133,7 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #if OLC_HOST == OLC_HOST_WINDOWS -#define OLC_FRIENDLY_HOST Host_Windows_WinAPI; +#define OLC_FRIENDLY_HOST Host_Windows_WinAPI #endif #if OLC_HOST == OLC_HOST_MACOS diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 8124881f..7cec77a0 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -299,29 +299,8 @@ namespace olc // where we can instatiate the host interface. // Initialise Host Interface -#if OLC_HOST == OLC_HOST_WINDOWS - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_MACOS - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - host = std::make_unique(); -#endif + host = std::make_unique(); -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_ANDROID - host = std::make_unique(); -#endif // DEVS!! Please don't merge these just yet @@ -401,7 +380,7 @@ namespace olc return false; } - // Link this olc::Window to host and renderer resources + // Link this olc::Window to host LinkToHost(host.get()); // Link this olc::PGEWindow to renderer and imageloader LinkToRenderer(gpu.get()); From ebe9646af3d9bf8063bf51a5fc08f11c1db07d53 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:03:42 +0000 Subject: [PATCH 51/56] sync --- olcPixelGameEngine3.h | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c13c5823..1fb48e74 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -267,7 +267,7 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #if OLC_HOST == OLC_HOST_WINDOWS -#define OLC_FRIENDLY_HOST Host_Windows_WinAPI; +#define OLC_FRIENDLY_HOST Host_Windows_WinAPI #endif #if OLC_HOST == OLC_HOST_MACOS @@ -14847,29 +14847,8 @@ namespace olc // where we can instatiate the host interface. // Initialise Host Interface -#if OLC_HOST == OLC_HOST_WINDOWS - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_MACOS - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - host = std::make_unique(); -#endif + host = std::make_unique(); -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - host = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_ANDROID - host = std::make_unique(); -#endif // DEVS!! Please don't merge these just yet @@ -14949,7 +14928,7 @@ namespace olc return false; } - // Link this olc::Window to host and renderer resources + // Link this olc::Window to host LinkToHost(host.get()); // Link this olc::PGEWindow to renderer and imageloader LinkToRenderer(gpu.get()); From 274e531a83e9a0791475ce7fd51a60bbe03d9ad9 Mon Sep 17 00:00:00 2001 From: dandistine <> Date: Sun, 1 Feb 2026 10:33:37 -0600 Subject: [PATCH 52/56] Update the linux platforms --- dev/src/host_lin_wayland.cpp | 77 +++++-- dev/src/host_lin_wayland.h | 17 +- dev/src/host_lin_x11.cpp | 265 ++++++++++++++---------- dev/src/host_lin_x11.h | 22 +- olcPixelGameEngine3.h | 379 ++++++++++++++++++++++------------- 5 files changed, 492 insertions(+), 268 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index f39054e4..dd011154 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -154,20 +154,77 @@ namespace olc::host wl_display_disconnect(display); } - bool Host_Linux_Wayland::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Linux_Wayland::OnApplicationStart(olc::PixelGameEngine* pPrimary) { - if(bBlockIfPossible) { - while(!terminate && wl_display_dispatch_pending(display) != -1) { + pPrimaryPGE = pPrimary; + return true; + } - } - } + bool Host_Linux_Wayland::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); + + while(systemActive && wl_display_dispatch_pending(display) != -1) { } + + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + + return pPrimaryPGE->OnPostContextEnd(); + } + bool Host_Linux_Wayland::StopSystem() + { + systemActive = false; return true; } - void Host_Linux_Wayland::TerminateSystemEventLoop() + bool Host_Linux_Wayland::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Linux_Wayland::OnSystemTick() { - terminate = true; + return pPrimaryPGE->OnContextTick();; + } + + bool Host_Linux_Wayland::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Linux_Wayland::OnApplicationEnd() + { + return true; } bool Host_Linux_Wayland::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) @@ -332,7 +389,6 @@ namespace olc::host if(itr != mapUID2OlcWindow.end()) { auto* ptr = itr->second; ptr->olc_OnWindowClose(); - terminate = true; } } } @@ -691,11 +747,6 @@ namespace olc::host return {}; } - bool Host_Linux_Wayland::ConnectHostResourceToRenderer() - { - return true; - } - bool Host_Linux_Wayland::SyncWithDesktopComposite() { return true; diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index e65fb7c1..2075dce7 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include //! END STDHEADER @@ -96,16 +97,11 @@ namespace olc::host Host_Linux_Wayland(); ~Host_Linux_Wayland(); - bool StartSystemEventLoop(bool bBlockIfPossible = false) override; - void TerminateSystemEventLoop() override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - - bool ConnectHostResourceToRenderer() override; olc::KeyboardLayout GetKeyboardLayout() const override; void UpdateKeyboardLayout(); @@ -113,6 +109,15 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + public: + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + bool StartSystem() override; + bool StopSystem() override; + bool OnSystemThreadStart() override; + bool OnSystemTick() override; + bool OnSystemThreadEnd() override; + bool OnApplicationEnd() override; + // Various callbacks from the wayland protocol static void registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version); static void registry_handle_global_remove_callback(void* data, wl_registry* registry, uint32_t name); @@ -184,7 +189,7 @@ namespace olc::host std::unordered_map mapUID2Window; std::unordered_map mapUID2OlcWindow; - std::atomic terminate {false}; + std::atomic systemActive {true}; std::unordered_map mapKeys; olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 52651e85..5637473e 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -73,10 +73,46 @@ namespace olc::host mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; } - bool Host_Linux_X11::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Linux_X11::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Linux_X11::StartSystem() { using namespace X11; + pPrimaryPGE->OnPreContextStart(); + + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); + auto get_pge_window = [&](auto x11_window) -> olc::Window* { auto itr = mapX11Window2PTR.find(x11_window); if(itr != mapX11Window2PTR.end()) { @@ -85,128 +121,149 @@ namespace olc::host return nullptr; }; - //std::unordered_map mapX11Window2PTR; - - if(bBlockIfPossible) { - X11::XEvent xev; - while(!terminate){ - while (XPending(olc_Display)) + X11::XEvent xev; + while(systemActive){ + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + + // If there's an update to the keyboard, update it's layout. + if (xev.type == xkbEventBase + XkbEventCode && kbExtensionsFound) { - XNextEvent(olc_Display, &xev); - - // If there's an update to the keyboard, update it's layout. - if (xev.type == xkbEventBase + XkbEventCode && kbExtensionsFound) - { - UpdateKeyboardLayout(); - } + UpdateKeyboardLayout(); + } - if (xev.type == Expose) - { - //auto* expose_event = reinterpret_cast(&xev); - X11::XExposeEvent& e = xev.xexpose; - if(auto* pge_window = get_pge_window(e.window); pge_window) { - X11::XWindowAttributes gwa; - X11::XGetWindowAttributes(e.display, e.window, &gwa); - pge_window->olc_OnWindowSize(olc::vi2d{gwa.width, gwa.height}); - } + if (xev.type == Expose) + { + //auto* expose_event = reinterpret_cast(&xev); + X11::XExposeEvent& e = xev.xexpose; + if(auto* pge_window = get_pge_window(e.window); pge_window) { + X11::XWindowAttributes gwa; + X11::XGetWindowAttributes(e.display, e.window, &gwa); + pge_window->olc_OnWindowSize(olc::vi2d{gwa.width, gwa.height}); } - else if (xev.type == ConfigureNotify) - { - X11::XConfigureEvent& xce = xev.xconfigure; - if(auto* pge_window = get_pge_window(xce.window); pge_window) { - pge_window->olc_OnWindowSize(olc::vi2d{xce.width, xce.height}); - } + } + else if (xev.type == ConfigureNotify) + { + X11::XConfigureEvent& xce = xev.xconfigure; + if(auto* pge_window = get_pge_window(xce.window); pge_window) { + pge_window->olc_OnWindowSize(olc::vi2d{xce.width, xce.height}); } - else if (xev.type == KeyPress) - { - KeySym ks; - - // Unset the "shift" bit so that Key and Shift-Key will be mapped to the same olc::key - // since the system kind of assumes this - xev.xkey.state &= ~(1); - - XLookupString(&xev.xkey, NULL, 0, &ks, NULL); - - if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { - auto it = mapKeys.find(static_cast(ks)); - if(it != mapKeys.end()) { - pge_window->olc_OnKeyPress(it->second, true); - } + } + else if (xev.type == KeyPress) + { + KeySym ks; + + // Unset the "shift" bit so that Key and Shift-Key will be mapped to the same olc::key + // since the system kind of assumes this + xev.xkey.state &= ~(1); + + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, true); } } - else if (xev.type == KeyRelease) - { - KeySym ks; + } + else if (xev.type == KeyRelease) + { + KeySym ks; - XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); - if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { - auto it = mapKeys.find(static_cast(ks)); - if(it != mapKeys.end()) { - pge_window->olc_OnKeyPress(it->second, false); - } + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, false); } } - else if (xev.type == ButtonPress) - { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - switch (xev.xbutton.button) - { - case 1: pge_window->olc_OnMouseButton(0, true); break; - case 2: pge_window->olc_OnMouseButton(2, true); break; - case 3: pge_window->olc_OnMouseButton(1, true); break; - case 4: pge_window->olc_OnMouseWheel(120); break; - case 5: pge_window->olc_OnMouseWheel(-120); break; - default: break; - } - + } + else if (xev.type == ButtonPress) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + switch (xev.xbutton.button) + { + case 1: pge_window->olc_OnMouseButton(0, true); break; + case 2: pge_window->olc_OnMouseButton(2, true); break; + case 3: pge_window->olc_OnMouseButton(1, true); break; + case 4: pge_window->olc_OnMouseWheel(120); break; + case 5: pge_window->olc_OnMouseWheel(-120); break; + default: break; } + } - else if (xev.type == ButtonRelease) - { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - switch (xev.xbutton.button) - { - case 1: pge_window->olc_OnMouseButton(0, false); break; - case 2: pge_window->olc_OnMouseButton(2, false); break; - case 3: pge_window->olc_OnMouseButton(1, false); break; - default: break; - } - } + } + else if (xev.type == ButtonRelease) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + switch (xev.xbutton.button) + { + case 1: pge_window->olc_OnMouseButton(0, false); break; + case 2: pge_window->olc_OnMouseButton(2, false); break; + case 3: pge_window->olc_OnMouseButton(1, false); break; + default: break; + } } - else if (xev.type == MotionNotify) - { - X11::XMotionEvent& xme = xev.xmotion; - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - pge_window->olc_OnMouseMove(olc::vi2d{xme.x, xme.y}); - - } + } + else if (xev.type == MotionNotify) + { + X11::XMotionEvent& xme = xev.xmotion; + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + pge_window->olc_OnMouseMove(olc::vi2d{xme.x, xme.y}); + } - // else if (xev.type == FocusIn) - // { - // ptrPGE->olc_UpdateKeyFocus(true); - // } - // else if (xev.type == FocusOut) - // { - // ptrPGE->olc_UpdateKeyFocus(false); - // } - else if (xev.type == ClientMessage) - { - X11::XClientMessageEvent& xcme = xev.xclient; - if(auto* pge_window = get_pge_window(xcme.window); pge_window) { - pge_window->olc_OnWindowClose(); - return false; - } + } + // else if (xev.type == FocusIn) + // { + // ptrPGE->olc_UpdateKeyFocus(true); + // } + // else if (xev.type == FocusOut) + // { + // ptrPGE->olc_UpdateKeyFocus(false); + // } + else if (xev.type == ClientMessage) + { + X11::XClientMessageEvent& xcme = xev.xclient; + if(auto* pge_window = get_pge_window(xcme.window); pge_window) { + pge_window->olc_OnWindowClose(); } } } } + + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + bool Host_Linux_X11::StopSystem() + { + systemActive = false; return true; } - void Host_Linux_X11::TerminateSystemEventLoop() + bool Host_Linux_X11::OnSystemThreadStart() { - terminate = true; + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Linux_X11::OnSystemTick() + { + return pPrimaryPGE->OnContextTick();; + } + + bool Host_Linux_X11::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Linux_X11::OnApplicationEnd() + { + return true; } bool Host_Linux_X11::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) @@ -271,12 +328,6 @@ namespace olc::host } return {}; } - - - bool Host_Linux_X11::ConnectHostResourceToRenderer() - { - return true; - } olc::KeyboardLayout Host_Linux_X11::GetKeyboardLayout() const { return keyboardLayout; diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index d68f7566..f2f070f8 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -3,6 +3,7 @@ //! START STDHEADER GLOBAL #include #include +#include #include #include //! END STDHEADER @@ -28,32 +29,37 @@ namespace olc::host private: X11::Display* olc_Display = nullptr; X11::Window olc_WindowRoot; - //X11::Window olc_Window; X11::XVisualInfo* olc_VisualInfo; X11::Colormap olc_ColourMap; X11::XSetWindowAttributes olc_SetWindowAttribs; public: Host_Linux_X11(); - bool StartSystemEventLoop(bool bBlockIfPossible = false) override; - void TerminateSystemEventLoop() override; + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - - bool ConnectHostResourceToRenderer() override; - + olc::KeyboardLayout GetKeyboardLayout() const override; void UpdateKeyboardLayout(); // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + + public: + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + bool StartSystem() override; + bool StopSystem() override; + bool OnSystemThreadStart() override; + bool OnSystemTick() override; + bool OnSystemThreadEnd() override; + bool OnApplicationEnd() override; + private: std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; - std::atomic terminate {false}; + std::atomic systemActive {true}; std::unordered_map mapKeys; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1fb48e74..4d3ca269 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4552,32 +4552,37 @@ namespace olc::host private: X11::Display* olc_Display = nullptr; X11::Window olc_WindowRoot; - //X11::Window olc_Window; X11::XVisualInfo* olc_VisualInfo; X11::Colormap olc_ColourMap; X11::XSetWindowAttributes olc_SetWindowAttribs; public: Host_Linux_X11(); - bool StartSystemEventLoop(bool bBlockIfPossible = false) override; - void TerminateSystemEventLoop() override; + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - - bool ConnectHostResourceToRenderer() override; - + olc::KeyboardLayout GetKeyboardLayout() const override; void UpdateKeyboardLayout(); // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + + public: + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + bool StartSystem() override; + bool StopSystem() override; + bool OnSystemThreadStart() override; + bool OnSystemTick() override; + bool OnSystemThreadEnd() override; + bool OnApplicationEnd() override; + private: std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; - std::atomic terminate {false}; + std::atomic systemActive {true}; std::unordered_map mapKeys; @@ -4676,16 +4681,11 @@ namespace olc::host Host_Linux_Wayland(); ~Host_Linux_Wayland(); - bool StartSystemEventLoop(bool bBlockIfPossible = false) override; - void TerminateSystemEventLoop() override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - - bool ConnectHostResourceToRenderer() override; olc::KeyboardLayout GetKeyboardLayout() const override; void UpdateKeyboardLayout(); @@ -4693,6 +4693,15 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + public: + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + bool StartSystem() override; + bool StopSystem() override; + bool OnSystemThreadStart() override; + bool OnSystemTick() override; + bool OnSystemThreadEnd() override; + bool OnApplicationEnd() override; + // Various callbacks from the wayland protocol static void registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version); static void registry_handle_global_remove_callback(void* data, wl_registry* registry, uint32_t name); @@ -4764,7 +4773,7 @@ namespace olc::host std::unordered_map mapUID2Window; std::unordered_map mapUID2OlcWindow; - std::atomic terminate {false}; + std::atomic systemActive {true}; std::unordered_map mapKeys; olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; @@ -8878,10 +8887,46 @@ namespace olc::host mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; } - bool Host_Linux_X11::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Linux_X11::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Linux_X11::StartSystem() { using namespace X11; + pPrimaryPGE->OnPreContextStart(); + + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); + auto get_pge_window = [&](auto x11_window) -> olc::Window* { auto itr = mapX11Window2PTR.find(x11_window); if(itr != mapX11Window2PTR.end()) { @@ -8890,128 +8935,149 @@ namespace olc::host return nullptr; }; - //std::unordered_map mapX11Window2PTR; - - if(bBlockIfPossible) { - X11::XEvent xev; - while(!terminate){ - while (XPending(olc_Display)) + X11::XEvent xev; + while(systemActive){ + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + + // If there's an update to the keyboard, update it's layout. + if (xev.type == xkbEventBase + XkbEventCode && kbExtensionsFound) { - XNextEvent(olc_Display, &xev); - - // If there's an update to the keyboard, update it's layout. - if (xev.type == xkbEventBase + XkbEventCode && kbExtensionsFound) - { - UpdateKeyboardLayout(); - } + UpdateKeyboardLayout(); + } - if (xev.type == Expose) - { - //auto* expose_event = reinterpret_cast(&xev); - X11::XExposeEvent& e = xev.xexpose; - if(auto* pge_window = get_pge_window(e.window); pge_window) { - X11::XWindowAttributes gwa; - X11::XGetWindowAttributes(e.display, e.window, &gwa); - pge_window->olc_OnWindowSize(olc::vi2d{gwa.width, gwa.height}); - } + if (xev.type == Expose) + { + //auto* expose_event = reinterpret_cast(&xev); + X11::XExposeEvent& e = xev.xexpose; + if(auto* pge_window = get_pge_window(e.window); pge_window) { + X11::XWindowAttributes gwa; + X11::XGetWindowAttributes(e.display, e.window, &gwa); + pge_window->olc_OnWindowSize(olc::vi2d{gwa.width, gwa.height}); } - else if (xev.type == ConfigureNotify) - { - X11::XConfigureEvent& xce = xev.xconfigure; - if(auto* pge_window = get_pge_window(xce.window); pge_window) { - pge_window->olc_OnWindowSize(olc::vi2d{xce.width, xce.height}); - } + } + else if (xev.type == ConfigureNotify) + { + X11::XConfigureEvent& xce = xev.xconfigure; + if(auto* pge_window = get_pge_window(xce.window); pge_window) { + pge_window->olc_OnWindowSize(olc::vi2d{xce.width, xce.height}); } - else if (xev.type == KeyPress) - { - KeySym ks; - - // Unset the "shift" bit so that Key and Shift-Key will be mapped to the same olc::key - // since the system kind of assumes this - xev.xkey.state &= ~(1); - - XLookupString(&xev.xkey, NULL, 0, &ks, NULL); - - if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { - auto it = mapKeys.find(static_cast(ks)); - if(it != mapKeys.end()) { - pge_window->olc_OnKeyPress(it->second, true); - } + } + else if (xev.type == KeyPress) + { + KeySym ks; + + // Unset the "shift" bit so that Key and Shift-Key will be mapped to the same olc::key + // since the system kind of assumes this + xev.xkey.state &= ~(1); + + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, true); } } - else if (xev.type == KeyRelease) - { - KeySym ks; + } + else if (xev.type == KeyRelease) + { + KeySym ks; - XLookupString(&xev.xkey, NULL, 0, &ks, NULL); + XLookupString(&xev.xkey, NULL, 0, &ks, NULL); - if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { - auto it = mapKeys.find(static_cast(ks)); - if(it != mapKeys.end()) { - pge_window->olc_OnKeyPress(it->second, false); - } + if(auto* pge_window = get_pge_window(xev.xkey.window); pge_window) { + auto it = mapKeys.find(static_cast(ks)); + if(it != mapKeys.end()) { + pge_window->olc_OnKeyPress(it->second, false); } } - else if (xev.type == ButtonPress) - { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - switch (xev.xbutton.button) - { - case 1: pge_window->olc_OnMouseButton(0, true); break; - case 2: pge_window->olc_OnMouseButton(2, true); break; - case 3: pge_window->olc_OnMouseButton(1, true); break; - case 4: pge_window->olc_OnMouseWheel(120); break; - case 5: pge_window->olc_OnMouseWheel(-120); break; - default: break; - } - + } + else if (xev.type == ButtonPress) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + switch (xev.xbutton.button) + { + case 1: pge_window->olc_OnMouseButton(0, true); break; + case 2: pge_window->olc_OnMouseButton(2, true); break; + case 3: pge_window->olc_OnMouseButton(1, true); break; + case 4: pge_window->olc_OnMouseWheel(120); break; + case 5: pge_window->olc_OnMouseWheel(-120); break; + default: break; } + } - else if (xev.type == ButtonRelease) - { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - switch (xev.xbutton.button) - { - case 1: pge_window->olc_OnMouseButton(0, false); break; - case 2: pge_window->olc_OnMouseButton(2, false); break; - case 3: pge_window->olc_OnMouseButton(1, false); break; - default: break; - } - } + } + else if (xev.type == ButtonRelease) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + switch (xev.xbutton.button) + { + case 1: pge_window->olc_OnMouseButton(0, false); break; + case 2: pge_window->olc_OnMouseButton(2, false); break; + case 3: pge_window->olc_OnMouseButton(1, false); break; + default: break; + } } - else if (xev.type == MotionNotify) - { - X11::XMotionEvent& xme = xev.xmotion; - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - pge_window->olc_OnMouseMove(olc::vi2d{xme.x, xme.y}); - - } + } + else if (xev.type == MotionNotify) + { + X11::XMotionEvent& xme = xev.xmotion; + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + pge_window->olc_OnMouseMove(olc::vi2d{xme.x, xme.y}); + } - // else if (xev.type == FocusIn) - // { - // ptrPGE->olc_UpdateKeyFocus(true); - // } - // else if (xev.type == FocusOut) - // { - // ptrPGE->olc_UpdateKeyFocus(false); - // } - else if (xev.type == ClientMessage) - { - X11::XClientMessageEvent& xcme = xev.xclient; - if(auto* pge_window = get_pge_window(xcme.window); pge_window) { - pge_window->olc_OnWindowClose(); - return false; - } + } + // else if (xev.type == FocusIn) + // { + // ptrPGE->olc_UpdateKeyFocus(true); + // } + // else if (xev.type == FocusOut) + // { + // ptrPGE->olc_UpdateKeyFocus(false); + // } + else if (xev.type == ClientMessage) + { + X11::XClientMessageEvent& xcme = xev.xclient; + if(auto* pge_window = get_pge_window(xcme.window); pge_window) { + pge_window->olc_OnWindowClose(); } } } } + + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + bool Host_Linux_X11::StopSystem() + { + systemActive = false; return true; } - void Host_Linux_X11::TerminateSystemEventLoop() + bool Host_Linux_X11::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Linux_X11::OnSystemTick() + { + return pPrimaryPGE->OnContextTick();; + } + + bool Host_Linux_X11::OnSystemThreadEnd() { - terminate = true; + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Linux_X11::OnApplicationEnd() + { + return true; } bool Host_Linux_X11::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) @@ -9076,12 +9142,6 @@ namespace olc::host } return {}; } - - - bool Host_Linux_X11::ConnectHostResourceToRenderer() - { - return true; - } olc::KeyboardLayout Host_Linux_X11::GetKeyboardLayout() const { return keyboardLayout; @@ -9301,20 +9361,77 @@ namespace olc::host wl_display_disconnect(display); } - bool Host_Linux_Wayland::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Linux_Wayland::OnApplicationStart(olc::PixelGameEngine* pPrimary) { - if(bBlockIfPossible) { - while(!terminate && wl_display_dispatch_pending(display) != -1) { + pPrimaryPGE = pPrimary; + return true; + } - } - } + bool Host_Linux_Wayland::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); + + while(systemActive && wl_display_dispatch_pending(display) != -1) { } + + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + bool Host_Linux_Wayland::StopSystem() + { + systemActive = false; return true; } - void Host_Linux_Wayland::TerminateSystemEventLoop() + bool Host_Linux_Wayland::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Linux_Wayland::OnSystemTick() + { + return pPrimaryPGE->OnContextTick();; + } + + bool Host_Linux_Wayland::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Linux_Wayland::OnApplicationEnd() { - terminate = true; + return true; } bool Host_Linux_Wayland::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) @@ -9479,7 +9596,6 @@ namespace olc::host if(itr != mapUID2OlcWindow.end()) { auto* ptr = itr->second; ptr->olc_OnWindowClose(); - terminate = true; } } } @@ -9838,11 +9954,6 @@ namespace olc::host return {}; } - bool Host_Linux_Wayland::ConnectHostResourceToRenderer() - { - return true; - } - bool Host_Linux_Wayland::SyncWithDesktopComposite() { return true; From 6fb24a09b4655d1cf070bbd36f450708a991464a Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 13:43:00 -0500 Subject: [PATCH 53/56] [emscripten] use new host/core relationships --- dev/src/host_web_emscripten.cpp | 88 +++++++++++++++++++++---- dev/src/host_web_emscripten.h | 23 +++++-- olcPixelGameEngine3.h | 111 +++++++++++++++++++++++++++----- 3 files changed, 190 insertions(+), 32 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index c1c21555..f875689d 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -7,6 +7,82 @@ namespace olc::host std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; std::unordered_map> Host_Web_Emscripten::mapUID2CallbackData; + // Called at very start of application + bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + std::cout << "Emscripten: OnApplicationStart.\n"; + pPrimaryPGE = pPrimary; + return true; + } + + void Host_Web_Emscripten::MainLoop(void* userData) + { + auto pHost = reinterpret_cast(userData); + + if(!pHost->OnSystemTick()) + { + if(!pHost->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + } + } + + // Called to start the host - this may mean different things on different hosts + bool Host_Web_Emscripten::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + if(!OnSystemThreadStart()) + { + // PGE->ContextStart() failed, or user aborted OnUserCreate() + return false; + } + + emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); + + // This code is never reached!! + + return true; + } + + // Called to stop the host, and shutdown all resources + bool Host_Web_Emscripten::StopSystem() + { + std::cout << "Emscripten: StopSystem.\n"; + return true; + } + + // Called at start of system event loop + bool Host_Web_Emscripten::OnSystemThreadStart() + { + std::cout << "Emscripten: OnSystemThreadStart.\n"; + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_Web_Emscripten::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_Web_Emscripten::OnSystemThreadEnd() + { + std::cout << "Emscripten: OnSystemThreadEnd.\n"; + emscripten_cancel_main_loop(); + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_Web_Emscripten::OnApplicationEnd() + { + std::cout << "Emscripten: OnApplicationEnd.\n"; + return true; + } + Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; @@ -165,12 +241,6 @@ namespace olc::host mapKeys[DOM_PK_PERIOD] = Key::PERIOD; } - bool Host_Web_Emscripten::StartSystemEventLoop(bool bBlockIfPossible) - { - std::cout << "Emscripten: StartSystemEventLoop called, but not used.\n"; - return true; - } - bool Host_Web_Emscripten::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { std::cout << "Emscripten: AddWindowFrame called.\n"; @@ -473,12 +543,6 @@ namespace olc::host return {}; } - bool Host_Web_Emscripten::ConnectHostResourceToRenderer() - { - std::cout << "Emscripten: ConnectHostResourceToRenderer not implemented.\n"; - return true; - } - // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Web_Emscripten::SyncWithDesktopComposite() { diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 06edc135..f16ea1a9 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -22,18 +22,33 @@ namespace olc::host { public: Host_Web_Emscripten(); - bool StartSystemEventLoop(bool bBlockIfPossible = false) override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - bool ConnectHostResourceToRenderer() override; - // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; olc::KeyboardLayout GetKeyboardLayout() const override; + + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + static void MainLoop(void* userData); public: // event callbacks static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); @@ -58,6 +73,7 @@ namespace olc::host static bool olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos); static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); static bool olc_OnWindowClose(olc::Window* pWindow); + private: // helpers static olc::Window* GetWindowFromCanvasId(std::string canvasId); @@ -72,7 +88,6 @@ namespace olc::host static std::unordered_map mapUID2CanvasId; static std::unordered_map> mapUID2CallbackData; static std::unordered_map mapCanvasId2PTR; - std::atomic terminate {false}; // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4d3ca269..0988d2b7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4793,18 +4793,33 @@ namespace olc::host { public: Host_Web_Emscripten(); - bool StartSystemEventLoop(bool bBlockIfPossible = false) override; bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - bool ConnectHostResourceToRenderer() override; - // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; olc::KeyboardLayout GetKeyboardLayout() const override; + + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + static void MainLoop(void* userData); public: // event callbacks static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); @@ -4829,6 +4844,7 @@ namespace olc::host static bool olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos); static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); static bool olc_OnWindowClose(olc::Window* pWindow); + private: // helpers static olc::Window* GetWindowFromCanvasId(std::string canvasId); @@ -4843,7 +4859,6 @@ namespace olc::host static std::unordered_map mapUID2CanvasId; static std::unordered_map> mapUID2CallbackData; static std::unordered_map mapCanvasId2PTR; - std::atomic terminate {false}; // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; @@ -9969,6 +9984,82 @@ namespace olc::host std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; std::unordered_map> Host_Web_Emscripten::mapUID2CallbackData; + // Called at very start of application + bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + std::cout << "Emscripten: OnApplicationStart.\n"; + pPrimaryPGE = pPrimary; + return true; + } + + void Host_Web_Emscripten::MainLoop(void* userData) + { + auto pHost = reinterpret_cast(userData); + + if(!pHost->OnSystemTick()) + { + if(!pHost->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + } + } + + // Called to start the host - this may mean different things on different hosts + bool Host_Web_Emscripten::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + if(!OnSystemThreadStart()) + { + // PGE->ContextStart() failed, or user aborted OnUserCreate() + return false; + } + + emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); + + // This code is never reached!! + + return true; + } + + // Called to stop the host, and shutdown all resources + bool Host_Web_Emscripten::StopSystem() + { + std::cout << "Emscripten: StopSystem.\n"; + return true; + } + + // Called at start of system event loop + bool Host_Web_Emscripten::OnSystemThreadStart() + { + std::cout << "Emscripten: OnSystemThreadStart.\n"; + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_Web_Emscripten::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_Web_Emscripten::OnSystemThreadEnd() + { + std::cout << "Emscripten: OnSystemThreadEnd.\n"; + emscripten_cancel_main_loop(); + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_Web_Emscripten::OnApplicationEnd() + { + std::cout << "Emscripten: OnApplicationEnd.\n"; + return true; + } + Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; @@ -10127,12 +10218,6 @@ namespace olc::host mapKeys[DOM_PK_PERIOD] = Key::PERIOD; } - bool Host_Web_Emscripten::StartSystemEventLoop(bool bBlockIfPossible) - { - std::cout << "Emscripten: StartSystemEventLoop called, but not used.\n"; - return true; - } - bool Host_Web_Emscripten::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { std::cout << "Emscripten: AddWindowFrame called.\n"; @@ -10435,12 +10520,6 @@ namespace olc::host return {}; } - bool Host_Web_Emscripten::ConnectHostResourceToRenderer() - { - std::cout << "Emscripten: ConnectHostResourceToRenderer not implemented.\n"; - return true; - } - // Wait for entire host desktop refresh (for smooooth vsync) bool Host_Web_Emscripten::SyncWithDesktopComposite() { From eb1ccde5e7f226eb5d27b9220fa686038151c7cb Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 13:46:45 -0500 Subject: [PATCH 54/56] [emscripten] add code comment explaining why there's no code after the main loop is set --- dev/src/host_web_emscripten.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index f875689d..b416b191 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -43,7 +43,7 @@ namespace olc::host emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); - // This code is never reached!! + // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); return true; } From 8324051d469bdf5b1fd25806b5ee989924e590c9 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 14:15:55 -0500 Subject: [PATCH 55/56] [emscripten] move all of the ending stuff into StopSystem and use that, as it should be used. --- dev/src/host_web_emscripten.cpp | 12 +++++------- olcPixelGameEngine3.h | 14 ++++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index b416b191..f05eff90 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -21,11 +21,7 @@ namespace olc::host if(!pHost->OnSystemTick()) { - if(!pHost->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } + pHost->StopSystem(); } } @@ -44,7 +40,7 @@ namespace olc::host emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); - + return true; } @@ -52,6 +48,9 @@ namespace olc::host bool Host_Web_Emscripten::StopSystem() { std::cout << "Emscripten: StopSystem.\n"; + OnSystemThreadEnd(); + pPrimaryPGE->OnPostContextEnd(); + emscripten_cancel_main_loop(); return true; } @@ -72,7 +71,6 @@ namespace olc::host bool Host_Web_Emscripten::OnSystemThreadEnd() { std::cout << "Emscripten: OnSystemThreadEnd.\n"; - emscripten_cancel_main_loop(); return pPrimaryPGE->OnContextEnd(); } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 0988d2b7..edbf55d1 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -9998,11 +9998,7 @@ namespace olc::host if(!pHost->OnSystemTick()) { - if(!pHost->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } + pHost->StopSystem(); } } @@ -10020,8 +10016,8 @@ namespace olc::host emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); - // This code is never reached!! - + // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); + return true; } @@ -10029,6 +10025,9 @@ namespace olc::host bool Host_Web_Emscripten::StopSystem() { std::cout << "Emscripten: StopSystem.\n"; + OnSystemThreadEnd(); + pPrimaryPGE->OnPostContextEnd(); + emscripten_cancel_main_loop(); return true; } @@ -10049,7 +10048,6 @@ namespace olc::host bool Host_Web_Emscripten::OnSystemThreadEnd() { std::cout << "Emscripten: OnSystemThreadEnd.\n"; - emscripten_cancel_main_loop(); return pPrimaryPGE->OnContextEnd(); } From 15a0741106b3874a2be6aa069962e89be01b3755 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:34:26 +0000 Subject: [PATCH 56/56] Final sync for a bit (hopefully) --- olcPixelGameEngine3.h | 52 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index edbf55d1..4e5930ee 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4522,11 +4522,11 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - /* - Note for Mac Users: Apple-branded extended keyboards often do not have a physical "NumLock" key; they act as "NumLock On" by default. - This behavior (where the key triggers the function flag) is more common when using third-party mechanical keyboards or specialized numpads on macOS. - */ - void ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + + // When modifier flag changes the keycode it will return true, else false + bool ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + + bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; } @@ -6320,7 +6320,7 @@ namespace olc::host { mapKeys[111] = Key::F12; // Arrow Keys - mapKeys[125] = Key::DOWN; + mapKeys[125] = Key::DOWN; mapKeys[123] = Key::LEFT; mapKeys[124] = Key::RIGHT; mapKeys[126] = Key::UP; @@ -6697,18 +6697,48 @@ namespace olc::host { } - void Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + bool Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + bool bisHandled = false; if (event.modifierFlags & NSEventModifierFlagCapsLock) { pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); } if (event.modifierFlags & NSEventModifierFlagShift) { pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); + if(event.keyCode == 39) + { + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + pPGEwindow->olc_OnKeyPress(mapKeys[50], pressed); + return true; + } + } if (event.modifierFlags & NSEventModifierFlagControl) { pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); } + if(event.modifierFlags & NSEventModifierFlagNumericPad) { + if(event.keyCode == 71 && pressed) // NumLock keycode + { + // We only tottle the NumLock state on key press to minic the latching of the key + bNumLockActive = !bNumLockActive; + } + + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + if(event.keyCode == 84) pPGEwindow->olc_OnKeyPress(mapKeys[125], pressed); + if(event.keyCode == 86) pPGEwindow->olc_OnKeyPress(mapKeys[123], pressed); + if(event.keyCode == 88) pPGEwindow->olc_OnKeyPress(mapKeys[124], pressed); + if(event.keyCode == 91) pPGEwindow->olc_OnKeyPress(mapKeys[126], pressed); + return true; + } + + + } + + return bisHandled; + } @@ -6719,13 +6749,13 @@ namespace olc::host { // Set up keyboard event handlers pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - ModifiersFlagsHandler(event, true); - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); + if(!ModifiersFlagsHandler(event, true)) + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); }); pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - ModifiersFlagsHandler(event, false); - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + if(!ModifiersFlagsHandler(event, false)) + pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); });