From 182d4bc7e35a5777234c57b81857dbd127a57a0f Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 27 Feb 2026 03:22:55 -0500 Subject: [PATCH 01/41] [imageloader] libpng implement CreateImageFromMemory --- dev/src/core.cpp | 19 ++++- dev/src/imload_lib_png.cpp | 168 ++++++++++++++++++++++--------------- dev/src/imload_lib_png.h | 9 ++ 3 files changed, 126 insertions(+), 70 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index e3af3d40..0a33fe51 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -256,7 +256,24 @@ namespace olc bool PGEWindow::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg) { - olc_IgnoreUnused(image, data, bytes, cfg); + if (pImageLoader->CreateImageFromMemory(image, data, bytes)) + { + // Image has loaded ok, and populated into pixel vector + // + // Create GPU Image + auto id = pRenderer->CreateTexture(image.Size(), cfg); + if (id == 0) + { + image.Create({ 0,0 }); + return false; + } + + // Associate CPU object with GPU Resource + image.SetGPUID(id); + return true; + } + + std::cout << "Create From Memory Failed\n"; return false; } diff --git a/dev/src/imload_lib_png.cpp b/dev/src/imload_lib_png.cpp index 3dcd0113..3cf656c4 100644 --- a/dev/src/imload_lib_png.cpp +++ b/dev/src/imload_lib_png.cpp @@ -1,100 +1,72 @@ #include "imload_lib_png.h" //! START IMPLEMENTATION -#include - namespace olc::imload { // Create an image resource based on an image file asset on disk bool ImageLoader_LibPNG::CreateImageFromFile(olc::Image& image, const std::string& sFileName) { - //////////////////////////////////////////////////////////////////////////// - // Use libpng, Thanks to Guillaume Cottenceau - // https://gist.github.com/niw/5963798 - // Also reading png from streams - // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ - png_structp png; - png_infop info; - - auto loadPNG = [&]() - { - png_read_info(png, info); - png_byte color_type; - png_byte bit_depth; - png_bytep* row_pointers; - image.Create( - { - static_cast(png_get_image_width(png, info)), - static_cast(png_get_image_height(png, info)) - } - ); - - color_type = png_get_color_type(png, info); - bit_depth = png_get_bit_depth(png, info); - if (bit_depth == 16) png_set_strip_16(png); - if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); - if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); - if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) - png_set_filler(png, 0xFF, PNG_FILLER_AFTER); - if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png); - png_read_update_info(png, info); - row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * image.Size().y); - for (int y = 0; y < image.Size().y; y++) { - row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); - } - png_read_image(png, row_pointers); - - // Iterate through image rows, converting into sprite format - for (int y = 0; y < image.Size().y; y++) - { - png_bytep row = row_pointers[y]; - for (int x = 0; x < image.Size().x; x++) - { - png_bytep px = &(row[x * 4]); - image.Pixel(olc::vi2d(x, y)) = olc::Pixel(px[0], px[1], px[2], px[3]); - } - } - - for (int y = 0; y < image.Size().y; y++) // Thanks maksym33 - free(row_pointers[y]); - free(row_pointers); - png_destroy_read_struct(&png, &info, nullptr); - }; + FILE* pngFileHandle = fopen(sFileName.c_str(), "rb"); + if(!pngFileHandle) + return false; - png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) return false; - info = png_create_info_struct(png); + png_infop info = png_create_info_struct(png); if (!info) return false; - if (setjmp(png_jmpbuf(png))) - return false; - + if(setjmp(png_jmpbuf(png))) { - FILE* f = fopen(sFileName.c_str(), "rb"); - if (!f) return false; - png_init_io(png, f); - loadPNG(); - fclose(f); + png_destroy_read_struct(&png, &info, nullptr); + fclose(pngFileHandle); + return false; } + + png_init_io(png, pngFileHandle); + bool decodeResult = DecodePNG(image, png, info); + + png_destroy_read_struct(&png, &info, nullptr); + fclose(pngFileHandle); - return true; + return decodeResult; } // Create an image resource based on an image file asset in memory bool ImageLoader_LibPNG::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - return false; + MemReader reader{ data, 0 }; + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) + return false; + std::cout << "create read struct success\n"; + + png_infop info = png_create_info_struct(png); + if (!info) + return false; + std::cout << "create info struct success\n"; + + if(setjmp(png_jmpbuf(png))) + { + std::cout << "setjmp/png_jmpbuf failed\n"; + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + std::cout << "setjmp/png_jmpbuf success\n"; + + png_set_read_fn(png, &reader, &ImageLoader_LibPNG::PNGReadFromMemory); + bool decodeResult = DecodePNG(image, png, info); + + png_destroy_read_struct(&png, &info, nullptr); + return decodeResult; } // Create an image resource based on an image file asset in memory bool ImageLoader_LibPNG::CreateImageFromMemory(olc::Image& image, const std::vector& data) { - return false; + return CreateImageFromMemory(image, data.data(), data.size()); } // Store an image as a file asset on disk @@ -108,6 +80,64 @@ namespace olc::imload { return false; } + + void ImageLoader_LibPNG::PNGReadFromMemory(png_structp png, png_bytep out, png_size_t count) + { + auto* reader = (MemReader*)png_get_io_ptr(png); + std::memcpy(out, reader->data + reader->offset, count); + reader->offset += count; + } + + bool ImageLoader_LibPNG::DecodePNG(olc::Image& image, png_structp png, png_infop info) + { + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + image.Create( + { + static_cast(png_get_image_width(png, info)), + static_cast(png_get_image_height(png, info)) + } + ); + + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + + std::vector rows(image.Size().y); + std::vector> rowData(image.Size().y); + for (int y = 0; y < image.Size().y; y++) { + rowData[y].resize(png_get_rowbytes(png, info)); + rows[y] = rowData[y].data(); + } + png_read_image(png, rows.data()); + // Iterate through image rows, converting into sprite format + for (int y = 0; y < image.Size().y; y++) + { + png_bytep row = rows[y]; + for (int x = 0; x < image.Size().x; x++) + { + png_bytep px = &(row[x * 4]); + image.Pixel(olc::vi2d(x, y)) = olc::Pixel(px[0], px[1], px[2], px[3]); + } + } + + return true; + } } //! END IMPLEMENTATION diff --git a/dev/src/imload_lib_png.h b/dev/src/imload_lib_png.h index afb5ceba..9589e78e 100644 --- a/dev/src/imload_lib_png.h +++ b/dev/src/imload_lib_png.h @@ -5,11 +5,14 @@ //! END CUSTOMHEADER //! START STDHEADER GLOBAL +#include #include //! END STDHEADER //! START DECLARATION #if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +#include + namespace olc::imload { class ImageLoader_LibPNG : public ImageLoader @@ -28,7 +31,13 @@ namespace olc::imload // Store an image as a file asset in memory bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + + public: // libpng readers + struct MemReader { const uint8_t* data; size_t offset; }; + static void PNGReadFromMemory(png_structp png, png_bytep out, png_size_t count); + private: // libpng internals + bool DecodePNG(olc::Image& image, png_structp png, png_infop info); }; } From bb59433950f60b3adcce04f6666daa1e65f441dd Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 27 Feb 2026 03:25:26 -0500 Subject: [PATCH 02/41] [imageloader] remove comments and update header --- dev/src/imload_lib_png.cpp | 4 - olcPixelGameEngine3.h | 192 +++++++++++++++++++++++-------------- 2 files changed, 121 insertions(+), 75 deletions(-) diff --git a/dev/src/imload_lib_png.cpp b/dev/src/imload_lib_png.cpp index 3cf656c4..5fadfefa 100644 --- a/dev/src/imload_lib_png.cpp +++ b/dev/src/imload_lib_png.cpp @@ -41,20 +41,16 @@ namespace olc::imload png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) return false; - std::cout << "create read struct success\n"; png_infop info = png_create_info_struct(png); if (!info) return false; - std::cout << "create info struct success\n"; if(setjmp(png_jmpbuf(png))) { - std::cout << "setjmp/png_jmpbuf failed\n"; png_destroy_read_struct(&png, &info, nullptr); return false; } - std::cout << "setjmp/png_jmpbuf success\n"; png_set_read_fn(png, &reader, &ImageLoader_LibPNG::PNGReadFromMemory); bool decodeResult = DecodePNG(image, png, info); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c089ff12..bd6583c7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6709,6 +6709,8 @@ namespace olc #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG #if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +#include + namespace olc::imload { class ImageLoader_LibPNG : public ImageLoader @@ -6727,7 +6729,13 @@ namespace olc::imload // Store an image as a file asset in memory bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + + public: // libpng readers + struct MemReader { const uint8_t* data; size_t offset; }; + static void PNGReadFromMemory(png_structp png, png_bytep out, png_size_t count); + private: // libpng internals + bool DecodePNG(olc::Image& image, png_structp png, png_infop info); }; } @@ -6814,7 +6822,6 @@ namespace olc::host // Make OS Update a window frame title, associated with olc::Window bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) { - std::cout << pWindow->GetWindowTitle() << "\n"; return true; } @@ -16937,7 +16944,24 @@ namespace olc bool PGEWindow::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg) { - olc_IgnoreUnused(image, data, bytes, cfg); + if (pImageLoader->CreateImageFromMemory(image, data, bytes)) + { + // Image has loaded ok, and populated into pixel vector + // + // Create GPU Image + auto id = pRenderer->CreateTexture(image.Size(), cfg); + if (id == 0) + { + image.Create({ 0,0 }); + return false; + } + + // Associate CPU object with GPU Resource + image.SetGPUID(id); + return true; + } + + std::cout << "Create From Memory Failed\n"; return false; } @@ -18145,100 +18169,68 @@ namespace olc::imload } #endif #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG -#include - namespace olc::imload { // Create an image resource based on an image file asset on disk bool ImageLoader_LibPNG::CreateImageFromFile(olc::Image& image, const std::string& sFileName) { - //////////////////////////////////////////////////////////////////////////// - // Use libpng, Thanks to Guillaume Cottenceau - // https://gist.github.com/niw/5963798 - // Also reading png from streams - // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ - png_structp png; - png_infop info; - - auto loadPNG = [&]() - { - png_read_info(png, info); - png_byte color_type; - png_byte bit_depth; - png_bytep* row_pointers; - image.Create( - { - static_cast(png_get_image_width(png, info)), - static_cast(png_get_image_height(png, info)) - } - ); - - color_type = png_get_color_type(png, info); - bit_depth = png_get_bit_depth(png, info); - if (bit_depth == 16) png_set_strip_16(png); - if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); - if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); - if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) - png_set_filler(png, 0xFF, PNG_FILLER_AFTER); - if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png); - png_read_update_info(png, info); - row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * image.Size().y); - for (int y = 0; y < image.Size().y; y++) { - row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); - } - png_read_image(png, row_pointers); - - // Iterate through image rows, converting into sprite format - for (int y = 0; y < image.Size().y; y++) - { - png_bytep row = row_pointers[y]; - for (int x = 0; x < image.Size().x; x++) - { - png_bytep px = &(row[x * 4]); - image.Pixel(olc::vi2d(x, y)) = olc::Pixel(px[0], px[1], px[2], px[3]); - } - } - - for (int y = 0; y < image.Size().y; y++) // Thanks maksym33 - free(row_pointers[y]); - free(row_pointers); - png_destroy_read_struct(&png, &info, nullptr); - }; + FILE* pngFileHandle = fopen(sFileName.c_str(), "rb"); + if(!pngFileHandle) + return false; - png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) return false; - info = png_create_info_struct(png); + png_infop info = png_create_info_struct(png); if (!info) return false; - if (setjmp(png_jmpbuf(png))) - return false; - + if(setjmp(png_jmpbuf(png))) { - FILE* f = fopen(sFileName.c_str(), "rb"); - if (!f) return false; - png_init_io(png, f); - loadPNG(); - fclose(f); + png_destroy_read_struct(&png, &info, nullptr); + fclose(pngFileHandle); + return false; } + + png_init_io(png, pngFileHandle); + bool decodeResult = DecodePNG(image, png, info); + + png_destroy_read_struct(&png, &info, nullptr); + fclose(pngFileHandle); - return true; + return decodeResult; } // Create an image resource based on an image file asset in memory bool ImageLoader_LibPNG::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - return false; + MemReader reader{ data, 0 }; + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) + return false; + + png_infop info = png_create_info_struct(png); + if (!info) + return false; + + if(setjmp(png_jmpbuf(png))) + { + png_destroy_read_struct(&png, &info, nullptr); + return false; + } + + png_set_read_fn(png, &reader, &ImageLoader_LibPNG::PNGReadFromMemory); + bool decodeResult = DecodePNG(image, png, info); + + png_destroy_read_struct(&png, &info, nullptr); + return decodeResult; } // Create an image resource based on an image file asset in memory bool ImageLoader_LibPNG::CreateImageFromMemory(olc::Image& image, const std::vector& data) { - return false; + return CreateImageFromMemory(image, data.data(), data.size()); } // Store an image as a file asset on disk @@ -18252,7 +18244,65 @@ namespace olc::imload { return false; } + + void ImageLoader_LibPNG::PNGReadFromMemory(png_structp png, png_bytep out, png_size_t count) + { + auto* reader = (MemReader*)png_get_io_ptr(png); + std::memcpy(out, reader->data + reader->offset, count); + reader->offset += count; + } + bool ImageLoader_LibPNG::DecodePNG(olc::Image& image, png_structp png, png_infop info) + { + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + image.Create( + { + static_cast(png_get_image_width(png, info)), + static_cast(png_get_image_height(png, info)) + } + ); + + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + + std::vector rows(image.Size().y); + std::vector> rowData(image.Size().y); + for (int y = 0; y < image.Size().y; y++) { + rowData[y].resize(png_get_rowbytes(png, info)); + rows[y] = rowData[y].data(); + } + png_read_image(png, rows.data()); + + // Iterate through image rows, converting into sprite format + for (int y = 0; y < image.Size().y; y++) + { + png_bytep row = rows[y]; + for (int x = 0; x < image.Size().x; x++) + { + png_bytep px = &(row[x * 4]); + image.Pixel(olc::vi2d(x, y)) = olc::Pixel(px[0], px[1], px[2], px[3]); + } + } + + return true; + } } #endif #if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER From 619a0b601e8fb94ec0b56347be288e7703b7f1eb Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 27 Feb 2026 03:40:54 -0500 Subject: [PATCH 03/41] [imageloader] wingdi implement CreateImageFromMemory --- dev/src/imload_wingdi.cpp | 40 +++++++++++++++++++++++++++------------ dev/src/imload_wingdi.h | 3 ++- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/dev/src/imload_wingdi.cpp b/dev/src/imload_wingdi.cpp index 3c5da723..b947721c 100644 --- a/dev/src/imload_wingdi.cpp +++ b/dev/src/imload_wingdi.cpp @@ -52,15 +52,7 @@ namespace olc::imload if (bmp->GetLastStatus() != Gdiplus::Ok) return false; // File wasn't valid - // Need to swizzle each pixel... - image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); - for (int y = 0; y < image.Size().y; y++) - for (int x = 0; x < image.Size().x; x++) - { - Gdiplus::Color c; - bmp->GetPixel(x, y, &c); - image.Pixel(olc::vi2d(x, y)) = olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha()); - } + DecodeBMP(image, bmp); // All done delete bmp; @@ -69,13 +61,22 @@ namespace olc::imload bool ImageLoader_WinGDI::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - olc_IgnoreUnused(image, data, bytes); - return false; + // Load file into windows "bitmap". 1992 calling... + Gdiplus::Bitmap* bmp = nullptr; + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)data, UINT(bytes))); + if (bmp->GetLastStatus() != Gdiplus::Ok) + return false; // File wasn't valid + + DecodeBMP(image, bmp); + + // All done + delete bmp; + return true; } bool ImageLoader_WinGDI::CreateImageFromMemory(olc::Image& image, const std::vector& data) { - olc_IgnoreUnused(image, data); + CreateImageFromMemory(image, data.data(), data.size()); return false; } @@ -90,5 +91,20 @@ namespace olc::imload olc_IgnoreUnused(image, data); return false; } + + bool ImageLoader_WinGDI::DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp) + { + // Need to swizzle each pixel... + image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); + for (int y = 0; y < image.Size().y; y++) + for (int x = 0; x < image.Size().x; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + image.Pixel(olc::vi2d(x, y)) = olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha()); + } + + return true; + } } //! END IMPLEMENTATION diff --git a/dev/src/imload_wingdi.h b/dev/src/imload_wingdi.h index 4f22450a..fcb2904e 100644 --- a/dev/src/imload_wingdi.h +++ b/dev/src/imload_wingdi.h @@ -71,7 +71,8 @@ namespace olc // Store an image as a file asset in memory bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - + private: + bool DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp); }; } } From 3a23c54cb7b469e75f91af51b09e1e4d9b82bcc1 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 27 Feb 2026 03:41:45 -0500 Subject: [PATCH 04/41] [sh] update single header --- olcPixelGameEngine3.h | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index bd6583c7..bf699f5d 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6665,7 +6665,8 @@ namespace olc // Store an image as a file asset in memory bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - + private: + bool DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp); }; } } @@ -18060,15 +18061,7 @@ namespace olc::imload if (bmp->GetLastStatus() != Gdiplus::Ok) return false; // File wasn't valid - // Need to swizzle each pixel... - image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); - for (int y = 0; y < image.Size().y; y++) - for (int x = 0; x < image.Size().x; x++) - { - Gdiplus::Color c; - bmp->GetPixel(x, y, &c); - image.Pixel(olc::vi2d(x, y)) = olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha()); - } + DecodeBMP(image, bmp); // All done delete bmp; @@ -18077,13 +18070,22 @@ namespace olc::imload bool ImageLoader_WinGDI::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - olc_IgnoreUnused(image, data, bytes); - return false; + // Load file into windows "bitmap". 1992 calling... + Gdiplus::Bitmap* bmp = nullptr; + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)data, UINT(bytes))); + if (bmp->GetLastStatus() != Gdiplus::Ok) + return false; // File wasn't valid + + DecodeBMP(image, bmp); + + // All done + delete bmp; + return true; } bool ImageLoader_WinGDI::CreateImageFromMemory(olc::Image& image, const std::vector& data) { - olc_IgnoreUnused(image, data); + CreateImageFromMemory(image, data.data(), data.size()); return false; } @@ -18098,6 +18100,21 @@ namespace olc::imload olc_IgnoreUnused(image, data); return false; } + + bool ImageLoader_WinGDI::DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp) + { + // Need to swizzle each pixel... + image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); + for (int y = 0; y < image.Size().y; y++) + for (int x = 0; x < image.Size().x; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + image.Pixel(olc::vi2d(x, y)) = olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha()); + } + + return true; + } } #endif #if OLC_IMAGELOADER == OLC_IMAGELOADER_MACOS From 9c74dadc8d32fb9dcff01414ff5fba7e520e1d60 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 27 Feb 2026 03:44:24 -0500 Subject: [PATCH 05/41] [examples] add temporary example with CreateImageFromMemory --- examples/olcPGE3_ImageQuads-FromMemory.cpp | 140 +++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 examples/olcPGE3_ImageQuads-FromMemory.cpp diff --git a/examples/olcPGE3_ImageQuads-FromMemory.cpp b/examples/olcPGE3_ImageQuads-FromMemory.cpp new file mode 100644 index 00000000..55f9e560 --- /dev/null +++ b/examples/olcPGE3_ImageQuads-FromMemory.cpp @@ -0,0 +1,140 @@ +/* + olc::PixelGameEngine3 Example - Image Quads + + Draws an image quads, demonstrating "warping" + + 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" +#include + +// Example application demonstrating quad drawing. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_ImageQuads : public olc::PixelGameEngine +{ +public: + Example_ImageQuads() + { + + } + +protected: + std::vector vecControlPoints; + int nSelectedPoint = -1; + + olc::Image imgTest; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + std::ifstream imgTestStream("./assets/sanity_texture.png", std::ios::binary | std::ios::ate); + std::vector buffer; + + if(!imgTestStream.is_open()) + throw std::runtime_error{"failed to load sanity_texture.png"}; + + buffer.resize(imgTestStream.tellg(), 0); + imgTestStream.seekg(0, std::ios::beg); + imgTestStream.read((char*)buffer.data(), buffer.size()); + imgTestStream.close(); + + CreateImageFromMemory(imgTest, buffer.data(), buffer.size()); + + buffer.clear(); + + // Load asset + // CreateImageFromFile(imgTest, "./assets/sanity_texture.png"); + + // Define control points for quad corners + vecControlPoints.push_back({ 50.0f, 50.0f }); // Top-Left + vecControlPoints.push_back({ 150.0f, 50.0f }); // Top-Right + vecControlPoints.push_back({ 150.0f, 150.0f }); // Bottom-Right + vecControlPoints.push_back({ 50.0f, 150.0f }); // Bottom-Left + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::BLACK); + + // Draw Background Gradient + draw.FilledRect({ 0, 0 }, draw.GetTargetSize(), + olc::Colour::WHITE, olc::Colour::YELLOW, + olc::Colour::CYAN, olc::Colour::MAGENTA); + + + // Handle Mouse Input + + // When the left button is pressed, check if we are near + // a control point. If so, select it. + if (mouse.GetButton(0).bPressed) + { + nSelectedPoint = -1; + for (int i = 0; i < vecControlPoints.size(); i++) + { + if ((vecControlPoints[i] - mouse.GetPosition()).mag() < 8.0f) + { + nSelectedPoint = i; + break; + } + } + } + + // If the left button is held, and we have a selected point, + // move the point to the mouse position + if (mouse.GetButton(0).bHeld) + { + if (nSelectedPoint != -1) + { + vecControlPoints[nSelectedPoint] = mouse.GetPosition().round(); + } + } + + // When the left button is released, clear the selected point + if (mouse.GetButton(0).bReleased) + { + nSelectedPoint = -1; + } + + // Draw Quad + draw.ImageQuad(imgTest, vecControlPoints); + + // Draw Control Points + for (const auto& p : vecControlPoints) + { + draw.Circle(p, 8, olc::Colour::RED); + } + + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_ImageQuads demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + //if (demo.Construct({1024, 960 }, { 1, 1 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file From 636c1dea4c84d40bbf90428a92302cd6cd81ee3b Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 28 Feb 2026 03:19:47 -0500 Subject: [PATCH 06/41] [imageloader][MacOS] use CoreGraphics/ImageIO for graphics loading, implement CreateImageFromMemory --- dev/src/api_macos.cpp | 196 ++++++++++++--------------- dev/src/api_macos.h | 2 + dev/src/api_macos_wrapper.hpp | 9 ++ dev/src/imload_macos.cpp | 39 +++++- olcPixelGameEngine3.h | 246 +++++++++++++++++++--------------- 5 files changed, 272 insertions(+), 220 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index fe6df724..979d56f8 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -11,8 +11,6 @@ static constexpr const char* kNSStringClass = "NSString"; static constexpr const char* kNSOpenGLPixelFormatClass = "NSOpenGLPixelFormat"; static constexpr const char* kNSOpenGLViewClass = "NSOpenGLView"; static constexpr const char* kNSObjectClass = "NSObject"; -static constexpr const char* kNSImageClass = "NSImage"; -static constexpr const char* kNSBitmapImageRepClass = "NSBitmapImageRep"; static constexpr const char* kAppDelegateClass = "AppDelegate"; static constexpr const char* kWindowDelegateClass = "WindowDelegate"; static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; @@ -127,18 +125,6 @@ static constexpr const char* kClickCountSel = "clickCount"; static constexpr const char* kModifierFlagsSel = "modifierFlags"; static constexpr const char* kUTF8StringSel = "UTF8String"; -// NSImage, NSBitmapImageRep, and image data access selectors -static constexpr const char* kInitWithContentsOfFileSel = "initWithContentsOfFile:"; -static constexpr const char* kRepresentationsSel = "representations"; -static constexpr const char* kCountSel = "count"; -static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; -static constexpr const char* kPixelsWideSel = "pixelsWide"; -static constexpr const char* kPixelsHighSel = "pixelsHigh"; -static constexpr const char* kBitsPerPixelSel = "bitsPerPixel"; -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"; @@ -284,18 +270,6 @@ namespace ObjectiveCSEL { static SEL modifierFlagsSel = nullptr; static SEL 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; - // NSLocale selectors static SEL currentLocaleSel = nullptr; static SEL localeIdentifierSel = nullptr; @@ -410,18 +384,6 @@ namespace ObjectiveCSEL { modifierFlagsSel = sel_registerName(kModifierFlagsSel); utf8StringSel = sel_registerName(kUTF8StringSel); - // NSImage, NSBitmapImageRep, and image data access selectors - initWithContentsOfFileSel = sel_registerName(kInitWithContentsOfFileSel); - representationsSel = sel_registerName(kRepresentationsSel); - countSel = sel_registerName(kCountSel); - objectAtIndexSel = sel_registerName(kObjectAtIndexSel); - pixelsWideSel = sel_registerName(kPixelsWideSel); - pixelsHighSel = sel_registerName(kPixelsHighSel); - bitsPerPixelSel = sel_registerName(kBitsPerPixelSel); - bytesPerRowSel = sel_registerName(kBytesPerRowSel); - hasAlphaSel = sel_registerName(kHasAlphaSel); - bitmapDataSel = sel_registerName(kBitmapDataSel); - // NSLocale selectors currentLocaleSel = sel_registerName(kCurrentLocaleSel); localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); @@ -789,6 +751,7 @@ struct ImageLoader { // Method function pointers with nullptr initialization BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; + BOOL (*loadFromMemory) (struct ImageLoader* self, const uint8_t* data, size_t bytes); void (*destroy) (struct ImageLoader* self){nullptr}; unsigned char* (*getPixelData) (const struct ImageLoader* self){nullptr}; void (*getImageInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel){nullptr}; @@ -1903,100 +1866,116 @@ extern "C" { return renderer; } - // Load image from file path using NSImage and NSBitmapImageRep - BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + { + if(!image) return NO; + // Clear any existing data if (self->pixelData) { free(self->pixelData); self->pixelData = NULL; } - self->width = kZeroWidth; - self->height = kZeroHeight; - self->bytesPerPixel = kZeroBytes; - self->bytesPerRow = kZeroRows; - self->hasAlpha = NO; - - // Get required classes and selectors - Class NSStringClass = objc_getClass(kNSStringClass); - Class NSImageClass = objc_getClass(kNSImageClass); - Class NSBitmapImageRepClass = objc_getClass(kNSBitmapImageRepClass); - - SEL stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); - SEL allocSel = sel_registerName(kAllocSel); - SEL initWithContentsOfFileSel = sel_registerName(kInitWithContentsOfFileSel); - SEL representationsSel = sel_registerName(kRepresentationsSel); - SEL countSel = sel_registerName(kCountSel); - SEL objectAtIndexSel = sel_registerName(kObjectAtIndexSel); - - // Create NSString from file path - id pathString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, stringWithUTF8StringSel, filePath); - - if (!pathString) { + self->width = CGImageGetWidth(image); + self->height = CGImageGetHeight(image); + self->bytesPerPixel = 4; + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; + + self->pixelData = (unsigned char*)malloc(self->bytesPerRow * self->height); + if(!self->pixelData) + { return NO; } - - // Create NSImage from file - id image = ((id(*)(id, SEL, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSImageClass, allocSel), - initWithContentsOfFileSel, pathString); - - if (!image) { + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + // Draw into a raw RGBA buffer that maps directly onto our Pixel layout + CGContextRef ctx = CGBitmapContextCreate( + self->pixelData, + self->width, self->height, + 8, // bits per component + self->bytesPerRow, // bytes per row + colorSpace, + kCGImageAlphaPremultipliedLast // RGBA byte order + ); + + CGColorSpaceRelease(colorSpace); + + if (!ctx) + { + free(self->pixelData); + self->pixelData = NULL; return NO; } + + CGContextDrawImage(ctx, CGRectMake(0, 0, self->width, self->height), image); + CGContextRelease(ctx); - // Get image representations - id representations = ((id(*)(id, SEL))objc_msgSend)(image, representationsSel); - NSUInteger repCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(representations, countSel); - - if (repCount == 0) { + CGImageRelease(image); + return YES; + } + + BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + + CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); + CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); + CFRelease(pathStr); + + if(!url) + { + printf("loadImageFromFile: bad path '%s'\n", filePath); return NO; } - - // Get first bitmap representation - id bitmapRep = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(representations, objectAtIndexSel, 0); - - // Check if it's a bitmap representation - if (!((BOOL(*)(id, SEL, Class))objc_msgSend)(bitmapRep, sel_registerName(kIsKindOfClassSel), NSBitmapImageRepClass)) { + + CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); + CFRelease(url); + + if(!src) + { + printf("loadImageFromFile: couldn't open '%s'\n", filePath); return NO; } - // Extract image properties - SEL pixelsWideSel = sel_registerName(kPixelsWideSel); - SEL pixelsHighSel = sel_registerName(kPixelsHighSel); - SEL bitsPerPixelSel = sel_registerName(kBitsPerPixelSel); - SEL bytesPerRowSel = sel_registerName(kBytesPerRowSel); - SEL hasAlphaSel = sel_registerName(kHasAlphaSel); - SEL bitmapDataSel = sel_registerName(kBitmapDataSel); - - self->width = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, pixelsWideSel); - self->height = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, pixelsHighSel); - int bitsPerPixel = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, bitsPerPixelSel); - self->bytesPerRow = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, bytesPerRowSel); - self->hasAlpha = (BOOL)((BOOL(*)(id, SEL))objc_msgSend)(bitmapRep, hasAlphaSel); + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); - self->bytesPerPixel = bitsPerPixel / kBitsPerByte; + return imageloader_decodeImage(self, image); + } - // Get raw bitmap data - unsigned char* sourceData = ((unsigned char*(*)(id, SEL))objc_msgSend)(bitmapRep, bitmapDataSel); + BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { - if (!sourceData || self->width <= kMinValidDimension || self->height <= kMinValidDimension) { + CFDataRef cfData = CFDataCreateWithBytesNoCopy( + nullptr, + reinterpret_cast(data), + (CFIndex)bytes, + kCFAllocatorNull // we own the buffer, CF must not free it + ); + + if (!cfData) + { + printf("loadImageFromMemory: CFData creation failed\n"); return NO; } - - // Allocate memory for pixel data - size_t totalBytes = self->height * self->bytesPerRow; - self->pixelData = (unsigned char*)malloc(totalBytes); - - if (!self->pixelData) { + + CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); + CFRelease(cfData); + + if (!src) + { + printf("loadImageFromMemory: src creation failed\n"); return NO; } + + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); - // Copy pixel data - memcpy(self->pixelData, sourceData, totalBytes); - - return YES; + return imageloader_decodeImage(self, image); } // Get raw pixel data pointer @@ -2074,6 +2053,7 @@ extern "C" { // Assign method pointers loader->loadFromFile = imageloader_loadFromFile; + loader->loadFromMemory = imageloader_loadFromMemory; loader->destroy = imageloader_destroy; loader->getPixelData = imageloader_getPixelData; loader->getImageInfo = imageloader_getImageInfo; diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index 50c47024..b8f77e7a 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -24,6 +24,7 @@ #include #include #include +#include extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) @@ -88,6 +89,7 @@ extern "C" { // Image Loader API - as implemented in api_macos.c struct ImageLoader* imageloader_init (void); BOOL imageloader_loadFromFile (struct ImageLoader* self, const char* filePath); + BOOL imageloader_loadFromMemory (struct ImageLoader* self, const uint8_t* data, size_t bytes); void imageloader_destroy (struct ImageLoader* self); unsigned char* imageloader_getPixelData (const struct ImageLoader* self); void imageloader_getImageInfo (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index 52be9a2b..e6ec4647 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -639,6 +639,15 @@ namespace olc { } return false; } + + bool loadFromMemory(const uint8_t* data, size_t bytes) { + if (loader_) { + BOOL result = imageloader_loadFromMemory(loader_, data, bytes); + loaded_ = (result != 0); + return loaded_; + } + return false; + } bool isLoaded() const noexcept { return loaded_ && loader_ && imageloader_isLoaded(loader_); diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index c144beb1..1618637e 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -50,12 +50,47 @@ namespace olc::imload bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - return false; + if(!data) return false; + + // Create macOS API wrapper image loader + olc::apis::macos::ImageLoader loader; + + if (!loader.loadFromMemory(data, bytes) || !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); + + // 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 std::vector& data) { - return false; + return CreateImageFromMemory(image, data.data(), data.size()); } bool ImageLoader_MacOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index bf699f5d..8a6e5dd8 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4244,6 +4244,7 @@ namespace olc #include #include #include +#include extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) @@ -4308,6 +4309,7 @@ extern "C" { // Image Loader API - as implemented in api_macos.c struct ImageLoader* imageloader_init (void); BOOL imageloader_loadFromFile (struct ImageLoader* self, const char* filePath); + BOOL imageloader_loadFromMemory (struct ImageLoader* self, const uint8_t* data, size_t bytes); void imageloader_destroy (struct ImageLoader* self); unsigned char* imageloader_getPixelData (const struct ImageLoader* self); void imageloader_getImageInfo (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel); @@ -4995,6 +4997,15 @@ namespace olc { } return false; } + + bool loadFromMemory(const uint8_t* data, size_t bytes) { + if (loader_) { + BOOL result = imageloader_loadFromMemory(loader_, data, bytes); + loaded_ = (result != 0); + return loaded_; + } + return false; + } bool isLoaded() const noexcept { return loaded_ && loader_ && imageloader_isLoaded(loader_); @@ -8238,8 +8249,6 @@ static constexpr const char* kNSStringClass = "NSString"; static constexpr const char* kNSOpenGLPixelFormatClass = "NSOpenGLPixelFormat"; static constexpr const char* kNSOpenGLViewClass = "NSOpenGLView"; static constexpr const char* kNSObjectClass = "NSObject"; -static constexpr const char* kNSImageClass = "NSImage"; -static constexpr const char* kNSBitmapImageRepClass = "NSBitmapImageRep"; static constexpr const char* kAppDelegateClass = "AppDelegate"; static constexpr const char* kWindowDelegateClass = "WindowDelegate"; static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; @@ -8354,18 +8363,6 @@ static constexpr const char* kClickCountSel = "clickCount"; static constexpr const char* kModifierFlagsSel = "modifierFlags"; static constexpr const char* kUTF8StringSel = "UTF8String"; -// NSImage, NSBitmapImageRep, and image data access selectors -static constexpr const char* kInitWithContentsOfFileSel = "initWithContentsOfFile:"; -static constexpr const char* kRepresentationsSel = "representations"; -static constexpr const char* kCountSel = "count"; -static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; -static constexpr const char* kPixelsWideSel = "pixelsWide"; -static constexpr const char* kPixelsHighSel = "pixelsHigh"; -static constexpr const char* kBitsPerPixelSel = "bitsPerPixel"; -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"; @@ -8511,18 +8508,6 @@ namespace ObjectiveCSEL { static SEL modifierFlagsSel = nullptr; static SEL 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; - // NSLocale selectors static SEL currentLocaleSel = nullptr; static SEL localeIdentifierSel = nullptr; @@ -8637,18 +8622,6 @@ namespace ObjectiveCSEL { modifierFlagsSel = sel_registerName(kModifierFlagsSel); utf8StringSel = sel_registerName(kUTF8StringSel); - // NSImage, NSBitmapImageRep, and image data access selectors - initWithContentsOfFileSel = sel_registerName(kInitWithContentsOfFileSel); - representationsSel = sel_registerName(kRepresentationsSel); - countSel = sel_registerName(kCountSel); - objectAtIndexSel = sel_registerName(kObjectAtIndexSel); - pixelsWideSel = sel_registerName(kPixelsWideSel); - pixelsHighSel = sel_registerName(kPixelsHighSel); - bitsPerPixelSel = sel_registerName(kBitsPerPixelSel); - bytesPerRowSel = sel_registerName(kBytesPerRowSel); - hasAlphaSel = sel_registerName(kHasAlphaSel); - bitmapDataSel = sel_registerName(kBitmapDataSel); - // NSLocale selectors currentLocaleSel = sel_registerName(kCurrentLocaleSel); localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); @@ -9016,6 +8989,7 @@ struct ImageLoader { // Method function pointers with nullptr initialization BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; + BOOL (*loadFromMemory) (struct ImageLoader* self, const uint8_t* data, size_t bytes); void (*destroy) (struct ImageLoader* self){nullptr}; unsigned char* (*getPixelData) (const struct ImageLoader* self){nullptr}; void (*getImageInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel){nullptr}; @@ -10130,100 +10104,116 @@ extern "C" { return renderer; } - // Load image from file path using NSImage and NSBitmapImageRep - BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + { + if(!image) return NO; + // Clear any existing data if (self->pixelData) { free(self->pixelData); self->pixelData = NULL; } - self->width = kZeroWidth; - self->height = kZeroHeight; - self->bytesPerPixel = kZeroBytes; - self->bytesPerRow = kZeroRows; - self->hasAlpha = NO; - - // Get required classes and selectors - Class NSStringClass = objc_getClass(kNSStringClass); - Class NSImageClass = objc_getClass(kNSImageClass); - Class NSBitmapImageRepClass = objc_getClass(kNSBitmapImageRepClass); - - SEL stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); - SEL allocSel = sel_registerName(kAllocSel); - SEL initWithContentsOfFileSel = sel_registerName(kInitWithContentsOfFileSel); - SEL representationsSel = sel_registerName(kRepresentationsSel); - SEL countSel = sel_registerName(kCountSel); - SEL objectAtIndexSel = sel_registerName(kObjectAtIndexSel); - - // Create NSString from file path - id pathString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, stringWithUTF8StringSel, filePath); - - if (!pathString) { + self->width = CGImageGetWidth(image); + self->height = CGImageGetHeight(image); + self->bytesPerPixel = 4; + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; + + self->pixelData = (unsigned char*)malloc(self->bytesPerRow * self->height); + if(!self->pixelData) + { return NO; } - - // Create NSImage from file - id image = ((id(*)(id, SEL, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSImageClass, allocSel), - initWithContentsOfFileSel, pathString); - - if (!image) { + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + // Draw into a raw RGBA buffer that maps directly onto our Pixel layout + CGContextRef ctx = CGBitmapContextCreate( + self->pixelData, + self->width, self->height, + 8, // bits per component + self->bytesPerRow, // bytes per row + colorSpace, + kCGImageAlphaPremultipliedLast // RGBA byte order + ); + + CGColorSpaceRelease(colorSpace); + + if (!ctx) + { + free(self->pixelData); + self->pixelData = NULL; return NO; } + + CGContextDrawImage(ctx, CGRectMake(0, 0, self->width, self->height), image); + CGContextRelease(ctx); - // Get image representations - id representations = ((id(*)(id, SEL))objc_msgSend)(image, representationsSel); - NSUInteger repCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(representations, countSel); - - if (repCount == 0) { + CGImageRelease(image); + return YES; + } + + BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + + CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); + CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); + CFRelease(pathStr); + + if(!url) + { + printf("loadImageFromFile: bad path '%s'\n", filePath); return NO; } - - // Get first bitmap representation - id bitmapRep = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(representations, objectAtIndexSel, 0); - - // Check if it's a bitmap representation - if (!((BOOL(*)(id, SEL, Class))objc_msgSend)(bitmapRep, sel_registerName(kIsKindOfClassSel), NSBitmapImageRepClass)) { + + CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); + CFRelease(url); + + if(!src) + { + printf("loadImageFromFile: couldn't open '%s'\n", filePath); return NO; } - // Extract image properties - SEL pixelsWideSel = sel_registerName(kPixelsWideSel); - SEL pixelsHighSel = sel_registerName(kPixelsHighSel); - SEL bitsPerPixelSel = sel_registerName(kBitsPerPixelSel); - SEL bytesPerRowSel = sel_registerName(kBytesPerRowSel); - SEL hasAlphaSel = sel_registerName(kHasAlphaSel); - SEL bitmapDataSel = sel_registerName(kBitmapDataSel); - - self->width = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, pixelsWideSel); - self->height = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, pixelsHighSel); - int bitsPerPixel = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, bitsPerPixelSel); - self->bytesPerRow = (int)((NSInteger(*)(id, SEL))objc_msgSend)(bitmapRep, bytesPerRowSel); - self->hasAlpha = (BOOL)((BOOL(*)(id, SEL))objc_msgSend)(bitmapRep, hasAlphaSel); + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); - self->bytesPerPixel = bitsPerPixel / kBitsPerByte; + return imageloader_decodeImage(self, image); + } - // Get raw bitmap data - unsigned char* sourceData = ((unsigned char*(*)(id, SEL))objc_msgSend)(bitmapRep, bitmapDataSel); + BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { - if (!sourceData || self->width <= kMinValidDimension || self->height <= kMinValidDimension) { + CFDataRef cfData = CFDataCreateWithBytesNoCopy( + nullptr, + reinterpret_cast(data), + (CFIndex)bytes, + kCFAllocatorNull // we own the buffer, CF must not free it + ); + + if (!cfData) + { + printf("loadImageFromMemory: CFData creation failed\n"); return NO; } - - // Allocate memory for pixel data - size_t totalBytes = self->height * self->bytesPerRow; - self->pixelData = (unsigned char*)malloc(totalBytes); - - if (!self->pixelData) { + + CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); + CFRelease(cfData); + + if (!src) + { + printf("loadImageFromMemory: src creation failed\n"); return NO; } + + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); - // Copy pixel data - memcpy(self->pixelData, sourceData, totalBytes); - - return YES; + return imageloader_decodeImage(self, image); } // Get raw pixel data pointer @@ -10301,6 +10291,7 @@ extern "C" { // Assign method pointers loader->loadFromFile = imageloader_loadFromFile; + loader->loadFromMemory = imageloader_loadFromMemory; loader->destroy = imageloader_destroy; loader->getPixelData = imageloader_getPixelData; loader->getImageInfo = imageloader_getImageInfo; @@ -18166,12 +18157,47 @@ namespace olc::imload bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - return false; + if(!data) return false; + + // Create macOS API wrapper image loader + olc::apis::macos::ImageLoader loader; + + if (!loader.loadFromMemory(data, bytes) || !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); + + // 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 std::vector& data) { - return false; + return CreateImageFromMemory(image, data.data(), data.size()); } bool ImageLoader_MacOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) From e5fd06655db531a455634ef6d78de3fa46e2f36a Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 28 Feb 2026 03:52:41 -0500 Subject: [PATCH 07/41] [imageloader][MacOS] CoreGraphics/ImageIO premultiplies, we want raw values. --- dev/src/api_macos.cpp | 1 + dev/src/imload_macos.cpp | 34 ++++++++++++++++++++++++++++++++++ olcPixelGameEngine3.h | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 979d56f8..7f9f6b99 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -1913,6 +1913,7 @@ extern "C" { CGContextRelease(ctx); CGImageRelease(image); + return YES; } diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index 1618637e..f8faf005 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -44,6 +44,23 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. + for(auto &p : image.GetPixels()) + { + if(p.a > 0) + { + p.r = (uint8_t)((p.r * 255) / p.a); + p.g = (uint8_t)((p.g * 255) / p.a); + p.b = (uint8_t)((p.b * 255) / p.a); + } + else + { + p.r = 0; + p.g = 0; + p.b = 0; + } + } + return true; } @@ -85,6 +102,23 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. + for(auto &p : image.GetPixels()) + { + if(p.a > 0) + { + p.r = (uint8_t)((p.r * 255) / p.a); + p.g = (uint8_t)((p.g * 255) / p.a); + p.b = (uint8_t)((p.b * 255) / p.a); + } + else + { + p.r = 0; + p.g = 0; + p.b = 0; + } + } + return true; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 8a6e5dd8..797581b0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10151,6 +10151,7 @@ extern "C" { CGContextRelease(ctx); CGImageRelease(image); + return YES; } @@ -18151,6 +18152,23 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. + for(auto &p : image.GetPixels()) + { + if(p.a > 0) + { + p.r = (uint8_t)((p.r * 255) / p.a); + p.g = (uint8_t)((p.g * 255) / p.a); + p.b = (uint8_t)((p.b * 255) / p.a); + } + else + { + p.r = 0; + p.g = 0; + p.b = 0; + } + } + return true; } @@ -18192,6 +18210,23 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. + for(auto &p : image.GetPixels()) + { + if(p.a > 0) + { + p.r = (uint8_t)((p.r * 255) / p.a); + p.g = (uint8_t)((p.g * 255) / p.a); + p.b = (uint8_t)((p.b * 255) / p.a); + } + else + { + p.r = 0; + p.g = 0; + p.b = 0; + } + } + return true; } From 33ab50e5cc84146742520bf096584fb094f442d2 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 28 Feb 2026 06:07:04 -0500 Subject: [PATCH 08/41] [imageloader][MacOS] add imageloader_pixel_t for easier pixel swizzling. --- dev/src/api_macos.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index b8f77e7a..ba1f7bc0 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -86,6 +86,11 @@ extern "C" { void opengl_destroy (struct OpenGLRenderer* self); bool opengl_resetContextForSize (struct OpenGLRenderer* self, double width, double height); + // Pixel Struct used by Image Loader API + typedef struct { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + } imageloader_pixel_t; + // Image Loader API - as implemented in api_macos.c struct ImageLoader* imageloader_init (void); BOOL imageloader_loadFromFile (struct ImageLoader* self, const char* filePath); From f635038528f6605394b76d34d8232095c80e8562 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 28 Feb 2026 06:08:06 -0500 Subject: [PATCH 09/41] [imageloader][MacOS] remove un-premultiply from image loader --- dev/src/imload_macos.cpp | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index f8faf005..94734e6e 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -44,25 +44,7 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); - // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. - for(auto &p : image.GetPixels()) - { - if(p.a > 0) - { - p.r = (uint8_t)((p.r * 255) / p.a); - p.g = (uint8_t)((p.g * 255) / p.a); - p.b = (uint8_t)((p.b * 255) / p.a); - } - else - { - p.r = 0; - p.g = 0; - p.b = 0; - } - } - return true; - } bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) @@ -102,23 +84,6 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); - // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. - for(auto &p : image.GetPixels()) - { - if(p.a > 0) - { - p.r = (uint8_t)((p.r * 255) / p.a); - p.g = (uint8_t)((p.g * 255) / p.a); - p.b = (uint8_t)((p.b * 255) / p.a); - } - else - { - p.r = 0; - p.g = 0; - p.b = 0; - } - } - return true; } From ef1bb0e13ea14429f114782398db93278c838d06 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 28 Feb 2026 06:09:30 -0500 Subject: [PATCH 10/41] [imageloader][MacOS] use CGDataProvider instead of the CGBitmapContext --- dev/src/api_macos.cpp | 68 ++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 7f9f6b99..276dbe09 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -742,7 +742,7 @@ struct OpenGLRenderer { // Modern image loading and pixel data extraction struct ImageLoader { - unsigned char* pixelData{nullptr}; // Raw pixel data (RGBA format) + imageloader_pixel_t* pixelData{nullptr}; // Raw pixel data (RGBA format) int width{kMinValidDimension}; // Image width in pixels int height{kMinValidDimension}; // Image height in pixels int bytesPerPixel{kZeroBytes}; // Number of bytes per pixel (typically 4 for RGBA) @@ -1876,42 +1876,50 @@ extern "C" { self->pixelData = NULL; } + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); + + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); + CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); + CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; + self->width = CGImageGetWidth(image); self->height = CGImageGetHeight(image); self->bytesPerPixel = 4; self->bytesPerRow = self->width * self->bytesPerPixel; self->hasAlpha = YES; - self->pixelData = (unsigned char*)malloc(self->bytesPerRow * self->height); + const uint8_t* imageData = CFDataGetBytePtr(rawData); + self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); if(!self->pixelData) { return NO; } + memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); + + // NOTE from Moros1138 + // + // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far + // the most common case, so in practice this block of code will never + // be run. However, if we find that there is a need to adjust the + // pixel data, this will need fleshing out. - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - // Draw into a raw RGBA buffer that maps directly onto our Pixel layout - CGContextRef ctx = CGBitmapContextCreate( - self->pixelData, - self->width, self->height, - 8, // bits per component - self->bytesPerRow, // bytes per row - colorSpace, - kCGImageAlphaPremultipliedLast // RGBA byte order - ); - - CGColorSpaceRelease(colorSpace); - - if (!ctx) + /* + if(byteOrder != kCGBitmapByteOrder32Little) { - free(self->pixelData); - self->pixelData = NULL; - return NO; + int pixelCount = self->width * self->height; + for(int i = 0; i < pixelCount; ++i) + { + auto p = self->pixelData[i]; + + // TODO: detect byte order, adjust as appropriate + + self->pixelData[i] = p; + } } + */ - CGContextDrawImage(ctx, CGRectMake(0, 0, self->width, self->height), image); - CGContextRelease(ctx); - + CFRelease(rawData); CGImageRelease(image); return YES; @@ -1981,7 +1989,7 @@ extern "C" { // Get raw pixel data pointer unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return self->pixelData; + return (unsigned char*)self->pixelData; } // Get image information @@ -2015,13 +2023,13 @@ extern "C" { // Calculate pixel offset (macOS uses bottom-left origin, so flip Y) int flippedY = self->height - kFlippedOffset - y; - unsigned char* pixel = self->pixelData + (flippedY * self->bytesPerRow) + (x * self->bytesPerPixel); - + imageloader_pixel_t* pixel = self->pixelData + (flippedY * self->width) + x; + // Extract color components (assuming RGBA or RGB format) - if (red) *red = pixel[0]; - if (green) *green = pixel[1]; - if (blue) *blue = pixel[2]; - if (alpha && self->bytesPerPixel >= kRGBABytesPerPixel) *alpha = pixel[3]; + if (red) *red = pixel->r; + if (green) *green = pixel->g; + if (blue) *blue = pixel->b; + if (alpha && self->bytesPerPixel >= kRGBABytesPerPixel) *alpha = pixel->a; else if (alpha) *alpha = kFullyOpaque; // Fully opaque if no alpha channel return YES; From b657b8783106272812e9c79cd76590fd3f4d2c84 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 28 Feb 2026 06:10:08 -0500 Subject: [PATCH 11/41] [sh] update single header --- olcPixelGameEngine3.h | 108 +++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 65 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 797581b0..7374d5ec 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4306,6 +4306,11 @@ extern "C" { void opengl_destroy (struct OpenGLRenderer* self); bool opengl_resetContextForSize (struct OpenGLRenderer* self, double width, double height); + // Pixel Struct used by Image Loader API + typedef struct { + uint8_t r; uint8_t g; uint8_t b; uint8_t a; + } imageloader_pixel_t; + // Image Loader API - as implemented in api_macos.c struct ImageLoader* imageloader_init (void); BOOL imageloader_loadFromFile (struct ImageLoader* self, const char* filePath); @@ -8980,7 +8985,7 @@ struct OpenGLRenderer { // Modern image loading and pixel data extraction struct ImageLoader { - unsigned char* pixelData{nullptr}; // Raw pixel data (RGBA format) + imageloader_pixel_t* pixelData{nullptr}; // Raw pixel data (RGBA format) int width{kMinValidDimension}; // Image width in pixels int height{kMinValidDimension}; // Image height in pixels int bytesPerPixel{kZeroBytes}; // Number of bytes per pixel (typically 4 for RGBA) @@ -10114,42 +10119,50 @@ extern "C" { self->pixelData = NULL; } + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); + + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); + CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); + CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; + self->width = CGImageGetWidth(image); self->height = CGImageGetHeight(image); self->bytesPerPixel = 4; self->bytesPerRow = self->width * self->bytesPerPixel; self->hasAlpha = YES; - self->pixelData = (unsigned char*)malloc(self->bytesPerRow * self->height); + const uint8_t* imageData = CFDataGetBytePtr(rawData); + self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); if(!self->pixelData) { return NO; } + memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); + + // NOTE from Moros1138 + // + // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far + // the most common case, so in practice this block of code will never + // be run. However, if we find that there is a need to adjust the + // pixel data, this will need fleshing out. - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - // Draw into a raw RGBA buffer that maps directly onto our Pixel layout - CGContextRef ctx = CGBitmapContextCreate( - self->pixelData, - self->width, self->height, - 8, // bits per component - self->bytesPerRow, // bytes per row - colorSpace, - kCGImageAlphaPremultipliedLast // RGBA byte order - ); - - CGColorSpaceRelease(colorSpace); - - if (!ctx) + /* + if(byteOrder != kCGBitmapByteOrder32Little) { - free(self->pixelData); - self->pixelData = NULL; - return NO; + int pixelCount = self->width * self->height; + for(int i = 0; i < pixelCount; ++i) + { + auto p = self->pixelData[i]; + + // TODO: detect byte order, adjust as appropriate + + self->pixelData[i] = p; + } } + */ - CGContextDrawImage(ctx, CGRectMake(0, 0, self->width, self->height), image); - CGContextRelease(ctx); - + CFRelease(rawData); CGImageRelease(image); return YES; @@ -10219,7 +10232,7 @@ extern "C" { // Get raw pixel data pointer unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return self->pixelData; + return (unsigned char*)self->pixelData; } // Get image information @@ -10253,13 +10266,13 @@ extern "C" { // Calculate pixel offset (macOS uses bottom-left origin, so flip Y) int flippedY = self->height - kFlippedOffset - y; - unsigned char* pixel = self->pixelData + (flippedY * self->bytesPerRow) + (x * self->bytesPerPixel); - + imageloader_pixel_t* pixel = self->pixelData + (flippedY * self->width) + x; + // Extract color components (assuming RGBA or RGB format) - if (red) *red = pixel[0]; - if (green) *green = pixel[1]; - if (blue) *blue = pixel[2]; - if (alpha && self->bytesPerPixel >= kRGBABytesPerPixel) *alpha = pixel[3]; + if (red) *red = pixel->r; + if (green) *green = pixel->g; + if (blue) *blue = pixel->b; + if (alpha && self->bytesPerPixel >= kRGBABytesPerPixel) *alpha = pixel->a; else if (alpha) *alpha = kFullyOpaque; // Fully opaque if no alpha channel return YES; @@ -18152,25 +18165,7 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); - // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. - for(auto &p : image.GetPixels()) - { - if(p.a > 0) - { - p.r = (uint8_t)((p.r * 255) / p.a); - p.g = (uint8_t)((p.g * 255) / p.a); - p.b = (uint8_t)((p.b * 255) / p.a); - } - else - { - p.r = 0; - p.g = 0; - p.b = 0; - } - } - return true; - } bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) @@ -18210,23 +18205,6 @@ namespace olc::imload // The api_macos will provide RGBA format with 4 bytes per pixel std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); - // The CoreGraphics/ImageIO premultiplies, but PGE wants raw values. - for(auto &p : image.GetPixels()) - { - if(p.a > 0) - { - p.r = (uint8_t)((p.r * 255) / p.a); - p.g = (uint8_t)((p.g * 255) / p.a); - p.b = (uint8_t)((p.b * 255) / p.a); - } - else - { - p.r = 0; - p.g = 0; - p.b = 0; - } - } - return true; } From c11f25c6623d4fbc5f881709b9883472e5981189 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 2 Mar 2026 19:56:11 -0500 Subject: [PATCH 12/41] [emscripten] add SetFullscreen to host --- dev/src/host_web_emscripten.cpp | 15 +++++++++++++++ dev/src/host_web_emscripten.h | 1 + 2 files changed, 16 insertions(+) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index eaeee040..98d37119 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -287,6 +287,21 @@ namespace olc::host return true; } + + bool Host_Web_Emscripten::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + if(!mapUID2CanvasId.contains(pWindow->GetUID())) + return false; + + auto canvasID = mapUID2CanvasId.at(pWindow->GetUID()); + + if(bFullScreen) + emscripten_request_fullscreen(canvasID.c_str(), true); + else + emscripten_exit_fullscreen(); + + return true; + } olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 47a12351..82415857 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -43,6 +43,7 @@ namespace olc::host bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; From d5a6dce8b687b7585f7ed58d2edf3ad67d78024d Mon Sep 17 00:00:00 2001 From: DCubix Date: Tue, 3 Mar 2026 21:06:40 -0400 Subject: [PATCH 13/41] fix android host --- dev/src/host_android.cpp | 16 ++++++++++++++-- dev/src/host_android.h | 8 ++++++++ olcPixelGameEngine3.h | 23 +++++++++++++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/dev/src/host_android.cpp b/dev/src/host_android.cpp index 7482f9a1..30195157 100644 --- a/dev/src/host_android.cpp +++ b/dev/src/host_android.cpp @@ -260,11 +260,9 @@ namespace olc::host LOGD("APP_CMD_TERM_WINDOW received"); } break; case APP_CMD_GAINED_FOCUS: { - host->pgeWindow->olc_OnMouseFocus(true); LOGD("APP_CMD_GAINED_FOCUS received"); } break; case APP_CMD_LOST_FOCUS: { - host->pgeWindow->olc_OnMouseFocus(false); LOGD("APP_CMD_LOST_FOCUS received"); } break; default: break; @@ -614,7 +612,21 @@ namespace olc::host } } } + + bool Host_Android::SetMousePosition(olc::Window *pWindow, const olc::vi2d &vPos) + { + return false; + } + + bool Host_Android::SetMouseVisible(olc::Window *pWindow, const bool bVisible) + { + return false; + } + bool Host_Android::SetFullScreen(olc::Window *pWindow, const bool bFullScreen) + { + return false; + } } void android_main(struct android_app* app) diff --git a/dev/src/host_android.h b/dev/src/host_android.h index 81eb67b1..19a08cac 100644 --- a/dev/src/host_android.h +++ b/dev/src/host_android.h @@ -1,4 +1,5 @@ #pragma once +#include "core.h" //! START CUSTOMHEADER #include "host_iface.h" @@ -57,6 +58,13 @@ namespace olc::host // Called at very end of application bool OnApplicationEnd() override; + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + // Set a window to fullscreen or not fullscreen + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + void OnAppCmd(AndroidApp* app, int32_t cmd); int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3b6b07c5..38f48f94 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5983,6 +5983,13 @@ namespace olc::host // Called at very end of application bool OnApplicationEnd() override; + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + // Set a window to fullscreen or not fullscreen + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + void OnAppCmd(AndroidApp* app, int32_t cmd); int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); @@ -12946,11 +12953,9 @@ namespace olc::host LOGD("APP_CMD_TERM_WINDOW received"); } break; case APP_CMD_GAINED_FOCUS: { - host->pgeWindow->olc_OnMouseFocus(true); LOGD("APP_CMD_GAINED_FOCUS received"); } break; case APP_CMD_LOST_FOCUS: { - host->pgeWindow->olc_OnMouseFocus(false); LOGD("APP_CMD_LOST_FOCUS received"); } break; default: break; @@ -13300,7 +13305,21 @@ namespace olc::host } } } + + bool Host_Android::SetMousePosition(olc::Window *pWindow, const olc::vi2d &vPos) + { + return false; + } + + bool Host_Android::SetMouseVisible(olc::Window *pWindow, const bool bVisible) + { + return false; + } + bool Host_Android::SetFullScreen(olc::Window *pWindow, const bool bFullScreen) + { + return false; + } } void android_main(struct android_app* app) From aefca17c487fc0019361174420e2576cb0f418db Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 6 Mar 2026 06:24:14 -0500 Subject: [PATCH 14/41] [core] should say OnBeforeSystemUpdate instead of OnAfterUserCreate --- dev/src/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 7b3a0449..1b8cb623 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -578,7 +578,7 @@ namespace olc { if (!pgex->OnBeforeSystemUpdate(this, fDT)) { - std::cout << "PGE OnContextTick(): User aborted in extension OnAfterUserCreate()\n"; + std::cout << "PGE OnContextTick(): User aborted in extension OnBeforeSystemUpdate()\n"; return false; } } From aa8ac2f27419d53a168394624a65c8d354e17d8e Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 6 Mar 2026 06:25:18 -0500 Subject: [PATCH 15/41] [sh] update single header --- olcPixelGameEngine3.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3b6b07c5..b5eb44b3 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -17516,7 +17516,7 @@ namespace olc { if (!pgex->OnBeforeSystemUpdate(this, fDT)) { - std::cout << "PGE OnContextTick(): User aborted in extension OnAfterUserCreate()\n"; + std::cout << "PGE OnContextTick(): User aborted in extension OnBeforeSystemUpdate()\n"; return false; } } From d0aedbf8cc4df66c3180e34d92d710fcf1a7306d Mon Sep 17 00:00:00 2001 From: John Galvin Date: Sat, 7 Mar 2026 16:03:51 +0000 Subject: [PATCH 16/41] 225-some-gpus-are-having-issues-with-wgl_swap_buffer-intel-irishduhd-nvidia-quadro-t1000-microsoft-surface --- dev/src/gpu_opengl33.cpp | 9 +++++---- olcPixelGameEngine3.h | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 51f8e306..3c1a792f 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -255,10 +255,11 @@ void main() if (pfnCreateContextAttribs) { int gl33_attribs[] = { - 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB - 0x2092, 3, // WGL_CONTEXT_MINOR_VERSION_ARB - 0x9126, 0x00000001, // WGL_CONTEXT_PROFILE_MASK_ARB = CORE - 0}; + 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB = 3 + 0x2092, 3, // WGL_CONTEXT_MINOR_VERSION_ARB = 3 + 0x2094, 0, // WGL_CONTEXT_FLAGS_ARB = 0 (no flags) + 0x9126, 0x00000002, // WGL_CONTEXT_PROFILE_MASK_ARB = COMPATIBILITY + 0 }; glRenderContext = pfnCreateContextAttribs(glDeviceContext, nullptr, gl33_attribs); } else diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3b6b07c5..8fc5321c 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -14330,10 +14330,11 @@ void main() if (pfnCreateContextAttribs) { int gl33_attribs[] = { - 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB - 0x2092, 3, // WGL_CONTEXT_MINOR_VERSION_ARB - 0x9126, 0x00000001, // WGL_CONTEXT_PROFILE_MASK_ARB = CORE - 0}; + 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB = 3 + 0x2092, 3, // WGL_CONTEXT_MINOR_VERSION_ARB = 3 + 0x2094, 0, // WGL_CONTEXT_FLAGS_ARB = 0 (no flags) + 0x9126, 0x00000002, // WGL_CONTEXT_PROFILE_MASK_ARB = COMPATIBILITY + 0 }; glRenderContext = pfnCreateContextAttribs(glDeviceContext, nullptr, gl33_attribs); } else From 20e7716ac298ccab1e44384dbc73a21746d8eadc Mon Sep 17 00:00:00 2001 From: John Galvin Date: Sat, 7 Mar 2026 21:35:41 +0000 Subject: [PATCH 17/41] Windows OS Window Furniture completed, SH updated. --- dev/src/core.h | 10 ++++ dev/src/host_win_winapi.cpp | 83 ++++++++++++++++++++++++-------- dev/src/host_win_winapi.h | 1 + dev/tests/test_mh.cpp | 10 ++++ olcPixelGameEngine3.h | 94 +++++++++++++++++++++++++++++-------- 5 files changed, 158 insertions(+), 40 deletions(-) diff --git a/dev/src/core.h b/dev/src/core.h index 81f9b757..fb5a8110 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -45,6 +45,16 @@ namespace olc bool bFullScreenable = true; // Allow the window to be resized by user bool bResizeable = true; + // Allow the window border to be hidden by user + bool bShowWindowBorder = true; + // Allow the window title bar to be hidden by user + bool bShowWindowTilebar = true; + // Allow the windows minimise button to be hidden by user + bool bShowWindowMinimiseButton = true; + // Allow the windows maximised button to be hidden by user + bool bShowWindowMaximiseButton = true; + // Allow the windows close button to be hidden by user + bool bShowWindowCloseButton = true; // Synchronise rendering with monitor bool bVSync = OLC_DEFAULT_VSYNC; // Behave like a host window, resizing the screen in response to window resize diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 3fe19d1a..d41a7b2d 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -284,25 +284,26 @@ namespace olc::host // Define window furniture DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); olc::vi2d vTopLeft = vWindowPos; - //// Handle Fullscreen - //if (bFullScreen) - //{ - // dwExStyle = 0; - // dwStyle = WS_VISIBLE | WS_POPUP; - // HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); - // MONITORINFO mi = { sizeof(mi) }; - // if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; - // vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; - // vTopLeft.x = 0; - // vTopLeft.y = 0; - //} + if (bFullScreen || pPrimaryPGE->config.bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + POINT olc_pt = { vWinPos.x, vWinPos.y }; + HMONITOR hmon = MonitorFromPoint(olc_pt, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return false; + vWinSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + // Keep client size as requested - RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); int width = rWndRect.right - rWndRect.left; int height = rWndRect.bottom - rWndRect.top; @@ -317,8 +318,15 @@ namespace olc::host GetClientRect(hWnd, &rClient); pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); + // Hide the close button if the user requested it, but only after styles are applied, + if (!pPrimaryPGE->config.bShowWindowCloseButton) + { + HMENU hMenu = GetSystemMenu(hWnd, FALSE); + DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + } + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | (WS_CAPTION | WS_SYSMENU | WS_POPUPWINDOW | WS_THICKFRAME)); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); @@ -406,22 +414,57 @@ namespace olc::host // Maximise, make on top, remove border and titlebar SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); - ShowWindow(hWnd, SW_MAXIMIZE); + ShowWindow(hWnd, SW_MAXIMIZE); } else { + olc::vi2d vWinPos = pPrimaryPGE->config.vWindowOffset; + olc::vi2d vWinSize = pPrimaryPGE->config.vScreenSize * pPrimaryPGE->config.vPixelSize; + // Restore original window style and position - SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); - ShowWindow(hWnd, SW_RESTORE); + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + // Get the style we should have based on the window config + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); + + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | dwStyle); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | dwExStyle); + ShowWindow(hWnd, SW_NORMAL); } UpdateWindow(hWnd); SetForegroundWindow(hWnd); SetFocus(hWnd); - SetActiveWindow(hWnd); + SetActiveWindow(hWnd); return true; } + + DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) + { + olc_IgnoreUnused(pWindow); + + DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx + + // Note for Microsoft: if you hide the border, it hides the title bar too, and via versa + + // For fullscreen,borderless/noTitlebar we want to skip all the window furniture and just have a big ol canvas + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return dwStyle |= WS_POPUP; + + // If any max/min/close button(s) display the button menu + if (pPrimaryPGE->config.bShowWindowCloseButton || pPrimaryPGE->config.bShowWindowMaximiseButton || pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_SYSMENU; + if (pPrimaryPGE->config.bShowWindowTilebar) dwStyle |= WS_CAPTION; // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) dwStyle |= WS_BORDER; // Add a border + if (pPrimaryPGE->config.bResizeable) dwStyle |= WS_THICKFRAME; // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_MINIMIZEBOX; // Add Min Button + if (pPrimaryPGE->config.bShowWindowMaximiseButton) dwStyle |= WS_MAXIMIZEBOX; // Add Max Button + + // Note: Close button is handled after dwStlyes are applied + + return dwStyle; + + + } LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 7138fac7..469e309f 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -107,6 +107,7 @@ namespace olc std::atomic systemActive = false; HCURSOR hCursorDefault = nullptr; HCURSOR hCursorNow = nullptr; + DWORD ConvertPGE2WindowStyle(const olc::Window* pWindow); public: LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 9ab64439..b2fb1a3b 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -790,6 +790,16 @@ int main() cfg.bVSync = false; cfg.sAppName = "Test mh"; + // Window furniture test + cfg.bFullScreen = false; + cfg.bFullScreenable = false; + cfg.bResizeable = true; + cfg.bShowWindowBorder = true; + cfg.bShowWindowTilebar = true; + cfg.bShowWindowMinimiseButton = false; + cfg.bShowWindowMaximiseButton = false; + cfg.bShowWindowCloseButton = true; + //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) if(demo.Construct(cfg)) demo.Start(); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3b6b07c5..60d637b0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3990,6 +3990,16 @@ namespace olc bool bFullScreenable = true; // Allow the window to be resized by user bool bResizeable = true; + // Allow the window border to be hidden by user + bool bShowWindowBorder = true; + // Allow the window title bar to be hidden by user + bool bShowWindowTilebar = true; + // Allow the windows minimise button to be hidden by user + bool bShowWindowMinimiseButton = true; + // Allow the windows maximised button to be hidden by user + bool bShowWindowMaximiseButton = true; + // Allow the windows close button to be hidden by user + bool bShowWindowCloseButton = true; // Synchronise rendering with monitor bool bVSync = OLC_DEFAULT_VSYNC; // Behave like a host window, resizing the screen in response to window resize @@ -4313,6 +4323,7 @@ namespace olc std::atomic systemActive = false; HCURSOR hCursorDefault = nullptr; HCURSOR hCursorNow = nullptr; + DWORD ConvertPGE2WindowStyle(const olc::Window* pWindow); public: LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -7296,25 +7307,26 @@ namespace olc::host // Define window furniture DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); olc::vi2d vTopLeft = vWindowPos; - //// Handle Fullscreen - //if (bFullScreen) - //{ - // dwExStyle = 0; - // dwStyle = WS_VISIBLE | WS_POPUP; - // HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); - // MONITORINFO mi = { sizeof(mi) }; - // if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; - // vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; - // vTopLeft.x = 0; - // vTopLeft.y = 0; - //} + if (bFullScreen || pPrimaryPGE->config.bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + POINT olc_pt = { vWinPos.x, vWinPos.y }; + HMONITOR hmon = MonitorFromPoint(olc_pt, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return false; + vWinSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + // Keep client size as requested - RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); int width = rWndRect.right - rWndRect.left; int height = rWndRect.bottom - rWndRect.top; @@ -7329,8 +7341,15 @@ namespace olc::host GetClientRect(hWnd, &rClient); pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); + // Hide the close button if the user requested it, but only after styles are applied, + if (!pPrimaryPGE->config.bShowWindowCloseButton) + { + HMENU hMenu = GetSystemMenu(hWnd, FALSE); + DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + } + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | (WS_CAPTION | WS_SYSMENU | WS_POPUPWINDOW | WS_THICKFRAME)); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); @@ -7418,22 +7437,57 @@ namespace olc::host // Maximise, make on top, remove border and titlebar SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); - ShowWindow(hWnd, SW_MAXIMIZE); + ShowWindow(hWnd, SW_MAXIMIZE); } else { + olc::vi2d vWinPos = pPrimaryPGE->config.vWindowOffset; + olc::vi2d vWinSize = pPrimaryPGE->config.vScreenSize * pPrimaryPGE->config.vPixelSize; + // Restore original window style and position - SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); - ShowWindow(hWnd, SW_RESTORE); + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + // Get the style we should have based on the window config + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); + + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | dwStyle); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | dwExStyle); + ShowWindow(hWnd, SW_NORMAL); } UpdateWindow(hWnd); SetForegroundWindow(hWnd); SetFocus(hWnd); - SetActiveWindow(hWnd); + SetActiveWindow(hWnd); return true; } + + DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) + { + olc_IgnoreUnused(pWindow); + + DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx + + // Note for Microsoft: if you hide the border, it hides the title bar too, and via versa + + // For fullscreen,borderless/noTitlebar we want to skip all the window furniture and just have a big ol canvas + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return dwStyle |= WS_POPUP; + + // If any max/min/close button(s) display the button menu + if (pPrimaryPGE->config.bShowWindowCloseButton || pPrimaryPGE->config.bShowWindowMaximiseButton || pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_SYSMENU; + if (pPrimaryPGE->config.bShowWindowTilebar) dwStyle |= WS_CAPTION; // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) dwStyle |= WS_BORDER; // Add a border + if (pPrimaryPGE->config.bResizeable) dwStyle |= WS_THICKFRAME; // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_MINIMIZEBOX; // Add Min Button + if (pPrimaryPGE->config.bShowWindowMaximiseButton) dwStyle |= WS_MAXIMIZEBOX; // Add Max Button + + // Note: Close button is handled after dwStlyes are applied + + return dwStyle; + + + } LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { From a8e8f2e9f4d12237de3511993fe29bd180c6724c Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:08:15 -0600 Subject: [PATCH 18/41] Add support for libdecor to unbreak mutter and similarly bad compositors. --- dev/src/host_lin_wayland.cpp | 225 +++++++++++++++++++++++++-- dev/src/host_lin_wayland.h | 65 +++++++- examples/CMakeLists.txt | 31 +++- olcPixelGameEngine3.h | 290 +++++++++++++++++++++++++++++++++-- 4 files changed, 575 insertions(+), 36 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index cf974aac..13e02a35 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -55,11 +55,28 @@ namespace olc::host .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback }; + #ifdef ENABLE_DECORATION_PROTOCOL static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener { .configure = Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback }; + #endif } + #ifdef ENABLE_LIBDECOR + namespace decor { + static libdecor_interface libdecor_error_listener = { + .error = Host_Linux_Wayland::libdecor_error_callback, + }; + + static libdecor_frame_interface libdecor_frame_listener = { + .configure = Host_Linux_Wayland::libdecor_frame_configure_callback, + .close = Host_Linux_Wayland::libdecor_close_callback, + .commit = Host_Linux_Wayland::libdecor_commit_callback, + .dismiss_popup = Host_Linux_Wayland::libdecor_dismiss_popup_callback + }; + } + #endif + Host_Linux_Wayland::Host_Linux_Wayland() { @@ -68,16 +85,39 @@ namespace olc::host wl_registry_add_listener(registry, &wayland::registry_listener, this); wl_display_roundtrip(display); + + if(compositor == nullptr || xdg_wm == nullptr || seat == nullptr) { + throw; + } - if(compositor == nullptr || xdg_wm == nullptr || seat == nullptr || decoration_manager == nullptr) { + // If only the decoration protocol is enabled, then not having the protocol is a hard error + #if defined(ENABLE_DECORATION_PROTOCOL) && !defined(ENABLE_LIBDECOR) + if(decoration_manager == nullptr) { throw; } + // If only libdecor is enabled, then flag "using_libdecor" + #elif !defined(ENABLE_DECORATION_PROTOCOL) && defined(ENABLE_LIBDECOR) + using_libdecor = true; + // If both are enabled, use libdecor if the decoration protocol is not present + #else + using_libdecor = (decoration_manager == nullptr); + #endif + + #ifdef ENABLE_LIBDECOR + if(!using_libdecor) { + xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); + } else { + decor_context = libdecor_new(display, &decor::libdecor_error_listener); + } + #else xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - wl_seat_add_listener(seat, &wayland::seat_listener, this); - + #endif + kb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + wl_display_roundtrip(display); + // Setup the keymap with XKB codes, which are basically the same as the X11 codes mapKeys[XKB_KEY_NoSymbol] = Key::NONE; @@ -143,8 +183,17 @@ namespace olc::host for (auto& itr : mapUID2Window) { auto& wayland_window = itr.second; wl_egl_window_destroy(wayland_window.window); - xdg_toplevel_destroy(wayland_window.toplevel); - xdg_surface_destroy(wayland_window.surface_xdg); + if(wayland_window.toplevel) { + xdg_toplevel_destroy(wayland_window.toplevel); + } + if(wayland_window.surface_xdg) { + xdg_surface_destroy(wayland_window.surface_xdg); + } + #ifdef ENABLE_LIBDECOR + if(wayland_window.decor_frame) { + libdecor_frame_close(wayland_window.decor_frame); + } + #endif wl_surface_destroy(wayland_window.surface); } @@ -193,7 +242,20 @@ namespace olc::host } }); + #if !defined(ENABLE_LIBDECOR) while(systemActive && wl_display_dispatch_pending(display) != -1) { } + #else + bool keep_running = true; + while(systemActive && keep_running) { + if(using_libdecor) { + if(decor_context) { + keep_running = libdecor_dispatch(decor_context, 0) >= 0; + } + } else { + keep_running = wl_display_dispatch_pending(display) != -1; + } + } + #endif systemActive = false; if(threadSystem.joinable()) @@ -236,27 +298,45 @@ namespace olc::host wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); w.surface = wl_compositor_create_surface(compositor); - w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); + + #ifdef ENABLE_LIBDECOR + if(!using_libdecor) { + #endif + w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); + + xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); + w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); + xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); + xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); + + #ifdef ENABLE_DECORATION_PROTOCOL + w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); + zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); + zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); + #endif + #ifdef ENABLE_LIBDECOR + } else { + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + libdecor_frame_map(w.decor_frame); + } + #endif - xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); - w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); - xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); - xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); wl_surface_set_opaque_region(w.surface, region); w.window = wl_egl_window_create(w.surface, vWindowSize.x, vWindowSize.y); w.olc_window_uid = pWindow->GetUID(); wl_surface_commit(w.surface); wl_region_destroy(region); - w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); - zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); - zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); - pWindow->SetWindowPosition(vWindowPos); pWindow->SetWindowSize(vWindowSize); mapUID2Window.insert_or_assign(pWindow->GetUID(), w); mapUID2OlcWindow.insert_or_assign(pWindow->GetUID(), pWindow); + return true; } @@ -266,7 +346,9 @@ namespace olc::host if(itr != mapUID2Window.end()) { auto& wayland_window = itr->second; wl_egl_window_destroy(wayland_window.window); + #ifdef ENABLE_DECORATION_PROTOCOL zxdg_toplevel_decoration_v1_destroy(wayland_window.decorations); + #endif xdg_toplevel_destroy(wayland_window.toplevel); xdg_surface_destroy(wayland_window.surface_xdg); wl_surface_destroy(wayland_window.surface); @@ -281,7 +363,15 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); + } else { + xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + } + #else xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + #endif } return true; } @@ -358,9 +448,25 @@ namespace olc::host if(itr != mapUID2Window.end()) { itr->second.fullscreen = bFullScreen; if(bFullScreen) { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); + } else { + xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); + } + #else xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); + #endif } else { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + libdecor_frame_unset_fullscreen(itr->second.decor_frame); + } else { + xdg_toplevel_unset_fullscreen(itr->second.toplevel); + } + #else xdg_toplevel_unset_fullscreen(itr->second.toplevel); + #endif } } return true; @@ -376,10 +482,13 @@ namespace olc::host } if(std::strcmp(interface, wl_seat_interface.name) == 0) { seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); + wl_seat_add_listener(seat, &wayland::seat_listener, this); } + #ifdef ENABLE_DECORATION_PROTOCOL if(std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { decoration_manager = static_cast(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, version)); } + #endif if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } @@ -827,11 +936,99 @@ namespace olc::host return; } + #ifdef ENABLE_DECORATION_PROTOCOL void Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode) { // auto* host = reinterpret_cast(data); // fprintf(stderr, "zxdg_decoration_manager_v1 mode %d\n", mode); } + #endif + + #ifdef ENABLE_LIBDECOR + void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) + { + std::cerr << "libdecor: " << error << ": " << message << "\n"; + } + + void Host_Linux_Wayland::libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data) + { + auto* host = reinterpret_cast(data); + host->libdecor_frame_configure(frame, config); + } + + void Host_Linux_Wayland::libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config) + { + for(auto& i : mapUID2Window) { + if(i.second.decor_frame == frame) { + auto* window = &i.second; + + int width{}; + int height{}; + + if(!libdecor_configuration_get_window_state(config, &window->decor_window_state)) { + window->decor_window_state = LIBDECOR_WINDOW_STATE_NONE; + } + + libdecor_configuration_get_content_size(config, frame, &width, &height); + + window->configured_width = width == 0 ? window->floating_width : width; + window->configured_height = height == 0 ? window->floating_height : height; + + libdecor_state* state = libdecor_state_new(window->configured_width, window->configured_height); + libdecor_frame_commit(frame, state, config); + libdecor_state_free(state); + + if(libdecor_frame_is_floating(frame)) { + window->floating_width = width; + window->floating_height = height; + } + + mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); + wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); + wl_surface_commit(window->surface); + } + } + } + + void Host_Linux_Wayland::libdecor_close_callback(libdecor_frame* frame, void* data) + { + auto* host = reinterpret_cast(data); + host->libdecor_close(frame); + } + + void Host_Linux_Wayland::libdecor_close(libdecor_frame* frame) + { + for(auto& i : mapUID2Window) { + if(i.second.decor_frame == frame) { + auto itr = mapUID2OlcWindow.find(i.second.olc_window_uid); + if (itr != mapUID2OlcWindow.end()) { + auto* ptr = itr->second; + ptr->olc_OnWindowClose(); + } + } + } + } + + void Host_Linux_Wayland::libdecor_commit_callback(libdecor_frame* frame, void* data) + { + auto* host = reinterpret_cast(data); + host->libdecor_commit(frame); + } + + void Host_Linux_Wayland::libdecor_commit(libdecor_frame* frame) + { + for(auto& i : mapUID2Window) { + if(i.second.decor_frame == frame) { + wl_surface_commit(i.second.surface); + } + } + } + + void Host_Linux_Wayland::libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data) + { + + } + #endif std::vector Host_Linux_Wayland::GetHostWindowDescriptor(olc::Window* pWindow) { diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 29132e02..3667ac06 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -16,10 +16,27 @@ //! END CUSTOMHEADER //! START DECLARATION +#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_LIBDECOR +#endif + +#if !defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_DECORATION_PROTOCOL +#endif + +#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) +#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." +#endif + #include #include #include "xdg-shell.h" + +// Only include the decoration protcol if we are not forcing libdecor +#ifdef ENABLE_DECORATION_PROTOCOL #include "xdg-decoration.h" +#endif + #include "pointer-warp.h" #include "cursor-shape.h" #include @@ -28,6 +45,10 @@ #include #include +#ifdef ENABLE_LIBDECOR +#include "libdecor.h" +#endif + #include #include @@ -41,7 +62,9 @@ namespace olc::host wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; xdg_toplevel* toplevel{nullptr}; + #ifdef ENABLE_DECORATION_PROTOCOL zxdg_toplevel_decoration_v1* decorations{nullptr}; + #endif wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -49,6 +72,17 @@ namespace olc::host bool cursor_visible{true}; // Ignore window size bounds for fullscreen events bool fullscreen{false}; + + #ifdef ENABLE_LIBDECOR + // libdecor support + libdecor_frame* decor_frame{nullptr}; + libdecor_frame_interface* decor_interface{nullptr}; + int configured_width{}; + int configured_height{}; + libdecor_window_state decor_window_state; + int floating_width{}; + int floating_height{}; + #endif }; namespace wayland { @@ -99,15 +133,23 @@ namespace olc::host xkb_keymap* kb_keymap; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; + #ifdef ENABLE_DECORATION_PROTOCOL zxdg_decoration_manager_v1* decoration_manager{nullptr}; + #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; - + wayland::PointerState pointer_state; - + size_t active_window_id; + + #ifdef ENABLE_LIBDECOR + // libdecor support + bool using_libdecor{false}; + libdecor* decor_context; + #endif public: Host_Linux_Wayland(); @@ -172,7 +214,19 @@ namespace olc::host static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); + #ifdef ENABLE_DECORATION_PROTOCOL static void xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode); + #endif + + #ifdef ENABLE_LIBDECOR + // libdecor callbacks + static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); + static void libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data); + static void libdecor_close_callback(libdecor_frame* frame, void* data); + static void libdecor_commit_callback(libdecor_frame* frame, void* data); + static void libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data); + #endif + private: // Wayland callback functions void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); @@ -204,6 +258,13 @@ namespace olc::host void xdg_toplevel_close(xdg_toplevel* toplevel); void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); + #ifdef ENABLE_LIBDECOR + // libdecor callback functions + void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); + void libdecor_close(libdecor_frame* frame); + void libdecor_commit(libdecor_frame* frame); + #endif + bool CreateEGLContext(WaylandWindow* window); std::unordered_map mapUID2Window; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4f010482..b958ce50 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,6 +8,8 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(BUILD_WAYLAND "Build example programs with Wayland" OFF) option(USE_STB "Use stb_image.h for image loading" OFF) +option(DISABLE_LIBDECOR "Disable libdecor usage in wayland builds" OFF) +option(FORCE_WAYLAND_LIBDECOR "Force wayland builds to only use libdecor" OFF) ###################################################################### # Directories @@ -26,6 +28,10 @@ if(UNIX AND BUILD_WAYLAND) pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) pkg_check_modules(EGL REQUIRED egl) + if(NOT DISABLE_LIBDECOR) + pkg_check_modules(LIBDECOR REQUIRED IMPORTED_TARGET libdecor-0) + endif() + find_package(OpenGL REQUIRED) find_package(PNG REQUIRED) find_package(Threads REQUIRED) @@ -61,20 +67,25 @@ if(UNIX AND BUILD_WAYLAND) endfunction() generate_wayland_protocol("xdg-shell" "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") - generate_wayland_protocol("xdg-decoration" "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") generate_wayland_protocol("pointer-warp" "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") generate_wayland_protocol("cursor-shape" "/usr/share/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml") generate_wayland_protocol("tablet" "/usr/share/wayland-protocols/stable/tablet/tablet-v2.xml") - + # Create a library for the wayland protocol files add_library(wayland_protocols INTERFACE) target_link_libraries(wayland_protocols INTERFACE wayland_xdg-shell - wayland_xdg-decoration wayland_pointer-warp wayland_cursor-shape wayland_tablet ) + + if(NOT FORCE_WAYLAND_LIBDECOR) + generate_wayland_protocol("xdg-decoration" "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") + target_link_libraries(wayland_protocols INTERFACE + wayland_xdg-decoration + ) + endif() target_include_directories(wayland_protocols INTERFACE ${CMAKE_CURRENT_BINARY_DIR} @@ -106,6 +117,12 @@ foreach(source ${SOURCES}) if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) if(BUILD_WAYLAND) target_compile_definitions(${EXE_NAME} PRIVATE OLC_HOST=3) + if(FORCE_WAYLAND_LIBDECOR) + target_compile_definitions(${EXE_NAME} PRIVATE FORCE_WAYLAND_LIBDECOR=1) + endif() + if(DISABLE_LIBDECOR) + target_compile_definitions(${EXE_NAME} PRIVATE DISABLE_LIBDECOR=1) + endif() target_link_libraries(${EXE_NAME} PRIVATE wayland_protocols @@ -116,7 +133,13 @@ foreach(source ${SOURCES}) OpenGL::GL PNG::PNG Threads::Threads - ) + ) + + if(NOT DISABLE_LIBDECOR) + target_link_libraries(${EXE_NAME} PRIVATE + PkgConfig::LIBDECOR + ) + endif() else() # x11 target_link_libraries(${EXE_NAME} PRIVATE png) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3b6b07c5..a7963847 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5632,10 +5632,27 @@ namespace olc::host #if OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_LIBDECOR +#endif + +#if !defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_DECORATION_PROTOCOL +#endif + +#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) +#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." +#endif + #include #include #include "xdg-shell.h" + +// Only include the decoration protcol if we are not forcing libdecor +#ifdef ENABLE_DECORATION_PROTOCOL #include "xdg-decoration.h" +#endif + #include "pointer-warp.h" #include "cursor-shape.h" #include @@ -5644,6 +5661,10 @@ namespace olc::host #include #include +#ifdef ENABLE_LIBDECOR +#include "libdecor.h" +#endif + #include #include @@ -5657,7 +5678,9 @@ namespace olc::host wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; xdg_toplevel* toplevel{nullptr}; + #ifdef ENABLE_DECORATION_PROTOCOL zxdg_toplevel_decoration_v1* decorations{nullptr}; + #endif wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -5665,6 +5688,17 @@ namespace olc::host bool cursor_visible{true}; // Ignore window size bounds for fullscreen events bool fullscreen{false}; + + #ifdef ENABLE_LIBDECOR + // libdecor support + libdecor_frame* decor_frame{nullptr}; + libdecor_frame_interface* decor_interface{nullptr}; + int configured_width{}; + int configured_height{}; + libdecor_window_state decor_window_state; + int floating_width{}; + int floating_height{}; + #endif }; namespace wayland { @@ -5715,15 +5749,23 @@ namespace olc::host xkb_keymap* kb_keymap; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; + #ifdef ENABLE_DECORATION_PROTOCOL zxdg_decoration_manager_v1* decoration_manager{nullptr}; + #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; - + wayland::PointerState pointer_state; - + size_t active_window_id; + + #ifdef ENABLE_LIBDECOR + // libdecor support + bool using_libdecor{false}; + libdecor* decor_context; + #endif public: Host_Linux_Wayland(); @@ -5788,7 +5830,19 @@ namespace olc::host static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); + #ifdef ENABLE_DECORATION_PROTOCOL static void xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode); + #endif + + #ifdef ENABLE_LIBDECOR + // libdecor callbacks + static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); + static void libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data); + static void libdecor_close_callback(libdecor_frame* frame, void* data); + static void libdecor_commit_callback(libdecor_frame* frame, void* data); + static void libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data); + #endif + private: // Wayland callback functions void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); @@ -5820,6 +5874,13 @@ namespace olc::host void xdg_toplevel_close(xdg_toplevel* toplevel); void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); + #ifdef ENABLE_LIBDECOR + // libdecor callback functions + void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); + void libdecor_close(libdecor_frame* frame); + void libdecor_commit(libdecor_frame* frame); + #endif + bool CreateEGLContext(WaylandWindow* window); std::unordered_map mapUID2Window; @@ -11238,11 +11299,28 @@ namespace olc::host .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback }; + #ifdef ENABLE_DECORATION_PROTOCOL static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener { .configure = Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback }; + #endif } + #ifdef ENABLE_LIBDECOR + namespace decor { + static libdecor_interface libdecor_error_listener = { + .error = Host_Linux_Wayland::libdecor_error_callback, + }; + + static libdecor_frame_interface libdecor_frame_listener = { + .configure = Host_Linux_Wayland::libdecor_frame_configure_callback, + .close = Host_Linux_Wayland::libdecor_close_callback, + .commit = Host_Linux_Wayland::libdecor_commit_callback, + .dismiss_popup = Host_Linux_Wayland::libdecor_dismiss_popup_callback + }; + } + #endif + Host_Linux_Wayland::Host_Linux_Wayland() { @@ -11251,16 +11329,39 @@ namespace olc::host wl_registry_add_listener(registry, &wayland::registry_listener, this); wl_display_roundtrip(display); + + if(compositor == nullptr || xdg_wm == nullptr || seat == nullptr) { + throw; + } - if(compositor == nullptr || xdg_wm == nullptr || seat == nullptr || decoration_manager == nullptr) { + // If only the decoration protocol is enabled, then not having the protocol is a hard error + #if defined(ENABLE_DECORATION_PROTOCOL) && !defined(ENABLE_LIBDECOR) + if(decoration_manager == nullptr) { throw; } + // If only libdecor is enabled, then flag "using_libdecor" + #elif !defined(ENABLE_DECORATION_PROTOCOL) && defined(ENABLE_LIBDECOR) + using_libdecor = true; + // If both are enabled, use libdecor if the decoration protocol is not present + #else + using_libdecor = (decoration_manager == nullptr); + #endif + + #ifdef ENABLE_LIBDECOR + if(!using_libdecor) { + xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); + } else { + decor_context = libdecor_new(display, &decor::libdecor_error_listener); + } + #else xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - wl_seat_add_listener(seat, &wayland::seat_listener, this); - + #endif + kb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + wl_display_roundtrip(display); + // Setup the keymap with XKB codes, which are basically the same as the X11 codes mapKeys[XKB_KEY_NoSymbol] = Key::NONE; @@ -11326,8 +11427,17 @@ namespace olc::host for (auto& itr : mapUID2Window) { auto& wayland_window = itr.second; wl_egl_window_destroy(wayland_window.window); - xdg_toplevel_destroy(wayland_window.toplevel); - xdg_surface_destroy(wayland_window.surface_xdg); + if(wayland_window.toplevel) { + xdg_toplevel_destroy(wayland_window.toplevel); + } + if(wayland_window.surface_xdg) { + xdg_surface_destroy(wayland_window.surface_xdg); + } + #ifdef ENABLE_LIBDECOR + if(wayland_window.decor_frame) { + libdecor_frame_close(wayland_window.decor_frame); + } + #endif wl_surface_destroy(wayland_window.surface); } @@ -11376,7 +11486,20 @@ namespace olc::host } }); + #if !defined(ENABLE_LIBDECOR) while(systemActive && wl_display_dispatch_pending(display) != -1) { } + #else + bool keep_running = true; + while(systemActive && keep_running) { + if(using_libdecor) { + if(decor_context) { + keep_running = libdecor_dispatch(decor_context, 0) >= 0; + } + } else { + keep_running = wl_display_dispatch_pending(display) != -1; + } + } + #endif systemActive = false; if(threadSystem.joinable()) @@ -11419,27 +11542,45 @@ namespace olc::host wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); w.surface = wl_compositor_create_surface(compositor); - w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); + + #ifdef ENABLE_LIBDECOR + if(!using_libdecor) { + #endif + w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); + + xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); + w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); + xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); + xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); + + #ifdef ENABLE_DECORATION_PROTOCOL + w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); + zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); + zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); + #endif + #ifdef ENABLE_LIBDECOR + } else { + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + libdecor_frame_map(w.decor_frame); + } + #endif - xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); - w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); - xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); - xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); wl_surface_set_opaque_region(w.surface, region); w.window = wl_egl_window_create(w.surface, vWindowSize.x, vWindowSize.y); w.olc_window_uid = pWindow->GetUID(); wl_surface_commit(w.surface); wl_region_destroy(region); - w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); - zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); - zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); - pWindow->SetWindowPosition(vWindowPos); pWindow->SetWindowSize(vWindowSize); mapUID2Window.insert_or_assign(pWindow->GetUID(), w); mapUID2OlcWindow.insert_or_assign(pWindow->GetUID(), pWindow); + return true; } @@ -11449,7 +11590,9 @@ namespace olc::host if(itr != mapUID2Window.end()) { auto& wayland_window = itr->second; wl_egl_window_destroy(wayland_window.window); + #ifdef ENABLE_DECORATION_PROTOCOL zxdg_toplevel_decoration_v1_destroy(wayland_window.decorations); + #endif xdg_toplevel_destroy(wayland_window.toplevel); xdg_surface_destroy(wayland_window.surface_xdg); wl_surface_destroy(wayland_window.surface); @@ -11464,7 +11607,15 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); + } else { + xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + } + #else xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + #endif } return true; } @@ -11541,9 +11692,25 @@ namespace olc::host if(itr != mapUID2Window.end()) { itr->second.fullscreen = bFullScreen; if(bFullScreen) { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); + } else { + xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); + } + #else xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); + #endif } else { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + libdecor_frame_unset_fullscreen(itr->second.decor_frame); + } else { + xdg_toplevel_unset_fullscreen(itr->second.toplevel); + } + #else xdg_toplevel_unset_fullscreen(itr->second.toplevel); + #endif } } return true; @@ -11559,10 +11726,13 @@ namespace olc::host } if(std::strcmp(interface, wl_seat_interface.name) == 0) { seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); + wl_seat_add_listener(seat, &wayland::seat_listener, this); } + #ifdef ENABLE_DECORATION_PROTOCOL if(std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { decoration_manager = static_cast(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, version)); } + #endif if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } @@ -12010,11 +12180,99 @@ namespace olc::host return; } + #ifdef ENABLE_DECORATION_PROTOCOL void Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode) { // auto* host = reinterpret_cast(data); // fprintf(stderr, "zxdg_decoration_manager_v1 mode %d\n", mode); } + #endif + + #ifdef ENABLE_LIBDECOR + void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) + { + std::cerr << "libdecor: " << error << ": " << message << "\n"; + } + + void Host_Linux_Wayland::libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data) + { + auto* host = reinterpret_cast(data); + host->libdecor_frame_configure(frame, config); + } + + void Host_Linux_Wayland::libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config) + { + for(auto& i : mapUID2Window) { + if(i.second.decor_frame == frame) { + auto* window = &i.second; + + int width{}; + int height{}; + + if(!libdecor_configuration_get_window_state(config, &window->decor_window_state)) { + window->decor_window_state = LIBDECOR_WINDOW_STATE_NONE; + } + + libdecor_configuration_get_content_size(config, frame, &width, &height); + + window->configured_width = width == 0 ? window->floating_width : width; + window->configured_height = height == 0 ? window->floating_height : height; + + libdecor_state* state = libdecor_state_new(window->configured_width, window->configured_height); + libdecor_frame_commit(frame, state, config); + libdecor_state_free(state); + + if(libdecor_frame_is_floating(frame)) { + window->floating_width = width; + window->floating_height = height; + } + + mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); + wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); + wl_surface_commit(window->surface); + } + } + } + + void Host_Linux_Wayland::libdecor_close_callback(libdecor_frame* frame, void* data) + { + auto* host = reinterpret_cast(data); + host->libdecor_close(frame); + } + + void Host_Linux_Wayland::libdecor_close(libdecor_frame* frame) + { + for(auto& i : mapUID2Window) { + if(i.second.decor_frame == frame) { + auto itr = mapUID2OlcWindow.find(i.second.olc_window_uid); + if (itr != mapUID2OlcWindow.end()) { + auto* ptr = itr->second; + ptr->olc_OnWindowClose(); + } + } + } + } + + void Host_Linux_Wayland::libdecor_commit_callback(libdecor_frame* frame, void* data) + { + auto* host = reinterpret_cast(data); + host->libdecor_commit(frame); + } + + void Host_Linux_Wayland::libdecor_commit(libdecor_frame* frame) + { + for(auto& i : mapUID2Window) { + if(i.second.decor_frame == frame) { + wl_surface_commit(i.second.surface); + } + } + } + + void Host_Linux_Wayland::libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data) + { + + } + #endif std::vector Host_Linux_Wayland::GetHostWindowDescriptor(olc::Window* pWindow) { From e38649016f01bb3554f30caaac9ddeaa19072d2d Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:28:17 -0600 Subject: [PATCH 19/41] Change cursor shape protocol for wayland-cursor --- dev/src/host_lin_wayland.cpp | 26 +++++++++++++++++------- dev/src/host_lin_wayland.h | 12 +++++++----- examples/CMakeLists.txt | 4 ++-- olcPixelGameEngine3.h | 38 ++++++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 13e02a35..13326e60 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -113,7 +113,19 @@ namespace olc::host #else xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); #endif - + + // Load the default cursor + cursor_theme = wl_cursor_theme_load(NULL, 24, shm); + wl_cursor *cursor = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr"); + + cursor_image = cursor->images[0]; + wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor_image); + + cursor_surface = wl_compositor_create_surface(compositor); + wl_surface_attach(cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(cursor_surface); + + kb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wl_display_roundtrip(display); @@ -200,6 +212,7 @@ namespace olc::host xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); xkb_context_unref(kb_context); + wl_cursor_theme_destroy(cursor_theme); wl_display_disconnect(display); } @@ -433,7 +446,7 @@ namespace olc::host itr->second.cursor_visible = bVisible; if(bVisible) { - wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + wl_pointer_set_cursor(pointer, enter_serial, cursor_surface, cursor_image->hotspot_x, cursor_image->hotspot_y); } else { wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); } @@ -477,6 +490,9 @@ namespace olc::host if(std::strcmp(interface, wl_compositor_interface.name) == 0) { compositor = static_cast(wl_registry_bind(registry, name, &wl_compositor_interface, version)); } + if(std::strcmp(interface, wl_shm_interface.name) == 0) { + shm = static_cast(wl_registry_bind(registry, name, &wl_shm_interface, version)); + } if(std::strcmp(interface, xdg_wm_base_interface.name) == 0) { xdg_wm = static_cast(wl_registry_bind(registry, name, &xdg_wm_base_interface, version)); } @@ -495,9 +511,6 @@ namespace olc::host if(std::strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { pointer_warp = static_cast(wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, version)); } - if(std::strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { - cursor_shape_manager = static_cast(wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version)); - } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -509,7 +522,6 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); - cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(cursor_shape_manager, pointer); wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } @@ -681,7 +693,7 @@ namespace olc::host // Need to set the mouse back to the correct hidden / not hidden state when it enters the window if(itr.second.cursor_visible) { - wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + wl_pointer_set_cursor(pointer, enter_serial, cursor_surface, cursor_image->hotspot_x, cursor_image->hotspot_y); } else { wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); } diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 3667ac06..21746c21 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -29,16 +29,16 @@ #endif #include +#include #include #include "xdg-shell.h" -// Only include the decoration protcol if we are not forcing libdecor +// Only include the decoration protocol if we are not forcing libdecor #ifdef ENABLE_DECORATION_PROTOCOL #include "xdg-decoration.h" #endif #include "pointer-warp.h" -#include "cursor-shape.h" #include #include #include @@ -123,6 +123,7 @@ namespace olc::host private: wl_display* display{nullptr}; wl_registry* registry{nullptr}; + wl_shm* shm{nullptr}; wl_compositor* compositor{nullptr}; wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; @@ -138,11 +139,12 @@ namespace olc::host #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; - wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; - wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; wayland::PointerState pointer_state; - + wl_surface* cursor_surface{nullptr}; + wl_cursor_image* cursor_image{nullptr}; + wl_cursor_theme* cursor_theme{nullptr}; + size_t active_window_id; #ifdef ENABLE_LIBDECOR diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b958ce50..b81dc40c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -27,6 +27,7 @@ if(UNIX AND BUILD_WAYLAND) pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) pkg_check_modules(EGL REQUIRED egl) + pkg_check_modules(WAYLAND_CURSOR REQUIRED wayland-cursor) if(NOT DISABLE_LIBDECOR) pkg_check_modules(LIBDECOR REQUIRED IMPORTED_TARGET libdecor-0) @@ -68,7 +69,6 @@ if(UNIX AND BUILD_WAYLAND) generate_wayland_protocol("xdg-shell" "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") generate_wayland_protocol("pointer-warp" "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") - generate_wayland_protocol("cursor-shape" "/usr/share/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml") generate_wayland_protocol("tablet" "/usr/share/wayland-protocols/stable/tablet/tablet-v2.xml") # Create a library for the wayland protocol files @@ -76,7 +76,6 @@ if(UNIX AND BUILD_WAYLAND) target_link_libraries(wayland_protocols INTERFACE wayland_xdg-shell wayland_pointer-warp - wayland_cursor-shape wayland_tablet ) @@ -129,6 +128,7 @@ foreach(source ${SOURCES}) ${XKBCOMMON_LIBRARIES} ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} + ${WAYLAND_CURSOR_LIBRARIES} ${EGL_LIBRARIES} OpenGL::GL PNG::PNG diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index a7963847..e78083a1 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5645,16 +5645,16 @@ namespace olc::host #endif #include +#include #include #include "xdg-shell.h" -// Only include the decoration protcol if we are not forcing libdecor +// Only include the decoration protocol if we are not forcing libdecor #ifdef ENABLE_DECORATION_PROTOCOL #include "xdg-decoration.h" #endif #include "pointer-warp.h" -#include "cursor-shape.h" #include #include #include @@ -5739,6 +5739,7 @@ namespace olc::host private: wl_display* display{nullptr}; wl_registry* registry{nullptr}; + wl_shm* shm{nullptr}; wl_compositor* compositor{nullptr}; wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; @@ -5754,11 +5755,12 @@ namespace olc::host #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; - wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; - wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; wayland::PointerState pointer_state; - + wl_surface* cursor_surface{nullptr}; + wl_cursor_image* cursor_image{nullptr}; + wl_cursor_theme* cursor_theme{nullptr}; + size_t active_window_id; #ifdef ENABLE_LIBDECOR @@ -11357,7 +11359,19 @@ namespace olc::host #else xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); #endif - + + // Load the default cursor + cursor_theme = wl_cursor_theme_load(NULL, 24, shm); + wl_cursor *cursor = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr"); + + cursor_image = cursor->images[0]; + wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor_image); + + cursor_surface = wl_compositor_create_surface(compositor); + wl_surface_attach(cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(cursor_surface); + + kb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wl_display_roundtrip(display); @@ -11444,6 +11458,7 @@ namespace olc::host xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); xkb_context_unref(kb_context); + wl_cursor_theme_destroy(cursor_theme); wl_display_disconnect(display); } @@ -11677,7 +11692,7 @@ namespace olc::host itr->second.cursor_visible = bVisible; if(bVisible) { - wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + wl_pointer_set_cursor(pointer, enter_serial, cursor_surface, cursor_image->hotspot_x, cursor_image->hotspot_y); } else { wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); } @@ -11721,6 +11736,9 @@ namespace olc::host if(std::strcmp(interface, wl_compositor_interface.name) == 0) { compositor = static_cast(wl_registry_bind(registry, name, &wl_compositor_interface, version)); } + if(std::strcmp(interface, wl_shm_interface.name) == 0) { + shm = static_cast(wl_registry_bind(registry, name, &wl_shm_interface, version)); + } if(std::strcmp(interface, xdg_wm_base_interface.name) == 0) { xdg_wm = static_cast(wl_registry_bind(registry, name, &xdg_wm_base_interface, version)); } @@ -11739,9 +11757,6 @@ namespace olc::host if(std::strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { pointer_warp = static_cast(wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, version)); } - if(std::strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { - cursor_shape_manager = static_cast(wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version)); - } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -11753,7 +11768,6 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); - cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(cursor_shape_manager, pointer); wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } @@ -11925,7 +11939,7 @@ namespace olc::host // Need to set the mouse back to the correct hidden / not hidden state when it enters the window if(itr.second.cursor_visible) { - wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + wl_pointer_set_cursor(pointer, enter_serial, cursor_surface, cursor_image->hotspot_x, cursor_image->hotspot_y); } else { wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); } From c6035c5692c6ad0fa5ef515da9c3517e51ef71c6 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Sun, 8 Mar 2026 11:30:49 +0000 Subject: [PATCH 20/41] 228-correct-return-false-from-onuserupdate-causing-cross-threading --- dev/src/api_macos.cpp | 14 +++++++++----- dev/src/host_apple_macos.cpp | 5 +++++ dev/src/host_apple_macos.h | 3 +++ olcPixelGameEngine3.h | 21 ++++++++++++++++----- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index fe6df724..22b0052c 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -1,6 +1,7 @@ #include "api_macos.h" #include #include +#include //! START IMPLEMENTATION @@ -1271,11 +1272,14 @@ void windowDidResize(id self, SEL _cmd, id notification) { // handle window will close events void windowWillClose(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; - gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window - gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window - if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { - gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); - } + dispatch_async(dispatch_get_main_queue(), ^{ + // Ensure all pending events are processed before closing the window + gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window + gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window + if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { + gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); + } + }); } // handle window did become key events diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index d9c0e813..ac8988b2 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -208,6 +208,11 @@ namespace olc::host { return true; } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) +{ + return false; // Fullscreen is currently not supported on MacOS Host + } + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; return true; diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 15d75c09..5abb5e69 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -62,6 +62,9 @@ namespace olc virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + // Set a window to fullscreen or not fullscreen + virtual bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const override; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index b5eb44b3..5f4ac0de 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5463,6 +5463,9 @@ namespace olc virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + // Set a window to fullscreen or not fullscreen + virtual bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const override; @@ -7882,6 +7885,11 @@ namespace olc::host { return true; } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) +{ + return false; // Fullscreen is currently not supported on MacOS Host + } + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; return true; @@ -9641,11 +9649,14 @@ void windowDidResize(id self, SEL _cmd, id notification) { // handle window will close events void windowWillClose(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; - gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window - gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window - if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { - gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); - } + dispatch_async(dispatch_get_main_queue(), ^{ + // Ensure all pending events are processed before closing the window + gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window + gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window + if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { + gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); + } + }); } // handle window did become key events From 08db92dd90a234a2cb5e8314656346341db0949a Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 8 Mar 2026 10:10:31 -0500 Subject: [PATCH 21/41] Init some nullptrs --- dev/src/host_lin_wayland.h | 4 ++-- olcPixelGameEngine3.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 21746c21..7678d1bb 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -131,7 +131,7 @@ namespace olc::host uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; - xkb_keymap* kb_keymap; + xkb_keymap* kb_keymap{nullptr}; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; #ifdef ENABLE_DECORATION_PROTOCOL @@ -150,7 +150,7 @@ namespace olc::host #ifdef ENABLE_LIBDECOR // libdecor support bool using_libdecor{false}; - libdecor* decor_context; + libdecor* decor_context{nullptr}; #endif public: diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e78083a1..0b592351 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5747,7 +5747,7 @@ namespace olc::host uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; - xkb_keymap* kb_keymap; + xkb_keymap* kb_keymap{nullptr}; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; #ifdef ENABLE_DECORATION_PROTOCOL @@ -5766,7 +5766,7 @@ namespace olc::host #ifdef ENABLE_LIBDECOR // libdecor support bool using_libdecor{false}; - libdecor* decor_context; + libdecor* decor_context{nullptr}; #endif public: From c8bc9b061f93f08b3edbc513eb360178b6c2ae1d Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:38:40 +0000 Subject: [PATCH 22/41] Add MacOS Windows Furniture and removed two debug lines from host_win.winapi.cpp --- dev/src/api_macos.cpp | 26 ++++++-- dev/src/api_macos.h | 4 +- dev/src/api_macos_wrapper.hpp | 17 ++++- dev/src/host_apple_macos.cpp | 67 +++++++++++++++++-- dev/src/host_apple_macos.h | 4 ++ dev/src/host_win_winapi.cpp | 3 - olcPixelGameEngine3.h | 118 ++++++++++++++++++++++++++++++---- 7 files changed, 210 insertions(+), 29 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index fe6df724..b08f20c5 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -60,6 +60,8 @@ static constexpr const char* kMakeKeyWindowSel = "makeKeyWindow static constexpr const char* kFrameSel = "frame"; static constexpr const char* kSetFrameDisplaySel = "setFrame:display:"; static constexpr const char* kSetFrameSel = "setFrame:"; +static constexpr const char* kStyleMaskSel = "styleMask"; +static constexpr const char* kToggleFullScreenSel = "toggleFullScreen:"; // NSWindowDelegate lifecycle and event methods selectors static constexpr const char* kWindowDidResizeSel = "windowDidResize:"; @@ -217,6 +219,8 @@ namespace ObjectiveCSEL { static SEL setFrameDisplaySel = nullptr; static SEL setFrameSel = nullptr; static SEL makeFirstResponderSel = nullptr; + static SEL styleMaskSel = nullptr; + static SEL toggleFullScreenSel = nullptr; // NSWindowDelegate lifecycle and event methods selectors static SEL windowDidResizeSel = nullptr; @@ -345,7 +349,9 @@ namespace ObjectiveCSEL { frameSel = sel_registerName(kFrameSel); setFrameDisplaySel = sel_registerName(kSetFrameDisplaySel); setFrameSel = sel_registerName(kSetFrameSel); - + styleMaskSel = sel_registerName(kStyleMaskSel); + toggleFullScreenSel = sel_registerName(kToggleFullScreenSel); + // NSWindowDelegate lifecycle and event methods selectors windowDidResizeSel = sel_registerName(kWindowDidResizeSel); windowWillCloseSel = sel_registerName(kWindowWillCloseSel); @@ -570,6 +576,7 @@ static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindow static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); +static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); // enum for backing store types enum class NSBackingStoreType : uint8_t { @@ -736,7 +743,7 @@ struct Window { void* windowDidDeminiaturizeUserData{nullptr}; // User data for window did deminiaturize callback // Method function pointers with nullptr initialization - void (*create) (struct Window* self){nullptr}; + void (*create) (struct Window* self, unsigned long styleMask){nullptr}; void (*show) (struct Window* self){nullptr}; void (*destroy) (struct Window* self){nullptr}; void (*setDelegate) (struct Window* self, id delegate){nullptr}; @@ -1475,7 +1482,7 @@ extern "C" { } // Create the NSWindow instance - void window_create(Window* self) { + void window_create(Window* self, unsigned long styleMask) { ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized // Get classes using const strings @@ -1485,9 +1492,6 @@ extern "C" { // Create window id windowAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSWindowClass, ObjectiveCSEL::allocSel); - unsigned long styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; - self->nsWindow = ((id(*)(id, SEL, NSRect, unsigned long, unsigned long, BOOL))objc_msgSend)( windowAlloc, ObjectiveCSEL::initWithContentRectSel, self->windowFrame, styleMask, NSBackingStoreBuffered, NSWindowCreateNow); // Use modern buffered backing store, create immediately @@ -1727,6 +1731,16 @@ extern "C" { } } + void window_toggleFullScreen(Window* self) { + // Toggle fullscreen + ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); + } + + bool window_isFullScreen(Window* self) { + unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); + return (mask & NSWindowStyleMaskFullScreen); + } + // Initialize OpenGL renderer void opengl_initialize(OpenGLRenderer* self, Window* window) { self->window = window; diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index 50c47024..8cf6493d 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -56,7 +56,7 @@ extern "C" { // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); - void window_create (struct Window* self); + void window_create (struct Window* self, unsigned long styleMask); void window_show (struct Window* self); void window_destroy (struct Window* self); void window_setTitle (struct Window* self, const char* title); @@ -72,6 +72,8 @@ extern "C" { void window_setContentViewFrame (struct Window* self, double* x, double* y, double* width, double* height); void window_setCursorVisibility (struct Window* self, BOOL visible); void window_setCursorPosition (struct Window* self, double x, double y); + void window_toggleFullScreen (struct Window* self); + bool window_isFullScreen (struct Window* self); // OpenGL Renderer API - as implemented in api_macos.c struct OpenGLRenderer* opengl_init (void); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index 52be9a2b..7fb90b8a 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -220,10 +220,10 @@ namespace olc { struct ::Window* getCHandle() const noexcept { return window_; } // Create and show the window - void show() { + void show(unsigned long styleMask) { if (window_) { setTitle(title_); - window_create(window_); + window_create(window_, styleMask); window_show(window_); } } @@ -481,6 +481,19 @@ namespace olc { } } + void toggleFullScreen() noexcept { + if (window_) { + window_toggleFullScreen(window_); + } + } + + bool isFullScreen() noexcept { + if (window_) { + return window_isFullScreen(window_); + } + return false; + } + // Non-copyable but movable Window(const Window&) = delete; diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index d9c0e813..6f01fd3b 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -19,6 +19,22 @@ namespace olc::host { constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 + // enum for window appearance and behavior bit flags + enum class NSWindowStyleMask : uint16_t { + Titled = (1 << 0), // Window has a title bar + Closable = (1 << 1), // Window can be closed + Miniaturizable = (1 << 2), // Window can be minimized + Resizable = (1 << 3), // Window can be resized + UtilityWindow = (1 << 4), // Utility window style + DocModalWindow = (1 << 6), // Document-modal window + NonactivatingPanel = (1 << 7), // Non-activating panel + HUDWindow = (1 << 13), // Heads-up display window + TexturedBackground = (1 << 8), // Textured background + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view + }; + Host_Apple_MacOS::Host_Apple_MacOS() { @@ -208,6 +224,38 @@ namespace olc::host { return true; } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + // if we're already in the specified state, return early + if(pMacOSWindow->isFullScreen() == bFullScreen) + return true; + + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->toggleFullScreen(); + }); + return true; + } + + uint16_t Host_Apple_MacOS::ConvertPGE2WindowStyle() + { + uint16_t nsStyle = 0; + + // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); + + // For MacOS you can only disable the buttons, you can't hide them + if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window + if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border + if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button + if (pPrimaryPGE->config.bShowWindowMaximiseButton) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Add Max Button + if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button + + return nsStyle; + + } + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; return true; @@ -240,7 +288,8 @@ namespace olc::host { MacEventsHandler(); // Create the window - pMacOSWindow->show(); + unsigned long styleMask = ConvertPGE2WindowStyle(); + pMacOSWindow->show(styleMask); pMacOSEventHandler->enable(); //--- Start up our engine threading system ----- @@ -378,7 +427,7 @@ namespace olc::host { { // This method should only be called on the PGE thread, use AddPendingMainThreadTask(CREATE_OPENGL_RENDERER); to queue it if needed if(pMacOSOpenGLRenderer == nullptr) - { + { vMacOSWindowDescriptors.clear(); // ensure we are starting fresh pMacOSOpenGLRenderer = std::make_shared(); @@ -400,7 +449,13 @@ namespace olc::host { // Set up OpenGL renderer for visual feedback pMacOSOpenGLRenderer->makeCurrentContext(); - } + + // Finally we set full screen if needed to ensure all out OpenGL setup is done before toggling full screen, + if(pPrimaryPGE->config.bFullScreen){ + SetFullScreen(pPGEwindow, true); + } + + } return true; } @@ -528,9 +583,13 @@ namespace olc::host { 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([&]() { }); + pMacApplication->setWillTerminateCallback([&]() { + //todo : add any cleanup code here if needed + }); pMacApplication->setDidBecomeActiveCallback([]() { }); diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 15d75c09..303ffd10 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -62,6 +62,8 @@ namespace olc virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + // Set a window to fullscreen or not fullscreen + virtual bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const override; @@ -146,6 +148,8 @@ namespace olc void MacOpenGLContextEventsHandler(); void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start + uint16_t ConvertPGE2WindowStyle(); + }; } diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index d41a7b2d..d7cc315d 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -418,9 +418,6 @@ namespace olc::host } else { - olc::vi2d vWinPos = pPrimaryPGE->config.vWindowOffset; - olc::vi2d vWinSize = pPrimaryPGE->config.vScreenSize * pPrimaryPGE->config.vPixelSize; - // Restore original window style and position DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Get the style we should have based on the window config diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 60d637b0..8a89d0f4 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4376,7 +4376,7 @@ extern "C" { // Window API - as implemented in api_macos.c struct Window* window_init (double x, double y, double width, double height); - void window_create (struct Window* self); + void window_create (struct Window* self, unsigned long styleMask); void window_show (struct Window* self); void window_destroy (struct Window* self); void window_setTitle (struct Window* self, const char* title); @@ -4392,6 +4392,8 @@ extern "C" { void window_setContentViewFrame (struct Window* self, double* x, double* y, double* width, double* height); void window_setCursorVisibility (struct Window* self, BOOL visible); void window_setCursorPosition (struct Window* self, double x, double y); + void window_toggleFullScreen (struct Window* self); + bool window_isFullScreen (struct Window* self); // OpenGL Renderer API - as implemented in api_macos.c struct OpenGLRenderer* opengl_init (void); @@ -4676,10 +4678,10 @@ namespace olc { struct ::Window* getCHandle() const noexcept { return window_; } // Create and show the window - void show() { + void show(unsigned long styleMask) { if (window_) { setTitle(title_); - window_create(window_); + window_create(window_, styleMask); window_show(window_); } } @@ -4937,6 +4939,19 @@ namespace olc { } } + void toggleFullScreen() noexcept { + if (window_) { + window_toggleFullScreen(window_); + } + } + + bool isFullScreen() noexcept { + if (window_) { + return window_isFullScreen(window_); + } + return false; + } + // Non-copyable but movable Window(const Window&) = delete; @@ -5474,6 +5489,8 @@ namespace olc virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + // Set a window to fullscreen or not fullscreen + virtual bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const override; @@ -5558,6 +5575,8 @@ namespace olc void MacOpenGLContextEventsHandler(); void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start + uint16_t ConvertPGE2WindowStyle(); + }; } @@ -7747,6 +7766,22 @@ namespace olc::host { constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 + // enum for window appearance and behavior bit flags + enum class NSWindowStyleMask : uint16_t { + Titled = (1 << 0), // Window has a title bar + Closable = (1 << 1), // Window can be closed + Miniaturizable = (1 << 2), // Window can be minimized + Resizable = (1 << 3), // Window can be resized + UtilityWindow = (1 << 4), // Utility window style + DocModalWindow = (1 << 6), // Document-modal window + NonactivatingPanel = (1 << 7), // Non-activating panel + HUDWindow = (1 << 13), // Heads-up display window + TexturedBackground = (1 << 8), // Textured background + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view + }; + Host_Apple_MacOS::Host_Apple_MacOS() { @@ -7936,6 +7971,38 @@ namespace olc::host { return true; } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + // if we're already in the specified state, return early + if(pMacOSWindow->isFullScreen() == bFullScreen) + return true; + + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->toggleFullScreen(); + }); + return true; + } + + uint16_t Host_Apple_MacOS::ConvertPGE2WindowStyle() + { + uint16_t nsStyle = 0; + + // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); + + // For MacOS you can only disable the buttons, you can't hide them + if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window + if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border + if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button + if (pPrimaryPGE->config.bShowWindowMaximiseButton) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Add Max Button + if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button + + return nsStyle; + + } + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; return true; @@ -7968,7 +8035,8 @@ namespace olc::host { MacEventsHandler(); // Create the window - pMacOSWindow->show(); + unsigned long styleMask = ConvertPGE2WindowStyle(); + pMacOSWindow->show(styleMask); pMacOSEventHandler->enable(); //--- Start up our engine threading system ----- @@ -8106,7 +8174,7 @@ namespace olc::host { { // This method should only be called on the PGE thread, use AddPendingMainThreadTask(CREATE_OPENGL_RENDERER); to queue it if needed if(pMacOSOpenGLRenderer == nullptr) - { + { vMacOSWindowDescriptors.clear(); // ensure we are starting fresh pMacOSOpenGLRenderer = std::make_shared(); @@ -8128,7 +8196,13 @@ namespace olc::host { // Set up OpenGL renderer for visual feedback pMacOSOpenGLRenderer->makeCurrentContext(); - } + + // Finally we set full screen if needed to ensure all out OpenGL setup is done before toggling full screen, + if(pPrimaryPGE->config.bFullScreen){ + SetFullScreen(pPGEwindow, true); + } + + } return true; } @@ -8256,9 +8330,13 @@ namespace olc::host { 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([&]() { }); + pMacApplication->setWillTerminateCallback([&]() { + //todo : add any cleanup code here if needed + }); pMacApplication->setDidBecomeActiveCallback([]() { }); @@ -8484,6 +8562,8 @@ static constexpr const char* kMakeKeyWindowSel = "makeKeyWindow static constexpr const char* kFrameSel = "frame"; static constexpr const char* kSetFrameDisplaySel = "setFrame:display:"; static constexpr const char* kSetFrameSel = "setFrame:"; +static constexpr const char* kStyleMaskSel = "styleMask"; +static constexpr const char* kToggleFullScreenSel = "toggleFullScreen:"; // NSWindowDelegate lifecycle and event methods selectors static constexpr const char* kWindowDidResizeSel = "windowDidResize:"; @@ -8641,6 +8721,8 @@ namespace ObjectiveCSEL { static SEL setFrameDisplaySel = nullptr; static SEL setFrameSel = nullptr; static SEL makeFirstResponderSel = nullptr; + static SEL styleMaskSel = nullptr; + static SEL toggleFullScreenSel = nullptr; // NSWindowDelegate lifecycle and event methods selectors static SEL windowDidResizeSel = nullptr; @@ -8769,7 +8851,9 @@ namespace ObjectiveCSEL { frameSel = sel_registerName(kFrameSel); setFrameDisplaySel = sel_registerName(kSetFrameDisplaySel); setFrameSel = sel_registerName(kSetFrameSel); - + styleMaskSel = sel_registerName(kStyleMaskSel); + toggleFullScreenSel = sel_registerName(kToggleFullScreenSel); + // NSWindowDelegate lifecycle and event methods selectors windowDidResizeSel = sel_registerName(kWindowDidResizeSel); windowWillCloseSel = sel_registerName(kWindowWillCloseSel); @@ -8994,6 +9078,7 @@ static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindow static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); +static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); // enum for backing store types enum class NSBackingStoreType : uint8_t { @@ -9160,7 +9245,7 @@ struct Window { void* windowDidDeminiaturizeUserData{nullptr}; // User data for window did deminiaturize callback // Method function pointers with nullptr initialization - void (*create) (struct Window* self){nullptr}; + void (*create) (struct Window* self, unsigned long styleMask){nullptr}; void (*show) (struct Window* self){nullptr}; void (*destroy) (struct Window* self){nullptr}; void (*setDelegate) (struct Window* self, id delegate){nullptr}; @@ -9899,7 +9984,7 @@ extern "C" { } // Create the NSWindow instance - void window_create(Window* self) { + void window_create(Window* self, unsigned long styleMask) { ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized // Get classes using const strings @@ -9909,9 +9994,6 @@ extern "C" { // Create window id windowAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSWindowClass, ObjectiveCSEL::allocSel); - unsigned long styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; - self->nsWindow = ((id(*)(id, SEL, NSRect, unsigned long, unsigned long, BOOL))objc_msgSend)( windowAlloc, ObjectiveCSEL::initWithContentRectSel, self->windowFrame, styleMask, NSBackingStoreBuffered, NSWindowCreateNow); // Use modern buffered backing store, create immediately @@ -10151,6 +10233,16 @@ extern "C" { } } + void window_toggleFullScreen(Window* self) { + // Toggle fullscreen + ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); + } + + bool window_isFullScreen(Window* self) { + unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); + return (mask & NSWindowStyleMaskFullScreen); + } + // Initialize OpenGL renderer void opengl_initialize(OpenGLRenderer* self, Window* window) { self->window = window; From e88199901995d3b9f316b4e4e0e7b8735c9bd321 Mon Sep 17 00:00:00 2001 From: DCubix Date: Sun, 8 Mar 2026 13:23:17 -0400 Subject: [PATCH 23/41] add olc_OnFocus --- dev/src/host_android.cpp | 2 ++ olcPixelGameEngine3.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev/src/host_android.cpp b/dev/src/host_android.cpp index 30195157..45e55e5b 100644 --- a/dev/src/host_android.cpp +++ b/dev/src/host_android.cpp @@ -261,9 +261,11 @@ namespace olc::host } break; case APP_CMD_GAINED_FOCUS: { LOGD("APP_CMD_GAINED_FOCUS received"); + host->pgeWindow->olc_OnFocus(true); } break; case APP_CMD_LOST_FOCUS: { LOGD("APP_CMD_LOST_FOCUS received"); + host->pgeWindow->olc_OnFocus(false); } break; default: break; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 35bc715a..8de07935 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -12954,9 +12954,11 @@ namespace olc::host } break; case APP_CMD_GAINED_FOCUS: { LOGD("APP_CMD_GAINED_FOCUS received"); + host->pgeWindow->olc_OnFocus(true); } break; case APP_CMD_LOST_FOCUS: { LOGD("APP_CMD_LOST_FOCUS received"); + host->pgeWindow->olc_OnFocus(false); } break; default: break; } From 4e6bd021938d20e19fde0631dc7a7e2f3cd209df Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:52:36 -0500 Subject: [PATCH 24/41] Fix libdecor threading and a bunch of unreleased pointers --- dev/src/host_lin_wayland.cpp | 84 +++++++++++++++++++++------------- dev/src/host_lin_wayland.h | 3 +- olcPixelGameEngine3.h | 87 ++++++++++++++++++++++-------------- 3 files changed, 108 insertions(+), 66 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 13326e60..24bc31d1 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -190,30 +190,59 @@ namespace olc::host UpdateKeyboardLayout(); } + WaylandWindow::~WaylandWindow() { + if(window) { + wl_egl_window_destroy(window); + } + if(toplevel) { + xdg_toplevel_destroy(toplevel); + } + if(surface_xdg) { + xdg_surface_destroy(surface_xdg); + } + #ifdef ENABLE_DECORATION_PROTOCOL + zxdg_toplevel_decoration_v1_destroy(decorations); + #endif + #ifdef ENABLE_LIBDECOR + if(decor_frame) { + libdecor_frame_unref(decor_frame); + } + #endif + wl_surface_destroy(surface); + } + Host_Linux_Wayland::~Host_Linux_Wayland() { - for (auto& itr : mapUID2Window) { - auto& wayland_window = itr.second; - wl_egl_window_destroy(wayland_window.window); - if(wayland_window.toplevel) { - xdg_toplevel_destroy(wayland_window.toplevel); - } - if(wayland_window.surface_xdg) { - xdg_surface_destroy(wayland_window.surface_xdg); - } - #ifdef ENABLE_LIBDECOR - if(wayland_window.decor_frame) { - libdecor_frame_close(wayland_window.decor_frame); - } - #endif - wl_surface_destroy(wayland_window.surface); + mapUID2OlcWindow.clear(); + mapUID2Window.clear(); + + #ifdef ENABLE_DECORATION_PROTOCOL + zxdg_decoration_manager_v1_destroy(decoration_manager); + #endif + #ifdef ENABLE_LIBDECOR + if(decor_context) { + libdecor_unref(decor_context); + decor_context = nullptr; } + #endif xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); xkb_context_unref(kb_context); wl_cursor_theme_destroy(cursor_theme); + wl_surface_destroy(cursor_surface); + xdg_wm_base_destroy(xdg_wm); + if(pointer_warp) + { + wp_pointer_warp_v1_destroy(pointer_warp); + } + wl_keyboard_destroy(keyboard); + wl_pointer_destroy(pointer); + wl_seat_destroy(seat); + wl_compositor_destroy(compositor); + wl_shm_destroy(shm); + wl_registry_destroy(registry); wl_display_disconnect(display); } @@ -262,6 +291,7 @@ namespace olc::host while(systemActive && keep_running) { if(using_libdecor) { if(decor_context) { + std::lock_guard l{decor_mutex}; keep_running = libdecor_dispatch(decor_context, 0) >= 0; } } else { @@ -306,7 +336,7 @@ namespace olc::host bool Host_Linux_Wayland::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { // Create a window - WaylandWindow w; + WaylandWindow& w = mapUID2Window[pWindow->GetUID()]; wl_region* region = wl_compositor_create_region(compositor); wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); @@ -329,6 +359,7 @@ namespace olc::host #endif #ifdef ENABLE_LIBDECOR } else { + std::lock_guard l{decor_mutex}; w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); w.floating_width = vWindowSize.x; w.floating_height = vWindowSize.y; @@ -347,7 +378,6 @@ namespace olc::host pWindow->SetWindowPosition(vWindowPos); pWindow->SetWindowSize(vWindowSize); - mapUID2Window.insert_or_assign(pWindow->GetUID(), w); mapUID2OlcWindow.insert_or_assign(pWindow->GetUID(), pWindow); return true; @@ -355,20 +385,9 @@ namespace olc::host bool Host_Linux_Wayland::CloseWindowFrame(olc::Window* pWindow) { - auto itr = mapUID2Window.find(pWindow->GetUID()); - if(itr != mapUID2Window.end()) { - auto& wayland_window = itr->second; - wl_egl_window_destroy(wayland_window.window); - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1_destroy(wayland_window.decorations); - #endif - xdg_toplevel_destroy(wayland_window.toplevel); - xdg_surface_destroy(wayland_window.surface_xdg); - wl_surface_destroy(wayland_window.surface); - auto uid = wayland_window.olc_window_uid; - mapUID2Window.erase(uid); - mapUID2OlcWindow.erase(uid); - } + const auto uid = pWindow->GetUID(); + mapUID2Window.erase(uid); + mapUID2OlcWindow.erase(uid); return true; } @@ -378,6 +397,7 @@ namespace olc::host if(itr != mapUID2Window.end()) { #ifdef ENABLE_LIBDECOR if(using_libdecor) { + std::lock_guard l{decor_mutex}; libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); } else { xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); @@ -522,7 +542,7 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 7678d1bb..b7286027 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -76,13 +76,13 @@ namespace olc::host #ifdef ENABLE_LIBDECOR // libdecor support libdecor_frame* decor_frame{nullptr}; - libdecor_frame_interface* decor_interface{nullptr}; int configured_width{}; int configured_height{}; libdecor_window_state decor_window_state; int floating_width{}; int floating_height{}; #endif + ~WaylandWindow(); }; namespace wayland { @@ -151,6 +151,7 @@ namespace olc::host // libdecor support bool using_libdecor{false}; libdecor* decor_context{nullptr}; + std::mutex decor_mutex; #endif public: diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 0b592351..1f253301 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5692,13 +5692,13 @@ namespace olc::host #ifdef ENABLE_LIBDECOR // libdecor support libdecor_frame* decor_frame{nullptr}; - libdecor_frame_interface* decor_interface{nullptr}; int configured_width{}; int configured_height{}; libdecor_window_state decor_window_state; int floating_width{}; int floating_height{}; #endif + ~WaylandWindow(); }; namespace wayland { @@ -5767,6 +5767,7 @@ namespace olc::host // libdecor support bool using_libdecor{false}; libdecor* decor_context{nullptr}; + std::mutex decor_mutex; #endif public: @@ -11436,30 +11437,59 @@ namespace olc::host UpdateKeyboardLayout(); } + WaylandWindow::~WaylandWindow() { + if(window) { + wl_egl_window_destroy(window); + } + if(toplevel) { + xdg_toplevel_destroy(toplevel); + } + if(surface_xdg) { + xdg_surface_destroy(surface_xdg); + } + #ifdef ENABLE_DECORATION_PROTOCOL + zxdg_toplevel_decoration_v1_destroy(decorations); + #endif + #ifdef ENABLE_LIBDECOR + if(decor_frame) { + libdecor_frame_unref(decor_frame); + } + #endif + wl_surface_destroy(surface); + } + Host_Linux_Wayland::~Host_Linux_Wayland() { - for (auto& itr : mapUID2Window) { - auto& wayland_window = itr.second; - wl_egl_window_destroy(wayland_window.window); - if(wayland_window.toplevel) { - xdg_toplevel_destroy(wayland_window.toplevel); - } - if(wayland_window.surface_xdg) { - xdg_surface_destroy(wayland_window.surface_xdg); - } - #ifdef ENABLE_LIBDECOR - if(wayland_window.decor_frame) { - libdecor_frame_close(wayland_window.decor_frame); - } - #endif - wl_surface_destroy(wayland_window.surface); + mapUID2OlcWindow.clear(); + mapUID2Window.clear(); + + #ifdef ENABLE_DECORATION_PROTOCOL + zxdg_decoration_manager_v1_destroy(decoration_manager); + #endif + #ifdef ENABLE_LIBDECOR + if(decor_context) { + libdecor_unref(decor_context); + decor_context = nullptr; } + #endif xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); xkb_context_unref(kb_context); wl_cursor_theme_destroy(cursor_theme); + wl_surface_destroy(cursor_surface); + xdg_wm_base_destroy(xdg_wm); + if(pointer_warp) + { + wp_pointer_warp_v1_destroy(pointer_warp); + } + wl_keyboard_destroy(keyboard); + wl_pointer_destroy(pointer); + wl_seat_destroy(seat); + wl_compositor_destroy(compositor); + wl_shm_destroy(shm); + wl_registry_destroy(registry); wl_display_disconnect(display); } @@ -11508,6 +11538,7 @@ namespace olc::host while(systemActive && keep_running) { if(using_libdecor) { if(decor_context) { + std::lock_guard l{decor_mutex}; keep_running = libdecor_dispatch(decor_context, 0) >= 0; } } else { @@ -11552,7 +11583,7 @@ namespace olc::host bool Host_Linux_Wayland::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { // Create a window - WaylandWindow w; + WaylandWindow& w = mapUID2Window[pWindow->GetUID()]; wl_region* region = wl_compositor_create_region(compositor); wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); @@ -11575,6 +11606,7 @@ namespace olc::host #endif #ifdef ENABLE_LIBDECOR } else { + std::lock_guard l{decor_mutex}; w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); w.floating_width = vWindowSize.x; w.floating_height = vWindowSize.y; @@ -11593,7 +11625,6 @@ namespace olc::host pWindow->SetWindowPosition(vWindowPos); pWindow->SetWindowSize(vWindowSize); - mapUID2Window.insert_or_assign(pWindow->GetUID(), w); mapUID2OlcWindow.insert_or_assign(pWindow->GetUID(), pWindow); return true; @@ -11601,20 +11632,9 @@ namespace olc::host bool Host_Linux_Wayland::CloseWindowFrame(olc::Window* pWindow) { - auto itr = mapUID2Window.find(pWindow->GetUID()); - if(itr != mapUID2Window.end()) { - auto& wayland_window = itr->second; - wl_egl_window_destroy(wayland_window.window); - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1_destroy(wayland_window.decorations); - #endif - xdg_toplevel_destroy(wayland_window.toplevel); - xdg_surface_destroy(wayland_window.surface_xdg); - wl_surface_destroy(wayland_window.surface); - auto uid = wayland_window.olc_window_uid; - mapUID2Window.erase(uid); - mapUID2OlcWindow.erase(uid); - } + const auto uid = pWindow->GetUID(); + mapUID2Window.erase(uid); + mapUID2OlcWindow.erase(uid); return true; } @@ -11624,6 +11644,7 @@ namespace olc::host if(itr != mapUID2Window.end()) { #ifdef ENABLE_LIBDECOR if(using_libdecor) { + std::lock_guard l{decor_mutex}; libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); } else { xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); @@ -11768,7 +11789,7 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { From 6a6d2ef1a78ba2a32dfe233d61971bf8abd212e2 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Mar 2026 09:27:14 -0500 Subject: [PATCH 25/41] Add window config struct --- dev/src/core.h | 14 +++++++------- dev/src/window.h | 12 ++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/dev/src/core.h b/dev/src/core.h index 81f9b757..99c2d6fb 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -31,7 +31,7 @@ namespace olc { // A grouping of all settable PGE properties - struct PGEConfig + struct PGEConfig : public WindowConfig { // Size of "screen" in PGE pixels olc::vi2d vScreenSize = { 256, 240 }; @@ -40,16 +40,16 @@ namespace olc // Top left location of shown main window olc::vi2d vWindowOffset = { 30,30 }; // Start in full-screen mode - bool bFullScreen = false; - // Allow full screen as an option with ALT-ENTER - bool bFullScreenable = true; - // Allow the window to be resized by user - bool bResizeable = true; + // bool bFullScreen = false; + // // Allow full screen as an option with ALT-ENTER + // bool bFullScreenable = true; + // // Allow the window to be resized by user + // bool bResizeable = true; // Synchronise rendering with monitor 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 + // Ensure aspect ratio of "screen" is maintained regardless of window size bool bRetainAspectRatio = true; // Force "screen" pixels to be integer in size bool bForceIntegerPixelSize = false; diff --git a/dev/src/window.h b/dev/src/window.h index bb35f5ac..fdae1852 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -44,6 +44,17 @@ namespace olc class Keyboard; } + struct WindowConfig + { + // Start in full-screen mode + bool bFullScreen = false; + // Allow full screen as an option with ALT-ENTER + bool bFullScreenable = true; + // Allow the window to be resized by user + bool bResizeable = true; + }; + + class Window { friend class olc::host::OLC_FRIENDLY_HOST; @@ -115,6 +126,7 @@ namespace olc protected: olc::host::Host* pHost = nullptr; + olc::WindowConfig config; protected: olc::hw::Mouse mouse; From f86cf1d03dab030259161636da24c2522a6dbeb9 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Mar 2026 10:53:54 -0500 Subject: [PATCH 26/41] Finish making certain configs per-window. --- dev/src/config.h | 2 +- dev/src/core.cpp | 6 +++++ dev/src/core.h | 4 +++ dev/src/window.cpp | 6 ++++- dev/src/window.h | 1 + olcPixelGameEngine3.h | 61 ++++++++++++++++++++++++++++++++++++------- 6 files changed, 69 insertions(+), 11 deletions(-) diff --git a/dev/src/config.h b/dev/src/config.h index b193a529..1e9e2945 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -136,7 +136,7 @@ #define OLC_MULTIWINDOW_YES 2 #if !defined(OLC_MULTIWINDOW) - #define OLC_MULTIWINDOW OLC_MULTIWINDOW_NO + #define OLC_MULTIWINDOW OLC_MULTIWINDOW_YES #endif diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 8c69277d..7ac6568a 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -64,6 +64,10 @@ namespace olc { } + PGEWindow::PGEWindow(const WindowConfig& config) : Window(config), draw() + { + } + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); @@ -378,6 +382,8 @@ namespace olc bool PixelGameEngine::Construct(const PGEConfig& cfg) { config = cfg; + // Also assign the window level config since that is what the Host will see + Window::config = cfg; // Check for constructor sAppName, if not set use Config sAppName if (sAppName.empty()) diff --git a/dev/src/core.h b/dev/src/core.h index 99c2d6fb..20b624c0 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -39,12 +39,15 @@ namespace olc olc::vi2d vPixelSize = { 4, 4 }; // Top left location of shown main window olc::vi2d vWindowOffset = { 30,30 }; + + // These three are inherited from WindowConfig // Start in full-screen mode // bool bFullScreen = false; // // Allow full screen as an option with ALT-ENTER // bool bFullScreenable = true; // // Allow the window to be resized by user // bool bResizeable = true; + // Synchronise rendering with monitor bool bVSync = OLC_DEFAULT_VSYNC; // Behave like a host window, resizing the screen in response to window resize @@ -68,6 +71,7 @@ namespace olc { public: PGEWindow(); + PGEWindow(const WindowConfig& config); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: diff --git a/dev/src/window.cpp b/dev/src/window.cpp index b8e7c728..466f7eca 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -10,7 +10,11 @@ namespace olc Window::Window() { nUniqueID = pgeguts::CreateUID(); - + } + + Window::Window(const WindowConfig& config) : config{config} + { + Window(); } Window::~Window() diff --git a/dev/src/window.h b/dev/src/window.h index fdae1852..05509090 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -62,6 +62,7 @@ namespace olc public: Window(); + Window(const WindowConfig& config); virtual ~Window(); void LinkToHost(olc::host::Host* host); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 243e2047..cd26bbbd 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -274,7 +274,7 @@ #define OLC_MULTIWINDOW_YES 2 #if !defined(OLC_MULTIWINDOW) - #define OLC_MULTIWINDOW OLC_MULTIWINDOW_NO + #define OLC_MULTIWINDOW OLC_MULTIWINDOW_YES #endif @@ -3734,6 +3734,17 @@ namespace olc class Keyboard; } + struct WindowConfig + { + // Start in full-screen mode + bool bFullScreen = false; + // Allow full screen as an option with ALT-ENTER + bool bFullScreenable = true; + // Allow the window to be resized by user + bool bResizeable = true; + }; + + class Window { friend class olc::host::OLC_FRIENDLY_HOST; @@ -3741,6 +3752,7 @@ namespace olc public: Window(); + Window(const WindowConfig& config); virtual ~Window(); void LinkToHost(olc::host::Host* host); @@ -3805,6 +3817,7 @@ namespace olc protected: olc::host::Host* pHost = nullptr; + olc::WindowConfig config; protected: olc::hw::Mouse mouse; @@ -3976,7 +3989,7 @@ namespace olc namespace olc { // A grouping of all settable PGE properties - struct PGEConfig + struct PGEConfig : public WindowConfig { // Size of "screen" in PGE pixels olc::vi2d vScreenSize = { 256, 240 }; @@ -3984,17 +3997,20 @@ namespace olc olc::vi2d vPixelSize = { 4, 4 }; // Top left location of shown main window olc::vi2d vWindowOffset = { 30,30 }; + + // These three are inherited from WindowConfig // Start in full-screen mode - bool bFullScreen = false; - // Allow full screen as an option with ALT-ENTER - bool bFullScreenable = true; - // Allow the window to be resized by user - bool bResizeable = true; + // bool bFullScreen = false; + // // Allow full screen as an option with ALT-ENTER + // bool bFullScreenable = true; + // // Allow the window to be resized by user + // bool bResizeable = true; + // Synchronise rendering with monitor 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 + // Ensure aspect ratio of "screen" is maintained regardless of window size bool bRetainAspectRatio = true; // Force "screen" pixels to be integer in size bool bForceIntegerPixelSize = false; @@ -4013,6 +4029,7 @@ namespace olc { public: PGEWindow(); + PGEWindow(const WindowConfig& config); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: @@ -5945,6 +5962,7 @@ namespace olc::host bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -12647,6 +12665,21 @@ namespace olc::host return true; } + + bool Host_Web_Emscripten::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + if(!mapUID2CanvasId.contains(pWindow->GetUID())) + return false; + + auto canvasID = mapUID2CanvasId.at(pWindow->GetUID()); + + if(bFullScreen) + emscripten_request_fullscreen(canvasID.c_str(), true); + else + emscripten_exit_fullscreen(); + + return true; + } olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { @@ -17342,6 +17375,10 @@ namespace olc { } + PGEWindow::PGEWindow(const WindowConfig& config) : Window(config), draw() + { + } + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); @@ -17656,6 +17693,8 @@ namespace olc bool PixelGameEngine::Construct(const PGEConfig& cfg) { config = cfg; + // Also assign the window level config since that is what the Host will see + Window::config = cfg; // Check for constructor sAppName, if not set use Config sAppName if (sAppName.empty()) @@ -18490,7 +18529,11 @@ namespace olc Window::Window() { nUniqueID = pgeguts::CreateUID(); - + } + + Window::Window(const WindowConfig& config) : config{config} + { + Window(); } Window::~Window() From 8b34d6fe8092369d3e213e0066cb2cbac8576460 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:29:02 -0500 Subject: [PATCH 27/41] Reset the default multiwindow mode --- dev/src/config.h | 2 +- olcPixelGameEngine3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/config.h b/dev/src/config.h index 1e9e2945..b193a529 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -136,7 +136,7 @@ #define OLC_MULTIWINDOW_YES 2 #if !defined(OLC_MULTIWINDOW) - #define OLC_MULTIWINDOW OLC_MULTIWINDOW_YES + #define OLC_MULTIWINDOW OLC_MULTIWINDOW_NO #endif diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index cd26bbbd..5e738a67 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -274,7 +274,7 @@ #define OLC_MULTIWINDOW_YES 2 #if !defined(OLC_MULTIWINDOW) - #define OLC_MULTIWINDOW OLC_MULTIWINDOW_YES + #define OLC_MULTIWINDOW OLC_MULTIWINDOW_NO #endif From 544bf15f3703f7eb5574a1a6ae2379c99e9c9b2f Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Mar 2026 11:38:43 -0500 Subject: [PATCH 28/41] Begin wayland implementation --- dev/src/host_lin_wayland.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 24bc31d1..4b4dc9d5 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -554,6 +554,34 @@ namespace olc::host void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) { + bool attempt_fullscreen {false}; + bool attempt_resize {false}; + + static const std::array resize_states { + xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN, + xdg_toplevel_state::XDG_TOPLEVEL_STATE_RESIZING, + xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_LEFT, + xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_RIGHT, + xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_TOP, + xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_BOTTOM + }; + + auto* state = reinterpret_cast(states->data); + auto* end = static_cast(states->data) + states->size; + for(;reinterpret_cast(state) < end; state++) + { + if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) + { + attempt_fullscreen = true; + } + + if(std::find(resize_states.begin(), resize_states.end(), *state) != resize_states.end()) + { + attempt_resize = true; + } + std::cout << *state << std::endl; + } + for(auto& i : mapUID2Window) { auto& w = i.second; if(w.toplevel == toplevel) { From dedb36108edd6bd71368c04970557baa3a515c4f Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Mar 2026 16:57:29 -0500 Subject: [PATCH 29/41] Finish wayland --- dev/src/host_lin_wayland.cpp | 73 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 4b4dc9d5..3e6f6db2 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -351,6 +351,10 @@ namespace olc::host w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); + if (!pWindow->config.bResizeable) { + xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); + xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); + } #ifdef ENABLE_DECORATION_PROTOCOL w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); @@ -365,7 +369,13 @@ namespace olc::host w.floating_height = vWindowSize.y; libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); + } + libdecor_frame_map(w.decor_frame); + } #endif @@ -479,7 +489,7 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { - itr->second.fullscreen = bFullScreen; + pWindow->bWindowIsFullscreen = bFullScreen; if(bFullScreen) { #ifdef ENABLE_LIBDECOR if(using_libdecor) { @@ -554,54 +564,42 @@ namespace olc::host void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) { - bool attempt_fullscreen {false}; - bool attempt_resize {false}; - - static const std::array resize_states { - xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN, - xdg_toplevel_state::XDG_TOPLEVEL_STATE_RESIZING, - xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_LEFT, - xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_RIGHT, - xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_TOP, - xdg_toplevel_state::XDG_TOPLEVEL_STATE_TILED_BOTTOM - }; + const auto& itr = std::find_if(mapUID2Window.begin(), mapUID2Window.end(), [=](const auto& w){return w.second.toplevel == toplevel;}); + if(itr == mapUID2Window.end()) + { + return; + } + + auto& [uid, window] = *itr; + const auto& olc_window = mapUID2OlcWindow.at(uid); + bool attempt_fullscreen {false}; auto* state = reinterpret_cast(states->data); auto* end = static_cast(states->data) + states->size; for(;reinterpret_cast(state) < end; state++) { + // The window.fullscreen is set any time the user commands via ShowFullscreen(), so we should allow this if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) { attempt_fullscreen = true; } + } - if(std::find(resize_states.begin(), resize_states.end(), *state) != resize_states.end()) - { - attempt_resize = true; + // If we're not going fullscreen, try to obey the bounds that have been configured by the compositor + if(!attempt_fullscreen) { + if(window.bounds_x != 0) { + width = std::min(width, window.bounds_x); } - std::cout << *state << std::endl; - } - for(auto& i : mapUID2Window) { - auto& w = i.second; - if(w.toplevel == toplevel) { - // Attempt to constrain the window size to what the compositor may have told us earlier - // in a bounds_configure message - if(!w.fullscreen) { - if(w.bounds_x != 0) { - width = std::min(width, w.bounds_x); - } - - if(w.bounds_y != 0) { - height = std::min(height, w.bounds_y); - } - } - - mapUID2OlcWindow[i.first]->olc_OnWindowSize({width, height}); - wl_egl_window_resize(i.second.window, width, height, 0, 0); - wl_surface_commit(i.second.surface); + if(window.bounds_y != 0) { + height = std::min(height, window.bounds_y); } } + + olc_window->bWindowIsFullscreen = attempt_fullscreen; + olc_window->olc_OnWindowSize({width, height}); + wl_egl_window_resize(window.window, width, height, 0, 0); + wl_surface_commit(window.surface); } void Host_Linux_Wayland::xdg_toplevel_close(xdg_toplevel* toplevel) @@ -1021,6 +1019,7 @@ namespace olc::host for(auto& i : mapUID2Window) { if(i.second.decor_frame == frame) { auto* window = &i.second; + auto& olc_window = mapUID2OlcWindow.at(i.first); int width{}; int height{}; @@ -1038,11 +1037,13 @@ namespace olc::host libdecor_frame_commit(frame, state, config); libdecor_state_free(state); - if(libdecor_frame_is_floating(frame)) { + // If we're not returning from fullscreen, goahead and resize + if(libdecor_frame_is_floating(frame) && !olc_window->bWindowIsFullscreen) { window->floating_width = width; window->floating_height = height; } + olc_window->bWindowIsFullscreen = (window->decor_window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); wl_surface_commit(window->surface); From f9370a4545a56627f9897fac08f5d1d8a841e986 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Mar 2026 16:57:51 -0500 Subject: [PATCH 30/41] Now do X11 --- dev/src/host_lin_x11.cpp | 57 +++++++++++++++-- dev/src/host_lin_x11.h | 1 + dev/src/window.h | 1 + examples/olcPGE3_Fish.cpp | 13 ++-- olcPixelGameEngine3.h | 127 +++++++++++++++++++++++++++++++------- 5 files changed, 166 insertions(+), 33 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index c28d7a24..6b1b715e 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -142,12 +142,50 @@ namespace olc::host if (xev.type == Expose) { - //auto* expose_event = reinterpret_cast(&xev); X11::XExposeEvent& e = xev.xexpose; + X11::Atom wm_state; + wm_state = X11::XInternAtom(olc_Display, "_NET_WM_STATE", True); + bool did_fullscreen = false; + + X11::Atom actual_type; + int actual_format; + unsigned long int num_items; + unsigned long int bytes; + unsigned char* property{nullptr}; + int res = X11::XGetWindowProperty(e.display, e.window, wm_state, + 0, + ~0, + False, + AnyPropertyType, + &actual_type, + &actual_format, + &num_items, + &bytes, + &property + ); + + if(res == Success) { + char* name; + if(X11::XGetAtomNames(e.display, (Atom*)property, num_items, &name)) + { + for(int i = 0; i < num_items; i++) + { + if(std::strcmp(name + i, "_NET_WM_STATE_FULLSCREEN") == 0) { + did_fullscreen = true; + } + } + } + } + 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(did_fullscreen && (!pge_window->config.bFullScreenable || !pge_window->config.bResizeable) && !pge_window->bWindowIsFullscreen) { + SetFullScreen(pge_window, false); + } else { + X11::XWindowAttributes gwa; + X11::XGetWindowAttributes(e.display, e.window, &gwa); + pge_window->olc_OnWindowSize(olc::vi2d{gwa.width, gwa.height}); + pge_window->bWindowIsFullscreen = did_fullscreen; + } } } else if (xev.type == ConfigureNotify) @@ -303,6 +341,16 @@ namespace olc::host X11::Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); X11::XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + if(!pWindow->config.bResizeable) + { + X11::XSizeHints size_hints; + size_hints.min_width = vWindowSize.x; + size_hints.max_width = vWindowSize.x; + size_hints.min_height = vWindowSize.y; + size_hints.max_height = vWindowSize.y; + size_hints.flags = PMinSize | PMaxSize; + X11::XSetNormalHints(olc_Display, olc_Window, &size_hints); + } XMapWindow(olc_Display, olc_Window); XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); @@ -492,6 +540,7 @@ namespace olc::host XWindowAttributes gwa; XGetWindowAttributes(olc_Display, win, &gwa); pWindow->olc_OnWindowSize({gwa.width, gwa.height}); + pWindow->bWindowIsFullscreen = bFullScreen; return true; } diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 318d0cd4..0f7c343a 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -4,6 +4,7 @@ //! START STDHEADER GLOBAL #include #include +#include #include #include #include diff --git a/dev/src/window.h b/dev/src/window.h index 05509090..dbc20c37 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -118,6 +118,7 @@ namespace olc bool bRequestToClose = false; bool bShouldRemove = false; bool bWindowIsFocused = false; + bool bWindowIsFullscreen = false; protected: size_t nUniqueID = size_t(-1); diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp index 3168d6ca..53032e6f 100644 --- a/examples/olcPGE3_Fish.cpp +++ b/examples/olcPGE3_Fish.cpp @@ -370,7 +370,6 @@ class Example_Fish : public olc::PixelGameEngine std::vector others; std::vector targets; - bool fullscreen = false; public: // Called once at the start, so create things here bool OnUserCreate() override @@ -390,8 +389,8 @@ class Example_Fish : public olc::PixelGameEngine { olc::Pixel background_color{73, 220, 222}; if(GetKeyboard().GetKey(olc::Key::ALT).bHeld && GetKeyboard().GetKey(olc::Key::ENTER).bPressed) { - fullscreen = !fullscreen; - ShowFullScreen(fullscreen); + bWindowIsFullscreen = !bWindowIsFullscreen; + ShowFullScreen(bWindowIsFullscreen); } // Clear screen to a background color @@ -437,10 +436,14 @@ int main() { // Construct demo application Example_Fish demo; - + olc::PGEConfig config; + config.vScreenSize = {256, 240}; + config.vPixelSize = {4, 4}; + //config.bFullScreenable = false; + config.bResizeable = false; // Create "screen" of 256x240 "pixels" // with a pixel size of 4x4 actual screen pixels - if (demo.Construct({ 256, 240 }, { 4, 4 })) + if (demo.Construct(config)) { // Start the application demo.Start(); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 5e738a67..b51db3c7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3808,6 +3808,7 @@ namespace olc bool bRequestToClose = false; bool bShouldRemove = false; bool bWindowIsFocused = false; + bool bWindowIsFullscreen = false; protected: size_t nUniqueID = size_t(-1); @@ -10942,12 +10943,50 @@ namespace olc::host if (xev.type == Expose) { - //auto* expose_event = reinterpret_cast(&xev); X11::XExposeEvent& e = xev.xexpose; + X11::Atom wm_state; + wm_state = X11::XInternAtom(olc_Display, "_NET_WM_STATE", True); + bool did_fullscreen = false; + + X11::Atom actual_type; + int actual_format; + unsigned long int num_items; + unsigned long int bytes; + unsigned char* property{nullptr}; + int res = X11::XGetWindowProperty(e.display, e.window, wm_state, + 0, + ~0, + False, + AnyPropertyType, + &actual_type, + &actual_format, + &num_items, + &bytes, + &property + ); + + if(res == Success) { + char* name; + if(X11::XGetAtomNames(e.display, (Atom*)property, num_items, &name)) + { + for(int i = 0; i < num_items; i++) + { + if(std::strcmp(name + i, "_NET_WM_STATE_FULLSCREEN") == 0) { + did_fullscreen = true; + } + } + } + } + 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(did_fullscreen && (!pge_window->config.bFullScreenable || !pge_window->config.bResizeable) && !pge_window->bWindowIsFullscreen) { + SetFullScreen(pge_window, false); + } else { + X11::XWindowAttributes gwa; + X11::XGetWindowAttributes(e.display, e.window, &gwa); + pge_window->olc_OnWindowSize(olc::vi2d{gwa.width, gwa.height}); + pge_window->bWindowIsFullscreen = did_fullscreen; + } } } else if (xev.type == ConfigureNotify) @@ -11103,6 +11142,16 @@ namespace olc::host X11::Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); X11::XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + if(!pWindow->config.bResizeable) + { + X11::XSizeHints size_hints; + size_hints.min_width = vWindowSize.x; + size_hints.max_width = vWindowSize.x; + size_hints.min_height = vWindowSize.y; + size_hints.max_height = vWindowSize.y; + size_hints.flags = PMinSize | PMaxSize; + X11::XSetNormalHints(olc_Display, olc_Window, &size_hints); + } XMapWindow(olc_Display, olc_Window); XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); @@ -11292,6 +11341,7 @@ namespace olc::host XWindowAttributes gwa; XGetWindowAttributes(olc_Display, win, &gwa); pWindow->olc_OnWindowSize({gwa.width, gwa.height}); + pWindow->bWindowIsFullscreen = bFullScreen; return true; } @@ -11648,6 +11698,10 @@ namespace olc::host w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); + if (!pWindow->config.bResizeable) { + xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); + xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); + } #ifdef ENABLE_DECORATION_PROTOCOL w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); @@ -11662,7 +11716,13 @@ namespace olc::host w.floating_height = vWindowSize.y; libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); + } + libdecor_frame_map(w.decor_frame); + } #endif @@ -11776,7 +11836,7 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { - itr->second.fullscreen = bFullScreen; + pWindow->bWindowIsFullscreen = bFullScreen; if(bFullScreen) { #ifdef ENABLE_LIBDECOR if(using_libdecor) { @@ -11851,26 +11911,42 @@ namespace olc::host void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) { - for(auto& i : mapUID2Window) { - auto& w = i.second; - if(w.toplevel == toplevel) { - // Attempt to constrain the window size to what the compositor may have told us earlier - // in a bounds_configure message - if(!w.fullscreen) { - if(w.bounds_x != 0) { - width = std::min(width, w.bounds_x); - } - - if(w.bounds_y != 0) { - height = std::min(height, w.bounds_y); - } - } - - mapUID2OlcWindow[i.first]->olc_OnWindowSize({width, height}); - wl_egl_window_resize(i.second.window, width, height, 0, 0); - wl_surface_commit(i.second.surface); + const auto& itr = std::find_if(mapUID2Window.begin(), mapUID2Window.end(), [=](const auto& w){return w.second.toplevel == toplevel;}); + if(itr == mapUID2Window.end()) + { + return; + } + + auto& [uid, window] = *itr; + const auto& olc_window = mapUID2OlcWindow.at(uid); + + bool attempt_fullscreen {false}; + auto* state = reinterpret_cast(states->data); + auto* end = static_cast(states->data) + states->size; + for(;reinterpret_cast(state) < end; state++) + { + // The window.fullscreen is set any time the user commands via ShowFullscreen(), so we should allow this + if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) + { + attempt_fullscreen = true; } } + + // If we're not going fullscreen, try to obey the bounds that have been configured by the compositor + if(!attempt_fullscreen) { + if(window.bounds_x != 0) { + width = std::min(width, window.bounds_x); + } + + if(window.bounds_y != 0) { + height = std::min(height, window.bounds_y); + } + } + + olc_window->bWindowIsFullscreen = attempt_fullscreen; + olc_window->olc_OnWindowSize({width, height}); + wl_egl_window_resize(window.window, width, height, 0, 0); + wl_surface_commit(window.surface); } void Host_Linux_Wayland::xdg_toplevel_close(xdg_toplevel* toplevel) @@ -12290,6 +12366,7 @@ namespace olc::host for(auto& i : mapUID2Window) { if(i.second.decor_frame == frame) { auto* window = &i.second; + auto& olc_window = mapUID2OlcWindow.at(i.first); int width{}; int height{}; @@ -12307,11 +12384,13 @@ namespace olc::host libdecor_frame_commit(frame, state, config); libdecor_state_free(state); - if(libdecor_frame_is_floating(frame)) { + // If we're not returning from fullscreen, goahead and resize + if(libdecor_frame_is_floating(frame) && !olc_window->bWindowIsFullscreen) { window->floating_width = width; window->floating_height = height; } + olc_window->bWindowIsFullscreen = (window->decor_window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); wl_surface_commit(window->surface); From c435a18f3402c16986e22e923d67832536760a80 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:17:23 +0000 Subject: [PATCH 31/41] On MacOS, the maximize button is tied to the resizable style, so we disable it if the window is not resizable --- dev/src/host_apple_macos.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 6f01fd3b..42f91668 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -28,8 +28,8 @@ namespace olc::host { UtilityWindow = (1 << 4), // Utility window style DocModalWindow = (1 << 6), // Document-modal window NonactivatingPanel = (1 << 7), // Non-activating panel - HUDWindow = (1 << 13), // Heads-up display window TexturedBackground = (1 << 8), // Textured background + HUDWindow = (1 << 13), // Heads-up display window UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar FullScreen = (1 << 14), // Full-screen window FullSizeContentView = (1 << 15) // Full-size content view @@ -243,6 +243,10 @@ namespace olc::host { // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); + // On MacOS, the maximize button is tied to the resizable style, so we disable it if the window is not resizable + if (pPrimaryPGE->config.bResizeable) + pPrimaryPGE->config.bShowWindowMaximiseButton = false; + // For MacOS you can only disable the buttons, you can't hide them if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar From 6e1eb211d894dce1822da4dc4d77cbfdecda0573 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:05:49 +0000 Subject: [PATCH 32/41] On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton --- dev/src/host_apple_macos.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 42f91668..0eddeb59 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -243,17 +243,13 @@ namespace olc::host { // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); - // On MacOS, the maximize button is tied to the resizable style, so we disable it if the window is not resizable - if (pPrimaryPGE->config.bResizeable) - pPrimaryPGE->config.bShowWindowMaximiseButton = false; - + // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, // For MacOS you can only disable the buttons, you can't hide them if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button - if (pPrimaryPGE->config.bShowWindowMaximiseButton) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Add Max Button if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button return nsStyle; From d09d107bbcc9fdac967aef46b1cc32c635c02c90 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:10:27 +0000 Subject: [PATCH 33/41] Updated PGE SH --- olcPixelGameEngine3.h | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c6f1aa60..29f32d15 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5972,6 +5972,7 @@ namespace olc::host bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -7556,9 +7557,6 @@ namespace olc::host } else { - olc::vi2d vWinPos = pPrimaryPGE->config.vWindowOffset; - olc::vi2d vWinSize = pPrimaryPGE->config.vScreenSize * pPrimaryPGE->config.vPixelSize; - // Restore original window style and position DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Get the style we should have based on the window config @@ -7871,8 +7869,8 @@ namespace olc::host { UtilityWindow = (1 << 4), // Utility window style DocModalWindow = (1 << 6), // Document-modal window NonactivatingPanel = (1 << 7), // Non-activating panel - HUDWindow = (1 << 13), // Heads-up display window TexturedBackground = (1 << 8), // Textured background + HUDWindow = (1 << 13), // Heads-up display window UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar FullScreen = (1 << 14), // Full-screen window FullSizeContentView = (1 << 15) // Full-size content view @@ -8086,13 +8084,13 @@ namespace olc::host { // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); + // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, // For MacOS you can only disable the buttons, you can't hide them if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button - if (pPrimaryPGE->config.bShowWindowMaximiseButton) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Add Max Button if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button return nsStyle; @@ -12785,6 +12783,21 @@ namespace olc::host return true; } + + bool Host_Web_Emscripten::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + if(!mapUID2CanvasId.contains(pWindow->GetUID())) + return false; + + auto canvasID = mapUID2CanvasId.at(pWindow->GetUID()); + + if(bFullScreen) + emscripten_request_fullscreen(canvasID.c_str(), true); + else + emscripten_exit_fullscreen(); + + return true; + } olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { From 7be29ea90181efcfe8e7acae0a58de6d41d90abc Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:58:53 +0000 Subject: [PATCH 34/41] RAII and clean exit failing on MacOS --- dev/src/api_macos.cpp | 12 +++++------ dev/src/api_macos_wrapper.hpp | 8 +++---- dev/src/host_apple_macos.cpp | 4 ++-- olcPixelGameEngine3.h | 40 +++++++++++++++++++++++------------ 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 42a908c1..71e9eb95 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -38,6 +38,7 @@ static constexpr const char* kSharedApplicationSel = "sharedApplica static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; static constexpr const char* kRunSel = "run"; +static constexpr const char* kStopSel = "stop:"; static constexpr const char* kTerminateSel = "terminate:"; // NSApplicationDelegate lifecycle methods @@ -177,6 +178,7 @@ namespace ObjectiveCSEL { static SEL activateIgnoringOtherAppsSel = nullptr; static SEL setActivationPolicySel = nullptr; static SEL runSel = nullptr; + static SEL stopSel = nullptr; static SEL terminateSEL = nullptr; // Application Screen management selectors @@ -293,6 +295,7 @@ namespace ObjectiveCSEL { activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); setActivationPolicySel = sel_registerName(kSetActivationPolicySel); runSel = sel_registerName(kRunSel); + stopSel = sel_registerName(kStopSel); terminateSEL = sel_registerName(kTerminateSel); // Application Screen management selectors @@ -639,11 +642,7 @@ struct Application { Application() = default; - ~Application() { - if (destroy) { - destroy(this); - } - } + ~Application() {} // Delete copy constructor and assignment Application(const Application&) = delete; @@ -1377,9 +1376,10 @@ extern "C" { ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); } + // Request to OS to gracefully terminate the application (RAII compatible) void application_stop(Application* self) { if (self && self->nsApp) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::terminateSEL, self->nsApp); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::stopSel, self->nsApp); } } diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index e6ec4647..7e4d6914 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -81,6 +81,7 @@ namespace olc { ~Application() { if (app_) { application_destroy(app_); // application_destroy now handles delete internally + app_ = nullptr; } } @@ -96,10 +97,9 @@ namespace olc { if (app_) application_run(app_); } - void terminate() noexcept { + void stop() noexcept { if (app_) { application_stop(app_); - app_ = nullptr; } } @@ -885,9 +885,7 @@ namespace olc { public: explicit EventHandler(Window& window) noexcept : window_(window) {} - ~EventHandler() noexcept { - disable(); - } + ~EventHandler() noexcept {} // Event handler setters - now using template helper void onKeyDown(std::function handler) { diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index ac8988b2..5506f83c 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -289,7 +289,6 @@ namespace olc::host { pMacApplication->run(); // Once the application run loop ends, join the system thread - systemActive = false; if(threadSystem.joinable()) threadSystem.join(); @@ -314,10 +313,11 @@ namespace olc::host { } if (pMacApplication) { - pMacApplication->terminate(); + pMacApplication->stop(); } }); + systemActive = false; return true; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 243e2047..bdbafc20 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4533,6 +4533,7 @@ namespace olc { ~Application() { if (app_) { application_destroy(app_); // application_destroy now handles delete internally + app_ = nullptr; } } @@ -4548,10 +4549,9 @@ namespace olc { if (app_) application_run(app_); } - void terminate() noexcept { + void stop() noexcept { if (app_) { application_stop(app_); - app_ = nullptr; } } @@ -5337,9 +5337,7 @@ namespace olc { public: explicit EventHandler(Window& window) noexcept : window_(window) {} - ~EventHandler() noexcept { - disable(); - } + ~EventHandler() noexcept {} // Event handler setters - now using template helper void onKeyDown(std::function handler) { @@ -5945,6 +5943,7 @@ namespace olc::host bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -8062,7 +8061,6 @@ namespace olc::host { pMacApplication->run(); // Once the application run loop ends, join the system thread - systemActive = false; if(threadSystem.joinable()) threadSystem.join(); @@ -8087,10 +8085,11 @@ namespace olc::host { } if (pMacApplication) { - pMacApplication->terminate(); + pMacApplication->stop(); } }); + systemActive = false; return true; } @@ -8511,6 +8510,7 @@ static constexpr const char* kSharedApplicationSel = "sharedApplica static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; static constexpr const char* kRunSel = "run"; +static constexpr const char* kStopSel = "stop:"; static constexpr const char* kTerminateSel = "terminate:"; // NSApplicationDelegate lifecycle methods @@ -8650,6 +8650,7 @@ namespace ObjectiveCSEL { static SEL activateIgnoringOtherAppsSel = nullptr; static SEL setActivationPolicySel = nullptr; static SEL runSel = nullptr; + static SEL stopSel = nullptr; static SEL terminateSEL = nullptr; // Application Screen management selectors @@ -8766,6 +8767,7 @@ namespace ObjectiveCSEL { activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); setActivationPolicySel = sel_registerName(kSetActivationPolicySel); runSel = sel_registerName(kRunSel); + stopSel = sel_registerName(kStopSel); terminateSEL = sel_registerName(kTerminateSel); // Application Screen management selectors @@ -9112,11 +9114,7 @@ struct Application { Application() = default; - ~Application() { - if (destroy) { - destroy(this); - } - } + ~Application() {} // Delete copy constructor and assignment Application(const Application&) = delete; @@ -9850,9 +9848,10 @@ extern "C" { ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); } + // Request to OS to gracefully terminate the application (RAII compatible) void application_stop(Application* self) { if (self && self->nsApp) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::terminateSEL, self->nsApp); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::stopSel, self->nsApp); } } @@ -12647,6 +12646,21 @@ namespace olc::host return true; } + + bool Host_Web_Emscripten::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + if(!mapUID2CanvasId.contains(pWindow->GetUID())) + return false; + + auto canvasID = mapUID2CanvasId.at(pWindow->GetUID()); + + if(bFullScreen) + emscripten_request_fullscreen(canvasID.c_str(), true); + else + emscripten_exit_fullscreen(); + + return true; + } olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { From 4b1009a88973ec92b345a507ae7b8458ec37b068 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:12:04 +0000 Subject: [PATCH 35/41] oops lol --- dev/src/host_win_winapi.cpp | 1 + olcPixelGameEngine3.h | 1 + 2 files changed, 2 insertions(+) diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index d7cc315d..b95c0048 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -223,6 +223,7 @@ namespace olc::host mapKeys[VK_CONTROL] = Key::CTRL; mapKeys[VK_SPACE] = Key::SPACE; mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + mapKeys[VK_MENU] = Key::ALT; // Numpad mapKeys[VK_NUMPAD0] = Key::NP0; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 39318963..2a0a1feb 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -7377,6 +7377,7 @@ namespace olc::host mapKeys[VK_CONTROL] = Key::CTRL; mapKeys[VK_SPACE] = Key::SPACE; mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + mapKeys[VK_MENU] = Key::ALT; // Numpad mapKeys[VK_NUMPAD0] = Key::NP0; From 94b20e44eda21dc5f014851bb10c894a1c26c1ce Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:40:03 +0100 Subject: [PATCH 36/41] +Draw::ImageRect(batch) --- dev/src/draw_batch.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp index a78148d1..33219089 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -440,9 +440,20 @@ const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion } const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) -{ - olc_IgnoreUnused(image, pos, size, tint); - // TODO: Implement this function +{ + // Add quad to existing task + olc::vf2d p0 = transformAffine.forward(olc::vf2d{ pos.x, pos.y }); + olc::vf2d p1 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y }); + olc::vf2d p2 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y + size.y }); + olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); + + // NOTE: We fake tint by simply setting vertex colour + 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({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].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({ {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} }); return batch; } From d75fc3886c1bf46b5075b281dd53c02a6d8ea30a Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:48:32 +0100 Subject: [PATCH 37/41] +FilledPolygon(batch, col) --- dev/src/draw.h | 34 +++++++++++++++++++++- dev/src/draw_batch.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/dev/src/draw.h b/dev/src/draw.h index b1ead2c0..f8b2a36b 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -123,7 +123,7 @@ [#] FilledPolygon(structure, points[], col, [tint]) [#] FilledPolygon(structure, points[], colours[], [tint]) - [ ] FilledPolygon(batch, structure, points[], col) + [#] FilledPolygon(batch, structure, points[], col) [#] FilledPolygon(batch, structure, points[], colours[]) [#] TexturedTriangle(p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image, [tint]) @@ -152,6 +152,7 @@ [#] Batch(LineBtach, [tint]) [#] Batch(FilledBatch, [tint]) [#] Batch(ImageBatch, [tint]) + [#] Batch(TexturedBatch, [tint]) 3D Rendering Functions @@ -186,6 +187,7 @@ namespace olc struct ImageBatch { GPUTask task; }; struct FilledBatch { GPUTask task; }; struct LineBatch { GPUTask task; }; + struct TextureBatch { GPUTask task; }; class Draw { @@ -589,6 +591,20 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured triangle, with per vertex colouring into a batch + const GPUTask& 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 = olc::Colour::WHITE); + // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( const olc::vf2d& p1, @@ -650,6 +666,14 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach + const FilledBatch& FilledPolygon( + FilledBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach const FilledBatch& FilledPolygon( FilledBatch& batch, @@ -861,6 +885,14 @@ namespace olc // Draws a line shape batch to the current target const GPUTask& Batch(olc::LineBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); + // Create a Textured batch for efficient repeated drawing of + // the same source texture on a polygon with per vertex colouring + TextureBatch CreateTextureBatch(olc::Image& image); + + // Draws an image batch to the current target + const GPUTask& Batch(olc::TextureBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); + + public: // GPU Task Creator Functions (not normally called by user) GPUTask TaskDrawLine( diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp index 33219089..7842bd0b 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -262,6 +262,70 @@ const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) +{ + auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + } + + olc::Pixel blendedCol = col.blend(tint); + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i - 1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + blendedCol); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i * 3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + blendedCol); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + blendedCol); + } + + default: + break; + } + + return batch; +} + const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { // TODO: Curiously, this approach is considerably slower than the naive approach of just From 23bde5b5ff49b20a9913de5865f7be26ce9b44ce Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:55:33 +0100 Subject: [PATCH 38/41] +TextureBatch type +Draw2D::TexturedTriangle(batch) --- dev/src/draw.cpp | 11 ++--------- dev/src/draw.h | 4 ++-- dev/src/draw_batch.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 91208319..a239b244 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -715,6 +715,8 @@ const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p return FilledTriangle(p1, p2, p3, col, col, col, tint); } + + const GPUTask& olc::Draw::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(); @@ -946,16 +948,7 @@ GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { From 710847c404921a9faf4bacd84b4f186ed02aae1e Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:03:15 +0100 Subject: [PATCH 39/41] +Draw2D::TexturedTriangle(batch) +Draw2D::TexturedPolygon(batch) --- dev/src/draw.h | 13 ++++++-- dev/src/draw_batch.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/dev/src/draw.h b/dev/src/draw.h index ad488c1d..4ad24138 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -127,10 +127,10 @@ [#] 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, [tint]) + [#] TexturedTriangle(batch, p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image, [tint]) [#] TexturedPolygon(structure, points[], colours[], uvs[], image, [tint]) - [ ] TexturedPolygon(batch, structure, points[], colours[], uvs[], image, [tint]) + [#] TexturedPolygon(batch, structure, points[], colours[], uvs[], image, [tint]) [#] String(pos, text, col, [scale], [font]) [#] StringProp(pos, text, col, [scale], [font]) @@ -697,6 +697,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring into a batch + const TextureBatch& TexturedPolygon( + olc::TextureBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const std::vector& vecColours, + const std::vector& vecTexCoords, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring const GPUTask& TexturedPolygon( diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp index 81dd561c..da0d8e8f 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -282,6 +282,77 @@ const TextureBatch& olc::Draw::TexturedTriangle(olc::TextureBatch& batch, const return batch; } +const TextureBatch& olc::Draw::TexturedPolygon(olc::TextureBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, const olc::Pixel tint) +{ + auto pushTriangle = [&]( + const size_t idx, 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) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {t1.x, t1.y}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {t2.x, t2.y}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {t3.x, t3.y}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + buffColours.reserve(vecColours.size()); + + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + buffColours.data[i] = vecColours[i].blend(tint); + } + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i - 1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + buffColours.data[0], buffColours.data[i], buffColours.data[i + 1], + vecTexCoords[0], vecTexCoords[i], vecTexCoords[i+1]); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i * 3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2], + vecTexCoords[i], vecTexCoords[i+1], vecTexCoords[i+2]); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2], + vecTexCoords[i], vecTexCoords[i + 1], vecTexCoords[i + 2]); + } + + default: + break; + } + + return batch; +} + const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { From cd70886416d894b7365d6757b45cd6da54441348 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:39:05 +0100 Subject: [PATCH 40/41] +sh sync --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +- olcPixelGameEngine3.h | 244 +++++++++++++++++- 2 files changed, 236 insertions(+), 16 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 0966737d..d809d4dc 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -46,8 +46,8 @@ true true - true - true + false + false true @@ -58,8 +58,8 @@ false false - false - false + true + true true diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 2a0a1feb..8d6c2169 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2710,6 +2710,7 @@ namespace olc struct ImageBatch { GPUTask task; }; struct FilledBatch { GPUTask task; }; struct LineBatch { GPUTask task; }; + struct TextureBatch { GPUTask task; }; class Draw { @@ -3113,6 +3114,20 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured triangle, with per vertex colouring into a batch + const TextureBatch& TexturedTriangle( + olc::TextureBatch& 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, + const olc::vf2d& t1, + const olc::vf2d& t2, + const olc::vf2d& t3, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( const olc::vf2d& p1, @@ -3174,6 +3189,14 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach + const FilledBatch& FilledPolygon( + FilledBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach const FilledBatch& FilledPolygon( FilledBatch& batch, @@ -3197,6 +3220,15 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring into a batch + const TextureBatch& TexturedPolygon( + olc::TextureBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const std::vector& vecColours, + const std::vector& vecTexCoords, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring const GPUTask& TexturedPolygon( @@ -3385,6 +3417,14 @@ namespace olc // Draws a line shape batch to the current target const GPUTask& Batch(olc::LineBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); + // Create a Textured batch for efficient repeated drawing of + // the same source texture on a polygon with per vertex colouring + TextureBatch CreateTextureBatch(olc::Image& image); + + // Draws an image batch to the current target + const GPUTask& Batch(olc::TextureBatch& batch, const olc::Pixel tint = olc::Colour::WHITE); + + public: // GPU Task Creator Functions (not normally called by user) GPUTask TaskDrawLine( @@ -16691,6 +16731,8 @@ const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p return FilledTriangle(p1, p2, p3, col, col, col, tint); } + + const GPUTask& olc::Draw::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(); @@ -16922,16 +16964,7 @@ GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, const olc::Pixel tint) +{ + auto pushTriangle = [&]( + const size_t idx, 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) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {t1.x, t1.y}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {t2.x, t2.y}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {t3.x, t3.y}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + buffColours.reserve(vecColours.size()); + + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + buffColours.data[i] = vecColours[i].blend(tint); + } + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i - 1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + buffColours.data[0], buffColours.data[i], buffColours.data[i + 1], + vecTexCoords[0], vecTexCoords[i], vecTexCoords[i+1]); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i * 3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2], + vecTexCoords[i], vecTexCoords[i+1], vecTexCoords[i+2]); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2], + vecTexCoords[i], vecTexCoords[i + 1], vecTexCoords[i + 2]); + } + + default: + break; + } + + return batch; +} + const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { @@ -17390,6 +17535,70 @@ const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) +{ + auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + } + + olc::Pixel blendedCol = col.blend(tint); + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i - 1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + blendedCol); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i * 3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + blendedCol); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + blendedCol); + } + + default: + break; + } + + return batch; +} + const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { // TODO: Curiously, this approach is considerably slower than the naive approach of just @@ -17568,9 +17777,20 @@ const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion } const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) -{ - olc_IgnoreUnused(image, pos, size, tint); - // TODO: Implement this function +{ + // Add quad to existing task + olc::vf2d p0 = transformAffine.forward(olc::vf2d{ pos.x, pos.y }); + olc::vf2d p1 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y }); + olc::vf2d p2 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y + size.y }); + olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); + + // NOTE: We fake tint by simply setting vertex colour + 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({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].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({ {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} }); return batch; } From 18c9c01988f1a64276050bbf2d82f1f845d95aa4 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Fri, 3 Apr 2026 16:42:21 +0100 Subject: [PATCH 41/41] Beta A - Release --- dev/src/sh_template.h | 2 +- olcPixelGameEngine3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 96dc33e2..99c88e02 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -8,7 +8,7 @@ olcPixelGameEngine3.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v3.00 | + | OneLoneCoder Pixel Game Engine v3.00 Beta A | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 8d6c2169..37104252 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8,7 +8,7 @@ olcPixelGameEngine3.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v3.00 | + | OneLoneCoder Pixel Game Engine v3.00 Beta A | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+