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