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/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index fe6df724..40188d23 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 @@ -11,8 +12,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"; @@ -39,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 @@ -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:"; @@ -127,18 +129,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"; @@ -190,6 +180,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 @@ -217,6 +208,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; @@ -284,18 +277,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; @@ -318,6 +299,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 @@ -345,7 +327,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); @@ -410,18 +394,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); @@ -570,6 +542,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 { @@ -676,11 +649,7 @@ struct Application { Application() = default; - ~Application() { - if (destroy) { - destroy(this); - } - } + ~Application() {} // Delete copy constructor and assignment Application(const Application&) = delete; @@ -736,7 +705,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}; @@ -780,7 +749,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) @@ -789,6 +758,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}; @@ -1271,11 +1241,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 @@ -1410,9 +1383,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); } } @@ -1475,7 +1449,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 +1459,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 +1698,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; @@ -1903,105 +1884,130 @@ 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) { - return NO; - } - - // Create NSImage from file - id image = ((id(*)(id, SEL, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSImageClass, allocSel), - initWithContentsOfFileSel, pathString); + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); + + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); + CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); + CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; - if (!image) { + self->width = CGImageGetWidth(image); + self->height = CGImageGetHeight(image); + self->bytesPerPixel = 4; + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; + + const uint8_t* imageData = CFDataGetBytePtr(rawData); + self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); + if(!self->pixelData) + { return NO; } + memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); - // Get image representations - id representations = ((id(*)(id, SEL))objc_msgSend)(image, representationsSel); - NSUInteger repCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(representations, countSel); - - if (repCount == 0) { + // NOTE from Moros1138 + // + // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far + // the most common case, so in practice this block of code will never + // be run. However, if we find that there is a need to adjust the + // pixel data, this will need fleshing out. + + /* + if(byteOrder != kCGBitmapByteOrder32Little) + { + int pixelCount = self->width * self->height; + for(int i = 0; i < pixelCount; ++i) + { + auto p = self->pixelData[i]; + + // TODO: detect byte order, adjust as appropriate + + self->pixelData[i] = p; + } + } + */ + + CFRelease(rawData); + CGImageRelease(image); + + return YES; + } + + 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 unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return self->pixelData; + return (unsigned char*)self->pixelData; } // Get image information @@ -2035,13 +2041,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; @@ -2074,6 +2080,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..62c17049 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) @@ -56,7 +57,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 +73,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); @@ -85,9 +88,15 @@ 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); + 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..a346b9cc 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; } } @@ -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; @@ -639,6 +652,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_); @@ -876,9 +898,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/core.cpp b/dev/src/core.cpp index 7b3a0449..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)); @@ -256,7 +260,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; } @@ -361,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()) @@ -578,7 +601,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; } } diff --git a/dev/src/core.h b/dev/src/core.h index 81f9b757..3cb282a4 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 }; @@ -39,17 +39,29 @@ 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; + // 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 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; @@ -68,6 +80,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/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& 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, @@ -673,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( @@ -861,6 +894,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 a78148d1..da0d8e8f 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -18,6 +18,17 @@ //! START IMPLEMENTATION using namespace olc; +ImageBatch olc::Draw::CreateImageBatch(olc::Image& image) +{ + PrepareImageForHW(image); + PrepareTargetForHW(); + + ImageBatch b; + b.task.structure = olc::Structure::List; + b.task.pImage = ℑ + return b; +} + const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) { batch.task.tint = tint; @@ -50,6 +61,23 @@ const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) return vecGPUTasks.data.emplace_back(batch.task); } +TextureBatch olc::Draw::CreateTextureBatch(olc::Image& image) +{ + PrepareImageForHW(image); + PrepareTargetForHW(); + + TextureBatch b; + b.task.structure = olc::Structure::List; + b.task.pImage = ℑ + return b; +} + +const GPUTask& olc::Draw::Batch(olc::TextureBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) { return Line(batch, p1, col, p2, col, tint); @@ -241,6 +269,90 @@ const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc: return batch; } +const TextureBatch& olc::Draw::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) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); + size_t idx = batch.task.vertexBuffer.size() - 3; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + const olc::vf2d a3 = transformAffine.forwardRound(p3); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1.blend(tint), {t1.x, t1.y}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2.blend(tint), {t2.x, t2.y}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3.blend(tint), {t3.x, t3.y}, {0, 0}, {0, 0}, {0, 0} }; + 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) { @@ -262,6 +374,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 @@ -440,9 +616,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; } 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/dev/src/host_android.cpp b/dev/src/host_android.cpp index 7482f9a1..45e55e5b 100644 --- a/dev/src/host_android.cpp +++ b/dev/src/host_android.cpp @@ -260,12 +260,12 @@ 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"); + host->pgeWindow->olc_OnFocus(true); } break; case APP_CMD_LOST_FOCUS: { - host->pgeWindow->olc_OnMouseFocus(false); LOGD("APP_CMD_LOST_FOCUS received"); + host->pgeWindow->olc_OnFocus(false); } break; default: break; } @@ -614,7 +614,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/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index d9c0e813..f2bde38c 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 + TexturedBackground = (1 << 8), // Textured background + HUDWindow = (1 << 13), // Heads-up display window + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view + }; + Host_Apple_MacOS::Host_Apple_MacOS() { @@ -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); + + // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, + // For MacOS you can only disable the buttons, you can't hide them + if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window + if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border + if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button + if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button + + 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 ----- @@ -284,7 +333,6 @@ namespace olc::host { pMacApplication->run(); // Once the application run loop ends, join the system thread - systemActive = false; if(threadSystem.joinable()) threadSystem.join(); @@ -309,10 +357,11 @@ namespace olc::host { } if (pMacApplication) { - pMacApplication->terminate(); + pMacApplication->stop(); } }); + systemActive = false; return true; } @@ -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_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index cf974aac..3e6f6db2 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,51 @@ 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 + + // 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); + // Setup the keymap with XKB codes, which are basically the same as the X11 codes mapKeys[XKB_KEY_NoSymbol] = Key::NONE; @@ -138,20 +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); - xdg_toplevel_destroy(wayland_window.toplevel); - xdg_surface_destroy(wayland_window.surface_xdg); - 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); } @@ -193,7 +284,21 @@ namespace olc::host } }); + #if !defined(ENABLE_LIBDECOR) while(systemActive && wl_display_dispatch_pending(display) != -1) { } + #else + bool keep_running = true; + while(systemActive && keep_running) { + if(using_libdecor) { + if(decor_context) { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; + } + } else { + keep_running = wl_display_dispatch_pending(display) != -1; + } + } + #endif systemActive = false; if(threadSystem.joinable()) @@ -231,49 +336,68 @@ 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); 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); + if (!pWindow->config.bResizeable) { + xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); + xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); + } + + #ifdef ENABLE_DECORATION_PROTOCOL + w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); + zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); + zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); + #endif + #ifdef ENABLE_LIBDECOR + } else { + std::lock_guard l{decor_mutex}; + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); + } + + libdecor_frame_map(w.decor_frame); + + } + #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; } 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); - zxdg_toplevel_decoration_v1_destroy(wayland_window.decorations); - 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; } @@ -281,7 +405,16 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + std::lock_guard l{decor_mutex}; + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); + } else { + xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + } + #else xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + #endif } return true; } @@ -343,7 +476,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); } @@ -356,11 +489,27 @@ 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) { + 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; @@ -371,24 +520,27 @@ 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)); } 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)); } 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) @@ -400,8 +552,7 @@ 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); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { @@ -413,26 +564,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) @@ -572,7 +739,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); } @@ -827,11 +994,102 @@ 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; + auto& olc_window = mapUID2OlcWindow.at(i.first); + + 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 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); + } + } + } + + 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..b7286027 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -16,18 +16,39 @@ //! END CUSTOMHEADER //! START DECLARATION +#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_LIBDECOR +#endif + +#if !defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_DECORATION_PROTOCOL +#endif + +#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) +#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." +#endif + #include +#include #include #include "xdg-shell.h" + +// Only include the decoration protocol if we are not forcing libdecor +#ifdef ENABLE_DECORATION_PROTOCOL #include "xdg-decoration.h" +#endif + #include "pointer-warp.h" -#include "cursor-shape.h" #include #include #include #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}; + int configured_width{}; + int configured_height{}; + libdecor_window_state decor_window_state; + int floating_width{}; + int floating_height{}; + #endif + ~WaylandWindow(); }; namespace wayland { @@ -89,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}; @@ -96,18 +131,28 @@ 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 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; + 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 + // libdecor support + bool using_libdecor{false}; + libdecor* decor_context{nullptr}; + std::mutex decor_mutex; + #endif public: Host_Linux_Wayland(); @@ -172,7 +217,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 +261,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/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/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; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 3fe19d1a..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; @@ -284,25 +285,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 +319,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 +415,54 @@ 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 { // 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/src/imload_lib_png.cpp b/dev/src/imload_lib_png.cpp index 3dcd0113..5fadfefa 100644 --- a/dev/src/imload_lib_png.cpp +++ b/dev/src/imload_lib_png.cpp @@ -1,100 +1,68 @@ #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; + + 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 @@ -108,6 +76,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); }; } diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index c144beb1..94734e6e 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -45,17 +45,51 @@ namespace olc::imload std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); return true; - } bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - 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/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); }; } } 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/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 bb35f5ac..dbc20c37 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; @@ -51,6 +62,7 @@ namespace olc public: Window(); + Window(const WindowConfig& config); virtual ~Window(); void LinkToHost(olc::host::Host* host); @@ -106,6 +118,7 @@ namespace olc bool bRequestToClose = false; bool bShouldRemove = false; bool bWindowIsFocused = false; + bool bWindowIsFullscreen = false; protected: size_t nUniqueID = size_t(-1); @@ -115,6 +128,7 @@ namespace olc protected: olc::host::Host* pHost = nullptr; + olc::WindowConfig config; protected: olc::hw::Mouse mouse; 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/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4f010482..b81dc40c 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 @@ -25,6 +27,11 @@ 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) + endif() find_package(OpenGL REQUIRED) find_package(PNG REQUIRED) @@ -61,20 +68,23 @@ 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,17 +116,30 @@ 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 ${XKBCOMMON_LIBRARIES} ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} + ${WAYLAND_CURSOR_LIBRARIES} ${EGL_LIBRARIES} 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/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/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 diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3b6b07c5..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 | +-------------------------------------------------------------+ @@ -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( @@ -3734,6 +3774,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 +3792,7 @@ namespace olc public: Window(); + Window(const WindowConfig& config); virtual ~Window(); void LinkToHost(olc::host::Host* host); @@ -3796,6 +3848,7 @@ namespace olc bool bRequestToClose = false; bool bShouldRemove = false; bool bWindowIsFocused = false; + bool bWindowIsFullscreen = false; protected: size_t nUniqueID = size_t(-1); @@ -3805,6 +3858,7 @@ namespace olc protected: olc::host::Host* pHost = nullptr; + olc::WindowConfig config; protected: olc::hw::Mouse mouse; @@ -3976,7 +4030,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 +4038,29 @@ 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; + // 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 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 +4079,7 @@ namespace olc { public: PGEWindow(); + PGEWindow(const WindowConfig& config); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: @@ -4313,6 +4380,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); @@ -4333,6 +4401,7 @@ namespace olc #include #include #include +#include extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) @@ -4365,7 +4434,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); @@ -4381,6 +4450,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); @@ -4394,9 +4465,15 @@ 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); + 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); @@ -4526,6 +4603,7 @@ namespace olc { ~Application() { if (app_) { application_destroy(app_); // application_destroy now handles delete internally + app_ = nullptr; } } @@ -4541,10 +4619,9 @@ namespace olc { if (app_) application_run(app_); } - void terminate() noexcept { + void stop() noexcept { if (app_) { application_stop(app_); - app_ = nullptr; } } @@ -4665,10 +4742,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_); } } @@ -4926,6 +5003,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; @@ -5084,6 +5174,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_); @@ -5321,9 +5420,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) { @@ -5463,6 +5560,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; @@ -5547,6 +5646,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(); + }; } @@ -5632,18 +5733,39 @@ namespace olc::host #if OLC_HOST == OLC_HOST_LINUX_WAYLAND +#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_LIBDECOR +#endif + +#if !defined(FORCE_WAYLAND_LIBDECOR) +#define ENABLE_DECORATION_PROTOCOL +#endif + +#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) +#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." +#endif + #include +#include #include #include "xdg-shell.h" + +// Only include the decoration protocol if we are not forcing libdecor +#ifdef ENABLE_DECORATION_PROTOCOL #include "xdg-decoration.h" +#endif + #include "pointer-warp.h" -#include "cursor-shape.h" #include #include #include #include #include +#ifdef ENABLE_LIBDECOR +#include "libdecor.h" +#endif + #include #include @@ -5657,7 +5779,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 +5789,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}; + int configured_width{}; + int configured_height{}; + libdecor_window_state decor_window_state; + int floating_width{}; + int floating_height{}; + #endif + ~WaylandWindow(); }; namespace wayland { @@ -5705,6 +5840,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}; @@ -5712,18 +5848,28 @@ 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 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; + 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 + // libdecor support + bool using_libdecor{false}; + libdecor* decor_context{nullptr}; + std::mutex decor_mutex; + #endif public: Host_Linux_Wayland(); @@ -5788,7 +5934,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 +5978,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; @@ -5862,6 +6027,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; @@ -5983,6 +6149,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); @@ -6768,7 +6941,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); }; } } @@ -6812,6 +6986,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 @@ -6830,7 +7006,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); }; } @@ -7235,6 +7417,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; @@ -7296,25 +7479,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 +7513,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 +7609,54 @@ 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 { // 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) { @@ -7693,6 +7916,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 + TexturedBackground = (1 << 8), // Textured background + HUDWindow = (1 << 13), // Heads-up display window + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view + }; + Host_Apple_MacOS::Host_Apple_MacOS() { @@ -7882,6 +8121,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); + + // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, + // For MacOS you can only disable the buttons, you can't hide them + if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window + if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border + if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button + if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button + + return nsStyle; + + } + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; return true; @@ -7914,7 +8185,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 ----- @@ -7958,7 +8230,6 @@ namespace olc::host { pMacApplication->run(); // Once the application run loop ends, join the system thread - systemActive = false; if(threadSystem.joinable()) threadSystem.join(); @@ -7983,10 +8254,11 @@ namespace olc::host { } if (pMacApplication) { - pMacApplication->terminate(); + pMacApplication->stop(); } }); + systemActive = false; return true; } @@ -8052,7 +8324,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(); @@ -8074,7 +8346,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; } @@ -8202,9 +8480,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([]() { }); @@ -8381,8 +8663,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"; @@ -8409,6 +8689,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 @@ -8430,6 +8711,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:"; @@ -8497,18 +8780,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"; @@ -8560,6 +8831,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 @@ -8587,6 +8859,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; @@ -8654,18 +8928,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; @@ -8688,6 +8950,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 @@ -8715,7 +8978,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); @@ -8780,18 +9045,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); @@ -8940,6 +9193,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 { @@ -9046,11 +9300,7 @@ struct Application { Application() = default; - ~Application() { - if (destroy) { - destroy(this); - } - } + ~Application() {} // Delete copy constructor and assignment Application(const Application&) = delete; @@ -9106,7 +9356,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}; @@ -9150,7 +9400,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) @@ -9159,6 +9409,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}; @@ -9641,11 +9892,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 @@ -9780,9 +10034,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); } } @@ -9845,7 +10100,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 @@ -9855,9 +10110,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 @@ -10097,6 +10349,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; @@ -10273,105 +10535,130 @@ 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); + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); + + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); + CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); + CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; - if (!pathString) { + self->width = CGImageGetWidth(image); + self->height = CGImageGetHeight(image); + self->bytesPerPixel = 4; + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; + + const uint8_t* imageData = CFDataGetBytePtr(rawData); + self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); + if(!self->pixelData) + { return NO; } + memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); - // Create NSImage from file - id image = ((id(*)(id, SEL, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSImageClass, allocSel), - initWithContentsOfFileSel, pathString); - - if (!image) { - return NO; + // NOTE from Moros1138 + // + // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far + // the most common case, so in practice this block of code will never + // be run. However, if we find that there is a need to adjust the + // pixel data, this will need fleshing out. + + /* + if(byteOrder != kCGBitmapByteOrder32Little) + { + int pixelCount = self->width * self->height; + for(int i = 0; i < pixelCount; ++i) + { + auto p = self->pixelData[i]; + + // TODO: detect byte order, adjust as appropriate + + self->pixelData[i] = p; + } } - - // Get image representations - id representations = ((id(*)(id, SEL))objc_msgSend)(image, representationsSel); - NSUInteger repCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(representations, countSel); - - if (repCount == 0) { + */ + + CFRelease(rawData); + 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 unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return self->pixelData; + return (unsigned char*)self->pixelData; } // Get image information @@ -10405,13 +10692,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; @@ -10444,6 +10731,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; @@ -10828,12 +11116,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) @@ -10989,6 +11315,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"); @@ -11178,6 +11514,7 @@ namespace olc::host XWindowAttributes gwa; XGetWindowAttributes(olc_Display, win, &gwa); pWindow->olc_OnWindowSize({gwa.width, gwa.height}); + pWindow->bWindowIsFullscreen = bFullScreen; return true; } @@ -11238,10 +11575,27 @@ 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 +11605,51 @@ 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 + + // 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); + // Setup the keymap with XKB codes, which are basically the same as the X11 codes mapKeys[XKB_KEY_NoSymbol] = Key::NONE; @@ -11321,20 +11710,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); - xdg_toplevel_destroy(wayland_window.toplevel); - xdg_surface_destroy(wayland_window.surface_xdg); - 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); } @@ -11376,7 +11804,21 @@ namespace olc::host } }); + #if !defined(ENABLE_LIBDECOR) while(systemActive && wl_display_dispatch_pending(display) != -1) { } + #else + bool keep_running = true; + while(systemActive && keep_running) { + if(using_libdecor) { + if(decor_context) { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; + } + } else { + keep_running = wl_display_dispatch_pending(display) != -1; + } + } + #endif systemActive = false; if(threadSystem.joinable()) @@ -11414,49 +11856,68 @@ 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); 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); + if (!pWindow->config.bResizeable) { + xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); + xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); + } + + #ifdef ENABLE_DECORATION_PROTOCOL + w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); + zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); + zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); + #endif + #ifdef ENABLE_LIBDECOR + } else { + std::lock_guard l{decor_mutex}; + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); + } + + libdecor_frame_map(w.decor_frame); + + } + #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; } 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); - zxdg_toplevel_decoration_v1_destroy(wayland_window.decorations); - 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; } @@ -11464,7 +11925,16 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { + #ifdef ENABLE_LIBDECOR + if(using_libdecor) { + std::lock_guard l{decor_mutex}; + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); + } else { + xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + } + #else xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); + #endif } return true; } @@ -11526,7 +11996,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); } @@ -11539,11 +12009,27 @@ 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) { + 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; @@ -11554,24 +12040,27 @@ 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)); } 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)); } 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) @@ -11583,8 +12072,7 @@ 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); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { @@ -11596,26 +12084,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) @@ -11755,7 +12259,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); } @@ -12010,11 +12514,102 @@ 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; + auto& olc_window = mapUID2OlcWindow.at(i.first); + + 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 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); + } + } + } + + 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) { @@ -12322,6 +12917,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 { @@ -12946,12 +13556,12 @@ 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"); + host->pgeWindow->olc_OnFocus(true); } break; case APP_CMD_LOST_FOCUS: { - host->pgeWindow->olc_OnMouseFocus(false); LOGD("APP_CMD_LOST_FOCUS received"); + host->pgeWindow->olc_OnFocus(false); } break; default: break; } @@ -13300,7 +13910,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) @@ -14330,10 +14954,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 @@ -16106,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(); @@ -16337,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) { @@ -16805,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 @@ -16983,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; } @@ -17002,6 +17807,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)); @@ -17194,7 +18003,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; } @@ -17299,6 +18125,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()) @@ -17516,7 +18344,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; } } @@ -18133,7 +18961,11 @@ namespace olc Window::Window() { nUniqueID = pgeguts::CreateUID(); - + } + + Window::Window(const WindowConfig& config) : config{config} + { + Window(); } Window::~Window() @@ -18322,15 +19154,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; @@ -18339,13 +19163,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; } @@ -18360,6 +19193,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 @@ -18406,17 +19254,51 @@ namespace olc::imload std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); return true; - } bool ImageLoader_MacOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - 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) @@ -18431,100 +19313,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 @@ -18538,7 +19388,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