diff --git a/README.md b/README.md index f0702b7..f54f5d2 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Tested On Platforms * Windows * Linux X11/Wayland * MacOS + * iOS * Emscripten * Android diff --git a/dev/cmake/Helpers.cmake b/dev/cmake/Helpers.cmake index e9da4e6..b4d5da0 100644 --- a/dev/cmake/Helpers.cmake +++ b/dev/cmake/Helpers.cmake @@ -103,7 +103,15 @@ function(add_example EXAMPLE_NAME) if(APPLE) if(CMAKE_SYSTEM_NAME STREQUAL "iOS") - # TODO: Building for iOS + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework UIKit") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework Foundation") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework CoreGraphics") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework QuartzCore") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework Metal") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework MetalKit") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework OpenGLES") + target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework GLKit") + else() target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework Metal") target_link_libraries(${EXAMPLE_NAME} PRIVATE "-framework MetalKit") diff --git a/dev/src/api_ios.cpp b/dev/src/api_ios.cpp new file mode 100644 index 0000000..aa3b22f --- /dev/null +++ b/dev/src/api_ios.cpp @@ -0,0 +1,2309 @@ +#include "api_ios.h" + +//! START IMPLEMENTATION + +// UIApplication and related class names +static constexpr const char* kNSAutoreleasePoolClass = "NSAutoreleasePool"; +static constexpr const char* kUIApplicationClass = "UIApplication"; +static constexpr const char* kUIViewControllerClass = "UIViewController"; +static constexpr const char* kSetRootViewControllerSel = "setRootViewController:"; +static constexpr const char* kUIViewClass = "UIView"; +static constexpr const char* kUIWindowClass = "UIWindow"; +static constexpr const char* kUIScreenClass = "UIScreen"; +static constexpr const char* kNSStringClass = "NSString"; +static constexpr const char* kEAGLContextClass = "EAGLContext"; +static constexpr const char* kGLKViewClass = "GLKView"; +static constexpr const char* kGLKViewControllerClass = "GLKViewController"; +static constexpr const char* kNSObjectClass = "NSObject"; +static constexpr const char* kUIImageClass = "UIImage"; +static constexpr const char* kAppDelegateClass = "AppDelegate"; // This is the OS default AppDelegate class name +static constexpr const char* kolcPGE3AppDelegateClass = "olcPGE3AppDelegate"; // Important: Custom AppDelegate subclass name +static constexpr const char* kViewControllerDelegateClass = "ViewControllerDelegate"; +static constexpr const char* kCustomGLKViewClass = "CustomGLKView"; +static constexpr const char* kNSBundleClass = "NSBundle"; // Class for handling app bundles i.e. getting app resource paths + +// UIWindow and UIScreen selectors +static constexpr const char* kmainScreenSel = "mainScreen"; +static constexpr const char* kBoundsSelScreen = "bounds"; +static constexpr const char* kNativeBounds = "nativeBounds"; +static constexpr const char* kScaleSelScreen = "scale"; +static constexpr const char* kInitWithFrameSel = "initWithFrame:"; +static constexpr const char* kMakeKeyAndVisibleSel = "makeKeyAndVisible"; +static constexpr const char* kMaximumFramesPerSecond = "maximumFramesPerSecond"; +static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; + +// Memory management selectors +static constexpr const char* kAllocSel = "alloc"; +static constexpr const char* kInitSel = "init"; +static constexpr const char* kDrainSel = "drain"; +static constexpr const char* kSetDelegateSel = "setDelegate:"; +static constexpr const char* kReleaseSel = "release"; +static constexpr const char* kRetainSel = "retain"; +static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; + +// UIApplication lifecycle and management selectors +static constexpr const char* kSharedApplicationSel = "sharedApplication"; +static constexpr const char* kRunSel = "run"; +static constexpr const char* kSetIdleTimerDisabledSel = "setIdleTimerDisabled:"; +static constexpr const char* kSetPausedSel = "setPaused:"; + +// UIApplicationDelegate lifecycle methods +static constexpr const char* kApplicationDidFinishLaunchingSel = "application:didFinishLaunchingWithOptions:"; +static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; +static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; +static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; +static constexpr const char* kApplicationDidEnterBackgroundSel = "applicationDidEnterBackground:"; +static constexpr const char* kApplicationWillEnterForegroundSel = "applicationWillEnterForeground:"; + +// UIViewController creation, display, and management selectors +static constexpr const char* kInitWithNibNameSel = "initWithNibName:bundle:"; +static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; +static constexpr const char* kSetTitleSel = "setTitle:"; +static constexpr const char* kViewSel = "view"; +static constexpr const char* kSetViewSel = "setView:"; +static constexpr const char* kPresentViewControllerSel = "presentViewController:animated:completion:"; +static constexpr const char* kDismissViewControllerSel = "dismissViewControllerAnimated:completion:"; +static constexpr const char* kFrameSel = "frame"; +static constexpr const char* kSetFrameSel = "setFrame:"; +static constexpr const char* kBoundsSel = "bounds"; +static constexpr const char* kSetBoundsSel = "setBounds:"; +static constexpr const char* kUpdateView = "update"; + +// GLKViewController lifecycle methods +static constexpr const char* kViewDidLoadSel = "viewDidLoad"; +static constexpr const char* kLoadViewSel = "loadView"; +static constexpr const char* kViewWillAppearSel = "viewWillAppear:"; +static constexpr const char* kViewDidAppearSel = "viewDidAppear:"; +static constexpr const char* kViewWillDisappearSel = "viewWillDisappear:"; +static constexpr const char* kViewDidDisappearSel = "viewDidDisappear:"; +static constexpr const char* kViewDidLayoutSubviewsSel = "viewDidLayoutSubviews"; + +// Touch event methods selectors +static constexpr const char* kTouchesBeganSel = "touchesBegan:withEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMoved:withEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEnded:withEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelled:withEvent:"; +static constexpr const char* kAllObjectsSel = "allObjects"; +static constexpr const char* kMajorRadius = "majorRadius"; + +// GLKView drawing methods selectors +static constexpr const char* kDrawRectSel = "drawRect:"; + +// Keyboard event methods selectors (for hardware keyboards) +static constexpr const char* kPressesBeganSel = "pressesBegan:withEvent:"; +static constexpr const char* kPressesEndedSel = "pressesEnded:withEvent:"; +static constexpr const char* kKeySel = "key"; +static constexpr const char* kKeyCodeSel = "keyCode"; +static constexpr const char* kCharactersSel = "characters"; +static constexpr const char* kModifierFlagsSel = "modifierFlags"; + +// Touch and press event property selectors +static constexpr const char* kLocationInViewSel = "locationInView:"; +static constexpr const char* kAllTouchesSel = "allTouches"; +static constexpr const char* kCountSel = "count"; +static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; + + +// EAGL and OpenGL ES management selectors +static constexpr const char* kInitWithAPISel = "initWithAPI:"; +static constexpr const char* kSetCurrentContextSel = "setCurrentContext:"; +static constexpr const char* kContextSel = "context"; +static constexpr const char* kSetContextSel = "setContext:"; +static constexpr const char* kDrawableColorFormatSel = "drawableColorFormat"; +static constexpr const char* kSetDrawableColorFormatSel = "setDrawableColorFormat:"; +static constexpr const char* kDrawableDepthFormatSel = "drawableDepthFormat"; +static constexpr const char* kSetDrawableDepthFormatSel = "setDrawableDepthFormat:"; +static constexpr const char* kDrawableMultisampleSel = "drawableMultisample"; +static constexpr const char* kSetDrawableMultisampleSel = "setDrawableMultisample:"; +static constexpr const char* kSetOpaqueSel = "setOpaque:"; +static constexpr const char* kSetContentScaleFactorSel = "setContentScaleFactor:"; +static constexpr const char* kContentScaleFactorSel = "contentScaleFactor"; +static constexpr const char* kSetEnableSetNeedsDisplaySel = "setEnableSetNeedsDisplay:"; +static constexpr const char* kSetAutoResizeDrawableSel = "setAutoResizeDrawable:"; +static constexpr const char* kSetPreferredFramesPerSecondSel = "setPreferredFramesPerSecond:"; +static constexpr const char* kBindDrawableSel = "bindDrawable"; +static constexpr const char* kDisplaySel = "display"; +static constexpr const char* kPresentRenderbufferSel = "presentRenderbuffer:"; +static constexpr const char* kSetNeedsDisplaySel = "setNeedsDisplay"; + +// Device and orientation selectors +static constexpr const char* kCurrentDeviceSel = "currentDevice"; +static constexpr const char* kOrientationSel = "orientation"; +static constexpr const char* kIsMultitouchEnabledSel = "isMultipleTouchEnabled"; +static constexpr const char* kSetMultipleTouchEnabledSel = "setMultipleTouchEnabled:"; +static constexpr const char* kSetUserInteractionEnabledSel = "setUserInteractionEnabled:"; +static constexpr const char* kIsUserInteractionEnabledSel = "isUserInteractionEnabled"; +static constexpr const char* kDeviceOrientationDidChangeSel = "deviceOrientationDidChange:"; +static constexpr const char* kBeginGenDeviceOrientationNotifSel = "beginGeneratingDeviceOrientationNotifications"; + +// Notification classes and selectors +static constexpr const char* kNSNotificationCenterClass = "NSNotificationCenter"; +static constexpr const char* kDefaultCenterSel = "defaultCenter"; +static constexpr const char* kAddObserverSel = "addObserver:selector:name:object:"; + +// Constants +static constexpr const char* kUIDeviceOrientationDidChangeNoti = "UIDeviceOrientationDidChangeNotification"; +static constexpr const char* kUIDeviceClass = "UIDevice"; + +// UIImage and image data access selectors +static constexpr const char* kInitWithContentsOfFileSel = "initWithContentsOfFile:"; +static constexpr const char* kRepresentationsSel = "representations"; +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"; +static constexpr const char* kNSBitmapImageRepClass = "NSBitmapImageRep"; +static constexpr const char* kCGImageSel = "CGImage"; +static constexpr const char* kNSDataClass = "NSData"; // Class for handling binary data, used for image loading +static constexpr const char* kdataWithBytesLengthSel = "dataWithBytes:length:"; // Selector for creating NSData from raw bytes +static constexpr const char* kImageWithDataSel = "imageWithData:"; + + +// OpenGL ES constants +static constexpr int kEAGLRenderingAPIOpenGLES3 = 3; +static constexpr int kEAGLColorFormatRGBA8 = 1; +static constexpr int kEAGLDepthFormat24 = 1; +static constexpr int kGLRenderbufferColor = 0x8CE0; // GL_COLOR_ATTACHMENT0 + +// NS Bundle selectors +static constexpr const char* kMainBundleSel = "mainBundle"; +static constexpr const char* kUTF8StringSel = "UTF8String"; +static constexpr const char* kbundlePathSel = "bundlePath"; + +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; + +// Default values and configuration settings +static constexpr const char* kWindowTitle = "C iOS OpenGLES Framework"; +static constexpr const char* kDefaultAppPath = "olcPGE3_iOS"; +static constexpr double kDefaultWindowWidth = 1280.0; // TODO: Upgate to the default ios screen size I think it is 538X402 +static constexpr double kDefaultWindowHeight = 728.0; +static constexpr int kDefaultFrameRate = 60; +static constexpr int kPreferredFrameRate = 120; +static constexpr int kBitsPerByte = 8; +static constexpr int kMinValidDimension = 0; +static constexpr int kRGBABytesPerPixel = 4; +static constexpr int kFullyOpaque = 255; +static constexpr int kZeroBytes = 0; +static constexpr int kZeroRows = 0; +static constexpr int kZeroWidth = 0; +static constexpr int kZeroHeight = 0; +static constexpr int kFlippedOffset = 1; +static constexpr int kNoButton = -1; + +// Objective-C method type encoding constants +static constexpr const char* kVoidMethodTypeEncoding = "v@:"; // Type encoding for void methods with no parameters: "v@:" +static constexpr const char* kBoolMethodTypeEncoding = "c@:"; // Type encoding for methods returning BOOL with no parameters: "c@:" +static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; // Type encoding for methods with event/notification parameter: "v@:@" +static constexpr const char* kIOSAppDelegateMethodTypeEncoding = "c@:@@"; // Type encoding for iOS app delegate methods returning BOOL: "c@:@@" (BOOL, self, _cmd, app, options) +static constexpr const char* kIOSAppMethodTypeEncoding = "v@:@"; // Type encoding for iOS app delegate methods with app parameter: "v@:@" (void, self, _cmd, app) +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@@"; // Type encoding for touch event methods with two object parameters: "v@:@@" +static constexpr const char* kClassMethodTypeEncoding = "#@:"; // Type encoding for class methods returning Class: "#@:" +static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"; // Type encoding for drawRect method with NSRect parameter: "v@:{CGRect={CGPoint=dd}{CGSize=dd}}" +static constexpr const char* kDrawGCectMethodTypeEncoding = "v@:{CGRect={CGPoint=dd}{CGSize=dd}}"; // Type encoding for drawRect method with CGRect parameter: "v@:{CGRect={CGPoint=dd}{CGSize=dd}}" +static constexpr const char* kMethodSignatureMethodTypeEncoding = "@@::"; // Type encoding for methodSignatureForSelector: returning NSMethodSignature: "@@::" + + +namespace ObjectiveCSEL { + + // Application memory management selectors + static SEL allocSel = nullptr; + static SEL initSel = nullptr; + static SEL drainSel = nullptr; + static SEL setDelegateSel = nullptr; + static SEL releaseSel = nullptr; + static SEL retainSel = nullptr; + static SEL isKindOfClassSel = nullptr; + + // UIApplication lifecycle and management selectors + static SEL sharedApplicationSel = nullptr; + static SEL runSel = nullptr; + static SEL setIdleTimerDisabledSel = nullptr; + static SEL setPausedSel = nullptr; + + // UIApplicationDelegate lifecycle methods + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; + static SEL applicationDidEnterBackgroundSel = nullptr; + static SEL applicationWillEnterForegroundSel = nullptr; + + // UIWindow and UIScreen selectors + static SEL mainScreenSel = nullptr; + static SEL boundsSelScreen = nullptr; + static SEL nativeBounds = nullptr; + static SEL scaleSelScreen = nullptr; + static SEL initWithFrameSel = nullptr; + static SEL makeKeyAndVisibleSel = nullptr; + static SEL maximumFramesPerSecond = nullptr; + static SEL setAutoresizingMaskSel = nullptr; + + // UIViewController creation, display, and management selectors + static SEL initWithNibNameSel = nullptr; + static SEL stringWithUTF8StringSel = nullptr; + static SEL setTitleSel = nullptr; + static SEL viewSel = nullptr; + static SEL setViewSel = nullptr; + static SEL presentViewControllerSel = nullptr; + static SEL dismissViewControllerSel = nullptr; + static SEL frameSel = nullptr; + static SEL setFrameSel = nullptr; + static SEL boundsSel = nullptr; + static SEL setBoundsSel = nullptr; + static SEL updateView = nullptr; + + // Root view controller selector + static SEL setRootViewControllerSel = nullptr; + + // GLKViewController lifecycle methods + static SEL viewDidLoadSel = nullptr; + static SEL loadViewSel = nullptr; + static SEL viewWillAppearSel = nullptr; + static SEL viewDidAppearSel = nullptr; + static SEL viewWillDisappearSel = nullptr; + static SEL viewDidDisappearSel = nullptr; + static SEL viewDidLayoutSubviewsSel = nullptr; + static SEL drawRectSel = nullptr; + static SEL setContentScaleFactor = nullptr; + static SEL contentScaleFactor = nullptr; + + // Touch event methods selectors + static SEL touchesBeganSel = nullptr; + static SEL touchesMovedSel = nullptr; + static SEL touchesEndedSel = nullptr; + static SEL touchesCancelledSel = nullptr; + static SEL allObjectsSel = nullptr; + static SEL majorRadius = nullptr; + + // Keyboard event methods selectors (for hardware keyboards) + static SEL pressesBeganSel = nullptr; + static SEL pressesEndedSel = nullptr; + + // Touch and press event property selectors + static SEL locationInViewSel = nullptr; + static SEL allTouchesSel = nullptr; + static SEL countSel = nullptr; + static SEL objectAtIndexSel = nullptr; + static SEL keySel = nullptr; + static SEL keyCodeSel = nullptr; + static SEL charactersSel = nullptr; + static SEL modifierFlagsSel = nullptr; + + // EAGL and OpenGL ES management selectors + static SEL initWithAPISel = nullptr; + static SEL setCurrentContextSel = nullptr; + static SEL contextSel = nullptr; + static SEL setContextSel = nullptr; + static SEL drawableColorFormatSel = nullptr; + static SEL setDrawableColorFormatSel = nullptr; + static SEL drawableDepthFormatSel = nullptr; + static SEL setDrawableDepthFormatSel = nullptr; + static SEL drawableMultisampleSel = nullptr; + static SEL setDrawableMultisampleSel = nullptr; + static SEL setEnableSetNeedsDisplaySel = nullptr; + static SEL bindDrawableSel = nullptr; + static SEL displaySel = nullptr; + static SEL presentRenderbufferSel = nullptr; + static SEL setNeedsDisplaySel = nullptr; + static SEL setPreferredFramesPerSecondSel = nullptr; + + // Device and orientation selectors + static SEL currentDeviceSel = nullptr; + static SEL orientationSel = nullptr; + static SEL isMultitouchEnabledSel = nullptr; + static SEL setMultipleTouchEnabledSel = nullptr; + static SEL setUserInteractionEnabledSel = nullptr; + static SEL isUserInteractionEnabledSel = nullptr; + static SEL deviceOrientationDidChangeSel = nullptr; + static SEL beginGenDeviceOrientationNotifSel = nullptr; + + // UI Image + static SEL initWithContentsOfFileSel = nullptr; + static SEL representationsSel = nullptr; + static SEL pixelsWideSel = nullptr; + static SEL pixelsHighSel = nullptr; + static SEL bitsPerPixelSel = nullptr; + static SEL bytesPerRowSel = nullptr; + static SEL hasAlphaSel = nullptr; + static SEL bitmapDataSel = nullptr; + static SEL cgImageSel = nullptr; + static SEL dataWithBytesLengthSel = nullptr; + static SEL imageWithDataSel = nullptr; + + // NS Bundle selectors + static SEL mainBundleSel = nullptr; + static SEL UTF8StringSel = nullptr; + static SEL bundlePathSel = nullptr; + + // NSLocale selectors + static SEL currentLocaleSel = nullptr; + static SEL localeIdentifierSel = nullptr; + + // Notification classes and selectors + static SEL defaultCenterSel = nullptr; + static SEL addObserverSel = nullptr; + + + + void initializeSelectors() { + if (allocSel) return; // Already initialized + + // Application memory management selectors + allocSel = sel_registerName(kAllocSel); + initSel = sel_registerName(kInitSel); + drainSel = sel_registerName(kDrainSel); + setDelegateSel = sel_registerName(kSetDelegateSel); + releaseSel = sel_registerName(kReleaseSel); + retainSel = sel_registerName(kRetainSel); + isKindOfClassSel = sel_registerName(kIsKindOfClassSel); + + // UIApplication lifecycle and management selectors + sharedApplicationSel = sel_registerName(kSharedApplicationSel); + runSel = sel_registerName(kRunSel); + setIdleTimerDisabledSel = sel_registerName(kSetIdleTimerDisabledSel); + setPausedSel = sel_registerName(kSetPausedSel); + + // UIApplicationDelegate lifecycle methods + applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); + applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); + applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); + applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); + applicationDidEnterBackgroundSel = sel_registerName(kApplicationDidEnterBackgroundSel); + applicationWillEnterForegroundSel = sel_registerName(kApplicationWillEnterForegroundSel); + + // UIWindow and UIScreen selectors + mainScreenSel = sel_registerName(kmainScreenSel); + boundsSelScreen = sel_registerName(kBoundsSelScreen); + nativeBounds = sel_registerName(kNativeBounds); + scaleSelScreen = sel_registerName(kScaleSelScreen); + initWithFrameSel = sel_registerName(kInitWithFrameSel); + makeKeyAndVisibleSel = sel_registerName(kMakeKeyAndVisibleSel); + maximumFramesPerSecond = sel_registerName(kMaximumFramesPerSecond); + setAutoresizingMaskSel = sel_registerName(kSetAutoresizingMaskSel); + + // UIViewController creation, display, and management selectors + initWithNibNameSel = sel_registerName(kInitWithNibNameSel); + stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); + setTitleSel = sel_registerName(kSetTitleSel); + viewSel = sel_registerName(kViewSel); + setViewSel = sel_registerName(kSetViewSel); + presentViewControllerSel = sel_registerName(kPresentViewControllerSel); + dismissViewControllerSel = sel_registerName(kDismissViewControllerSel); + frameSel = sel_registerName(kFrameSel); + setFrameSel = sel_registerName(kSetFrameSel); + boundsSel = sel_registerName(kBoundsSel); + setBoundsSel = sel_registerName(kSetBoundsSel); + updateView = sel_registerName(kUpdateView); + + // Root view controller selector + setRootViewControllerSel = sel_registerName(kSetRootViewControllerSel); + + // GLKViewController lifecycle methods + viewDidLoadSel = sel_registerName(kViewDidLoadSel); + loadViewSel = sel_registerName(kLoadViewSel); + viewWillAppearSel = sel_registerName(kViewWillAppearSel); + viewDidAppearSel = sel_registerName(kViewDidAppearSel); + viewWillDisappearSel = sel_registerName(kViewWillDisappearSel); + viewDidDisappearSel = sel_registerName(kViewDidDisappearSel); + viewDidLayoutSubviewsSel = sel_registerName(kViewDidLayoutSubviewsSel); + drawRectSel = sel_registerName(kDrawRectSel); + + // Touch event methods selectors + touchesBeganSel = sel_registerName(kTouchesBeganSel); + touchesMovedSel = sel_registerName(kTouchesMovedSel); + touchesEndedSel = sel_registerName(kTouchesEndedSel); + touchesCancelledSel = sel_registerName(kTouchesCancelledSel); + allObjectsSel = sel_registerName(kAllObjectsSel); + majorRadius = sel_registerName(kMajorRadius); + + // Keyboard event methods selectors (for hardware keyboards) + pressesBeganSel = sel_registerName(kPressesBeganSel); + pressesEndedSel = sel_registerName(kPressesEndedSel); + keySel = sel_registerName(kKeySel); + keyCodeSel = sel_registerName(kKeyCodeSel); + charactersSel = sel_registerName(kCharactersSel); + modifierFlagsSel = sel_registerName(kModifierFlagsSel); + + // Touch and press event property selectors + locationInViewSel = sel_registerName(kLocationInViewSel); + allTouchesSel = sel_registerName(kAllTouchesSel); + countSel = sel_registerName(kCountSel); + objectAtIndexSel = sel_registerName(kObjectAtIndexSel); + keySel = sel_registerName(kKeySel); + charactersSel = sel_registerName(kCharactersSel); + + // EAGL and OpenGL ES management selectors + initWithAPISel = sel_registerName(kInitWithAPISel); + setCurrentContextSel = sel_registerName(kSetCurrentContextSel); + contextSel = sel_registerName(kContextSel); + setContextSel = sel_registerName(kSetContextSel); + drawableColorFormatSel = sel_registerName(kDrawableColorFormatSel); + setDrawableColorFormatSel = sel_registerName(kSetDrawableColorFormatSel); + drawableDepthFormatSel = sel_registerName(kDrawableDepthFormatSel); + setDrawableDepthFormatSel = sel_registerName(kSetDrawableDepthFormatSel); + drawableMultisampleSel = sel_registerName(kDrawableMultisampleSel); + setDrawableMultisampleSel = sel_registerName(kSetDrawableMultisampleSel); + setEnableSetNeedsDisplaySel = sel_registerName(kSetEnableSetNeedsDisplaySel); + bindDrawableSel = sel_registerName(kBindDrawableSel); + displaySel = sel_registerName(kDisplaySel); + presentRenderbufferSel = sel_registerName(kPresentRenderbufferSel); + setNeedsDisplaySel = sel_registerName(kSetNeedsDisplaySel); + setPreferredFramesPerSecondSel = sel_registerName(kSetPreferredFramesPerSecondSel); + setContentScaleFactor = sel_registerName(kSetContentScaleFactorSel); + contentScaleFactor = sel_registerName(kContentScaleFactorSel); + + // Device and orientation selectors + currentDeviceSel = sel_registerName(kCurrentDeviceSel); + orientationSel = sel_registerName(kOrientationSel); + isMultitouchEnabledSel = sel_registerName(kIsMultitouchEnabledSel); + setMultipleTouchEnabledSel = sel_registerName(kSetMultipleTouchEnabledSel); + setUserInteractionEnabledSel = sel_registerName(kSetUserInteractionEnabledSel); + isUserInteractionEnabledSel = sel_registerName(kIsUserInteractionEnabledSel); + deviceOrientationDidChangeSel = sel_registerName(kDeviceOrientationDidChangeSel); + beginGenDeviceOrientationNotifSel = sel_registerName(kBeginGenDeviceOrientationNotifSel); + + // UI Image + initWithContentsOfFileSel = sel_registerName(kInitWithContentsOfFileSel); + representationsSel = sel_registerName(kRepresentationsSel); + pixelsWideSel = sel_registerName(kPixelsWideSel); + pixelsHighSel = sel_registerName(kPixelsHighSel); + bitsPerPixelSel = sel_registerName(kBitsPerPixelSel); + bytesPerRowSel = sel_registerName(kBytesPerRowSel); + hasAlphaSel = sel_registerName(kHasAlphaSel); + bitmapDataSel = sel_registerName(kBitmapDataSel); + cgImageSel = sel_registerName(kCGImageSel); + dataWithBytesLengthSel = sel_registerName(kdataWithBytesLengthSel); + imageWithDataSel = sel_registerName(kImageWithDataSel); + + // NS Bundle selectors + mainBundleSel = sel_registerName(kMainBundleSel); + UTF8StringSel = sel_registerName(kUTF8StringSel); + bundlePathSel = sel_registerName(kbundlePathSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + + // Notification classes and selectors + defaultCenterSel = sel_registerName(kDefaultCenterSel); + addObserverSel = sel_registerName(kAddObserverSel); + + } + + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); + } + +} + +// Single initialization function called once at startup +void initialize_ios_api() { + ObjectiveCSEL::initializeSelectors(); +} + +// Concepts safety templates for Objective-C interactions +template +concept CallbackType = std::is_function_v>; // Checks if T is a function type + +template +concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) + +// TODO: May not be required as iOS handles memory management more automatically than macOS in most cases +// Autorelease pool management functions +extern "C" { + extern void* objc_autoreleasePoolPush(void); + extern void objc_autoreleasePoolPop(void* pool); + extern void* NSStringFromClass(Class aClass); + // Add UIApplicationMain declaration + extern int UIApplicationMain(int argc, char* argv[], id principalClassName, id delegateClassName); +} + +// Objective-C autorelease pools +class AutoreleasePool { +public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } + + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; + +private: + void* pool_; +}; + +// TODO: End + +// Keeps our C --> Objective-C function pointer types clean +using NSInteger = long; +using NSUInteger = unsigned long; + +/* +* WARNING: Global delegate pointers for Objective-C callbacks +* These global pointers are used to route Objective-C delegate callbacks +* to the appropriate C++ structures and methods. +* +* In short: Don't mess with these unless you know what you're doing! +* +* Global variables are evil, but sometimes necessary for bridging C++ and Objective-C. +*/ +static ViewController* gptrVeiwControllerEvents = nullptr; +static GLKView* gptrGLKViewEvents = nullptr; +static Application* gptrApplicationDelegate = nullptr; +static ViewController* gptrViewControllerDelegate = nullptr; + +// TODO: May not be needed +// Forward declarations of internal structures +struct Application; +struct ApplicationDelegate; +void updateView(id self, SEL _cmd); +void drawRect(id self, SEL _cmd, CGRect rect); +id createCustomGLKView(CGRect frame); +void viewcontroller_enableFlexibleSizing(struct ViewController* self); +struct ViewController; +struct ViewControllerDelegate; +struct OpenGLRenderer; +struct ImageLoader; + +// Forward declarations of internal structures +struct Application { + id uiApp{nullptr}; // UIApplication instance + id delegate{nullptr}; // ApplicationDelegate + id uiWindow{nullptr}; // UIWindow instance + + // Callback function pointers + std::function willFinishLaunchingCallback{nullptr}; + std::function didFinishLaunchingCallback{nullptr}; + std::function willTerminateCallback{nullptr}; + std::function didBecomeActiveCallback{nullptr}; + std::function willResignActiveCallback{nullptr}; + std::function didEnterBackgroundCallback{nullptr}; + std::function willEnterForegroundCallback{nullptr}; + + void* willFinishLaunchingUserData{nullptr}; // User data for will finish launching callback + void* didFinishLaunchingUserData{nullptr}; // User data for did finish launching callback + void* willTerminateUserData{nullptr}; // User data for will terminate callback + void* didBecomeActiveUserData{nullptr}; // User data for did become active callback + void* willResignActiveUserData{nullptr}; // User data for will resign active callback + void* didEnterBackgroundUserData{nullptr}; // User data for did enter background callback + void* willEnterForegroundUserData{nullptr}; // User data for will enter foreground + + // Method function pointers + // TODO: Update order of start upo events to suit iOS lifecycle + void (*initialize) (struct Application* self){nullptr}; + void (*activate) (struct Application* self){nullptr}; + void (*run) (struct Application* self){nullptr}; + void (*destroy) (struct Application* self){nullptr}; + + std::string appPath = ""; // Application path + const char* (*getAppPath) (struct Application* self); + + Application() = default; + + ~Application() { + if (destroy) { + destroy(this); + } + } + + // Delete copy constructor and assignment + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + // Allow move semantics + Application(Application&&) = default; + Application& operator=(Application&&) = default; + +}; + +// Handles UIApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; + +struct ViewController { + id viewController; // UIViewController instance + id glkview; // GLKView instance + id delegate; // ViewControllerDelegate instance + char* title; // Title of the view controller + CGRect frame; // Frame of the view controller + struct GLKView* glkViewStruct; // Reference to GLKView struct for touch handling + + // Lifecycle callbacks + std::function viewDidLoadCallback{nullptr}; + std::function viewWillAppearCallback{nullptr}; + std::function viewDidAppearCallback{nullptr}; + std::function viewWillDisappearCallback{nullptr}; + std::function viewDidDisappearCallback{nullptr}; + std::function viewDidLayoutSubviewsCallback{nullptr}; + std::function orientationChangedCallback{nullptr}; + std::function updateCallback{nullptr}; + + + void* viewDidLoadUserData{nullptr}; // User data for viewDidLoad callback + void* viewWillAppearUserData{nullptr}; // User data for viewWillAppear callback + void* viewDidAppearUserData{nullptr}; // User data for viewDidAppear callback + void* viewWillDisappearUserData{nullptr}; // User data for viewWillDisappear callback + void* viewDidDisappearUserData{nullptr}; // User data for viewDidDisappear callback + void* viewDidLayoutSubviewsUserData{nullptr}; // User data for viewDidLayout + void* orientationChangedUserData{nullptr}; // User data for orientation changed callback + void* updateUserData{nullptr}; // User data for update callback + +}; + +struct GLKView { + id glkView; // GLKView instance + struct ViewController* parentViewController; // Reference to parent view controller + + // Touch callbacks + std::function touchBeganCallback{nullptr}; + std::function touchMovedCallback{nullptr}; + std::function touchEndedCallback{nullptr}; + std::function touchCancelledCallback{nullptr}; + + + void* touchBeganUserData {nullptr}; // User data for touch began callback + void* touchMovedUserData {nullptr}; // User data for touch moved callback + void* touchEndedUserData {nullptr}; // User data for touch ended callback + void* touchCancelledUserData {nullptr}; // User data for touch cancelled callback + + // Event callback function pointers with nullptr initialization + std::function keyDownCallback{nullptr}; + std::function keyUpCallback{nullptr}; + + void* keyDownUserData{nullptr}; // User data for key down callback + void* keyUpUserData {nullptr}; // User data for key up callback + + // Draw callback function pointer + std::function drawCallback{nullptr}; + void* drawUserData{nullptr}; // User data for draw callback +}; + +struct ViewControllerDelegate { + Class isa; // Objective-C class pointer (required) +}; + +struct OpenGLRenderer { + id eaglContext {nullptr}; // EAGLContext instance + id eaglView {nullptr}; // Custom UIView with CAEAGLLayer + id viewController{nullptr}; // UIViewController instance + id glkView {nullptr}; // GLKView + id displayLink {nullptr}; // CADisplayLink instance + BOOL vsyncEnabled{YES}; // VSync enabled flag + unsigned int framebuffer{0}; // OpenGL framebuffer ID + unsigned int colorRenderbuffer{0}; // Color renderbuffer ID + unsigned int depthRenderbuffer{0}; // Depth renderbuffer ID + +}; + +// Modern image loading and pixel data extraction +struct ImageLoader { + + unsigned char* 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) + int bytesPerRow {kZeroRows}; // Number of bytes per row + BOOL hasAlpha {NO}; // Whether image has alpha channel + std::string appPath = ""; // Application path + + // Method function pointers with nullptr initialization + char (*getAppPath) (const struct ImageLoader* self){nullptr}; + BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; + 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}; + void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; + BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; + BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; + unsigned int (*createOpenGLTexture) (const struct ImageLoader* self){nullptr}; +}; + +// TODO: Update MacOS to use these safe Objective-C functions for better error handling and safety +static Class safe_objc_getClass(const char* name) { + Class cls = objc_getClass(name); + if (!cls) { + printf("Error: Could not find class %s\n", name); + return nil; + } + return cls; +} + +// TODO: Remove Unused +static SEL safe_sel_registerName(const char* name) { + SEL sel = sel_registerName(name); + if (!sel) { + printf("Error: Could not register selector %s\n", name); + return NULL; + } + return sel; +} + +static id safe_objc_msgSend(id self, SEL op) { + if (!self || !op) return nil; + return ((id(*)(id, SEL))objc_msgSend)(self, op); +} + +static id safe_objc_msgSend_id(id self, SEL op, id arg) { + if (!self || !op) return nil; + return ((id(*)(id, SEL, id))objc_msgSend)(self, op, arg); +} + +static void safe_objc_msgSend_void(id self, SEL op) { + if (!self || !op) return; + ((void(*)(id, SEL))objc_msgSend)(self, op); +} + +static void safe_objc_msgSend_void_bool(id self, SEL op, BOOL arg) { + if (!self || !op) return; + ((void(*)(id, SEL, BOOL))objc_msgSend)(self, op, arg); +} + +// TODO: End + + +// ============================================================================ +// IMPLEMENTATION OF C API FUNCTIONS +// ============================================================================ + + +// Called when application is about to terminate +void applicationWillTerminate(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->willTerminateCallback) { + gptrApplicationDelegate->willTerminateCallback(gptrApplicationDelegate->willTerminateUserData); + } +} + + + +// Called when application becomes active +void applicationDidBecomeActive(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->didBecomeActiveCallback) { + gptrApplicationDelegate->didBecomeActiveCallback(gptrApplicationDelegate->didBecomeActiveUserData); + } +} + +// Called when application resigns active status +void applicationWillResignActive(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->willResignActiveCallback(gptrApplicationDelegate->willResignActiveUserData); + } +} + + +// Called when application resigns active status +void applicationDidEnterBackground(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->didEnterBackgroundCallback(gptrApplicationDelegate->didEnterBackgroundUserData); + } +} + +// Called when application resigns active status +void applicationWillEnterForeground(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->willEnterForegroundCallback(gptrApplicationDelegate->willEnterForegroundUserData); + } +} + + + +// Called when application has finished launching - iOS version +BOOL applicationDidFinishLaunchingWithOptions(id self, SEL _cmd, id application, id launchOptions) { + (void)self;(void)_cmd;(void)application;(void)launchOptions; + + if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { + gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); + } + + return YES; // Must return BOOL for iOS delegate method +} + + + +// Create and configure application delegate +id createApplicationDelegate(void) { + + // Get NSObject class as superclass + Class NSObjectClass = objc_getClass(kNSObjectClass); + // Create a new class inheriting from NSObject using const string + Class AppDelegateClass = objc_allocateClassPair(NSObjectClass, kolcPGE3AppDelegateClass, 0); + + // Add application delegate methods + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillTerminateSel, (IMP)applicationWillTerminate, kIOSAppMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidBecomeActiveSel, (IMP)applicationDidBecomeActive, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillResignActiveSel, (IMP)applicationWillResignActive, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidEnterBackgroundSel, (IMP)applicationDidEnterBackground, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillEnterForegroundSel, (IMP)applicationWillEnterForeground, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidFinishLaunchingSel, (IMP)applicationDidFinishLaunchingWithOptions, kIOSAppDelegateMethodTypeEncoding); + + // Register the class with the runtime + objc_registerClassPair(AppDelegateClass); + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(AppDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + // Retain the delegate to ensure it stays alive + if (delegate) { + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::retainSel); + } + + return delegate; +} + + + +// Application API Implementation +struct Application* application_init(void) { + + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + + Application* app = new Application(); + + // Assign the method pointers + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getAppPath = application_getApplicationPath; + + return app; +} + + + + +char* get_application_path(void) +{ + const char* pathCString = kDefaultAppPath; + + // Get application path using NSBundle + Class bundleClass = objc_getClass(kNSBundleClass); + + // Get main bundle + id mainBundle = ((id(*)(Class, SEL))objc_msgSend)(bundleClass, ObjectiveCSEL::mainBundleSel); + + if (mainBundle) { + // Get bundle path as NSString + id bundlePath = ((id(*)(id, SEL))objc_msgSend)(mainBundle, ObjectiveCSEL::bundlePathSel); + + if (bundlePath) { + // Convert NSString to C string + pathCString = ((const char*(*)(id, SEL))objc_msgSend)(bundlePath, ObjectiveCSEL::UTF8StringSel); + } + } + return const_cast(pathCString); +} + +// iOS specific application start and run function +int run_ios_application(int argc, char* argv[]) { + + // Get NSAutoreleasePool for memory management + Class poolClass = objc_getClass(kNSAutoreleasePoolClass); + + // Get the application pool instance + id poolInstance = ((id(*)(Class, SEL))objc_msgSend)(poolClass, ObjectiveCSEL::allocSel); + id pool = ((id(*)(id, SEL))objc_msgSend)(poolInstance, ObjectiveCSEL::initSel); + + // Get PGE AppDelegate class so that iOS Event fires on our host_apple_ios + Class appDelegateClass = objc_getClass(kolcPGE3AppDelegateClass); + id delegateClassName = (id)NSStringFromClass(appDelegateClass); + + // Call UIApplicationMain to start running with our delegate + int result = UIApplicationMain(argc, argv, nil, delegateClassName); + + // Clean up + ((void(*)(id, SEL))objc_msgSend)(pool, ObjectiveCSEL::drainSel); + + return result; +} + +void application_initialize(struct Application* self) { + + // Initialize cached selectors + initialize_ios_api(); + + // Set global application reference + gptrApplicationDelegate = self; + + // Create and set application delegate + self->delegate = createApplicationDelegate(); + + // Set the appPath + self->appPath = get_application_path(); + + // Set the correct args + int argc = 1; + char* argv[2]; + argv[0] = const_cast(self->appPath.c_str()); + argv[1] = nullptr; + + // Start and run the iOS application + int exitCode = run_ios_application(argc, argv); + (void)exitCode; // Currently unused, but could be logged or handled + + } + + + +void application_activate(struct Application* self) { + if(!self) return; + + // Create window programmatically + Class windowClass = objc_getClass(kUIWindowClass); + Class screenClass = objc_getClass(kUIScreenClass); + + // Get main screen bounds + id mainScreen = ((id(*)(Class, SEL))objc_msgSend)(screenClass,ObjectiveCSEL::mainScreenSel); + CGRect screenBounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::boundsSel); + + // Create window + id window = ((id(*)(Class, SEL))objc_msgSend)(windowClass, ObjectiveCSEL::allocSel); + window = ((id(*)(id, SEL, CGRect))objc_msgSend)(window, ObjectiveCSEL::initWithFrameSel, screenBounds); + + // Create a basic view controller as root view controller + Class vcClass = objc_getClass(kUIViewControllerClass); + id rootViewController = ((id(*)(Class, SEL))objc_msgSend)(vcClass, ObjectiveCSEL::allocSel); + rootViewController = ((id(*)(id, SEL))objc_msgSend)(rootViewController, ObjectiveCSEL::initSel); + + // Set root view controller + ((void(*)(id, SEL, id))objc_msgSend)(window, ObjectiveCSEL::setRootViewControllerSel, rootViewController); + + // Make window visible + ((void(*)(id, SEL))objc_msgSend) (window, ObjectiveCSEL::makeKeyAndVisibleSel); + + // Store window reference + self->uiWindow = window; + + // Activate the UIApplication once (There can only be one!) + if(!self->uiApp) + { + Class UIApplicationClass = objc_getClass(kUIApplicationClass); + self->uiApp = ((id(*)(Class, SEL))objc_msgSend)(UIApplicationClass, ObjectiveCSEL::sharedApplicationSel); + + // Disable idle timer to prevent screen from dimming during gameplay + safe_objc_msgSend_void_bool(self->uiApp, ObjectiveCSEL::setIdleTimerDisabledSel, YES); + } + + +} +void application_run(struct Application* self) { + if (!self || !self->uiApp) return; + + // In iOS, we don't typically call UIApplicationMain from here + // The main run loop is handled by the system + // This is more of a placeholder for consistency with macOS API + // Left in for potential future use or custom run loop handling if needed +} + +void application_destroy(struct Application* self) { + if (!self) return; + + if (self->delegate) { + safe_objc_msgSend_void(self->delegate, ObjectiveCSEL::releaseSel); + } + + free(self); +} + +const char* application_getApplicationPath(Application* self) { + (void)self; + return get_application_path(); +} + +// Get system locale identifier +const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::UTF8StringSel); + + return localeIdentifier; +} + +// ============================================================================ +// TOUCH AND KEYBOARD EVENT HANDLER IMPLEMENTATIONS +// ============================================================================ + + +// iOS Touch Event Data Structure +struct iOSTouchEventData { + uint32_t touchID = 0; // Stable sequential touch ID + CGPoint location = {0.0, 0.0}; // Touch location in view coordinates + CGSize touchSize = {1.0, 1.0}; // Touch size (width, height) + bool bIsActive = true; // Touch is active (true for Began/Moved, false for Ended/Cancelled) +}; + +// Maps UITouch object pointers to stable uint32_t touch IDs +static std::unordered_map siOSTouchIDMap; +static uint32_t siOSNextTouchID = 0; +static std::unordered_map siOSTouchIDEvents; + +// Convert touch location to physical pixel coordinates for PGE +void convertToContentViewCoordinates(CGPoint& location, id view) { + if (!view) return; + + // Get the content scale factor for Retina displays (1.0, 2.0, or 3.0) + CGFloat scale = ((CGFloat(*)(id, SEL))objc_msgSend)(view, ObjectiveCSEL::contentScaleFactor); + + // Convert from logical points to physical pixels + location.x *= scale; + location.y *= scale; + +} + +// Iterate through touches, extract data, and fire the callback +// bRemove: true when processing Ended or Cancelled events (clean up touch tracking) +static void updateiOSTouchData(id touches, + const std::function& callback, + void* userData, bool bRemove = false) +{ + if (!callback) return; + + NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touches, ObjectiveCSEL::countSel); + if (count == 0) return; + + id touchArray = ((id(*)(id, SEL))objc_msgSend)(touches, ObjectiveCSEL::allObjectsSel); + + for (NSUInteger i = 0; i < count; ++i) { + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + + iOSTouchEventData data; + + // Get the view from the touch + id touchView = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::viewSel); + + // Get touch location in view coordinates (logical points) + CGPoint location = ((CGPoint(*)(id, SEL, id))objc_msgSend)(touch, ObjectiveCSEL::locationInViewSel, touchView); + + // Convert to physical pixels and flip Y coordinate for OpenGL/PGE + convertToContentViewCoordinates(location, touchView); + + // get the UITouch Major and Minor radius (touch size) if available, otherwise default to 1.0 + CGFloat majorRadius = 1.0; + if (touch && ObjectiveCSEL::majorRadius) { + majorRadius = ((CGFloat(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::majorRadius); + } + + + data.location = location; + data.touchSize = {majorRadius, majorRadius}; + data.bIsActive = !bRemove; + + // Assign a stable sequential uint32_t ID using the UITouch object pointer as key + uintptr_t key = reinterpret_cast(touch); + + auto [it, inserted] = siOSTouchIDMap.emplace(key, siOSNextTouchID); + if (inserted) ++siOSNextTouchID; + uint32_t tid = it->second; + + data.touchID = tid; + siOSTouchIDEvents.emplace(key, data); + + // Fire the callback with the stable touch ID and coordinates + callback(tid, data.location.x, data.location.y, data.touchSize.width, data.touchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) { + siOSTouchIDEvents.erase(key); + siOSTouchIDMap.erase(key); + } + } + + // Reset touch ID counter when all touches are released + if (bRemove && siOSTouchIDMap.size() == 0) { + siOSNextTouchID = 0; + } +} + +// Touch event handlers - called by Objective-C runtime +void touchesBegan(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchBeganCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchBeganCallback, gptrGLKViewEvents->touchBeganUserData, false); +} + +void touchesMoved(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchMovedCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchMovedCallback, gptrGLKViewEvents->touchMovedUserData, false); +} + +void touchesEnded(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchEndedCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchEndedCallback, gptrGLKViewEvents->touchEndedUserData, true); +} + +void touchesCancelled(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchCancelledCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchCancelledCallback, gptrGLKViewEvents->touchCancelledUserData, true); +} + +// Structure to hold common key event data +struct KeyEventData { + uint16_t keyCode = 0; + const char* characters = nullptr; + uint modifierFlags = 0; +}; + +// Extract common key event data from NSEvent and press +KeyEventData extractKeyEventData(id event, id press) { + KeyEventData data; + id key = ((id(*)(id, SEL))objc_msgSend)(press, ObjectiveCSEL::keySel); + data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(key, ObjectiveCSEL::keyCodeSel); + id characters = ((id(*)(id, SEL))objc_msgSend)(key, ObjectiveCSEL::charactersSel); + data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::UTF8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + // printf("Key Event: keyCode=%u, characters=%s, modifierFlags=%u\n", data.keyCode, data.characters ? data.characters : "null", data.modifierFlags); + return data; +} + +// Keyboard event handlers +void pressesBegan(id self, SEL _cmd, id presses, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !presses) return; // edge case with presses been null when bluetooth keyboard is disconnected + + // Get an NS array of all presses from the NSSet + id pressesArray = ((id(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::allObjectsSel); + + NSUInteger pressCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::countSel); + if (pressCount == 0) return; + + for(NSUInteger i = 0; i < pressCount; i++) + { + id press = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(pressesArray, ObjectiveCSEL::objectAtIndexSel, i); + + KeyEventData data = extractKeyEventData(event, press); + + if (gptrGLKViewEvents->keyDownCallback) { + gptrGLKViewEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrGLKViewEvents->keyDownUserData); + } + } + +} + +void pressesEnded(id self, SEL _cmd, id presses, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !presses) return; // edge case with presses been null when bluetooth keyboard is disconnected + + // Get an NS array of all presses from the NSSet that have ended + id pressesArray = ((id(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::allObjectsSel); + + NSUInteger pressCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::countSel); + if (pressCount == 0) return; + + for(NSUInteger i = 0; i < pressCount; i++) + { + id press = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(pressesArray, ObjectiveCSEL::objectAtIndexSel, i); + + KeyEventData data = extractKeyEventData(event, press); + + if (gptrGLKViewEvents->keyUpCallback) { + gptrGLKViewEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrGLKViewEvents->keyUpUserData); + + } + + } +} + + +// View Controller lifecycle event handlers +void viewDidLoad(id self, SEL _cmd) { + (void)self; (void)_cmd; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidLoadCallback) { + gptrViewControllerDelegate->viewDidLoadCallback(gptrViewControllerDelegate->viewDidLoadUserData); + } else { + printf("No viewDidLoadCallback set or gptrViewControllerDelegate is null\n"); + } +} + +void device_getScreenBounds(CGRect* rect) { + + Class screenClass = safe_objc_getClass(kUIScreenClass); + + id mainScreen = safe_objc_msgSend((id)screenClass, ObjectiveCSEL::mainScreenSel); + if (!mainScreen) { + return; + } + + // Try nativeBounds first (returns points in native orientation) + CGRect nativeBounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::nativeBounds); + + if (nativeBounds.size.width > 0 && nativeBounds.size.height > 0) { + *rect = nativeBounds; + } else { + // Fallback to regular bounds + CGRect bounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::boundsSel); + *rect = bounds; + } +} + +void loadView(id self, SEL _cmd) { + (void)self; (void)_cmd; + + device_getScreenBounds(&gptrVeiwControllerEvents->frame); + + if (gptrVeiwControllerEvents) { + // Create the GLKView with the stored frame + gptrVeiwControllerEvents->glkview = createCustomGLKView(gptrVeiwControllerEvents->frame); + if (gptrVeiwControllerEvents->glkview) { + // Set it as the view + safe_objc_msgSend_id(self, ObjectiveCSEL::setViewSel, gptrVeiwControllerEvents->glkview); + + // Initialize GLKView struct for touch event handling + gptrVeiwControllerEvents->glkViewStruct = glkview_init(gptrVeiwControllerEvents); + if (gptrVeiwControllerEvents->glkViewStruct) { + // Enable auto-sizing + viewcontroller_enableFlexibleSizing(gptrVeiwControllerEvents); + } else { + printf("Failed to initialize GLKView struct in loadView\n"); + } + } else { + printf("Failed to create GLKView in loadView\n"); + } + } else { + printf("gptrVeiwControllerEvents is null in loadView\n"); + } +} + +void viewWillAppear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewWillAppearCallback) { + gptrViewControllerDelegate->viewWillAppearCallback(gptrViewControllerDelegate->viewWillAppearUserData); + } +} + +void viewDidAppear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidAppearCallback) { + gptrViewControllerDelegate->viewDidAppearCallback(gptrViewControllerDelegate->viewDidAppearUserData); + } +} + +void viewWillDisappear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewWillDisappearCallback) { + gptrViewControllerDelegate->viewWillDisappearCallback(gptrViewControllerDelegate->viewWillDisappearUserData); + } +} + +void viewDidDisappear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidDisappearCallback) { + gptrViewControllerDelegate->viewDidDisappearCallback(gptrViewControllerDelegate->viewDidDisappearUserData); + } +} + +void viewDidLayoutSubviews(id self, SEL _cmd) { + (void)self; (void)_cmd; + + device_getScreenBounds(&gptrVeiwControllerEvents->frame); + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidLayoutSubviewsCallback) { + gptrViewControllerDelegate->viewDidLayoutSubviewsCallback(gptrViewControllerDelegate->viewDidLayoutSubviewsUserData); + } +} + +void updateView(id self, SEL _cmd) { + (void)self; (void)_cmd; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->updateCallback) { + gptrViewControllerDelegate->updateCallback(gptrViewControllerDelegate->updateUserData); + } +} + +// Update the orientation change handler to receive NSNotification +void viewOrientationChanged(id self, SEL _cmd, id notification) { + (void)self; (void)_cmd; (void)notification; + + if (!gptrViewControllerDelegate) return; + + // Get current device orientation + Class deviceClass = objc_getClass(kUIDeviceClass); + + id currentDevice = ((id(*)(Class, SEL))objc_msgSend)(deviceClass, ObjectiveCSEL::currentDeviceSel); + int orientation = ((int(*)(id, SEL))objc_msgSend)(currentDevice, ObjectiveCSEL::orientationSel); + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->orientationChangedCallback) { + gptrViewControllerDelegate->orientationChangedCallback(orientation, gptrViewControllerDelegate->orientationChangedUserData); + } +} + +// Create custom GLKViewController subclass with event handling +id createCustomViewController(void) { + // Check if class already exists to avoid duplicate registration + Class CustomViewControllerClass = objc_getClass(kViewControllerDelegateClass); + + if (!CustomViewControllerClass) { + // Create custom GLKViewController subclass only if it doesn't exist + Class GLKViewControllerClass = objc_getClass(kGLKViewControllerClass); + CustomViewControllerClass = objc_allocateClassPair(GLKViewControllerClass, kViewControllerDelegateClass, 0); + + if (!CustomViewControllerClass) { + printf("Failed to create custom GLKViewController class\n"); + return nil; + } + + // Add keyboard event methods + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::pressesBeganSel, (IMP)pressesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::pressesEndedSel, (IMP)pressesEnded, kTouchEventMethodTypeEncoding); + + // Add lifecycle methods + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::updateView, (IMP)updateView, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::loadViewSel, (IMP)loadView, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidLoadSel, (IMP)viewDidLoad, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewWillAppearSel, (IMP)viewWillAppear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidAppearSel, (IMP)viewDidAppear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewWillDisappearSel, (IMP)viewWillDisappear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidDisappearSel, (IMP)viewDidDisappear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidLayoutSubviewsSel, (IMP)viewDidLayoutSubviews, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::deviceOrientationDidChangeSel, (IMP)viewOrientationChanged, kEventHandlerMethodTypeEncoding); + + + // Register the class + objc_registerClassPair(CustomViewControllerClass); + + } + + // Create instance + id viewController = ((id(*)(Class, SEL))objc_msgSend)(CustomViewControllerClass, ObjectiveCSEL::allocSel); + viewController = ((id(*)(id, SEL))objc_msgSend)(viewController, ObjectiveCSEL::initSel); + + if (!viewController) { + printf("Failed to create GLKViewController instance\n"); + return nil; + } + return viewController; +} + +void drawRect(id self, SEL _cmd, CGRect rect) { + (void)self; (void)_cmd; (void)rect; + + if (gptrGLKViewEvents && gptrGLKViewEvents->drawCallback) { + gptrGLKViewEvents->drawCallback(rect.origin.x, rect.origin.y, + rect.size.width, rect.size.height, + gptrGLKViewEvents->drawUserData); + } +} + +// Create custom GLKView subclass for touch handling +id createCustomGLKView(CGRect frame) { + // Check if class already exists to avoid duplicate registration + Class CustomGLKViewClass = objc_getClass(kCustomGLKViewClass); + + if (!CustomGLKViewClass) { + // Create custom GLKView subclass only if it doesn't exist + Class GLKViewClass = objc_getClass(kGLKViewClass); + CustomGLKViewClass = objc_allocateClassPair(GLKViewClass, kCustomGLKViewClass, 0); + + if (!CustomGLKViewClass) { + printf("Failed to create custom GLKView class\n"); + return nil; + } + + // Add touch event methods to the view + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)touchesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)touchesMoved, kTouchEventMethodTypeEncoding); + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)touchesEnded, kTouchEventMethodTypeEncoding); + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)touchesCancelled, kTouchEventMethodTypeEncoding); + + // Add draw methods + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::drawRectSel, (IMP)drawRect, kDrawRectMethodTypeEncoding); + + // Register the class + objc_registerClassPair(CustomGLKViewClass); + + } + + // Create instance + id view = ((id(*)(Class, SEL))objc_msgSend)(CustomGLKViewClass, ObjectiveCSEL::allocSel); + view = ((id(*)(id, SEL, CGRect))objc_msgSend)(view, ObjectiveCSEL::initWithFrameSel, frame); + + if (!view) { + printf("Failed to create GLKView instance\n"); + return nil; + } + + // Enable user interaction (disabled by default on GLKView) + ((void(*)(id, SEL, BOOL))objc_msgSend)(view, ObjectiveCSEL::setUserInteractionEnabledSel, YES); + + // Enable multiple touch support + ((void(*)(id, SEL, BOOL))objc_msgSend)(view, ObjectiveCSEL::setMultipleTouchEnabledSel, YES); + + return view; +} + +// GLKView API Implementation +struct GLKView* glkview_init(struct ViewController* viewController) +{ + // TODO: Remove second call to this + if(gptrVeiwControllerEvents->glkViewStruct != nullptr) + return gptrVeiwControllerEvents->glkViewStruct; + + if (!viewController || !viewController->glkview) { + printf("Error: Invalid view controller or GLKView not initialized\n"); + return nullptr; + } + + struct GLKView* glkViewStruct = (struct GLKView*)malloc(sizeof(struct GLKView)); + if (!glkViewStruct) { + printf("Error: Failed to allocate GLKView struct\n"); + return nullptr; + } + + memset(glkViewStruct, 0, sizeof(struct GLKView)); + glkViewStruct->glkView = viewController->glkview; + glkViewStruct->parentViewController = viewController; + + // Set up global pointer for touch event routing + gptrGLKViewEvents = glkViewStruct; + + return glkViewStruct; +} + +void glkview_destroy(struct GLKView* self) { + if (!self) return; + + if (gptrGLKViewEvents == self) { + gptrGLKViewEvents = nullptr; + } + + free(self); +} + +void* glkview_getNativeView(const struct GLKView* self) { + if (!self) return nullptr; + return self->glkView; +} + +void glkview_setFrame(struct GLKView* self, double x, double y, double width, double height) { + if (!self || !self->glkView) return; + + CGRect frame = CGRectMake(x, y, width, height); + typedef void (*SetFrameFunc)(id, SEL, CGRect); + ((SetFrameFunc)objc_msgSend)(self->glkView, ObjectiveCSEL::setFrameSel, frame); +} + +void glkview_getFrame(const struct GLKView* self, double* x, double* y, double* width, double* height) { + if (!self || !self->glkView) return; + + CGRect frame = ((CGRect(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::setFrameSel); + if (x) *x = frame.origin.x; + if (y) *y = frame.origin.y; + if (width) *width = frame.size.width; + if (height) *height = frame.size.height; +} + +void glkview_setMultipleTouchEnabled(struct GLKView* self, BOOL enabled) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setMultipleTouchEnabledSel, enabled); +} + +BOOL glkview_isMultipleTouchEnabled(const struct GLKView* self) { + if (!self || !self->glkView) return NO; + return ((BOOL(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::isMultitouchEnabledSel); +} + +void glkview_enableTouchHandling(struct GLKView* self) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setUserInteractionEnabledSel, YES); +} + +void glkview_disableTouchHandling(struct GLKView* self) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setUserInteractionEnabledSel, NO); +} + +void glkview_debugTouchSetup(struct GLKView* self) { + if (!self || !self->glkView) { + printf("GLKView Debug: Invalid GLKView struct or view\n"); + return; + } + + BOOL touchEnabled = ((BOOL(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::isUserInteractionEnabledSel); + BOOL multiTouchEnabled = ((BOOL(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::isMultitouchEnabledSel); + + printf(" GLKView Touch Debug:\n"); + printf(" Touch Enabled: %s\n", touchEnabled ? "YES" : "NO"); + printf(" Multi-touch Enabled: %s\n", multiTouchEnabled ? "YES" : "NO"); + printf(" Touch Began Callback: %s\n", self->touchBeganCallback ? "Set" : "Not Set"); + printf(" Touch Moved Callback: %s\n", self->touchMovedCallback ? "Set" : "Not Set"); + printf(" Touch Ended Callback: %s\n", self->touchEndedCallback ? "Set" : "Not Set"); + printf(" Touch Cancelled Callback: %s\n", self->touchCancelledCallback ? "Set" : "Not Set"); +} + +void glkview_setNeedsDisplay(struct GLKView* self) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void(self->glkView, ObjectiveCSEL::setNeedsDisplaySel); +} + + +// View Controller API Implementation +struct ViewController* viewcontroller_init(double width, double height) { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + + struct ViewController* pViewController = (struct ViewController*)malloc(sizeof(struct ViewController)); + if (!pViewController) return NULL; + + memset(pViewController, 0, sizeof(struct ViewController)); + + // Set initial frame + pViewController->frame.origin.x = 0.0; + pViewController->frame.origin.y = 0.0; + pViewController->frame.size.width = width; + pViewController->frame.size.height = height; + + // Create custom GLKViewController with event handling + pViewController->viewController = createCustomViewController(); + if (!pViewController->viewController) { + free(pViewController); + return NULL; + } + + // Set up global pointer for event routing + gptrVeiwControllerEvents = pViewController; + gptrViewControllerDelegate = pViewController; + + return pViewController; +} + +void setupOrientationNotifications(struct ViewController* self) { + if (!self || !self->viewController) return; + + // Get notification center + Class notificationCenterClass = objc_getClass(kNSNotificationCenterClass); + id notificationCenter = ((id(*)(Class, SEL))objc_msgSend)(notificationCenterClass, ObjectiveCSEL::defaultCenterSel); + + // Create notification name NSString + Class stringClass = objc_getClass(kNSStringClass); + id notificationName = ((id(*)(Class, SEL, const char*))objc_msgSend)( + stringClass, ObjectiveCSEL::stringWithUTF8StringSel, kUIDeviceOrientationDidChangeNoti); + + // Register for orientation notifications + ((void(*)(id, SEL, id, SEL, id, id))objc_msgSend)( + notificationCenter, ObjectiveCSEL::addObserverSel, + self->viewController, ObjectiveCSEL::deviceOrientationDidChangeSel, + notificationName, nil); + + // Enable orientation notifications + Class deviceClass = objc_getClass(kUIDeviceClass); + + id currentDevice = ((id(*)(Class, SEL))objc_msgSend)(deviceClass, ObjectiveCSEL::currentDeviceSel); + ((void(*)(id, SEL))objc_msgSend)(currentDevice, ObjectiveCSEL::beginGenDeviceOrientationNotifSel); +} + +int device_getMaxFramesPerSecond(void) { + + Class screenClass = objc_getClass(kUIScreenClass); + + // Get main screen + id mainScreen = ((id(*)(Class, SEL))objc_msgSend)(screenClass, ObjectiveCSEL::mainScreenSel); + + // Get maximum frames per second from screen + NSInteger maxFPS = ((NSInteger(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::maximumFramesPerSecond); + + return (int)maxFPS; +} + +void viewcontroller_setAutoresizingMask(struct ViewController* self, unsigned int mask) { + if (!self || !self->glkview) return; + + ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glkview, ObjectiveCSEL::setAutoresizingMaskSel, mask); +} + +void viewcontroller_enableFlexibleSizing(struct ViewController* self) { + if (!self) return; + + // Enable flexible width and height + unsigned int flexibleSizing = (1 << 1) | (1 << 4); // UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight + viewcontroller_setAutoresizingMask(self, flexibleSizing); +} + + +void viewcontroller_present(struct ViewController* self) { + if (!self || !self->viewController) { + printf("Error: Invalid view controller for presentation\n"); + return; + } + + setupOrientationNotifications(self); + + // Set preferred frames per second based on device capabilities + if (self->viewController) { + ((void(*)(id, SEL, int))objc_msgSend)(self->viewController, ObjectiveCSEL::setPreferredFramesPerSecondSel, device_getMaxFramesPerSecond()); + } + + // Enable the rendering loop + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->viewController, ObjectiveCSEL::setPausedSel, YES); + + // Set delegate for GLKViewController updates + ((void(*)(id, SEL, id))objc_msgSend)(self->viewController, ObjectiveCSEL::setDelegateSel, self->viewController); + + // Get the main window from your application and set as root view controller + if (gptrApplicationDelegate && gptrApplicationDelegate->uiWindow) { + ((void(*)(id, SEL, id))objc_msgSend)(gptrApplicationDelegate->uiWindow, + ObjectiveCSEL::setRootViewControllerSel, + self->viewController); + + } else { + printf("Warning: No main window found for presentation\n"); + } + + +} + +void viewcontroller_destroy(struct ViewController* self) { + if (!self) return; + + if (self->title) { + free(self->title); + } + + if (self->glkViewStruct) { + glkview_destroy(self->glkViewStruct); + self->glkViewStruct = nullptr; + } + + if (self->glkview) { + safe_objc_msgSend_void(self->glkview, ObjectiveCSEL::releaseSel); + } + + if (self->viewController) { + safe_objc_msgSend_void(self->viewController, ObjectiveCSEL::releaseSel); + } + + if (self->delegate) { + safe_objc_msgSend_void(self->delegate, ObjectiveCSEL::releaseSel); + } + + free(self); +} + +void viewcontroller_setPause(struct ViewController* self, BOOL paused) { + if (!self || !self->viewController) return; + + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->viewController, ObjectiveCSEL::setPausedSel, paused ? YES : NO); + +} + +void viewcontroller_setTitle(struct ViewController* self, const char* title) { + if (!self || !self->viewController || !title) return; + + // Free existing title + if (self->title) { + free(self->title); + } + + // Copy new title + size_t len = strlen(title); + self->title = (char*)malloc(len + 1); + if (!self->title) { + return; + } + strcpy(self->title, title); + + // Create NSString and set title + Class stringClass = safe_objc_getClass(kNSStringClass); + + id titleString = ((id(*)(id, SEL, const char*))objc_msgSend)((id)stringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); + safe_objc_msgSend_id(self->viewController, ObjectiveCSEL::setTitleSel, titleString); +} + + +const char* viewcontroller_getTitle(const struct ViewController* self) { + if (!self) return NULL; + return self->title; +} + +void viewcontroller_updateViewFromiOS(struct ViewController* self) { + if (!self || !self->glkview) return; + + // Update frame from the actual view bounds + CGRect bounds = ((CGRect(*)(id, SEL))objc_msgSend)(self->glkview, ObjectiveCSEL::boundsSel); + self->frame = bounds; +} + +void viewcontroller_getViewFrame(const struct ViewController* self, double* x, double* y, double* width, double* height) { + if (!self) return; + + if (x) *x = self->frame.origin.x; + if (y) *y = self->frame.origin.y; + if (width) *width = self->frame.size.width; + if (height) *height = self->frame.size.height; +} + +void viewcontroller_getViewSize(const struct ViewController* self, double* width, double* height) { + if (!self) return; + + if (width) *width = self->frame.size.width; + if (height) *height = self->frame.size.height; +} + +void viewcontroller_setViewFrame(struct ViewController* self, double x, double y, double width, double height) { + if (!self) return; + + self->frame.origin.x = x; + self->frame.origin.y = y; + self->frame.size.width = width; + self->frame.size.height = height; + + if (self->glkview) { + typedef void (*SetFrameFunc)(id, SEL, CGRect); + ((SetFrameFunc)objc_msgSend)(self->glkview, ObjectiveCSEL::setFrameSel, self->frame); + } +} + +void viewcontroller_setViewSize(struct ViewController* self, double width, double height) { + viewcontroller_setViewFrame(self, self->frame.origin.x, self->frame.origin.y, width, height); +} + +// OpenGL ES Renderer API Implementation +struct OpenGLRenderer* opengl_init(void) { + struct OpenGLRenderer* renderer = (struct OpenGLRenderer*)malloc(sizeof(struct OpenGLRenderer)); + if (!renderer) return NULL; + + memset(renderer, 0, sizeof(struct OpenGLRenderer)); + return renderer; +} + +void opengl_initialize(struct OpenGLRenderer* self, struct ViewController* viewController) { + if (!self || !viewController || !viewController->glkview) return; + + // Create EAGL context for OpenGL ES 3.0 + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + + id allocatedContext = safe_objc_msgSend((id)eaglClass, ObjectiveCSEL::allocSel); + if (!allocatedContext) return; + + self->eaglContext = ((id(*)(id, SEL, int))objc_msgSend)(allocatedContext,ObjectiveCSEL::initWithAPISel, kEAGLRenderingAPIOpenGLES3); + if (!self->eaglContext) return; + + // Set context on GLKView + self->glkView = viewController->glkview; + safe_objc_msgSend_id(self->glkView, ObjectiveCSEL::setContextSel, self->eaglContext); +} + + +void opengl_setupContext(struct OpenGLRenderer* self) { + if (!self || !self->eaglContext) return; + + // Set current context + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + safe_objc_msgSend_id((id)eaglClass, ObjectiveCSEL::setCurrentContextSel, self->eaglContext); + + GLint maxSamples = OLC_MSAA_SAMPLES; + if(maxSamples > 0) + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + + // Configure GLKView properties + if (self->glkView) { + + // Set color format to RGBA8 + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableColorFormatSel, kEAGLColorFormatRGBA8); + + // Set depth format to 24-bit + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableDepthFormatSel, kEAGLDepthFormat24); + + // Enable multisampling + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableMultisampleSel, maxSamples); + + // Enable automatic display updates (recommended for games) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glkView, ObjectiveCSEL::setEnableSetNeedsDisplaySel, NO); + + } +} + +void* opengl_getEAGLContext(const struct OpenGLRenderer* self) { + if (!self) return NULL; + return self->eaglContext; +} + +void* opengl_getEAGLContextPtr(struct OpenGLRenderer* self) { + if (!self) return NULL; + return &(self->eaglContext); +} + +void opengl_makeCurrentContext(struct OpenGLRenderer* self) { + if (!self || !self->eaglContext) return; + + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + safe_objc_msgSend_id((id)eaglClass, ObjectiveCSEL::setCurrentContextSel, self->eaglContext); +} + +void opengl_setVsync(struct OpenGLRenderer* self, BOOL enabled) { + if (!self) return; + self->vsyncEnabled = enabled; +} + + +void opengl_swapBuffers(struct OpenGLRenderer* self) { + if (!self || !self->glkView) return; + + // In GLKit, buffer swapping is handled automatically by GLKView + // We can trigger a display update instead + safe_objc_msgSend_void(self->glkView, ObjectiveCSEL::displaySel); +} + +void opengl_destroy(struct OpenGLRenderer* self) { + if (!self) return; + + if (self->eaglContext) { + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + safe_objc_msgSend_id((id)eaglClass,ObjectiveCSEL::setCurrentContextSel, nil); + + safe_objc_msgSend_void(self->eaglContext, ObjectiveCSEL::releaseSel); + } + + free(self); +} + +char* imageloader_getApplicationPath(struct ImageLoader* self) +{ + return get_application_path(); +} + +// Load image from file path using NSImage and NSBitmapImageRep +BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // 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 UIImageClass = objc_getClass(kUIImageClass); + + // Create NSString from file path + id pathString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, filePath); + + if (!pathString) { + return NO; + } + + // Create UIImage from file + id image = ((id(*)(id, SEL, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(UIImageClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithContentsOfFileSel, pathString); + + if (!image) { + return NO; + } + + CGImageRef cgImage = ((CGImageRef(*)(id, SEL))objc_msgSend)(image, ObjectiveCSEL::cgImageSel); + + if (!cgImage) { + return NO; + } + + self->width = (int)CGImageGetWidth(cgImage); + self->height = (int)CGImageGetHeight(cgImage); + self->bytesPerPixel = (int)CGImageGetBitsPerPixel(cgImage) / kBitsPerByte; // 4 for RGBA, 3 for RGB + self->bytesPerRow = (int)CGImageGetBytesPerRow(cgImage); // self->width * self->bytesPerPixel; + self->hasAlpha = CGImageGetAlphaInfo(cgImage) != kCGImageAlphaNone; + + // Get pixel data directly from CGImage without drawing + CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage); + CFDataRef data = CGDataProviderCopyData(dataProvider); + + if (data) { + const unsigned char* bytes = CFDataGetBytePtr(data); + size_t dataLength = CFDataGetLength(data); + self->pixelData = (unsigned char*)malloc(dataLength); + + memcpy(self->pixelData, bytes, dataLength); + CFRelease(data); + return YES; + } + + return NO; + +} + +BOOL imageloader_loadFromMemory(struct ImageLoader* self, const unsigned char* data, size_t length) { + // 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; + + // Create NSData from memory + Class NSDataClass = objc_getClass(kNSDataClass); + id nsData = ((id(*)(Class, SEL, const void*, size_t))objc_msgSend)( NSDataClass, ObjectiveCSEL::dataWithBytesLengthSel, data, length); + + if (!nsData) { + return NO; + } + + // Create a UIImage from NSData + Class UIImageClass = objc_getClass(kUIImageClass); + id image = ((id(*)(Class, SEL, id))objc_msgSend)(UIImageClass, ObjectiveCSEL::imageWithDataSel, nsData); + + if (!image) { + return NO; + } + + CGImageRef cgImage = ((CGImageRef(*)(id, SEL))objc_msgSend)(image, ObjectiveCSEL::cgImageSel); + + if (!cgImage) { + return NO; + } + + self->width = (int)CGImageGetWidth(cgImage); + self->height = (int)CGImageGetHeight(cgImage); + self->bytesPerPixel = (int)CGImageGetBitsPerPixel(cgImage) / kBitsPerByte; // 4 for RGBA, 3 for RGB + self->bytesPerRow = (int)CGImageGetBytesPerRow(cgImage); // self->width * self->bytesPerPixel; + self->hasAlpha = CGImageGetAlphaInfo(cgImage) != kCGImageAlphaNone; + + // Get pixel data directly from CGImage without drawing + CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage); + CFDataRef dataRef = CGDataProviderCopyData(dataProvider); + + if (dataRef) { + const unsigned char* bytes = CFDataGetBytePtr(dataRef); + size_t dataLength = CFDataGetLength(dataRef); + self->pixelData = (unsigned char*)malloc(dataLength); + + memcpy(self->pixelData, bytes, dataLength); + CFRelease(dataRef); + return YES; + } + + return NO; +} + + + + + +// Get raw pixel data pointer +unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return self->pixelData; +} + +// Get image information +void imageloader_getImageInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; +} + +// Get detailed image format information +void imageloader_getDetailedInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; + if (bytesPerRow) *bytesPerRow = self->bytesPerRow; + if (hasAlpha) *hasAlpha = self->hasAlpha; +} + +// Check if image is loaded +BOOL imageloader_isLoaded(const struct ImageLoader* self) { + return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); +} + +// Get pixel at specific coordinates +BOOL imageloader_getPixel(const struct ImageLoader* self, int x, int y, + unsigned char* red, unsigned char* green, + unsigned char* blue, unsigned char* alpha) { + + if (!imageloader_isLoaded(self) || x < kMinValidDimension || x >= self->width || y < kMinValidDimension || y >= self->height) { + return NO; + } + + // 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); + + // 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]; + else if (alpha) *alpha = kFullyOpaque; // Fully opaque if no alpha channel + + return YES; +} + +// Cleanup ImageLoader resources +void imageloader_destroy(struct ImageLoader* self) { + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + self->width = kZeroWidth; + self->height = kZeroHeight; + self->bytesPerPixel = kZeroBytes; + self->bytesPerRow = kZeroRows; + self->hasAlpha = NO; +} + +// Create OpenGL texture from ImageLoader: TODO: Remove not needed +unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* loader) { + if (!loader->isLoaded(const_cast(loader))) { + return 0; + } + + unsigned int textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + + // Set texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Determine format + GLenum format = (loader->bytesPerPixel == kRGBABytesPerPixel) ? GL_RGBA : GL_RGB; + + // Upload texture data + glTexImage2D(GL_TEXTURE_2D, 0, format, loader->width, loader->height, + 0, format, GL_UNSIGNED_BYTE, loader->pixelData); + + // Generate mipmaps for better quality at different scales + glGenerateMipmap(GL_TEXTURE_2D); + + return textureID; +} + +// ImageLoader constructor +struct ImageLoader* imageloader_init() { + struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); + + // Initialize properties + loader->pixelData = NULL; + loader->width = kZeroWidth; + loader->height = kZeroHeight; + loader->bytesPerPixel = kZeroBytes; + loader->bytesPerRow = kZeroRows; + loader->hasAlpha = NO; + + // Assign method pointers + loader->loadFromFile = imageloader_loadFromFile; + loader->destroy = imageloader_destroy; + loader->getPixelData = imageloader_getPixelData; + loader->getImageInfo = imageloader_getImageInfo; + loader->getDetailedInfo = imageloader_getDetailedInfo; + loader->isLoaded = imageloader_isLoaded; + loader->getPixel = imageloader_getPixel; + loader->createOpenGLTexture = imageloader_createOpenGLTexture; + + return loader; +} + +// GLKView Event Handler API Implementation +void glkview_setTouchBeganCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchBeganCallback = callback; + self->touchBeganUserData = userData; +} + +void glkview_setTouchMovedCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchMovedCallback = callback; + self->touchMovedUserData = userData; +} + +void glkview_setTouchEndedCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchEndedCallback = callback; + self->touchEndedUserData = userData; +} + +void glkview_setTouchCancelledCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchCancelledCallback = callback; + self->touchCancelledUserData = userData; +} + +void glkview_setKeyDownCallback(struct GLKView* self, KeyEventCallback callback, void* userData) { + if (!self) return; + self->keyDownCallback = callback; + self->keyDownUserData = userData; +} + +void glkview_setKeyUpCallback(struct GLKView* self, KeyEventCallback callback, void* userData) { + if (!self) return; + self->keyUpCallback = callback; + self->keyUpUserData = userData; +} + + + +void glkview_setDrawCallback(struct GLKView* self, DrawCallback callback, void* userData) { + if (!self) return; + self->drawCallback = callback; + self->drawUserData = userData; +} + +// Application Delegate API Implementation +void application_setDidFinishLaunchingCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->didFinishLaunchingCallback = callback; + self->didFinishLaunchingUserData = userData; +} + +void application_setWillTerminateCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->willTerminateCallback = callback; + self->willTerminateUserData = userData; +} + +void application_setDidBecomeActiveCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->didBecomeActiveCallback = callback; + self->didBecomeActiveUserData = userData; +} + +void application_setWillResignActiveCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->willResignActiveCallback = callback; + self->willResignActiveUserData = userData; +} + +void application_setDidEnterBackgroundCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->didEnterBackgroundCallback = callback; + self->didEnterBackgroundUserData = userData; +} + +void application_setWillEnterForegroundCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->willEnterForegroundCallback = callback; + self->willEnterForegroundUserData = userData; +} + +// View Controller Delegate API Implementation +void viewcontroller_setViewDidLoadCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidLoadCallback = callback; + self->viewDidLoadUserData = userData; +} + +void viewcontroller_setViewWillAppearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewWillAppearCallback = callback; + self->viewWillAppearUserData = userData; +} + +void viewcontroller_setViewDidAppearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidAppearCallback = callback; + self->viewDidAppearUserData = userData; +} + +void viewcontroller_setViewWillDisappearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewWillDisappearCallback = callback; + self->viewWillDisappearUserData = userData; +} + +void viewcontroller_setViewDidDisappearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidDisappearCallback = callback; + self->viewDidDisappearUserData = userData; +} + +void viewcontroller_setViewDidLayoutSubviewsCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidLayoutSubviewsCallback = callback; + self->viewDidLayoutSubviewsUserData = userData; +} + +void viewcontroller_setUpdateCallback(struct ViewController* self, UpdateCallback callback, void* userData) { + if (!self) return; + self->updateCallback = callback; + self->updateUserData = userData; +} + +void viewcontroller_setOrientationChangedCallback(struct ViewController* self, OrientationChangedCallback callback, void* userData) { + if (!self) return; + self->orientationChangedCallback = callback; + self->orientationChangedUserData = userData; +} + +BOOL device_isMultitouchCapable(void) { + // Most iOS devices support multitouch + return YES; +} + +BOOL device_hasHardwareKeyboard(void) { + // This would require checking for connected external keyboards + // For now, return NO (most devices use on-screen keyboard) + return NO; +} + +void device_getScreenBounds(double* width, double* height) { + if (!width || !height) return; + + // Get main screen bounds + Class screenClass = safe_objc_getClass(kUIScreenClass); + id mainScreen = safe_objc_msgSend((id)screenClass, ObjectiveCSEL::mainScreenSel); + if (!mainScreen) { + *width = *height = 0.0; + return; + } + + CGRect bounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::boundsSel); + *width = bounds.size.width; + *height = bounds.size.height; +} + + +//! END IMPLEMENTATION + diff --git a/dev/src/api_ios.h b/dev/src/api_ios.h new file mode 100644 index 0000000..a048934 --- /dev/null +++ b/dev/src/api_ios.h @@ -0,0 +1,197 @@ +// C API declarations for C++ wrapper + +#pragma once +#include "config.h" + +//! START STDHEADER GLOBAL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START DECLARATION +// For C/C++ compilation, we need to include the C-compatible headers first +#include +#include +#include +#include +#include +#include +#include + + +extern "C" { + + // Forward declarations + struct Application; + struct ApplicationDelegate; + struct ViewController; + struct GLKView; + struct OpenGLRenderer; + struct ImageLoader; + + // Application API - as implemented in api_.cpp + struct Application* application_init (void); + void application_initialize (struct Application* self); + void application_activate (struct Application* self); + void application_run (struct Application* self); + void application_destroy (struct Application* self); + const char* application_getApplicationPath (struct Application* self); + const char* application_getSystemLocale (struct Application* self); + + + // View Controller API - as implemented in api_.cpp + struct ViewController* viewcontroller_init (double width, double height); + void viewcontroller_present (struct ViewController* self); + void viewcontroller_destroy (struct ViewController* self); + void viewcontroller_setTitle (struct ViewController* self, const char* title); + void viewcontroller_setPause (struct ViewController* self, BOOL pause); + const char* viewcontroller_getTitle (const struct ViewController* self); + void viewcontroller_updateViewFromiOS (struct ViewController* self); + void viewcontroller_getViewFrame (const struct ViewController* self, double* x, double* y, double* width, double* height); + void viewcontroller_getViewSize (const struct ViewController* self, double* width, double* height); + void viewcontroller_setViewFrame (struct ViewController* self, double x, double y, double width, double height); + void viewcontroller_setViewSize (struct ViewController* self, double width, double height); + + // GLKView API - as implemented in api_.cpp + struct GLKView* glkview_init (struct ViewController* viewController); + void glkview_destroy (struct GLKView* self); + void* glkview_getNativeView (const struct GLKView* self); // Returns UIView, cast to void* + void glkview_setFrame (struct GLKView* self, double x, double y, double width, double height); + void glkview_getFrame (const struct GLKView* self, double* x, double* y, double* width, double* height); + void glkview_setMultipleTouchEnabled (struct GLKView* self, BOOL enabled); + BOOL glkview_isMultipleTouchEnabled (const struct GLKView* self); + void glkview_enableTouchHandling (struct GLKView* self); + void glkview_disableTouchHandling (struct GLKView* self); + + // OpenGL ES Renderer API - as implemented in api_.cpp + struct OpenGLRenderer* opengl_init (void); + void opengl_initialize (struct OpenGLRenderer* self, struct ViewController* viewController); + void opengl_setupContext (struct OpenGLRenderer* self); + void* opengl_getEAGLContext (const struct OpenGLRenderer* self); // Returns EAGLContext, cast to void* + void* opengl_getEAGLContextPtr (struct OpenGLRenderer* self); // Returns EAGLContext as void* + void opengl_makeCurrentContext (struct OpenGLRenderer* self); // Make OpenGL context current + void opengl_setVsync (struct OpenGLRenderer* self, BOOL enabled); // Enable/disable vsync + void opengl_swapBuffers (struct OpenGLRenderer* self); // Present framebuffer + void opengl_destroy (struct OpenGLRenderer* self); + + // Image Loader API - as implemented in api_.cpp + struct ImageLoader* imageloader_init (void); + BOOL imageloader_loadFromFile (struct ImageLoader* self, const char* filePath); + BOOL imageloader_loadFromMemory (struct ImageLoader* self, const unsigned char* data, size_t length); + 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); + void imageloader_getDetailedInfo (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha); + BOOL imageloader_isLoaded (const struct ImageLoader* self); + BOOL imageloader_getPixel (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha); + unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* self); + char* imageloader_getApplicationPath (struct ImageLoader* self); + + + // Autorelease pool management + void* objc_autoreleasePoolPush (void); + void objc_autoreleasePoolPop (void* pool); + + // =========================================================================== + // TOUCH EVENT HANDLING API + // =========================================================================== + + // Touch event callback function types + typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, uint modifierFlags, void* userData); + typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); + typedef void (*DrawCallback) (double x, double y, double width, double height, void* userData); + typedef void (*UpdateCallback) (void* userData); + + // Touch event handler setup - Now handled by GLKView + void glkview_setTouchBeganCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + void glkview_setTouchMovedCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + void glkview_setTouchEndedCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + void glkview_setTouchCancelledCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + + // Keyboard event handler setup (for hardware keyboards) - Now handled by GLKView + void glkview_setKeyDownCallback (struct GLKView* self, KeyEventCallback callback, void* userData); + void glkview_setKeyUpCallback (struct GLKView* self, KeyEventCallback callback, void* userData); + + // Draw event handler setup - Now handled by GLKView + void glkview_setDrawCallback (struct GLKView* self, DrawCallback callback, void* userData); + void glkview_setNeedsDisplay (struct GLKView* self); + + // Touch system management - Now handled by GLKView + void glkview_debugTouchSetup (struct GLKView* self); + + // Touch system management + void viewcontroller_enableTouchHandling (struct ViewController* self); + void viewcontroller_disableTouchHandling (struct ViewController* self); + void viewcontroller_setMultipleTouchEnabled (struct ViewController* self, BOOL enabled); + + // Update event handler setup + void viewcontroller_setUpdateCallback (struct ViewController* self, UpdateCallback callback, void* userData); + + // Touch diagnostics TODO: Remove this in production builds + void viewcontroller_debugTouchSetup (struct ViewController* self); + + // =========================================================================== + // APPLICATION DELEGATE EVENT HANDLING API + // =========================================================================== + + // Application delegate callback function type + typedef void (*ApplicationDelegateCallback)(void* userData); + + // Application delegate event handler setup + void application_setDidFinishLaunchingCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setWillTerminateCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setDidBecomeActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setWillResignActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setDidEnterBackgroundCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setWillEnterForegroundCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + + // =========================================================================== + // VIEW CONTROLLER DELEGATE EVENT HANDLING API + // =========================================================================== + + // View controller delegate callback function type + typedef void (*ViewControllerDelegateCallback) (void* userData); + typedef void (*OrientationChangedCallback) (int orientation, void* userData); + + // View controller delegate event handler setup + void viewcontroller_setViewDidLoadCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewWillAppearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewDidAppearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewWillDisappearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewDidDisappearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewDidLayoutSubviewsCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + + void viewcontroller_setOrientationChangedCallback (struct ViewController* self, OrientationChangedCallback callback, void* userData); + + // =========================================================================== + // DEVICE ORIENTATION AND CAPABILITIES + // =========================================================================== + + // Device orientation enumeration + typedef enum { + DeviceOrientationUnknown = 0, + DeviceOrientationPortrait = 1, + DeviceOrientationPortraitUpsideDown = 2, + DeviceOrientationLandscapeLeft = 3, + DeviceOrientationLandscapeRight = 4, + DeviceOrientationFaceUp = 5, + DeviceOrientationFaceDown = 6 + } DeviceOrientation; + + // Device capability queries + DeviceOrientation device_getCurrentOrientation (void); + BOOL device_isMultitouchCapable (void); + BOOL device_hasHardwareKeyboard (void); + void device_getScreenBounds (double* width, double* height); + void device_getSafeAreaInsets (double* top, double* left, double* bottom, double* right); +} + +//! END DECLARATION diff --git a/dev/src/api_ios_wrapper.hpp b/dev/src/api_ios_wrapper.hpp new file mode 100644 index 0000000..469bc63 --- /dev/null +++ b/dev/src/api_ios_wrapper.hpp @@ -0,0 +1,929 @@ +#pragma once +#include "config.h" + +//! START STDHEADER GLOBAL +#include +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "api_ios.h" +//! END CUSTOMHEADER + +//! START DECLARATION +namespace olc { + namespace apis { + namespace ios { + + // RAII helper for autorelease pools + class AutoreleasePool { + private: + void* pool_; + + public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + + ~AutoreleasePool() noexcept { + if (pool_) { + objc_autoreleasePoolPop(pool_); + } + } + + // Non-copyable and non-movable for safety + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; + }; + + // RAII wrapper for OpenGL ES texture management + class OpenGLTexture { + private: + unsigned int textureID_; + + public: + explicit constexpr OpenGLTexture(unsigned int id = 0) noexcept : textureID_(id) {} + + ~OpenGLTexture() noexcept { + if (textureID_ != 0) { + glDeleteTextures(1, &textureID_); + } + } + + constexpr unsigned int get() const noexcept { return textureID_; } + constexpr bool isValid() const noexcept { return textureID_ != 0; } + + // Release ownership + unsigned int release() noexcept { + unsigned int id = textureID_; + textureID_ = 0; + return id; + } + + // Reset with new texture ID + void reset(unsigned int id = 0) noexcept { + if (textureID_ != 0) { + glDeleteTextures(1, &textureID_); + } + textureID_ = id; + } + + // Move semantics + OpenGLTexture(OpenGLTexture&& other) noexcept : textureID_(other.textureID_) { + other.textureID_ = 0; + } + + OpenGLTexture& operator=(OpenGLTexture&& other) noexcept { + if (this != &other) { + reset(); + textureID_ = other.textureID_; + other.textureID_ = 0; + } + return *this; + } + + // Non-copyable + OpenGLTexture(const OpenGLTexture&) = delete; + OpenGLTexture& operator=(const OpenGLTexture&) = delete; + }; + + // Exception class for framework errors + class FrameworkException : public std::runtime_error { + public: + explicit FrameworkException(const std::string& message) + : std::runtime_error("iOS Wrapper Framework Error: " + message) {} + }; + + // Touch event structure + struct TouchEvent { + uint32_t touchID; + double x, y; + double sizeX, sizeY; + + TouchEvent(uint32_t id, double x, double y, double sx, double sy) noexcept + : touchID(id), x(x), y(y), sizeX(sx), sizeY(sy) {} + + TouchEvent(TouchEvent&&) noexcept = default; + TouchEvent& operator=(TouchEvent&&) noexcept = default; + TouchEvent(const TouchEvent&) = default; + TouchEvent& operator=(const TouchEvent&) = default; + }; + + // Key event structure (for hardware keyboards) + struct KeyEvent { + unsigned short keyCode; + std::string characters; + }; + + // Device orientation enumeration + enum class DeviceOrientation { + Unknown = 0, + Portrait = 1, + PortraitUpsideDown = 2, + LandscapeLeft = 3, + LandscapeRight = 4, + FaceUp = 5, + FaceDown = 6 + }; + + // Application wrapper class + class Application { + private: + struct ::Application* app_; + + std::string appPath_; + + // Store callback pointers for cleanup + std::vector>> callbacks_; + + // Template helper to reduce code duplication + template + void setCallback(SetterFunc setter, std::function callback) { + if (!app_) return; + + auto callbackPtr = std::make_unique>(std::move(callback)); + auto* rawPtr = callbackPtr.get(); + callbacks_.emplace_back(std::move(callbackPtr)); + + setter(app_, [](void* userData) { + auto* cb = static_cast*>(userData); + (*cb)(); + }, rawPtr); + } + + public: + explicit Application() : app_(nullptr) { + app_ = application_init(); + + if (!app_) { + throw FrameworkException("Failed to initialize iOS application"); + } + } + + ~Application() { + if (app_) { + application_destroy(app_); + } + } + + // Sets the iOS internal path to the application + void setApplicationPath(const std::string& path) { + if (app_) { + appPath_ = path; + } + } + + // Get the system locale as a string (e.g., "en_US", "fr_FR") + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } + + std::string getApplicationPath() + { + if(app_) { + const char* path = application_getApplicationPath(app_); + if (path) { + std::string appPath(path); + return appPath; + } + + } + return std::string{}; + } + + void initialize() noexcept { + if (app_) application_initialize(app_); + } + + void activate() noexcept { + if (app_) application_activate(app_); + } + + void run() noexcept { + if (app_) application_run(app_); + } + + // Get underlying C handle + struct ::Application* getCHandle() const noexcept { return app_; } + + + // Set callback for application did finish launching event + void setDidFinishLaunchingCallback(std::function callback) { + setCallback(application_setDidFinishLaunchingCallback, std::move(callback)); + } + + // Set callback for application will terminate event + void setWillTerminateCallback(std::function callback) { + setCallback(application_setWillTerminateCallback, std::move(callback)); + } + + // Set callback for application did become active event + void setDidBecomeActiveCallback(std::function callback) { + setCallback(application_setDidBecomeActiveCallback, std::move(callback)); + } + + // Set callback for application will resign active event + void setWillResignActiveCallback(std::function callback) { + setCallback(application_setWillResignActiveCallback, std::move(callback)); + } + + // Set callback for application did enter background event + void setDidEnterBackgroundCallback(std::function callback) { + setCallback(application_setDidEnterBackgroundCallback, std::move(callback)); + } + + // Set callback for application will enter foreground event + void setWillEnterForegroundCallback(std::function callback) { + setCallback(application_setWillEnterForegroundCallback, std::move(callback)); + } + + // Non-copyable but movable + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + Application(Application&& other) noexcept : app_(other.app_), callbacks_(std::move(other.callbacks_)) { + other.app_ = nullptr; + } + Application& operator=(Application&& other) noexcept { + if (this != &other) { + if (app_) { + application_destroy(app_); + } + app_ = other.app_; + callbacks_ = std::move(other.callbacks_); + other.app_ = nullptr; + } + return *this; + } + }; + + // View Controller wrapper class + class ViewController { + private: + struct ::ViewController* viewController_; + + // Template helper for simple callbacks + template + void setCallback(SetterFunc setter, std::function callback) { + if (!viewController_) return; + + auto callbackPtr = std::make_unique>(std::move(callback)); + auto* rawPtr = callbackPtr.get(); + callbacks_.emplace_back(std::move(callbackPtr)); + + setter(viewController_, [](void* userData) { + auto* cb = static_cast*>(userData); + (*cb)(); + }, rawPtr); + } + + // Template helper for orientation changed callback + template + void setOrientCallback(SetterFunc setter, std::function callback) { + if (!viewController_) return; + + auto callbackPtr = std::make_unique>(std::move(callback)); + auto* rawPtr = callbackPtr.get(); + orientationCallbacks_.emplace_back(std::move(callbackPtr)); + + setter(viewController_, [](int orientation, void* userData) { + auto* cb = static_cast*>(userData); + (*cb)(static_cast(orientation)); + }, rawPtr); + + } + + public: + std::string title_; + std::vector>> callbacks_; + std::vector>> orientationCallbacks_; + + public: + ViewController(double width, double height, const std::string& title) + : viewController_(nullptr), title_(title) { + viewController_ = viewcontroller_init(width, height); + if (!viewController_) { + throw FrameworkException("Failed to create iOS view controller"); + } + } + + ~ViewController() { + if (viewController_) { + viewcontroller_destroy(viewController_); + } + } + + // Get underlying C handle + struct ::ViewController* getCHandle() const noexcept { return viewController_; } + + // Present the view controller + void present() { + if (viewController_) { + viewcontroller_present(viewController_); + } + } + + // Update the internal frame from the iOS view + void updateViewFromiOS() { + if (viewController_) { + viewcontroller_updateViewFromiOS(viewController_); + } + } + + const std::string& getTitle() const noexcept { return title_; } + + void setTitle(const std::string& title) { + title_ = title; + if (viewController_) { + viewcontroller_setTitle(viewController_, title.c_str()); + } + } + + void setPaused(bool paused) noexcept { + if (viewController_) { + viewcontroller_setPause(viewController_, paused); + } + } + + // Get the current view size + CGRect getSize() const noexcept { + if (viewController_) { + CGRect frame{}; + viewcontroller_getViewFrame(viewController_, &frame.origin.x, &frame.origin.y, &frame.size.width, &frame.size.height); + return frame; + } + return CGRect{0, 0, 0, 0}; + } + + void getViewSize(double& width, double& height) const noexcept { + if (viewController_) { + viewcontroller_getViewSize(viewController_, &width, &height); + } else { + width = height = 0.0; + } + } + + // Set the view frame size + void setFrameSize(double width, double height) noexcept { + if (viewController_) { + double x = 0.0, y = 0.0; + viewcontroller_getViewFrame(viewController_, &x, &y, nullptr, nullptr); + viewcontroller_setViewFrame(viewController_, x, y, width, height); + } + } + + void setPosition(double x, double y) noexcept { + if (viewController_) { + double width = 0.0, height = 0.0; + viewcontroller_getViewFrame(viewController_, nullptr, nullptr, &width, &height); + viewcontroller_setViewFrame(viewController_, x, y, width, height); + } + } + + // View controller lifecycle callbacks + void setViewDidLoadCallback(std::function callback) { + setCallback(viewcontroller_setViewDidLoadCallback, std::move(callback)); + } + + void setViewWillAppearCallback(std::function callback) { + setCallback(viewcontroller_setViewWillAppearCallback, std::move(callback)); + } + + void setViewDidAppearCallback(std::function callback) { + setCallback(viewcontroller_setViewDidAppearCallback, std::move(callback)); + } + + void setViewWillDisappearCallback(std::function callback) { + setCallback(viewcontroller_setViewWillDisappearCallback, std::move(callback)); + } + + void setViewDidDisappearCallback(std::function callback) { + setCallback(viewcontroller_setViewDidDisappearCallback, std::move(callback)); + } + + void setViewDidLayoutSubviewsCallback(std::function callback) { + setCallback(viewcontroller_setViewDidLayoutSubviewsCallback, std::move(callback)); + } + + void setUpdateCallback(std::function callback) { + setCallback(viewcontroller_setUpdateCallback, std::move(callback)); + } + + void setOrientationChangedCallback(std::function callback) { + setOrientCallback(viewcontroller_setOrientationChangedCallback, std::move(callback)); + + } + + // Non-copyable but movable + ViewController(const ViewController&) = delete; + ViewController& operator=(const ViewController&) = delete; + ViewController(ViewController&& other) noexcept + : viewController_(other.viewController_), title_(std::move(other.title_)), + callbacks_(std::move(other.callbacks_)), + orientationCallbacks_(std::move(other.orientationCallbacks_)) { + other.viewController_ = nullptr; + } + + ViewController& operator=(ViewController&& other) noexcept { + if (this != &other) { + if (viewController_) { + viewcontroller_destroy(viewController_); + } + viewController_ = other.viewController_; + title_ = std::move(other.title_); + callbacks_ = std::move(other.callbacks_); + orientationCallbacks_ = std::move(other.orientationCallbacks_); + other.viewController_ = nullptr; + } + return *this; + } + }; + + // GLKView wrapper class + class GLKView { + private: + struct ::GLKView* glkView_; + ViewController* parentViewController_; + + // Touch event handlers + std::function touchBeganHandler_; + std::function touchMovedHandler_; + std::function touchEndedHandler_; + std::function touchCancelledHandler_; + + std::function keyDownHandler; + std::function keyUpHandler; + std::function drawHandler; + + // Store callback pointers for cleanup + std::vector>> touchCallbacks_; + std::vector&)>>> multiTouchCallbacks_; + std::vector>> keyCallbacks_; + + // Re-register all callbacks after GLKView initialization + void reregisterCallbacks() { + if (!glkView_) return; + + if (touchBeganHandler_) { + glkview_setTouchBeganCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchBeganHandler_) { + handler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (touchMovedHandler_) { + glkview_setTouchMovedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchMovedHandler_) { + handler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (touchEndedHandler_) { + glkview_setTouchEndedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchEndedHandler_) { + handler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (touchCancelledHandler_) { + glkview_setTouchCancelledCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchCancelledHandler_) { + handler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (keyDownHandler) { + glkview_setKeyDownCallback(glkView_, [](uint16_t keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyDownHandler) { + handler->keyDownHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + + if (keyUpHandler) { + glkview_setKeyUpCallback(glkView_, [](unsigned short keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyUpHandler) { + handler->keyUpHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + + + if (drawHandler) { + glkview_setDrawCallback(glkView_, [](double x, double y, double width, double height, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->drawHandler) { + handler->drawHandler(x, y, width, height); + } + }, this); + } + } + + public: + explicit GLKView(ViewController& parentViewController) + : glkView_(nullptr), parentViewController_(&parentViewController) { + } + + ~GLKView() { + // Note: The underlying GLKView is managed by ViewController, so we don't destroy it here + } + + // Initialize GLKView after ViewController presentation + bool initialize() { + if (parentViewController_ && parentViewController_->getCHandle()) { + // Get the GLKView struct from the presented ViewController + glkView_ = glkview_init(parentViewController_->getCHandle()); + if (glkView_) { + // Re-register all callbacks that were set before initialization + reregisterCallbacks(); + return true; + } + } + return false; + } + + // Get underlying C handle + struct ::GLKView* getCHandle() const noexcept { return glkView_; } + + // Get native view pointer + void* getNativeView() const { + return glkView_ ? glkview_getNativeView(glkView_) : nullptr; + } + + // Frame management + void setFrame(double x, double y, double width, double height) { + if (glkView_) { + glkview_setFrame(glkView_, x, y, width, height); + } + } + + // Get the current frame of the GLKView + CGRect getFrame(double x, double y, double width, double height) const { + if (glkView_) { + CGRect frame{}; + glkview_getFrame(glkView_, &frame.origin.x, &frame.origin.y, &frame.size.width, &frame.size.height); + return frame; + } + return CGRect{0, 0, 0, 0}; + } + + // Touch handling management + void setMultipleTouchEnabled(bool enabled) { + if (glkView_) { + glkview_setMultipleTouchEnabled(glkView_, enabled ? YES : NO); + } + } + + bool isMultipleTouchEnabled() const { + return glkView_ ? glkview_isMultipleTouchEnabled(glkView_) != NO : false; + } + + void enableTouchHandling() { + if (glkView_) { + glkview_enableTouchHandling(glkView_); + } + } + + void disableTouchHandling() { + if (glkView_) { + glkview_disableTouchHandling(glkView_); + } + } + + void debugTouchSetup() { + if (glkView_) { + glkview_debugTouchSetup(glkView_); + } + } + + void updateDisplay() + { + if(glkView_) { + glkview_setNeedsDisplay(glkView_); + } + } + + // Touch event setters + void setTouchBeganCallback(std::function callback) { + touchBeganHandler_ = callback; + if (glkView_) { + glkview_setTouchBeganCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchBeganHandler_) { + handler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + void setTouchMovedCallback(std::function callback) { + touchMovedHandler_ = callback; + if (glkView_) { + glkview_setTouchMovedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchMovedHandler_) { + handler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + void setTouchEndedCallback(std::function callback) { + touchEndedHandler_ = callback; + if (glkView_) { + glkview_setTouchEndedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchEndedHandler_) { + handler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + void setTouchCancelledCallback(std::function callback) { + touchCancelledHandler_ = callback; + if (glkView_) { + glkview_setTouchCancelledCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchCancelledHandler_) { + handler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + // Keyboard event setters + void setKeyDownCallback(std::function callback) { + keyDownHandler = callback; + if (glkView_) { + glkview_setKeyDownCallback(glkView_, [](uint16_t keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyDownHandler) { + handler->keyDownHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + } + + void setKeyUpCallback(std::function callback) { + keyUpHandler = callback; + if (glkView_) { + glkview_setKeyUpCallback(glkView_, [](uint16_t keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyUpHandler) { + handler->keyUpHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + } + + + // Draw callback setter + void setDrawCallback(std::function callback) { + drawHandler = callback; + if (glkView_) { + glkview_setDrawCallback(glkView_, [](double x, double y, double width, double height, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->drawHandler) { + handler->drawHandler(x, y, width, height); + } + }, this); + } + } + + + + // Non-copyable but movable + GLKView(const GLKView&) = delete; + GLKView& operator=(const GLKView&) = delete; + GLKView(GLKView&& other) noexcept + : glkView_(other.glkView_), + parentViewController_(other.parentViewController_), + touchCallbacks_(std::move(other.touchCallbacks_)), + multiTouchCallbacks_(std::move(other.multiTouchCallbacks_)), + keyCallbacks_(std::move(other.keyCallbacks_)) { + other.glkView_ = nullptr; + other.parentViewController_ = nullptr; + } + + GLKView& operator=(GLKView&& other) noexcept { + if (this != &other) { + glkView_ = other.glkView_; + parentViewController_ = other.parentViewController_; + touchCallbacks_ = std::move(other.touchCallbacks_); + multiTouchCallbacks_ = std::move(other.multiTouchCallbacks_); + keyCallbacks_ = std::move(other.keyCallbacks_); + other.glkView_ = nullptr; + other.parentViewController_ = nullptr; + } + return *this; + } + }; + + // OpenGL ES Renderer class + class OpenGLRenderer { + private: + struct ::OpenGLRenderer* renderer_; + + public: + OpenGLRenderer() : renderer_(nullptr) { + renderer_ = opengl_init(); + if (!renderer_) { + throw FrameworkException("Failed to create iOS OpenGL renderer"); + } + } + + ~OpenGLRenderer() { + if (renderer_) { + opengl_destroy(renderer_); + } + } + + void attachToViewController(ViewController& viewController) { + if (renderer_) { + opengl_initialize(renderer_, viewController.getCHandle()); + } + } + + void setupContext() { + if (renderer_) { + opengl_setupContext(renderer_); + } + } + + void* getEAGLContext() const { + return renderer_ ? opengl_getEAGLContext(renderer_) : nullptr; + } + + void makeCurrentContext() { + if (renderer_) { + opengl_makeCurrentContext(renderer_); + } + } + + void setVsync(bool enabled) { + if (renderer_) { + opengl_setVsync(renderer_, enabled ? YES : NO); + } + } + + void enableVsync() { + setVsync(true); + } + + void disableVsync() { + setVsync(false); + } + + void swapBuffers() { + if (renderer_) { + opengl_swapBuffers(renderer_); + } + } + + // Non-copyable but movable + OpenGLRenderer(const OpenGLRenderer&) = delete; + OpenGLRenderer& operator=(const OpenGLRenderer&) = delete; + OpenGLRenderer(OpenGLRenderer&& other) noexcept : renderer_(other.renderer_) { + other.renderer_ = nullptr; + } + OpenGLRenderer& operator=(OpenGLRenderer&& other) noexcept { + if (this != &other) { + if (renderer_) { + opengl_destroy(renderer_); + } + renderer_ = other.renderer_; + other.renderer_ = nullptr; + } + return *this; + } + }; + + // Image loader wrapper class + class ImageLoader { + private: + struct ::ImageLoader* loader_; + std::string filePath_; + bool loaded_; + + public: + explicit ImageLoader() : loader_(nullptr), loaded_(false) { + loader_ = imageloader_init(); + if (!loader_) { + throw FrameworkException("Failed to create image loader"); + } + } + + ~ImageLoader() { + if (loader_) { + imageloader_destroy(loader_); + free(loader_); + } + } + + std::string GetApplicationPath() + { + if(loader_) { + char* path = imageloader_getApplicationPath(loader_); + if (path) { + std::string appPath(path); + free(path); + return appPath; + } + + } + return std::string{}; + } + + bool loadFromFile(const std::string& filePath) { + filePath_ = filePath; + if (loader_) { + BOOL result = imageloader_loadFromFile(loader_, filePath.c_str()); + loaded_ = (result != 0); + return loaded_; + } + 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_); + } + + OpenGLTexture createOpenGLTexture() const noexcept { + if (loader_ && loaded_) { + return OpenGLTexture(imageloader_createOpenGLTexture(loader_)); + } + return OpenGLTexture{}; + } + + // Legacy method for backwards compatibility + unsigned int createOpenGLTextureID() const noexcept { + if (loader_ && loaded_) { + return imageloader_createOpenGLTexture(loader_); + } + return 0; + } + + void getImageInfo(int& width, int& height, int& bytesPerPixel) const noexcept { + if (loader_ && loaded_) { + imageloader_getImageInfo(loader_, &width, &height, &bytesPerPixel); + } else { + width = height = bytesPerPixel = 0; + } + } + + const std::string& getFilePath() const noexcept { return filePath_; } + struct ::ImageLoader* getCHandle() const noexcept { return loader_; } + + // Non-copyable but movable + ImageLoader(const ImageLoader&) = delete; + ImageLoader& operator=(const ImageLoader&) = delete; + ImageLoader(ImageLoader&& other) noexcept + : loader_(other.loader_), filePath_(std::move(other.filePath_)), loaded_(other.loaded_) { + other.loader_ = nullptr; + other.loaded_ = false; + } + ImageLoader& operator=(ImageLoader&& other) noexcept { + if (this != &other) { + if (loader_) { + imageloader_destroy(loader_); + free(loader_); + } + loader_ = other.loader_; + filePath_ = std::move(other.filePath_); + loaded_ = other.loaded_; + other.loader_ = nullptr; + other.loaded_ = false; + } + return *this; + } + }; + + } // namespace ios + } // namespace apis +} // namespace olc + +//! END DECLARATION diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 578a847..7dc760f 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -37,7 +37,7 @@ namespace olc::apis::opengl bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffersEXT)) != nullptr; // bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; -#elif OLC_HOST == OLC_HOST_ANDROID +#elif OLC_HOST == OLC_HOST_ANDROID || OLC_HOST == OLC_HOST_IOS bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; @@ -117,7 +117,7 @@ namespace olc::apis::opengl sLocation << "OGL33 Error: GL_INVALID_FRAMEBUFFER_OPERATION\n"; break; case GL_OUT_OF_MEMORY: sLocation << "OGL33 Error: GL_OUT_OF_MEMORY\n"; break; -#if OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS case GL_STACK_UNDERFLOW: sLocation << "OGL33 Error: GL_STACK_UNDERFLOW\n"; break; case GL_STACK_OVERFLOW: @@ -155,7 +155,7 @@ namespace olc::apis::opengl void gl::glTexEnvf(GLenum target, GLenum pname, GLfloat param) { -#if OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS ::glTexEnvf(target, pname, param); CheckError(); #endif @@ -235,7 +235,7 @@ namespace olc::apis::opengl void gl::glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS ::glGetTexImage(target, level, format, type, pixels); CheckError(); #endif @@ -249,7 +249,7 @@ namespace olc::apis::opengl void gl::glPolygonMode(GLenum face, GLenum mode) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS ::glPolygonMode(face, mode); CheckError(); #endif @@ -454,7 +454,7 @@ namespace olc::apis::opengl void gl::glTexImage2DMultisample(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS _glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); CheckError(); #endif diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index c6c120a..5b1c8b6 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -44,19 +44,27 @@ #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) #endif -#if OLC_HOST == OLC_HOST_MACOS - #define GL_SILENCE_DEPRECATION // Stops MacOS & iOS whining about OpenGL is deprecated and we should use Metal +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED // Stops MacOS & iOS whining about OpenGL even more #define CALLSTYLE #define OGL_LOAD(t) &::t #define GL_GLEXT_PROTOTYPES - #undef GL_CLAMP + #undef GL_CLAMP #define GL_CLAMP GL_CLAMP_TO_EDGE - #include // Correct issue with Unknown type name 'ptrdiff_t' - #include - #include - #include - #include + #include + #if OLC_HOST == OLC_HOST_MACOS + #include + #include + #include + #include + #endif + #if OLC_HOST == OLC_HOST_IOS + #define GL_LINE GL_LINES + #define GL_FILL GL_LINE_STRIP + #include + #include + #endif + #endif #if OLC_HOST == OLC_HOST_EMSCRIPTEN @@ -122,6 +130,12 @@ namespace olc typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); #endif +#if OLC_HOST == OLC_HOST_IOS + typedef void* glDeviceContext_t; + typedef void* glRenderContext_t; // EAGLContext is the OpenGL ES rendering context for iOS + typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); +#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); typedef X11::GLXContext glDeviceContext_t; diff --git a/dev/src/config.h b/dev/src/config.h index fb23c03..04c79c7 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -47,8 +47,16 @@ #endif #if defined(__APPLE__) + // Note: Normally we dont include headers in config files but this is an exception + // as __APPLE__ is defined for both MacOS and iOS builds. we need to differentiate here. + #include + #if TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR + #define GLES_SILENCE_DEPRECATION + #define OLC_HOST OLC_HOST_IOS + #else #define GL_SILENCE_DEPRECATION #define OLC_HOST OLC_HOST_MACOS + #endif #endif #if defined(__EMSCRIPTEN__) @@ -93,6 +101,7 @@ #define OLC_IMAGELOADER_LIB_PNG 4 #define OLC_IMAGELOADER_NDK_IMAGEDECODER 5 #define OLC_IMAGELOADER_STB_IMAGE 6 +#define OLC_IMAGELOADER_IOS 7 #if defined(OLC_USE_STB_IMAGE) #define OLC_IMAGELOADER OLC_IMAGELOADER_STB_IMAGE @@ -110,6 +119,11 @@ #define OLC_IMAGELOADER_CLASS ImageLoader_MacOS #endif + #if OLC_HOST == OLC_HOST_IOS + #define OLC_IMAGELOADER OLC_IMAGELOADER_IOS + #define OLC_IMAGELOADER_CLASS ImageLoader_iOS + #endif + #if OLC_HOST == OLC_HOST_LINUX_X11 || OLC_HOST == OLC_HOST_LINUX_WAYLAND #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG #define OLC_IMAGELOADER_CLASS ImageLoader_LibPNG @@ -156,7 +170,7 @@ #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS // De-Noise in clang (C++20) MacOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-pragmas" // Allow unknown pragmas for compatibility with different compilers @@ -201,5 +215,8 @@ inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #define OLC_FRIENDLY_HOST Host_Android #endif +#if OLC_HOST == OLC_HOST_IOS +#define OLC_FRIENDLY_HOST Host_Apple_iOS +#endif //! END CONFIGURATION diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 19da9c9..798db87 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -16,11 +16,15 @@ #include "host_win_winapi.h" #endif -// Johnnyg63: Added define for MACOS +// Johnnyg63: Added define for MACOS/iOS #if OLC_HOST == OLC_HOST_MACOS #include "host_apple_macos.h" #endif +#if OLC_HOST == OLC_HOST_IOS +#include "host_apple_ios.h" +#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 #include "host_lin_x11.h" #endif @@ -49,6 +53,10 @@ #include "imload_macos.h" #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_IOS +#include "imload_ios.h" +#endif + #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG #include "imload_lib_png.h" #endif diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 86660a5..d2ac195 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -3,7 +3,7 @@ #include "gpu_iface.h" //! START IMPLEMENTATION -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpessimizing-move" // Suppress clang C++20 warning about moves preventing copy elision on macOS #endif @@ -1272,7 +1272,7 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic pop #endif diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 5aea813..e29a216 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -6,7 +6,7 @@ namespace olc::gpu // === PIXEL SHADER PGE DEFAULTS === std::string Shader::static_PS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS R"(#version 330 core )" #else @@ -45,7 +45,7 @@ void main() // === VERTEX SHADER PGE DEFAULTS === std::string Shader::static_VS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS R"(#version 330 core )" #else @@ -320,6 +320,17 @@ void main() #endif +#if OLC_HOST == OLC_HOST_IOS + + // os_win_id[0] is the OLC EAGL OpenGL Device Context + glRenderContext = (olc::apis::opengl::glRenderContext_t)os_win_id[0]; + if (!glRenderContext) { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } + +#endif + #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID #if OLC_HOST == OLC_HOST_ANDROID @@ -474,7 +485,7 @@ void main() gl.glEnableVertexAttribArray(5); // Buffers are configured, unbind for now - gl.glBindBuffer(gl.GL_ARRAY_BUFFER_X, 0); + gl.glBindBuffer(gl.GL_ARRAY_BUFFER_X, nScreenFBO); // Updated to better support iOS GLKit, and other Platforms FBOs which are not 0 gl.glBindVertexArray(0); @@ -529,7 +540,7 @@ void main() // PGE Specific requirements // Texturing Enabled -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS gl.glEnable(GL_TEXTURE_2D); // Turn on texturing gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); #endif @@ -561,6 +572,10 @@ void main() CGLSetCurrentContext(NULL); CGLDestroyContext((CGLContextObj)glRenderContext); #endif +#if OLC_HOST == OLC_HOST_IOS + // GLKit manages the OpenGL context on iOS, so we don't need to destroy it manually. + // Left in for completeness and future-proofing, but should not be called on iOS. +#endif #if OLC_HOST == OLC_HOST_LINUX_X11 auto* display = X11::XOpenDisplay(nullptr); X11::glXMakeCurrent(display, 0, NULL); @@ -598,6 +613,14 @@ void main() return false; } #endif +#if OLC_HOST == OLC_HOST_IOS + // params[0] is the OLC EAGL OpenGL Device Context + glRenderContext = (olc::apis::opengl::glRenderContext_t)os_win_id[0]; + if (!glRenderContext) { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } +#endif #if OLC_HOST == OLC_HOST_LINUX_X11 const auto window = reinterpret_cast(os_win_id[0]); auto* display = reinterpret_cast(os_win_id[1]); @@ -653,6 +676,14 @@ void main() return false; } +#endif +#if OLC_HOST == OLC_HOST_IOS + // params[0] is the OLC EAGL OpenGL Device Context + glRenderContext = (olc::apis::opengl::glRenderContext_t)os_win_id[0]; + if (!glRenderContext) { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } #endif return true; } @@ -713,7 +744,7 @@ void main() mapTextureToRenderbuffer[id] = rboId; } -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_MACOS && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_MACOS && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif @@ -749,7 +780,7 @@ void main() ); // Unbind renderbuffer - gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, 0); + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nScreenFBO); // Updated to better support iOS GLKit, and other Platforms FBOs which are not 0 } // Update size tracking @@ -769,7 +800,7 @@ void main() // which has been blitted to via ResolveMSAA if its an MSAA texture gl.glBindTexture(GL_TEXTURE_2D, image.GetGPUID()); -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS gl.glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); #else gl.glReadPixels(0, 0, image.Size().x, image.Size().y, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); @@ -1322,6 +1353,10 @@ void main() #endif +#if OLC_HOST == OLC_HOST_IOS + olc_IgnoreUnused(bVerticalSyncNow); +#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 const auto window_handle = reinterpret_cast(os_win_id[0]); auto* display = reinterpret_cast(os_win_id[1]); diff --git a/dev/src/host_apple_ios.cpp b/dev/src/host_apple_ios.cpp new file mode 100644 index 0000000..fd057d0 --- /dev/null +++ b/dev/src/host_apple_ios.cpp @@ -0,0 +1,595 @@ +#include "config.h" +#include "host_apple_ios.h" +#include "core.h" +#include + +#if OLC_HOST == OLC_HOST_IOS + +//! START IMPLEMENTATION +namespace olc::host { + + + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 + + Host_Apple_iOS::Host_Apple_iOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[4] = Key::A; + mapKeys[5] = Key::B; + mapKeys[6] = Key::C; + mapKeys[7] = Key::D; + mapKeys[8] = Key::E; + mapKeys[9] = Key::F; + mapKeys[10] = Key::G; + mapKeys[11] = Key::H; + mapKeys[12] = Key::I; + mapKeys[13] = Key::J; + mapKeys[14] = Key::K; + mapKeys[15] = Key::L; + mapKeys[16] = Key::M; + mapKeys[17] = Key::N; + mapKeys[18] = Key::O; + mapKeys[19] = Key::P; + mapKeys[20] = Key::Q; + mapKeys[21] = Key::R; + mapKeys[22] = Key::S; + mapKeys[23] = Key::T; + mapKeys[24] = Key::U; + mapKeys[25] = Key::V; + mapKeys[26] = Key::W; + mapKeys[27] = Key::X; + mapKeys[28] = Key::Y; + mapKeys[29] = Key::Z; + + // Numeric keys + mapKeys[30] = Key::K1; + mapKeys[31] = Key::K2; + mapKeys[32] = Key::K3; + mapKeys[33] = Key::K4; + mapKeys[34] = Key::K5; + mapKeys[35] = Key::K6; + mapKeys[36] = Key::K7; + mapKeys[37] = Key::K8; + mapKeys[38] = Key::K9; + mapKeys[39] = Key::K0; + + // Other Keys + mapKeys[40] = Key::ENTER; // Return + mapKeys[41] = Key::ESCAPE; // Escape + mapKeys[42] = Key::BACK; // Delete (Backspace) + mapKeys[43] = Key::TAB; // Tab + mapKeys[44] = Key::SPACE; // Space + mapKeys[45] = Key::DEL; // Forward Delete + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + mapKeys[114] = Key::INS; // Help (Insert equivalent) + + // Function Keys + mapKeys[58] = Key::F1; + mapKeys[59] = Key::F2; + mapKeys[60] = Key::F3; + mapKeys[61] = Key::F4; + mapKeys[62] = Key::F5; + mapKeys[63] = Key::F6; + mapKeys[64] = Key::F7; + mapKeys[65] = Key::F8; + mapKeys[66] = Key::F9; + mapKeys[67] = Key::F10; + mapKeys[68] = Key::F11; + mapKeys[69] = Key::F12; + + mapKeys[74] = Key::HOME; // Home + mapKeys[75] = Key::PGUP; // Page Up + mapKeys[76] = Key::DEL; // Delete (Forward Delete) + mapKeys[77] = Key::END; // End + mapKeys[78] = Key::PGDN; // Page Down + + + // Arrow Keys + mapKeys[79] = Key::RIGHT; + mapKeys[80] = Key::LEFT; + mapKeys[81] = Key::DOWN; + mapKeys[82] = Key::UP; + + // CHRL, SHIFT, Instert (Help) Keys + mapKeys[117] = Key::INS; // Insert (Help key on Mac) + mapKeys[224] = Key::CTRL; // Left Control + mapKeys[225] = Key::SHIFT; // Left Shift + mapKeys[229] = Key::SHIFT; // Left Shift + + // Symbol Keys (OEM equivalents) + mapKeys[51] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[56] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[53] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[47] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[49] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[48] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[52] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + // Unknown and may not be supported, but included for completeness will come back to this + mapKeys[84] = Key::NP_DIV; // Numpad / + mapKeys[85] = Key::NP_MUL; // Numpad * + mapKeys[86] = Key::NP_SUB; // Numpad - + mapKeys[87] = Key::NP_ADD; // Numpad + + mapKeys[88] = Key::RETURN; // Numpad Enter + mapKeys[89] = Key::NP1; + mapKeys[90] = Key::NP2; + mapKeys[91] = Key::NP3; + mapKeys[92] = Key::NP4; + mapKeys[93] = Key::NP5; + mapKeys[94] = Key::NP6; + mapKeys[95] = Key::NP7; + mapKeys[96] = Key::NP8; + mapKeys[97] = Key::NP9; + mapKeys[98] = Key::NP0; + mapKeys[99] = Key::NP_DECIMAL; // Numpad . + mapKeys[915] = Key::PAUSE; // Not Supported, F16 (often used as pause) + mapKeys[916] = Key::SCROLL; // Not Supported, F14 (scroll lock equivalent) + mapKeys[917] = Key::OEM_8; // Section sign § (varies by keyboard) + } + + bool Host_Apple_iOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + olc_IgnoreUnused(bFullScreen); + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); + + // iOS typically uses full screen, but we'll store the requested dimensions + frameBounds.x = static_cast(vWindowPos.x); + frameBounds.y = static_cast(vWindowPos.y); + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); + + return true; + } + + bool Host_Apple_iOS::CloseWindowFrame(olc::Window* pWindow) + { + if (!pIOSViewController) return false; + if (!pWindow) return false; + pWindow->olc_OnWindowClose(); + return true; + } + + bool Host_Apple_iOS::UpdateWindowFrameTitle(olc::Window* pWindow) + { + if (!pIOSViewController) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pIOSViewController->setTitle(pWindow->GetWindowTitle().c_str()); + }); + return true; + } + + std::vector Host_Apple_iOS::GetHostWindowDescriptor(olc::Window* pWindow) + { + return vIOSWindowDescriptors; + } + + bool Host_Apple_iOS::SyncWithDesktopComposite() + { + if(!enableVSync) + { + pIOSOpenGLRenderer->enableVsync(); + enableVSync = true; + } + + return enableVSync; + } + + bool Host_Apple_iOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + //pMacOSWindow->setCursorPosition(vPos.x, vPos.y); + }); + return false; + } + + bool Host_Apple_iOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + //pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; + } + + bool Host_Apple_iOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + olc_IgnoreUnused(pWindow); + dispatch_async(dispatch_get_main_queue(), ^{ + //pMacOSWindow->toggleFullScreen(); + }); + return true; + } + + bool Host_Apple_iOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Apple_iOS::StartSystem(){ + + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Create iOS Application instance + pIOSApplication = std::make_unique(); + + // Set up application delegate event handlers + IOSApplicationEventsHandler(); + + // Initialize and activate application first + pIOSApplication->initialize(); + + return true; + + } + + bool Host_Apple_iOS::StopSystem() + { + // Note: iOS applications typically don't have a "stop" method, but we can clean up resources here if needed + //dispatch_sync(dispatch_get_main_queue(), ^{ + + //}); + return true; + } + + bool Host_Apple_iOS::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Apple_iOS::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + bool Host_Apple_iOS::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Apple_iOS::OnApplicationEnd() + { + return true; + } + + std::string Host_Apple_iOS::GetApplicationPath() + { + if(pIOSApplication) + { + return pIOSApplication->getApplicationPath(); + } + return "ACCESS-DEINED"; + } + + olc::KeyboardLayout Host_Apple_iOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pIOSApplication) + { + std::string locale = pIOSApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + + + void Host_Apple_iOS::UpdateIOSViewFrameBounds(olc::apis::ios::DeviceOrientation orientation) + { + // NOTE: iOS handles rotation automatically, but we need to adjust the PGE window size accordingly + // IMPORTANT: PortraitUpsideDown is not implemented, in short have a happy life and forget this exist ;) + CurrentOrientation = orientation; + switch (orientation) { + case olc::apis::ios::DeviceOrientation::Portrait: + pIOSViewController->getViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + break; + case olc::apis::ios::DeviceOrientation::LandscapeLeft: + case olc::apis::ios::DeviceOrientation::LandscapeRight: + pIOSViewController->getViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.height), static_cast(frameBounds.width)}); + break; + case olc::apis::ios::DeviceOrientation::FaceUp: + case olc::apis::ios::DeviceOrientation::FaceDown: + case olc::apis::ios::DeviceOrientation::PortraitUpsideDown: + case olc::apis::ios::DeviceOrientation::Unknown: + default: + break; + } + + + } + + void Host_Apple_iOS::IOSApplicationEventsHandler() + { + // Application event handling code here + // Set application delegate event handlers + pIOSApplication->setDidFinishLaunchingCallback([&]() { + // Tell the PGE 3.0 we have loaded the application + + pIOSApplication->activate(); + + // Initialize the iOS View Controller + pIOSViewController = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 iOS Demo"); + + // Set up view controller event handlers + IOSViewControllerEventsHandler(); + + // Present the view controller + pIOSViewController->present(); + + // App became active (foreground) + if(pIOSOpenGLRenderer == nullptr) + { + vIOSWindowDescriptors.clear(); // ensure we are starting fresh + pIOSOpenGLRenderer = std::make_shared(); + + pIOSOpenGLRenderer->attachToViewController(*pIOSViewController); + pIOSOpenGLRenderer->setupContext(); + + pIOSGLContextObj = pIOSOpenGLRenderer->getEAGLContext(); + + pIOSOpenGLRenderer->setVsync(false); + + vIOSWindowDescriptors.push_back(pIOSGLContextObj); + } + + // Set up OpenGL renderer for visual feedback + pIOSOpenGLRenderer->makeCurrentContext(); + + //std::cout << "iOS Application did finish launching." << std::endl; + }); + + pIOSApplication->setWillTerminateCallback([&]() { + // TODO: Implement olc_OnApplicationTerminate in window.h/cpp + //std::cout << "iOS Application will terminate." << std::endl; + }); + + pIOSApplication->setDidBecomeActiveCallback([&]() { + // TODO: manage thread resuming + + if(pPGEwindow) + pPGEwindow->olc_OnFocus(true); + ///std::cout << "iOS Application did become active." << std::endl; + }); + + pIOSApplication->setWillResignActiveCallback([&]() { + + if(pPGEwindow) + pPGEwindow->olc_OnFocus(false); + // TODO: manage thread pausing + //std::cout << "iOS Application will resign active." << std::endl; + }); + + pIOSApplication->setDidEnterBackgroundCallback([&]() { + // App entered background + if(pPGEwindow) + pPGEwindow->olc_OnFocus(false); + std::cout << "iOS Application did enter background." << std::endl; + }); + + pIOSApplication->setWillEnterForegroundCallback([&]() { + // App will enter foreground + if(pPGEwindow) + pPGEwindow->olc_OnFocus(true); + //std::cout << "iOS Application will enter foreground." << std::endl; + }); + } + + void Host_Apple_iOS::IOSViewControllerEventsHandler() + { + // View controller event handling code here + pIOSViewController->setViewDidLoadCallback([&]() { + // View controller loaded + + // Set up GLKView after presentation + pIOSGLKView = std::make_unique(*pIOSViewController); + // Setup Event handlers (including touch events) + IOSGLKViewEventHandler(); + if (!pIOSGLKView->initialize()) + { + std::cerr << "Failed to initialize GLKView." << std::endl; + return; + } + pIOSGLKView->enableTouchHandling(); + + auto test = pIOSGLKView->isMultipleTouchEnabled(); + if(!test) + pIOSGLKView->setMultipleTouchEnabled(true); + pIOSGLKView->debugTouchSetup(); + //std::cout << "iOS View Controller did load." << std::endl; + }); + + pIOSViewController->setViewWillAppearCallback([&]() { + // View will appear + + //std::cout << "iOS View Controller will appear." << std::endl; + }); + + pIOSViewController->setViewDidAppearCallback([&]() { + // View appeared + + if(!bIOSDrawing) + { + pIOSViewController->setPaused(false); // Start drawing + } + //std::cout << "iOS View Controller did appear." << std::endl; + }); + + pIOSViewController->setViewWillDisappearCallback([&]() { + // View will disappear + pPGEwindow->olc_OnWindowClose(); + //std::cout << "iOS View Controller will disappear." << std::endl; + }); + + pIOSViewController->setViewDidDisappearCallback([&]() { + // View disappeared + + pPGEwindow->olc_ShouldRemove(); + //std::cout << "iOS View Controller did disappear." << std::endl; + }); + + pIOSViewController->setViewDidLayoutSubviewsCallback([&]() { + + UpdateIOSViewFrameBounds(CurrentOrientation); + }); + + pIOSViewController->setUpdateCallback( [&]() { + // This is where we will call the engine's main loop for rendering + + }); + + // Device orientation changes + pIOSViewController->setOrientationChangedCallback([&](olc::apis::ios::DeviceOrientation orientation) { + UpdateIOSViewFrameBounds(orientation); + }); + + } + + // handles both down and up strokes for every supported key that isn't a modifier + void Host_Apple_iOS::KeyboardEventHandler(uint16_t nKeyCode, uint nModifierFlags, bool isPressed){ + // handle num clear/lock key only on the down stroke. + if(isPressed && nKeyCode == 83) + { + bNumLockActive = !bNumLockActive; + return; + } + + if(!bNumLockActive) + { + // 94 right, 92 left, 90 down, 96 up >>> 79 RIGHT, 80 LEFT, 81 DOWN, 82 UP + switch(nKeyCode) + { + case 94: nKeyCode = 79; break; + case 92: nKeyCode = 80; break; + case 90: nKeyCode = 81; break; + case 96: nKeyCode = 82; break; + default: break; + } + } + + // Check Modifier flags for Shift, Control, Caps, and Command keys + unsigned int changedFlags = nModifierFlags ^ prevFlags; + + // Check For Shift key + if (changedFlags & NSEventModifierFlagShift) { + bool isPressed = nModifierFlags & NSEventModifierFlagShift; + pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); + } + + // Check for Control key + if (changedFlags & NSEventModifierFlagControl) { + bool isPressed = nModifierFlags & NSEventModifierFlagControl; + pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); + } + + // Check for Option / ALT key + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = nModifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + } + + prevFlags = nModifierFlags; + + pPGEwindow->olc_OnKeyPress(mapKeys[nKeyCode], isPressed); + + } + + void Host_Apple_iOS::IOSGLKViewEventHandler() + { + + // Set up drawing callback for GLKView + pIOSGLKView->setDrawCallback([&](double x, double y, double width, double height) { + + pIOSOpenGLRenderer->makeCurrentContext(); + + if(!bPGEInitialized) + { + pPrimaryPGE->OnContextStart(); + bPGEInitialized = true; + } + pPrimaryPGE->OnContextTick(); + }); + + // Set up touch event handlers (iOS primary input method) + pIOSGLKView->setTouchBeganCallback([&](const olc::apis::ios::TouchEvent& event) { + // Convert touch to mouse button press for compatibility + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Began at (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; + }); + + pIOSGLKView->setTouchMovedCallback([&](const olc::apis::ios::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Moved to (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; + }); + + pIOSGLKView->setTouchEndedCallback([&](const olc::apis::ios::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Ended at (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; + }); + + pIOSGLKView->setTouchCancelledCallback([&](const olc::apis::ios::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Cancelled at (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; + }); + + // Set up keyboard event handlers + pIOSGLKView->setKeyDownCallback([&](uint16_t keyCode, const std::string& characters, uint modifierFlags) { + //std::cout << "Key Down - Code: " << keyCode << ", Chars: '" << characters << "'" << std::endl; + KeyboardEventHandler(keyCode, modifierFlags, true); + }); + + pIOSGLKView->setKeyUpCallback([&](uint16_t keyCode, const std::string& characters, uint modifierFlags) { + //std::cout << "Key Up - Code: " << keyCode << std::endl; + KeyboardEventHandler(keyCode, modifierFlags, false); + }); + } +} + +//! END IMPLEMENTATION +#endif /* OLC_HOST == OLC_HOST_IOS */ diff --git a/dev/src/host_apple_ios.h b/dev/src/host_apple_ios.h new file mode 100644 index 0000000..b03904f --- /dev/null +++ b/dev/src/host_apple_ios.h @@ -0,0 +1,151 @@ +#pragma once +#include "config.h" + +//! START STDHEADER GLOBAL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "host_iface.h" +#include "api_ios_wrapper.hpp" +#include "window.h" +//! END CUSTOMHEADER + +//! START IOS_CONFIG +//! END IOS_CONFIG + +#if OLC_HOST == OLC_HOST_IOS +//! START DECLARATION +#include // Grand Central Dispatch: Apple's C-based API for managing concurrent operations on iOS +#include // POSIX threads: Used for pthread_main_np() to check if current thread is the main thread + +namespace olc +{ + namespace host + { + // Manages our iOS Host + class Host_Apple_iOS : public olc::host::Host + { + + public: + olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window + + public: + // Default constructor + Host_Apple_iOS(); + virtual ~Host_Apple_iOS() {}; + + public: + // Check/Get last error + HostError GetLastError() const { return lastError; } + + public: + virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + virtual bool CloseWindowFrame(olc::Window* pWindow) override; + virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + // Wait for entire host desktop refresh (for smooth vsync) + virtual bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + 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; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() override; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() override; + // Called at start of system event loop + virtual bool OnSystemThreadStart() override; + // Called to perform primary window update + virtual bool OnSystemTick() override; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() override; + // Called at very end of application + virtual bool OnApplicationEnd() override; + + protected: + HostError lastError = HostError::None; + + public: + // Internal iOS functions + // iOS Application and View Controller pointers + std::unique_ptr pIOSApplication = nullptr; // Smart Pointer to iOS Application instance + std::unique_ptr pIOSViewController = nullptr; // Smart Pointer to iOS View Controller instance + std::unique_ptr pIOSGLKView = nullptr; // Smart Pointer to iOS GLKView instance + std::shared_ptr pIOSOpenGLRenderer = nullptr; // Smart Pointer to iOS OpenGL Renderer instance + + // Returns the full path to the application bundle on iOS or "ACCESS-DEINED" + std::string GetApplicationPath(); + + + void* pIOSGLContextObj = nullptr; + std::once_flag initialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + + + + void (*pPGEEngineFunc)(void*); + void (*pPGECoreUpdateFunc)(void*); + void* pPGEUserData; + + private: + + std::vector vIOSWindowDescriptors; // Vector to hold view descriptors + bool enableVSync = false; // VSync enabled flag + + bool bIOSDrawing = false; // Flag indicating if iOS drawing is active + bool bPGEInitialized = false; // Flag indicating if PGE has been initialized + + struct sFrameBounds + { + double x = 0.0; + double y = 0.0; + double width = 800.0; + double height = 600.0; + } frameBounds; + + olc::apis::ios::DeviceOrientation CurrentOrientation = olc::apis::ios::DeviceOrientation::Unknown; + void UpdateIOSViewFrameBounds(olc::apis::ios::DeviceOrientation orientation); + + // Keyboard stuff + bool bNumLockActive = false; // Track Num Lock state + unsigned int prevFlags = 0; // Track previous modifier flags for keyboard events + void KeyboardEventHandler(uint16_t nKeyCode, uint nModifierFlags, bool isPressed); + + void IOSApplicationEventsHandler(); + void IOSViewControllerEventsHandler(); + void IOSGLKViewEventHandler(); + void IOSOpenGLContextEventsHandler(); + + + }; + } +} + +//! END DECLARATION +#endif /* OLC_HOST == OLC_HOST_IOS */ diff --git a/dev/src/imload_ios.cpp b/dev/src/imload_ios.cpp new file mode 100644 index 0000000..27a73d9 --- /dev/null +++ b/dev/src/imload_ios.cpp @@ -0,0 +1,171 @@ +#include "imload_ios.h" + +//! START CUSTOMHEADER +#include "api_ios.h" // For ios_imageloader_getPixelData function +//! END CUSTOMHEADER + +//! START IMPLEMENTATION +namespace olc::imload +{ + + bool ImageLoader_iOS::CreateImageFromFile(olc::Image& image, const std::string& sFileName) + { + try { + + // Create iOS API wrapper image loader + olc::apis::ios::ImageLoader loader; + + // OK Working with mobile storage, we need to check for the file in various locations + std::filesystem::path filePath(sFileName); + + // Lets check if the developer has provided a full path to the file + if (!std::filesystem::exists(filePath)) { + + // File does not exist at the provided path, try to locate it in the app bundle + char* cAppFilePath = imageloader_getApplicationPath(loader.getCHandle()); + if (cAppFilePath) { + + // we need the directory of the app bundle to search for the file + std::filesystem::path fsAppDir(cAppFilePath); + std::filesystem::path potentialPath = fsAppDir / sFileName; + std::filesystem::path normalizedPath = std::filesystem::weakly_canonical(potentialPath); + + // lets check if the file exists in the app bundle directory (Project Directory) + if(std::filesystem::exists(normalizedPath)) { + filePath = normalizedPath; + } + else + { + bool bFound = false; + // Try multiple locations + for(auto& entry : std::filesystem::directory_iterator(fsAppDir)) + { + if (entry.is_directory()) { + potentialPath = entry.path() / sFileName; + normalizedPath = std::filesystem::weakly_canonical(potentialPath); + + if (std::filesystem::exists(normalizedPath)) { + filePath = potentialPath; + bFound = true; + break; + } + + } // end is entry directory + } // end directory search + + if(!bFound) return false; // all done nothing found + + } // end (Project Directory) search + + } + else + { + return false; // Failed to get application path + } + + } + + + // Convert back to string for the loader + std::string fullPath = filePath.string(); + + // Load the image file + if (!loader.loadFromFile(fullPath) || !loader.isLoaded()) { + return false; // Failed to load file + } + + // Get image dimensions and info + int width =0; int height = 0; int bytesPerPixel = 0; + 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.CreateNoGPU({width, height})) { + return false; // Failed to create image + } + + // Clear and resize the pixel vector + image.GetPixels().clear(); + image.GetPixels().resize(width * height); + + // Copy pixel data - assuming the loader provides RGBA data + // The api_ios should provide RGBA format with 4 bytes per pixel + std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + + return true; + } + catch (const std::exception& e) { + // Handle any exceptions from the wrapper + printf("Exception in CreateImageFromFile: %s\n", e.what()); + return false; + } + } + + bool ImageLoader_iOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) + { + if(!data) return false; + + // Create macOS API wrapper image loader + olc::apis::ios::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.CreateNoGPU({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_iOS::CreateImageFromMemory(olc::Image& image, const std::vector& data) + { + return CreateImageFromMemory(image, data.data(), data.size()); + } + + bool ImageLoader_iOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + { + olc_IgnoreUnused(image, sFileName); + return false; + } + + bool ImageLoader_iOS::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) + { + olc_IgnoreUnused(image, data); + return false; + } +} +//! END IMPLEMENTATION diff --git a/dev/src/imload_ios.h b/dev/src/imload_ios.h new file mode 100644 index 0000000..6eb75d5 --- /dev/null +++ b/dev/src/imload_ios.h @@ -0,0 +1,46 @@ +#pragma once + +//! START CUSTOMHEADER +#include "imload_iface.h" +#include "api_ios_wrapper.hpp" +//! END CUSTOMHEADER + +//! START STDHEADER GLOBAL +#include +//! END STDHEADER + + +//! START DECLARATION +#if !defined(PGE_IMAGELOADER_IOS_DECLARED) +namespace olc +{ + namespace imload + { + class ImageLoader_iOS : public ImageLoader + { + public: + + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + + }; + } +} + +#define PGE_IMAGELOADER_IOS_DECLARED 1 +#endif +//! END DECLARATION + + diff --git a/dev/src/pixel.h b/dev/src/pixel.h index 95bb5de..f42dcda 100644 --- a/dev/src/pixel.h +++ b/dev/src/pixel.h @@ -17,7 +17,7 @@ //! START DECLARATION #if !defined(PGE_PIXEL_DECLARED) -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" // Silence warnings about implicitly generated copy constructor for Pixel #endif @@ -328,7 +328,7 @@ namespace olc } } -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic pop #endif diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index c694782..cbcb06e 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -171,6 +171,12 @@ //! GRAB api_macos_wrapper.hpp DECLARATION #endif +#if OLC_HOST == OLC_HOST_IOS +//! GRAB api_ios.h DECLARATION + +//! GRAB api_ios_wrapper.hpp DECLARATION +#endif + #if !defined(OLC_USE_WXWIDGETS) #if OLC_HOST == OLC_HOST_NONE @@ -191,6 +197,12 @@ //! GRAB host_apple_macos.h DECLARATION #endif +#if OLC_HOST == OLC_HOST_IOS +//! GRAB host_apple_ios.h IOS_CONFIG + +//! GRAB host_apple_ios.h DECLARATION +#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 //! GRAB host_lin_x11.h X11_CONFIG @@ -245,6 +257,10 @@ //! GRAB imload_macos.h DECLARATION #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_IOS +//! GRAB imload_ios.h DECLARATION +#endif + #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG //! GRAB imload_lib_png.h DECLARATION #endif @@ -264,6 +280,11 @@ //! GRAB api_macos.cpp IMPLEMENTATION #endif +#if OLC_HOST == OLC_HOST_IOS +//! GRAB api_ios.cpp IMPLEMENTATION +#endif + + #if !defined(OLC_USE_WXWIDGETS) #if OLC_HOST == OLC_HOST_NONE @@ -278,6 +299,10 @@ //! GRAB host_apple_macos.cpp IMPLEMENTATION #endif +#if OLC_HOST == OLC_HOST_IOS +//! GRAB host_apple_ios.cpp IMPLEMENTATION +#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 //! GRAB host_lin_x11.cpp IMPLEMENTATION #endif @@ -364,6 +389,9 @@ #if OLC_IMAGELOADER == OLC_IMAGELOADER_MACOS //! GRAB imload_macos.cpp IMPLEMENTATION #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_IOS +//! GRAB imload_ios.cpp IMPLEMENTATION +#endif #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG //! GRAB imload_lib_png.cpp IMPLEMENTATION #endif diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index f02fd43..b446b06 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -17,7 +17,7 @@ //! START DECLARATION #if !defined(PGE_TRANSFORM2D_DECLARED) -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wignored-qualifiers" // Silence warnings about ignored qualifiers in olc::t_2d #endif @@ -263,7 +263,7 @@ namespace olc typedef t_2d tf2d; typedef t_2d td2d; } -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic pop #endif #define PGE_TRANSFORM2D_DECLARED 1 diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index b9f4874..e3af581 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -3,29 +3,56 @@ archiveVersion = 1; classes = { }; - objectVersion = 70; + objectVersion = 73; objects = { /* Begin PBXBuildFile section */ 460496082E362F190047E223 /* test_mh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495FB2E300E0F0047E223 /* test_mh.cpp */; }; - 4610525930593E6D00C2BC3A /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; - 4610525A30593E6D00C2BC3A /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B1E5942E40F8B0009734EC /* MetalKit.framework */; }; - 4610525B30593E6D00C2BC3A /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B1E5922E40F8A6009734EC /* AppKit.framework */; }; - 4610525C30593E6D00C2BC3A /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFC2E39137100B74FA3 /* Metal.framework */; }; - 4610525D30593E6D00C2BC3A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */; }; - 4610525E30593E6D00C2BC3A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF62E39135300B74FA3 /* Foundation.framework */; }; - 4610526630593EE400C2BC3A /* assets in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4610526530593EE400C2BC3A /* assets */; }; - 46152BD72FE2DD1E00DCAC34 /* hw_touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */; }; + 460496092E362F190047E224 /* test_mh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495FB2E300E0F0047E223 /* test_mh.cpp */; }; + 46161A4B3030B3D5006A67D5 /* assets in CopyFiles */ = {isa = PBXBuildFile; fileRef = 46161A4A3030B3D5006A67D5 /* assets */; }; + 461B0B6D2F04346B00F47017 /* host_apple_ios.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 461B0B692F04346B00F47017 /* host_apple_ios.cpp */; }; 462680A42E69D03800799C36 /* image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495EC2E300E000047E223 /* image.cpp */; }; 462680A52E69D15C00799C36 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; - 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; - 466C385E2F41D5D400F75AF4 /* draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 466C385C2F41D5D400F75AF4 /* draw.cpp */; }; - 466C38602F41D69000F75AF4 /* draw_batch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 466C385F2F41D69000F75AF4 /* draw_batch.cpp */; }; + 462680A72E69D03800799C37 /* image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495EC2E300E000047E223 /* image.cpp */; }; + 462680D22E6C3C4500799C37 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; 467318BA2EEC213700714D4C /* font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467318B92EEC213700714D4C /* font.cpp */; }; + 467318BB2EEC213700714D4D /* font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467318B92EEC213700714D4C /* font.cpp */; }; + 467441883034840E00E2DD84 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 467441873034840E00E2DD84 /* CoreGraphics.framework */; }; + 467441F73034B2BF00E2DD84 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 467441873034840E00E2DD84 /* CoreGraphics.framework */; }; + 467797882FF022A300171349 /* hw_keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797832FF022A300171349 /* hw_keyboard.cpp */; }; + 467797892FF022A300171349 /* hw_touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797852FF022A300171349 /* hw_touch.cpp */; }; + 4677978A2FF022A300171349 /* draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797812FF022A300171349 /* draw.cpp */; }; + 4677978B2FF022A300171349 /* draw_batch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797822FF022A300171349 /* draw_batch.cpp */; }; + 4677978C2FF022A300171349 /* hw_keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797832FF022A300171349 /* hw_keyboard.cpp */; }; + 4677978D2FF022A300171349 /* hw_touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797852FF022A300171349 /* hw_touch.cpp */; }; + 4677978E2FF022A300171349 /* draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797812FF022A300171349 /* draw.cpp */; }; + 4677978F2FF022A300171349 /* draw_batch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467797822FF022A300171349 /* draw_batch.cpp */; }; 46964CE82EE5C23300B7BCF1 /* api_macos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46964CE62EE5C23300B7BCF1 /* api_macos.cpp */; }; 469A92132E79B0F1007BB460 /* hw_mouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 469A92122E79B0F1007BB460 /* hw_mouse.cpp */; }; + 469A92142E79B0F1007BB461 /* hw_mouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 469A92122E79B0F1007BB460 /* hw_mouse.cpp */; }; + 46A1B5982E40F8B0009734EE /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46A1B5972E40F8B0009734EE /* GLKit.framework */; }; + 46A1B5A02E39196500B74FA5 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFC2E39137100B74FA3 /* Metal.framework */; }; + 46A1B5A12E39195A00B74FA5 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */; }; + 46A1B5A22E39193D00B74FA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF62E39135300B74FA3 /* Foundation.framework */; }; + 46ABFEA7302CC0B600C95998 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46ABFEA6302CC0B600C95998 /* OpenGLES.framework */; }; + 46ABFEA9302CC0BF00C95998 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46ABFEA8302CC0BF00C95998 /* UIKit.framework */; }; + 46ABFEBE302CC20600C95998 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; + 46ABFEBF302CC20600C95998 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B1E5942E40F8B0009734EC /* MetalKit.framework */; }; + 46ABFEC0302CC20600C95998 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B1E5922E40F8A6009734EC /* AppKit.framework */; }; + 46ABFEC1302CC20600C95998 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFC2E39137100B74FA3 /* Metal.framework */; }; + 46ABFEC2302CC20600C95998 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */; }; + 46ABFEC3302CC20600C95998 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF62E39135300B74FA3 /* Foundation.framework */; }; + 46ABFEDD302CC25F00C95998 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46ABFEA6302CC0B600C95998 /* OpenGLES.framework */; }; + 46ABFEDE302CC25F00C95998 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46ABFEA8302CC0BF00C95998 /* UIKit.framework */; }; + 46ABFEDF302CC25F00C95998 /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46A1B5972E40F8B0009734EE /* GLKit.framework */; }; + 46ABFEE0302CC25F00C95998 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFC2E39137100B74FA3 /* Metal.framework */; }; + 46ABFEE1302CC25F00C95998 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */; }; + 46ABFEE2302CC25F00C95998 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF62E39135300B74FA3 /* Foundation.framework */; }; + 46ABFF58302CC3D200C95998 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; 46B1E5932E40F8A6009734EC /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B1E5922E40F8A6009734EC /* AppKit.framework */; }; 46B1E5952E40F8B0009734EC /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B1E5942E40F8B0009734EC /* MetalKit.framework */; }; + 46C0F41D2F0D08940053B0C8 /* imload_ios.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46C0F41C2F0D08940053B0C8 /* imload_ios.cpp */; }; + 46C0F41E2F0D08940053B0C8 /* api_ios.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46C0F4192F0D08940053B0C8 /* api_ios.cpp */; }; 46C8F5B92E7DA67900C11019 /* imload_macos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46C8F5B82E7DA67900C11019 /* imload_macos.cpp */; }; 46E85DFE2E39193D00B74FA3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF62E39135300B74FA3 /* Foundation.framework */; }; 46E85DFF2E39195A00B74FA3 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */; }; @@ -35,28 +62,49 @@ 46E85E092E3924FB00B74FA3 /* core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495DD2E300E000047E223 /* core.cpp */; }; 46E85E0A2E39252200B74FA3 /* window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495F22E300E000047E223 /* window.cpp */; }; 46E85E0B2E39252B00B74FA3 /* gpu_opengl33.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495E12E300E000047E223 /* gpu_opengl33.cpp */; }; - 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */; }; + 46E85E0C2E3924CC00B74FA4 /* api_opengl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495DB2E300E000047E223 /* api_opengl.cpp */; }; + 46E85E0E2E3924FB00B74FA4 /* core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495DD2E300E000047E223 /* core.cpp */; }; + 46E85E0F2E39252200B74FA4 /* window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495F22E300E000047E223 /* window.cpp */; }; + 46E85E102E39252B00B74FA4 /* gpu_opengl33.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495E12E300E000047E223 /* gpu_opengl33.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - 4610525F30593E6D00C2BC3A /* CopyFiles */ = { + 46161A4C3030B3E2006A67D5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = tests; + dstSubfolderSpec = 7; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 46ABFEC4302CC20600C95998 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 7; files = ( - 4610526630593EE400C2BC3A /* assets in CopyFiles */, + 46161A4B3030B3D5006A67D5 /* assets in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; - 8998CE1E2E057F4E007DDD96 /* CopyFiles */ = { + 46ABFF6B302E600200C95998 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; + dstPath = assets; + dstSubfolderSpec = 7; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8998CE1E2E057F4E007DDD96 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = tests; + dstSubfolderSpec = 16; files = ( ); - runOnlyForDeploymentPostprocessing = 1; + runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -84,19 +132,21 @@ 460495F12E300E000047E223 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = ""; }; 460495F22E300E000047E223 /* window.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = window.cpp; sourceTree = ""; }; 460495FB2E300E0F0047E223 /* test_mh.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_mh.cpp; sourceTree = ""; }; - 4610526330593E6D00C2BC3A /* olcPGE3_SH */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = olcPGE3_SH; sourceTree = BUILT_PRODUCTS_DIR; }; - 4610526530593EE400C2BC3A /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../examples/assets; sourceTree = ""; }; - 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_touch.h; sourceTree = ""; }; - 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_touch.cpp; sourceTree = ""; }; + 46161A4A3030B3D5006A67D5 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../examples/assets; sourceTree = ""; }; + 461B0B682F04346B00F47017 /* host_apple_ios.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = host_apple_ios.h; sourceTree = ""; }; + 461B0B692F04346B00F47017 /* host_apple_ios.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = host_apple_ios.cpp; sourceTree = ""; }; 462680D02E6C3C4500799C36 /* gpu_iface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpu_iface.cpp; sourceTree = ""; }; - 466C38492F3A0B0E00F75AF4 /* matrix4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix4d.h; sourceTree = ""; }; - 466C384A2F3A0B0E00F75AF4 /* vector4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vector4d.h; sourceTree = ""; }; - 466C385B2F41D5D400F75AF4 /* draw.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = draw.h; sourceTree = ""; }; - 466C385C2F41D5D400F75AF4 /* draw.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw.cpp; sourceTree = ""; }; - 466C385D2F41D5D400F75AF4 /* extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = extension.h; sourceTree = ""; }; - 466C385F2F41D69000F75AF4 /* draw_batch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw_batch.cpp; sourceTree = ""; }; 467318B82EEC213700714D4C /* font.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = font.h; sourceTree = ""; }; 467318B92EEC213700714D4C /* font.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = font.cpp; sourceTree = ""; }; + 467441873034840E00E2DD84 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + 467797802FF022A300171349 /* draw.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = draw.h; sourceTree = ""; }; + 467797812FF022A300171349 /* draw.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw.cpp; sourceTree = ""; }; + 467797822FF022A300171349 /* draw_batch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw_batch.cpp; sourceTree = ""; }; + 467797832FF022A300171349 /* hw_keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_keyboard.cpp; sourceTree = ""; }; + 467797842FF022A300171349 /* hw_touch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_touch.h; sourceTree = ""; }; + 467797852FF022A300171349 /* hw_touch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_touch.cpp; sourceTree = ""; }; + 467797862FF022A300171349 /* matrix4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix4d.h; sourceTree = ""; }; + 467797872FF022A300171349 /* vector4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vector4d.h; sourceTree = ""; }; 46964CE52EE5C23300B7BCF1 /* api_macos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = api_macos.h; sourceTree = ""; }; 46964CE62EE5C23300B7BCF1 /* api_macos.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = api_macos.cpp; sourceTree = ""; }; 46964CE72EE5C23300B7BCF1 /* api_macos_wrapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = api_macos_wrapper.hpp; sourceTree = ""; }; @@ -105,28 +155,39 @@ 469A92102E79B04B007BB460 /* hw_keyboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_keyboard.h; sourceTree = ""; }; 469A92112E79B0B0007BB460 /* hw_mouse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_mouse.h; sourceTree = ""; }; 469A92122E79B0F1007BB460 /* hw_mouse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_mouse.cpp; sourceTree = ""; }; + 46A1B5972E40F8B0009734EE /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; }; + 46ABFEA6302CC0B600C95998 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.2.sdk/System/Library/Frameworks/OpenGLES.framework; sourceTree = DEVELOPER_DIR; }; + 46ABFEA8302CC0BF00C95998 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.2.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 46ABFEC8302CC20600C95998 /* olcPGE3_SH */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = olcPGE3_SH; sourceTree = BUILT_PRODUCTS_DIR; }; + 46ABFEE6302CC25F00C95998 /* olcPGE3_iOS_SH.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = olcPGE3_iOS_SH.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 46ABFF5B302CC58200C95998 /* olcPixelGameEngine3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = olcPixelGameEngine3.h; path = ../../olcPixelGameEngine3.h; sourceTree = ""; }; 46B1E5922E40F8A6009734EC /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 46B1E5942E40F8B0009734EC /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 46C0F4182F0D08940053B0C8 /* api_ios.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = api_ios.h; sourceTree = ""; }; + 46C0F4192F0D08940053B0C8 /* api_ios.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = api_ios.cpp; sourceTree = ""; }; + 46C0F41A2F0D08940053B0C8 /* api_ios_wrapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = api_ios_wrapper.hpp; sourceTree = ""; }; + 46C0F41B2F0D08940053B0C8 /* imload_ios.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = imload_ios.h; sourceTree = ""; }; + 46C0F41C2F0D08940053B0C8 /* imload_ios.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = imload_ios.cpp; sourceTree = ""; }; 46C8F5B72E7DA67900C11019 /* imload_macos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = imload_macos.h; sourceTree = ""; }; 46C8F5B82E7DA67900C11019 /* imload_macos.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = imload_macos.cpp; sourceTree = ""; }; - 46E03AD8305C0D3A003BFA1D /* olcPixelGameEngine3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = olcPixelGameEngine3.h; path = ../../olcPixelGameEngine3.h; sourceTree = ""; }; 46E85DF62E39135300B74FA3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 46E85DF82E39135B00B74FA3 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 46E85DFC2E39137100B74FA3 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_keyboard.cpp; sourceTree = ""; }; 8998CE202E057F4E007DDD96 /* olcPGE3 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = olcPGE3; sourceTree = BUILT_PRODUCTS_DIR; }; 8998CE2C2E057F85007DDD96 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 8998CE332E057F92007DDD96 /* pixel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixel.h; sourceTree = ""; }; 8998CE342E057F92007DDD96 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; 8998CE362E057F92007DDD96 /* vector2d.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector2d.h; sourceTree = ""; }; 8998CE372E057F92007DDD96 /* transform2d.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = transform2d.h; sourceTree = ""; }; + 8998CE402E057F4E007DDD98 /* olcPGE3_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = olcPGE3_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 46E03B94305C0DC1003BFA1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + 46161A523030B44E006A67D5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( + olcPGE3_3DCube.cpp, olcPGE3_AntiAliasing.cpp, olcPGE3_BatchesOfFills.cpp, olcPGE3_BatchesOfLines.cpp, @@ -142,6 +203,40 @@ olcPGE3_ImageRegion.cpp, olcPGE3_ImageScaleRotate.cpp, olcPGE3_ImageTiling.cpp, + olcPGE3_Lines.cpp, + olcPGE3_LocalTransforms.cpp, + olcPGE3_Mouse.cpp, + olcPGE3_Pixels.cpp, + olcPGE3_PixelShaders.cpp, + olcPGE3_Polygons.cpp, + olcPGE3_Rectangles.cpp, + olcPGE3_Shockwave.cpp, + olcPGE3_SimplePixelShaders.cpp, + olcPGE3_TextBasics.cpp, + olcPGE3_TextFontMap.cpp, + olcPGE3_Touch.cpp, + olcPGE3_WorldTransform.cpp, + ); + target = 46ABFEAB302CC20600C95998 /* olcPGE3_SH */; + }; + 46161ABF3030D316006A67D5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + olcPGE3_3DCube.cpp, + olcPGE3_AntiAliasing.cpp, + olcPGE3_BatchesOfFills.cpp, + olcPGE3_BatchesOfLines.cpp, + olcPGE3_BlendingModes.cpp, + olcPGE3_Camera2D_Tiles.cpp, + olcPGE3_CirclesEllipses.cpp, + olcPGE3_Extensions.cpp, + olcPGE3_Fish.cpp, + olcPGE3_FrameGraph.cpp, + "olcPGE3_ImageQuads-FromMemory.cpp", + olcPGE3_ImageRect.cpp, + olcPGE3_ImageRegion.cpp, + olcPGE3_ImageScaleRotate.cpp, + olcPGE3_ImageTiling.cpp, olcPGE3_Keyboard.cpp, olcPGE3_Lines.cpp, olcPGE3_LocalTransforms.cpp, @@ -157,27 +252,57 @@ olcPGE3_Touch.cpp, olcPGE3_WorldTransform.cpp, ); - target = 4610524630593E6D00C2BC3A /* olcPGE3_SH */; + target = 46ABFECA302CC25F00C95998 /* olcPGE3_iOS_SH */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ +/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */ + 467442013034B47D00E2DD84 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = { + isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet; + buildPhase = 46ABFF6B302E600200C95998 /* CopyFiles */; + membershipExceptions = ( + assets/gamescene.png, + assets/graph_paper.png, + assets/minsanity_texture.png, + assets/olc_font.png, + assets/pixelfish.png, + assets/sanity_cube.png, + assets/sanity_texture.png, + assets/tile_atlas_4x4_16x16.png, + ); + }; +/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ - 46E03B67305C0D9A003BFA1D /* examples */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (46E03B94305C0DC1003BFA1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); name = examples; path = ../../examples; sourceTree = ""; }; - 46E03B6E305C0D9A003BFA1D /* extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = extensions; path = ../../extensions; sourceTree = ""; }; - 46E03B75305C0D9A003BFA1D /* utilities */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = utilities; path = ../../utilities; sourceTree = ""; }; + 4616166E302F6AED006A67D5 /* examples */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (46161A523030B44E006A67D5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 46161ABF3030D316006A67D5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 467442013034B47D00E2DD84 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); name = examples; path = ../../examples; sourceTree = ""; }; + 461616B1302F6B2A006A67D5 /* utilities */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = utilities; path = ../../utilities; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ - 4610525830593E6D00C2BC3A /* Frameworks */ = { + 46ABFEBD302CC20600C95998 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 46ABFEBE302CC20600C95998 /* OpenGL.framework in Frameworks */, + 46ABFEBF302CC20600C95998 /* MetalKit.framework in Frameworks */, + 46ABFEC0302CC20600C95998 /* AppKit.framework in Frameworks */, + 46ABFEC1302CC20600C95998 /* Metal.framework in Frameworks */, + 46ABFEC2302CC20600C95998 /* QuartzCore.framework in Frameworks */, + 46ABFEC3302CC20600C95998 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 46ABFEDC302CC25F00C95998 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4610525930593E6D00C2BC3A /* OpenGL.framework in Frameworks */, - 4610525A30593E6D00C2BC3A /* MetalKit.framework in Frameworks */, - 4610525B30593E6D00C2BC3A /* AppKit.framework in Frameworks */, - 4610525C30593E6D00C2BC3A /* Metal.framework in Frameworks */, - 4610525D30593E6D00C2BC3A /* QuartzCore.framework in Frameworks */, - 4610525E30593E6D00C2BC3A /* Foundation.framework in Frameworks */, + 467441F73034B2BF00E2DD84 /* CoreGraphics.framework in Frameworks */, + 46ABFEDD302CC25F00C95998 /* OpenGLES.framework in Frameworks */, + 46ABFEDE302CC25F00C95998 /* UIKit.framework in Frameworks */, + 46ABFEDF302CC25F00C95998 /* GLKit.framework in Frameworks */, + 46ABFEE0302CC25F00C95998 /* Metal.framework in Frameworks */, + 46ABFEE1302CC25F00C95998 /* QuartzCore.framework in Frameworks */, + 46ABFEE2302CC25F00C95998 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -194,18 +319,36 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8998CE422E057F4E007DDD97 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 467441883034840E00E2DD84 /* CoreGraphics.framework in Frameworks */, + 46ABFEA7302CC0B600C95998 /* OpenGLES.framework in Frameworks */, + 46ABFEA9302CC0BF00C95998 /* UIKit.framework in Frameworks */, + 46A1B5982E40F8B0009734EE /* GLKit.framework in Frameworks */, + 46A1B5A02E39196500B74FA5 /* Metal.framework in Frameworks */, + 46A1B5A12E39195A00B74FA5 /* QuartzCore.framework in Frameworks */, + 46A1B5A22E39193D00B74FA5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 46E85DF52E39135300B74FA3 /* Frameworks */ = { isa = PBXGroup; children = ( + 467441873034840E00E2DD84 /* CoreGraphics.framework */, + 46ABFEA8302CC0BF00C95998 /* UIKit.framework */, + 46ABFEA6302CC0B600C95998 /* OpenGLES.framework */, 46B1E5942E40F8B0009734EC /* MetalKit.framework */, 46B1E5922E40F8A6009734EC /* AppKit.framework */, 46E85DFC2E39137100B74FA3 /* Metal.framework */, 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */, 46E85DF82E39135B00B74FA3 /* OpenGL.framework */, 46E85DF62E39135300B74FA3 /* Foundation.framework */, + 46A1B5972E40F8B0009734EE /* GLKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -213,15 +356,14 @@ 8998CE172E057F4E007DDD96 = { isa = PBXGroup; children = ( - 46E03B67305C0D9A003BFA1D /* examples */, - 46E03B6E305C0D9A003BFA1D /* extensions */, - 46E03B75305C0D9A003BFA1D /* utilities */, - 46E03AD8305C0D3A003BFA1D /* olcPixelGameEngine3.h */, - 4610526530593EE400C2BC3A /* assets */, - 8998CE322E057F92007DDD96 /* src */, + 46161A4A3030B3D5006A67D5 /* assets */, 8998CE2A2E057F85007DDD96 /* tests */, 46E85DF52E39135300B74FA3 /* Frameworks */, 8998CE212E057F4E007DDD96 /* Products */, + 8998CE322E057F92007DDD96 /* src */, + 46ABFF5B302CC58200C95998 /* olcPixelGameEngine3.h */, + 4616166E302F6AED006A67D5 /* examples */, + 461616B1302F6B2A006A67D5 /* utilities */, ); sourceTree = ""; }; @@ -229,7 +371,9 @@ isa = PBXGroup; children = ( 8998CE202E057F4E007DDD96 /* olcPGE3 */, - 4610526330593E6D00C2BC3A /* olcPGE3_SH */, + 8998CE402E057F4E007DDD98 /* olcPGE3_iOS.app */, + 46ABFEC8302CC20600C95998 /* olcPGE3_SH */, + 46ABFEE6302CC25F00C95998 /* olcPGE3_iOS_SH.app */, ); name = Products; sourceTree = ""; @@ -247,43 +391,51 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( - 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */, - 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */, - 466C385F2F41D69000F75AF4 /* draw_batch.cpp */, - 466C385B2F41D5D400F75AF4 /* draw.h */, - 466C385C2F41D5D400F75AF4 /* draw.cpp */, - 466C385D2F41D5D400F75AF4 /* extension.h */, - 466C38492F3A0B0E00F75AF4 /* matrix4d.h */, - 466C384A2F3A0B0E00F75AF4 /* vector4d.h */, - 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */, - 467318B82EEC213700714D4C /* font.h */, - 467318B92EEC213700714D4C /* font.cpp */, + 467797802FF022A300171349 /* draw.h */, + 467797812FF022A300171349 /* draw.cpp */, + 467797822FF022A300171349 /* draw_batch.cpp */, + 467797832FF022A300171349 /* hw_keyboard.cpp */, + 467797842FF022A300171349 /* hw_touch.h */, + 467797852FF022A300171349 /* hw_touch.cpp */, + 467797862FF022A300171349 /* matrix4d.h */, + 467797872FF022A300171349 /* vector4d.h */, + 46C0F4182F0D08940053B0C8 /* api_ios.h */, + 46C0F4192F0D08940053B0C8 /* api_ios.cpp */, + 46C0F41A2F0D08940053B0C8 /* api_ios_wrapper.hpp */, 46964CE52EE5C23300B7BCF1 /* api_macos.h */, 46964CE62EE5C23300B7BCF1 /* api_macos.cpp */, 46964CE72EE5C23300B7BCF1 /* api_macos_wrapper.hpp */, - 469A92122E79B0F1007BB460 /* hw_mouse.cpp */, - 469A92112E79B0B0007BB460 /* hw_mouse.h */, - 469A92102E79B04B007BB460 /* hw_keyboard.h */, - 469A920F2E79B03C007BB460 /* imload_iface.h */, - 469A920E2E79B01E007BB460 /* hw_input.h */, - 462680D02E6C3C4500799C36 /* gpu_iface.cpp */, 460495DA2E300E000047E223 /* api_opengl.h */, 460495DB2E300E000047E223 /* api_opengl.cpp */, 8998CE342E057F92007DDD96 /* config.h */, 460495DC2E300E000047E223 /* core.h */, 460495DD2E300E000047E223 /* core.cpp */, + 467318B82EEC213700714D4C /* font.h */, + 467318B92EEC213700714D4C /* font.cpp */, 460495DF2E300E000047E223 /* gpu_iface.h */, + 462680D02E6C3C4500799C36 /* gpu_iface.cpp */, 460495E02E300E000047E223 /* gpu_opengl33.h */, 460495E12E300E000047E223 /* gpu_opengl33.cpp */, 460495E22E300E000047E223 /* gputask.h */, + 461B0B682F04346B00F47017 /* host_apple_ios.h */, + 461B0B692F04346B00F47017 /* host_apple_ios.cpp */, 460495E32E300E000047E223 /* host_apple_macos.h */, 460495E42E300E000047E223 /* host_apple_macos.cpp */, 460495E52E300E000047E223 /* host_iface.h */, 460495E62E300E000047E223 /* host_lin_wayland.h */, 460495E72E300E000047E223 /* host_lin_x11.h */, 460495E82E300E000047E223 /* host_web_emscripten.h */, + 469A920E2E79B01E007BB460 /* hw_input.h */, + 469A92102E79B04B007BB460 /* hw_keyboard.h */, + 469A92112E79B0B0007BB460 /* hw_mouse.h */, + 469A92122E79B0F1007BB460 /* hw_mouse.cpp */, 460495EB2E300E000047E223 /* image.h */, 460495EC2E300E000047E223 /* image.cpp */, + 469A920F2E79B03C007BB460 /* imload_iface.h */, + 46C0F41B2F0D08940053B0C8 /* imload_ios.h */, + 46C0F41C2F0D08940053B0C8 /* imload_ios.cpp */, + 46C8F5B72E7DA67900C11019 /* imload_macos.h */, + 46C8F5B82E7DA67900C11019 /* imload_macos.cpp */, 460495ED2E300E000047E223 /* matrix3d.h */, 460495EE2E300E000047E223 /* olcpge3.h */, 8998CE332E057F92007DDD96 /* pixel.h */, @@ -293,8 +445,6 @@ 8998CE362E057F92007DDD96 /* vector2d.h */, 460495F12E300E000047E223 /* window.h */, 460495F22E300E000047E223 /* window.cpp */, - 46C8F5B72E7DA67900C11019 /* imload_macos.h */, - 46C8F5B82E7DA67900C11019 /* imload_macos.cpp */, ); name = src; path = ../src; @@ -303,28 +453,48 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 4610524630593E6D00C2BC3A /* olcPGE3_SH */ = { + 46ABFEAB302CC20600C95998 /* olcPGE3_SH */ = { isa = PBXNativeTarget; - buildConfigurationList = 4610526030593E6D00C2BC3A /* Build configuration list for PBXNativeTarget "olcPGE3_SH" */; + buildConfigurationList = 46ABFEC5302CC20600C95998 /* Build configuration list for PBXNativeTarget "olcPGE3_SH" */; buildPhases = ( - 4610524730593E6D00C2BC3A /* Sources */, - 4610525830593E6D00C2BC3A /* Frameworks */, - 4610525F30593E6D00C2BC3A /* CopyFiles */, + 46ABFEAC302CC20600C95998 /* Sources */, + 46ABFEBD302CC20600C95998 /* Frameworks */, + 46ABFEC4302CC20600C95998 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( - 46E03B67305C0D9A003BFA1D /* examples */, - 46E03B6E305C0D9A003BFA1D /* extensions */, - 46E03B75305C0D9A003BFA1D /* utilities */, + 4616166E302F6AED006A67D5 /* examples */, + 461616B1302F6B2A006A67D5 /* utilities */, ); name = olcPGE3_SH; productName = olcPGE3; - productReference = 4610526330593E6D00C2BC3A /* olcPGE3_SH */; + productReference = 46ABFEC8302CC20600C95998 /* olcPGE3_SH */; productType = "com.apple.product-type.tool"; }; + 46ABFECA302CC25F00C95998 /* olcPGE3_iOS_SH */ = { + isa = PBXNativeTarget; + buildConfigurationList = 46ABFEE3302CC25F00C95998 /* Build configuration list for PBXNativeTarget "olcPGE3_iOS_SH" */; + buildPhases = ( + 46ABFECB302CC25F00C95998 /* Sources */, + 46ABFEDC302CC25F00C95998 /* Frameworks */, + 46ABFF6B302E600200C95998 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 4616166E302F6AED006A67D5 /* examples */, + 461616B1302F6B2A006A67D5 /* utilities */, + ); + name = olcPGE3_iOS_SH; + productName = olcPGE3_iOS; + productReference = 46ABFEE6302CC25F00C95998 /* olcPGE3_iOS_SH.app */; + productType = "com.apple.product-type.application"; + }; 8998CE1F2E057F4E007DDD96 /* olcPGE3 */ = { isa = PBXNativeTarget; buildConfigurationList = 8998CE272E057F4E007DDD96 /* Build configuration list for PBXNativeTarget "olcPGE3" */; @@ -342,6 +512,23 @@ productReference = 8998CE202E057F4E007DDD96 /* olcPGE3 */; productType = "com.apple.product-type.tool"; }; + 8998CE432E057F4E007DDD99 /* olcPGE3_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8998CE442E057F4E007DDD99 /* Build configuration list for PBXNativeTarget "olcPGE3_iOS" */; + buildPhases = ( + 8998CE452E057F4E007DDD99 /* Sources */, + 8998CE422E057F4E007DDD97 /* Frameworks */, + 46161A4C3030B3E2006A67D5 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = olcPGE3_iOS; + productName = olcPGE3_iOS; + productReference = 8998CE402E057F4E007DDD98 /* olcPGE3_iOS.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -354,10 +541,13 @@ 8998CE1F2E057F4E007DDD96 = { CreatedOnToolsVersion = 14.3.1; }; + 8998CE432E057F4E007DDD99 = { + CreatedOnToolsVersion = 14.3.1; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 8998CE1B2E057F4E007DDD96 /* Build configuration list for PBXProject "olcPGE3" */; - compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -365,18 +555,28 @@ Base, ); mainGroup = 8998CE172E057F4E007DDD96; + preferredProjectObjectVersion = 56; productRefGroup = 8998CE212E057F4E007DDD96 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 8998CE1F2E057F4E007DDD96 /* olcPGE3 */, - 4610524630593E6D00C2BC3A /* olcPGE3_SH */, + 46ABFEAB302CC20600C95998 /* olcPGE3_SH */, + 8998CE432E057F4E007DDD99 /* olcPGE3_iOS */, + 46ABFECA302CC25F00C95998 /* olcPGE3_iOS_SH */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - 4610524730593E6D00C2BC3A /* Sources */ = { + 46ABFEAC302CC20600C95998 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 46ABFECB302CC25F00C95998 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -387,8 +587,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */, - 466C38602F41D69000F75AF4 /* draw_batch.cpp in Sources */, + 46ABFF58302CC3D200C95998 /* gpu_iface.cpp in Sources */, + 4677978F2FF022A300171349 /* draw_batch.cpp in Sources */, 462680A42E69D03800799C36 /* image.cpp in Sources */, 46E85E0B2E39252B00B74FA3 /* gpu_opengl33.cpp in Sources */, 46E85E0A2E39252200B74FA3 /* window.cpp in Sources */, @@ -396,39 +596,135 @@ 467318BA2EEC213700714D4C /* font.cpp in Sources */, 46E85E072E3924D900B74FA3 /* host_apple_macos.cpp in Sources */, 469A92132E79B0F1007BB460 /* hw_mouse.cpp in Sources */, + 4677978C2FF022A300171349 /* hw_keyboard.cpp in Sources */, + 4677978D2FF022A300171349 /* hw_touch.cpp in Sources */, 46E85E062E3924CC00B74FA3 /* api_opengl.cpp in Sources */, 46964CE82EE5C23300B7BCF1 /* api_macos.cpp in Sources */, - 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */, 46C8F5B92E7DA67900C11019 /* imload_macos.cpp in Sources */, + 4677978E2FF022A300171349 /* draw.cpp in Sources */, 460496082E362F190047E223 /* test_mh.cpp in Sources */, - 46152BD72FE2DD1E00DCAC34 /* hw_touch.cpp in Sources */, - 466C385E2F41D5D400F75AF4 /* draw.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8998CE452E057F4E007DDD99 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 462680D22E6C3C4500799C37 /* gpu_iface.cpp in Sources */, + 4677978B2FF022A300171349 /* draw_batch.cpp in Sources */, + 462680A72E69D03800799C37 /* image.cpp in Sources */, + 46E85E102E39252B00B74FA4 /* gpu_opengl33.cpp in Sources */, + 46E85E0F2E39252200B74FA4 /* window.cpp in Sources */, + 46E85E0E2E3924FB00B74FA4 /* core.cpp in Sources */, + 467318BB2EEC213700714D4D /* font.cpp in Sources */, + 461B0B6D2F04346B00F47017 /* host_apple_ios.cpp in Sources */, + 469A92142E79B0F1007BB461 /* hw_mouse.cpp in Sources */, + 467797882FF022A300171349 /* hw_keyboard.cpp in Sources */, + 467797892FF022A300171349 /* hw_touch.cpp in Sources */, + 46E85E0C2E3924CC00B74FA4 /* api_opengl.cpp in Sources */, + 46C0F41E2F0D08940053B0C8 /* api_ios.cpp in Sources */, + 4677978A2FF022A300171349 /* draw.cpp in Sources */, + 46C0F41D2F0D08940053B0C8 /* imload_ios.cpp in Sources */, + 460496092E362F190047E224 /* test_mh.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 4610526130593E6D00C2BC3A /* Debug */ = { + 46ABFEC6302CC20600C95998 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1"; HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + IPHONEOS_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; }; name = Debug; }; - 4610526230593E6D00C2BC3A /* Release */ = { + 46ABFEC7302CC20600C95998 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1"; HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + IPHONEOS_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; + }; + name = Release; + }; + 46ABFEE4302CC25F00C95998 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + DEVELOPMENT_TEAM = ""; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1"; + HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + INFOPLIST_FILE = olcPGE3_iOS_Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = olcPGE3iOS; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = ""; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.onelonecoder.olcPGE3iOSSH; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; + }; + name = Debug; + }; + 46ABFEE5302CC25F00C95998 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + DEVELOPMENT_TEAM = ""; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1 "; + HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + INFOPLIST_FILE = olcPGE3_iOS_Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = olcPGE3iOS; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = ""; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.onelonecoder.olcPGE3iOSSH; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; }; name = Release; }; @@ -547,8 +843,14 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1"; HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + IPHONEOS_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; }; name = Debug; }; @@ -558,19 +860,96 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1"; HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + IPHONEOS_DEPLOYMENT_TARGET = ""; PRODUCT_NAME = "$(TARGET_NAME)"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; + }; + name = Release; + }; + 8998CE462E057F4E007DDD9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + DEVELOPMENT_TEAM = ""; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1"; + HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + INFOPLIST_FILE = olcPGE3_iOS_Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = olcPGE3iOS; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = ""; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.onelonecoder.olcPGE3iOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; + }; + name = Debug; + }; + 8998CE472E057F4E007DDD9B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.0; + DEVELOPMENT_TEAM = ""; + GCC_INPUT_FILETYPE = sourcecode.cpp.cpp; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "GL_SILENCE_DEPRECATION=1 "; + HEADER_SEARCH_PATHS = "$[PROJECT_DIR]/"; + INFOPLIST_FILE = olcPGE3_iOS_Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = olcPGE3iOS; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = ""; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.onelonecoder.olcPGE3iOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = ""; + WATCHOS_DEPLOYMENT_TARGET = ""; + XROS_DEPLOYMENT_TARGET = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 4610526030593E6D00C2BC3A /* Build configuration list for PBXNativeTarget "olcPGE3_SH" */ = { + 46ABFEC5302CC20600C95998 /* Build configuration list for PBXNativeTarget "olcPGE3_SH" */ = { isa = XCConfigurationList; buildConfigurations = ( - 4610526130593E6D00C2BC3A /* Debug */, - 4610526230593E6D00C2BC3A /* Release */, + 46ABFEC6302CC20600C95998 /* Debug */, + 46ABFEC7302CC20600C95998 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 46ABFEE3302CC25F00C95998 /* Build configuration list for PBXNativeTarget "olcPGE3_iOS_SH" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 46ABFEE4302CC25F00C95998 /* Debug */, + 46ABFEE5302CC25F00C95998 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -593,6 +972,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 8998CE442E057F4E007DDD99 /* Build configuration list for PBXNativeTarget "olcPGE3_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8998CE462E057F4E007DDD9A /* Debug */, + 8998CE472E057F4E007DDD9B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 8998CE182E057F4E007DDD96 /* Project object */; diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/xcshareddata/xcschemes/olcPGE3_SH.xcscheme b/dev/xcode_macos/olcPGE3.xcodeproj/xcshareddata/xcschemes/olcPGE3_SH.xcscheme index 3952f1d..f1fbf18 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/xcshareddata/xcschemes/olcPGE3_SH.xcscheme +++ b/dev/xcode_macos/olcPGE3.xcodeproj/xcshareddata/xcschemes/olcPGE3_SH.xcscheme @@ -15,7 +15,7 @@ buildForAnalyzing = "YES"> @@ -45,7 +45,7 @@ runnableDebuggingMode = "0"> @@ -62,7 +62,7 @@ runnableDebuggingMode = "0"> diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index a5edfe0..d34597c 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -192,8 +192,16 @@ #endif #if defined(__APPLE__) + // Note: Normally we dont include headers in config files but this is an exception + // as __APPLE__ is defined for both MacOS and iOS builds. we need to differentiate here. + #include + #if TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR + #define GLES_SILENCE_DEPRECATION + #define OLC_HOST OLC_HOST_IOS + #else #define GL_SILENCE_DEPRECATION #define OLC_HOST OLC_HOST_MACOS + #endif #endif #if defined(__EMSCRIPTEN__) @@ -238,6 +246,7 @@ #define OLC_IMAGELOADER_LIB_PNG 4 #define OLC_IMAGELOADER_NDK_IMAGEDECODER 5 #define OLC_IMAGELOADER_STB_IMAGE 6 +#define OLC_IMAGELOADER_IOS 7 #if defined(OLC_USE_STB_IMAGE) #define OLC_IMAGELOADER OLC_IMAGELOADER_STB_IMAGE @@ -255,6 +264,11 @@ #define OLC_IMAGELOADER_CLASS ImageLoader_MacOS #endif + #if OLC_HOST == OLC_HOST_IOS + #define OLC_IMAGELOADER OLC_IMAGELOADER_IOS + #define OLC_IMAGELOADER_CLASS ImageLoader_iOS + #endif + #if OLC_HOST == OLC_HOST_LINUX_X11 || OLC_HOST == OLC_HOST_LINUX_WAYLAND #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG #define OLC_IMAGELOADER_CLASS ImageLoader_LibPNG @@ -301,7 +315,7 @@ #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS // De-Noise in clang (C++20) MacOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-pragmas" // Allow unknown pragmas for compatibility with different compilers @@ -346,10 +360,13 @@ inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #define OLC_FRIENDLY_HOST Host_Android #endif +#if OLC_HOST == OLC_HOST_IOS +#define OLC_FRIENDLY_HOST Host_Apple_iOS +#endif #if !defined(PGE_PIXEL_DECLARED) -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" // Silence warnings about implicitly generated copy constructor for Pixel #endif @@ -660,7 +677,7 @@ namespace olc } } -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic pop #endif @@ -2061,7 +2078,7 @@ namespace olc #endif #if !defined(PGE_TRANSFORM2D_DECLARED) -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wignored-qualifiers" // Silence warnings about ignored qualifiers in olc::t_2d #endif @@ -2307,7 +2324,7 @@ namespace olc typedef t_2d tf2d; typedef t_2d td2d; } -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic pop #endif #define PGE_TRANSFORM2D_DECLARED 1 @@ -5768,6574 +5785,10735 @@ namespace olc { #endif -#if !defined(OLC_USE_WXWIDGETS) - -#if OLC_HOST == OLC_HOST_NONE - -namespace olc::host -{ - class Host_None : public olc::host::Host - { - public: // OS Window Handling - // Make OS Create a window frame, associated with olc::Window - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - // Make OS Close a window frame, associated with olc::Window - bool CloseWindowFrame(olc::Window* pWindow) override; - // Make OS Update a window frame title, associated with olc::Window - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - // Get OS-specific window descriptor(s) for given olc::Window - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for OS desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; - - public: // Platform specific Mouse Control - // 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; - - public: // OS Specific Environment Information - virtual olc::KeyboardLayout GetKeyboardLayout() const override; - - public: // Platform Specific OS<->PGE Linkage - // Called at very start of application - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - bool StartSystem() override; - // Called to stop the host, and shutdown all resources - bool StopSystem() override; - // Called at start of system event loop - bool OnSystemThreadStart() override; - // Called to perform primary window update - bool OnSystemTick() override; - // Called at end of system event loop - bool OnSystemThreadEnd() override; - // Called at very end of application - bool OnApplicationEnd() override; - private: - bool systemActive = false; - }; -} -#endif - -#if OLC_HOST == OLC_HOST_WINDOWS -#if defined(UNICODE) || defined(_UNICODE) - #define olcT(s) L##s -#else - #define olcT(s) s -#endif - -#define _WINSOCKAPI_ - -#if !defined(VC_EXTRALEAN) -#define VC_EXTRALEAN -#endif - -#if !defined(NOMINMAX) -#define NOMINMAX -#endif - -// In Code::Blocks -#if !defined(_WIN32_WINNT) - #ifdef HAVE_MSMF - #define _WIN32_WINNT 0x0600 // Windows Vista - #else - //#define _WIN32_WINNT 0x0603 // Windows 8.1 - - // Windows 10 Minimum - #define WINVER 0x0A00 - #define _WIN32_WINNT 0x0A00 - #endif -#endif +#if OLC_HOST == OLC_HOST_IOS +// For C/C++ compilation, we need to include the C-compatible headers first +#include +#include +#include +#include +#include +#include +#include -// Embrace MSVC superiority -#pragma comment(lib, "user32.lib") -#pragma comment(lib, "gdi32.lib") -#pragma comment(lib, "Dwmapi.lib") -#include -#include -#include -#include -#undef _WINSOCKAPI_ +extern "C" { + + // Forward declarations + struct Application; + struct ApplicationDelegate; + struct ViewController; + struct GLKView; + struct OpenGLRenderer; + struct ImageLoader; + + // Application API - as implemented in api_.cpp + struct Application* application_init (void); + void application_initialize (struct Application* self); + void application_activate (struct Application* self); + void application_run (struct Application* self); + void application_destroy (struct Application* self); + const char* application_getApplicationPath (struct Application* self); + const char* application_getSystemLocale (struct Application* self); + + + // View Controller API - as implemented in api_.cpp + struct ViewController* viewcontroller_init (double width, double height); + void viewcontroller_present (struct ViewController* self); + void viewcontroller_destroy (struct ViewController* self); + void viewcontroller_setTitle (struct ViewController* self, const char* title); + void viewcontroller_setPause (struct ViewController* self, BOOL pause); + const char* viewcontroller_getTitle (const struct ViewController* self); + void viewcontroller_updateViewFromiOS (struct ViewController* self); + void viewcontroller_getViewFrame (const struct ViewController* self, double* x, double* y, double* width, double* height); + void viewcontroller_getViewSize (const struct ViewController* self, double* width, double* height); + void viewcontroller_setViewFrame (struct ViewController* self, double x, double y, double width, double height); + void viewcontroller_setViewSize (struct ViewController* self, double width, double height); + + // GLKView API - as implemented in api_.cpp + struct GLKView* glkview_init (struct ViewController* viewController); + void glkview_destroy (struct GLKView* self); + void* glkview_getNativeView (const struct GLKView* self); // Returns UIView, cast to void* + void glkview_setFrame (struct GLKView* self, double x, double y, double width, double height); + void glkview_getFrame (const struct GLKView* self, double* x, double* y, double* width, double* height); + void glkview_setMultipleTouchEnabled (struct GLKView* self, BOOL enabled); + BOOL glkview_isMultipleTouchEnabled (const struct GLKView* self); + void glkview_enableTouchHandling (struct GLKView* self); + void glkview_disableTouchHandling (struct GLKView* self); + + // OpenGL ES Renderer API - as implemented in api_.cpp + struct OpenGLRenderer* opengl_init (void); + void opengl_initialize (struct OpenGLRenderer* self, struct ViewController* viewController); + void opengl_setupContext (struct OpenGLRenderer* self); + void* opengl_getEAGLContext (const struct OpenGLRenderer* self); // Returns EAGLContext, cast to void* + void* opengl_getEAGLContextPtr (struct OpenGLRenderer* self); // Returns EAGLContext as void* + void opengl_makeCurrentContext (struct OpenGLRenderer* self); // Make OpenGL context current + void opengl_setVsync (struct OpenGLRenderer* self, BOOL enabled); // Enable/disable vsync + void opengl_swapBuffers (struct OpenGLRenderer* self); // Present framebuffer + void opengl_destroy (struct OpenGLRenderer* self); + + // Image Loader API - as implemented in api_.cpp + struct ImageLoader* imageloader_init (void); + BOOL imageloader_loadFromFile (struct ImageLoader* self, const char* filePath); + BOOL imageloader_loadFromMemory (struct ImageLoader* self, const unsigned char* data, size_t length); + 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); + void imageloader_getDetailedInfo (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha); + BOOL imageloader_isLoaded (const struct ImageLoader* self); + BOOL imageloader_getPixel (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha); + unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* self); + char* imageloader_getApplicationPath (struct ImageLoader* self); -namespace olc -{ - namespace host - { - // Host for Windows OS - Single Window Only! - class Host_Windows_WinAPI : public olc::host::Host - { - + + // Autorelease pool management + void* objc_autoreleasePoolPush (void); + void objc_autoreleasePoolPop (void* pool); + + // =========================================================================== + // TOUCH EVENT HANDLING API + // =========================================================================== + + // Touch event callback function types + typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, uint modifierFlags, void* userData); + typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); + typedef void (*DrawCallback) (double x, double y, double width, double height, void* userData); + typedef void (*UpdateCallback) (void* userData); + + // Touch event handler setup - Now handled by GLKView + void glkview_setTouchBeganCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + void glkview_setTouchMovedCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + void glkview_setTouchEndedCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + void glkview_setTouchCancelledCallback (struct GLKView* self, TouchEventCallback callback, void* userData); + + // Keyboard event handler setup (for hardware keyboards) - Now handled by GLKView + void glkview_setKeyDownCallback (struct GLKView* self, KeyEventCallback callback, void* userData); + void glkview_setKeyUpCallback (struct GLKView* self, KeyEventCallback callback, void* userData); + + // Draw event handler setup - Now handled by GLKView + void glkview_setDrawCallback (struct GLKView* self, DrawCallback callback, void* userData); + void glkview_setNeedsDisplay (struct GLKView* self); - public: - Host_Windows_WinAPI(); - virtual ~Host_Windows_WinAPI() {}; + // Touch system management - Now handled by GLKView + void glkview_debugTouchSetup (struct GLKView* self); + + // Touch system management + void viewcontroller_enableTouchHandling (struct ViewController* self); + void viewcontroller_disableTouchHandling (struct ViewController* self); + void viewcontroller_setMultipleTouchEnabled (struct ViewController* self, BOOL enabled); + // Update event handler setup + void viewcontroller_setUpdateCallback (struct ViewController* self, UpdateCallback callback, void* userData); - public: // OS Window Handling - // Make OS Create a window frame, associated with olc::Window - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - // Make OS Close a window frame, associated with olc::Window - bool CloseWindowFrame(olc::Window* pWindow) override; - // Make OS Update a window frame title, associated with olc::Window - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - // Get OS-specific window descriptor(s) for given olc::Window - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for OS desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; + // Touch diagnostics TODO: Remove this in production builds + void viewcontroller_debugTouchSetup (struct ViewController* self); + + // =========================================================================== + // APPLICATION DELEGATE EVENT HANDLING API + // =========================================================================== + + // Application delegate callback function type + typedef void (*ApplicationDelegateCallback)(void* userData); + + // Application delegate event handler setup + void application_setDidFinishLaunchingCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setWillTerminateCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setDidBecomeActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setWillResignActiveCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setDidEnterBackgroundCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); + void application_setWillEnterForegroundCallback (struct Application* self, ApplicationDelegateCallback callback, void* userData); - public: // Platform specific Mouse Control - bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; - bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; - bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + // =========================================================================== + // VIEW CONTROLLER DELEGATE EVENT HANDLING API + // =========================================================================== + + // View controller delegate callback function type + typedef void (*ViewControllerDelegateCallback) (void* userData); + typedef void (*OrientationChangedCallback) (int orientation, void* userData); + + // View controller delegate event handler setup + void viewcontroller_setViewDidLoadCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewWillAppearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewDidAppearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewWillDisappearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewDidDisappearCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + void viewcontroller_setViewDidLayoutSubviewsCallback (struct ViewController* self, ViewControllerDelegateCallback callback, void* userData); + + void viewcontroller_setOrientationChangedCallback (struct ViewController* self, OrientationChangedCallback callback, void* userData); + + // =========================================================================== + // DEVICE ORIENTATION AND CAPABILITIES + // =========================================================================== + + // Device orientation enumeration + typedef enum { + DeviceOrientationUnknown = 0, + DeviceOrientationPortrait = 1, + DeviceOrientationPortraitUpsideDown = 2, + DeviceOrientationLandscapeLeft = 3, + DeviceOrientationLandscapeRight = 4, + DeviceOrientationFaceUp = 5, + DeviceOrientationFaceDown = 6 + } DeviceOrientation; + + // Device capability queries + DeviceOrientation device_getCurrentOrientation (void); + BOOL device_isMultitouchCapable (void); + BOOL device_hasHardwareKeyboard (void); + void device_getScreenBounds (double* width, double* height); + void device_getSafeAreaInsets (double* top, double* left, double* bottom, double* right); +} - public: // OS Specific Environment Information - olc::KeyboardLayout GetKeyboardLayout() const override; - public: - // Called at very start of application - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - bool StartSystem() override; - // Called to stop the host, and shutdown all resources - bool StopSystem() override; - // Called at start of system event loop - bool OnSystemThreadStart() override; - // Called to perform primary window update - bool OnSystemTick() override; - // Called at end of system event loop - bool OnSystemThreadEnd() override; - // Called at very end of application - bool OnApplicationEnd() override; +namespace olc { + namespace apis { + namespace ios { + // RAII helper for autorelease pools + class AutoreleasePool { + private: + void* pool_; + + public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + + ~AutoreleasePool() noexcept { + if (pool_) { + objc_autoreleasePoolPop(pool_); + } + } + + // Non-copyable and non-movable for safety + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; + }; + + // RAII wrapper for OpenGL ES texture management + class OpenGLTexture { + private: + unsigned int textureID_; + + public: + explicit constexpr OpenGLTexture(unsigned int id = 0) noexcept : textureID_(id) {} + + ~OpenGLTexture() noexcept { + if (textureID_ != 0) { + glDeleteTextures(1, &textureID_); + } + } + + constexpr unsigned int get() const noexcept { return textureID_; } + constexpr bool isValid() const noexcept { return textureID_ != 0; } + + // Release ownership + unsigned int release() noexcept { + unsigned int id = textureID_; + textureID_ = 0; + return id; + } + + // Reset with new texture ID + void reset(unsigned int id = 0) noexcept { + if (textureID_ != 0) { + glDeleteTextures(1, &textureID_); + } + textureID_ = id; + } + + // Move semantics + OpenGLTexture(OpenGLTexture&& other) noexcept : textureID_(other.textureID_) { + other.textureID_ = 0; + } + + OpenGLTexture& operator=(OpenGLTexture&& other) noexcept { + if (this != &other) { + reset(); + textureID_ = other.textureID_; + other.textureID_ = 0; + } + return *this; + } + + // Non-copyable + OpenGLTexture(const OpenGLTexture&) = delete; + OpenGLTexture& operator=(const OpenGLTexture&) = delete; + }; + // Exception class for framework errors + class FrameworkException : public std::runtime_error { + public: + explicit FrameworkException(const std::string& message) + : std::runtime_error("iOS Wrapper Framework Error: " + message) {} + }; + + // Touch event structure + struct TouchEvent { + uint32_t touchID; + double x, y; + double sizeX, sizeY; - private: // Windows OS Specific Things - std::unordered_map mapUID2HWND; - std::unordered_map mapHWND2PTR; - std::wstring ConvertS2W(std::string s); - std::atomic systemActive = false; - HCURSOR hCursorDefault = nullptr; - HCURSOR hCursorNow = nullptr; - DWORD ConvertPGE2WindowStyle(const olc::Window* pWindow); + TouchEvent(uint32_t id, double x, double y, double sx, double sy) noexcept + : touchID(id), x(x), y(y), sizeX(sx), sizeY(sy) {} - public: - LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + TouchEvent(TouchEvent&&) noexcept = default; + TouchEvent& operator=(TouchEvent&&) noexcept = default; + TouchEvent(const TouchEvent&) = default; + TouchEvent& operator=(const TouchEvent&) = default; + }; + + // Key event structure (for hardware keyboards) + struct KeyEvent { + unsigned short keyCode; + std::string characters; + }; + + // Device orientation enumeration + enum class DeviceOrientation { + Unknown = 0, + Portrait = 1, + PortraitUpsideDown = 2, + LandscapeLeft = 3, + LandscapeRight = 4, + FaceUp = 5, + FaceDown = 6 + }; + + // Application wrapper class + class Application { + private: + struct ::Application* app_; + + std::string appPath_; + + // Store callback pointers for cleanup + std::vector>> callbacks_; + + // Template helper to reduce code duplication + template + void setCallback(SetterFunc setter, std::function callback) { + if (!app_) return; + + auto callbackPtr = std::make_unique>(std::move(callback)); + auto* rawPtr = callbackPtr.get(); + callbacks_.emplace_back(std::move(callbackPtr)); + + setter(app_, [](void* userData) { + auto* cb = static_cast*>(userData); + (*cb)(); + }, rawPtr); + } + + public: + explicit Application() : app_(nullptr) { + app_ = application_init(); + + if (!app_) { + throw FrameworkException("Failed to initialize iOS application"); + } + } + + ~Application() { + if (app_) { + application_destroy(app_); + } + } + + // Sets the iOS internal path to the application + void setApplicationPath(const std::string& path) { + if (app_) { + appPath_ = path; + } + } + + // Get the system locale as a string (e.g., "en_US", "fr_FR") + std::string getSystemLocale() const { + const char* localeC = application_getSystemLocale(app_); + return localeC ? std::string(localeC) : std::string("en_GB"); + } + + std::string getApplicationPath() + { + if(app_) { + const char* path = application_getApplicationPath(app_); + if (path) { + std::string appPath(path); + return appPath; + } + + } + return std::string{}; + } + + void initialize() noexcept { + if (app_) application_initialize(app_); + } + + void activate() noexcept { + if (app_) application_activate(app_); + } + + void run() noexcept { + if (app_) application_run(app_); + } + + // Get underlying C handle + struct ::Application* getCHandle() const noexcept { return app_; } + + + // Set callback for application did finish launching event + void setDidFinishLaunchingCallback(std::function callback) { + setCallback(application_setDidFinishLaunchingCallback, std::move(callback)); + } + + // Set callback for application will terminate event + void setWillTerminateCallback(std::function callback) { + setCallback(application_setWillTerminateCallback, std::move(callback)); + } + + // Set callback for application did become active event + void setDidBecomeActiveCallback(std::function callback) { + setCallback(application_setDidBecomeActiveCallback, std::move(callback)); + } + + // Set callback for application will resign active event + void setWillResignActiveCallback(std::function callback) { + setCallback(application_setWillResignActiveCallback, std::move(callback)); + } + + // Set callback for application did enter background event + void setDidEnterBackgroundCallback(std::function callback) { + setCallback(application_setDidEnterBackgroundCallback, std::move(callback)); + } + + // Set callback for application will enter foreground event + void setWillEnterForegroundCallback(std::function callback) { + setCallback(application_setWillEnterForegroundCallback, std::move(callback)); + } + + // Non-copyable but movable + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + Application(Application&& other) noexcept : app_(other.app_), callbacks_(std::move(other.callbacks_)) { + other.app_ = nullptr; + } + Application& operator=(Application&& other) noexcept { + if (this != &other) { + if (app_) { + application_destroy(app_); + } + app_ = other.app_; + callbacks_ = std::move(other.callbacks_); + other.app_ = nullptr; + } + return *this; + } + }; + + // View Controller wrapper class + class ViewController { + private: + struct ::ViewController* viewController_; + + // Template helper for simple callbacks + template + void setCallback(SetterFunc setter, std::function callback) { + if (!viewController_) return; + + auto callbackPtr = std::make_unique>(std::move(callback)); + auto* rawPtr = callbackPtr.get(); + callbacks_.emplace_back(std::move(callbackPtr)); + + setter(viewController_, [](void* userData) { + auto* cb = static_cast*>(userData); + (*cb)(); + }, rawPtr); + } + + // Template helper for orientation changed callback + template + void setOrientCallback(SetterFunc setter, std::function callback) { + if (!viewController_) return; + + auto callbackPtr = std::make_unique>(std::move(callback)); + auto* rawPtr = callbackPtr.get(); + orientationCallbacks_.emplace_back(std::move(callbackPtr)); + + setter(viewController_, [](int orientation, void* userData) { + auto* cb = static_cast*>(userData); + (*cb)(static_cast(orientation)); + }, rawPtr); + + } + + public: + std::string title_; + std::vector>> callbacks_; + std::vector>> orientationCallbacks_; + + public: + ViewController(double width, double height, const std::string& title) + : viewController_(nullptr), title_(title) { + viewController_ = viewcontroller_init(width, height); + if (!viewController_) { + throw FrameworkException("Failed to create iOS view controller"); + } + } + + ~ViewController() { + if (viewController_) { + viewcontroller_destroy(viewController_); + } + } + + // Get underlying C handle + struct ::ViewController* getCHandle() const noexcept { return viewController_; } + + // Present the view controller + void present() { + if (viewController_) { + viewcontroller_present(viewController_); + } + } + + // Update the internal frame from the iOS view + void updateViewFromiOS() { + if (viewController_) { + viewcontroller_updateViewFromiOS(viewController_); + } + } + + const std::string& getTitle() const noexcept { return title_; } + + void setTitle(const std::string& title) { + title_ = title; + if (viewController_) { + viewcontroller_setTitle(viewController_, title.c_str()); + } + } + + void setPaused(bool paused) noexcept { + if (viewController_) { + viewcontroller_setPause(viewController_, paused); + } + } + + // Get the current view size + CGRect getSize() const noexcept { + if (viewController_) { + CGRect frame{}; + viewcontroller_getViewFrame(viewController_, &frame.origin.x, &frame.origin.y, &frame.size.width, &frame.size.height); + return frame; + } + return CGRect{0, 0, 0, 0}; + } + + void getViewSize(double& width, double& height) const noexcept { + if (viewController_) { + viewcontroller_getViewSize(viewController_, &width, &height); + } else { + width = height = 0.0; + } + } + + // Set the view frame size + void setFrameSize(double width, double height) noexcept { + if (viewController_) { + double x = 0.0, y = 0.0; + viewcontroller_getViewFrame(viewController_, &x, &y, nullptr, nullptr); + viewcontroller_setViewFrame(viewController_, x, y, width, height); + } + } + + void setPosition(double x, double y) noexcept { + if (viewController_) { + double width = 0.0, height = 0.0; + viewcontroller_getViewFrame(viewController_, nullptr, nullptr, &width, &height); + viewcontroller_setViewFrame(viewController_, x, y, width, height); + } + } + + // View controller lifecycle callbacks + void setViewDidLoadCallback(std::function callback) { + setCallback(viewcontroller_setViewDidLoadCallback, std::move(callback)); + } + + void setViewWillAppearCallback(std::function callback) { + setCallback(viewcontroller_setViewWillAppearCallback, std::move(callback)); + } + + void setViewDidAppearCallback(std::function callback) { + setCallback(viewcontroller_setViewDidAppearCallback, std::move(callback)); + } + + void setViewWillDisappearCallback(std::function callback) { + setCallback(viewcontroller_setViewWillDisappearCallback, std::move(callback)); + } + + void setViewDidDisappearCallback(std::function callback) { + setCallback(viewcontroller_setViewDidDisappearCallback, std::move(callback)); + } + + void setViewDidLayoutSubviewsCallback(std::function callback) { + setCallback(viewcontroller_setViewDidLayoutSubviewsCallback, std::move(callback)); + } + + void setUpdateCallback(std::function callback) { + setCallback(viewcontroller_setUpdateCallback, std::move(callback)); + } + + void setOrientationChangedCallback(std::function callback) { + setOrientCallback(viewcontroller_setOrientationChangedCallback, std::move(callback)); + + } + + // Non-copyable but movable + ViewController(const ViewController&) = delete; + ViewController& operator=(const ViewController&) = delete; + ViewController(ViewController&& other) noexcept + : viewController_(other.viewController_), title_(std::move(other.title_)), + callbacks_(std::move(other.callbacks_)), + orientationCallbacks_(std::move(other.orientationCallbacks_)) { + other.viewController_ = nullptr; + } + + ViewController& operator=(ViewController&& other) noexcept { + if (this != &other) { + if (viewController_) { + viewcontroller_destroy(viewController_); + } + viewController_ = other.viewController_; + title_ = std::move(other.title_); + callbacks_ = std::move(other.callbacks_); + orientationCallbacks_ = std::move(other.orientationCallbacks_); + other.viewController_ = nullptr; + } + return *this; + } + }; + + // GLKView wrapper class + class GLKView { + private: + struct ::GLKView* glkView_; + ViewController* parentViewController_; + + // Touch event handlers + std::function touchBeganHandler_; + std::function touchMovedHandler_; + std::function touchEndedHandler_; + std::function touchCancelledHandler_; + + std::function keyDownHandler; + std::function keyUpHandler; + std::function drawHandler; + + // Store callback pointers for cleanup + std::vector>> touchCallbacks_; + std::vector&)>>> multiTouchCallbacks_; + std::vector>> keyCallbacks_; + + // Re-register all callbacks after GLKView initialization + void reregisterCallbacks() { + if (!glkView_) return; + + if (touchBeganHandler_) { + glkview_setTouchBeganCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchBeganHandler_) { + handler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (touchMovedHandler_) { + glkview_setTouchMovedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchMovedHandler_) { + handler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (touchEndedHandler_) { + glkview_setTouchEndedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchEndedHandler_) { + handler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (touchCancelledHandler_) { + glkview_setTouchCancelledCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchCancelledHandler_) { + handler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + + if (keyDownHandler) { + glkview_setKeyDownCallback(glkView_, [](uint16_t keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyDownHandler) { + handler->keyDownHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + + if (keyUpHandler) { + glkview_setKeyUpCallback(glkView_, [](unsigned short keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyUpHandler) { + handler->keyUpHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + + + if (drawHandler) { + glkview_setDrawCallback(glkView_, [](double x, double y, double width, double height, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->drawHandler) { + handler->drawHandler(x, y, width, height); + } + }, this); + } + } + + public: + explicit GLKView(ViewController& parentViewController) + : glkView_(nullptr), parentViewController_(&parentViewController) { + } + + ~GLKView() { + // Note: The underlying GLKView is managed by ViewController, so we don't destroy it here + } + + // Initialize GLKView after ViewController presentation + bool initialize() { + if (parentViewController_ && parentViewController_->getCHandle()) { + // Get the GLKView struct from the presented ViewController + glkView_ = glkview_init(parentViewController_->getCHandle()); + if (glkView_) { + // Re-register all callbacks that were set before initialization + reregisterCallbacks(); + return true; + } + } + return false; + } + + // Get underlying C handle + struct ::GLKView* getCHandle() const noexcept { return glkView_; } + + // Get native view pointer + void* getNativeView() const { + return glkView_ ? glkview_getNativeView(glkView_) : nullptr; + } + + // Frame management + void setFrame(double x, double y, double width, double height) { + if (glkView_) { + glkview_setFrame(glkView_, x, y, width, height); + } + } + + // Get the current frame of the GLKView + CGRect getFrame(double x, double y, double width, double height) const { + if (glkView_) { + CGRect frame{}; + glkview_getFrame(glkView_, &frame.origin.x, &frame.origin.y, &frame.size.width, &frame.size.height); + return frame; + } + return CGRect{0, 0, 0, 0}; + } + + // Touch handling management + void setMultipleTouchEnabled(bool enabled) { + if (glkView_) { + glkview_setMultipleTouchEnabled(glkView_, enabled ? YES : NO); + } + } + + bool isMultipleTouchEnabled() const { + return glkView_ ? glkview_isMultipleTouchEnabled(glkView_) != NO : false; + } + + void enableTouchHandling() { + if (glkView_) { + glkview_enableTouchHandling(glkView_); + } + } + + void disableTouchHandling() { + if (glkView_) { + glkview_disableTouchHandling(glkView_); + } + } + + void debugTouchSetup() { + if (glkView_) { + glkview_debugTouchSetup(glkView_); + } + } + + void updateDisplay() + { + if(glkView_) { + glkview_setNeedsDisplay(glkView_); + } + } + + // Touch event setters + void setTouchBeganCallback(std::function callback) { + touchBeganHandler_ = callback; + if (glkView_) { + glkview_setTouchBeganCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchBeganHandler_) { + handler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + void setTouchMovedCallback(std::function callback) { + touchMovedHandler_ = callback; + if (glkView_) { + glkview_setTouchMovedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchMovedHandler_) { + handler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + void setTouchEndedCallback(std::function callback) { + touchEndedHandler_ = callback; + if (glkView_) { + glkview_setTouchEndedCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchEndedHandler_) { + handler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + void setTouchCancelledCallback(std::function callback) { + touchCancelledHandler_ = callback; + if (glkView_) { + glkview_setTouchCancelledCallback(glkView_, [](uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->touchCancelledHandler_) { + handler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + }, this); + } + } + + // Keyboard event setters + void setKeyDownCallback(std::function callback) { + keyDownHandler = callback; + if (glkView_) { + glkview_setKeyDownCallback(glkView_, [](uint16_t keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyDownHandler) { + handler->keyDownHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + } + + void setKeyUpCallback(std::function callback) { + keyUpHandler = callback; + if (glkView_) { + glkview_setKeyUpCallback(glkView_, [](uint16_t keyCode, const char* characters, uint modifierFlags, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->keyUpHandler) { + handler->keyUpHandler(keyCode, std::string(characters ? characters : ""), modifierFlags); + } + }, this); + } + } + + + // Draw callback setter + void setDrawCallback(std::function callback) { + drawHandler = callback; + if (glkView_) { + glkview_setDrawCallback(glkView_, [](double x, double y, double width, double height, void* userData) { + GLKView* handler = static_cast(userData); + if (handler->drawHandler) { + handler->drawHandler(x, y, width, height); + } + }, this); + } + } + + + + // Non-copyable but movable + GLKView(const GLKView&) = delete; + GLKView& operator=(const GLKView&) = delete; + GLKView(GLKView&& other) noexcept + : glkView_(other.glkView_), + parentViewController_(other.parentViewController_), + touchCallbacks_(std::move(other.touchCallbacks_)), + multiTouchCallbacks_(std::move(other.multiTouchCallbacks_)), + keyCallbacks_(std::move(other.keyCallbacks_)) { + other.glkView_ = nullptr; + other.parentViewController_ = nullptr; + } + + GLKView& operator=(GLKView&& other) noexcept { + if (this != &other) { + glkView_ = other.glkView_; + parentViewController_ = other.parentViewController_; + touchCallbacks_ = std::move(other.touchCallbacks_); + multiTouchCallbacks_ = std::move(other.multiTouchCallbacks_); + keyCallbacks_ = std::move(other.keyCallbacks_); + other.glkView_ = nullptr; + other.parentViewController_ = nullptr; + } + return *this; + } + }; + + // OpenGL ES Renderer class + class OpenGLRenderer { + private: + struct ::OpenGLRenderer* renderer_; + + public: + OpenGLRenderer() : renderer_(nullptr) { + renderer_ = opengl_init(); + if (!renderer_) { + throw FrameworkException("Failed to create iOS OpenGL renderer"); + } + } + + ~OpenGLRenderer() { + if (renderer_) { + opengl_destroy(renderer_); + } + } + + void attachToViewController(ViewController& viewController) { + if (renderer_) { + opengl_initialize(renderer_, viewController.getCHandle()); + } + } + + void setupContext() { + if (renderer_) { + opengl_setupContext(renderer_); + } + } + + void* getEAGLContext() const { + return renderer_ ? opengl_getEAGLContext(renderer_) : nullptr; + } + + void makeCurrentContext() { + if (renderer_) { + opengl_makeCurrentContext(renderer_); + } + } + + void setVsync(bool enabled) { + if (renderer_) { + opengl_setVsync(renderer_, enabled ? YES : NO); + } + } + + void enableVsync() { + setVsync(true); + } + + void disableVsync() { + setVsync(false); + } + + void swapBuffers() { + if (renderer_) { + opengl_swapBuffers(renderer_); + } + } + + // Non-copyable but movable + OpenGLRenderer(const OpenGLRenderer&) = delete; + OpenGLRenderer& operator=(const OpenGLRenderer&) = delete; + OpenGLRenderer(OpenGLRenderer&& other) noexcept : renderer_(other.renderer_) { + other.renderer_ = nullptr; + } + OpenGLRenderer& operator=(OpenGLRenderer&& other) noexcept { + if (this != &other) { + if (renderer_) { + opengl_destroy(renderer_); + } + renderer_ = other.renderer_; + other.renderer_ = nullptr; + } + return *this; + } + }; + + // Image loader wrapper class + class ImageLoader { + private: + struct ::ImageLoader* loader_; + std::string filePath_; + bool loaded_; + + public: + explicit ImageLoader() : loader_(nullptr), loaded_(false) { + loader_ = imageloader_init(); + if (!loader_) { + throw FrameworkException("Failed to create image loader"); + } + } + + ~ImageLoader() { + if (loader_) { + imageloader_destroy(loader_); + free(loader_); + } + } + + std::string GetApplicationPath() + { + if(loader_) { + char* path = imageloader_getApplicationPath(loader_); + if (path) { + std::string appPath(path); + free(path); + return appPath; + } + + } + return std::string{}; + } + + bool loadFromFile(const std::string& filePath) { + filePath_ = filePath; + if (loader_) { + BOOL result = imageloader_loadFromFile(loader_, filePath.c_str()); + loaded_ = (result != 0); + return loaded_; + } + 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_); + } + + OpenGLTexture createOpenGLTexture() const noexcept { + if (loader_ && loaded_) { + return OpenGLTexture(imageloader_createOpenGLTexture(loader_)); + } + return OpenGLTexture{}; + } + + // Legacy method for backwards compatibility + unsigned int createOpenGLTextureID() const noexcept { + if (loader_ && loaded_) { + return imageloader_createOpenGLTexture(loader_); + } + return 0; + } + + void getImageInfo(int& width, int& height, int& bytesPerPixel) const noexcept { + if (loader_ && loaded_) { + imageloader_getImageInfo(loader_, &width, &height, &bytesPerPixel); + } else { + width = height = bytesPerPixel = 0; + } + } + + const std::string& getFilePath() const noexcept { return filePath_; } + struct ::ImageLoader* getCHandle() const noexcept { return loader_; } + + // Non-copyable but movable + ImageLoader(const ImageLoader&) = delete; + ImageLoader& operator=(const ImageLoader&) = delete; + ImageLoader(ImageLoader&& other) noexcept + : loader_(other.loader_), filePath_(std::move(other.filePath_)), loaded_(other.loaded_) { + other.loader_ = nullptr; + other.loaded_ = false; + } + ImageLoader& operator=(ImageLoader&& other) noexcept { + if (this != &other) { + if (loader_) { + imageloader_destroy(loader_); + free(loader_); + } + loader_ = other.loader_; + filePath_ = std::move(other.filePath_); + loaded_ = other.loaded_; + other.loader_ = nullptr; + other.loaded_ = false; + } + return *this; + } + }; + + } // namespace ios + } // namespace apis +} // namespace olc + +#endif + +#if !defined(OLC_USE_WXWIDGETS) + +#if OLC_HOST == OLC_HOST_NONE + +namespace olc::host +{ + class Host_None : public olc::host::Host + { + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // 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; + + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + private: + bool systemActive = false; + }; +} +#endif + +#if OLC_HOST == OLC_HOST_WINDOWS +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif + +#define _WINSOCKAPI_ + +#if !defined(VC_EXTRALEAN) +#define VC_EXTRALEAN +#endif + +#if !defined(NOMINMAX) +#define NOMINMAX +#endif + +// In Code::Blocks +#if !defined(_WIN32_WINNT) + #ifdef HAVE_MSMF + #define _WIN32_WINNT 0x0600 // Windows Vista + #else + //#define _WIN32_WINNT 0x0603 // Windows 8.1 + + // Windows 10 Minimum + #define WINVER 0x0A00 + #define _WIN32_WINNT 0x0A00 + #endif +#endif + +// Embrace MSVC superiority +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "Dwmapi.lib") + +#include +#include +#include +#include +#undef _WINSOCKAPI_ + +namespace olc +{ + namespace host + { + // Host for Windows OS - Single Window Only! + class Host_Windows_WinAPI : public olc::host::Host + { + + + public: + Host_Windows_WinAPI(); + virtual ~Host_Windows_WinAPI() {}; + + + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + 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; + + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + + + private: // Windows OS Specific Things + std::unordered_map mapUID2HWND; + std::unordered_map mapHWND2PTR; + std::wstring ConvertS2W(std::string s); + std::atomic systemActive = false; + HCURSOR hCursorDefault = nullptr; + HCURSOR hCursorNow = nullptr; + DWORD ConvertPGE2WindowStyle(const olc::Window* pWindow); + + public: + LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + + }; + } +} +#endif + +#if OLC_HOST == OLC_HOST_MACOS + + +#include // Grand Central Dispatch: Apple's C-based API for managing concurrent operations on macOS and iOS. +#include // POSIX threads: Used for pthread_main_np() to check if current thread is the main thread +namespace olc +{ + namespace host + { + // Manages our MacOS Host + class Host_Apple_MacOS : public olc::host::Host + { + public: + olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window + + public: + Host_Apple_MacOS(); + virtual ~Host_Apple_MacOS() {}; + + public: + HostError GetLastError() const { return lastError; } + + public: + virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + virtual bool CloseWindowFrame(olc::Window* pWindow) override; + virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + // Wait for entire host desktop refresh (for smooooth vsync), + virtual bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + 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; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() override; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() override; + // Called at start of system event loop + virtual bool OnSystemThreadStart() override; + // Called to perform primary window update + virtual bool OnSystemTick() override; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() override; + // Called at very end of application + virtual bool OnApplicationEnd() override; + + protected: + HostError lastError = HostError::None; + + public: + // MacOS Application and Window pointers + std::unique_ptr pMacApplication = nullptr; + std::unique_ptr pMacOSWindow = nullptr; + std::unique_ptr pMacOSEventHandler = nullptr; + std::shared_ptr pMacOSOpenGLRenderer = nullptr; + + void* pMacGLConextObj = nullptr; + std::once_flag intialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + + private: + enum MAINTASKS{ + NONE, + CREATE_OPENGL_RENDERER, + RESIZE_WINDOW, + BECOME_ACTIVE, + RESIGN_ACTIVE, + MINIMIZE_WINDOW, + DEMINIMIZE_WINDOW + }; + + // Internal Mac OS functions + bool ExecutePendingMainThreadTasks(void); // Execute pending tasks on main thread + bool MainThreadTasks(void); // Handle main thread tasks + bool AddPendingMainThreadTask(MAINTASKS task); // Add a pending task to main thread. Note: You should ever add tasks that require main thread execution only from the PGE thread + bool CreateCGLContextObj(); // Create CGL Context Object + std::vector vPendingMainThreadTasks; // Vector of pending main thread tasks + + std::vector vMacOSWindowDescriptors; // Vector to hold window descriptors + bool enableVSync = false; // VSync enabled flag + bool bSkipFrame = false; // Flag to indicate if frame should be skipped + + // Thread synchronization for PGE Thread V Main thread + mutable std::mutex mainThreadPendingTasksMutex; // Mutex for main thread pending tasks + std::condition_variable mainThreadResetCondition; // Condition variable for main thread reset + std::atomic isMainThreadResetting{false}; // Atomic flag for resetting main thread + + mutable std::mutex pgeThreadPendingTasksMutex; // Mutex for PGE thread pending tasks + std::condition_variable pgeThreadResetCondition; // Condition variable for PGE thread reset + std::atomic isPGEThreadResetting{true}; // Atomic flag for resetting PGE thread + + std::atomic systemActive = false; // Atomic flag for system active state + + struct sFrameBounds + { + double x = 0.0; + double y = 0.0; + double width = 800.0; + double height = 600.0; + } frameBounds; + + void MacApplicationEventsHandler(); + void MacWindowEventsHandler(); + void MacEventsHandler(); + 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(); + + + }; + } +} + +#endif + +#if OLC_HOST == OLC_HOST_IOS + +#include // Grand Central Dispatch: Apple's C-based API for managing concurrent operations on iOS +#include // POSIX threads: Used for pthread_main_np() to check if current thread is the main thread + +namespace olc +{ + namespace host + { + // Manages our iOS Host + class Host_Apple_iOS : public olc::host::Host + { + + public: + olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window + + public: + // Default constructor + Host_Apple_iOS(); + virtual ~Host_Apple_iOS() {}; + + public: + // Check/Get last error + HostError GetLastError() const { return lastError; } + + public: + virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + virtual bool CloseWindowFrame(olc::Window* pWindow) override; + virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + // Wait for entire host desktop refresh (for smooth vsync) + virtual bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + 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; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() override; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() override; + // Called at start of system event loop + virtual bool OnSystemThreadStart() override; + // Called to perform primary window update + virtual bool OnSystemTick() override; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() override; + // Called at very end of application + virtual bool OnApplicationEnd() override; + + protected: + HostError lastError = HostError::None; + + public: + // Internal iOS functions + // iOS Application and View Controller pointers + std::unique_ptr pIOSApplication = nullptr; // Smart Pointer to iOS Application instance + std::unique_ptr pIOSViewController = nullptr; // Smart Pointer to iOS View Controller instance + std::unique_ptr pIOSGLKView = nullptr; // Smart Pointer to iOS GLKView instance + std::shared_ptr pIOSOpenGLRenderer = nullptr; // Smart Pointer to iOS OpenGL Renderer instance + + // Returns the full path to the application bundle on iOS or "ACCESS-DEINED" + std::string GetApplicationPath(); + + + void* pIOSGLContextObj = nullptr; + std::once_flag initialAppFlag; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + + + + void (*pPGEEngineFunc)(void*); + void (*pPGECoreUpdateFunc)(void*); + void* pPGEUserData; + + private: + + std::vector vIOSWindowDescriptors; // Vector to hold view descriptors + bool enableVSync = false; // VSync enabled flag + + bool bIOSDrawing = false; // Flag indicating if iOS drawing is active + bool bPGEInitialized = false; // Flag indicating if PGE has been initialized + + struct sFrameBounds + { + double x = 0.0; + double y = 0.0; + double width = 800.0; + double height = 600.0; + } frameBounds; + + olc::apis::ios::DeviceOrientation CurrentOrientation = olc::apis::ios::DeviceOrientation::Unknown; + void UpdateIOSViewFrameBounds(olc::apis::ios::DeviceOrientation orientation); + + // Keyboard stuff + bool bNumLockActive = false; // Track Num Lock state + unsigned int prevFlags = 0; // Track previous modifier flags for keyboard events + void KeyboardEventHandler(uint16_t nKeyCode, uint nModifierFlags, bool isPressed); + + void IOSApplicationEventsHandler(); + void IOSViewControllerEventsHandler(); + void IOSGLKViewEventHandler(); + void IOSOpenGLContextEventsHandler(); + + + }; + } +} + +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 + +#include +#include +namespace X11 +{ +#include +#include +#include +#include +#include +#include +#undef None +constexpr int None = 0L; +} + +namespace olc::host +{ + class Host_Linux_X11 : public olc::host::Host + { + private: + X11::Display* olc_Display = nullptr; + X11::Window olc_WindowRoot; + X11::XVisualInfo* olc_VisualInfo; + X11::Colormap olc_ColourMap; + X11::XSetWindowAttributes olc_SetWindowAttribs; + + int xinput_extension_code{}; + public: + Host_Linux_X11(); + + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + bool CloseWindowFrame(olc::Window* pWindow) override; + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + olc::KeyboardLayout GetKeyboardLayout() const override; + void UpdateKeyboardLayout(); + + // Wait for entire host desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + + public: + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + bool StartSystem() override; + bool StopSystem() override; + bool OnSystemThreadStart() override; + bool OnSystemTick() override; + bool OnSystemThreadEnd() override; + bool OnApplicationEnd() override; + + private: + void enableTouch(X11::Display* display, X11::Window window); + void handleTouchEvent(X11::XIDeviceEvent* event); + std::unordered_map mapUID2X11Window; + std::unordered_map mapX11Window2PTR; + std::atomic systemActive {true}; + + std::unordered_map mapKeys; + std::unordered_map mapMouseButtons; + + // Keyboard Layout Variables + olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; + bool kbExtensionsFound = false; + int xkbEventBase = 0; + int xkbErrorBase = 0; + + // Mouse Variables + std::unordered_map mapUID2X11Cursor; + bool bMouseIsVisible = true; + }; +} + +#endif + +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + + +#include +#include +#include +#include "xdg-shell.h" + +#include "pointer-warp.h" +#include +#include +#include +#include +#include +#include + +#include "libdecor.h" + + +#include +#include + +#ifndef WL_KEYBOARD_KEY_STATE_REPEATED +#define WL_KEYBOARD_KEY_STATE_REPEATED 2 +#endif + +namespace olc::host +{ + struct WaylandWindow { + wl_surface* surface{nullptr}; + xdg_surface* surface_xdg{nullptr}; + wl_egl_window* window{nullptr}; + size_t olc_window_uid{0}; + int32_t bounds_x{0}; + int32_t bounds_y{0}; + bool cursor_visible{true}; + // Ignore window size bounds for fullscreen events + bool fullscreen{false}; + + // 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{}; + ~WaylandWindow(); + }; + + namespace wayland { + enum PointerEventMask { + PointerEventEnter = 1 << 0, + PointerEventLeave = 1 << 1, + PointerEventMotion = 1 << 2, + PointerEventButton = 1 << 3, + PointerEventAxis = 1 << 4, + PointerEventAxisSource = 1 << 5, + PointerEventAxisStop = 1 << 6, + PointerEventDiscrete = 1 << 7 + }; + + struct PointerState { + uint32_t event_mask{0}; + wl_surface* surface{nullptr}; + wl_fixed_t surface_x{}; + wl_fixed_t surface_y{}; + uint32_t button{0}; + uint32_t state{0}; + uint32_t time{0}; + uint32_t serial{0}; + + struct Axis { + wl_fixed_t value{}; + int32_t discrete{0}; + bool valid{false}; + }; + + std::array axes{}; + uint32_t axis_source{0}; + }; + + enum TouchEventMask { + TouchEventDown = 1 << 0, + TouchEventUp = 1 << 1, + TouchEventMotion = 1 << 2, + TouchEventCancel = 1 << 3, + TouchEventShape = 1 << 4, + TouchEventOrientation = 1 << 5 + }; + + struct TouchState { + uint32_t event_mask{0}; + wl_surface* surface{nullptr}; + wl_fixed_t surface_x{}; + wl_fixed_t surface_y{}; + wl_fixed_t major{}; + wl_fixed_t minor{}; + wl_fixed_t orientation{}; + + uint32_t time{0}; + uint32_t serial{0}; + int32_t id{0}; + }; + } + + class Host_Linux_Wayland : public olc::host::Host + { + 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}; + wl_keyboard* keyboard{nullptr}; + wl_touch* touch{nullptr}; + uint32_t keyboard_version{0}; + xkb_context* kb_context{nullptr}; + xkb_state* kb_state{nullptr}; + xkb_keymap* kb_keymap{nullptr}; + uint32_t kb_group{0}; + xdg_wm_base* xdg_wm{nullptr}; + wp_pointer_warp_v1* pointer_warp{nullptr}; + uint32_t enter_serial{0}; + + wayland::PointerState pointer_state; + std::unordered_map touches; + wl_surface* cursor_surface{nullptr}; + wl_cursor_image* cursor_image{nullptr}; + wl_cursor_theme* cursor_theme{nullptr}; + + size_t active_window_id; + + // libdecor support + bool using_libdecor{false}; + libdecor* decor_context{nullptr}; + std::mutex decor_mutex; + + public: + Host_Linux_Wayland(); + ~Host_Linux_Wayland(); + + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + bool CloseWindowFrame(olc::Window* pWindow) override; + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + + olc::KeyboardLayout GetKeyboardLayout() const override; + void UpdateKeyboardLayout(); + + // Wait for entire host desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + + public: + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + bool StartSystem() override; + bool StopSystem() override; + bool OnSystemThreadStart() override; + bool OnSystemTick() override; + bool OnSystemThreadEnd() override; + bool OnApplicationEnd() override; + + // Various callbacks from the wayland protocol + static void registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version); + static void registry_handle_global_remove_callback(void* data, wl_registry* registry, uint32_t name); + + static void seat_capabilities_callback(void* data, wl_seat* seat, uint32_t capabilities); + static void seat_name_callback(void* data, wl_seat* wl_seat, const char* name); + + // pointer callbacks + static void pointer_enter_callback(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y); + static void pointer_leave_callback(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface); + static void pointer_motion_callback(void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); + static void pointer_button_callback(void* data, wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); + static void pointer_axis_callback(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value); + static void pointer_frame_callback(void* data, wl_pointer* pointer); + static void pointer_axis_source_callback(void* data, wl_pointer* pointer, uint32_t axis_source); + static void pointer_axis_stop_callback(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis); + static void pointer_axis_discrete_callback(void* data, wl_pointer* pointer, uint32_t axis, int32_t discrete); + static void pointer_axis_value120_callback(void* data, wl_pointer* pointer, uint32_t axis, int32_t value120); + static void pointer_axis_relative_direction_callback(void* data, wl_pointer* pointer, uint32_t axis, uint32_t direction); + + // Keyboard callbacks + static void keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); + static void keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); + static void keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); + static void keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); + static void keyboard_modifiers_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); + static void keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay); + + // Touch callbacks + static void touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + static void touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_frame_callback(void* data, wl_touch* touch); + static void touch_cancel_callback(void* data, wl_touch* touch); + static void touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + static void touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation); + + // xdg callbacks + static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); + static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); + + // 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); + + private: + // Wayland callback functions + void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); + void registry_handle_global_remove(wl_registry* registry, uint32_t name); + void seat_capabilities(wl_seat* seat, uint32_t capabilities); + + // Pointer callback functions + void pointer_enter(wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y); + void pointer_leave(wl_pointer* pointer, uint32_t serial, wl_surface* surface); + void pointer_motion(wl_pointer* pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); + void pointer_button(wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); + void pointer_axis(wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value); + void pointer_frame(wl_pointer* pointer); + void pointer_axis_source(wl_pointer* pointer, uint32_t axis_source); + void pointer_axis_stop(wl_pointer* pointer, uint32_t time, uint32_t axis); + void pointer_axis_discrete(wl_pointer* pointer, uint32_t axis, int32_t discrete); + void pointer_axis_value120(wl_pointer* pointer, uint32_t axis, int32_t value120); + void pointer_axis_relative_direction(wl_pointer* pointer, uint32_t axis, uint32_t direction); + + // Keyboard Callback Functions + void keyboard_keymap(wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); + void keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); + void keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); + void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); + void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); + + // Touch callbacks + void touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + void touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_frame(wl_touch* touch); + void touch_cancel(wl_touch* touch); + void touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + void touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation); + + // libdecor callback functions + void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); + void libdecor_close(libdecor_frame* frame); + void libdecor_commit(libdecor_frame* frame); + + bool CreateEGLContext(WaylandWindow* window); + + std::unordered_map mapUID2Window; + std::unordered_map mapUID2OlcWindow; + std::atomic systemActive {true}; + std::unordered_map mapKeys; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; + }; +} + +#endif + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + +#include +#include +#include + +namespace olc::host +{ + class Host_Web_Emscripten : public olc::host::Host + { + public: + Host_Web_Emscripten(); + + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for entire host desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + + public: // OS Specific Environment Information + olc::KeyboardLayout GetKeyboardLayout() const override; + + public: + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + static void MainLoop(void* userData); + + public: // event callbacks + static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); + static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData); + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); + static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData); + static EM_BOOL fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); + static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData); + static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData); + static EM_BOOL visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); + + private: // Window Wrappers + // Set Mouse Device State + static bool olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed); + static bool olc_OnMouseMove(olc::Window* pWindow, const olc::vi2d& vMousePos); + static bool olc_OnMouseWheel(olc::Window* pWindow, const int32_t nScroll); + static bool olc_OnFocus(olc::Window* pWindow, const bool bHasFocus); + + // Set Keyboard Device State + static bool olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed); + + // Set Window State + static bool olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos); + static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); + static bool olc_OnWindowClose(olc::Window* pWindow); + + private: // Emscripten internal funcs + std::string getNavigatorLocale(); + + public: // Callback data type + struct CallbackData { + Host_Web_Emscripten* pHost; + olc::Window* pWindow; + std::string canvasId; + }; + + private: + static std::unordered_map mapUID2CanvasId; + static std::unordered_map> mapUID2CallbackData; + static std::unordered_map mapCanvasId2PTR; + + // Map of system keycodes to olc::Keycodes + std::unordered_map mapKeys; + // Map of system mouse buttons to olc mouse buttons + std::unordered_map mapMouseButtons; + std::chrono::steady_clock::time_point timeHidden; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; + }; + + +} + +#endif + +#if OLC_HOST == OLC_HOST_ANDROID + +#include +#include +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "olcPGE3", __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "olcPGE3", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "olcPGE3", __VA_ARGS__)) +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "olcPGE3", __VA_ARGS__)) +#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "olcPGE3", __VA_ARGS__)) + +// We allow users to create a normal main function for android apps +extern int main(int argc, char** argv); + +namespace olc::host +{ + using AndroidApp = struct android_app; + class Host_Android : public olc::host::Host + { + public: + Host_Android(); + + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + bool CloseWindowFrame(olc::Window* pWindow) override; + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + bool SyncWithDesktopComposite() override; + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + olc::KeyboardLayout GetKeyboardLayout() const override; + + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + + // 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); + + bool IsInitialized() const { return initialized.load(); } + + void ShowKeyboard(bool bShow); + + // TODO: file loading support (temporary) + std::vector OpenFile(const std::string& sFileName); + std::string OpenTextFile(const std::string& sFileName); + + static AndroidApp* androidApp; + protected: + olc::Window* pgeWindow = nullptr; + std::atomic initialized{false}, systemActive{false}; + bool shiftOn = false; + + void handleMouse(size_t id, int32_t action, AInputEvent* event); + void handleStylus(size_t id, int32_t action, AInputEvent* event); + void handleFinger(size_t id, int32_t action, AInputEvent* event); + + void PollEvents( + const std::function& funcContinue, + bool bBlocking = false + ); + }; + + class JNI + { + public: + static void Init(AndroidApp* app); + static void Detach(); + + static JNIEnv* GetEnv(); + + private: + static JavaVM* jvm; + }; + + class JNIObject + { + public: + JNIObject() = default; + JNIObject(jobject obj); + + ~JNIObject(); + + JNIObject(const JNIObject&) = delete; + JNIObject& operator=(const JNIObject&) = delete; + + JNIObject(JNIObject&& other) noexcept; + + JNIObject& operator=(JNIObject&& other) noexcept; + + jobject Get() const { return obj; } + jobject operator *() const { return obj; } + operator bool() const { return obj != nullptr; } + + template + R Call( + const std::string& methodName, + const std::string& methodSig, + Args&&... args + ) + { + JNIEnv* env = JNI::GetEnv(); + jclass objClass = env->GetObjectClass(obj); + jmethodID methodID = env->GetMethodID( + objClass, + methodName.c_str(), + methodSig.c_str() + ); + env->DeleteLocalRef(objClass); + + R result{}; + + if constexpr (std::is_same_v) + { + env->CallVoidMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallIntMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallBooleanMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallByteMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallCharMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallShortMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallLongMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallFloatMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v) + { + result = env->CallDoubleMethod(obj, methodID, std::forward(args)...); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + jobject callResult = env->CallObjectMethod(obj, methodID, std::forward(args)...); + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + return {}; + } + if constexpr (std::is_same_v) + { + result = callResult; + } + else // JNIObject + { + result = JNIObject(callResult); + } + } + else + { + static_assert(sizeof(R) == 0, "Unsupported return type in JNIObject::Call"); + } + + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + env->ExceptionClear(); + return {}; + } + return result; + } + + private: + jobject obj = nullptr; + }; + + class JNIString + { + public: + JNIString(const std::string& str) + { + JNIEnv* env = JNI::GetEnv(); + jstring local = env->NewStringUTF(str.c_str()); + jstr = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + } + + JNIString(jstring jstr) + { + JNIEnv* env = JNI::GetEnv(); + const char* chars = env->GetStringUTFChars(jstr, nullptr); + std::string str(chars); + env->ReleaseStringUTFChars(jstr, chars); + jstring local = env->NewStringUTF(str.c_str()); + this->jstr = static_cast(env->NewGlobalRef(local)); + env->DeleteLocalRef(local); + } + + ~JNIString() + { + if (jstr) + { + JNI::GetEnv()->DeleteGlobalRef(jstr); + } + jstr = nullptr; + } + + operator std::string() const + { + JNIEnv* env = JNI::GetEnv(); + const char* chars = env->GetStringUTFChars(jstr, nullptr); + std::string result(chars); + env->ReleaseStringUTFChars(jstr, chars); + return result; + } + + jstring operator*() const + { + return jstr; + } + + private: + jstring jstr = nullptr; + }; + +} +#endif + +#else +#include +#include + +// Not really an olc::Host, but some stand alone components that wxWidgets +// can use and get a PGE3 like rendering and user experience +namespace olc::wx +{ + class PGE3Core : public wxGLCanvas + { + public: + PGE3Core(wxWindow* parent); + virtual ~PGE3Core(); + + // Create an image resource + bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName, const ImageConfig& cfg = olc::ImageConfig()); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg = olc::ImageConfig()); + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName); + // Store an image as a file asset in memory + //bool WriteImageToMemory(const olc::Image& image, std::vector bytes, const std::string& sFileName); + // Destroy an image + void DestroyImage(olc::Image& image); + + public: + wxGLContext* get() const; + olc::gpu::Renderer* GetRenderer(); + olc::imload::ImageLoader* GetImageLoader(); + + private: // wxWidgets Specific + wxGLContext* m_glContext = nullptr; + + private: // Global PGE3 Components (excluding host) + std::unique_ptr pRenderer; + std::unique_ptr pImageLoader; + }; + + class PGE3Panel : public wxGLCanvas + { + public: + PGE3Panel(wxWindow* parent); + PGE3Panel(wxWindow* parent, const olc::vi2d& vFixedSize); + virtual ~PGE3Panel(); + + void ResetDrawState(); + + protected: // Local PGE3 Components + olc::Draw draw; + + private: + // Same core is loaded across all panels in application + static olc::wx::PGE3Core* m_pCore; + olc::Image imgPrimary; + + bool bFixedSize = false; + olc::vi2d vFixedSizeImage; + + olc::vf2d ScaleMouse(wxMouseEvent& evt); + + + private: // wxWidgets Overrides + void Event_OnPaint(wxPaintEvent& evt); + void Event_OnResize(wxSizeEvent& evt); + void Event_OnMouseLeftUp(wxMouseEvent& evt); + void Event_OnMouseLeftDown(wxMouseEvent& evt); + void Event_OnMouseRightUp(wxMouseEvent& evt); + void Event_OnMouseRightDown(wxMouseEvent& evt); + void Event_OnMouseMiddleUp(wxMouseEvent& evt); + void Event_OnMouseMiddleDown(wxMouseEvent& evt); + void Event_OnMouseMove(wxMouseEvent& evt); + void Event_OnMouseWheel(wxMouseEvent& evt); + + + protected: // User Overrides + virtual void OnRender() {}; // Called via wxWidgets ->Refresh() + + public: + virtual void OnCreate() {}; + virtual void OnUpdate(const float fElapsedTime) {}; + + // Mouse Handlers + virtual void OnMouseMiddleUp(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseMiddleDown(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseLeftUp(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseLeftDown(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseRightUp(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseRightDown(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseMove(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + virtual void OnMouseWheel(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; + }; +} +#endif + + +#if OLC_GPU == OLC_GPU_NONE + +#if !defined(PGE_RENDERER_NONE_DECLARED) +namespace olc::gpu +{ + class Shader_None : public olc::gpu::Shader + { + public: + std::string Compile() override; + int32_t CreateUniform(const std::string& name) override; + }; + + class Renderer_None : public olc::gpu::Renderer + { + public: // Device Stuff + // Constructs a GPU Device interface + bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; + // Destroys a GPU device interface + bool DestroyDevice() override; + // If applicable, relocate the rendering context + bool RetargetDevice(std::vector os_win_id) override; + // Prepare an OS rendering target + bool PrepareWindowTarget(std::vector os_win_id) override; + + + public: // Texture Resource Stuff + // Allocates a new texture resource in VRAM, returns handle + uint32_t CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg = olc::ImageConfig()) override; + // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM + bool WriteTexture(const uint32_t texid, olc::Image& image) override; + // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM + bool ReadTexture(const uint32_t texid, olc::Image& image) override; + // Destroys and releases texture resource for given handle + bool DeleteTexture(const uint32_t texid) override; + // Makes active the given texture resource (for subsequent sampling operations) + bool AssignTextureSource(const uint32_t slot, const uint32_t texid) override; + // Makes active the given texture resource (for subsequent rendering operations) + bool AssignTextureTarget(const uint32_t slot, const uint32_t texid) override; + // Resolves an MSAA texture into a normal texture + bool ResolveMSAA(const uint32_t msaaTexId) override; + + public: // Shader Construction Stuff + // Change the shader used for subsequent GPU drawing tasks + bool ApplyShader(const Shader& shader) override; + // Reset to default shader for subsequent GPU drawing tasks + bool ApplyDefaultShader() override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const float value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::vf2d& value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::Pixel value) override; + + public: // GPU Task Stuff + bool DoGPUTask(const olc::GPUTask& task) override; + + public: // Swap Chain Stuff + // Clears the viewport to a specific colour and depth + bool ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) override; + // Sets the viewport area of the drawing space + bool SetViewport(const olc::vf2d& pos, const olc::vf2d& size) override; + // Configures defaults prior to drawing + bool DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) override; + // Displays the final output + bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; + protected: + Shader_None shaderDefault; + }; +} + + + +#define PGE_RENDERER_NONE_DECLARED 1 +#endif +#endif + + +#if OLC_GPU == OLC_GPU_OPENGL33 + +#if OLC_HOST == OLC_HOST_WINDOWS + #include + #pragma comment(lib, "gdi32.lib") + #pragma comment(lib, "opengl32.lib") +#if defined(__MINGW32__) || defined(__MINGW64__) + #include +#else + #include +#endif + #define CALLSTYLE __stdcall + // ooof... was getting a bunch of spurious C4191 from MSVC 17.14.9, so round trip via void-town + #define OGL_LOAD(t) reinterpret_cast(reinterpret_cast(wglGetProcAddress(#t))) +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 + #include + #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) +#endif + +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + #include + #include + + #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) +#endif + +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS + #define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED // Stops MacOS & iOS whining about OpenGL even more + #define CALLSTYLE + #define OGL_LOAD(t) &::t + #define GL_GLEXT_PROTOTYPES + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE + #include + #if OLC_HOST == OLC_HOST_MACOS + #include + #include + #include + #include + #endif + #if OLC_HOST == OLC_HOST_IOS + #define GL_LINE GL_LINES + #define GL_FILL GL_LINE_STRIP + #include + #include + #endif + +#endif + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN + #include + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #include + #define CALLSTYLE + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE + + #define OGL_LOAD(t) ::t +#endif + +#if OLC_HOST == OLC_HOST_ANDROID + #include + #include + #define GL_GLEXT_PROTOTYPES + #include + #define CALLSTYLE + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE + #define GL_LINE 0 + #define GL_FILL 0 + #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) +#endif + +#if OLC_HOST == OLC_HOST_WXWIDGETS - // Map of system keycodes to olc::Keycodes - std::unordered_map mapKeys; + +#endif + +#if !defined(CALLSTYLE) + #define CALLSTYLE +#endif + + + +namespace olc +{ + namespace apis::opengl + { + + typedef char GLchar; + typedef ptrdiff_t GLsizeiptr; + +#if OLC_HOST == OLC_HOST_WINDOWS + // "Target Host Window Handle" + typedef HDC glDeviceContext_t; + // "State of OpenGL Machinary" + typedef HGLRC glRenderContext_t; + // Johnnyg63: moved glShaderSource_t definition for Windows, as it does not match macOS and android signatures + typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +#endif + +#if OLC_HOST == OLC_HOST_MACOS + typedef void* glDeviceContext_t; + typedef CGLContextObj glRenderContext_t; + typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); +#endif + +#if OLC_HOST == OLC_HOST_IOS + typedef void* glDeviceContext_t; + typedef void* glRenderContext_t; // EAGLContext is the OpenGL ES rendering context for iOS + typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 + typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); + typedef X11::GLXContext glDeviceContext_t; + typedef X11::GLXContext glRenderContext_t; +#endif + +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID + typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei size, const GLchar *const * string, const GLint * length); + typedef void glDeviceContext_t; + typedef struct + { + EGLDisplay display; + EGLContext context; + EGLSurface surface; + EGLConfig config; + } glRenderContext_t; +#endif + + typedef GLuint CALLSTYLE glCreateShader_t(GLenum type); + typedef GLuint CALLSTYLE glCreateProgram_t(void); + + typedef void CALLSTYLE glDeleteShader_t(GLuint shader); + typedef void CALLSTYLE glCompileShader_t(GLuint shader); + typedef void CALLSTYLE glLinkProgram_t(GLuint program); + typedef void CALLSTYLE glDeleteProgram_t(GLuint program); + typedef void CALLSTYLE glAttachShader_t(GLuint program, GLuint shader); + typedef void CALLSTYLE glBindBuffer_t(GLenum target, GLuint buffer); + typedef void CALLSTYLE glBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); + typedef void CALLSTYLE glGenBuffers_t(GLsizei n, GLuint* buffers); + typedef void CALLSTYLE glVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + typedef void CALLSTYLE glEnableVertexAttribArray_t(GLuint index); + typedef void CALLSTYLE glUseProgram_t(GLuint program); + typedef void CALLSTYLE glBindVertexArray_t(GLuint array); + typedef void CALLSTYLE glGenVertexArrays_t(GLsizei n, GLuint* arrays); + typedef void CALLSTYLE glGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + typedef GLint CALLSTYLE glGetUniformLocation_t(GLuint program, const GLchar* name); + typedef void CALLSTYLE glUniform1f_t(GLint location, GLfloat v0); + typedef void CALLSTYLE glUniform1i_t(GLint location, GLint v0); + typedef void CALLSTYLE glUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); + typedef void CALLSTYLE glUniform4fv_t(GLint location, GLsizei count, const GLfloat* value); + typedef void CALLSTYLE glUniformMatrix4fv_t(GLint location, GLsizei count, GLboolean trasnpose, const GLfloat* value); + typedef void CALLSTYLE glActiveTexture_t(GLenum texture); + typedef void CALLSTYLE glGenFramebuffers_t(GLsizei n, GLuint* ids); + typedef void CALLSTYLE glBindFramebuffer_t(GLenum target, GLuint fb); + typedef GLenum CALLSTYLE glCheckFramebufferStatus_t(GLenum target); + typedef void CALLSTYLE glDeleteFramebuffers_t(GLsizei n, const GLuint* fbs); + typedef void CALLSTYLE glFramebufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + typedef void CALLSTYLE glDrawBuffers_t(GLsizei n, const GLenum* bufs); + typedef void CALLSTYLE glBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); + typedef void CALLSTYLE glTexImage2DMultisample_t(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); + typedef void CALLSTYLE glBlitFramebuffer_t(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + typedef void CALLSTYLE glGenRenderbuffers_t(GLsizei n, GLuint* renderbuffers); + typedef void CALLSTYLE glBindRenderbuffer_t(GLenum target, GLuint renderbuffer); + typedef void CALLSTYLE glRenderbufferStorageMultisample_t(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); + typedef void CALLSTYLE glFramebufferRenderbuffer_t(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); + typedef void CALLSTYLE glDeleteRenderbuffers_t(GLsizei n, const GLuint* renderbuffers); + typedef void CALLSTYLE glGetInternalformativ_t(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); + typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); + typedef void CALLSTYLE glGetIntegerv_t(GLenum pname, GLint *data); + typedef void CALLSTYLE glGetRenderbufferParameteriv_t(GLenum target, GLenum pname, GLint* params); + typedef void CALLSTYLE glRenderbufferStorage_t(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + +#if OLC_HOST == OLC_HOST_WINDOWS + typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 + typedef GLint CALLSTYLE glXSwapIntervalEXT_t(X11::Display* dpy, X11::GLXDrawable drawable, GLint interval); +#endif + + // A little GL class (singleton) + class gl + { + public: + static gl& Get(); + inline bool HasLoaded() const { return bLoaded; } + + private: + gl() = default; + static bool bLoaded; + + private: + bool LoadAll(); + + protected: + glCreateShader_t* _glCreateShader = nullptr; + glCreateProgram_t* _glCreateProgram = nullptr; + glShaderSource_t* _glShaderSource = nullptr; + glDeleteShader_t* _glDeleteShader = nullptr; + glCompileShader_t* _glCompileShader = nullptr; + glLinkProgram_t* _glLinkProgram = nullptr; + glDeleteProgram_t* _glDeleteProgram = nullptr; + glAttachShader_t* _glAttachShader = nullptr; + glBindBuffer_t* _glBindBuffer = nullptr; + glBufferData_t* _glBufferData = nullptr; + glGenBuffers_t* _glGenBuffers = nullptr; + glVertexAttribPointer_t* _glVertexAttribPointer = nullptr; + glEnableVertexAttribArray_t* _glEnableVertexAttribArray = nullptr; + glUseProgram_t* _glUseProgram = nullptr; + glBindVertexArray_t* _glBindVertexArray = nullptr; + glGenVertexArrays_t* _glGenVertexArrays = nullptr; + glGetShaderInfoLog_t* _glGetShaderInfoLog = nullptr; + glGetUniformLocation_t* _glGetUniformLocation = nullptr; + glUniform1f_t* _glUniform1f = nullptr; + glUniform1i_t* _glUniform1i = nullptr; + glUniform2fv_t* _glUniform2fv = nullptr; + glUniform4fv_t* _glUniform4fv = nullptr; + glUniformMatrix4fv_t* _glUniformMatrix4fv = nullptr; + glActiveTexture_t* _glActiveTexture = nullptr; + glGenFramebuffers_t* _glGenFramebuffers = nullptr; + glBindFramebuffer_t* _glBindFramebuffer = nullptr; + glCheckFramebufferStatus_t* _glCheckFramebufferStatus = nullptr; + glDeleteFramebuffers_t* _glDeleteFramebuffers = nullptr; + glFramebufferTexture2D_t* _glFramebufferTexture2D = nullptr; + glDrawBuffers_t* _glDrawBuffers = nullptr; + glBlendFuncSeparate_t* _glBlendFuncSeparate = nullptr; + glTexImage2DMultisample_t* _glTexImage2DMultisample = nullptr; + glBlitFramebuffer_t* _glBlitFramebuffer = nullptr; + glGenRenderbuffers_t* _glGenRenderbuffers = nullptr; + glBindRenderbuffer_t* _glBindRenderbuffer = nullptr; + glRenderbufferStorageMultisample_t* _glRenderbufferStorageMultisample = nullptr; + glFramebufferRenderbuffer_t* _glFramebufferRenderbuffer = nullptr; + glDeleteRenderbuffers_t* _glDeleteRenderbuffers = nullptr; + glGetInternalformativ_t* _glGetInternalformativ = nullptr; + glGetShaderiv_t* _glGetShaderiv = nullptr; + glGetIntegerv_t *_glGetIntegerv = nullptr; + glGetRenderbufferParameteriv_t* _glGetRenderbufferParameteriv = nullptr; + glRenderbufferStorage_t* _glRenderbufferStorage = nullptr; +#if OLC_HOST == OLC_HOST_WINDOWS + wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; +#endif + +#if OLC_HOST == OLC_HOST_LINUX_X11 + public: + glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; +#endif + + public: + // Proxies allow switchable, clutter-free error checking + + // OpenGL3.3 Proxies + + GLuint glCreateShader(GLenum type); + GLuint glCreateProgram(void); + void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); + void glDeleteShader(GLuint shader); + void glCompileShader(GLuint shader); + void glLinkProgram(GLuint program); + void glDeleteProgram(GLuint program); + void glAttachShader(GLuint program, GLuint shader); + void glBindBuffer(GLenum target, GLuint buffer); + void glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage); + void glGenBuffers(GLsizei n, GLuint* buffers); + void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + void glEnableVertexAttribArray(GLuint index); + void glUseProgram(GLuint program); + void glBindVertexArray(GLuint array); + void glGenVertexArrays(GLsizei n, GLuint* arrays); + void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + GLint glGetUniformLocation(GLuint program, const GLchar* name); + void glUniform1f(GLint location, GLfloat v0); + void glUniform1i(GLint location, GLint v0); + void glUniform2fv(GLint location, GLsizei count, const GLfloat* value); + void glUniform4fv(GLint location, GLsizei count, const GLfloat* value); + void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean trasnpose, const GLfloat* value); + void glActiveTexture(GLenum texture); + void glGenFramebuffers(GLsizei n, GLuint* ids); + void glBindFramebuffer(GLenum target, GLuint fb); + GLenum glCheckFramebufferStatus(GLenum target); + void glDeleteFramebuffers(GLsizei n, const GLuint* fbs); + void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + void glDrawBuffers(GLsizei n, const GLenum* bufs); + void glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); + void glTexImage2DMultisample(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); + void glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); + void glGenRenderbuffers(GLsizei n, GLuint* renderbuffers); + void glBindRenderbuffer(GLenum target, GLuint renderbuffer); + void glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); + void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); + void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); + void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); + void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); + void glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params); + void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + + // OpenGL1.2 Proxies (just keeps things tidy imo) + void glGenTextures(GLsizei n, GLuint* textures); + void glBindTexture(GLenum target, GLuint texture); + void glTexParameteri(GLenum target, GLenum pname, GLint param); + void glTexEnvf(GLenum target, GLenum pname, GLfloat param); + void glDeleteTextures(GLsizei n, const GLuint* textures); + void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels); + void glClear(GLbitfield mask); + void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); + void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); + void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* data); + void glCullFace(GLenum mode); + void glEnable(GLenum cap); + void glDisable(GLenum cap); + void glDrawArrays(GLenum mode, GLint first, GLsizei count); + void glBlendFunc(GLenum sfactor, GLenum dfactor); + void glDepthFunc(GLenum func); + void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); + void glHint(GLenum target, GLenum mode); + void glPolygonMode(GLenum face, GLenum mode); + void glFrontFace(GLenum mode); + void glGetIntegerv(GLenum pname, GLint *data); + + + void glSwapInterval(GLsizei n); + + // Constants + static constexpr GLenum GL_FRAMEBUFFER_COMPLETE_X = 0x8CD5; + static constexpr GLenum GL_TEXTURE_2D_MULTISAMPLE_X = 0x9100; + static constexpr GLenum GL_COLOR_ATTACHMENT0_X = 0x8CE0; + static constexpr GLenum GL_TEXTURE0_X = 0x84C0; + static constexpr GLenum GL_READ_FRAMEBUFFER_X = 0x8CA8; + static constexpr GLenum GL_DRAW_FRAMEBUFFER_X = 0x8ca9; + static constexpr GLenum GL_FRAMEBUFFER_X = 0x8D40; + static constexpr GLenum GL_ARRAY_BUFFER_X = 0x8892; + static constexpr GLenum GL_STREAM_DRAW_X = 0x88E0; + static constexpr GLenum GL_DRAW_FRAMEBUFFER_BINDING_X = 0x8CA6; + static constexpr GLenum GL_FRAGMENT_SHADER_X = 0x8B30; + static constexpr GLenum GL_VERTEX_SHADER_X = 0x8B31; + static constexpr GLenum GL_GEOMETRY_SHADER_X = 0x8DD9; + static constexpr GLenum GL_MULTISAMPLE_X = 0x809D; + static constexpr GLenum GL_RENDERBUFFER_X = 0x8D41; + static constexpr GLenum GL_SAMPLES_X = 0x80A9; + static constexpr GLenum GL_COMPILE_STATUS_X = 0x8B81; + static constexpr GLenum GL_INFO_LOG_LENGTH_X = 0x8B84; + static constexpr GLenum GL_DEPTH_COMPONENT24_X = 0x81A6; + static constexpr GLenum GL_DEPTH_ATTACHMENT_X = 0x8D00; + static constexpr GLenum GL_RENDERBUFFER_SAMPLES_X = 0x8CAB; + private: + bool CheckError(const std::source_location loc = std::source_location::current()); }; } -} -#endif - -#if OLC_HOST == OLC_HOST_MACOS - + +} // olc namespace -#include // Grand Central Dispatch: Apple's C-based API for managing concurrent operations on macOS and iOS. -#include // POSIX threads: Used for pthread_main_np() to check if current thread is the main thread +#if !defined(PGE_RENDERER_OPENGL33_DECLARED) namespace olc { - namespace host - { - // Manages our MacOS Host - class Host_Apple_MacOS : public olc::host::Host - { - public: - olc::Window* pPGEwindow = nullptr; // Pointer to PGE Window - - public: - Host_Apple_MacOS(); - virtual ~Host_Apple_MacOS() {}; - - public: - HostError GetLastError() const { return lastError; } + namespace gpu + { + class Shader_GLSL33 : public olc::gpu::Shader + { + public: + std::string Compile() override; + int32_t CreateUniform(const std::string& name) override; + }; - public: - virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - virtual bool CloseWindowFrame(olc::Window* pWindow) override; - virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + class Renderer_OGL33 : public olc::gpu::Renderer + { + public: // Device Stuff + // Constructs a GPU Device interface + bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; + // Destroys a GPU device interface + bool DestroyDevice() override; + // If applicable, relocate the rendering context + bool RetargetDevice(std::vector os_win_id) override; + // Prepare an OS rendering target + bool PrepareWindowTarget(std::vector os_win_id) override; - virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for entire host desktop refresh (for smooooth vsync), - virtual bool SyncWithDesktopComposite() override; - - public: // Platform specific Mouse Control - // Force the mouse position in pixels relative to window - 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: // Texture Resource Stuff + // Allocates a new texture resource in VRAM, returns handle + uint32_t CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg = olc::ImageConfig()) override; + // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM + bool WriteTexture(const uint32_t texid, olc::Image& image) override; + // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM + bool ReadTexture(const uint32_t texid, olc::Image& image) override; + // Destroys and releases texture resource for given handle + bool DeleteTexture(const uint32_t texid) override; + // Makes active the given texture resource (for subsequent sampling operations) + bool AssignTextureSource(const uint32_t slot, const uint32_t texid) override; + // Makes active the given texture resource (for subsequent rendering operations) + bool AssignTextureTarget(const uint32_t slot, const uint32_t texid) override; + // Resolves an MSAA texture into a normal texture + virtual bool ResolveMSAA(const uint32_t msaaTexId) override; - public: // OS Specific Environment Information - virtual olc::KeyboardLayout GetKeyboardLayout() const override; + public: // Shader Construction Stuff + // Change the shader used for subsequent GPU drawing tasks + bool ApplyShader(const Shader& shader) override; + // Reset to default shader for subsequent GPU drawing tasks + bool ApplyDefaultShader() override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const float value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::vf2d& value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::Pixel value) override; - public: // Platform Specific OS<->PGE Linkage - // Called at very start of application - virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - virtual bool StartSystem() override; - // Called to stop the host, and shutdown all resources - virtual bool StopSystem() override; - // Called at start of system event loop - virtual bool OnSystemThreadStart() override; - // Called to perform primary window update - virtual bool OnSystemTick() override; - // Called at end of system event loop - virtual bool OnSystemThreadEnd() override; - // Called at very end of application - virtual bool OnApplicationEnd() override; + public: // GPU Task Stuff + virtual bool DoGPUTask(const olc::GPUTask& task) override; - protected: - HostError lastError = HostError::None; + public: // Swap Chain Stuff + // Clears the viewport to a specific colour and depth + virtual bool ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) override; + // Sets the viewport area of the drawing space + virtual bool SetViewport(const olc::vf2d& pos, const olc::vf2d& size) override; + // Configures defaults prior to drawing + virtual bool DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) override; + // Displays the final output + virtual bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; - public: - // MacOS Application and Window pointers - std::unique_ptr pMacApplication = nullptr; - std::unique_ptr pMacOSWindow = nullptr; - std::unique_ptr pMacOSEventHandler = nullptr; - std::shared_ptr pMacOSOpenGLRenderer = nullptr; + + protected: // These may need some thinking about re multiple window + //olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID + olc::apis::opengl::glRenderContext_t glRenderContext; +#else + olc::apis::opengl::glRenderContext_t glRenderContext = 0; +#endif - void* pMacGLConextObj = nullptr; - std::once_flag intialAppFlag; - - // Map of system keycodes to olc::Keycodes - std::unordered_map mapKeys; - - private: - enum MAINTASKS{ - NONE, - CREATE_OPENGL_RENDERER, - RESIZE_WINDOW, - BECOME_ACTIVE, - RESIGN_ACTIVE, - MINIMIZE_WINDOW, - DEMINIMIZE_WINDOW - }; - - // Internal Mac OS functions - bool ExecutePendingMainThreadTasks(void); // Execute pending tasks on main thread - bool MainThreadTasks(void); // Handle main thread tasks - bool AddPendingMainThreadTask(MAINTASKS task); // Add a pending task to main thread. Note: You should ever add tasks that require main thread execution only from the PGE thread - bool CreateCGLContextObj(); // Create CGL Context Object - std::vector vPendingMainThreadTasks; // Vector of pending main thread tasks - - std::vector vMacOSWindowDescriptors; // Vector to hold window descriptors - bool enableVSync = false; // VSync enabled flag - bool bSkipFrame = false; // Flag to indicate if frame should be skipped + Shader_GLSL33 shaderDefault; + uint32_t nDefaultVB = 0; + uint32_t nDefaultVA = 0; + uint32_t nDefaultFBO = 0; + uint32_t nScreenFBO = 0; + olc::Image imgBlank; + olc::vf2d vTargetSize; - // Thread synchronization for PGE Thread V Main thread - mutable std::mutex mainThreadPendingTasksMutex; // Mutex for main thread pending tasks - std::condition_variable mainThreadResetCondition; // Condition variable for main thread reset - std::atomic isMainThreadResetting{false}; // Atomic flag for resetting main thread + uint32_t nCurrentTextureTarget = 0; + uint32_t nCurrentTextureSource = 0; - mutable std::mutex pgeThreadPendingTasksMutex; // Mutex for PGE thread pending tasks - std::condition_variable pgeThreadResetCondition; // Condition variable for PGE thread reset - std::atomic isPGEThreadResetting{true}; // Atomic flag for resetting PGE thread - - std::atomic systemActive = false; // Atomic flag for system active state + //std::unordered_map mapMSAAToResolved; + uint32_t nResolveFBO_Read = 0; + uint32_t nResolveFBO_Draw = 0; - struct sFrameBounds - { - double x = 0.0; - double y = 0.0; - double width = 800.0; - double height = 600.0; - } frameBounds; + std::unordered_map mapTextureSizes; - void MacApplicationEventsHandler(); - void MacWindowEventsHandler(); - void MacEventsHandler(); - 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(); - - - }; - } -} + std::unordered_map mapTextureToRenderbuffer; -#endif + const Shader* pCurrentShader = nullptr; -#if OLC_HOST == OLC_HOST_LINUX_X11 + uint32_t nDepthRBO = 0; // Shared depth renderbuffer + olc::vi2d vCurrentDepthSize = {0, 0}; // Track current depth buffer size + int32_t nCurrentDepthSamples = 0; // Track current MSAA sample count -#include -#include -namespace X11 -{ -#include -#include -#include -#include -#include -#include -#undef None -constexpr int None = 0L; +#if OLC_HOST == OLC_HOST_ANDROID + EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); +#endif + + }; + } } +#define PGE_RENDERER_OPENGL33_DECLARED 1 +#endif +#endif -namespace olc::host -{ - class Host_Linux_X11 : public olc::host::Host - { - private: - X11::Display* olc_Display = nullptr; - X11::Window olc_WindowRoot; - X11::XVisualInfo* olc_VisualInfo; - X11::Colormap olc_ColourMap; - X11::XSetWindowAttributes olc_SetWindowAttribs; +#if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif - int xinput_extension_code{}; - public: - Host_Linux_X11(); +#define _WINSOCKAPI_ - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - bool CloseWindowFrame(olc::Window* pWindow) override; - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; +#if !defined(VC_EXTRALEAN) + #define VC_EXTRALEAN +#endif - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; +#if !defined(NOMINMAX) + #define NOMINMAX +#endif - olc::KeyboardLayout GetKeyboardLayout() const override; - void UpdateKeyboardLayout(); +// In Code::Blocks +//#if !defined(_WIN32_WINNT) +// #ifdef HAVE_MSMF +// #define _WIN32_WINNT 0x0600 // Windows Vista +// #else +// #define _WIN32_WINNT 0x0500 // Windows 2000 +// #endif +//#endif - // Wait for entire host desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; +// Embrace MSVC superiority +#pragma comment(lib, "gdiplus.lib") +#pragma comment(lib, "Shlwapi.lib") +#include +#include +#if !defined(__MINGW32__) && !defined(__MINGW64__) +#include +#endif +#include +#undef _WINSOCKAPI_ - // 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; - bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; - public: - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - bool StartSystem() override; - bool StopSystem() override; - bool OnSystemThreadStart() override; - bool OnSystemTick() override; - bool OnSystemThreadEnd() override; - bool OnApplicationEnd() override; - - private: - void enableTouch(X11::Display* display, X11::Window window); - void handleTouchEvent(X11::XIDeviceEvent* event); - std::unordered_map mapUID2X11Window; - std::unordered_map mapX11Window2PTR; - std::atomic systemActive {true}; +#if !defined(PGE_IMAGELOADER_WINGDI_DECLARED) +namespace olc +{ + namespace imload + { + class ImageLoader_WinGDI : public ImageLoader + { + public: + + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - std::unordered_map mapKeys; - std::unordered_map mapMouseButtons; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; - // Keyboard Layout Variables - olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; - bool kbExtensionsFound = false; - int xkbEventBase = 0; - int xkbErrorBase = 0; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - // Mouse Variables - std::unordered_map mapUID2X11Cursor; - bool bMouseIsVisible = true; - }; + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + private: + bool DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp); + }; + } } +#define PGE_IMAGELOADER_WINGDI_DECLARED 1 +#endif #endif -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - +#if OLC_IMAGELOADER == OLC_IMAGELOADER_MACOS +#if !defined(PGE_IMAGELOADER_MACOS_DECLARED) +namespace olc +{ + namespace imload + { + class ImageLoader_MacOS : public ImageLoader + { + public: + + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; -#include -#include -#include -#include "xdg-shell.h" + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; -#include "pointer-warp.h" -#include -#include -#include -#include -#include -#include + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; -#include "libdecor.h" + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; -#include -#include + }; + } +} -#ifndef WL_KEYBOARD_KEY_STATE_REPEATED -#define WL_KEYBOARD_KEY_STATE_REPEATED 2 +#define PGE_IMAGELOADER_MACOS_DECLARED 1 +#endif #endif -namespace olc::host +#if OLC_IMAGELOADER == OLC_IMAGELOADER_IOS +#if !defined(PGE_IMAGELOADER_IOS_DECLARED) +namespace olc { - struct WaylandWindow { - wl_surface* surface{nullptr}; - xdg_surface* surface_xdg{nullptr}; - wl_egl_window* window{nullptr}; - size_t olc_window_uid{0}; - int32_t bounds_x{0}; - int32_t bounds_y{0}; - bool cursor_visible{true}; - // Ignore window size bounds for fullscreen events - bool fullscreen{false}; - - // 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{}; - ~WaylandWindow(); - }; - - namespace wayland { - enum PointerEventMask { - PointerEventEnter = 1 << 0, - PointerEventLeave = 1 << 1, - PointerEventMotion = 1 << 2, - PointerEventButton = 1 << 3, - PointerEventAxis = 1 << 4, - PointerEventAxisSource = 1 << 5, - PointerEventAxisStop = 1 << 6, - PointerEventDiscrete = 1 << 7 - }; - - struct PointerState { - uint32_t event_mask{0}; - wl_surface* surface{nullptr}; - wl_fixed_t surface_x{}; - wl_fixed_t surface_y{}; - uint32_t button{0}; - uint32_t state{0}; - uint32_t time{0}; - uint32_t serial{0}; + namespace imload + { + class ImageLoader_iOS : public ImageLoader + { + public: + + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - struct Axis { - wl_fixed_t value{}; - int32_t discrete{0}; - bool valid{false}; - }; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; - std::array axes{}; - uint32_t axis_source{0}; - }; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - enum TouchEventMask { - TouchEventDown = 1 << 0, - TouchEventUp = 1 << 1, - TouchEventMotion = 1 << 2, - TouchEventCancel = 1 << 3, - TouchEventShape = 1 << 4, - TouchEventOrientation = 1 << 5 - }; + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; - struct TouchState { - uint32_t event_mask{0}; - wl_surface* surface{nullptr}; - wl_fixed_t surface_x{}; - wl_fixed_t surface_y{}; - wl_fixed_t major{}; - wl_fixed_t minor{}; - wl_fixed_t orientation{}; + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - uint32_t time{0}; - uint32_t serial{0}; - int32_t id{0}; }; } +} - class Host_Linux_Wayland : public olc::host::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}; - wl_keyboard* keyboard{nullptr}; - wl_touch* touch{nullptr}; - uint32_t keyboard_version{0}; - xkb_context* kb_context{nullptr}; - xkb_state* kb_state{nullptr}; - xkb_keymap* kb_keymap{nullptr}; - uint32_t kb_group{0}; - xdg_wm_base* xdg_wm{nullptr}; - wp_pointer_warp_v1* pointer_warp{nullptr}; - uint32_t enter_serial{0}; - - wayland::PointerState pointer_state; - std::unordered_map touches; - wl_surface* cursor_surface{nullptr}; - wl_cursor_image* cursor_image{nullptr}; - wl_cursor_theme* cursor_theme{nullptr}; - - size_t active_window_id; - - // libdecor support - bool using_libdecor{false}; - libdecor* decor_context{nullptr}; - std::mutex decor_mutex; - - public: - Host_Linux_Wayland(); - ~Host_Linux_Wayland(); +#define PGE_IMAGELOADER_IOS_DECLARED 1 +#endif +#endif - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - bool CloseWindowFrame(olc::Window* pWindow) override; - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; +#if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG +#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +#include - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; +namespace olc::imload +{ + class ImageLoader_LibPNG : public ImageLoader + { + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - olc::KeyboardLayout GetKeyboardLayout() const override; - void UpdateKeyboardLayout(); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; - // Wait for entire host desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; - bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; - bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; - bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - public: - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - bool StartSystem() override; - bool StopSystem() override; - bool OnSystemThreadStart() override; - bool OnSystemTick() override; - bool OnSystemThreadEnd() override; - bool OnApplicationEnd() override; + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - // Various callbacks from the wayland protocol - static void registry_handle_global_callback(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version); - static void registry_handle_global_remove_callback(void* data, wl_registry* registry, uint32_t name); + 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); - static void seat_capabilities_callback(void* data, wl_seat* seat, uint32_t capabilities); - static void seat_name_callback(void* data, wl_seat* wl_seat, const char* name); + private: // libpng internals + bool DecodePNG(olc::Image& image, png_structp png, png_infop info); + }; +} - // pointer callbacks - static void pointer_enter_callback(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y); - static void pointer_leave_callback(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface); - static void pointer_motion_callback(void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); - static void pointer_button_callback(void* data, wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); - static void pointer_axis_callback(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value); - static void pointer_frame_callback(void* data, wl_pointer* pointer); - static void pointer_axis_source_callback(void* data, wl_pointer* pointer, uint32_t axis_source); - static void pointer_axis_stop_callback(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis); - static void pointer_axis_discrete_callback(void* data, wl_pointer* pointer, uint32_t axis, int32_t discrete); - static void pointer_axis_value120_callback(void* data, wl_pointer* pointer, uint32_t axis, int32_t value120); - static void pointer_axis_relative_direction_callback(void* data, wl_pointer* pointer, uint32_t axis, uint32_t direction); +#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 +#endif +#endif - // Keyboard callbacks - static void keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); - static void keyboard_enter_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); - static void keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); - static void keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); - static void keyboard_modifiers_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); - static void keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay); +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER +#include - // Touch callbacks - static void touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); - static void touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); - static void touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); - static void touch_frame_callback(void* data, wl_touch* touch); - static void touch_cancel_callback(void* data, wl_touch* touch); - static void touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); - static void touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation); +#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) +namespace olc::imload +{ - // xdg callbacks - static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); - static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); + // Create an image resource based on an image file asset + class ImageLoader_NDKImageDecoder : public ImageLoader + { + public: + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - // 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); - - private: - // Wayland callback functions - void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); - void registry_handle_global_remove(wl_registry* registry, uint32_t name); - void seat_capabilities(wl_seat* seat, uint32_t capabilities); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; - // Pointer callback functions - void pointer_enter(wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y); - void pointer_leave(wl_pointer* pointer, uint32_t serial, wl_surface* surface); - void pointer_motion(wl_pointer* pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); - void pointer_button(wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); - void pointer_axis(wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value); - void pointer_frame(wl_pointer* pointer); - void pointer_axis_source(wl_pointer* pointer, uint32_t axis_source); - void pointer_axis_stop(wl_pointer* pointer, uint32_t time, uint32_t axis); - void pointer_axis_discrete(wl_pointer* pointer, uint32_t axis, int32_t discrete); - void pointer_axis_value120(wl_pointer* pointer, uint32_t axis, int32_t value120); - void pointer_axis_relative_direction(wl_pointer* pointer, uint32_t axis, uint32_t direction); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - // Keyboard Callback Functions - void keyboard_keymap(wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size); - void keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys); - void keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface); - void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); - void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; - // Touch callbacks - void touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); - void touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); - void touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); - void touch_frame(wl_touch* touch); - void touch_cancel(wl_touch* touch); - void touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); - void touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation); + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + }; +} +#define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 +#endif +#endif - // 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); +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +#if !defined(PGE_IMAGELOADER_STB_DECLARED) +namespace olc::imload +{ + class ImageLoader_STB_Image : public ImageLoader + { + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - bool CreateEGLContext(WaylandWindow* window); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - std::unordered_map mapUID2Window; - std::unordered_map mapUID2OlcWindow; - std::atomic systemActive {true}; - std::unordered_map mapKeys; - olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; } +#define PGE_IMAGELOADER_STB_DECLARED 1 +#endif #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN -#include -#include -#include +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) -namespace olc::host -{ - class Host_Web_Emscripten : public olc::host::Host - { - public: - Host_Web_Emscripten(); - - public: // OS Window Handling - // Make OS Create a window frame, associated with olc::Window - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - // Make OS Close a window frame, associated with olc::Window - bool CloseWindowFrame(olc::Window* pWindow) override; - // Make OS Update a window frame title, associated with olc::Window - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - // Get OS-specific window descriptor(s) for given olc::Window - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for entire host desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; +#if OLC_HOST == OLC_HOST_MACOS - public: // Platform specific Mouse Control - // 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; - bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; +// Application consts selectors +static constexpr const char* kRespondsToSelector = "respondsToSelector:"; +static constexpr const char* kNSApplicationClass = "NSApplication"; +static constexpr const char* kNSWindowClass = "NSWindow"; +static constexpr const char* kNSStringClass = "NSString"; +static constexpr const char* kNSOpenGLPixelFormatClass = "NSOpenGLPixelFormat"; +static constexpr const char* kNSOpenGLViewClass = "NSOpenGLView"; +static constexpr const char* kNSObjectClass = "NSObject"; +static constexpr const char* kAppDelegateClass = "AppDelegate"; +static constexpr const char* kWindowDelegateClass = "WindowDelegate"; +static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; +static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; - public: // OS Specific Environment Information - olc::KeyboardLayout GetKeyboardLayout() const override; +// Application Screen management selectors +//static constexpr const char* kNSScreenClass = "NSScreen"; // Temp remove unused variable warning +static constexpr const char* kScreenSel = "screen"; +static constexpr const char* kNSCursorClass = "NSCursor"; +static constexpr const char* kUnhideSel = "unhide"; +static constexpr const char* kHideSel = "hide"; +static constexpr const char* kIsHiddenSel = "isHidden"; + + +// Application memory management selectors +static constexpr const char* kAllocSel = "alloc"; +static constexpr const char* kInitSel = "init"; +static constexpr const char* kSetDelegateSel = "setDelegate:"; +static constexpr const char* kReleaseSel = "release"; +static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; + +// NSApplication lifecycle and management selectors +static constexpr const char* kSharedApplicationSel = "sharedApplication"; +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 +static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; +static constexpr const char* kApplicationDidFinishLaunchingSel = "applicationDidFinishLaunching:"; +static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; +static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; +static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; + +// NSWindow creation, display, and management selectors +static constexpr const char* kInitWithContentRectSel = "initWithContentRect:styleMask:backing:defer:"; +static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; +static constexpr const char* kSetTitleSel = "setTitle:"; +static constexpr const char* kOrderFrontRegardlessSel = "orderFrontRegardless"; +static constexpr const char* kSetAcceptsMouseMovedEventsSel = "setAcceptsMouseMovedEvents:"; +static constexpr const char* kMakeFirstResponderSel = "makeFirstResponder:"; +static constexpr const char* kMakeKeyAndOrderFrontSel = "makeKeyAndOrderFront:"; +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:"; - public: - // Called at very start of application - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - bool StartSystem() override; - // Called to stop the host, and shutdown all resources - bool StopSystem() override; - // Called at start of system event loop - bool OnSystemThreadStart() override; - // Called to perform primary window update - bool OnSystemTick() override; - // Called at end of system event loop - bool OnSystemThreadEnd() override; - // Called at very end of application - bool OnApplicationEnd() override; - - static void MainLoop(void* userData); +// NSWindowDelegate lifecycle and event methods selectors +static constexpr const char* kWindowDidResizeSel = "windowDidResize:"; +static constexpr const char* kWindowWillCloseSel = "windowWillClose:"; +static constexpr const char* kWindowDidBecomeKeySel = "windowDidBecomeKey:"; +static constexpr const char* kWindowDidResignKeySel = "windowDidResignKey:"; +static constexpr const char* kWindowDidMiniaturizeSel = "windowDidMiniaturize:"; +static constexpr const char* kWindowDidDeminiaturizeSel = "windowDidDeminiaturize:"; - public: // event callbacks - static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); - static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData); - static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); - static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData); - static EM_BOOL fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); - static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData); - static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData); - static EM_BOOL visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); +// NSResponder keyboard and mouse event methods selectors +static constexpr const char* kKeyDownSel = "keyDown:"; +static constexpr const char* kKeyUpSel = "keyUp:"; +static constexpr const char* kFlagsChangedSel = "flagsChanged:"; +static constexpr const char* kMouseDownSel = "mouseDown:"; +static constexpr const char* kMouseUpSel = "mouseUp:"; +static constexpr const char* kMouseDraggedSel = "mouseDragged:"; +static constexpr const char* kMouseMovedSel = "mouseMoved:"; +static constexpr const char* kRightMouseDownSel = "rightMouseDown:"; +static constexpr const char* kRightMouseUpSel = "rightMouseUp:"; +static constexpr const char* kRightMouseDraggedSel = "rightMouseDragged:"; +static constexpr const char* kOtherMouseDownSel = "otherMouseDown:"; +static constexpr const char* kOtherMouseUpSel = "otherMouseUp:"; +static constexpr const char* kOtherMouseDraggedSel = "otherMouseDragged:"; +static constexpr const char* kScrollWheelSel = "scrollWheel:"; +static constexpr const char* kDeltaXSel = "deltaX"; +static constexpr const char* kDeltaYSel = "deltaY"; +static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; +static constexpr const char* kMouseEnteredSel = "mouseEntered:"; +static constexpr const char* kMouseExitedSel = "mouseExited:"; +static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; +static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; +static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; - private: // Window Wrappers - // Set Mouse Device State - static bool olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed); - static bool olc_OnMouseMove(olc::Window* pWindow, const olc::vi2d& vMousePos); - static bool olc_OnMouseWheel(olc::Window* pWindow, const int32_t nScroll); - static bool olc_OnFocus(olc::Window* pWindow, const bool bHasFocus); - - // Set Keyboard Device State - static bool olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed); +// Copied a lot of this code from iOS, will need to be tested +// NSResponder touch event method selectors +static constexpr const char* kSetAcceptsTouchEventsSel = "setAcceptsTouchEvents:"; +static constexpr const char* kSetWantsRestingTouchesSel = "setWantsRestingTouches:"; +static constexpr const char* kCGEventSel = "CGEvent"; +static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; - // Set Window State - static bool olc_OnWindowPosition(olc::Window* pWindow, const olc::vi2d& vWindowPos); - static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); - static bool olc_OnWindowClose(olc::Window* pWindow); - - private: // Emscripten internal funcs - std::string getNavigatorLocale(); +// NSTouch / NSSet data extraction selectors +static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; +static constexpr const char* kAllObjectsSel = "allObjects"; +static constexpr const char* kNormalizedPositionSel = "normalizedPosition"; +static constexpr const char* kDeviceSizeSel = "deviceSize"; +static constexpr const char* kIdentitySel = "identity"; +static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; +static constexpr const char* kTouchCountSel = "count"; - public: // Callback data type - struct CallbackData { - Host_Web_Emscripten* pHost; - olc::Window* pWindow; - std::string canvasId; - }; +// NSResponder tablet / stylus event method selectors +static constexpr const char* kTabletPointSel = "tabletPoint:"; +static constexpr const char* kTabletProximitySel = "tabletProximity:"; +static constexpr const char* kPressureChangeSel = "pressureChangeWithEvent:"; +static constexpr const char* kStageSel = "stage"; - private: - static std::unordered_map mapUID2CanvasId; - static std::unordered_map> mapUID2CallbackData; - static std::unordered_map mapCanvasId2PTR; - - // Map of system keycodes to olc::Keycodes - std::unordered_map mapKeys; - // Map of system mouse buttons to olc mouse buttons - std::unordered_map mapMouseButtons; - std::chrono::steady_clock::time_point timeHidden; - olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; - }; - - -} +// NSEvent tablet data extraction selectors +static constexpr const char* kPenPressureSel = "pressure"; +static constexpr const char* kPenRotationSel = "rotation"; +static constexpr const char* kPenTiltSel = "tilt"; +static constexpr const char* kPenIsEnteringProximitySel = "isEnteringProximity"; +static constexpr const char* kPointingDeviceTypeSel = "pointingDeviceType"; +static constexpr const char* kPenTangentialPressureSel = "tangentialPressure"; -#endif -#if OLC_HOST == OLC_HOST_ANDROID +// Managing first responder status and keyboard focus selectors +static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; +static constexpr const char* kBecomeFirstResponderSel = "becomeFirstResponder"; +static constexpr const char* kCanBecomeKeyViewSel = "canBecomeKeyView"; +static constexpr const char* kNeedsPanelToBecomeKeySel = "needsPanelToBecomeKey"; +static constexpr const char* kDrawRectSel = "drawRect:"; +static constexpr const char* kReshapeSel = "reshape"; +static constexpr const char* kUpdateSel = "update"; -#include -#include -#include +// NSOpenGL pixel format, view, and context management selectors +static constexpr const char* kInitWithAttributesSel = "initWithAttributes:"; +static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame:pixelFormat:"; +static constexpr const char* kSetContentViewSel = "setContentView:"; +static constexpr const char* kContentViewSel = "contentView"; +static constexpr const char* kBoundsSel = "bounds"; +static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru +static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; +static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; +static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; +static constexpr const char* kBackingScaleFactorSel = "backingScaleFactor"; +static constexpr const char* kOpenGLContextSel = "openGLContext"; +static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; +static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; +static constexpr const char* kFlushBufferSel = "flushBuffer"; +static constexpr const char* kDisplaySel = "display"; +static constexpr const char* kCGLContextObjSel = "CGLContextObj"; +static constexpr const char* kSetValuesSel = "setValues:forParameter:"; -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "olcPGE3", __VA_ARGS__)) -#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "olcPGE3", __VA_ARGS__)) -#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "olcPGE3", __VA_ARGS__)) -#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "olcPGE3", __VA_ARGS__)) -#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "olcPGE3", __VA_ARGS__)) +// Extracting data from NSEvent objects selectors +static constexpr const char* kKeyCodeSel = "keyCode"; +static constexpr const char* kCharactersSel = "characters"; +static constexpr const char* kLocationInWindowSel = "locationInWindow"; +static constexpr const char* kButtonNumberSel = "buttonNumber"; +static constexpr const char* kClickCountSel = "clickCount"; +static constexpr const char* kModifierFlagsSel = "modifierFlags"; +static constexpr const char* kUTF8StringSel = "UTF8String"; -// We allow users to create a normal main function for android apps -extern int main(int argc, char** argv); +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; -namespace olc::host -{ - using AndroidApp = struct android_app; - class Host_Android : public olc::host::Host - { - public: - Host_Android(); - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - bool CloseWindowFrame(olc::Window* pWindow) override; - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - bool SyncWithDesktopComposite() override; - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - olc::KeyboardLayout GetKeyboardLayout() const override; +// Default values and configuration settings +static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; +static constexpr double kDefaultWindowWidth = 800.0; +static constexpr double kDefaultWindowHeight = 600.0; +// static constexpr int kBitsPerByte = 8; // Temp remove unused variable warning +static constexpr int kMinValidDimension = 0; +static constexpr int kRGBABytesPerPixel = 4; +static constexpr int kFullyOpaque = 255; +static constexpr int kZeroBytes = 0; +static constexpr int kZeroRows = 0; +static constexpr int kZeroWidth = 0; +static constexpr int kZeroHeight = 0; +static constexpr int kFlippedOffset = 1; +static constexpr int kNoButton = -1; +// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning +bool bAllowHideCursor = true; +bool bHideCursor = false; - // Called at very start of application - bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - bool StartSystem() override; - // Called to stop the host, and shutdown all resources - bool StopSystem() override; - // Called at start of system event loop - bool OnSystemThreadStart() override; - // Called to perform primary window update - bool OnSystemTick() override; - // Called at end of system event loop - bool OnSystemThreadEnd() override; - // Called at very end of application - bool OnApplicationEnd() override; +// Objective-C method type encoding constants +// Type encoding for methods returning BOOL with no parameters: "c@:" +static constexpr const char* kBoolMethodTypeEncoding = "c@:"; - // 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; +// Type encoding for void methods with event/notification parameter: "v@:@" +static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; - void OnAppCmd(AndroidApp* app, int32_t cmd); - int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); +// Type encoding for drawRect method with NSRect parameter: "v@:{NSRect={NSPoint=dd}{NSSize=dd}}" +static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"; - bool IsInitialized() const { return initialized.load(); } +// Type encoding for void methods with no parameters: "v@:" +static constexpr const char* kVoidMethodTypeEncoding = "v@:"; - void ShowKeyboard(bool bShow); +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; - // TODO: file loading support (temporary) - std::vector OpenFile(const std::string& sFileName); - std::string OpenTextFile(const std::string& sFileName); +namespace ObjectiveCSEL { + +// Application memory management selectors + static SEL allocSel = nullptr; + static SEL initSel = nullptr; + static SEL setDelegateSel = nullptr; + static SEL releaseSel = nullptr; + static SEL isKindOfClassSel = nullptr; - static AndroidApp* androidApp; - protected: - olc::Window* pgeWindow = nullptr; - std::atomic initialized{false}, systemActive{false}; - bool shiftOn = false; + // NSApplication lifecycle and management selectors + static SEL sharedApplicationSel = nullptr; + static SEL activateIgnoringOtherAppsSel = nullptr; + static SEL setActivationPolicySel = nullptr; + static SEL runSel = nullptr; + static SEL stopSel = nullptr; + static SEL terminateSEL = nullptr; - void handleMouse(size_t id, int32_t action, AInputEvent* event); - void handleStylus(size_t id, int32_t action, AInputEvent* event); - void handleFinger(size_t id, int32_t action, AInputEvent* event); + // Application Screen management selectors + static SEL screenSel = nullptr; + static SEL unhideSel = nullptr; + static SEL hideSel = nullptr; + static SEL isHiddenSel = nullptr; - void PollEvents( - const std::function& funcContinue, - bool bBlocking = false - ); - }; + // NSApplicationDelegate lifecycle methods + static SEL applicationWillFinishLaunchingSel = nullptr; + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; - class JNI - { - public: - static void Init(AndroidApp* app); - static void Detach(); + // NSWindow creation, display, and management selectors + static SEL initWithContentRectSel = nullptr; + static SEL stringWithUTF8StringSel = nullptr; + static SEL setTitleSel = nullptr; + static SEL orderFrontRegardlessSel = nullptr; + static SEL setAcceptsMouseMovedEventsSel = nullptr; + static SEL makeKeyAndOrderFrontSel = nullptr; + static SEL makeKeyWindowSel = nullptr; + static SEL frameSel = nullptr; + static SEL setFrameDisplaySel = nullptr; + static SEL setFrameSel = nullptr; + static SEL makeFirstResponderSel = nullptr; + static SEL styleMaskSel = nullptr; + static SEL toggleFullScreenSel = nullptr; - static JNIEnv* GetEnv(); + // NSWindowDelegate lifecycle and event methods selectors + static SEL windowDidResizeSel = nullptr; + static SEL windowWillCloseSel = nullptr; + static SEL windowDidBecomeKeySel = nullptr; + static SEL windowDidResignKeySel = nullptr; + static SEL windowDidMiniaturizeSel = nullptr; + static SEL windowDidDeminiaturizeSel = nullptr; - private: - static JavaVM* jvm; - }; + // NSResponder keyboard and mouse event methods selectors + static SEL keyDownSel = nullptr; + static SEL keyUpSel = nullptr; + static SEL flagsChangedSel = nullptr; + static SEL mouseDownSel = nullptr; + static SEL mouseUpSel = nullptr; + static SEL mouseDraggedSel = nullptr; + static SEL mouseMovedSel = nullptr; + static SEL rightMouseDownSel = nullptr; + static SEL rightMouseUpSel = nullptr; + static SEL rightMouseDraggedSel = nullptr; + static SEL otherMouseDownSel = nullptr; + static SEL otherMouseUpSel = nullptr; + static SEL otherMouseDraggedSel = nullptr; + static SEL scrollWheelSel = nullptr; + static SEL deltaXSel = nullptr; + static SEL deltaYSel = nullptr; + static SEL updateTrackingAreasSel = nullptr; + static SEL mouseEnteredSel = nullptr; + static SEL mouseExitedSel = nullptr; - class JNIObject - { - public: - JNIObject() = default; - JNIObject(jobject obj); + // Mouse Tracking + static SEL addTrackingAreaSel = nullptr; + static SEL initWithRectSel = nullptr; - ~JNIObject(); + // Touch event selectors + static SEL setAcceptsTouchEventsSel = nullptr; + static SEL setWantsRestingTouchesSel = nullptr; + static SEL cgEventSel = nullptr; + static SEL touchesBeganSel = nullptr; + static SEL touchesMovedSel = nullptr; + static SEL touchesEndedSel = nullptr; + static SEL touchesCancelledSel = nullptr; + static SEL touchesMatchingPhaseSel = nullptr; + static SEL allObjectsSel = nullptr; + static SEL normalizedPositionSel = nullptr; + static SEL deviceSizeSel = nullptr; + static SEL identitySel = nullptr; + static SEL objectAtIndexSel = nullptr; + static SEL touchesCountSel = nullptr; - JNIObject(const JNIObject&) = delete; - JNIObject& operator=(const JNIObject&) = delete; + // Tablet / stylus event selectors + static SEL tabletPointSel = nullptr; + static SEL tabletProximitySel = nullptr; + static SEL penPressureSel = nullptr; + static SEL penRotationSel = nullptr; + static SEL penTiltSel = nullptr; + static SEL penIsEnteringProximitySel = nullptr; + static SEL pointingDeviceTypeSel = nullptr; + static SEL penTangentialPressureSel = nullptr; + static SEL pressureChangeSel = nullptr; + static SEL stageSel = nullptr; - JNIObject(JNIObject&& other) noexcept; + // Managing first responder status and keyboard focus selectors + static SEL acceptsFirstResponderSel = nullptr; + static SEL becomeFirstResponderSel = nullptr; + static SEL canBecomeKeyViewSel = nullptr; + static SEL needsPanelToBecomeKeySel = nullptr; + static SEL drawRectSel = nullptr; + static SEL reshapeSel = nullptr; + static SEL updateSel = nullptr; - JNIObject& operator=(JNIObject&& other) noexcept; + // NSOpenGL pixel format, view, and context management selectors + static SEL initWithAttributesSel = nullptr; + static SEL initWithFramePixelFormatSel = nullptr; + static SEL setContentViewSel = nullptr; + static SEL contentViewSel = nullptr; + static SEL boundsSel = nullptr; + static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru + static SEL convertPointToBackingSel = nullptr; + static SEL convertPointFromBackingSel = nullptr; + static SEL convertPointFromViewSel = nullptr; + static SEL openGLContextSel = nullptr; + static SEL makeCurrentContextSel = nullptr; + static SEL setAutoresizingMaskSel = nullptr; + static SEL flushBufferSel = nullptr; + static SEL displaySel = nullptr; + static SEL CGLContextObjSel = nullptr; + static SEL setValuesSel = nullptr; + static SEL backingScaleFactorSel = nullptr; - jobject Get() const { return obj; } - jobject operator *() const { return obj; } - operator bool() const { return obj != nullptr; } + // Extracting data from NSEvent objects selectors + static SEL keyCodeSel = nullptr; + static SEL charactersSel = nullptr; + static SEL locationInWindowSel = nullptr; + static SEL buttonNumberSel = nullptr; + static SEL clickCountSel = nullptr; + static SEL modifierFlagsSel = nullptr; + static SEL utf8StringSel = nullptr; - template - R Call( - const std::string& methodName, - const std::string& methodSig, - Args&&... args - ) - { - JNIEnv* env = JNI::GetEnv(); - jclass objClass = env->GetObjectClass(obj); - jmethodID methodID = env->GetMethodID( - objClass, - methodName.c_str(), - methodSig.c_str() - ); - env->DeleteLocalRef(objClass); + // NSLocale selectors + static SEL currentLocaleSel = nullptr; + static SEL localeIdentifierSel = nullptr; + // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning + // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning - R result{}; - - if constexpr (std::is_same_v) - { - env->CallVoidMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallIntMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallBooleanMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallByteMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallCharMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallShortMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallLongMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallFloatMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v) - { - result = env->CallDoubleMethod(obj, methodID, std::forward(args)...); - } - else if constexpr (std::is_same_v || std::is_same_v) - { - jobject callResult = env->CallObjectMethod(obj, methodID, std::forward(args)...); - if (env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - return {}; - } - if constexpr (std::is_same_v) - { - result = callResult; - } - else // JNIObject - { - result = JNIObject(callResult); - } - } - else - { - static_assert(sizeof(R) == 0, "Unsupported return type in JNIObject::Call"); - } - - if (env->ExceptionCheck()) - { - env->ExceptionDescribe(); - env->ExceptionClear(); - return {}; - } - return result; - } + // Initialize all selectors - called once at startup + void initializeSelectors() { + if (allocSel) return; // Already initialized + + // Application memory management selectors + allocSel = sel_registerName(kAllocSel); + initSel = sel_registerName(kInitSel); + setDelegateSel = sel_registerName(kSetDelegateSel); + releaseSel = sel_registerName(kReleaseSel); + isKindOfClassSel = sel_registerName(kIsKindOfClassSel); - private: - jobject obj = nullptr; - }; + // NSApplication lifecycle and management selectors + sharedApplicationSel = sel_registerName(kSharedApplicationSel); + activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); + setActivationPolicySel = sel_registerName(kSetActivationPolicySel); + runSel = sel_registerName(kRunSel); + stopSel = sel_registerName(kStopSel); + terminateSEL = sel_registerName(kTerminateSel); + + // Application Screen management selectors + screenSel = sel_registerName(kScreenSel); + unhideSel = sel_registerName(kUnhideSel); + hideSel = sel_registerName(kHideSel); + isHiddenSel = sel_registerName(kIsHiddenSel); - class JNIString - { - public: - JNIString(const std::string& str) - { - JNIEnv* env = JNI::GetEnv(); - jstring local = env->NewStringUTF(str.c_str()); - jstr = static_cast(env->NewGlobalRef(local)); - env->DeleteLocalRef(local); - } + // NSApplicationDelegate lifecycle methods + applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); + applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); + applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); + applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); + applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); - JNIString(jstring jstr) - { - JNIEnv* env = JNI::GetEnv(); - const char* chars = env->GetStringUTFChars(jstr, nullptr); - std::string str(chars); - env->ReleaseStringUTFChars(jstr, chars); - jstring local = env->NewStringUTF(str.c_str()); - this->jstr = static_cast(env->NewGlobalRef(local)); - env->DeleteLocalRef(local); - } + // NSWindow creation, display, and management selectors + initWithContentRectSel = sel_registerName(kInitWithContentRectSel); + stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); + setTitleSel = sel_registerName(kSetTitleSel); + orderFrontRegardlessSel = sel_registerName(kOrderFrontRegardlessSel); + setAcceptsMouseMovedEventsSel = sel_registerName(kSetAcceptsMouseMovedEventsSel); + makeFirstResponderSel = sel_registerName(kMakeFirstResponderSel); + makeKeyAndOrderFrontSel = sel_registerName(kMakeKeyAndOrderFrontSel); + makeKeyWindowSel = sel_registerName(kMakeKeyWindowSel); + 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); + windowDidBecomeKeySel = sel_registerName(kWindowDidBecomeKeySel); + windowDidResignKeySel = sel_registerName(kWindowDidResignKeySel); + windowDidMiniaturizeSel = sel_registerName(kWindowDidMiniaturizeSel); + windowDidDeminiaturizeSel = sel_registerName(kWindowDidDeminiaturizeSel); - ~JNIString() - { - if (jstr) - { - JNI::GetEnv()->DeleteGlobalRef(jstr); - } - jstr = nullptr; - } + // NSResponder keyboard and mouse event methods selectors + keyDownSel = sel_registerName(kKeyDownSel); + keyUpSel = sel_registerName(kKeyUpSel); + flagsChangedSel = sel_registerName(kFlagsChangedSel); + mouseDownSel = sel_registerName(kMouseDownSel); + mouseUpSel = sel_registerName(kMouseUpSel); + mouseDraggedSel = sel_registerName(kMouseDraggedSel); + mouseMovedSel = sel_registerName(kMouseMovedSel); + rightMouseDownSel = sel_registerName(kRightMouseDownSel); + rightMouseUpSel = sel_registerName(kRightMouseUpSel); + rightMouseDraggedSel = sel_registerName(kRightMouseDraggedSel); + otherMouseDownSel = sel_registerName(kOtherMouseDownSel); + otherMouseUpSel = sel_registerName(kOtherMouseUpSel); + otherMouseDraggedSel = sel_registerName(kOtherMouseDraggedSel); + scrollWheelSel = sel_registerName(kScrollWheelSel); + deltaXSel = sel_registerName(kDeltaXSel); + deltaYSel = sel_registerName(kDeltaYSel); + updateTrackingAreasSel = sel_registerName(kUpdateTrackingAreasSel); + mouseEnteredSel = sel_registerName(kMouseEnteredSel); + mouseExitedSel = sel_registerName(kMouseExitedSel); + addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); + initWithRectSel = sel_registerName(kInitWithRectSel); + + // Touch event selectors + setAcceptsTouchEventsSel = sel_registerName(kSetAcceptsTouchEventsSel); + setWantsRestingTouchesSel = sel_registerName(kSetWantsRestingTouchesSel); + cgEventSel = sel_registerName(kCGEventSel); + touchesBeganSel = sel_registerName(kTouchesBeganSel); + touchesMovedSel = sel_registerName(kTouchesMovedSel); + touchesEndedSel = sel_registerName(kTouchesEndedSel); + touchesCancelledSel = sel_registerName(kTouchesCancelledSel); + touchesMatchingPhaseSel = sel_registerName(kTouchesMatchingPhaseSel); + allObjectsSel = sel_registerName(kAllObjectsSel); + normalizedPositionSel = sel_registerName(kNormalizedPositionSel); + deviceSizeSel = sel_registerName(kDeviceSizeSel); + identitySel = sel_registerName(kIdentitySel); + objectAtIndexSel = sel_registerName(kObjectAtIndexSel); + touchesCountSel = sel_registerName(kTouchCountSel); - operator std::string() const - { - JNIEnv* env = JNI::GetEnv(); - const char* chars = env->GetStringUTFChars(jstr, nullptr); - std::string result(chars); - env->ReleaseStringUTFChars(jstr, chars); - return result; - } + // Tablet / stylus event selectors + tabletPointSel = sel_registerName(kTabletPointSel); + tabletProximitySel = sel_registerName(kTabletProximitySel); + penPressureSel = sel_registerName(kPenPressureSel); + penRotationSel = sel_registerName(kPenRotationSel); + penTiltSel = sel_registerName(kPenTiltSel); + penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); + pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); + penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); + pressureChangeSel = sel_registerName(kPressureChangeSel); + stageSel = sel_registerName(kStageSel); - jstring operator*() const - { - return jstr; - } + // Managing first responder status and keyboard focus selectors + acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); + becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); + canBecomeKeyViewSel = sel_registerName(kCanBecomeKeyViewSel); + needsPanelToBecomeKeySel = sel_registerName(kNeedsPanelToBecomeKeySel); + drawRectSel = sel_registerName(kDrawRectSel); + reshapeSel = sel_registerName(kReshapeSel); + updateSel = sel_registerName(kUpdateSel); - private: - jstring jstr = nullptr; - }; + // NSOpenGL pixel format, view, and context management selectors + initWithAttributesSel = sel_registerName(kInitWithAttributesSel); + initWithFramePixelFormatSel = sel_registerName(kInitWithFramePixelFormatSel); + setContentViewSel = sel_registerName(kSetContentViewSel); + contentViewSel = sel_registerName(kContentViewSel); + boundsSel = sel_registerName(kBoundsSel); + convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru + convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); + convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); + convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); + openGLContextSel = sel_registerName(kOpenGLContextSel); + makeCurrentContextSel = sel_registerName(kMakeCurrentContextSel); + setAutoresizingMaskSel = sel_registerName(kSetAutoresizingMaskSel); + flushBufferSel = sel_registerName(kFlushBufferSel); + displaySel = sel_registerName(kDisplaySel); + CGLContextObjSel = sel_registerName(kCGLContextObjSel); + setValuesSel = sel_registerName(kSetValuesSel); + backingScaleFactorSel = sel_registerName(kBackingScaleFactorSel); -} -#endif + // Extracting data from NSEvent objects selectors + keyCodeSel = sel_registerName(kKeyCodeSel); + charactersSel = sel_registerName(kCharactersSel); + locationInWindowSel = sel_registerName(kLocationInWindowSel); + buttonNumberSel = sel_registerName(kButtonNumberSel); + clickCountSel = sel_registerName(kClickCountSel); + modifierFlagsSel = sel_registerName(kModifierFlagsSel); + utf8StringSel = sel_registerName(kUTF8StringSel); -#else -#include -#include + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + } -// Not really an olc::Host, but some stand alone components that wxWidgets -// can use and get a PGE3 like rendering and user experience -namespace olc::wx -{ - class PGE3Core : public wxGLCanvas - { - public: - PGE3Core(wxWindow* parent); - virtual ~PGE3Core(); + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); + } +} - // Create an image resource - bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName, const ImageConfig& cfg = olc::ImageConfig()); - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg = olc::ImageConfig()); - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName); - // Store an image as a file asset in memory - //bool WriteImageToMemory(const olc::Image& image, std::vector bytes, const std::string& sFileName); - // Destroy an image - void DestroyImage(olc::Image& image); +// Single initialization function called once at startup +void initialize_macos_api() { + ObjectiveCSEL::initializeSelectors(); +} - public: - wxGLContext* get() const; - olc::gpu::Renderer* GetRenderer(); - olc::imload::ImageLoader* GetImageLoader(); +// Concepts safety templates for Objective-C interactions +template +concept CallbackType = std::is_function_v>; // Checks if T is a function type - private: // wxWidgets Specific - wxGLContext* m_glContext = nullptr; +template +concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) - private: // Global PGE3 Components (excluding host) - std::unique_ptr pRenderer; - std::unique_ptr pImageLoader; - }; +// Autorelease pool management functions +extern "C" { + extern void* objc_autoreleasePoolPush(void); + extern void objc_autoreleasePoolPop(void* pool); +} - class PGE3Panel : public wxGLCanvas - { - public: - PGE3Panel(wxWindow* parent); - PGE3Panel(wxWindow* parent, const olc::vi2d& vFixedSize); - virtual ~PGE3Panel(); +// Objective-C autorelease pools +class AutoreleasePool { +public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } + + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; + +private: + void* pool_; +}; - void ResetDrawState(); - protected: // Local PGE3 Components - olc::Draw draw; +using NSPoint = CGPoint; +using NSInteger = long; +using NSUInteger = unsigned long; - private: - // Same core is loaded across all panels in application - static olc::wx::PGE3Core* m_pCore; - olc::Image imgPrimary; +// Forward declarations +struct Application; +struct Window; +struct OpenGLRenderer; +struct ApplicationDelegate; +struct CustomOpenGLView; +struct WindowDelegate; +struct ImageLoader; - bool bFixedSize = false; - olc::vi2d vFixedSizeImage; +// enums for OpenGL pixel format and rendering capabilities +enum class NSOpenGLPixelFormatAttribute : uint16_t { + // Renderer selection + AllRenderers = 1, // Use all available renderers + RendererID = 70, // Specific renderer ID + NoRecovery = 72, // No recovery from renderer failures + Accelerated = 73, // Hardware accelerated rendering + AllowOfflineRenderers = 96, // Allow offline renderers + AcceleratedCompute = 97, // Accelerated compute support - olc::vf2d ScaleMouse(wxMouseEvent& evt); + // Buffer configuration + TripleBuffer = 3, // Triple buffering support + DoubleBuffer = 5, // Double buffering (recommended) + Stereo = 6, // Stereo buffering for 3D + AuxBuffers = 7, // Number of auxiliary buffers + BackingStore = 76, // Backing store configuration + + // Color and depth configuration + ColorSize = 8, // Total color buffer size (bits) + AlphaSize = 11, // Alpha channel size (bits) + DepthSize = 12, // Depth buffer size (bits) + StencilSize = 13, // Stencil buffer size (bits) + AccumSize = 14, // Accumulation buffer size (bits) + ColorFloat = 58, // Floating-point color buffer + // Antialiasing configuration + Multisample = 59, // Multisampling antialiasing + Supersample = 60, // Supersampling antialiasing + SampleBuffers = 55, // Number of sample buffers + Samples = 56, // Samples per pixel + SampleAlpha = 61, // Sample alpha to coverage + AuxDepthStencil = 57, // Auxiliary depth/stencil buffer - private: // wxWidgets Overrides - void Event_OnPaint(wxPaintEvent& evt); - void Event_OnResize(wxSizeEvent& evt); - void Event_OnMouseLeftUp(wxMouseEvent& evt); - void Event_OnMouseLeftDown(wxMouseEvent& evt); - void Event_OnMouseRightUp(wxMouseEvent& evt); - void Event_OnMouseRightDown(wxMouseEvent& evt); - void Event_OnMouseMiddleUp(wxMouseEvent& evt); - void Event_OnMouseMiddleDown(wxMouseEvent& evt); - void Event_OnMouseMove(wxMouseEvent& evt); - void Event_OnMouseWheel(wxMouseEvent& evt); + // Policy configuration + MinimumPolicy = 51, // Minimum attribute policy + MaximumPolicy = 52, // Maximum attribute policy + ClosestPolicy = 74, // Closest match policy + // Advanced configuration + OpenGLProfile = 99, // OpenGL profile selection + ScreenMask = 84, // Screen mask for multi-display + VirtualScreenCount = 128, // Number of virtual screens +}; - protected: // User Overrides - virtual void OnRender() {}; // Called via wxWidgets ->Refresh() +// Backward compatibility +static constexpr int NSOpenGLPFADoubleBuffer = static_cast(NSOpenGLPixelFormatAttribute::DoubleBuffer); +static constexpr int NSOpenGLPFAColorSize = static_cast(NSOpenGLPixelFormatAttribute::ColorSize); +static constexpr int NSOpenGLPFADepthSize = static_cast(NSOpenGLPixelFormatAttribute::DepthSize); +static constexpr int NSOpenGLPFAAccelerated = static_cast(NSOpenGLPixelFormatAttribute::Accelerated); +static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFormatAttribute::OpenGLProfile); +static constexpr int NSOpenGLPFASampleBuffers = static_cast(NSOpenGLPixelFormatAttribute::SampleBuffers); +static constexpr int NSOpenGLPFAMultisample = static_cast(NSOpenGLPixelFormatAttribute::Multisample); +static constexpr int NSOpenGLAllowOfflineRenderers = static_cast(NSOpenGLPixelFormatAttribute::AllowOfflineRenderers); +// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning - public: - virtual void OnCreate() {}; - virtual void OnUpdate(const float fElapsedTime) {}; +// enum for OpenGL profile versions +enum class NSOpenGLProfile : int { + VersionLegacy = 0x1000, // Legacy OpenGL (deprecated) + Version3_2Core = 0x3200, // OpenGL 3.2 Core Profile + Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile +}; - // Mouse Handlers - virtual void OnMouseMiddleUp(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseMiddleDown(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseLeftUp(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseLeftDown(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseRightUp(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseRightDown(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseMove(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - virtual void OnMouseWheel(const olc::vf2d& vWorldPos, const bool bShift, const bool bControl) {}; - }; -} -#endif +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; +// Backward compatibility +//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); +static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); -#if OLC_GPU == OLC_GPU_NONE +// enum for window appearance and behavior bit flags +enum class NSWindowStyleMask : uint16_t { + Titled = (1 << 0), // Window has a title bar + Closable = (1 << 1), // Window can be closed + Miniaturizable = (1 << 2), // Window can be minimized + Resizable = (1 << 3), // Window can be resized + UtilityWindow = (1 << 4), // Utility window style + DocModalWindow = (1 << 6), // Document-modal window + NonactivatingPanel = (1 << 7), // Non-activating panel + HUDWindow = (1 << 13), // Heads-up display window + TexturedBackground = (1 << 8), // Textured background + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view +}; -#if !defined(PGE_RENDERER_NONE_DECLARED) -namespace olc::gpu -{ - class Shader_None : public olc::gpu::Shader - { - public: - std::string Compile() override; - int32_t CreateUniform(const std::string& name) override; - }; - - class Renderer_None : public olc::gpu::Renderer - { - public: // Device Stuff - // Constructs a GPU Device interface - bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; - // Destroys a GPU device interface - bool DestroyDevice() override; - // If applicable, relocate the rendering context - bool RetargetDevice(std::vector os_win_id) override; - // Prepare an OS rendering target - bool PrepareWindowTarget(std::vector os_win_id) override; +// Backward compatibility +// static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); // Temp remove unused variable warning +static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); +// enum for backing store types +enum class NSBackingStoreType : uint8_t { + Retained = 0, // Deprecated - backing store is retained + Nonretained = 1, // Deprecated - backing store is not retained + Buffered = 2 // Modern - double-buffered backing store (recommended) +}; - public: // Texture Resource Stuff - // Allocates a new texture resource in VRAM, returns handle - uint32_t CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg = olc::ImageConfig()) override; - // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM - bool WriteTexture(const uint32_t texid, olc::Image& image) override; - // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM - bool ReadTexture(const uint32_t texid, olc::Image& image) override; - // Destroys and releases texture resource for given handle - bool DeleteTexture(const uint32_t texid) override; - // Makes active the given texture resource (for subsequent sampling operations) - bool AssignTextureSource(const uint32_t slot, const uint32_t texid) override; - // Makes active the given texture resource (for subsequent rendering operations) - bool AssignTextureTarget(const uint32_t slot, const uint32_t texid) override; - // Resolves an MSAA texture into a normal texture - bool ResolveMSAA(const uint32_t msaaTexId) override; +static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); - public: // Shader Construction Stuff - // Change the shader used for subsequent GPU drawing tasks - bool ApplyShader(const Shader& shader) override; - // Reset to default shader for subsequent GPU drawing tasks - bool ApplyDefaultShader() override; - // Set uniform variable for subsequent GPU drawing tasks - bool SetUniform(const std::string& name, const float value) override; - // Set uniform variable for subsequent GPU drawing tasks - bool SetUniform(const std::string& name, const olc::vf2d& value) override; - // Set uniform variable for subsequent GPU drawing tasks - bool SetUniform(const std::string& name, const olc::Pixel value) override; +// enum for window creation timing options +enum class NSWindowDefer : uint8_t { + CreateNow = 0, // Create window immediately (NO/false) + DeferCreation = 1 // Defer window creation (YES/true) +}; - public: // GPU Task Stuff - bool DoGPUTask(const olc::GPUTask& task) override; +// Backward compatibility +static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); - public: // Swap Chain Stuff - // Clears the viewport to a specific colour and depth - bool ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) override; - // Sets the viewport area of the drawing space - bool SetViewport(const olc::vf2d& pos, const olc::vf2d& size) override; - // Configures defaults prior to drawing - bool DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) override; - // Displays the final output - bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; - protected: - Shader_None shaderDefault; - }; -} +// enum for view autoresizing behavior flags +enum class NSAutoresizingMask : uint8_t { + NotSizable = 0, // View maintains fixed size + MinXMargin = 1, // Left margin is flexible + WidthSizable = 2, // Width changes with superview width + MaxXMargin = 4, // Right margin is flexible + MinYMargin = 8, // Bottom margin is flexible + HeightSizable = 16, // Height changes with superview height + MaxYMargin = 32 // Top margin is flexible +}; + +// Backward compatibility +static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); +static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); +// enum for application activation and UI visibility policies +enum class NSApplicationActivationPolicy : uint8_t { + Regular = 0, // Normal app with dock icon and menu bar Recommended + Accessory = 1, // Utility app, no dock icon but can have menu bar + Prohibited = 2 // Background only, no UI elements +}; +// Backward compatibility +static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); -#define PGE_RENDERER_NONE_DECLARED 1 -#endif -#endif +// Mouse Tracking const +static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; +static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; +static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; +/* +* WARNING: Global delegate pointers for Objective-C callbacks +* These global pointers are used to route Objective-C delegate callbacks +* to the appropriate C++ structures and methods. +* +* In short: Don't mess with these unless you know what you're doing! +* +* Global variables are evil, but sometimes necessary for bridging C++ and Objective-C. +*/ +static Window* gptrNSWindowEvents = nullptr; +static Application* gptrApplicationDelegate = nullptr; +static Window* gptrWindowDelegate = nullptr; -#if OLC_GPU == OLC_GPU_OPENGL33 +// Handles NSApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; -#if OLC_HOST == OLC_HOST_WINDOWS - #include - #pragma comment(lib, "gdi32.lib") - #pragma comment(lib, "opengl32.lib") -#if defined(__MINGW32__) || defined(__MINGW64__) - #include -#else - #include -#endif - #define CALLSTYLE __stdcall - // ooof... was getting a bunch of spurious C4191 from MSVC 17.14.9, so round trip via void-town - #define OGL_LOAD(t) reinterpret_cast(reinterpret_cast(wglGetProcAddress(#t))) -#endif +// Custom NSOpenGLView subclass for handling input events +struct CustomOpenGLView { + Class isa; // Objective-C class pointer (required) + void* renderer; // C Pointer to associated renderer +}; -#if OLC_HOST == OLC_HOST_LINUX_X11 - #include - #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) -#endif +// Handles NSWindow delegate methods and window events +struct WindowDelegate { + Class isa; // Objective-C class pointer (required) + void* renderer; // Pointer to associated renderer +}; -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - #include - #include +// Application management with member initialization +struct Application { + id nsApp{nullptr}; // NSApplication instance + id delegate{nullptr}; // Application delegate instance - #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) -#endif + // Callback function pointers + std::function willFinishLaunchingCallback{nullptr}; + std::function didFinishLaunchingCallback{nullptr}; + std::function willTerminateCallback{nullptr}; + std::function didBecomeActiveCallback{nullptr}; + std::function willResignActiveCallback{nullptr}; -#if OLC_HOST == OLC_HOST_MACOS - #define GL_SILENCE_DEPRECATION // Stops MacOS & iOS whining about OpenGL is deprecated and we should use Metal - #define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED // Stops MacOS & iOS whining about OpenGL even more - #define CALLSTYLE - #define OGL_LOAD(t) &::t - #define GL_GLEXT_PROTOTYPES - #undef GL_CLAMP - #define GL_CLAMP GL_CLAMP_TO_EDGE - #include // Correct issue with Unknown type name 'ptrdiff_t' - #include - #include - #include - #include -#endif + void* willFinishLaunchingUserData{nullptr}; // User data for will finish launching callback + void* didFinishLaunchingUserData{nullptr}; // User data for did finish launching callback + void* willTerminateUserData{nullptr}; // User data for will terminate callback + void* didBecomeActiveUserData{nullptr}; // User data for did become active callback + void* willResignActiveUserData{nullptr}; // User data for will resign active callback -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - #include - #include - #include - #define GL_GLEXT_PROTOTYPES - #include - #include - #define CALLSTYLE - #undef GL_CLAMP - #define GL_CLAMP GL_CLAMP_TO_EDGE + // Method function pointers + void (*initialize) (struct Application* self){nullptr}; + void (*activate) (struct Application* self){nullptr}; + void (*run) (struct Application* self){nullptr}; + void (*destroy) (struct Application* self){nullptr}; + const char* (*getSystemLocale)(struct Application* self){nullptr}; + + Application() = default; + + ~Application() {} + + // Delete copy constructor and assignment + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + Application(Application&&) = default; + Application& operator=(Application&&) = default; +}; - #define OGL_LOAD(t) ::t -#endif +// Window management with member initialization +struct Window { + id nsWindow{nullptr}; // NSWindow instance + id delegate{nullptr}; // Window delegate instance + OpenGLRenderer* renderer{nullptr}; // Associated OpenGL renderer + NSRect windowFrame{}; // Window frame rectangle + NSRect contentViewFrame{}; // Content view frame rectangle + const char* title{nullptr}; // Window title string -#if OLC_HOST == OLC_HOST_ANDROID - #include - #include - #define GL_GLEXT_PROTOTYPES - #include - #define CALLSTYLE - #undef GL_CLAMP - #define GL_CLAMP GL_CLAMP_TO_EDGE - #define GL_LINE 0 - #define GL_FILL 0 - #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) -#endif + // Event callback function pointers with nullptr initialization + void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*flagsChangedCallback) (unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseDraggedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*rightMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*rightMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*rightMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*otherMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*otherMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*otherMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseEnteredCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseExitedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*scrollWheelCallback) (double x, double y, double deltaX, double deltaY, unsigned int modifierFlags, void* userData){nullptr}; -#if OLC_HOST == OLC_HOST_WXWIDGETS + // Touch event callback function pointers with nullptr initialization + void (*touchBeganCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchMovedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchEndedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchCancelledCallback)(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void* touchUserData{nullptr}; + // Stylus (tablet) event callback function pointer + void (*stylusCallback)(uint32_t touchID, double x, double y, + float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData){nullptr}; + void* stylusUserData{nullptr}; + bool bStylusInProximity{false}; // true while stylus is hovering near the surface + + void* eventUserData{nullptr}; // User data for event callbacks + BOOL acceptsInputEvents{NO}; // Whether the window accepts input events + + // Window delegate event callback function pointers with nullptr initialization + void (*windowDidResizeCallback) (void* userData){nullptr}; + void (*windowWillCloseCallback) (void* userData){nullptr}; + void (*windowDidBecomeKeyCallback) (void* userData){nullptr}; + void (*windowDidResignKeyCallback) (void* userData){nullptr}; + void (*windowDidMiniaturizeCallback) (void* userData){nullptr}; + void (*windowDidDeminiaturizeCallback)(void* userData){nullptr}; -#endif + void* windowDidResizeUserData{nullptr}; // User data for window resize callback + void* windowWillCloseUserData{nullptr}; // User data for window will close callback + void* windowDidBecomeKeyUserData{nullptr}; // User data for window did become key callback + void* windowDidResignKeyUserData{nullptr}; // User data for window did resign key callback + void* windowDidMiniaturizeUserData{nullptr}; // User data for window did miniaturize callback + void* windowDidDeminiaturizeUserData{nullptr}; // User data for window did deminiaturize callback + + // Method function pointers with nullptr initialization + 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}; + void (*removeDelegate) (struct Window* self){nullptr}; + const char* (*getTitle) (const struct Window* self){nullptr}; + void (*setTitle) (struct Window* self, const char* title){nullptr}; -#if !defined(CALLSTYLE) - #define CALLSTYLE -#endif + void (*setWindowSize) (struct Window* self, double width, double height){nullptr}; + void (*getWindowSize) (const struct Window* self, double* width, double* height){nullptr}; + void (*setWindowPosition) (struct Window* self, double x, double y){nullptr}; + void (*getWindowPosition) (const struct Window* self, double* x, double* y){nullptr}; + void (*setWindowFrame) (struct Window* self, double x, double y, double width, double height){nullptr}; + void (*getWindowFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; + void (*getCurrentFrame) (struct Window* self){nullptr}; + void (*getContentViewFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; + void (*setContentViewFrame) (struct Window* self, double* x, double* y, double* width, double* height){nullptr}; +}; +// Modern OpenGL context management and rendering operations +struct OpenGLRenderer { + id pixelFormat{nullptr}; // NSOpenGLPixelFormat instance + id glView{nullptr}; // NSOpenGLView instance + id glContext{nullptr}; // NSOpenGLContext instance + unsigned int MSAA_Samples{OLC_MSAA_SAMPLES}; // Multisample anti-aliasing samples + Window* window{nullptr}; // Associated window pointer + // Method function pointers with nullptr initialization + void (*initialize) (struct OpenGLRenderer* self, Window* window){nullptr}; + void (*setupContext) (struct OpenGLRenderer* self){nullptr}; + void (*renderYellowBackground)(struct OpenGLRenderer* self){nullptr}; + void (*renderTexturedQuad) (struct OpenGLRenderer* self, unsigned int textureID){nullptr}; + void* (*getOpenGLContext) (const struct OpenGLRenderer* self){nullptr}; + void* (*getCGLContextObj) (struct OpenGLRenderer* self){nullptr}; + void* (*getCGLContextObjPtr) (struct OpenGLRenderer* self){nullptr}; + void (*makeCurrentContext) (struct OpenGLRenderer* self){nullptr}; + void (*setVsync) (struct OpenGLRenderer* self, BOOL enabled){nullptr}; + void (*destroy) (struct OpenGLRenderer* self){nullptr}; + bool (*resetContextForSize) (struct OpenGLRenderer* self, double width, double height){nullptr}; -namespace olc -{ - namespace apis::opengl - { +}; - typedef char GLchar; - typedef ptrdiff_t GLsizeiptr; +// Modern image loading and pixel data extraction +struct ImageLoader { + 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) + int bytesPerRow{kZeroRows}; // Number of bytes per row + BOOL hasAlpha{NO}; // Whether image has alpha channel -#if OLC_HOST == OLC_HOST_WINDOWS - // "Target Host Window Handle" - typedef HDC glDeviceContext_t; - // "State of OpenGL Machinary" - typedef HGLRC glRenderContext_t; - // Johnnyg63: moved glShaderSource_t definition for Windows, as it does not match macOS and android signatures - typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); -#endif - -#if OLC_HOST == OLC_HOST_MACOS - typedef void* glDeviceContext_t; - typedef CGLContextObj glRenderContext_t; - typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); - typedef X11::GLXContext glDeviceContext_t; - typedef X11::GLXContext glRenderContext_t; -#endif + // 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}; + void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; + BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; + BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; + unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; +}; -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID - typedef void CALLSTYLE glShaderSource_t(GLuint shader, GLsizei size, const GLchar *const * string, const GLint * length); - typedef void glDeviceContext_t; - typedef struct - { - EGLDisplay display; - EGLContext context; - EGLSurface surface; - EGLConfig config; - } glRenderContext_t; -#endif +// Called when application is about to finish launching +void applicationWillFinishLaunching(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; // Remove unused parameter warnings + if (gptrApplicationDelegate && gptrApplicationDelegate->willFinishLaunchingCallback) { + gptrApplicationDelegate->willFinishLaunchingCallback(gptrApplicationDelegate->willFinishLaunchingUserData); + } +} - typedef GLuint CALLSTYLE glCreateShader_t(GLenum type); - typedef GLuint CALLSTYLE glCreateProgram_t(void); - - typedef void CALLSTYLE glDeleteShader_t(GLuint shader); - typedef void CALLSTYLE glCompileShader_t(GLuint shader); - typedef void CALLSTYLE glLinkProgram_t(GLuint program); - typedef void CALLSTYLE glDeleteProgram_t(GLuint program); - typedef void CALLSTYLE glAttachShader_t(GLuint program, GLuint shader); - typedef void CALLSTYLE glBindBuffer_t(GLenum target, GLuint buffer); - typedef void CALLSTYLE glBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); - typedef void CALLSTYLE glGenBuffers_t(GLsizei n, GLuint* buffers); - typedef void CALLSTYLE glVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); - typedef void CALLSTYLE glEnableVertexAttribArray_t(GLuint index); - typedef void CALLSTYLE glUseProgram_t(GLuint program); - typedef void CALLSTYLE glBindVertexArray_t(GLuint array); - typedef void CALLSTYLE glGenVertexArrays_t(GLsizei n, GLuint* arrays); - typedef void CALLSTYLE glGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); - typedef GLint CALLSTYLE glGetUniformLocation_t(GLuint program, const GLchar* name); - typedef void CALLSTYLE glUniform1f_t(GLint location, GLfloat v0); - typedef void CALLSTYLE glUniform1i_t(GLint location, GLint v0); - typedef void CALLSTYLE glUniform2fv_t(GLint location, GLsizei count, const GLfloat* value); - typedef void CALLSTYLE glUniform4fv_t(GLint location, GLsizei count, const GLfloat* value); - typedef void CALLSTYLE glUniformMatrix4fv_t(GLint location, GLsizei count, GLboolean trasnpose, const GLfloat* value); - typedef void CALLSTYLE glActiveTexture_t(GLenum texture); - typedef void CALLSTYLE glGenFramebuffers_t(GLsizei n, GLuint* ids); - typedef void CALLSTYLE glBindFramebuffer_t(GLenum target, GLuint fb); - typedef GLenum CALLSTYLE glCheckFramebufferStatus_t(GLenum target); - typedef void CALLSTYLE glDeleteFramebuffers_t(GLsizei n, const GLuint* fbs); - typedef void CALLSTYLE glFramebufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); - typedef void CALLSTYLE glDrawBuffers_t(GLsizei n, const GLenum* bufs); - typedef void CALLSTYLE glBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); - typedef void CALLSTYLE glTexImage2DMultisample_t(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); - typedef void CALLSTYLE glBlitFramebuffer_t(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - typedef void CALLSTYLE glGenRenderbuffers_t(GLsizei n, GLuint* renderbuffers); - typedef void CALLSTYLE glBindRenderbuffer_t(GLenum target, GLuint renderbuffer); - typedef void CALLSTYLE glRenderbufferStorageMultisample_t(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); - typedef void CALLSTYLE glFramebufferRenderbuffer_t(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); - typedef void CALLSTYLE glDeleteRenderbuffers_t(GLsizei n, const GLuint* renderbuffers); - typedef void CALLSTYLE glGetInternalformativ_t(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); - typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); - typedef void CALLSTYLE glGetIntegerv_t(GLenum pname, GLint *data); - typedef void CALLSTYLE glGetRenderbufferParameteriv_t(GLenum target, GLenum pname, GLint* params); - typedef void CALLSTYLE glRenderbufferStorage_t(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +// Called when application has finished launching +void applicationDidFinishLaunching(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { + gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); + } +} -#if OLC_HOST == OLC_HOST_WINDOWS - typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); -#endif +// Called when application is about to terminate +void applicationWillTerminate(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->willTerminateCallback) { + gptrApplicationDelegate->willTerminateCallback(gptrApplicationDelegate->willTerminateUserData); + } +} -#if OLC_HOST == OLC_HOST_LINUX_X11 - typedef GLint CALLSTYLE glXSwapIntervalEXT_t(X11::Display* dpy, X11::GLXDrawable drawable, GLint interval); -#endif +// Called when application becomes active +void applicationDidBecomeActive(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->didBecomeActiveCallback) { + gptrApplicationDelegate->didBecomeActiveCallback(gptrApplicationDelegate->didBecomeActiveUserData); + } +} - // A little GL class (singleton) - class gl - { - public: - static gl& Get(); - inline bool HasLoaded() const { return bLoaded; } +// Called when application resigns active status +void applicationWillResignActive(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->willResignActiveCallback(gptrApplicationDelegate->willResignActiveUserData); + } +} - private: - gl() = default; - static bool bLoaded; +// Create and configure application delegate +id createApplicationDelegate() { - private: - bool LoadAll(); + // Get NSObject class as superclass using const string + Class NSObjectClass = objc_getClass(kNSObjectClass); + + // Create a new class inheriting from NSObject using const string + Class AppDelegateClass = objc_allocateClassPair(NSObjectClass, kAppDelegateClass, 0); + + // Add application delegate methods + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillFinishLaunchingSel, (IMP)applicationWillFinishLaunching, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidFinishLaunchingSel, (IMP)applicationDidFinishLaunching, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillTerminateSel, (IMP)applicationWillTerminate, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidBecomeActiveSel, (IMP)applicationDidBecomeActive, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillResignActiveSel, (IMP)applicationWillResignActive, kEventHandlerMethodTypeEncoding); - protected: - glCreateShader_t* _glCreateShader = nullptr; - glCreateProgram_t* _glCreateProgram = nullptr; - glShaderSource_t* _glShaderSource = nullptr; - glDeleteShader_t* _glDeleteShader = nullptr; - glCompileShader_t* _glCompileShader = nullptr; - glLinkProgram_t* _glLinkProgram = nullptr; - glDeleteProgram_t* _glDeleteProgram = nullptr; - glAttachShader_t* _glAttachShader = nullptr; - glBindBuffer_t* _glBindBuffer = nullptr; - glBufferData_t* _glBufferData = nullptr; - glGenBuffers_t* _glGenBuffers = nullptr; - glVertexAttribPointer_t* _glVertexAttribPointer = nullptr; - glEnableVertexAttribArray_t* _glEnableVertexAttribArray = nullptr; - glUseProgram_t* _glUseProgram = nullptr; - glBindVertexArray_t* _glBindVertexArray = nullptr; - glGenVertexArrays_t* _glGenVertexArrays = nullptr; - glGetShaderInfoLog_t* _glGetShaderInfoLog = nullptr; - glGetUniformLocation_t* _glGetUniformLocation = nullptr; - glUniform1f_t* _glUniform1f = nullptr; - glUniform1i_t* _glUniform1i = nullptr; - glUniform2fv_t* _glUniform2fv = nullptr; - glUniform4fv_t* _glUniform4fv = nullptr; - glUniformMatrix4fv_t* _glUniformMatrix4fv = nullptr; - glActiveTexture_t* _glActiveTexture = nullptr; - glGenFramebuffers_t* _glGenFramebuffers = nullptr; - glBindFramebuffer_t* _glBindFramebuffer = nullptr; - glCheckFramebufferStatus_t* _glCheckFramebufferStatus = nullptr; - glDeleteFramebuffers_t* _glDeleteFramebuffers = nullptr; - glFramebufferTexture2D_t* _glFramebufferTexture2D = nullptr; - glDrawBuffers_t* _glDrawBuffers = nullptr; - glBlendFuncSeparate_t* _glBlendFuncSeparate = nullptr; - glTexImage2DMultisample_t* _glTexImage2DMultisample = nullptr; - glBlitFramebuffer_t* _glBlitFramebuffer = nullptr; - glGenRenderbuffers_t* _glGenRenderbuffers = nullptr; - glBindRenderbuffer_t* _glBindRenderbuffer = nullptr; - glRenderbufferStorageMultisample_t* _glRenderbufferStorageMultisample = nullptr; - glFramebufferRenderbuffer_t* _glFramebufferRenderbuffer = nullptr; - glDeleteRenderbuffers_t* _glDeleteRenderbuffers = nullptr; - glGetInternalformativ_t* _glGetInternalformativ = nullptr; - glGetShaderiv_t* _glGetShaderiv = nullptr; - glGetIntegerv_t *_glGetIntegerv = nullptr; - glGetRenderbufferParameteriv_t* _glGetRenderbufferParameteriv = nullptr; - glRenderbufferStorage_t* _glRenderbufferStorage = nullptr; -#if OLC_HOST == OLC_HOST_WINDOWS - wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; -#endif + // Register the class with the runtime + objc_registerClassPair(AppDelegateClass); + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(AppDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + return delegate; +} -#if OLC_HOST == OLC_HOST_LINUX_X11 - public: - glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; -#endif +//====================================================================// +// Keyboard Event Handling - public: - // Proxies allow switchable, clutter-free error checking +// Structure to hold common key event data +struct KeyEventData { + unsigned short keyCode = 0; + const char* characters = nullptr; + unsigned int modifierFlags = 0; +}; - // OpenGL3.3 Proxies +// Extract common key event data from NSEvent +KeyEventData extractKeyEventData(id event) { + KeyEventData data; + data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); + id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); + data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + return data; +} - GLuint glCreateShader(GLenum type); - GLuint glCreateProgram(void); - void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); - void glDeleteShader(GLuint shader); - void glCompileShader(GLuint shader); - void glLinkProgram(GLuint program); - void glDeleteProgram(GLuint program); - void glAttachShader(GLuint program, GLuint shader); - void glBindBuffer(GLenum target, GLuint buffer); - void glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage); - void glGenBuffers(GLsizei n, GLuint* buffers); - void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); - void glEnableVertexAttribArray(GLuint index); - void glUseProgram(GLuint program); - void glBindVertexArray(GLuint array); - void glGenVertexArrays(GLsizei n, GLuint* arrays); - void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); - GLint glGetUniformLocation(GLuint program, const GLchar* name); - void glUniform1f(GLint location, GLfloat v0); - void glUniform1i(GLint location, GLint v0); - void glUniform2fv(GLint location, GLsizei count, const GLfloat* value); - void glUniform4fv(GLint location, GLsizei count, const GLfloat* value); - void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean trasnpose, const GLfloat* value); - void glActiveTexture(GLenum texture); - void glGenFramebuffers(GLsizei n, GLuint* ids); - void glBindFramebuffer(GLenum target, GLuint fb); - GLenum glCheckFramebufferStatus(GLenum target); - void glDeleteFramebuffers(GLsizei n, const GLuint* fbs); - void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); - void glDrawBuffers(GLsizei n, const GLenum* bufs); - void glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); - void glTexImage2DMultisample(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); - void glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); - void glGenRenderbuffers(GLsizei n, GLuint* renderbuffers); - void glBindRenderbuffer(GLenum target, GLuint renderbuffer); - void glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); - void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); - void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); - void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); - void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); - void glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params); - void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + // Handle key down events +void view_keyDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; // Remove unused parameter warnings - // OpenGL1.2 Proxies (just keeps things tidy imo) - void glGenTextures(GLsizei n, GLuint* textures); - void glBindTexture(GLenum target, GLuint texture); - void glTexParameteri(GLenum target, GLenum pname, GLint param); - void glTexEnvf(GLenum target, GLenum pname, GLfloat param); - void glDeleteTextures(GLsizei n, const GLuint* textures); - void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels); - void glClear(GLbitfield mask); - void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); - void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); - void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* data); - void glCullFace(GLenum mode); - void glEnable(GLenum cap); - void glDisable(GLenum cap); - void glDrawArrays(GLenum mode, GLint first, GLsizei count); - void glBlendFunc(GLenum sfactor, GLenum dfactor); - void glDepthFunc(GLenum func); - void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); - void glHint(GLenum target, GLenum mode); - void glPolygonMode(GLenum face, GLenum mode); - void glFrontFace(GLenum mode); - void glGetIntegerv(GLenum pname, GLint *data); + KeyEventData data = extractKeyEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - void glSwapInterval(GLsizei n); +// Handle key up events +void view_keyUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - // Constants - static constexpr GLenum GL_FRAMEBUFFER_COMPLETE_X = 0x8CD5; - static constexpr GLenum GL_TEXTURE_2D_MULTISAMPLE_X = 0x9100; - static constexpr GLenum GL_COLOR_ATTACHMENT0_X = 0x8CE0; - static constexpr GLenum GL_TEXTURE0_X = 0x84C0; - static constexpr GLenum GL_READ_FRAMEBUFFER_X = 0x8CA8; - static constexpr GLenum GL_DRAW_FRAMEBUFFER_X = 0x8ca9; - static constexpr GLenum GL_FRAMEBUFFER_X = 0x8D40; - static constexpr GLenum GL_ARRAY_BUFFER_X = 0x8892; - static constexpr GLenum GL_STREAM_DRAW_X = 0x88E0; - static constexpr GLenum GL_DRAW_FRAMEBUFFER_BINDING_X = 0x8CA6; - static constexpr GLenum GL_FRAGMENT_SHADER_X = 0x8B30; - static constexpr GLenum GL_VERTEX_SHADER_X = 0x8B31; - static constexpr GLenum GL_GEOMETRY_SHADER_X = 0x8DD9; - static constexpr GLenum GL_MULTISAMPLE_X = 0x809D; - static constexpr GLenum GL_RENDERBUFFER_X = 0x8D41; - static constexpr GLenum GL_SAMPLES_X = 0x80A9; - static constexpr GLenum GL_COMPILE_STATUS_X = 0x8B81; - static constexpr GLenum GL_INFO_LOG_LENGTH_X = 0x8B84; - static constexpr GLenum GL_DEPTH_COMPONENT24_X = 0x81A6; - static constexpr GLenum GL_DEPTH_ATTACHMENT_X = 0x8D00; - static constexpr GLenum GL_RENDERBUFFER_SAMPLES_X = 0x8CAB; - private: - bool CheckError(const std::source_location loc = std::source_location::current()); + KeyEventData data = extractKeyEventData(event); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - }; - } - -} // olc namespace +void view_flagsChanged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -#if !defined(PGE_RENDERER_OPENGL33_DECLARED) -namespace olc -{ - namespace gpu - { - class Shader_GLSL33 : public olc::gpu::Shader - { - public: - std::string Compile() override; - int32_t CreateUniform(const std::string& name) override; - }; + unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - class Renderer_OGL33 : public olc::gpu::Renderer - { - public: // Device Stuff - // Constructs a GPU Device interface - bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; - // Destroys a GPU device interface - bool DestroyDevice() override; - // If applicable, relocate the rendering context - bool RetargetDevice(std::vector os_win_id) override; - // Prepare an OS rendering target - bool PrepareWindowTarget(std::vector os_win_id) override; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { + gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + } +} +//====================================================================// +// Mouse Event Handling - public: // Texture Resource Stuff - // Allocates a new texture resource in VRAM, returns handle - uint32_t CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg = olc::ImageConfig()) override; - // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM - bool WriteTexture(const uint32_t texid, olc::Image& image) override; - // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM - bool ReadTexture(const uint32_t texid, olc::Image& image) override; - // Destroys and releases texture resource for given handle - bool DeleteTexture(const uint32_t texid) override; - // Makes active the given texture resource (for subsequent sampling operations) - bool AssignTextureSource(const uint32_t slot, const uint32_t texid) override; - // Makes active the given texture resource (for subsequent rendering operations) - bool AssignTextureTarget(const uint32_t slot, const uint32_t texid) override; - // Resolves an MSAA texture into a normal texture - virtual bool ResolveMSAA(const uint32_t msaaTexId) override; +// All values are converted to physical backing pixels so they match the OpenGL +// viewport on HighDPI displays - AppKit reports locations in logical points, but +// PGE is in physical pixels. +void convertToContentViewCoordinates(NSPoint& location) { - public: // Shader Construction Stuff - // Change the shader used for subsequent GPU drawing tasks - bool ApplyShader(const Shader& shader) override; - // Reset to default shader for subsequent GPU drawing tasks - bool ApplyDefaultShader() override; - // Set uniform variable for subsequent GPU drawing tasks - bool SetUniform(const std::string& name, const float value) override; - // Set uniform variable for subsequent GPU drawing tasks - bool SetUniform(const std::string& name, const olc::vf2d& value) override; - // Set uniform variable for subsequent GPU drawing tasks - bool SetUniform(const std::string& name, const olc::Pixel value) override; + if(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] + { + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - public: // GPU Task Stuff - virtual bool DoGPUTask(const olc::GPUTask& task) override; + + // Convert from window coordinates to view coordinates (still logical points) + NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + + // Scale from logical points to physical backing pixels + NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); - public: // Swap Chain Stuff - // Clears the viewport to a specific colour and depth - virtual bool ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) override; - // Sets the viewport area of the drawing space - virtual bool SetViewport(const olc::vf2d& pos, const olc::vf2d& size) override; - // Configures defaults prior to drawing - virtual bool DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) override; - // Displays the final output - virtual bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; + // Get content bounds in physical pixels for Y-flip + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); - - protected: // These may need some thinking about re multiple window - //olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; -#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID - olc::apis::opengl::glRenderContext_t glRenderContext; -#else - olc::apis::opengl::glRenderContext_t glRenderContext = 0; -#endif + // Flip Y coordinate from bottom-left to top-left + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + + } + } + else + { + // Fallback: use default window height + location.y = kDefaultWindowHeight - location.y; + } +} - Shader_GLSL33 shaderDefault; - uint32_t nDefaultVB = 0; - uint32_t nDefaultVA = 0; - uint32_t nDefaultFBO = 0; - uint32_t nScreenFBO = 0; - olc::Image imgBlank; - olc::vf2d vTargetSize; +// Mouse event data structure to hold common mouse event information +struct MouseEventData { + NSPoint location = {0.0, 0.0}; + NSUInteger modifierFlags = 0; + NSInteger buttonNumber = 0; +}; - uint32_t nCurrentTextureTarget = 0; - uint32_t nCurrentTextureSource = 0; +// Extract common mouse event data from NSEvent +MouseEventData extractMouseEventData(id event) { + MouseEventData data; + data.location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + data.modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + data.buttonNumber = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::buttonNumberSel); + + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.location); + return data; +} - //std::unordered_map mapMSAAToResolved; - uint32_t nResolveFBO_Read = 0; - uint32_t nResolveFBO_Draw = 0; +// Stylus event data structure to hold common stylus event information +struct StylusEventData { - std::unordered_map mapTextureSizes; + uint32_t nTabletDeviceID = 0; // Unique identifier for the tablet device + uint8_t nPointerType = 0; // Pointer type 0 = Mouse, 1 = Pen, 2 = Eraser + uint8_t nPointerEventType = 0; // Type of pointer 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + NSPoint nspLocation = {0.0, 0.0}; // Location of the stylus event + double dPressure = 0.0; // Pressure applied by the stylus + double dTangentialPressure = 0.0; // Tangential pressure applied by the stylus + double dRotationRadians = 0.0; // Rotation of the stylus in radians + double dTiltX_radians = 0.0; // Tilt of the stylus in the X direction in radians + double dTiltY_radians = 0.0; // Tilt of the stylus in the Y direction in radians + uint16_t buttonMask = 0; // Bitmask for stylus buttons (MAX 16 buttons, 1 = pressed, 0 = released) - std::unordered_map mapTextureToRenderbuffer; +}; - const Shader* pCurrentShader = nullptr; +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { - uint32_t nDepthRBO = 0; // Shared depth renderbuffer - olc::vi2d vCurrentDepthSize = {0, 0}; // Track current depth buffer size - int32_t nCurrentDepthSamples = 0; // Track current MSAA sample count + StylusEventData data; + id cgEvent = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + data.nTabletDeviceID = (uint32_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventDeviceID); -#if OLC_HOST == OLC_HOST_ANDROID - EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); -#endif + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event - }; - } -} -#define PGE_RENDERER_OPENGL33_DECLARED 1 -#endif -#endif + data.dPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointPressure); + data.dRotationRadians = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventRotation) * (M_PI / 180.0); + data.dTangentialPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventTangentialPressure); -#if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI -#if defined(UNICODE) || defined(_UNICODE) - #define olcT(s) L##s -#else - #define olcT(s) s -#endif + data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); -#define _WINSOCKAPI_ + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); -#if !defined(VC_EXTRALEAN) - #define VC_EXTRALEAN -#endif + // Clamp to safe math bounds (-1.0 to 1.0) to avoid domain errors in asin + tiltX_raw = std::fmax(-1.0, std::fmin(1.0, tiltX_raw)); + tiltY_raw = std::fmax(-1.0, std::fmin(1.0, tiltY_raw)); -#if !defined(NOMINMAX) - #define NOMINMAX -#endif + // Convert to true physical angles in RADIANS -PI/2 to +PI/2 + data.dTiltX_radians = std::asin(tiltX_raw); + data.dTiltY_radians = std::asin(tiltY_raw); -// In Code::Blocks -//#if !defined(_WIN32_WINNT) -// #ifdef HAVE_MSMF -// #define _WIN32_WINNT 0x0600 // Windows Vista -// #else -// #define _WIN32_WINNT 0x0500 // Windows 2000 -// #endif -//#endif + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} + +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) -// Embrace MSVC superiority -#pragma comment(lib, "gdiplus.lib") -#pragma comment(lib, "Shlwapi.lib") -#include -#include -#if !defined(__MINGW32__) && !defined(__MINGW64__) -#include -#endif -#include -#undef _WINSOCKAPI_ +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; -#if !defined(PGE_IMAGELOADER_WINGDI_DECLARED) -namespace olc -{ - namespace imload - { - class ImageLoader_WinGDI : public ImageLoader - { - public: - - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; +// Returns the approximated trackpad touch size in pixels based on the linear force value (0.0 to 1.0) +CGSize getApproxTrackPadTouchSize(float linearForce){ + + CGSize cgTouchSize = {0.0, 0.0}; + const float mmToPoints = 72.0f / 25.4f; // ~2.834 points per mm + float minSizePoints = 2.0f * mmToPoints; // 3mm lower bound approx values from Google + float maxSizePoints = 20.0f * mmToPoints; // 16mm upper bound + + // Interpolate the finger footprint size within our point boundaries + float touchSizeInPoints = minSizePoints + (linearForce * (maxSizePoints - minSizePoints) / 2.0f); - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + // Retrieve the window scale factor (1.0 for Standard, 2.0 for Retina, 3.0 for bannana displays, etc.) + CGFloat pixelScale = 1.0f; + if (gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) { + // Get backing scale factor from the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + pixelScale = ((CGFloat(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::backingScaleFactorSel); + } + } + cgTouchSize = CGSizeMake(touchSizeInPoints * pixelScale, touchSizeInPoints * pixelScale); + return cgTouchSize; +}; - // 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); - }; - } -} +// Mouse event data structure to hold common mouse event information +struct TouchEventData { + uint32_t touchID = 0; // Stable sequential touch ID + NSPoint nspLocation = {0.0,0.0}; // X coordinate in content-view pixel coordinates + double pressure = 0.0; // Pressure value + CGSize nsTouchSize = {0.0, 0.0}; // Touch Size X (not available on macOS trackpad) + CGSize cgDeviceSize = {0.0, 0.0}; // Device size (width, height) in ponits + bool bIsActive = true; // Touch is active (true for Began/Moved, false for Ended/Cancelled) + +}; -#define PGE_IMAGELOADER_WINGDI_DECLARED 1 -#endif -#endif +std::unordered_map sTouchIDEvents; +static std::unordered_map sTouchIDMap; +static uint32_t sNextTouchID = 0; +CGSize cgsApproxTouchSize{0.0, 0.0}; // Stores the approx width and height of a touch event in CG coordinates -#if OLC_IMAGELOADER == OLC_IMAGELOADER_MACOS -#if !defined(PGE_IMAGELOADER_MACOS_DECLARED) -namespace olc +//Iterate through Touches, updates and fires the callback. +static void updateTouchData(id event, NSUInteger phase, + void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) { - namespace imload + if (!callback) return; + + NSUInteger count = 0; + id touchArray = nullptr; + id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); + + // Edge case, there can be an touch event before a pressure event, therefore we need to check if the approximate touch size has been initialized + if(cgsApproxTouchSize.width == 0.0 || cgsApproxTouchSize.height == 0.0) { - class ImageLoader_MacOS : public ImageLoader - { - public: + cgsApproxTouchSize = getApproxTrackPadTouchSize(NSPressureStageNormal); + } + + + if (touchCollection) + { + count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + + // Get current screen the window is on + id currentScreen = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::screenSel); + NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); + + for (NSUInteger i = 0; i < count; ++i) { - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + TouchEventData data; + + // normalizedPosition is NSPoint in [0,1] range on the trackpad surface + NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); + + // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) + data.nspLocation.x = normPos.x * screenFrame.width; + data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; + + // Get Device Size + data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); + // Approximate touch size based on pressure stage (macOS does not provide actual touch size) + data.nsTouchSize = cgsApproxTouchSize; + data.bIsActive = !bRemove; - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + // Assign a stable sequential uint32_t IDs same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + data.touchID = tid; + sTouchIDEvents.emplace(key, data); + callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) + { + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); + } + + } + + } - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + // A bit painful, but we need to check of any keys that have been removed as the touchleave event happened outside of our application + std::vector vFoundKeys; + std::vector vMissingKeys; + touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, NSTouchPhaseAny, nil); + + // If we have a touch collection and the phase is not cancelled, we can check for missing keys, otherwise we will just remove all keys from the map + if (touchCollection && phase != NSTouchPhaseCancelled) + { + count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); + touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); + for (NSUInteger i = 0; i < count; ++i) { + + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + vFoundKeys.push_back(key); - // Store an image as a file asset in memory - bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + } + } + + // Find the missing keys and fire the touchEndedCallback for them + for(auto& [key, data] : sTouchIDEvents) { + + if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { + gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); + vMissingKeys.push_back(key); + } + + } + + // Remove the missing keys from the maps + for(auto& fKeys: vMissingKeys) { + sTouchIDEvents.erase(fKeys); + sTouchIDMap.erase(fKeys); + } + + vFoundKeys.clear(); vMissingKeys.clear(); + + // Finally reset the next touch ID if there are no active touches remaining + if (sTouchIDMap.size() == 0) sNextTouchID = 0; +} - }; +// TODO move to new location +// Handle Force Touch pressure changes on trackpad (single-finger press) +void view_pressureChange(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + auto toNormalizedPressure = [](ForceTouchPressure pressure) -> double { + return static_cast(static_cast(pressure)) / 100.0; + }; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + + // Get CGEvent to determine event subtype + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + + // A force touch trackpad event will have a subtype of 0 (default) or greater than 2 (tablet proximity) + if (nPointerEventType == kCGEventMouseSubtypeDefault || nPointerEventType > kCGEventMouseSubtypeTabletProximity) { + + // KRespondsToSelector + BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + + NSInteger stage = NSPressureStageNormal; + if(supportsStage) + { + // stage returns: 0 = normal click, 1 = light press, 2 = force click + stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); + } + + // Map stage to normalized pressure values as requested + double pressure = toNormalizedPressure(ForceTouchPressure::Normal); + + switch (stage) { + case NSPressureStageLight: + pressure = toNormalizedPressure(ForceTouchPressure::Light); + break; + case NSPressureStageNormal: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + case NSPressureStageForce: + pressure = toNormalizedPressure(ForceTouchPressure::Force); + break; + default: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + } + + // now we need to work out the size + cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); + + for(auto& [key, data] : sTouchIDEvents) { + data.pressure = pressure; + data.nsTouchSize = cgsApproxTouchSize; + } + + } } } -#define PGE_IMAGELOADER_MACOS_DECLARED 1 -#endif -#endif - -#if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG -#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) -#include - -namespace olc::imload -{ - class ImageLoader_LibPNG : public ImageLoader - { - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; +// Handle left mouse down events +void view_mouseDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; // Remove unused parameter warnings + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { + MouseEventData data = extractMouseEventData(event); + gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, + (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + } + else if (nPointerEventType == 1 || nPointerEventType == 2) + { + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + true, false, true, + gptrNSWindowEvents->stylusUserData); - // 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); - }; } -#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 -#endif -#endif +// Handle left mouse up events +void view_mouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER -#include + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); -#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) -namespace olc::imload -{ + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { - // Create an image resource based on an image file asset - class ImageLoader_NDKImageDecoder : public ImageLoader + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] { + gptrNSWindowEvents->mouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, + (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + } + else if(nPointerEventType == 1 || nPointerEventType == 2) { - public: - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; - - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + false, true, true, + gptrNSWindowEvents->stylusUserData); - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + } - // Store an image as a file asset in memory - bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - }; } -#define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 -#endif -#endif - -#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE -#if !defined(PGE_IMAGELOADER_STB_DECLARED) -namespace olc::imload -{ - class ImageLoader_STB_Image : public ImageLoader - { - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; +// Handle mouse drag events +void view_mouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { - // Store an image as a file asset in memory - bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { + MouseEventData data = extractMouseEventData(event); + gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + } + else if (nPointerEventType == 1 || nPointerEventType == 2) + { + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + true, false, true, + gptrNSWindowEvents->stylusUserData); + } - }; } -#define PGE_IMAGELOADER_STB_DECLARED 1 -#endif -#endif +// Handle mouse movement events +void view_mouseMoved(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + // if it is a trackpad event, ignore it. + if (nPointerEventType > 2) return; + + // a Mouse move event and Stlus roximity Event have the same properties. + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + convertToContentViewCoordinates(location); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { + gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } + + +} -#if OLC_HOST == OLC_HOST_MACOS +// Handle right mouse down events +void view_rightMouseDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { + gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// Application consts selectors -static constexpr const char* kRespondsToSelector = "respondsToSelector:"; -static constexpr const char* kNSApplicationClass = "NSApplication"; -static constexpr const char* kNSWindowClass = "NSWindow"; -static constexpr const char* kNSStringClass = "NSString"; -static constexpr const char* kNSOpenGLPixelFormatClass = "NSOpenGLPixelFormat"; -static constexpr const char* kNSOpenGLViewClass = "NSOpenGLView"; -static constexpr const char* kNSObjectClass = "NSObject"; -static constexpr const char* kAppDelegateClass = "AppDelegate"; -static constexpr const char* kWindowDelegateClass = "WindowDelegate"; -static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; -static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; +// Handle right mouse up events +void view_rightMouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { + gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// Application Screen management selectors -//static constexpr const char* kNSScreenClass = "NSScreen"; // Temp remove unused variable warning -static constexpr const char* kScreenSel = "screen"; -static constexpr const char* kNSCursorClass = "NSCursor"; -static constexpr const char* kUnhideSel = "unhide"; -static constexpr const char* kHideSel = "hide"; -static constexpr const char* kIsHiddenSel = "isHidden"; +// Handle right mouse drag events +void view_rightMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { + gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + +} +// Handle other mouse button down events +void view_otherMouseDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { + gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// Application memory management selectors -static constexpr const char* kAllocSel = "alloc"; -static constexpr const char* kInitSel = "init"; -static constexpr const char* kSetDelegateSel = "setDelegate:"; -static constexpr const char* kReleaseSel = "release"; -static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; +// Handle other mouse button up events +void view_otherMouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { + gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// NSApplication lifecycle and management selectors -static constexpr const char* kSharedApplicationSel = "sharedApplication"; -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:"; +void view_otherMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -// NSApplicationDelegate lifecycle methods -static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; -static constexpr const char* kApplicationDidFinishLaunchingSel = "applicationDidFinishLaunching:"; -static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; -static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; -static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + + MouseEventData data = extractMouseEventData(event); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { + gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// NSWindow creation, display, and management selectors -static constexpr const char* kInitWithContentRectSel = "initWithContentRect:styleMask:backing:defer:"; -static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; -static constexpr const char* kSetTitleSel = "setTitle:"; -static constexpr const char* kOrderFrontRegardlessSel = "orderFrontRegardless"; -static constexpr const char* kSetAcceptsMouseMovedEventsSel = "setAcceptsMouseMovedEvents:"; -static constexpr const char* kMakeFirstResponderSel = "makeFirstResponder:"; -static constexpr const char* kMakeKeyAndOrderFrontSel = "makeKeyAndOrderFront:"; -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:"; +// Handle scroll wheel events +void view_scrollWheel(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + double deltaX = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaXSel); + double deltaY = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaYSel); -// NSWindowDelegate lifecycle and event methods selectors -static constexpr const char* kWindowDidResizeSel = "windowDidResize:"; -static constexpr const char* kWindowWillCloseSel = "windowWillClose:"; -static constexpr const char* kWindowDidBecomeKeySel = "windowDidBecomeKey:"; -static constexpr const char* kWindowDidResignKeySel = "windowDidResignKey:"; -static constexpr const char* kWindowDidMiniaturizeSel = "windowDidMiniaturize:"; -static constexpr const char* kWindowDidDeminiaturizeSel = "windowDidDeminiaturize:"; + convertToContentViewCoordinates(location); -// NSResponder keyboard and mouse event methods selectors -static constexpr const char* kKeyDownSel = "keyDown:"; -static constexpr const char* kKeyUpSel = "keyUp:"; -static constexpr const char* kFlagsChangedSel = "flagsChanged:"; -static constexpr const char* kMouseDownSel = "mouseDown:"; -static constexpr const char* kMouseUpSel = "mouseUp:"; -static constexpr const char* kMouseDraggedSel = "mouseDragged:"; -static constexpr const char* kMouseMovedSel = "mouseMoved:"; -static constexpr const char* kRightMouseDownSel = "rightMouseDown:"; -static constexpr const char* kRightMouseUpSel = "rightMouseUp:"; -static constexpr const char* kRightMouseDraggedSel = "rightMouseDragged:"; -static constexpr const char* kOtherMouseDownSel = "otherMouseDown:"; -static constexpr const char* kOtherMouseUpSel = "otherMouseUp:"; -static constexpr const char* kOtherMouseDraggedSel = "otherMouseDragged:"; -static constexpr const char* kScrollWheelSel = "scrollWheel:"; -static constexpr const char* kDeltaXSel = "deltaX"; -static constexpr const char* kDeltaYSel = "deltaY"; -static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; -static constexpr const char* kMouseEnteredSel = "mouseEntered:"; -static constexpr const char* kMouseExitedSel = "mouseExited:"; -static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; -static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; -static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { + gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// Copied a lot of this code from iOS, will need to be tested -// NSResponder touch event method selectors -static constexpr const char* kSetAcceptsTouchEventsSel = "setAcceptsTouchEvents:"; -static constexpr const char* kSetWantsRestingTouchesSel = "setWantsRestingTouches:"; -static constexpr const char* kCGEventSel = "CGEvent"; -static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; -static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; -static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; -static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; +// New events for mouse entered/exited +void view_mouseEntered(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Allow hiding cursor when mouse is inside window + bAllowHideCursor = true; + + // If the cursor is currently hidden, hide it again to ensure it stays hidden while inside the window + if(bHideCursor) + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); -// NSTouch / NSSet data extraction selectors -static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; -static constexpr const char* kAllObjectsSel = "allObjects"; -static constexpr const char* kNormalizedPositionSel = "normalizedPosition"; -static constexpr const char* kDeviceSizeSel = "deviceSize"; -static constexpr const char* kIdentitySel = "identity"; -static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; -static constexpr const char* kTouchCountSel = "count"; + convertToContentViewCoordinates(location); -// NSResponder tablet / stylus event method selectors -static constexpr const char* kTabletPointSel = "tabletPoint:"; -static constexpr const char* kTabletProximitySel = "tabletProximity:"; -static constexpr const char* kPressureChangeSel = "pressureChangeWithEvent:"; -static constexpr const char* kStageSel = "stage"; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { + gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// NSEvent tablet data extraction selectors -static constexpr const char* kPenPressureSel = "pressure"; -static constexpr const char* kPenRotationSel = "rotation"; -static constexpr const char* kPenTiltSel = "tilt"; -static constexpr const char* kPenIsEnteringProximitySel = "isEnteringProximity"; -static constexpr const char* kPointingDeviceTypeSel = "pointingDeviceType"; -static constexpr const char* kPenTangentialPressureSel = "tangentialPressure"; +void view_mouseExited(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Ensure cursor is visible when leaving window + bAllowHideCursor = false; + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + convertToContentViewCoordinates(location); -// Managing first responder status and keyboard focus selectors -static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; -static constexpr const char* kBecomeFirstResponderSel = "becomeFirstResponder"; -static constexpr const char* kCanBecomeKeyViewSel = "canBecomeKeyView"; -static constexpr const char* kNeedsPanelToBecomeKeySel = "needsPanelToBecomeKey"; -static constexpr const char* kDrawRectSel = "drawRect:"; -static constexpr const char* kReshapeSel = "reshape"; -static constexpr const char* kUpdateSel = "update"; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { + gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} -// NSOpenGL pixel format, view, and context management selectors -static constexpr const char* kInitWithAttributesSel = "initWithAttributes:"; -static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame:pixelFormat:"; -static constexpr const char* kSetContentViewSel = "setContentView:"; -static constexpr const char* kContentViewSel = "contentView"; -static constexpr const char* kBoundsSel = "bounds"; -static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru -static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; -static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; -static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; -static constexpr const char* kBackingScaleFactorSel = "backingScaleFactor"; -static constexpr const char* kOpenGLContextSel = "openGLContext"; -static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; -static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; -static constexpr const char* kFlushBufferSel = "flushBuffer"; -static constexpr const char* kDisplaySel = "display"; -static constexpr const char* kCGLContextObjSel = "CGLContextObj"; -static constexpr const char* kSetValuesSel = "setValues:forParameter:"; -// Extracting data from NSEvent objects selectors -static constexpr const char* kKeyCodeSel = "keyCode"; -static constexpr const char* kCharactersSel = "characters"; -static constexpr const char* kLocationInWindowSel = "locationInWindow"; -static constexpr const char* kButtonNumberSel = "buttonNumber"; -static constexpr const char* kClickCountSel = "clickCount"; -static constexpr const char* kModifierFlagsSel = "modifierFlags"; -static constexpr const char* kUTF8StringSel = "UTF8String"; +void view_touchesBegan(id self, SEL _cmd, id event) { + (void)_cmd; + + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseBegan, + gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); + } +} -// NSLocale class and method names -static constexpr const char* kNSLocaleClass = "NSLocale"; -static constexpr const char* kCurrentLocaleSel = "currentLocale"; -static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; +void view_touchesMoved(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseMoved, + gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); + } +} +void view_touchesEnded(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseEnded, + gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); + } +} -// Default values and configuration settings -static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; -static constexpr double kDefaultWindowWidth = 800.0; -static constexpr double kDefaultWindowHeight = 600.0; -// static constexpr int kBitsPerByte = 8; // Temp remove unused variable warning -static constexpr int kMinValidDimension = 0; -static constexpr int kRGBABytesPerPixel = 4; -static constexpr int kFullyOpaque = 255; -static constexpr int kZeroBytes = 0; -static constexpr int kZeroRows = 0; -static constexpr int kZeroWidth = 0; -static constexpr int kZeroHeight = 0; -static constexpr int kFlippedOffset = 1; -static constexpr int kNoButton = -1; -// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning -bool bAllowHideCursor = true; -bool bHideCursor = false; +void view_touchesCancelled(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseCancelled, + gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); + } +} -// Objective-C method type encoding constants -// Type encoding for methods returning BOOL with no parameters: "c@:" -static constexpr const char* kBoolMethodTypeEncoding = "c@:"; +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) -// Type encoding for void methods with event/notification parameter: "v@:@" -static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletProximity(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet proximity events here + BOOL entering = ((BOOL(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::penIsEnteringProximitySel); + // For furture support should call a stylusCallback with proximity event data, but for now just set the flag + gptrNSWindowEvents->bStylusInProximity = (entering == YES); + } +} -// Type encoding for drawRect method with NSRect parameter: "v@:{NSRect={NSPoint=dd}{NSSize=dd}}" -static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"; +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletPoint(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet point events here + } +} -// Type encoding for void methods with no parameters: "v@:" -static constexpr const char* kVoidMethodTypeEncoding = "v@:"; -// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" -static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; +void view_updateTrackingAreas(id self, SEL _cmd) { + (void)self;(void)_cmd; -namespace ObjectiveCSEL { - -// Application memory management selectors - static SEL allocSel = nullptr; - static SEL initSel = nullptr; - static SEL setDelegateSel = nullptr; - static SEL releaseSel = nullptr; - static SEL isKindOfClassSel = nullptr; + // Create new tracking area + NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); + + // Correct tracking options: mouse entered/exited + active in key window + NSUInteger options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; + + Class NSTrackingAreaClass = objc_getClass(kTrackingAreaClass); + id trackingArea = ((id(*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSTrackingAreaClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithRectSel, bounds, options, self, nil); + + ((void(*)(id, SEL, id))objc_msgSend)(self, ObjectiveCSEL::addTrackingAreaSel, trackingArea); +} - // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel = nullptr; - static SEL activateIgnoringOtherAppsSel = nullptr; - static SEL setActivationPolicySel = nullptr; - static SEL runSel = nullptr; - static SEL stopSel = nullptr; - static SEL terminateSEL = nullptr; +// Determine if view can accept first responder status +id view_acceptsFirstResponder(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; // Return YES to accept first responder status +} - // Application Screen management selectors - static SEL screenSel = nullptr; - static SEL unhideSel = nullptr; - static SEL hideSel = nullptr; - static SEL isHiddenSel = nullptr; +// Make the view the first responder +id view_becomeFirstResponder(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; // Return YES to become first responder +} - // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel = nullptr; - static SEL applicationDidFinishLaunchingSel = nullptr; - static SEL applicationWillTerminateSel = nullptr; - static SEL applicationDidBecomeActiveSel = nullptr; - static SEL applicationWillResignActiveSel = nullptr; +// Determine if the view can become key view +id view_canBecomeKeyView(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; +} - // NSWindow creation, display, and management selectors - static SEL initWithContentRectSel = nullptr; - static SEL stringWithUTF8StringSel = nullptr; - static SEL setTitleSel = nullptr; - static SEL orderFrontRegardlessSel = nullptr; - static SEL setAcceptsMouseMovedEventsSel = nullptr; - static SEL makeKeyAndOrderFrontSel = nullptr; - static SEL makeKeyWindowSel = nullptr; - static SEL frameSel = nullptr; - static SEL setFrameDisplaySel = nullptr; - static SEL setFrameSel = nullptr; - static SEL makeFirstResponderSel = nullptr; - static SEL styleMaskSel = nullptr; - static SEL toggleFullScreenSel = nullptr; +// Determine if the view needs the panel to become key +id view_needsPanelToBecomeKey(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; +} - // NSWindowDelegate lifecycle and event methods selectors - static SEL windowDidResizeSel = nullptr; - static SEL windowWillCloseSel = nullptr; - static SEL windowDidBecomeKeySel = nullptr; - static SEL windowDidResignKeySel = nullptr; - static SEL windowDidMiniaturizeSel = nullptr; - static SEL windowDidDeminiaturizeSel = nullptr; +// Empty drawRect method - prevents automatic drawing on main thread +void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { + (void)self;(void)_cmd;(void)dirtyRect; +} - // NSResponder keyboard and mouse event methods selectors - static SEL keyDownSel = nullptr; - static SEL keyUpSel = nullptr; - static SEL flagsChangedSel = nullptr; - static SEL mouseDownSel = nullptr; - static SEL mouseUpSel = nullptr; - static SEL mouseDraggedSel = nullptr; - static SEL mouseMovedSel = nullptr; - static SEL rightMouseDownSel = nullptr; - static SEL rightMouseUpSel = nullptr; - static SEL rightMouseDraggedSel = nullptr; - static SEL otherMouseDownSel = nullptr; - static SEL otherMouseUpSel = nullptr; - static SEL otherMouseDraggedSel = nullptr; - static SEL scrollWheelSel = nullptr; - static SEL deltaXSel = nullptr; - static SEL deltaYSel = nullptr; - static SEL updateTrackingAreasSel = nullptr; - static SEL mouseEnteredSel = nullptr; - static SEL mouseExitedSel = nullptr; +// Empty reshape method - prevents automatic context reshaping on main thread +void view_reshape(id self, SEL _cmd) { + (void)self;(void)_cmd; +} - // Mouse Tracking - static SEL addTrackingAreaSel = nullptr; - static SEL initWithRectSel = nullptr; +// Empty update method - prevents automatic context updates on main thread +void view_update(id self, SEL _cmd) { + (void)self;(void)_cmd; +} - // Touch event selectors - static SEL setAcceptsTouchEventsSel = nullptr; - static SEL setWantsRestingTouchesSel = nullptr; - static SEL cgEventSel = nullptr; - static SEL touchesBeganSel = nullptr; - static SEL touchesMovedSel = nullptr; - static SEL touchesEndedSel = nullptr; - static SEL touchesCancelledSel = nullptr; - static SEL touchesMatchingPhaseSel = nullptr; - static SEL allObjectsSel = nullptr; - static SEL normalizedPositionSel = nullptr; - static SEL deviceSizeSel = nullptr; - static SEL identitySel = nullptr; - static SEL objectAtIndexSel = nullptr; - static SEL touchesCountSel = nullptr; +// Create custom OpenGL view class +Class createCustomOpenGLViewClass() { + Class existingClass = objc_getClass(kCustomOpenGLViewClass); + if (existingClass) { + return existingClass; + } + + // Get NSOpenGLView class as superclass + Class NSOpenGLViewClass = objc_getClass(kNSOpenGLViewClass); + + // Create a new class inheriting from NSOpenGLView + Class CustomViewClass = objc_allocateClassPair(NSOpenGLViewClass, kCustomOpenGLViewClass, 0); + + if (!CustomViewClass) { + return NULL; + } + + // Keyboard event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::keyDownSel, (IMP)view_keyDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::keyUpSel, (IMP)view_keyUp, kEventHandlerMethodTypeEncoding); + + // Flags Changed event handler + class_addMethod(CustomViewClass, ObjectiveCSEL::flagsChangedSel, (IMP)view_flagsChanged, kEventHandlerMethodTypeEncoding); + + // Mouse event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDownSel, (IMP)view_mouseDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseUpSel, (IMP)view_mouseUp, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDraggedSel, (IMP)view_mouseDragged, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseMovedSel, (IMP)view_mouseMoved, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDownSel, (IMP)view_rightMouseDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseUpSel, (IMP)view_rightMouseUp, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDraggedSel, (IMP)view_rightMouseDragged, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDownSel, (IMP)view_otherMouseDown, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseUpSel, (IMP)view_otherMouseUp, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); + + // Touch event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)view_touchesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::pressureChangeSel, (IMP)view_pressureChange, kEventHandlerMethodTypeEncoding); + + // Tablet / stylus event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); + + // Area tracking for Mouse entered/exited event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseExitedSel, (IMP)view_mouseExited, kEventHandlerMethodTypeEncoding); + + // First responder methods + class_addMethod(CustomViewClass, ObjectiveCSEL::acceptsFirstResponderSel, (IMP)view_acceptsFirstResponder, kBoolMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::becomeFirstResponderSel, (IMP)view_becomeFirstResponder, kBoolMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::canBecomeKeyViewSel, (IMP)view_canBecomeKeyView, kBoolMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::needsPanelToBecomeKeySel, (IMP)view_needsPanelToBecomeKey, kBoolMethodTypeEncoding); - // Tablet / stylus event selectors - static SEL tabletPointSel = nullptr; - static SEL tabletProximitySel = nullptr; - static SEL penPressureSel = nullptr; - static SEL penRotationSel = nullptr; - static SEL penTiltSel = nullptr; - static SEL penIsEnteringProximitySel = nullptr; - static SEL pointingDeviceTypeSel = nullptr; - static SEL penTangentialPressureSel = nullptr; - static SEL pressureChangeSel = nullptr; - static SEL stageSel = nullptr; + // DrawRect method + class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); - // Managing first responder status and keyboard focus selectors - static SEL acceptsFirstResponderSel = nullptr; - static SEL becomeFirstResponderSel = nullptr; - static SEL canBecomeKeyViewSel = nullptr; - static SEL needsPanelToBecomeKeySel = nullptr; - static SEL drawRectSel = nullptr; - static SEL reshapeSel = nullptr; - static SEL updateSel = nullptr; + // Block automatic OpenGL operations on main thread + class_addMethod(CustomViewClass, ObjectiveCSEL::reshapeSel, (IMP)view_reshape, kVoidMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::updateSel, (IMP)view_update, kVoidMethodTypeEncoding); + + // Register the class with the runtime + objc_registerClassPair(CustomViewClass); + + return CustomViewClass; +} + +// Forward declaration for C API function used by window event handlers +extern "C" void window_updateFrameFromOSX(Window* self); - // NSOpenGL pixel format, view, and context management selectors - static SEL initWithAttributesSel = nullptr; - static SEL initWithFramePixelFormatSel = nullptr; - static SEL setContentViewSel = nullptr; - static SEL contentViewSel = nullptr; - static SEL boundsSel = nullptr; - static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru - static SEL convertPointToBackingSel = nullptr; - static SEL convertPointFromBackingSel = nullptr; - static SEL convertPointFromViewSel = nullptr; - static SEL openGLContextSel = nullptr; - static SEL makeCurrentContextSel = nullptr; - static SEL setAutoresizingMaskSel = nullptr; - static SEL flushBufferSel = nullptr; - static SEL displaySel = nullptr; - static SEL CGLContextObjSel = nullptr; - static SEL setValuesSel = nullptr; - static SEL backingScaleFactorSel = nullptr; +// handle window resize events +void windowDidResize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; - // Extracting data from NSEvent objects selectors - static SEL keyCodeSel = nullptr; - static SEL charactersSel = nullptr; - static SEL locationInWindowSel = nullptr; - static SEL buttonNumberSel = nullptr; - static SEL clickCountSel = nullptr; - static SEL modifierFlagsSel = nullptr; - static SEL utf8StringSel = nullptr; + if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { - // NSLocale selectors - static SEL currentLocaleSel = nullptr; - static SEL localeIdentifierSel = nullptr; - // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning - // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning + // Update frame data only + window_updateFrameFromOSX(gptrWindowDelegate); - // Initialize all selectors - called once at startup - void initializeSelectors() { - if (allocSel) return; // Already initialized - - // Application memory management selectors - allocSel = sel_registerName(kAllocSel); - initSel = sel_registerName(kInitSel); - setDelegateSel = sel_registerName(kSetDelegateSel); - releaseSel = sel_registerName(kReleaseSel); - isKindOfClassSel = sel_registerName(kIsKindOfClassSel); + // Get the new content view size + double width, height; + window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); - // NSApplication lifecycle and management selectors - sharedApplicationSel = sel_registerName(kSharedApplicationSel); - activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); - setActivationPolicySel = sel_registerName(kSetActivationPolicySel); - runSel = sel_registerName(kRunSel); - stopSel = sel_registerName(kStopSel); - terminateSEL = sel_registerName(kTerminateSel); + if (gptrWindowDelegate->windowDidResizeCallback) { + gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); + } - // Application Screen management selectors - screenSel = sel_registerName(kScreenSel); - unhideSel = sel_registerName(kUnhideSel); - hideSel = sel_registerName(kHideSel); - isHiddenSel = sel_registerName(kIsHiddenSel); + } +} - // NSApplicationDelegate lifecycle methods - applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); - applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); - applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); - applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); - applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); +// handle window will close events +void windowWillClose(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + 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); + } + }); +} - // NSWindow creation, display, and management selectors - initWithContentRectSel = sel_registerName(kInitWithContentRectSel); - stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); - setTitleSel = sel_registerName(kSetTitleSel); - orderFrontRegardlessSel = sel_registerName(kOrderFrontRegardlessSel); - setAcceptsMouseMovedEventsSel = sel_registerName(kSetAcceptsMouseMovedEventsSel); - makeFirstResponderSel = sel_registerName(kMakeFirstResponderSel); - makeKeyAndOrderFrontSel = sel_registerName(kMakeKeyAndOrderFrontSel); - makeKeyWindowSel = sel_registerName(kMakeKeyWindowSel); - 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); - windowDidBecomeKeySel = sel_registerName(kWindowDidBecomeKeySel); - windowDidResignKeySel = sel_registerName(kWindowDidResignKeySel); - windowDidMiniaturizeSel = sel_registerName(kWindowDidMiniaturizeSel); - windowDidDeminiaturizeSel = sel_registerName(kWindowDidDeminiaturizeSel); +// handle window did become key events +void windowDidBecomeKey(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidBecomeKeyCallback) { + gptrWindowDelegate->windowDidBecomeKeyCallback(gptrWindowDelegate->windowDidBecomeKeyUserData); + } +} - // NSResponder keyboard and mouse event methods selectors - keyDownSel = sel_registerName(kKeyDownSel); - keyUpSel = sel_registerName(kKeyUpSel); - flagsChangedSel = sel_registerName(kFlagsChangedSel); - mouseDownSel = sel_registerName(kMouseDownSel); - mouseUpSel = sel_registerName(kMouseUpSel); - mouseDraggedSel = sel_registerName(kMouseDraggedSel); - mouseMovedSel = sel_registerName(kMouseMovedSel); - rightMouseDownSel = sel_registerName(kRightMouseDownSel); - rightMouseUpSel = sel_registerName(kRightMouseUpSel); - rightMouseDraggedSel = sel_registerName(kRightMouseDraggedSel); - otherMouseDownSel = sel_registerName(kOtherMouseDownSel); - otherMouseUpSel = sel_registerName(kOtherMouseUpSel); - otherMouseDraggedSel = sel_registerName(kOtherMouseDraggedSel); - scrollWheelSel = sel_registerName(kScrollWheelSel); - deltaXSel = sel_registerName(kDeltaXSel); - deltaYSel = sel_registerName(kDeltaYSel); - updateTrackingAreasSel = sel_registerName(kUpdateTrackingAreasSel); - mouseEnteredSel = sel_registerName(kMouseEnteredSel); - mouseExitedSel = sel_registerName(kMouseExitedSel); - addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); - initWithRectSel = sel_registerName(kInitWithRectSel); - - // Touch event selectors - setAcceptsTouchEventsSel = sel_registerName(kSetAcceptsTouchEventsSel); - setWantsRestingTouchesSel = sel_registerName(kSetWantsRestingTouchesSel); - cgEventSel = sel_registerName(kCGEventSel); - touchesBeganSel = sel_registerName(kTouchesBeganSel); - touchesMovedSel = sel_registerName(kTouchesMovedSel); - touchesEndedSel = sel_registerName(kTouchesEndedSel); - touchesCancelledSel = sel_registerName(kTouchesCancelledSel); - touchesMatchingPhaseSel = sel_registerName(kTouchesMatchingPhaseSel); - allObjectsSel = sel_registerName(kAllObjectsSel); - normalizedPositionSel = sel_registerName(kNormalizedPositionSel); - deviceSizeSel = sel_registerName(kDeviceSizeSel); - identitySel = sel_registerName(kIdentitySel); - objectAtIndexSel = sel_registerName(kObjectAtIndexSel); - touchesCountSel = sel_registerName(kTouchCountSel); +// handle window did resign key events +void windowDidResignKey(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidResignKeyCallback) { + gptrWindowDelegate->windowDidResignKeyCallback(gptrWindowDelegate->windowDidResignKeyUserData); + } +} - // Tablet / stylus event selectors - tabletPointSel = sel_registerName(kTabletPointSel); - tabletProximitySel = sel_registerName(kTabletProximitySel); - penPressureSel = sel_registerName(kPenPressureSel); - penRotationSel = sel_registerName(kPenRotationSel); - penTiltSel = sel_registerName(kPenTiltSel); - penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); - pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); - penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); - pressureChangeSel = sel_registerName(kPressureChangeSel); - stageSel = sel_registerName(kStageSel); +// Handle window did miniaturize events +void windowDidMiniaturize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidMiniaturizeCallback) { + gptrWindowDelegate->windowDidMiniaturizeCallback(gptrWindowDelegate->windowDidMiniaturizeUserData); + } +} - // Managing first responder status and keyboard focus selectors - acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); - becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); - canBecomeKeyViewSel = sel_registerName(kCanBecomeKeyViewSel); - needsPanelToBecomeKeySel = sel_registerName(kNeedsPanelToBecomeKeySel); - drawRectSel = sel_registerName(kDrawRectSel); - reshapeSel = sel_registerName(kReshapeSel); - updateSel = sel_registerName(kUpdateSel); +// Handle window did deminiaturize events +void windowDidDeminiaturize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrWindowDelegate && gptrWindowDelegate->windowDidDeminiaturizeCallback) { + gptrWindowDelegate->windowDidDeminiaturizeCallback(gptrWindowDelegate->windowDidDeminiaturizeUserData); + } +} - // NSOpenGL pixel format, view, and context management selectors - initWithAttributesSel = sel_registerName(kInitWithAttributesSel); - initWithFramePixelFormatSel = sel_registerName(kInitWithFramePixelFormatSel); - setContentViewSel = sel_registerName(kSetContentViewSel); - contentViewSel = sel_registerName(kContentViewSel); - boundsSel = sel_registerName(kBoundsSel); - convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru - convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel); - convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel); - convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel); - openGLContextSel = sel_registerName(kOpenGLContextSel); - makeCurrentContextSel = sel_registerName(kMakeCurrentContextSel); - setAutoresizingMaskSel = sel_registerName(kSetAutoresizingMaskSel); - flushBufferSel = sel_registerName(kFlushBufferSel); - displaySel = sel_registerName(kDisplaySel); - CGLContextObjSel = sel_registerName(kCGLContextObjSel); - setValuesSel = sel_registerName(kSetValuesSel); - backingScaleFactorSel = sel_registerName(kBackingScaleFactorSel); +// Create and configure window delegate +id createWindowDelegate(OpenGLRenderer* renderer) { + // Get NSObject class as superclass using const string + Class NSObjectClass = objc_getClass(kNSObjectClass); + + // Create a new class inheriting from NSObject using const string + Class WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kWindowDelegateClass, kZeroBytes); + + // Add window delegate methods + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); - // Extracting data from NSEvent objects selectors - keyCodeSel = sel_registerName(kKeyCodeSel); - charactersSel = sel_registerName(kCharactersSel); - locationInWindowSel = sel_registerName(kLocationInWindowSel); - buttonNumberSel = sel_registerName(kButtonNumberSel); - clickCountSel = sel_registerName(kClickCountSel); - modifierFlagsSel = sel_registerName(kModifierFlagsSel); - utf8StringSel = sel_registerName(kUTF8StringSel); + // Register the class with the runtime + objc_registerClassPair(WindowDelegateClass); + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + // Store renderer reference in delegate + struct WindowDelegate* windowDelegate = (struct WindowDelegate*)delegate; + windowDelegate->renderer = renderer; + + return delegate; +} + +// Create a window delegate for general window events +id createWindowDelegateForWindow(void) { + + // Check if WindowDelegate class already exists to avoid duplicate registration + static Class WindowDelegateClass = NULL; + if (!WindowDelegateClass) { + // Get NSObject class as superclass using const string + Class NSObjectClass = objc_getClass(kNSObjectClass); + + // Create a new class inheriting from NSObject using const string + WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kGeneralWindowDelegateClass, kZeroBytes); - // NSLocale selectors - currentLocaleSel = sel_registerName(kCurrentLocaleSel); - localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); - } + if (!WindowDelegateClass) { + return NULL; + } - // Ensures we only initialize selectors once (Thread-safe) - void ensureInitialized() { - static std::once_flag initialized; - std::call_once(initialized, [&]() { - initializeSelectors(); - }); - } -} + // Window delegate methods + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); -// Single initialization function called once at startup -void initialize_macos_api() { - ObjectiveCSEL::initializeSelectors(); + // Register the class with the runtime + objc_registerClassPair(WindowDelegateClass); + } + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + return delegate; } -// Concepts safety templates for Objective-C interactions -template -concept CallbackType = std::is_function_v>; // Checks if T is a function type - -template -concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) + // Forward declarations for C API functions used by internal helpers + extern "C" void window_setDelegate(Window* self, id delegate); -// Autorelease pool management functions extern "C" { - extern void* objc_autoreleasePoolPush(void); - extern void objc_autoreleasePoolPop(void* pool); -} + // Initialize the application + void application_initialize(Application* self) { -// Objective-C autorelease pools -class AutoreleasePool { -public: - AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} - ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } - - AutoreleasePool(const AutoreleasePool&) = delete; - AutoreleasePool& operator=(const AutoreleasePool&) = delete; - AutoreleasePool(AutoreleasePool&&) = delete; - AutoreleasePool& operator=(AutoreleasePool&&) = delete; + // Initialize cached selectors for performance + initialize_macos_api(); + + // Set global application reference for delegate callbacks + gptrApplicationDelegate = self; + + // Get NSApplication + Class NSApplicationClass = objc_getClass(kNSApplicationClass); + self->nsApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, ObjectiveCSEL::sharedApplicationSel); + + // Set activation policy to regular + ((void(*)(id, SEL, NSInteger))objc_msgSend)(self->nsApp, ObjectiveCSEL::setActivationPolicySel, NSApplicationActivationPolicyRegular); + + // Create and set application delegate + self->delegate = createApplicationDelegate(); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::setDelegateSel, self->delegate); + } -private: - void* pool_; -}; + // Activate the application + void application_activate(Application* self) { + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); + } + // Run the application event loop + void application_run(Application* self) { + ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); + } -using NSPoint = CGPoint; -using NSInteger = long; -using NSUInteger = unsigned long; + // 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::stopSel, self->nsApp); + } + } -// Forward declarations -struct Application; -struct Window; -struct OpenGLRenderer; -struct ApplicationDelegate; -struct CustomOpenGLView; -struct WindowDelegate; -struct ImageLoader; + // Destroy the application + void application_destroy(Application* self) { + if (self) { + delete self; + } + } -// enums for OpenGL pixel format and rendering capabilities -enum class NSOpenGLPixelFormatAttribute : uint16_t { - // Renderer selection - AllRenderers = 1, // Use all available renderers - RendererID = 70, // Specific renderer ID - NoRecovery = 72, // No recovery from renderer failures - Accelerated = 73, // Hardware accelerated rendering - AllowOfflineRenderers = 96, // Allow offline renderers - AcceleratedCompute = 97, // Accelerated compute support + // Get system locale identifier + const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); + + return localeIdentifier; + } - // Buffer configuration - TripleBuffer = 3, // Triple buffering support - DoubleBuffer = 5, // Double buffering (recommended) - Stereo = 6, // Stereo buffering for 3D - AuxBuffers = 7, // Number of auxiliary buffers - BackingStore = 76, // Backing store configuration - - // Color and depth configuration - ColorSize = 8, // Total color buffer size (bits) - AlphaSize = 11, // Alpha channel size (bits) - DepthSize = 12, // Depth buffer size (bits) - StencilSize = 13, // Stencil buffer size (bits) - AccumSize = 14, // Accumulation buffer size (bits) - ColorFloat = 58, // Floating-point color buffer - // Antialiasing configuration - Multisample = 59, // Multisampling antialiasing - Supersample = 60, // Supersampling antialiasing - SampleBuffers = 55, // Number of sample buffers - Samples = 56, // Samples per pixel - SampleAlpha = 61, // Sample alpha to coverage - AuxDepthStencil = 57, // Auxiliary depth/stencil buffer + // Ensure window has a delegate for window events + void ensureWindowDelegate(struct Window* self) { - // Policy configuration - MinimumPolicy = 51, // Minimum attribute policy - MaximumPolicy = 52, // Maximum attribute policy - ClosestPolicy = 74, // Closest match policy + // If the windows is created but has no delegate, create and set one + if (self && !self->delegate) { + // Create a window delegate + id delegate = createWindowDelegateForWindow(); + if (delegate) { + // Set it on the window + window_setDelegate(self, delegate); + } + } + } - // Advanced configuration - OpenGLProfile = 99, // OpenGL profile selection - ScreenMask = 84, // Screen mask for multi-display - VirtualScreenCount = 128, // Number of virtual screens -}; + // Application constructor + Application* application_init() { -// Backward compatibility -static constexpr int NSOpenGLPFADoubleBuffer = static_cast(NSOpenGLPixelFormatAttribute::DoubleBuffer); -static constexpr int NSOpenGLPFAColorSize = static_cast(NSOpenGLPixelFormatAttribute::ColorSize); -static constexpr int NSOpenGLPFADepthSize = static_cast(NSOpenGLPixelFormatAttribute::DepthSize); -static constexpr int NSOpenGLPFAAccelerated = static_cast(NSOpenGLPixelFormatAttribute::Accelerated); -static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFormatAttribute::OpenGLProfile); -static constexpr int NSOpenGLPFASampleBuffers = static_cast(NSOpenGLPixelFormatAttribute::SampleBuffers); -static constexpr int NSOpenGLPFAMultisample = static_cast(NSOpenGLPixelFormatAttribute::Multisample); -static constexpr int NSOpenGLAllowOfflineRenderers = static_cast(NSOpenGLPixelFormatAttribute::AllowOfflineRenderers); -// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized -// enum for OpenGL profile versions -enum class NSOpenGLProfile : int { - VersionLegacy = 0x1000, // Legacy OpenGL (deprecated) - Version3_2Core = 0x3200, // OpenGL 3.2 Core Profile - Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile -}; + Application* app = new Application(); -// enum for OpenGL context parameters -enum NSOpenGLContextParameter : int { - NSOpenGLContextParameterSwapInterval = 222, - NSOpenGLContextParameterSurfaceOrder = 235, - NSOpenGLContextParameterSurfaceOpacity = 236, - NSOpenGLContextParameterSurfaceBackingSize = 237 -}; + // Assign the method pointers + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; -// Backward compatibility -//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); -static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); + return app; + } -// enum for window appearance and behavior bit flags -enum class NSWindowStyleMask : uint16_t { - Titled = (1 << 0), // Window has a title bar - Closable = (1 << 1), // Window can be closed - Miniaturizable = (1 << 2), // Window can be minimized - Resizable = (1 << 3), // Window can be resized - UtilityWindow = (1 << 4), // Utility window style - DocModalWindow = (1 << 6), // Document-modal window - NonactivatingPanel = (1 << 7), // Non-activating panel - HUDWindow = (1 << 13), // Heads-up display window - TexturedBackground = (1 << 8), // Textured background - UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar - FullScreen = (1 << 14), // Full-screen window - FullSizeContentView = (1 << 15) // Full-size content view -}; + // Create the NSWindow instance + void window_create(Window* self, unsigned long styleMask) { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized -// Backward compatibility -// static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); // Temp remove unused variable warning -// static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); // Temp remove unused variable warning -// static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); // Temp remove unused variable warning -// static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); // Temp remove unused variable warning -static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); + // Get classes using const strings + Class NSWindowClass = objc_getClass(kNSWindowClass); + Class NSStringClass = objc_getClass(kNSStringClass); -// enum for backing store types -enum class NSBackingStoreType : uint8_t { - Retained = 0, // Deprecated - backing store is retained - Nonretained = 1, // Deprecated - backing store is not retained - Buffered = 2 // Modern - double-buffered backing store (recommended) -}; + // Create window + id windowAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSWindowClass, ObjectiveCSEL::allocSel); + + 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 + + // Set window title using stored title or default + const char* titleToUse = self->title ? self->title : kWindowTitle; + id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, titleToUse); + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); -static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); + // Set the content view frame + NSRect contentFrame = {self->windowFrame.x, self->windowFrame.y, self->windowFrame.width, self->windowFrame.height}; + if (self->nsWindow) { + id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); + } + } -// enum for window creation timing options -enum class NSWindowDefer : uint8_t { - CreateNow = 0, // Create window immediately (NO/false) - DeferCreation = 1 // Defer window creation (YES/true) -}; + window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, + &self->contentViewFrame.width, &self->contentViewFrame.height); + } -// Backward compatibility -static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); + // Show the window and set up event handling + void window_show(Window* self) { -// enum for view autoresizing behavior flags -enum class NSAutoresizingMask : uint8_t { - NotSizable = 0, // View maintains fixed size - MinXMargin = 1, // Left margin is flexible - WidthSizable = 2, // Width changes with superview width - MaxXMargin = 4, // Right margin is flexible - MinYMargin = 8, // Bottom margin is flexible - HeightSizable = 16, // Height changes with superview height - MaxYMargin = 32 // Top margin is flexible -}; + // Show window + ((void(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::orderFrontRegardlessSel); + + // Enable mouse moved events + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setAcceptsMouseMovedEventsSel, YES); + + // Ensure window delegate is created and set + ensureWindowDelegate(self); + if (self->delegate) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, self->delegate); + } + + // Make the window key to ensure it can receive keyboard events + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::makeKeyAndOrderFrontSel, NULL); + } -// Backward compatibility -static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); -static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); + // Destroy the window + void window_destroy(Window* self) { + (void)self; + // NSWindow will be cleaned up by autorelease pool + } -// enum for application activation and UI visibility policies -enum class NSApplicationActivationPolicy : uint8_t { - Regular = 0, // Normal app with dock icon and menu bar Recommended - Accessory = 1, // Utility app, no dock icon but can have menu bar - Prohibited = 2 // Background only, no UI elements -}; + // Window delegate setter + void window_setDelegate(Window* self, id delegate) { + self->delegate = delegate; + + // Set global window reference for delegate callbacks + gptrWindowDelegate = self; + + // Set the delegate on the actual NSWindow + if (self->nsWindow && delegate) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, delegate); + } + } -// Backward compatibility -static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); + // Removes the delegate from the window and clears the reference to prevent accidental use after close + void window_removeDelegate(Window* self) { + + if (self->nsWindow) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, nil); + } + self->delegate = NULL; + + } -// Mouse Tracking const -static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; -static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; -static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; + // Window title getter and setter + const char* window_getTitle(const Window* self) { + return self->title; + } -/* -* WARNING: Global delegate pointers for Objective-C callbacks -* These global pointers are used to route Objective-C delegate callbacks -* to the appropriate C++ structures and methods. -* -* In short: Don't mess with these unless you know what you're doing! -* -* Global variables are evil, but sometimes necessary for bridging C++ and Objective-C. -*/ -static Window* gptrNSWindowEvents = nullptr; -static Application* gptrApplicationDelegate = nullptr; -static Window* gptrWindowDelegate = nullptr; + // Window title setter + void window_setTitle(Window* self, const char* title) { + self->title = title; -// Handles NSApplication delegate methods and lifecycle events -struct ApplicationDelegate { - Class isa; // Objective-C class pointer (required) -}; + if (self->nsWindow) { + Class NSStringClass = objc_getClass(kNSStringClass); -// Custom NSOpenGLView subclass for handling input events -struct CustomOpenGLView { - Class isa; // Objective-C class pointer (required) - void* renderer; // C Pointer to associated renderer -}; + id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); -// Handles NSWindow delegate methods and window events -struct WindowDelegate { - Class isa; // Objective-C class pointer (required) - void* renderer; // Pointer to associated renderer -}; + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); + } + } -// Application management with member initialization -struct Application { - id nsApp{nullptr}; // NSApplication instance - id delegate{nullptr}; // Application delegate instance + // Refresh internal frame representation from actual NSWindow (OSX) + void window_updateFrameFromOSX(Window* self) { + self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); + } - // Callback function pointers - std::function willFinishLaunchingCallback{nullptr}; - std::function didFinishLaunchingCallback{nullptr}; - std::function willTerminateCallback{nullptr}; - std::function didBecomeActiveCallback{nullptr}; - std::function willResignActiveCallback{nullptr}; + // Get the content view size (excludes title bar and borders) + void window_getContentViewFrame(const Window* self, double* x, double* y, double* width, double* height) { + if (!self || !self->nsWindow) { + if (x) *x = kMinValidDimension; + if (y) *y = kMinValidDimension; + if (width) *width = kDefaultWindowWidth; + if (height) *height = kDefaultWindowHeight; + return; + } + + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); + if (!contentView) { + if (x) *x = kMinValidDimension; + if (y) *y = kMinValidDimension; + if (width) *width = kDefaultWindowWidth; + if (height) *height = kDefaultWindowHeight; + return; + } + + // Thank you, Ben the ultimate Guru, + // Get the content view bounds in points (logical coordinates). + // On macOS, AppKit uses points rather than physical pixels. On HighDPI + // displays (that are scaling) a point maps to >1 pixels. + // convertRectToBacking: converts the point-based rect to physical pixels, + // which is what OpenGL expects + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + + if (x) *x = pixelBounds.x; + if (y) *y = pixelBounds.y; + if (width) *width = pixelBounds.width; + if (height) *height = pixelBounds.height; + + } - void* willFinishLaunchingUserData{nullptr}; // User data for will finish launching callback - void* didFinishLaunchingUserData{nullptr}; // User data for did finish launching callback - void* willTerminateUserData{nullptr}; // User data for will terminate callback - void* didBecomeActiveUserData{nullptr}; // User data for did become active callback - void* willResignActiveUserData{nullptr}; // User data for will resign active callback + // Get the content view size (excludes title bar and borders) + void window_setContentViewFrame(struct Window* self, double* x, double* y, double* width, double* height) { + if (!self || !self->nsWindow) { + if (x) *x = kMinValidDimension; + if (y) *y = kMinValidDimension; + if (width) *width = kDefaultWindowWidth; + if (height) *height = kDefaultWindowHeight; + return; + } - // Method function pointers - void (*initialize) (struct Application* self){nullptr}; - void (*activate) (struct Application* self){nullptr}; - void (*run) (struct Application* self){nullptr}; - void (*destroy) (struct Application* self){nullptr}; - const char* (*getSystemLocale)(struct Application* self){nullptr}; - - Application() = default; - - ~Application() {} - - // Delete copy constructor and assignment - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - Application(Application&&) = default; - Application& operator=(Application&&) = default; -}; + // Set the content view frame + NSRect contentFrame = {x ? *x : 0, y ? *y : 0, width ? *width : 0, height ? *height : 0}; + if (self->nsWindow) { + id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); + } + } + } -// Window management with member initialization -struct Window { - id nsWindow{nullptr}; // NSWindow instance - id delegate{nullptr}; // Window delegate instance - OpenGLRenderer* renderer{nullptr}; // Associated OpenGL renderer - NSRect windowFrame{}; // Window frame rectangle - NSRect contentViewFrame{}; // Content view frame rectangle - const char* title{nullptr}; // Window title string + // Get Window frame (x, y, width, height) + void window_getWindowFrame(const Window* self, double* x, double* y, double* width, double* height) { + + window_updateFrameFromOSX(const_cast(self)); - // Event callback function pointers with nullptr initialization - void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; - void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; - void (*flagsChangedCallback) (unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseDraggedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*rightMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*rightMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*rightMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*otherMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*otherMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*otherMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseEnteredCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*mouseExitedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; - void (*scrollWheelCallback) (double x, double y, double deltaX, double deltaY, unsigned int modifierFlags, void* userData){nullptr}; + auto posX = self ? self->windowFrame.x : kMinValidDimension; + auto posY = self ? self->windowFrame.y : kMinValidDimension; + auto w = self ? self->windowFrame.width : kDefaultWindowWidth; + auto h = self ? self->windowFrame.height : kDefaultWindowHeight; + if (x) *x = posX; + if (y) *y = posY; + if (width) *width = w; + if (height) *height = h; + } - // Touch event callback function pointers with nullptr initialization - void (*touchBeganCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void (*touchMovedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void (*touchEndedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void (*touchCancelledCallback)(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; - void* touchUserData{nullptr}; + // Get window position (x, y) + void window_getWindowPosition(const struct Window* self, double* x, double* y) { + window_getWindowFrame(self, x, y, nullptr, nullptr); + } - // Stylus (tablet) event callback function pointer - void (*stylusCallback)(uint32_t touchID, double x, double y, - float pressure, float rotation, - float tiltX, float tiltY, - bool bPress, bool bRelease, bool bIsStylus, - void* userData){nullptr}; - void* stylusUserData{nullptr}; - bool bStylusInProximity{false}; // true while stylus is hovering near the surface - - void* eventUserData{nullptr}; // User data for event callbacks - BOOL acceptsInputEvents{NO}; // Whether the window accepts input events - - // Window delegate event callback function pointers with nullptr initialization - void (*windowDidResizeCallback) (void* userData){nullptr}; - void (*windowWillCloseCallback) (void* userData){nullptr}; - void (*windowDidBecomeKeyCallback) (void* userData){nullptr}; - void (*windowDidResignKeyCallback) (void* userData){nullptr}; - void (*windowDidMiniaturizeCallback) (void* userData){nullptr}; - void (*windowDidDeminiaturizeCallback)(void* userData){nullptr}; + // Get Window size (width, height) + void window_getWindowSize(const Window* self, double* width, double* height) { + window_getWindowFrame(self, nullptr, nullptr, width, height); + } - void* windowDidResizeUserData{nullptr}; // User data for window resize callback - void* windowWillCloseUserData{nullptr}; // User data for window will close callback - void* windowDidBecomeKeyUserData{nullptr}; // User data for window did become key callback - void* windowDidResignKeyUserData{nullptr}; // User data for window did resign key callback - void* windowDidMiniaturizeUserData{nullptr}; // User data for window did miniaturize callback - void* windowDidDeminiaturizeUserData{nullptr}; // User data for window did deminiaturize callback - - // Method function pointers with nullptr initialization - 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}; - void (*removeDelegate) (struct Window* self){nullptr}; - const char* (*getTitle) (const struct Window* self){nullptr}; - void (*setTitle) (struct Window* self, const char* title){nullptr}; + // Set window frame (x, y, width, height) + void window_setWindowFrame(Window* self, double x, double y, double width, double height) { + if (self) { + // Update internal frame representation + self->windowFrame.x = x; + self->windowFrame.y = y; + self->windowFrame.width = width; + self->windowFrame.height = height; - void (*setWindowSize) (struct Window* self, double width, double height){nullptr}; - void (*getWindowSize) (const struct Window* self, double* width, double* height){nullptr}; - void (*setWindowPosition) (struct Window* self, double x, double y){nullptr}; - void (*getWindowPosition) (const struct Window* self, double* x, double* y){nullptr}; - void (*setWindowFrame) (struct Window* self, double x, double y, double width, double height){nullptr}; - void (*getWindowFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; - void (*getCurrentFrame) (struct Window* self){nullptr}; - void (*getContentViewFrame) (const struct Window* self, double* x, double* y, double* width, double* height){nullptr}; - void (*setContentViewFrame) (struct Window* self, double* x, double* y, double* width, double* height){nullptr}; -}; + // Create new NSRect for the frame + NSRect newFrame = {x, y, width, height}; + + // Set the frame on the actual NSWindow + if (self->nsWindow) { + ((void(*)(id, SEL, NSRect, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setFrameDisplaySel, newFrame, YES); + } + } + } -// Modern OpenGL context management and rendering operations -struct OpenGLRenderer { - id pixelFormat{nullptr}; // NSOpenGLPixelFormat instance - id glView{nullptr}; // NSOpenGLView instance - id glContext{nullptr}; // NSOpenGLContext instance - unsigned int MSAA_Samples{OLC_MSAA_SAMPLES}; // Multisample anti-aliasing samples - Window* window{nullptr}; // Associated window pointer + // Set window position (x, y) + void window_setWindowPosition(Window* self, double x, double y) { + window_setWindowFrame(self, x, y, self->windowFrame.width, self->windowFrame.height); + } - // Method function pointers with nullptr initialization - void (*initialize) (struct OpenGLRenderer* self, Window* window){nullptr}; - void (*setupContext) (struct OpenGLRenderer* self){nullptr}; - void (*renderYellowBackground)(struct OpenGLRenderer* self){nullptr}; - void (*renderTexturedQuad) (struct OpenGLRenderer* self, unsigned int textureID){nullptr}; - void* (*getOpenGLContext) (const struct OpenGLRenderer* self){nullptr}; - void* (*getCGLContextObj) (struct OpenGLRenderer* self){nullptr}; - void* (*getCGLContextObjPtr) (struct OpenGLRenderer* self){nullptr}; - void (*makeCurrentContext) (struct OpenGLRenderer* self){nullptr}; - void (*setVsync) (struct OpenGLRenderer* self, BOOL enabled){nullptr}; - void (*destroy) (struct OpenGLRenderer* self){nullptr}; - bool (*resetContextForSize) (struct OpenGLRenderer* self, double width, double height){nullptr}; + // Set window size (width, height) + void window_setWindowSize(Window* self, double width, double height) { + window_setWindowFrame(self, self->windowFrame.x, self->windowFrame.y, width, height); + } -}; + // Set the cusor position relative to the content view + void window_setCursorPosition(Window* self, double x, double y) { + if (!self || !self->nsWindow) return; + + // Get current screen the window is on + id currentScreen = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::screenSel); + NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); + + // Update internal frame representation to get the latest window position + window_updateFrameFromOSX(self); + NSPoint location = {self->windowFrame.x, self->windowFrame.y}; + + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + + if (contentView) { + + // x,y are in physical backing pixels. CGWarpMouseCursorPosition + // and all AppKit frame/bounds values use logical points, so convert the incoming + // pixel position to points. + NSPoint pixelPos = { x, y }; + NSPoint pointPos = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( contentView, ObjectiveCSEL::convertPointFromBackingSel, pixelPos); + + // Get content bounds in points for clamping and Y-flip + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + + // NOTE: clamp to keep the cursor within the window content area + auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); + CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + + } + + } -// Modern image loading and pixel data extraction -struct ImageLoader { - 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) - int bytesPerRow{kZeroRows}; // Number of bytes per row - BOOL hasAlpha{NO}; // Whether image has alpha channel - - // 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}; - void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; - BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; - BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; - unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; -}; + // Set cursor visibility + void window_setCursorVisibility(Window* self, BOOL visible) { + if (!self || !self->nsWindow) return; + bHideCursor = !visible; + if (bHideCursor && bAllowHideCursor) { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + } else { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + } + } -// Called when application is about to finish launching -void applicationWillFinishLaunching(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; // Remove unused parameter warnings - if (gptrApplicationDelegate && gptrApplicationDelegate->willFinishLaunchingCallback) { - gptrApplicationDelegate->willFinishLaunchingCallback(gptrApplicationDelegate->willFinishLaunchingUserData); + void window_toggleFullScreen(Window* self) { + // Toggle fullscreen + ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); } -} -// Called when application has finished launching -void applicationDidFinishLaunching(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { - gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); + bool window_isFullScreen(Window* self) { + unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); + return (mask & NSWindowStyleMaskFullScreen); } -} -// Called when application is about to terminate -void applicationWillTerminate(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->willTerminateCallback) { - gptrApplicationDelegate->willTerminateCallback(gptrApplicationDelegate->willTerminateUserData); + // Initialize OpenGL renderer + void opengl_initialize(OpenGLRenderer* self, Window* window) { + self->window = window; + window->renderer = self; + + // Get classes using const strings + Class NSOpenGLPixelFormatClass = objc_getClass(kNSOpenGLPixelFormatClass); + Class CustomOpenGLViewClass = createCustomOpenGLViewClass(); // Use our custom class + + unsigned int sampleBuffers = (self->MSAA_Samples > 0) ? 1 : 0; + std::vector preferredSamples = {32, 16, 8, 4, 2, 0}; + if(self->MSAA_Samples <= 0) { + // If no multisampling requested, only try 0 samples + preferredSamples.clear(); + preferredSamples.resize(1); + preferredSamples.push_back(0); + } + + for(unsigned int samples: preferredSamples) + { + // Create pixel format attributes array using enum values + const unsigned int attrs[] = { + NSOpenGLPFADoubleBuffer, // Enable double buffering + NSOpenGLPFADepthSize, 32, // 32-bit depth buffer + NSOpenGLPFAColorSize, 24, // 24-bit color + NSOpenGLPFAAccelerated, // Hardware acceleration + NSOpenGLAllowOfflineRenderers, // Allow offline renderers + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, // OpenGL 4.1 Core Profile + NSOpenGLPFAMultisample, samples, // 0x --> 32X Multisampling + NSOpenGLPFASampleBuffers, sampleBuffers, // Number of sample buffers + 0 // null terminator + }; + + // Create pixel format + self->pixelFormat = ((id(*)(id, SEL, const unsigned int*))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSOpenGLPixelFormatClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithAttributesSel, attrs); + + // Check if pixel format was created successfully + if (self->pixelFormat) { + self->MSAA_Samples = samples; + break; // Successfully created pixel format + } + } + + // Create custom OpenGL view with event handling + NSRect glViewFrame = {0.0, 0.0, window->contentViewFrame.width, window->contentViewFrame.height}; + self->glView = ((id(*)(id, SEL, NSRect, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(CustomOpenGLViewClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithFramePixelFormatSel, glViewFrame, self->pixelFormat); + + // Set autoresizing mask to make the view resize with the window + unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); + + // Enable touch events (NSTouch) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + + // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); + + } -} -// Called when application becomes active -void applicationDidBecomeActive(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->didBecomeActiveCallback) { - gptrApplicationDelegate->didBecomeActiveCallback(gptrApplicationDelegate->didBecomeActiveUserData); + // Set up OpenGL context and make view first responder + void opengl_setupContext(OpenGLRenderer* self) { + // Set the OpenGL view as the window's content view + ((void(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::setContentViewSel, self->glView); + + // Make the OpenGL context current + self->glContext = ((id(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::openGLContextSel); + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + + // Make sure the window is key first + ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); + + // Make the OpenGL view the first responder so it can receive keyboard and mouse events + ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); + + // Additional debug: check if view can become key view + ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); } -} -// Called when application resigns active status -void applicationWillResignActive(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { - gptrApplicationDelegate->willResignActiveCallback(gptrApplicationDelegate->willResignActiveUserData); + // Implementation function + bool opengl_resetContextForSize(OpenGLRenderer* self, double width, double height) { + if (!self || !self->glContext || !self->glView) { + return false; + } + + // Make context current + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + + // Update the view frame + NSRect newFrame = {0.0, 0.0, width, height}; + ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); + + // Force context to reshape + ((void(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::reshapeSel); + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::updateSel); + + // Update viewport + glViewport(0, 0, (GLsizei)width, (GLsizei)height); + + // Clear buffers + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + return true; } -} -// Create and configure application delegate -id createApplicationDelegate() { - // Get NSObject class as superclass using const string - Class NSObjectClass = objc_getClass(kNSObjectClass); - - // Create a new class inheriting from NSObject using const string - Class AppDelegateClass = objc_allocateClassPair(NSObjectClass, kAppDelegateClass, 0); - - // Add application delegate methods - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillFinishLaunchingSel, (IMP)applicationWillFinishLaunching, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidFinishLaunchingSel, (IMP)applicationDidFinishLaunching, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillTerminateSel, (IMP)applicationWillTerminate, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidBecomeActiveSel, (IMP)applicationDidBecomeActive, kEventHandlerMethodTypeEncoding); - class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillResignActiveSel, (IMP)applicationWillResignActive, kEventHandlerMethodTypeEncoding); + // Get the underlying OpenGL context + void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { + return (void*)self->glContext; + } - // Register the class with the runtime - objc_registerClassPair(AppDelegateClass); - - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(AppDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + // Get the underlying CGLContextObj from the OpenGL renderer + void* opengl_getCGLContextObj(struct OpenGLRenderer* self) { + if (!self || !self->glContext) { return nullptr;} + + return (void*)((void*(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::CGLContextObjSel); + } - return delegate; -} - -//====================================================================// -// Keyboard Event Handling - -// Structure to hold common key event data -struct KeyEventData { - unsigned short keyCode = 0; - const char* characters = nullptr; - unsigned int modifierFlags = 0; -}; - -// Extract common key event data from NSEvent -KeyEventData extractKeyEventData(id event) { - KeyEventData data; - data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::keyCodeSel); - id characters = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::charactersSel); - data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::utf8StringSel); - data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - return data; -} - - // Handle key down events -void view_keyDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings - - KeyEventData data = extractKeyEventData(event); + // Get the underlying CGLContextObj (C API version) + void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { + return (void*)opengl_getCGLContextObj(self); + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + // Make the OpenGL context current + void opengl_makeCurrentContext(struct OpenGLRenderer* self) { + if (self && self->glContext) { + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + } } -} -// Handle key up events -void view_keyUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // Enable or disable vertical synchronization (vsync) + void opengl_setVsync(OpenGLRenderer* self, BOOL enabled) { + if (!self || !self->glContext) { return; } + + // Make context current first + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + + // Set swap interval: 1 for vsync enabled, 0 for disabled + GLint swapInterval = enabled ? 1 : 0; + + // Use NSOpenGLContext setValues:forParameter: to set swap interval + const GLint parameter = NSOpenGLContextParameterSwapInterval; + ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); + } - KeyEventData data = extractKeyEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + // Destroy the OpenGL renderer + void opengl_destroy(OpenGLRenderer* self) { + if (self) { + // Release OpenGL resources + if (self->glContext) { + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::releaseSel); + } + free(self); + } } -} -void view_flagsChanged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // OpenGL renderer constructor + OpenGLRenderer* opengl_init() { - unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); + renderer->pixelFormat = NULL; + renderer->glView = NULL; + renderer->glContext = NULL; + renderer->window = NULL; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { - gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + // Assign method pointers + renderer->initialize = opengl_initialize; + renderer->setupContext = opengl_setupContext; + renderer->getOpenGLContext = opengl_getOpenGLContext; + renderer->getCGLContextObj = opengl_getCGLContextObj; + renderer->getCGLContextObjPtr = opengl_getCGLContextObjPtr; + renderer->makeCurrentContext = opengl_makeCurrentContext; + renderer->setVsync = opengl_setVsync; + renderer->destroy = opengl_destroy; + renderer->resetContextForSize = opengl_resetContextForSize; + + return renderer; } -} -//====================================================================// -// Mouse Event Handling + static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + { + if(!image) return NO; -// All values are converted to physical backing pixels so they match the OpenGL -// viewport on HighDPI displays - AppKit reports locations in logical points, but -// PGE is in physical pixels. -void convertToContentViewCoordinates(NSPoint& location) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } - if(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] - { - // Get the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); - - // Convert from window coordinates to view coordinates (still logical points) - NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); - - // Scale from logical points to physical backing pixels - NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning + //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning + //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning + + self->width = (int)CGImageGetWidth(image); + self->height = (int)CGImageGetHeight(image); + self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; - // Get content bounds in physical pixels for Y-flip - NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + const uint8_t* imageData = CFDataGetBytePtr(rawData); + self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); + if(!self->pixelData) + { + return NO; + } + memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); + + // NOTE from Moros1138 + // + // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far + // the most common case, so in practice this block of code will never + // be run. However, if we find that there is a need to adjust the + // pixel data, this will need fleshing out. - // Flip Y coordinate from bottom-left to top-left - location.x = pixelLocation.x; - location.y = pixelBounds.height - pixelLocation.y; - + /* + 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; } - else - { - // Fallback: use default window height - location.y = kDefaultWindowHeight - location.y; - } -} -// Mouse event data structure to hold common mouse event information -struct MouseEventData { - NSPoint location = {0.0, 0.0}; - NSUInteger modifierFlags = 0; - NSInteger buttonNumber = 0; -}; + BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } -// Extract common mouse event data from NSEvent -MouseEventData extractMouseEventData(id event) { - MouseEventData data; - data.location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - data.modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - data.buttonNumber = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::buttonNumberSel); - - // Convert to content view coordinates and flip Y coordinate - convertToContentViewCoordinates(data.location); - return data; -} + CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); + CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); + CFRelease(pathStr); -// Stylus event data structure to hold common stylus event information -struct StylusEventData { + if(!url) + { + printf("loadImageFromFile: bad path '%s'\n", filePath); + return NO; + } - uint32_t nTabletDeviceID = 0; // Unique identifier for the tablet device - uint8_t nPointerType = 0; // Pointer type 0 = Mouse, 1 = Pen, 2 = Eraser - uint8_t nPointerEventType = 0; // Type of pointer 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event - NSPoint nspLocation = {0.0, 0.0}; // Location of the stylus event - double dPressure = 0.0; // Pressure applied by the stylus - double dTangentialPressure = 0.0; // Tangential pressure applied by the stylus - double dRotationRadians = 0.0; // Rotation of the stylus in radians - double dTiltX_radians = 0.0; // Tilt of the stylus in the X direction in radians - double dTiltY_radians = 0.0; // Tilt of the stylus in the Y direction in radians - uint16_t buttonMask = 0; // Bitmask for stylus buttons (MAX 16 buttons, 1 = pressed, 0 = released) + CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); + CFRelease(url); -}; + if(!src) + { + printf("loadImageFromFile: couldn't open '%s'\n", filePath); + return NO; + } + + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); -// Extract common mouse event data from NSEvent -StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + return imageloader_decodeImage(self, image); + } - StylusEventData data; - id cgEvent = ((id(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - data.nspLocation = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - data.nTabletDeviceID = (uint32_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventDeviceID); + BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { + + CFDataRef cfData = CFDataCreateWithBytesNoCopy( + nullptr, + reinterpret_cast(data), + (CFIndex)bytes, + kCFAllocatorNull // we own the buffer, CF must not free it + ); - data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); - data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + if (!cfData) + { + printf("loadImageFromMemory: CFData creation failed\n"); + return NO; + } - data.dPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointPressure); - data.dRotationRadians = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventRotation) * (M_PI / 180.0); - data.dTangentialPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventTangentialPressure); + CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); + CFRelease(cfData); - data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); + if (!src) + { + printf("loadImageFromMemory: src creation failed\n"); + return NO; + } - double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); - double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); + + return imageloader_decodeImage(self, image); + } - // Clamp to safe math bounds (-1.0 to 1.0) to avoid domain errors in asin - tiltX_raw = std::fmax(-1.0, std::fmin(1.0, tiltX_raw)); - tiltY_raw = std::fmax(-1.0, std::fmin(1.0, tiltY_raw)); + // Get raw pixel data pointer + unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return (unsigned char*)self->pixelData; + } - // Convert to true physical angles in RADIANS -PI/2 to +PI/2 - data.dTiltX_radians = std::asin(tiltX_raw); - data.dTiltY_radians = std::asin(tiltY_raw); + // Get image information + void imageloader_getImageInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; + } - // Convert to content view coordinates and flip Y coordinate - convertToContentViewCoordinates(data.nspLocation); - - return data; -} + // Get detailed image format information + void imageloader_getDetailedInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; + if (bytesPerRow) *bytesPerRow = self->bytesPerRow; + if (hasAlpha) *hasAlpha = self->hasAlpha; + } -//====================================================================// -// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + // Check if image is loaded + BOOL imageloader_isLoaded(const struct ImageLoader* self) { + return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); + } + // Get pixel at specific coordinates + BOOL imageloader_getPixel(const struct ImageLoader* self, int x, int y, + unsigned char* red, unsigned char* green, + unsigned char* blue, unsigned char* alpha) { + if (!imageloader_isLoaded(self) || x < kMinValidDimension || x >= self->width || y < kMinValidDimension || y >= self->height) { + return NO; + } + + // Calculate pixel offset (macOS uses bottom-left origin, so flip Y) + int flippedY = self->height - kFlippedOffset - y; + imageloader_pixel_t* pixel = self->pixelData + (flippedY * self->width) + x; -// NSTouchPhase bitmask values -enum NSTouchPhase : NSUInteger { - NSTouchPhaseBegan = 1 << 0, - NSTouchPhaseMoved = 1 << 1, - NSTouchPhaseStationary = 1 << 2, - NSTouchPhaseEnded = 1 << 3, - NSTouchPhaseCancelled = 1 << 4, - NSTouchPhaseAny = ULONG_MAX -}; + // Extract color components (assuming RGBA or RGB format) + 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; + } -enum NSPressureStage : NSInteger { - NSPressureStageLight = 0, // Light press - NSPressureStageNormal = 1, // Normal press - NSPressureStageForce = 2 // Force click -}; + // Cleanup ImageLoader resources + void imageloader_destroy(struct ImageLoader* self) { + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + self->width = kZeroWidth; + self->height = kZeroHeight; + self->bytesPerPixel = kZeroBytes; + self->bytesPerRow = kZeroRows; + self->hasAlpha = NO; + } + + // ImageLoader constructor + struct ImageLoader* imageloader_init() { + struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); + + // Initialize properties + loader->pixelData = NULL; + loader->width = kZeroWidth; + loader->height = kZeroHeight; + loader->bytesPerPixel = kZeroBytes; + loader->bytesPerRow = kZeroRows; + loader->hasAlpha = NO; + + // Assign method pointers + loader->loadFromFile = imageloader_loadFromFile; + loader->loadFromMemory = imageloader_loadFromMemory; + loader->destroy = imageloader_destroy; + loader->getPixelData = imageloader_getPixelData; + loader->getImageInfo = imageloader_getImageInfo; + loader->getDetailedInfo = imageloader_getDetailedInfo; + loader->isLoaded = imageloader_isLoaded; + loader->getPixel = imageloader_getPixel; + + return loader; + } -enum class ForceTouchPressure : int { - Light = 25, // 0.25 - Light touch - Normal = 50, // 0.50 - Normal press - Force = 100, // 1.00 - Force click -}; + // Window constructor + Window* window_init(double x, double y, double width, double height) { + Window* window = (Window*)malloc(sizeof(Window)); + + // Initialize window properties + window->nsWindow = NULL; + window->delegate = NULL; + window->title = NULL; + + // Set initial window frame + window->windowFrame.x = x; + window->windowFrame.y = y; + window->windowFrame.width = width; + window->windowFrame.height = height; -// Returns the approximated trackpad touch size in pixels based on the linear force value (0.0 to 1.0) -CGSize getApproxTrackPadTouchSize(float linearForce){ - - CGSize cgTouchSize = {0.0, 0.0}; - const float mmToPoints = 72.0f / 25.4f; // ~2.834 points per mm - float minSizePoints = 2.0f * mmToPoints; // 3mm lower bound approx values from Google - float maxSizePoints = 20.0f * mmToPoints; // 16mm upper bound - - // Interpolate the finger footprint size within our point boundaries - float touchSizeInPoints = minSizePoints + (linearForce * (maxSizePoints - minSizePoints) / 2.0f); + // Set window content view frame + window->contentViewFrame.x = x; + window->contentViewFrame.y = y; + window->contentViewFrame.width = width; + window->contentViewFrame.height = height; + + // Initialize callback pointers + window->keyDownCallback = NULL; + window->keyUpCallback = NULL; + window->mouseDownCallback = NULL; + window->mouseUpCallback = NULL; + window->mouseMovedCallback = NULL; + window->mouseDraggedCallback = NULL; + window->rightMouseDownCallback = NULL; + window->rightMouseUpCallback = NULL; + window->otherMouseDownCallback = NULL; + window->otherMouseUpCallback = NULL; + window->scrollWheelCallback = NULL; - // Retrieve the window scale factor (1.0 for Standard, 2.0 for Retina, 3.0 for bannana displays, etc.) - CGFloat pixelScale = 1.0f; - if (gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) { - // Get backing scale factor from the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { - pixelScale = ((CGFloat(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::backingScaleFactorSel); - } - } - cgTouchSize = CGSizeMake(touchSizeInPoints * pixelScale, touchSizeInPoints * pixelScale); - return cgTouchSize; -}; + // Event handling disabled by default + window->acceptsInputEvents = NO; -// Mouse event data structure to hold common mouse event information -struct TouchEventData { - uint32_t touchID = 0; // Stable sequential touch ID - NSPoint nspLocation = {0.0,0.0}; // X coordinate in content-view pixel coordinates - double pressure = 0.0; // Pressure value - CGSize nsTouchSize = {0.0, 0.0}; // Touch Size X (not available on macOS trackpad) - CGSize cgDeviceSize = {0.0, 0.0}; // Device size (width, height) in ponits - bool bIsActive = true; // Touch is active (true for Began/Moved, false for Ended/Cancelled) - -}; + // Initialize window delegate callback pointers + window->windowDidResizeCallback = NULL; + window->windowWillCloseCallback = NULL; + window->windowDidBecomeKeyCallback = NULL; + window->windowDidResignKeyCallback = NULL; + window->windowDidMiniaturizeCallback = NULL; + window->windowDidDeminiaturizeCallback = NULL; + window->windowDidResizeUserData = NULL; + window->windowWillCloseUserData = NULL; + window->windowDidBecomeKeyUserData = NULL; + window->windowDidResignKeyUserData = NULL; + window->windowDidMiniaturizeUserData = NULL; + window->windowDidDeminiaturizeUserData = NULL; -std::unordered_map sTouchIDEvents; -static std::unordered_map sTouchIDMap; -static uint32_t sNextTouchID = 0; -CGSize cgsApproxTouchSize{0.0, 0.0}; // Stores the approx width and height of a touch event in CG coordinates + // Assign method pointers + window->create = window_create; + window->show = window_show; + window->destroy = window_destroy; + window->setDelegate = window_setDelegate; + window->removeDelegate = window_removeDelegate; + window->getTitle = window_getTitle; + window->setTitle = window_setTitle; + window->setWindowSize = window_setWindowSize; + window->getWindowSize = window_getWindowSize; + window->setWindowPosition = window_setWindowPosition; + window->getWindowPosition = window_getWindowPosition; + window->setWindowFrame = window_setWindowFrame; + window->getWindowFrame = window_getWindowFrame; + window->getCurrentFrame = window_updateFrameFromOSX; + window->getContentViewFrame = window_getContentViewFrame; + window->setContentViewFrame = window_setContentViewFrame; -//Iterate through Touches, updates and fires the callback. -static void updateTouchData(id event, NSUInteger phase, - void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) -{ - if (!callback) return; - - NSUInteger count = 0; - id touchArray = nullptr; - id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); - - // Edge case, there can be an touch event before a pressure event, therefore we need to check if the approximate touch size has been initialized - if(cgsApproxTouchSize.width == 0.0 || cgsApproxTouchSize.height == 0.0) - { - cgsApproxTouchSize = getApproxTrackPadTouchSize(NSPressureStageNormal); + return window; } - - - if (touchCollection) - { - count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - - // Get current screen the window is on - id currentScreen = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::screenSel); - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); - - for (NSUInteger i = 0; i < count; ++i) { - - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - TouchEventData data; - - // normalizedPosition is NSPoint in [0,1] range on the trackpad surface - NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); - - // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) - data.nspLocation.x = normPos.x * screenFrame.width; - data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; - - // Get Device Size - data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); - // Approximate touch size based on pressure stage (macOS does not provide actual touch size) - data.nsTouchSize = cgsApproxTouchSize; - data.bIsActive = !bRemove; - - // Assign a stable sequential uint32_t IDs same as iOS does - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - - auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); - if (inserted) ++sNextTouchID; - uint32_t tid = it->second; - data.touchID = tid; - sTouchIDEvents.emplace(key, data); - callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); - // Clean up map entry once the touch has ended or been cancelled - if (bRemove) - { - sTouchIDEvents.erase(key); - sTouchIDMap.erase(key); - } - - } - + // Event callback setter functions + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyDownCallback = callback; + self->eventUserData = userData; } - // A bit painful, but we need to check of any keys that have been removed as the touchleave event happened outside of our application - std::vector vFoundKeys; - std::vector vMissingKeys; - touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, NSTouchPhaseAny, nil); + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyUpCallback = callback; + self->eventUserData = userData; + } - // If we have a touch collection and the phase is not cancelled, we can check for missing keys, otherwise we will just remove all keys from the map - if (touchCollection && phase != NSTouchPhaseCancelled) - { - count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - for (NSUInteger i = 0; i < count; ++i) { - - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - vFoundKeys.push_back(key); + void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { + self->flagsChangedCallback = callback; + self->eventUserData = userData; + } - } + void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDownCallback = callback; + self->eventUserData = userData; } - - // Find the missing keys and fire the touchEndedCallback for them - for(auto& [key, data] : sTouchIDEvents) { - - if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { - gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); - vMissingKeys.push_back(key); - } - + + void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseUpCallback = callback; + self->eventUserData = userData; } - - // Remove the missing keys from the maps - for(auto& fKeys: vMissingKeys) { - sTouchIDEvents.erase(fKeys); - sTouchIDMap.erase(fKeys); + + void window_setMouseMovedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseMovedCallback = callback; + self->eventUserData = userData; } - - vFoundKeys.clear(); vMissingKeys.clear(); - - // Finally reset the next touch ID if there are no active touches remaining - if (sTouchIDMap.size() == 0) sNextTouchID = 0; -} -// TODO move to new location -// Handle Force Touch pressure changes on trackpad (single-finger press) -void view_pressureChange(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - auto toNormalizedPressure = [](ForceTouchPressure pressure) -> double { - return static_cast(static_cast(pressure)) / 100.0; - }; - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - - // Get CGEvent to determine event subtype - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - - // A force touch trackpad event will have a subtype of 0 (default) or greater than 2 (tablet proximity) - if (nPointerEventType == kCGEventMouseSubtypeDefault || nPointerEventType > kCGEventMouseSubtypeTabletProximity) { + void window_setMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDraggedCallback = callback; + self->eventUserData = userData; + } - // KRespondsToSelector - BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDownCallback = callback; + self->eventUserData = userData; + } - NSInteger stage = NSPressureStageNormal; - if(supportsStage) - { - // stage returns: 0 = normal click, 1 = light press, 2 = force click - stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); - } - - // Map stage to normalized pressure values as requested - double pressure = toNormalizedPressure(ForceTouchPressure::Normal); - - switch (stage) { - case NSPressureStageLight: - pressure = toNormalizedPressure(ForceTouchPressure::Light); - break; - case NSPressureStageNormal: - pressure = toNormalizedPressure(ForceTouchPressure::Normal); - break; - case NSPressureStageForce: - pressure = toNormalizedPressure(ForceTouchPressure::Force); - break; - default: - pressure = toNormalizedPressure(ForceTouchPressure::Normal); - break; - } - - // now we need to work out the size - cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); - - for(auto& [key, data] : sTouchIDEvents) { - data.pressure = pressure; - data.nsTouchSize = cgsApproxTouchSize; - } - - } + void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDraggedCallback = callback; + self->eventUserData = userData; + } + + void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseUpCallback = callback; + self->eventUserData = userData; + } + + void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDownCallback = callback; + self->eventUserData = userData; } -} -// Handle left mouse down events -void view_mouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseUpCallback = callback; + self->eventUserData = userData; + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDraggedCallback = callback; + self->eventUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - MouseEventData data = extractMouseEventData(event); - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { + self->scrollWheelCallback = callback; + self->eventUserData = userData; } - else if (nPointerEventType == 1 || nPointerEventType == 2) - { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - true, false, true, - gptrNSWindowEvents->stylusUserData); + void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseEnteredCallback = callback; + self->eventUserData = userData; } -} + void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseExitedCallback = callback; + self->eventUserData = userData; + } -// Handle left mouse up events -void view_mouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchMovedCallback = callback; + self->touchUserData = userData; + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchEndedCallback = callback; + self->touchUserData = userData; + } - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] { - gptrNSWindowEvents->mouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchCancelledCallback = callback; + self->touchUserData = userData; } - else if(nPointerEventType == 1 || nPointerEventType == 2) + + void window_setStylusCallback(Window* self, + void (*callback)(uint32_t, double, double, float, float, float, float, bool, bool, bool, void*), + void* userData) { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - false, true, true, - gptrNSWindowEvents->stylusUserData); + self->stylusCallback = callback; + self->stylusUserData = userData; + } + void window_enableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = YES; + gptrNSWindowEvents = self; // Set global reference for event handlers + } } -} + void window_disableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = NO; + if (gptrNSWindowEvents == self) { + gptrNSWindowEvents = NULL; + } + } + } -// Handle mouse drag events -void view_mouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // Window delegate callback setter functions + void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResizeCallback = callback; + self->windowDidResizeUserData = userData; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowWillCloseCallback = callback; + self->windowWillCloseUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - MouseEventData data = extractMouseEventData(event); - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidBecomeKeyCallback = callback; + self->windowDidBecomeKeyUserData = userData; } - else if (nPointerEventType == 1 || nPointerEventType == 2) - { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - true, false, true, - gptrNSWindowEvents->stylusUserData); - } -} + void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResignKeyCallback = callback; + self->windowDidResignKeyUserData = userData; + } + void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidMiniaturizeCallback = callback; + self->windowDidMiniaturizeUserData = userData; + } -// Handle mouse movement events -void view_mouseMoved(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidDeminiaturizeCallback = callback; + self->windowDidDeminiaturizeUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // if it is a trackpad event, ignore it. - if (nPointerEventType > 2) return; - - // a Mouse move event and Stlus roximity Event have the same properties. - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { - gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + // Application delegate callback setter functions + void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->willFinishLaunchingCallback = callback; + self->willFinishLaunchingUserData = userData; } - - -} -// Handle right mouse down events -void view_rightMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->didFinishLaunchingCallback = callback; + self->didFinishLaunchingUserData = userData; } -} -// Handle right mouse up events -void view_rightMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { - gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { + self->willTerminateCallback = callback; + self->willTerminateUserData = userData; } -} -// Handle right mouse drag events -void view_rightMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->didBecomeActiveCallback = callback; + self->didBecomeActiveUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->willResignActiveCallback = callback; + self->willResignActiveUserData = userData; } - -} -// Handle other mouse button down events -void view_otherMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} +} // extern "C" + +#endif + +#if OLC_HOST == OLC_HOST_IOS + +// UIApplication and related class names +static constexpr const char* kNSAutoreleasePoolClass = "NSAutoreleasePool"; +static constexpr const char* kUIApplicationClass = "UIApplication"; +static constexpr const char* kUIViewControllerClass = "UIViewController"; +static constexpr const char* kSetRootViewControllerSel = "setRootViewController:"; +static constexpr const char* kUIViewClass = "UIView"; +static constexpr const char* kUIWindowClass = "UIWindow"; +static constexpr const char* kUIScreenClass = "UIScreen"; +static constexpr const char* kNSStringClass = "NSString"; +static constexpr const char* kEAGLContextClass = "EAGLContext"; +static constexpr const char* kGLKViewClass = "GLKView"; +static constexpr const char* kGLKViewControllerClass = "GLKViewController"; +static constexpr const char* kNSObjectClass = "NSObject"; +static constexpr const char* kUIImageClass = "UIImage"; +static constexpr const char* kAppDelegateClass = "AppDelegate"; // This is the OS default AppDelegate class name +static constexpr const char* kolcPGE3AppDelegateClass = "olcPGE3AppDelegate"; // Important: Custom AppDelegate subclass name +static constexpr const char* kViewControllerDelegateClass = "ViewControllerDelegate"; +static constexpr const char* kCustomGLKViewClass = "CustomGLKView"; +static constexpr const char* kNSBundleClass = "NSBundle"; // Class for handling app bundles i.e. getting app resource paths + +// UIWindow and UIScreen selectors +static constexpr const char* kmainScreenSel = "mainScreen"; +static constexpr const char* kBoundsSelScreen = "bounds"; +static constexpr const char* kNativeBounds = "nativeBounds"; +static constexpr const char* kScaleSelScreen = "scale"; +static constexpr const char* kInitWithFrameSel = "initWithFrame:"; +static constexpr const char* kMakeKeyAndVisibleSel = "makeKeyAndVisible"; +static constexpr const char* kMaximumFramesPerSecond = "maximumFramesPerSecond"; +static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; + +// Memory management selectors +static constexpr const char* kAllocSel = "alloc"; +static constexpr const char* kInitSel = "init"; +static constexpr const char* kDrainSel = "drain"; +static constexpr const char* kSetDelegateSel = "setDelegate:"; +static constexpr const char* kReleaseSel = "release"; +static constexpr const char* kRetainSel = "retain"; +static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; + +// UIApplication lifecycle and management selectors +static constexpr const char* kSharedApplicationSel = "sharedApplication"; +static constexpr const char* kRunSel = "run"; +static constexpr const char* kSetIdleTimerDisabledSel = "setIdleTimerDisabled:"; +static constexpr const char* kSetPausedSel = "setPaused:"; + +// UIApplicationDelegate lifecycle methods +static constexpr const char* kApplicationDidFinishLaunchingSel = "application:didFinishLaunchingWithOptions:"; +static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; +static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; +static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; +static constexpr const char* kApplicationDidEnterBackgroundSel = "applicationDidEnterBackground:"; +static constexpr const char* kApplicationWillEnterForegroundSel = "applicationWillEnterForeground:"; + +// UIViewController creation, display, and management selectors +static constexpr const char* kInitWithNibNameSel = "initWithNibName:bundle:"; +static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; +static constexpr const char* kSetTitleSel = "setTitle:"; +static constexpr const char* kViewSel = "view"; +static constexpr const char* kSetViewSel = "setView:"; +static constexpr const char* kPresentViewControllerSel = "presentViewController:animated:completion:"; +static constexpr const char* kDismissViewControllerSel = "dismissViewControllerAnimated:completion:"; +static constexpr const char* kFrameSel = "frame"; +static constexpr const char* kSetFrameSel = "setFrame:"; +static constexpr const char* kBoundsSel = "bounds"; +static constexpr const char* kSetBoundsSel = "setBounds:"; +static constexpr const char* kUpdateView = "update"; + +// GLKViewController lifecycle methods +static constexpr const char* kViewDidLoadSel = "viewDidLoad"; +static constexpr const char* kLoadViewSel = "loadView"; +static constexpr const char* kViewWillAppearSel = "viewWillAppear:"; +static constexpr const char* kViewDidAppearSel = "viewDidAppear:"; +static constexpr const char* kViewWillDisappearSel = "viewWillDisappear:"; +static constexpr const char* kViewDidDisappearSel = "viewDidDisappear:"; +static constexpr const char* kViewDidLayoutSubviewsSel = "viewDidLayoutSubviews"; + +// Touch event methods selectors +static constexpr const char* kTouchesBeganSel = "touchesBegan:withEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMoved:withEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEnded:withEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelled:withEvent:"; +static constexpr const char* kAllObjectsSel = "allObjects"; +static constexpr const char* kMajorRadius = "majorRadius"; + +// GLKView drawing methods selectors +static constexpr const char* kDrawRectSel = "drawRect:"; -// Handle other mouse button up events -void view_otherMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { - gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} +// Keyboard event methods selectors (for hardware keyboards) +static constexpr const char* kPressesBeganSel = "pressesBegan:withEvent:"; +static constexpr const char* kPressesEndedSel = "pressesEnded:withEvent:"; +static constexpr const char* kKeySel = "key"; +static constexpr const char* kKeyCodeSel = "keyCode"; +static constexpr const char* kCharactersSel = "characters"; +static constexpr const char* kModifierFlagsSel = "modifierFlags"; -void view_otherMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; +// Touch and press event property selectors +static constexpr const char* kLocationInViewSel = "locationInView:"; +static constexpr const char* kAllTouchesSel = "allTouches"; +static constexpr const char* kCountSel = "count"; +static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} -// Handle scroll wheel events -void view_scrollWheel(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - double deltaX = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaXSel); - double deltaY = ((double(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::deltaYSel); +// EAGL and OpenGL ES management selectors +static constexpr const char* kInitWithAPISel = "initWithAPI:"; +static constexpr const char* kSetCurrentContextSel = "setCurrentContext:"; +static constexpr const char* kContextSel = "context"; +static constexpr const char* kSetContextSel = "setContext:"; +static constexpr const char* kDrawableColorFormatSel = "drawableColorFormat"; +static constexpr const char* kSetDrawableColorFormatSel = "setDrawableColorFormat:"; +static constexpr const char* kDrawableDepthFormatSel = "drawableDepthFormat"; +static constexpr const char* kSetDrawableDepthFormatSel = "setDrawableDepthFormat:"; +static constexpr const char* kDrawableMultisampleSel = "drawableMultisample"; +static constexpr const char* kSetDrawableMultisampleSel = "setDrawableMultisample:"; +static constexpr const char* kSetOpaqueSel = "setOpaque:"; +static constexpr const char* kSetContentScaleFactorSel = "setContentScaleFactor:"; +static constexpr const char* kContentScaleFactorSel = "contentScaleFactor"; +static constexpr const char* kSetEnableSetNeedsDisplaySel = "setEnableSetNeedsDisplay:"; +static constexpr const char* kSetAutoResizeDrawableSel = "setAutoResizeDrawable:"; +static constexpr const char* kSetPreferredFramesPerSecondSel = "setPreferredFramesPerSecond:"; +static constexpr const char* kBindDrawableSel = "bindDrawable"; +static constexpr const char* kDisplaySel = "display"; +static constexpr const char* kPresentRenderbufferSel = "presentRenderbuffer:"; +static constexpr const char* kSetNeedsDisplaySel = "setNeedsDisplay"; + +// Device and orientation selectors +static constexpr const char* kCurrentDeviceSel = "currentDevice"; +static constexpr const char* kOrientationSel = "orientation"; +static constexpr const char* kIsMultitouchEnabledSel = "isMultipleTouchEnabled"; +static constexpr const char* kSetMultipleTouchEnabledSel = "setMultipleTouchEnabled:"; +static constexpr const char* kSetUserInteractionEnabledSel = "setUserInteractionEnabled:"; +static constexpr const char* kIsUserInteractionEnabledSel = "isUserInteractionEnabled"; +static constexpr const char* kDeviceOrientationDidChangeSel = "deviceOrientationDidChange:"; +static constexpr const char* kBeginGenDeviceOrientationNotifSel = "beginGeneratingDeviceOrientationNotifications"; + +// Notification classes and selectors +static constexpr const char* kNSNotificationCenterClass = "NSNotificationCenter"; +static constexpr const char* kDefaultCenterSel = "defaultCenter"; +static constexpr const char* kAddObserverSel = "addObserver:selector:name:object:"; + +// Constants +static constexpr const char* kUIDeviceOrientationDidChangeNoti = "UIDeviceOrientationDidChangeNotification"; +static constexpr const char* kUIDeviceClass = "UIDevice"; + +// UIImage and image data access selectors +static constexpr const char* kInitWithContentsOfFileSel = "initWithContentsOfFile:"; +static constexpr const char* kRepresentationsSel = "representations"; +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"; +static constexpr const char* kNSBitmapImageRepClass = "NSBitmapImageRep"; +static constexpr const char* kCGImageSel = "CGImage"; +static constexpr const char* kNSDataClass = "NSData"; // Class for handling binary data, used for image loading +static constexpr const char* kdataWithBytesLengthSel = "dataWithBytes:length:"; // Selector for creating NSData from raw bytes +static constexpr const char* kImageWithDataSel = "imageWithData:"; + + +// OpenGL ES constants +static constexpr int kEAGLRenderingAPIOpenGLES3 = 3; +static constexpr int kEAGLColorFormatRGBA8 = 1; +static constexpr int kEAGLDepthFormat24 = 1; +static constexpr int kGLRenderbufferColor = 0x8CE0; // GL_COLOR_ATTACHMENT0 + +// NS Bundle selectors +static constexpr const char* kMainBundleSel = "mainBundle"; +static constexpr const char* kUTF8StringSel = "UTF8String"; +static constexpr const char* kbundlePathSel = "bundlePath"; - convertToContentViewCoordinates(location); +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { - gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} +// Default values and configuration settings +static constexpr const char* kWindowTitle = "C iOS OpenGLES Framework"; +static constexpr const char* kDefaultAppPath = "olcPGE3_iOS"; +static constexpr double kDefaultWindowWidth = 1280.0; // TODO: Upgate to the default ios screen size I think it is 538X402 +static constexpr double kDefaultWindowHeight = 728.0; +static constexpr int kDefaultFrameRate = 60; +static constexpr int kPreferredFrameRate = 120; +static constexpr int kBitsPerByte = 8; +static constexpr int kMinValidDimension = 0; +static constexpr int kRGBABytesPerPixel = 4; +static constexpr int kFullyOpaque = 255; +static constexpr int kZeroBytes = 0; +static constexpr int kZeroRows = 0; +static constexpr int kZeroWidth = 0; +static constexpr int kZeroHeight = 0; +static constexpr int kFlippedOffset = 1; +static constexpr int kNoButton = -1; -// New events for mouse entered/exited -void view_mouseEntered(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - - // Allow hiding cursor when mouse is inside window - bAllowHideCursor = true; +// Objective-C method type encoding constants +static constexpr const char* kVoidMethodTypeEncoding = "v@:"; // Type encoding for void methods with no parameters: "v@:" +static constexpr const char* kBoolMethodTypeEncoding = "c@:"; // Type encoding for methods returning BOOL with no parameters: "c@:" +static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; // Type encoding for methods with event/notification parameter: "v@:@" +static constexpr const char* kIOSAppDelegateMethodTypeEncoding = "c@:@@"; // Type encoding for iOS app delegate methods returning BOOL: "c@:@@" (BOOL, self, _cmd, app, options) +static constexpr const char* kIOSAppMethodTypeEncoding = "v@:@"; // Type encoding for iOS app delegate methods with app parameter: "v@:@" (void, self, _cmd, app) +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@@"; // Type encoding for touch event methods with two object parameters: "v@:@@" +static constexpr const char* kClassMethodTypeEncoding = "#@:"; // Type encoding for class methods returning Class: "#@:" +static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"; // Type encoding for drawRect method with NSRect parameter: "v@:{CGRect={CGPoint=dd}{CGSize=dd}}" +static constexpr const char* kDrawGCectMethodTypeEncoding = "v@:{CGRect={CGPoint=dd}{CGSize=dd}}"; // Type encoding for drawRect method with CGRect parameter: "v@:{CGRect={CGPoint=dd}{CGSize=dd}}" +static constexpr const char* kMethodSignatureMethodTypeEncoding = "@@::"; // Type encoding for methodSignatureForSelector: returning NSMethodSignature: "@@::" + + +namespace ObjectiveCSEL { - // If the cursor is currently hidden, hide it again to ensure it stays hidden while inside the window - if(bHideCursor) - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + // Application memory management selectors + static SEL allocSel = nullptr; + static SEL initSel = nullptr; + static SEL drainSel = nullptr; + static SEL setDelegateSel = nullptr; + static SEL releaseSel = nullptr; + static SEL retainSel = nullptr; + static SEL isKindOfClassSel = nullptr; + + // UIApplication lifecycle and management selectors + static SEL sharedApplicationSel = nullptr; + static SEL runSel = nullptr; + static SEL setIdleTimerDisabledSel = nullptr; + static SEL setPausedSel = nullptr; + + // UIApplicationDelegate lifecycle methods + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; + static SEL applicationDidEnterBackgroundSel = nullptr; + static SEL applicationWillEnterForegroundSel = nullptr; + + // UIWindow and UIScreen selectors + static SEL mainScreenSel = nullptr; + static SEL boundsSelScreen = nullptr; + static SEL nativeBounds = nullptr; + static SEL scaleSelScreen = nullptr; + static SEL initWithFrameSel = nullptr; + static SEL makeKeyAndVisibleSel = nullptr; + static SEL maximumFramesPerSecond = nullptr; + static SEL setAutoresizingMaskSel = nullptr; + + // UIViewController creation, display, and management selectors + static SEL initWithNibNameSel = nullptr; + static SEL stringWithUTF8StringSel = nullptr; + static SEL setTitleSel = nullptr; + static SEL viewSel = nullptr; + static SEL setViewSel = nullptr; + static SEL presentViewControllerSel = nullptr; + static SEL dismissViewControllerSel = nullptr; + static SEL frameSel = nullptr; + static SEL setFrameSel = nullptr; + static SEL boundsSel = nullptr; + static SEL setBoundsSel = nullptr; + static SEL updateView = nullptr; + + // Root view controller selector + static SEL setRootViewControllerSel = nullptr; + + // GLKViewController lifecycle methods + static SEL viewDidLoadSel = nullptr; + static SEL loadViewSel = nullptr; + static SEL viewWillAppearSel = nullptr; + static SEL viewDidAppearSel = nullptr; + static SEL viewWillDisappearSel = nullptr; + static SEL viewDidDisappearSel = nullptr; + static SEL viewDidLayoutSubviewsSel = nullptr; + static SEL drawRectSel = nullptr; + static SEL setContentScaleFactor = nullptr; + static SEL contentScaleFactor = nullptr; + + // Touch event methods selectors + static SEL touchesBeganSel = nullptr; + static SEL touchesMovedSel = nullptr; + static SEL touchesEndedSel = nullptr; + static SEL touchesCancelledSel = nullptr; + static SEL allObjectsSel = nullptr; + static SEL majorRadius = nullptr; + + // Keyboard event methods selectors (for hardware keyboards) + static SEL pressesBeganSel = nullptr; + static SEL pressesEndedSel = nullptr; + + // Touch and press event property selectors + static SEL locationInViewSel = nullptr; + static SEL allTouchesSel = nullptr; + static SEL countSel = nullptr; + static SEL objectAtIndexSel = nullptr; + static SEL keySel = nullptr; + static SEL keyCodeSel = nullptr; + static SEL charactersSel = nullptr; + static SEL modifierFlagsSel = nullptr; + + // EAGL and OpenGL ES management selectors + static SEL initWithAPISel = nullptr; + static SEL setCurrentContextSel = nullptr; + static SEL contextSel = nullptr; + static SEL setContextSel = nullptr; + static SEL drawableColorFormatSel = nullptr; + static SEL setDrawableColorFormatSel = nullptr; + static SEL drawableDepthFormatSel = nullptr; + static SEL setDrawableDepthFormatSel = nullptr; + static SEL drawableMultisampleSel = nullptr; + static SEL setDrawableMultisampleSel = nullptr; + static SEL setEnableSetNeedsDisplaySel = nullptr; + static SEL bindDrawableSel = nullptr; + static SEL displaySel = nullptr; + static SEL presentRenderbufferSel = nullptr; + static SEL setNeedsDisplaySel = nullptr; + static SEL setPreferredFramesPerSecondSel = nullptr; + + // Device and orientation selectors + static SEL currentDeviceSel = nullptr; + static SEL orientationSel = nullptr; + static SEL isMultitouchEnabledSel = nullptr; + static SEL setMultipleTouchEnabledSel = nullptr; + static SEL setUserInteractionEnabledSel = nullptr; + static SEL isUserInteractionEnabledSel = nullptr; + static SEL deviceOrientationDidChangeSel = nullptr; + static SEL beginGenDeviceOrientationNotifSel = nullptr; - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + // UI Image + static SEL initWithContentsOfFileSel = nullptr; + static SEL representationsSel = nullptr; + static SEL pixelsWideSel = nullptr; + static SEL pixelsHighSel = nullptr; + static SEL bitsPerPixelSel = nullptr; + static SEL bytesPerRowSel = nullptr; + static SEL hasAlphaSel = nullptr; + static SEL bitmapDataSel = nullptr; + static SEL cgImageSel = nullptr; + static SEL dataWithBytesLengthSel = nullptr; + static SEL imageWithDataSel = nullptr; + + // NS Bundle selectors + static SEL mainBundleSel = nullptr; + static SEL UTF8StringSel = nullptr; + static SEL bundlePathSel = nullptr; + + // NSLocale selectors + static SEL currentLocaleSel = nullptr; + static SEL localeIdentifierSel = nullptr; + + // Notification classes and selectors + static SEL defaultCenterSel = nullptr; + static SEL addObserverSel = nullptr; - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { - gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + + void initializeSelectors() { + if (allocSel) return; // Already initialized + + // Application memory management selectors + allocSel = sel_registerName(kAllocSel); + initSel = sel_registerName(kInitSel); + drainSel = sel_registerName(kDrainSel); + setDelegateSel = sel_registerName(kSetDelegateSel); + releaseSel = sel_registerName(kReleaseSel); + retainSel = sel_registerName(kRetainSel); + isKindOfClassSel = sel_registerName(kIsKindOfClassSel); + + // UIApplication lifecycle and management selectors + sharedApplicationSel = sel_registerName(kSharedApplicationSel); + runSel = sel_registerName(kRunSel); + setIdleTimerDisabledSel = sel_registerName(kSetIdleTimerDisabledSel); + setPausedSel = sel_registerName(kSetPausedSel); + + // UIApplicationDelegate lifecycle methods + applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); + applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); + applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); + applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); + applicationDidEnterBackgroundSel = sel_registerName(kApplicationDidEnterBackgroundSel); + applicationWillEnterForegroundSel = sel_registerName(kApplicationWillEnterForegroundSel); + + // UIWindow and UIScreen selectors + mainScreenSel = sel_registerName(kmainScreenSel); + boundsSelScreen = sel_registerName(kBoundsSelScreen); + nativeBounds = sel_registerName(kNativeBounds); + scaleSelScreen = sel_registerName(kScaleSelScreen); + initWithFrameSel = sel_registerName(kInitWithFrameSel); + makeKeyAndVisibleSel = sel_registerName(kMakeKeyAndVisibleSel); + maximumFramesPerSecond = sel_registerName(kMaximumFramesPerSecond); + setAutoresizingMaskSel = sel_registerName(kSetAutoresizingMaskSel); + + // UIViewController creation, display, and management selectors + initWithNibNameSel = sel_registerName(kInitWithNibNameSel); + stringWithUTF8StringSel = sel_registerName(kStringWithUTF8StringSel); + setTitleSel = sel_registerName(kSetTitleSel); + viewSel = sel_registerName(kViewSel); + setViewSel = sel_registerName(kSetViewSel); + presentViewControllerSel = sel_registerName(kPresentViewControllerSel); + dismissViewControllerSel = sel_registerName(kDismissViewControllerSel); + frameSel = sel_registerName(kFrameSel); + setFrameSel = sel_registerName(kSetFrameSel); + boundsSel = sel_registerName(kBoundsSel); + setBoundsSel = sel_registerName(kSetBoundsSel); + updateView = sel_registerName(kUpdateView); + + // Root view controller selector + setRootViewControllerSel = sel_registerName(kSetRootViewControllerSel); + + // GLKViewController lifecycle methods + viewDidLoadSel = sel_registerName(kViewDidLoadSel); + loadViewSel = sel_registerName(kLoadViewSel); + viewWillAppearSel = sel_registerName(kViewWillAppearSel); + viewDidAppearSel = sel_registerName(kViewDidAppearSel); + viewWillDisappearSel = sel_registerName(kViewWillDisappearSel); + viewDidDisappearSel = sel_registerName(kViewDidDisappearSel); + viewDidLayoutSubviewsSel = sel_registerName(kViewDidLayoutSubviewsSel); + drawRectSel = sel_registerName(kDrawRectSel); + + // Touch event methods selectors + touchesBeganSel = sel_registerName(kTouchesBeganSel); + touchesMovedSel = sel_registerName(kTouchesMovedSel); + touchesEndedSel = sel_registerName(kTouchesEndedSel); + touchesCancelledSel = sel_registerName(kTouchesCancelledSel); + allObjectsSel = sel_registerName(kAllObjectsSel); + majorRadius = sel_registerName(kMajorRadius); + + // Keyboard event methods selectors (for hardware keyboards) + pressesBeganSel = sel_registerName(kPressesBeganSel); + pressesEndedSel = sel_registerName(kPressesEndedSel); + keySel = sel_registerName(kKeySel); + keyCodeSel = sel_registerName(kKeyCodeSel); + charactersSel = sel_registerName(kCharactersSel); + modifierFlagsSel = sel_registerName(kModifierFlagsSel); + + // Touch and press event property selectors + locationInViewSel = sel_registerName(kLocationInViewSel); + allTouchesSel = sel_registerName(kAllTouchesSel); + countSel = sel_registerName(kCountSel); + objectAtIndexSel = sel_registerName(kObjectAtIndexSel); + keySel = sel_registerName(kKeySel); + charactersSel = sel_registerName(kCharactersSel); + + // EAGL and OpenGL ES management selectors + initWithAPISel = sel_registerName(kInitWithAPISel); + setCurrentContextSel = sel_registerName(kSetCurrentContextSel); + contextSel = sel_registerName(kContextSel); + setContextSel = sel_registerName(kSetContextSel); + drawableColorFormatSel = sel_registerName(kDrawableColorFormatSel); + setDrawableColorFormatSel = sel_registerName(kSetDrawableColorFormatSel); + drawableDepthFormatSel = sel_registerName(kDrawableDepthFormatSel); + setDrawableDepthFormatSel = sel_registerName(kSetDrawableDepthFormatSel); + drawableMultisampleSel = sel_registerName(kDrawableMultisampleSel); + setDrawableMultisampleSel = sel_registerName(kSetDrawableMultisampleSel); + setEnableSetNeedsDisplaySel = sel_registerName(kSetEnableSetNeedsDisplaySel); + bindDrawableSel = sel_registerName(kBindDrawableSel); + displaySel = sel_registerName(kDisplaySel); + presentRenderbufferSel = sel_registerName(kPresentRenderbufferSel); + setNeedsDisplaySel = sel_registerName(kSetNeedsDisplaySel); + setPreferredFramesPerSecondSel = sel_registerName(kSetPreferredFramesPerSecondSel); + setContentScaleFactor = sel_registerName(kSetContentScaleFactorSel); + contentScaleFactor = sel_registerName(kContentScaleFactorSel); + + // Device and orientation selectors + currentDeviceSel = sel_registerName(kCurrentDeviceSel); + orientationSel = sel_registerName(kOrientationSel); + isMultitouchEnabledSel = sel_registerName(kIsMultitouchEnabledSel); + setMultipleTouchEnabledSel = sel_registerName(kSetMultipleTouchEnabledSel); + setUserInteractionEnabledSel = sel_registerName(kSetUserInteractionEnabledSel); + isUserInteractionEnabledSel = sel_registerName(kIsUserInteractionEnabledSel); + deviceOrientationDidChangeSel = sel_registerName(kDeviceOrientationDidChangeSel); + beginGenDeviceOrientationNotifSel = sel_registerName(kBeginGenDeviceOrientationNotifSel); + + // UI Image + initWithContentsOfFileSel = sel_registerName(kInitWithContentsOfFileSel); + representationsSel = sel_registerName(kRepresentationsSel); + pixelsWideSel = sel_registerName(kPixelsWideSel); + pixelsHighSel = sel_registerName(kPixelsHighSel); + bitsPerPixelSel = sel_registerName(kBitsPerPixelSel); + bytesPerRowSel = sel_registerName(kBytesPerRowSel); + hasAlphaSel = sel_registerName(kHasAlphaSel); + bitmapDataSel = sel_registerName(kBitmapDataSel); + cgImageSel = sel_registerName(kCGImageSel); + dataWithBytesLengthSel = sel_registerName(kdataWithBytesLengthSel); + imageWithDataSel = sel_registerName(kImageWithDataSel); + + // NS Bundle selectors + mainBundleSel = sel_registerName(kMainBundleSel); + UTF8StringSel = sel_registerName(kUTF8StringSel); + bundlePathSel = sel_registerName(kbundlePathSel); + + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + + // Notification classes and selectors + defaultCenterSel = sel_registerName(kDefaultCenterSel); + addObserverSel = sel_registerName(kAddObserverSel); + } -} -void view_mouseExited(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - - // Ensure cursor is visible when leaving window - bAllowHideCursor = false; - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); - - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); + } - convertToContentViewCoordinates(location); +} - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { - gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } +// Single initialization function called once at startup +void initialize_ios_api() { + ObjectiveCSEL::initializeSelectors(); } +// Concepts safety templates for Objective-C interactions +template +concept CallbackType = std::is_function_v>; // Checks if T is a function type -void view_touchesBegan(id self, SEL _cmd, id event) { - (void)_cmd; - - // Ensure event is not null before parsing - if (!event) return; - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseBegan, - gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); - } +template +concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) + +// TODO: May not be required as iOS handles memory management more automatically than macOS in most cases +// Autorelease pool management functions +extern "C" { + extern void* objc_autoreleasePoolPush(void); + extern void objc_autoreleasePoolPop(void* pool); + extern void* NSStringFromClass(Class aClass); + // Add UIApplicationMain declaration + extern int UIApplicationMain(int argc, char* argv[], id principalClassName, id delegateClassName); } -void view_touchesMoved(id self, SEL _cmd, id event) { - (void)_cmd; - // Ensure event is not null before parsing - if (!event) return; +// Objective-C autorelease pools +class AutoreleasePool { +public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseMoved, - gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); - } -} + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; -void view_touchesEnded(id self, SEL _cmd, id event) { - (void)_cmd; - // Ensure event is not null before parsing - if (!event) return; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseEnded, - gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); - } -} +private: + void* pool_; +}; -void view_touchesCancelled(id self, SEL _cmd, id event) { - (void)_cmd; - // Ensure event is not null before parsing - if (!event) return; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - updateTouchData(event, NSTouchPhaseCancelled, - gptrNSWindowEvents->touchCancelledCallback, gptrNSWindowEvents->touchUserData, true); - } -} +// TODO: End -//====================================================================// -// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) +// Keeps our C --> Objective-C function pointer types clean +using NSInteger = long; +using NSUInteger = unsigned long; -// This should only be called when a Stylus driver is not installed or it an Apple device -void view_tabletProximity(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - // Handle tablet proximity events here - BOOL entering = ((BOOL(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::penIsEnteringProximitySel); - // For furture support should call a stylusCallback with proximity event data, but for now just set the flag - gptrNSWindowEvents->bStylusInProximity = (entering == YES); +/* +* WARNING: Global delegate pointers for Objective-C callbacks +* These global pointers are used to route Objective-C delegate callbacks +* to the appropriate C++ structures and methods. +* +* In short: Don't mess with these unless you know what you're doing! +* +* Global variables are evil, but sometimes necessary for bridging C++ and Objective-C. +*/ +static ViewController* gptrVeiwControllerEvents = nullptr; +static GLKView* gptrGLKViewEvents = nullptr; +static Application* gptrApplicationDelegate = nullptr; +static ViewController* gptrViewControllerDelegate = nullptr; + +// TODO: May not be needed +// Forward declarations of internal structures +struct Application; +struct ApplicationDelegate; +void updateView(id self, SEL _cmd); +void drawRect(id self, SEL _cmd, CGRect rect); +id createCustomGLKView(CGRect frame); +void viewcontroller_enableFlexibleSizing(struct ViewController* self); +struct ViewController; +struct ViewControllerDelegate; +struct OpenGLRenderer; +struct ImageLoader; + +// Forward declarations of internal structures +struct Application { + id uiApp{nullptr}; // UIApplication instance + id delegate{nullptr}; // ApplicationDelegate + id uiWindow{nullptr}; // UIWindow instance + + // Callback function pointers + std::function willFinishLaunchingCallback{nullptr}; + std::function didFinishLaunchingCallback{nullptr}; + std::function willTerminateCallback{nullptr}; + std::function didBecomeActiveCallback{nullptr}; + std::function willResignActiveCallback{nullptr}; + std::function didEnterBackgroundCallback{nullptr}; + std::function willEnterForegroundCallback{nullptr}; + + void* willFinishLaunchingUserData{nullptr}; // User data for will finish launching callback + void* didFinishLaunchingUserData{nullptr}; // User data for did finish launching callback + void* willTerminateUserData{nullptr}; // User data for will terminate callback + void* didBecomeActiveUserData{nullptr}; // User data for did become active callback + void* willResignActiveUserData{nullptr}; // User data for will resign active callback + void* didEnterBackgroundUserData{nullptr}; // User data for did enter background callback + void* willEnterForegroundUserData{nullptr}; // User data for will enter foreground + + // Method function pointers + // TODO: Update order of start upo events to suit iOS lifecycle + void (*initialize) (struct Application* self){nullptr}; + void (*activate) (struct Application* self){nullptr}; + void (*run) (struct Application* self){nullptr}; + void (*destroy) (struct Application* self){nullptr}; + + std::string appPath = ""; // Application path + const char* (*getAppPath) (struct Application* self); + + Application() = default; + + ~Application() { + if (destroy) { + destroy(this); + } } -} + + // Delete copy constructor and assignment + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + // Allow move semantics + Application(Application&&) = default; + Application& operator=(Application&&) = default; + +}; -// This should only be called when a Stylus driver is not installed or it an Apple device -void view_tabletPoint(id self, SEL _cmd, id event) { - (void)self;(void)_cmd;(void)event; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { - // Handle tablet point events here - } -} +// Handles UIApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; +struct ViewController { + id viewController; // UIViewController instance + id glkview; // GLKView instance + id delegate; // ViewControllerDelegate instance + char* title; // Title of the view controller + CGRect frame; // Frame of the view controller + struct GLKView* glkViewStruct; // Reference to GLKView struct for touch handling + + // Lifecycle callbacks + std::function viewDidLoadCallback{nullptr}; + std::function viewWillAppearCallback{nullptr}; + std::function viewDidAppearCallback{nullptr}; + std::function viewWillDisappearCallback{nullptr}; + std::function viewDidDisappearCallback{nullptr}; + std::function viewDidLayoutSubviewsCallback{nullptr}; + std::function orientationChangedCallback{nullptr}; + std::function updateCallback{nullptr}; + + + void* viewDidLoadUserData{nullptr}; // User data for viewDidLoad callback + void* viewWillAppearUserData{nullptr}; // User data for viewWillAppear callback + void* viewDidAppearUserData{nullptr}; // User data for viewDidAppear callback + void* viewWillDisappearUserData{nullptr}; // User data for viewWillDisappear callback + void* viewDidDisappearUserData{nullptr}; // User data for viewDidDisappear callback + void* viewDidLayoutSubviewsUserData{nullptr}; // User data for viewDidLayout + void* orientationChangedUserData{nullptr}; // User data for orientation changed callback + void* updateUserData{nullptr}; // User data for update callback + +}; -void view_updateTrackingAreas(id self, SEL _cmd) { - (void)self;(void)_cmd; +struct GLKView { + id glkView; // GLKView instance + struct ViewController* parentViewController; // Reference to parent view controller + + // Touch callbacks + std::function touchBeganCallback{nullptr}; + std::function touchMovedCallback{nullptr}; + std::function touchEndedCallback{nullptr}; + std::function touchCancelledCallback{nullptr}; - // Create new tracking area - NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); - // Correct tracking options: mouse entered/exited + active in key window - NSUInteger options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; + void* touchBeganUserData {nullptr}; // User data for touch began callback + void* touchMovedUserData {nullptr}; // User data for touch moved callback + void* touchEndedUserData {nullptr}; // User data for touch ended callback + void* touchCancelledUserData {nullptr}; // User data for touch cancelled callback + + // Event callback function pointers with nullptr initialization + std::function keyDownCallback{nullptr}; + std::function keyUpCallback{nullptr}; - Class NSTrackingAreaClass = objc_getClass(kTrackingAreaClass); - id trackingArea = ((id(*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSTrackingAreaClass, ObjectiveCSEL::allocSel), - ObjectiveCSEL::initWithRectSel, bounds, options, self, nil); + void* keyDownUserData{nullptr}; // User data for key down callback + void* keyUpUserData {nullptr}; // User data for key up callback - ((void(*)(id, SEL, id))objc_msgSend)(self, ObjectiveCSEL::addTrackingAreaSel, trackingArea); -} - -// Determine if view can accept first responder status -id view_acceptsFirstResponder(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; // Return YES to accept first responder status -} + // Draw callback function pointer + std::function drawCallback{nullptr}; + void* drawUserData{nullptr}; // User data for draw callback +}; -// Make the view the first responder -id view_becomeFirstResponder(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; // Return YES to become first responder -} +struct ViewControllerDelegate { + Class isa; // Objective-C class pointer (required) +}; -// Determine if the view can become key view -id view_canBecomeKeyView(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; -} +struct OpenGLRenderer { + id eaglContext {nullptr}; // EAGLContext instance + id eaglView {nullptr}; // Custom UIView with CAEAGLLayer + id viewController{nullptr}; // UIViewController instance + id glkView {nullptr}; // GLKView + id displayLink {nullptr}; // CADisplayLink instance + BOOL vsyncEnabled{YES}; // VSync enabled flag + unsigned int framebuffer{0}; // OpenGL framebuffer ID + unsigned int colorRenderbuffer{0}; // Color renderbuffer ID + unsigned int depthRenderbuffer{0}; // Depth renderbuffer ID -// Determine if the view needs the panel to become key -id view_needsPanelToBecomeKey(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; -} +}; -// Empty drawRect method - prevents automatic drawing on main thread -void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { - (void)self;(void)_cmd;(void)dirtyRect; -} +// Modern image loading and pixel data extraction +struct ImageLoader { + + unsigned char* 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) + int bytesPerRow {kZeroRows}; // Number of bytes per row + BOOL hasAlpha {NO}; // Whether image has alpha channel + std::string appPath = ""; // Application path -// Empty reshape method - prevents automatic context reshaping on main thread -void view_reshape(id self, SEL _cmd) { - (void)self;(void)_cmd; -} + // Method function pointers with nullptr initialization + char (*getAppPath) (const struct ImageLoader* self){nullptr}; + BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; + 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}; + void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; + BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; + BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; + unsigned int (*createOpenGLTexture) (const struct ImageLoader* self){nullptr}; +}; -// Empty update method - prevents automatic context updates on main thread -void view_update(id self, SEL _cmd) { - (void)self;(void)_cmd; +// TODO: Update MacOS to use these safe Objective-C functions for better error handling and safety +static Class safe_objc_getClass(const char* name) { + Class cls = objc_getClass(name); + if (!cls) { + printf("Error: Could not find class %s\n", name); + return nil; + } + return cls; } -// Create custom OpenGL view class -Class createCustomOpenGLViewClass() { - Class existingClass = objc_getClass(kCustomOpenGLViewClass); - if (existingClass) { - return existingClass; - } - - // Get NSOpenGLView class as superclass - Class NSOpenGLViewClass = objc_getClass(kNSOpenGLViewClass); - - // Create a new class inheriting from NSOpenGLView - Class CustomViewClass = objc_allocateClassPair(NSOpenGLViewClass, kCustomOpenGLViewClass, 0); - - if (!CustomViewClass) { +// TODO: Remove Unused +static SEL safe_sel_registerName(const char* name) { + SEL sel = sel_registerName(name); + if (!sel) { + printf("Error: Could not register selector %s\n", name); return NULL; } - - // Keyboard event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::keyDownSel, (IMP)view_keyDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::keyUpSel, (IMP)view_keyUp, kEventHandlerMethodTypeEncoding); - - // Flags Changed event handler - class_addMethod(CustomViewClass, ObjectiveCSEL::flagsChangedSel, (IMP)view_flagsChanged, kEventHandlerMethodTypeEncoding); - - // Mouse event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDownSel, (IMP)view_mouseDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseUpSel, (IMP)view_mouseUp, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDraggedSel, (IMP)view_mouseDragged, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseMovedSel, (IMP)view_mouseMoved, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDownSel, (IMP)view_rightMouseDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseUpSel, (IMP)view_rightMouseUp, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::rightMouseDraggedSel, (IMP)view_rightMouseDragged, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDownSel, (IMP)view_otherMouseDown, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseUpSel, (IMP)view_otherMouseUp, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); - - // Touch event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)view_touchesBegan, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)view_touchesMoved, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)view_touchesEnded, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)view_touchesCancelled, kTouchEventMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::pressureChangeSel, (IMP)view_pressureChange, kEventHandlerMethodTypeEncoding); - - // Tablet / stylus event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::tabletProximitySel, (IMP)view_tabletProximity, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::tabletPointSel, (IMP)view_tabletPoint, kEventHandlerMethodTypeEncoding); - - // Area tracking for Mouse entered/exited event handler methods - class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::mouseExitedSel, (IMP)view_mouseExited, kEventHandlerMethodTypeEncoding); - - // First responder methods - class_addMethod(CustomViewClass, ObjectiveCSEL::acceptsFirstResponderSel, (IMP)view_acceptsFirstResponder, kBoolMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::becomeFirstResponderSel, (IMP)view_becomeFirstResponder, kBoolMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::canBecomeKeyViewSel, (IMP)view_canBecomeKeyView, kBoolMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::needsPanelToBecomeKeySel, (IMP)view_needsPanelToBecomeKey, kBoolMethodTypeEncoding); + return sel; +} - // DrawRect method - class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); +static id safe_objc_msgSend(id self, SEL op) { + if (!self || !op) return nil; + return ((id(*)(id, SEL))objc_msgSend)(self, op); +} - // Block automatic OpenGL operations on main thread - class_addMethod(CustomViewClass, ObjectiveCSEL::reshapeSel, (IMP)view_reshape, kVoidMethodTypeEncoding); - class_addMethod(CustomViewClass, ObjectiveCSEL::updateSel, (IMP)view_update, kVoidMethodTypeEncoding); +static id safe_objc_msgSend_id(id self, SEL op, id arg) { + if (!self || !op) return nil; + return ((id(*)(id, SEL, id))objc_msgSend)(self, op, arg); +} - // Register the class with the runtime - objc_registerClassPair(CustomViewClass); - - return CustomViewClass; +static void safe_objc_msgSend_void(id self, SEL op) { + if (!self || !op) return; + ((void(*)(id, SEL))objc_msgSend)(self, op); } -// Forward declaration for C API function used by window event handlers -extern "C" void window_updateFrameFromOSX(Window* self); +static void safe_objc_msgSend_void_bool(id self, SEL op, BOOL arg) { + if (!self || !op) return; + ((void(*)(id, SEL, BOOL))objc_msgSend)(self, op, arg); +} -// handle window resize events -void windowDidResize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; +// TODO: End - if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { - // Update frame data only - window_updateFrameFromOSX(gptrWindowDelegate); +// ============================================================================ +// IMPLEMENTATION OF C API FUNCTIONS +// ============================================================================ - // Get the new content view size - double width, height; - window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); - if (gptrWindowDelegate->windowDidResizeCallback) { - gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); - } - +// Called when application is about to terminate +void applicationWillTerminate(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->willTerminateCallback) { + gptrApplicationDelegate->willTerminateCallback(gptrApplicationDelegate->willTerminateUserData); } } -// handle window will close events -void windowWillClose(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - 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 -void windowDidBecomeKey(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidBecomeKeyCallback) { - gptrWindowDelegate->windowDidBecomeKeyCallback(gptrWindowDelegate->windowDidBecomeKeyUserData); + +// Called when application becomes active +void applicationDidBecomeActive(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->didBecomeActiveCallback) { + gptrApplicationDelegate->didBecomeActiveCallback(gptrApplicationDelegate->didBecomeActiveUserData); } } -// handle window did resign key events -void windowDidResignKey(id self, SEL _cmd, id notification) { +// Called when application resigns active status +void applicationWillResignActive(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidResignKeyCallback) { - gptrWindowDelegate->windowDidResignKeyCallback(gptrWindowDelegate->windowDidResignKeyUserData); + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->willResignActiveCallback(gptrApplicationDelegate->willResignActiveUserData); } } -// Handle window did miniaturize events -void windowDidMiniaturize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidMiniaturizeCallback) { - gptrWindowDelegate->windowDidMiniaturizeCallback(gptrWindowDelegate->windowDidMiniaturizeUserData); + +// Called when application resigns active status +void applicationDidEnterBackground(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->didEnterBackgroundCallback(gptrApplicationDelegate->didEnterBackgroundUserData); } } -// Handle window did deminiaturize events -void windowDidDeminiaturize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; - if (gptrWindowDelegate && gptrWindowDelegate->windowDidDeminiaturizeCallback) { - gptrWindowDelegate->windowDidDeminiaturizeCallback(gptrWindowDelegate->windowDidDeminiaturizeUserData); +// Called when application resigns active status +void applicationWillEnterForeground(id self, SEL _cmd, id application) { + (void)self;(void)_cmd;(void)application; + if (gptrApplicationDelegate && gptrApplicationDelegate->willResignActiveCallback) { + gptrApplicationDelegate->willEnterForegroundCallback(gptrApplicationDelegate->willEnterForegroundUserData); } } -// Create and configure window delegate -id createWindowDelegate(OpenGLRenderer* renderer) { - // Get NSObject class as superclass using const string - Class NSObjectClass = objc_getClass(kNSObjectClass); - - // Create a new class inheriting from NSObject using const string - Class WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kWindowDelegateClass, kZeroBytes); - - // Add window delegate methods - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); - - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + +// Called when application has finished launching - iOS version +BOOL applicationDidFinishLaunchingWithOptions(id self, SEL _cmd, id application, id launchOptions) { + (void)self;(void)_cmd;(void)application;(void)launchOptions; - // Store renderer reference in delegate - struct WindowDelegate* windowDelegate = (struct WindowDelegate*)delegate; - windowDelegate->renderer = renderer; + if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { + gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); + } - return delegate; + return YES; // Must return BOOL for iOS delegate method } -// Create a window delegate for general window events -id createWindowDelegateForWindow(void) { - // Check if WindowDelegate class already exists to avoid duplicate registration - static Class WindowDelegateClass = NULL; - if (!WindowDelegateClass) { - // Get NSObject class as superclass using const string - Class NSObjectClass = objc_getClass(kNSObjectClass); - - // Create a new class inheriting from NSObject using const string - WindowDelegateClass = objc_allocateClassPair(NSObjectClass, kGeneralWindowDelegateClass, kZeroBytes); - if (!WindowDelegateClass) { - return NULL; - } +// Create and configure application delegate +id createApplicationDelegate(void) { - // Window delegate methods - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); - class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); + // Get NSObject class as superclass + Class NSObjectClass = objc_getClass(kNSObjectClass); + // Create a new class inheriting from NSObject using const string + Class AppDelegateClass = objc_allocateClassPair(NSObjectClass, kolcPGE3AppDelegateClass, 0); + + // Add application delegate methods + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillTerminateSel, (IMP)applicationWillTerminate, kIOSAppMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidBecomeActiveSel, (IMP)applicationDidBecomeActive, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillResignActiveSel, (IMP)applicationWillResignActive, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidEnterBackgroundSel, (IMP)applicationDidEnterBackground, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationWillEnterForegroundSel, (IMP)applicationWillEnterForeground, kEventHandlerMethodTypeEncoding); + class_addMethod(AppDelegateClass, ObjectiveCSEL::applicationDidFinishLaunchingSel, (IMP)applicationDidFinishLaunchingWithOptions, kIOSAppDelegateMethodTypeEncoding); - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); - } + // Register the class with the runtime + objc_registerClassPair(AppDelegateClass); // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); + id delegate = ((id(*)(Class, SEL))objc_msgSend)(AppDelegateClass, ObjectiveCSEL::allocSel); delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + // Retain the delegate to ensure it stays alive + if (delegate) { + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::retainSel); + } + return delegate; } - // Forward declarations for C API functions used by internal helpers - extern "C" void window_setDelegate(Window* self, id delegate); - -extern "C" { - // Initialize the application - void application_initialize(Application* self) { - - // Initialize cached selectors for performance - initialize_macos_api(); - - // Set global application reference for delegate callbacks - gptrApplicationDelegate = self; - - // Get NSApplication - Class NSApplicationClass = objc_getClass(kNSApplicationClass); - self->nsApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, ObjectiveCSEL::sharedApplicationSel); - - // Set activation policy to regular - ((void(*)(id, SEL, NSInteger))objc_msgSend)(self->nsApp, ObjectiveCSEL::setActivationPolicySel, NSApplicationActivationPolicyRegular); - - // Create and set application delegate - self->delegate = createApplicationDelegate(); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::setDelegateSel, self->delegate); - } - // Activate the application - void application_activate(Application* self) { - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); - } - // Run the application event loop - void application_run(Application* self) { - ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); - } +// Application API Implementation +struct Application* application_init(void) { + + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - // 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::stopSel, self->nsApp); - } - } + Application* app = new Application(); - // Destroy the application - void application_destroy(Application* self) { - if (self) { - delete self; - } - } + // Assign the method pointers + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getAppPath = application_getApplicationPath; + + return app; +} - // Get system locale identifier - const char* application_getSystemLocale(Application* self) { - (void)self; - - // Get NSLocale class - Class NSLocaleClass = objc_getClass(kNSLocaleClass); - - // Get current locale - id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); - - // Get locale identifier - id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); - - // Convert to C string (en-US, en-GB, en-IE etc) - const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::utf8StringSel); - - return localeIdentifier; - } - // Ensure window has a delegate for window events - void ensureWindowDelegate(struct Window* self) { - // If the windows is created but has no delegate, create and set one - if (self && !self->delegate) { - // Create a window delegate - id delegate = createWindowDelegateForWindow(); - if (delegate) { - // Set it on the window - window_setDelegate(self, delegate); - } +char* get_application_path(void) +{ + const char* pathCString = kDefaultAppPath; + + // Get application path using NSBundle + Class bundleClass = objc_getClass(kNSBundleClass); + + // Get main bundle + id mainBundle = ((id(*)(Class, SEL))objc_msgSend)(bundleClass, ObjectiveCSEL::mainBundleSel); + + if (mainBundle) { + // Get bundle path as NSString + id bundlePath = ((id(*)(id, SEL))objc_msgSend)(mainBundle, ObjectiveCSEL::bundlePathSel); + + if (bundlePath) { + // Convert NSString to C string + pathCString = ((const char*(*)(id, SEL))objc_msgSend)(bundlePath, ObjectiveCSEL::UTF8StringSel); } } + return const_cast(pathCString); +} - // Application constructor - Application* application_init() { - - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized +// iOS specific application start and run function +int run_ios_application(int argc, char* argv[]) { + + // Get NSAutoreleasePool for memory management + Class poolClass = objc_getClass(kNSAutoreleasePoolClass); - Application* app = new Application(); + // Get the application pool instance + id poolInstance = ((id(*)(Class, SEL))objc_msgSend)(poolClass, ObjectiveCSEL::allocSel); + id pool = ((id(*)(id, SEL))objc_msgSend)(poolInstance, ObjectiveCSEL::initSel); - // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; - app->getSystemLocale = application_getSystemLocale; + // Get PGE AppDelegate class so that iOS Event fires on our host_apple_ios + Class appDelegateClass = objc_getClass(kolcPGE3AppDelegateClass); + id delegateClassName = (id)NSStringFromClass(appDelegateClass); - return app; - } + // Call UIApplicationMain to start running with our delegate + int result = UIApplicationMain(argc, argv, nil, delegateClassName); - // Create the NSWindow instance - void window_create(Window* self, unsigned long styleMask) { - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + // Clean up + ((void(*)(id, SEL))objc_msgSend)(pool, ObjectiveCSEL::drainSel); - // Get classes using const strings - Class NSWindowClass = objc_getClass(kNSWindowClass); - Class NSStringClass = objc_getClass(kNSStringClass); + return result; +} - // Create window - id windowAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSWindowClass, ObjectiveCSEL::allocSel); - - 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 +void application_initialize(struct Application* self) { - // Set window title using stored title or default - const char* titleToUse = self->title ? self->title : kWindowTitle; - id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, titleToUse); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); + // Initialize cached selectors + initialize_ios_api(); + + // Set global application reference + gptrApplicationDelegate = self; + + // Create and set application delegate + self->delegate = createApplicationDelegate(); + + // Set the appPath + self->appPath = get_application_path(); + + // Set the correct args + int argc = 1; + char* argv[2]; + argv[0] = const_cast(self->appPath.c_str()); + argv[1] = nullptr; + + // Start and run the iOS application + int exitCode = run_ios_application(argc, argv); + (void)exitCode; // Currently unused, but could be logged or handled + + } - // Set the content view frame - NSRect contentFrame = {self->windowFrame.x, self->windowFrame.y, self->windowFrame.width, self->windowFrame.height}; - if (self->nsWindow) { - id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { - ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); - } - } - window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, - &self->contentViewFrame.width, &self->contentViewFrame.height); - } - // Show the window and set up event handling - void window_show(Window* self) { +void application_activate(struct Application* self) { + if(!self) return; + + // Create window programmatically + Class windowClass = objc_getClass(kUIWindowClass); + Class screenClass = objc_getClass(kUIScreenClass); + + // Get main screen bounds + id mainScreen = ((id(*)(Class, SEL))objc_msgSend)(screenClass,ObjectiveCSEL::mainScreenSel); + CGRect screenBounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::boundsSel); + + // Create window + id window = ((id(*)(Class, SEL))objc_msgSend)(windowClass, ObjectiveCSEL::allocSel); + window = ((id(*)(id, SEL, CGRect))objc_msgSend)(window, ObjectiveCSEL::initWithFrameSel, screenBounds); + + // Create a basic view controller as root view controller + Class vcClass = objc_getClass(kUIViewControllerClass); + id rootViewController = ((id(*)(Class, SEL))objc_msgSend)(vcClass, ObjectiveCSEL::allocSel); + rootViewController = ((id(*)(id, SEL))objc_msgSend)(rootViewController, ObjectiveCSEL::initSel); + + // Set root view controller + ((void(*)(id, SEL, id))objc_msgSend)(window, ObjectiveCSEL::setRootViewControllerSel, rootViewController); + + // Make window visible + ((void(*)(id, SEL))objc_msgSend) (window, ObjectiveCSEL::makeKeyAndVisibleSel); - // Show window - ((void(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::orderFrontRegardlessSel); - - // Enable mouse moved events - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setAcceptsMouseMovedEventsSel, YES); - - // Ensure window delegate is created and set - ensureWindowDelegate(self); - if (self->delegate) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, self->delegate); - } + // Store window reference + self->uiWindow = window; + + // Activate the UIApplication once (There can only be one!) + if(!self->uiApp) + { + Class UIApplicationClass = objc_getClass(kUIApplicationClass); + self->uiApp = ((id(*)(Class, SEL))objc_msgSend)(UIApplicationClass, ObjectiveCSEL::sharedApplicationSel); - // Make the window key to ensure it can receive keyboard events - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::makeKeyAndOrderFrontSel, NULL); + // Disable idle timer to prevent screen from dimming during gameplay + safe_objc_msgSend_void_bool(self->uiApp, ObjectiveCSEL::setIdleTimerDisabledSel, YES); } + - // Destroy the window - void window_destroy(Window* self) { - (void)self; - // NSWindow will be cleaned up by autorelease pool - } +} +void application_run(struct Application* self) { + if (!self || !self->uiApp) return; - // Window delegate setter - void window_setDelegate(Window* self, id delegate) { - self->delegate = delegate; - - // Set global window reference for delegate callbacks - gptrWindowDelegate = self; - - // Set the delegate on the actual NSWindow - if (self->nsWindow && delegate) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, delegate); - } - } + // In iOS, we don't typically call UIApplicationMain from here + // The main run loop is handled by the system + // This is more of a placeholder for consistency with macOS API + // Left in for potential future use or custom run loop handling if needed +} - // Removes the delegate from the window and clears the reference to prevent accidental use after close - void window_removeDelegate(Window* self) { - - if (self->nsWindow) { - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, nil); - } - self->delegate = NULL; - +void application_destroy(struct Application* self) { + if (!self) return; + + if (self->delegate) { + safe_objc_msgSend_void(self->delegate, ObjectiveCSEL::releaseSel); } + + free(self); +} - // Window title getter and setter - const char* window_getTitle(const Window* self) { - return self->title; - } +const char* application_getApplicationPath(Application* self) { + (void)self; + return get_application_path(); +} - // Window title setter - void window_setTitle(Window* self, const char* title) { - self->title = title; +// Get system locale identifier +const char* application_getSystemLocale(Application* self) { + (void)self; + + // Get NSLocale class + Class NSLocaleClass = objc_getClass(kNSLocaleClass); + + // Get current locale + id currentLocale = ((id(*)(Class, SEL))objc_msgSend)(NSLocaleClass, ObjectiveCSEL::currentLocaleSel); + + // Get locale identifier + id localeIdentifierNS = ((id(*)(id, SEL))objc_msgSend)(currentLocale, ObjectiveCSEL::localeIdentifierSel); + + // Convert to C string (en-US, en-GB, en-IE etc) + const char* localeIdentifier = ((const char*(*)(id, SEL))objc_msgSend)(localeIdentifierNS, ObjectiveCSEL::UTF8StringSel); + + return localeIdentifier; +} - if (self->nsWindow) { - Class NSStringClass = objc_getClass(kNSStringClass); +// ============================================================================ +// TOUCH AND KEYBOARD EVENT HANDLER IMPLEMENTATIONS +// ============================================================================ - id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); - } - } +// iOS Touch Event Data Structure +struct iOSTouchEventData { + uint32_t touchID = 0; // Stable sequential touch ID + CGPoint location = {0.0, 0.0}; // Touch location in view coordinates + CGSize touchSize = {1.0, 1.0}; // Touch size (width, height) + bool bIsActive = true; // Touch is active (true for Began/Moved, false for Ended/Cancelled) +}; - // Refresh internal frame representation from actual NSWindow (OSX) - void window_updateFrameFromOSX(Window* self) { - self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); - } +// Maps UITouch object pointers to stable uint32_t touch IDs +static std::unordered_map siOSTouchIDMap; +static uint32_t siOSNextTouchID = 0; +static std::unordered_map siOSTouchIDEvents; - // Get the content view size (excludes title bar and borders) - void window_getContentViewFrame(const Window* self, double* x, double* y, double* width, double* height) { - if (!self || !self->nsWindow) { - if (x) *x = kMinValidDimension; - if (y) *y = kMinValidDimension; - if (width) *width = kDefaultWindowWidth; - if (height) *height = kDefaultWindowHeight; - return; - } +// Convert touch location to physical pixel coordinates for PGE +void convertToContentViewCoordinates(CGPoint& location, id view) { + if (!view) return; + + // Get the content scale factor for Retina displays (1.0, 2.0, or 3.0) + CGFloat scale = ((CGFloat(*)(id, SEL))objc_msgSend)(view, ObjectiveCSEL::contentScaleFactor); + + // Convert from logical points to physical pixels + location.x *= scale; + location.y *= scale; + +} + +// Iterate through touches, extract data, and fire the callback +// bRemove: true when processing Ended or Cancelled events (clean up touch tracking) +static void updateiOSTouchData(id touches, + const std::function& callback, + void* userData, bool bRemove = false) +{ + if (!callback) return; + + NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touches, ObjectiveCSEL::countSel); + if (count == 0) return; + + id touchArray = ((id(*)(id, SEL))objc_msgSend)(touches, ObjectiveCSEL::allObjectsSel); + + for (NSUInteger i = 0; i < count; ++i) { + id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); + if (!touch) continue; - // Get the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); - if (!contentView) { - if (x) *x = kMinValidDimension; - if (y) *y = kMinValidDimension; - if (width) *width = kDefaultWindowWidth; - if (height) *height = kDefaultWindowHeight; - return; + iOSTouchEventData data; + + // Get the view from the touch + id touchView = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::viewSel); + + // Get touch location in view coordinates (logical points) + CGPoint location = ((CGPoint(*)(id, SEL, id))objc_msgSend)(touch, ObjectiveCSEL::locationInViewSel, touchView); + + // Convert to physical pixels and flip Y coordinate for OpenGL/PGE + convertToContentViewCoordinates(location, touchView); + + // get the UITouch Major and Minor radius (touch size) if available, otherwise default to 1.0 + CGFloat majorRadius = 1.0; + if (touch && ObjectiveCSEL::majorRadius) { + majorRadius = ((CGFloat(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::majorRadius); } - // Thank you, Ben the ultimate Guru, - // Get the content view bounds in points (logical coordinates). - // On macOS, AppKit uses points rather than physical pixels. On HighDPI - // displays (that are scaling) a point maps to >1 pixels. - // convertRectToBacking: converts the point-based rect to physical pixels, - // which is what OpenGL expects - NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + + data.location = location; + data.touchSize = {majorRadius, majorRadius}; + data.bIsActive = !bRemove; - if (x) *x = pixelBounds.x; - if (y) *y = pixelBounds.y; - if (width) *width = pixelBounds.width; - if (height) *height = pixelBounds.height; + // Assign a stable sequential uint32_t ID using the UITouch object pointer as key + uintptr_t key = reinterpret_cast(touch); - } - - // Get the content view size (excludes title bar and borders) - void window_setContentViewFrame(struct Window* self, double* x, double* y, double* width, double* height) { - if (!self || !self->nsWindow) { - if (x) *x = kMinValidDimension; - if (y) *y = kMinValidDimension; - if (width) *width = kDefaultWindowWidth; - if (height) *height = kDefaultWindowHeight; - return; + auto [it, inserted] = siOSTouchIDMap.emplace(key, siOSNextTouchID); + if (inserted) ++siOSNextTouchID; + uint32_t tid = it->second; + + data.touchID = tid; + siOSTouchIDEvents.emplace(key, data); + + // Fire the callback with the stable touch ID and coordinates + callback(tid, data.location.x, data.location.y, data.touchSize.width, data.touchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) { + siOSTouchIDEvents.erase(key); + siOSTouchIDMap.erase(key); } - - // Set the content view frame - NSRect contentFrame = {x ? *x : 0, y ? *y : 0, width ? *width : 0, height ? *height : 0}; - if (self->nsWindow) { - id contentView = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::contentViewSel); - if (contentView) { - ((void(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::setFrameSel, contentFrame); - } - } } - - // Get Window frame (x, y, width, height) - void window_getWindowFrame(const Window* self, double* x, double* y, double* width, double* height) { - - window_updateFrameFromOSX(const_cast(self)); - - auto posX = self ? self->windowFrame.x : kMinValidDimension; - auto posY = self ? self->windowFrame.y : kMinValidDimension; - auto w = self ? self->windowFrame.width : kDefaultWindowWidth; - auto h = self ? self->windowFrame.height : kDefaultWindowHeight; - if (x) *x = posX; - if (y) *y = posY; - if (width) *width = w; - if (height) *height = h; + + // Reset touch ID counter when all touches are released + if (bRemove && siOSTouchIDMap.size() == 0) { + siOSNextTouchID = 0; } +} - // Get window position (x, y) - void window_getWindowPosition(const struct Window* self, double* x, double* y) { - window_getWindowFrame(self, x, y, nullptr, nullptr); - } +// Touch event handlers - called by Objective-C runtime +void touchesBegan(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchBeganCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchBeganCallback, gptrGLKViewEvents->touchBeganUserData, false); +} - // Get Window size (width, height) - void window_getWindowSize(const Window* self, double* width, double* height) { - window_getWindowFrame(self, nullptr, nullptr, width, height); - } +void touchesMoved(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchMovedCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchMovedCallback, gptrGLKViewEvents->touchMovedUserData, false); +} - // Set window frame (x, y, width, height) - void window_setWindowFrame(Window* self, double x, double y, double width, double height) { - if (self) { - // Update internal frame representation - self->windowFrame.x = x; - self->windowFrame.y = y; - self->windowFrame.width = width; - self->windowFrame.height = height; +void touchesEnded(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchEndedCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchEndedCallback, gptrGLKViewEvents->touchEndedUserData, true); +} - // Create new NSRect for the frame - NSRect newFrame = {x, y, width, height}; - - // Set the frame on the actual NSWindow - if (self->nsWindow) { - ((void(*)(id, SEL, NSRect, BOOL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setFrameDisplaySel, newFrame, YES); - } - } - } +void touchesCancelled(id self, SEL _cmd, id touches, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !gptrGLKViewEvents->touchCancelledCallback) return; + + updateiOSTouchData(touches, gptrGLKViewEvents->touchCancelledCallback, gptrGLKViewEvents->touchCancelledUserData, true); +} - // Set window position (x, y) - void window_setWindowPosition(Window* self, double x, double y) { - window_setWindowFrame(self, x, y, self->windowFrame.width, self->windowFrame.height); - } +// Structure to hold common key event data +struct KeyEventData { + uint16_t keyCode = 0; + const char* characters = nullptr; + uint modifierFlags = 0; +}; - // Set window size (width, height) - void window_setWindowSize(Window* self, double width, double height) { - window_setWindowFrame(self, self->windowFrame.x, self->windowFrame.y, width, height); - } +// Extract common key event data from NSEvent and press +KeyEventData extractKeyEventData(id event, id press) { + KeyEventData data; + id key = ((id(*)(id, SEL))objc_msgSend)(press, ObjectiveCSEL::keySel); + data.keyCode = ((unsigned short(*)(id, SEL))objc_msgSend)(key, ObjectiveCSEL::keyCodeSel); + id characters = ((id(*)(id, SEL))objc_msgSend)(key, ObjectiveCSEL::charactersSel); + data.characters = ((const char*(*)(id, SEL))objc_msgSend)(characters, ObjectiveCSEL::UTF8StringSel); + data.modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + // printf("Key Event: keyCode=%u, characters=%s, modifierFlags=%u\n", data.keyCode, data.characters ? data.characters : "null", data.modifierFlags); + return data; +} - // Set the cusor position relative to the content view - void window_setCursorPosition(Window* self, double x, double y) { - if (!self || !self->nsWindow) return; - - // Get current screen the window is on - id currentScreen = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::screenSel); - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); +// Keyboard event handlers +void pressesBegan(id self, SEL _cmd, id presses, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !presses) return; // edge case with presses been null when bluetooth keyboard is disconnected + + // Get an NS array of all presses from the NSSet + id pressesArray = ((id(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::allObjectsSel); + + NSUInteger pressCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::countSel); + if (pressCount == 0) return; + + for(NSUInteger i = 0; i < pressCount; i++) + { + id press = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(pressesArray, ObjectiveCSEL::objectAtIndexSel, i); - // Update internal frame representation to get the latest window position - window_updateFrameFromOSX(self); - NSPoint location = {self->windowFrame.x, self->windowFrame.y}; + KeyEventData data = extractKeyEventData(event, press); - // Get the content view - id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (gptrGLKViewEvents->keyDownCallback) { + gptrGLKViewEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrGLKViewEvents->keyDownUserData); + } + } + +} + +void pressesEnded(id self, SEL _cmd, id presses, id event) { + (void)self; (void)_cmd; (void)event; + + if (!gptrGLKViewEvents || !presses) return; // edge case with presses been null when bluetooth keyboard is disconnected + + // Get an NS array of all presses from the NSSet that have ended + id pressesArray = ((id(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::allObjectsSel); + + NSUInteger pressCount = ((NSUInteger(*)(id, SEL))objc_msgSend)(presses, ObjectiveCSEL::countSel); + if (pressCount == 0) return; + + for(NSUInteger i = 0; i < pressCount; i++) + { + id press = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(pressesArray, ObjectiveCSEL::objectAtIndexSel, i); - if (contentView) { + KeyEventData data = extractKeyEventData(event, press); - // x,y are in physical backing pixels. CGWarpMouseCursorPosition - // and all AppKit frame/bounds values use logical points, so convert the incoming - // pixel position to points. - NSPoint pixelPos = { x, y }; - NSPoint pointPos = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( contentView, ObjectiveCSEL::convertPointFromBackingSel, pixelPos); - - // Get content bounds in points for clamping and Y-flip - NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - - // NOTE: clamp to keep the cursor within the window content area - auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); - auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, - screenFrame.height - location.y - contentBounds.height, - screenFrame.height - location.y); - CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + if (gptrGLKViewEvents->keyUpCallback) { + gptrGLKViewEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrGLKViewEvents->keyUpUserData); } } +} - // Set cursor visibility - void window_setCursorVisibility(Window* self, BOOL visible) { - if (!self || !self->nsWindow) return; - bHideCursor = !visible; - if (bHideCursor && bAllowHideCursor) { - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); - } else { - ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); - } - } - void window_toggleFullScreen(Window* self) { - // Toggle fullscreen - ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); +// View Controller lifecycle event handlers +void viewDidLoad(id self, SEL _cmd) { + (void)self; (void)_cmd; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidLoadCallback) { + gptrViewControllerDelegate->viewDidLoadCallback(gptrViewControllerDelegate->viewDidLoadUserData); + } else { + printf("No viewDidLoadCallback set or gptrViewControllerDelegate is null\n"); } +} - bool window_isFullScreen(Window* self) { - unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); - return (mask & NSWindowStyleMaskFullScreen); +void device_getScreenBounds(CGRect* rect) { + + Class screenClass = safe_objc_getClass(kUIScreenClass); + + id mainScreen = safe_objc_msgSend((id)screenClass, ObjectiveCSEL::mainScreenSel); + if (!mainScreen) { + return; } + + // Try nativeBounds first (returns points in native orientation) + CGRect nativeBounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::nativeBounds); + + if (nativeBounds.size.width > 0 && nativeBounds.size.height > 0) { + *rect = nativeBounds; + } else { + // Fallback to regular bounds + CGRect bounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::boundsSel); + *rect = bounds; + } +} - // Initialize OpenGL renderer - void opengl_initialize(OpenGLRenderer* self, Window* window) { - self->window = window; - window->renderer = self; - - // Get classes using const strings - Class NSOpenGLPixelFormatClass = objc_getClass(kNSOpenGLPixelFormatClass); - Class CustomOpenGLViewClass = createCustomOpenGLViewClass(); // Use our custom class - - unsigned int sampleBuffers = (self->MSAA_Samples > 0) ? 1 : 0; - std::vector preferredSamples = {32, 16, 8, 4, 2, 0}; - if(self->MSAA_Samples <= 0) { - // If no multisampling requested, only try 0 samples - preferredSamples.clear(); - preferredSamples.resize(1); - preferredSamples.push_back(0); - } +void loadView(id self, SEL _cmd) { + (void)self; (void)_cmd; - for(unsigned int samples: preferredSamples) - { - // Create pixel format attributes array using enum values - const unsigned int attrs[] = { - NSOpenGLPFADoubleBuffer, // Enable double buffering - NSOpenGLPFADepthSize, 32, // 32-bit depth buffer - NSOpenGLPFAColorSize, 24, // 24-bit color - NSOpenGLPFAAccelerated, // Hardware acceleration - NSOpenGLAllowOfflineRenderers, // Allow offline renderers - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, // OpenGL 4.1 Core Profile - NSOpenGLPFAMultisample, samples, // 0x --> 32X Multisampling - NSOpenGLPFASampleBuffers, sampleBuffers, // Number of sample buffers - 0 // null terminator - }; - - // Create pixel format - self->pixelFormat = ((id(*)(id, SEL, const unsigned int*))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(NSOpenGLPixelFormatClass, ObjectiveCSEL::allocSel), - ObjectiveCSEL::initWithAttributesSel, attrs); + device_getScreenBounds(&gptrVeiwControllerEvents->frame); + + if (gptrVeiwControllerEvents) { + // Create the GLKView with the stored frame + gptrVeiwControllerEvents->glkview = createCustomGLKView(gptrVeiwControllerEvents->frame); + if (gptrVeiwControllerEvents->glkview) { + // Set it as the view + safe_objc_msgSend_id(self, ObjectiveCSEL::setViewSel, gptrVeiwControllerEvents->glkview); - // Check if pixel format was created successfully - if (self->pixelFormat) { - self->MSAA_Samples = samples; - break; // Successfully created pixel format + // Initialize GLKView struct for touch event handling + gptrVeiwControllerEvents->glkViewStruct = glkview_init(gptrVeiwControllerEvents); + if (gptrVeiwControllerEvents->glkViewStruct) { + // Enable auto-sizing + viewcontroller_enableFlexibleSizing(gptrVeiwControllerEvents); + } else { + printf("Failed to initialize GLKView struct in loadView\n"); } + } else { + printf("Failed to create GLKView in loadView\n"); } + } else { + printf("gptrVeiwControllerEvents is null in loadView\n"); + } +} + +void viewWillAppear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewWillAppearCallback) { + gptrViewControllerDelegate->viewWillAppearCallback(gptrViewControllerDelegate->viewWillAppearUserData); + } +} + +void viewDidAppear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidAppearCallback) { + gptrViewControllerDelegate->viewDidAppearCallback(gptrViewControllerDelegate->viewDidAppearUserData); + } +} + +void viewWillDisappear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewWillDisappearCallback) { + gptrViewControllerDelegate->viewWillDisappearCallback(gptrViewControllerDelegate->viewWillDisappearUserData); + } +} + +void viewDidDisappear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidDisappearCallback) { + gptrViewControllerDelegate->viewDidDisappearCallback(gptrViewControllerDelegate->viewDidDisappearUserData); + } +} + +void viewDidLayoutSubviews(id self, SEL _cmd) { + (void)self; (void)_cmd; + + device_getScreenBounds(&gptrVeiwControllerEvents->frame); + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidLayoutSubviewsCallback) { + gptrViewControllerDelegate->viewDidLayoutSubviewsCallback(gptrViewControllerDelegate->viewDidLayoutSubviewsUserData); + } +} + +void updateView(id self, SEL _cmd) { + (void)self; (void)_cmd; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->updateCallback) { + gptrViewControllerDelegate->updateCallback(gptrViewControllerDelegate->updateUserData); + } +} + +// Update the orientation change handler to receive NSNotification +void viewOrientationChanged(id self, SEL _cmd, id notification) { + (void)self; (void)_cmd; (void)notification; + + if (!gptrViewControllerDelegate) return; + + // Get current device orientation + Class deviceClass = objc_getClass(kUIDeviceClass); + + id currentDevice = ((id(*)(Class, SEL))objc_msgSend)(deviceClass, ObjectiveCSEL::currentDeviceSel); + int orientation = ((int(*)(id, SEL))objc_msgSend)(currentDevice, ObjectiveCSEL::orientationSel); + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->orientationChangedCallback) { + gptrViewControllerDelegate->orientationChangedCallback(orientation, gptrViewControllerDelegate->orientationChangedUserData); + } +} + +// Create custom GLKViewController subclass with event handling +id createCustomViewController(void) { + // Check if class already exists to avoid duplicate registration + Class CustomViewControllerClass = objc_getClass(kViewControllerDelegateClass); + + if (!CustomViewControllerClass) { + // Create custom GLKViewController subclass only if it doesn't exist + Class GLKViewControllerClass = objc_getClass(kGLKViewControllerClass); + CustomViewControllerClass = objc_allocateClassPair(GLKViewControllerClass, kViewControllerDelegateClass, 0); - // Create custom OpenGL view with event handling - NSRect glViewFrame = {0.0, 0.0, window->contentViewFrame.width, window->contentViewFrame.height}; - self->glView = ((id(*)(id, SEL, NSRect, id))objc_msgSend)( - ((id(*)(Class, SEL))objc_msgSend)(CustomOpenGLViewClass, ObjectiveCSEL::allocSel), - ObjectiveCSEL::initWithFramePixelFormatSel, glViewFrame, self->pixelFormat); - - // Set autoresizing mask to make the view resize with the window - unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); + if (!CustomViewControllerClass) { + printf("Failed to create custom GLKViewController class\n"); + return nil; + } - // Enable touch events (NSTouch) - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + // Add keyboard event methods + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::pressesBeganSel, (IMP)pressesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::pressesEndedSel, (IMP)pressesEnded, kTouchEventMethodTypeEncoding); + + // Add lifecycle methods + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::updateView, (IMP)updateView, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::loadViewSel, (IMP)loadView, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidLoadSel, (IMP)viewDidLoad, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewWillAppearSel, (IMP)viewWillAppear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidAppearSel, (IMP)viewDidAppear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewWillDisappearSel, (IMP)viewWillDisappear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidDisappearSel, (IMP)viewDidDisappear, kBoolMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::viewDidLayoutSubviewsSel, (IMP)viewDidLayoutSubviews, kVoidMethodTypeEncoding); + class_addMethod(CustomViewControllerClass, ObjectiveCSEL::deviceOrientationDidChangeSel, (IMP)viewOrientationChanged, kEventHandlerMethodTypeEncoding); - // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); + // Register the class + objc_registerClassPair(CustomViewControllerClass); } + + // Create instance + id viewController = ((id(*)(Class, SEL))objc_msgSend)(CustomViewControllerClass, ObjectiveCSEL::allocSel); + viewController = ((id(*)(id, SEL))objc_msgSend)(viewController, ObjectiveCSEL::initSel); + + if (!viewController) { + printf("Failed to create GLKViewController instance\n"); + return nil; + } + return viewController; +} - // Set up OpenGL context and make view first responder - void opengl_setupContext(OpenGLRenderer* self) { - // Set the OpenGL view as the window's content view - ((void(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::setContentViewSel, self->glView); - - // Make the OpenGL context current - self->glContext = ((id(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::openGLContextSel); - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Make sure the window is key first - ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); - - // Make the OpenGL view the first responder so it can receive keyboard and mouse events - ((BOOL(*)(id, SEL, id))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeFirstResponderSel, self->glView); - - // Additional debug: check if view can become key view - ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); +void drawRect(id self, SEL _cmd, CGRect rect) { + (void)self; (void)_cmd; (void)rect; + + if (gptrGLKViewEvents && gptrGLKViewEvents->drawCallback) { + gptrGLKViewEvents->drawCallback(rect.origin.x, rect.origin.y, + rect.size.width, rect.size.height, + gptrGLKViewEvents->drawUserData); } +} - // Implementation function - bool opengl_resetContextForSize(OpenGLRenderer* self, double width, double height) { - if (!self || !self->glContext || !self->glView) { - return false; - } - - // Make context current - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Update the view frame - NSRect newFrame = {0.0, 0.0, width, height}; - ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); +// Create custom GLKView subclass for touch handling +id createCustomGLKView(CGRect frame) { + // Check if class already exists to avoid duplicate registration + Class CustomGLKViewClass = objc_getClass(kCustomGLKViewClass); + + if (!CustomGLKViewClass) { + // Create custom GLKView subclass only if it doesn't exist + Class GLKViewClass = objc_getClass(kGLKViewClass); + CustomGLKViewClass = objc_allocateClassPair(GLKViewClass, kCustomGLKViewClass, 0); - // Force context to reshape - ((void(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::reshapeSel); - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::updateSel); + if (!CustomGLKViewClass) { + printf("Failed to create custom GLKView class\n"); + return nil; + } - // Update viewport - glViewport(0, 0, (GLsizei)width, (GLsizei)height); + // Add touch event methods to the view + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesBeganSel, (IMP)touchesBegan, kTouchEventMethodTypeEncoding); + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesMovedSel, (IMP)touchesMoved, kTouchEventMethodTypeEncoding); + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesEndedSel, (IMP)touchesEnded, kTouchEventMethodTypeEncoding); + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::touchesCancelledSel, (IMP)touchesCancelled, kTouchEventMethodTypeEncoding); - // Clear buffers - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Add draw methods + class_addMethod(CustomGLKViewClass, ObjectiveCSEL::drawRectSel, (IMP)drawRect, kDrawRectMethodTypeEncoding); - return true; - } + // Register the class + objc_registerClassPair(CustomGLKViewClass); - - // Get the underlying OpenGL context - void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { - return (void*)self->glContext; } + + // Create instance + id view = ((id(*)(Class, SEL))objc_msgSend)(CustomGLKViewClass, ObjectiveCSEL::allocSel); + view = ((id(*)(id, SEL, CGRect))objc_msgSend)(view, ObjectiveCSEL::initWithFrameSel, frame); + + if (!view) { + printf("Failed to create GLKView instance\n"); + return nil; + } + + // Enable user interaction (disabled by default on GLKView) + ((void(*)(id, SEL, BOOL))objc_msgSend)(view, ObjectiveCSEL::setUserInteractionEnabledSel, YES); + + // Enable multiple touch support + ((void(*)(id, SEL, BOOL))objc_msgSend)(view, ObjectiveCSEL::setMultipleTouchEnabledSel, YES); + + return view; +} - // Get the underlying CGLContextObj from the OpenGL renderer - void* opengl_getCGLContextObj(struct OpenGLRenderer* self) { - if (!self || !self->glContext) { return nullptr;} - - return (void*)((void*(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::CGLContextObjSel); +// GLKView API Implementation +struct GLKView* glkview_init(struct ViewController* viewController) +{ + // TODO: Remove second call to this + if(gptrVeiwControllerEvents->glkViewStruct != nullptr) + return gptrVeiwControllerEvents->glkViewStruct; + + if (!viewController || !viewController->glkview) { + printf("Error: Invalid view controller or GLKView not initialized\n"); + return nullptr; } - // Get the underlying CGLContextObj (C API version) - void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { - return (void*)opengl_getCGLContextObj(self); + struct GLKView* glkViewStruct = (struct GLKView*)malloc(sizeof(struct GLKView)); + if (!glkViewStruct) { + printf("Error: Failed to allocate GLKView struct\n"); + return nullptr; } + + memset(glkViewStruct, 0, sizeof(struct GLKView)); + glkViewStruct->glkView = viewController->glkview; + glkViewStruct->parentViewController = viewController; - // Make the OpenGL context current - void opengl_makeCurrentContext(struct OpenGLRenderer* self) { - if (self && self->glContext) { - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - } + // Set up global pointer for touch event routing + gptrGLKViewEvents = glkViewStruct; + + return glkViewStruct; +} + +void glkview_destroy(struct GLKView* self) { + if (!self) return; + + if (gptrGLKViewEvents == self) { + gptrGLKViewEvents = nullptr; } + + free(self); +} - // Enable or disable vertical synchronization (vsync) - void opengl_setVsync(OpenGLRenderer* self, BOOL enabled) { - if (!self || !self->glContext) { return; } - - // Make context current first - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Set swap interval: 1 for vsync enabled, 0 for disabled - GLint swapInterval = enabled ? 1 : 0; - - // Use NSOpenGLContext setValues:forParameter: to set swap interval - const GLint parameter = NSOpenGLContextParameterSwapInterval; - ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); +void* glkview_getNativeView(const struct GLKView* self) { + if (!self) return nullptr; + return self->glkView; +} + +void glkview_setFrame(struct GLKView* self, double x, double y, double width, double height) { + if (!self || !self->glkView) return; + + CGRect frame = CGRectMake(x, y, width, height); + typedef void (*SetFrameFunc)(id, SEL, CGRect); + ((SetFrameFunc)objc_msgSend)(self->glkView, ObjectiveCSEL::setFrameSel, frame); +} + +void glkview_getFrame(const struct GLKView* self, double* x, double* y, double* width, double* height) { + if (!self || !self->glkView) return; + + CGRect frame = ((CGRect(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::setFrameSel); + if (x) *x = frame.origin.x; + if (y) *y = frame.origin.y; + if (width) *width = frame.size.width; + if (height) *height = frame.size.height; +} + +void glkview_setMultipleTouchEnabled(struct GLKView* self, BOOL enabled) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setMultipleTouchEnabledSel, enabled); +} + +BOOL glkview_isMultipleTouchEnabled(const struct GLKView* self) { + if (!self || !self->glkView) return NO; + return ((BOOL(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::isMultitouchEnabledSel); +} + +void glkview_enableTouchHandling(struct GLKView* self) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setUserInteractionEnabledSel, YES); +} + +void glkview_disableTouchHandling(struct GLKView* self) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setUserInteractionEnabledSel, NO); +} + +void glkview_debugTouchSetup(struct GLKView* self) { + if (!self || !self->glkView) { + printf("GLKView Debug: Invalid GLKView struct or view\n"); + return; } + + BOOL touchEnabled = ((BOOL(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::isUserInteractionEnabledSel); + BOOL multiTouchEnabled = ((BOOL(*)(id, SEL))objc_msgSend)(self->glkView, ObjectiveCSEL::isMultitouchEnabledSel); + + printf(" GLKView Touch Debug:\n"); + printf(" Touch Enabled: %s\n", touchEnabled ? "YES" : "NO"); + printf(" Multi-touch Enabled: %s\n", multiTouchEnabled ? "YES" : "NO"); + printf(" Touch Began Callback: %s\n", self->touchBeganCallback ? "Set" : "Not Set"); + printf(" Touch Moved Callback: %s\n", self->touchMovedCallback ? "Set" : "Not Set"); + printf(" Touch Ended Callback: %s\n", self->touchEndedCallback ? "Set" : "Not Set"); + printf(" Touch Cancelled Callback: %s\n", self->touchCancelledCallback ? "Set" : "Not Set"); +} - // Destroy the OpenGL renderer - void opengl_destroy(OpenGLRenderer* self) { - if (self) { - // Release OpenGL resources - if (self->glContext) { - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::releaseSel); - } - free(self); - } +void glkview_setNeedsDisplay(struct GLKView* self) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void(self->glkView, ObjectiveCSEL::setNeedsDisplaySel); +} + + +// View Controller API Implementation +struct ViewController* viewcontroller_init(double width, double height) { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + + struct ViewController* pViewController = (struct ViewController*)malloc(sizeof(struct ViewController)); + if (!pViewController) return NULL; + + memset(pViewController, 0, sizeof(struct ViewController)); + + // Set initial frame + pViewController->frame.origin.x = 0.0; + pViewController->frame.origin.y = 0.0; + pViewController->frame.size.width = width; + pViewController->frame.size.height = height; + + // Create custom GLKViewController with event handling + pViewController->viewController = createCustomViewController(); + if (!pViewController->viewController) { + free(pViewController); + return NULL; } + + // Set up global pointer for event routing + gptrVeiwControllerEvents = pViewController; + gptrViewControllerDelegate = pViewController; + + return pViewController; +} - // OpenGL renderer constructor - OpenGLRenderer* opengl_init() { +void setupOrientationNotifications(struct ViewController* self) { + if (!self || !self->viewController) return; + + // Get notification center + Class notificationCenterClass = objc_getClass(kNSNotificationCenterClass); + id notificationCenter = ((id(*)(Class, SEL))objc_msgSend)(notificationCenterClass, ObjectiveCSEL::defaultCenterSel); + + // Create notification name NSString + Class stringClass = objc_getClass(kNSStringClass); + id notificationName = ((id(*)(Class, SEL, const char*))objc_msgSend)( + stringClass, ObjectiveCSEL::stringWithUTF8StringSel, kUIDeviceOrientationDidChangeNoti); + + // Register for orientation notifications + ((void(*)(id, SEL, id, SEL, id, id))objc_msgSend)( + notificationCenter, ObjectiveCSEL::addObserverSel, + self->viewController, ObjectiveCSEL::deviceOrientationDidChangeSel, + notificationName, nil); + + // Enable orientation notifications + Class deviceClass = objc_getClass(kUIDeviceClass); + + id currentDevice = ((id(*)(Class, SEL))objc_msgSend)(deviceClass, ObjectiveCSEL::currentDeviceSel); + ((void(*)(id, SEL))objc_msgSend)(currentDevice, ObjectiveCSEL::beginGenDeviceOrientationNotifSel); +} - OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); - renderer->pixelFormat = NULL; - renderer->glView = NULL; - renderer->glContext = NULL; - renderer->window = NULL; +int device_getMaxFramesPerSecond(void) { + + Class screenClass = objc_getClass(kUIScreenClass); + + // Get main screen + id mainScreen = ((id(*)(Class, SEL))objc_msgSend)(screenClass, ObjectiveCSEL::mainScreenSel); + + // Get maximum frames per second from screen + NSInteger maxFPS = ((NSInteger(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::maximumFramesPerSecond); + + return (int)maxFPS; +} - // Assign method pointers - renderer->initialize = opengl_initialize; - renderer->setupContext = opengl_setupContext; - renderer->getOpenGLContext = opengl_getOpenGLContext; - renderer->getCGLContextObj = opengl_getCGLContextObj; - renderer->getCGLContextObjPtr = opengl_getCGLContextObjPtr; - renderer->makeCurrentContext = opengl_makeCurrentContext; - renderer->setVsync = opengl_setVsync; - renderer->destroy = opengl_destroy; - renderer->resetContextForSize = opengl_resetContextForSize; +void viewcontroller_setAutoresizingMask(struct ViewController* self, unsigned int mask) { + if (!self || !self->glkview) return; + + ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glkview, ObjectiveCSEL::setAutoresizingMaskSel, mask); +} - return renderer; +void viewcontroller_enableFlexibleSizing(struct ViewController* self) { + if (!self) return; + + // Enable flexible width and height + unsigned int flexibleSizing = (1 << 1) | (1 << 4); // UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight + viewcontroller_setAutoresizingMask(self, flexibleSizing); +} + + +void viewcontroller_present(struct ViewController* self) { + if (!self || !self->viewController) { + printf("Error: Invalid view controller for presentation\n"); + return; } - static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) - { - if(!image) return NO; + setupOrientationNotifications(self); + + // Set preferred frames per second based on device capabilities + if (self->viewController) { + ((void(*)(id, SEL, int))objc_msgSend)(self->viewController, ObjectiveCSEL::setPreferredFramesPerSecondSel, device_getMaxFramesPerSecond()); + } + + // Enable the rendering loop + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->viewController, ObjectiveCSEL::setPausedSel, YES); + + // Set delegate for GLKViewController updates + ((void(*)(id, SEL, id))objc_msgSend)(self->viewController, ObjectiveCSEL::setDelegateSel, self->viewController); + + // Get the main window from your application and set as root view controller + if (gptrApplicationDelegate && gptrApplicationDelegate->uiWindow) { + ((void(*)(id, SEL, id))objc_msgSend)(gptrApplicationDelegate->uiWindow, + ObjectiveCSEL::setRootViewControllerSel, + self->viewController); - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } + } else { + printf("Warning: No main window found for presentation\n"); + } + + +} - CGDataProviderRef provider = CGImageGetDataProvider(image); - CFDataRef rawData = CGDataProviderCopyData(provider); +void viewcontroller_destroy(struct ViewController* self) { + if (!self) return; + + if (self->title) { + free(self->title); + } + + if (self->glkViewStruct) { + glkview_destroy(self->glkViewStruct); + self->glkViewStruct = nullptr; + } + + if (self->glkview) { + safe_objc_msgSend_void(self->glkview, ObjectiveCSEL::releaseSel); + } + + if (self->viewController) { + safe_objc_msgSend_void(self->viewController, ObjectiveCSEL::releaseSel); + } + + if (self->delegate) { + safe_objc_msgSend_void(self->delegate, ObjectiveCSEL::releaseSel); + } + + free(self); +} - //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning - //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning - //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning - - self->width = (int)CGImageGetWidth(image); - self->height = (int)CGImageGetHeight(image); - self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA - self->bytesPerRow = self->width * self->bytesPerPixel; - self->hasAlpha = YES; +void viewcontroller_setPause(struct ViewController* self, BOOL paused) { + if (!self || !self->viewController) return; + + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->viewController, ObjectiveCSEL::setPausedSel, paused ? YES : NO); - const uint8_t* imageData = CFDataGetBytePtr(rawData); - self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); - if(!self->pixelData) - { - return NO; - } - memcpy(self->pixelData, imageData, self->width * self->height * self->bytesPerPixel); - - // NOTE from Moros1138 - // - // On Apple Silicon and x86 Macs kCGBitmapByteOrder32Little is by far - // the most common case, so in practice this block of code will never - // be run. However, if we find that there is a need to adjust the - // pixel data, this will need fleshing out. +} + +void viewcontroller_setTitle(struct ViewController* self, const char* title) { + if (!self || !self->viewController || !title) return; + + // Free existing title + if (self->title) { + free(self->title); + } + + // Copy new title + size_t len = strlen(title); + self->title = (char*)malloc(len + 1); + if (!self->title) { + return; + } + strcpy(self->title, title); + + // Create NSString and set title + Class stringClass = safe_objc_getClass(kNSStringClass); + + id titleString = ((id(*)(id, SEL, const char*))objc_msgSend)((id)stringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); + safe_objc_msgSend_id(self->viewController, ObjectiveCSEL::setTitleSel, titleString); +} + - /* - 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; - } - } - */ +const char* viewcontroller_getTitle(const struct ViewController* self) { + if (!self) return NULL; + return self->title; +} - CFRelease(rawData); - CGImageRelease(image); +void viewcontroller_updateViewFromiOS(struct ViewController* self) { + if (!self || !self->glkview) return; + + // Update frame from the actual view bounds + CGRect bounds = ((CGRect(*)(id, SEL))objc_msgSend)(self->glkview, ObjectiveCSEL::boundsSel); + self->frame = bounds; +} - return YES; +void viewcontroller_getViewFrame(const struct ViewController* self, double* x, double* y, double* width, double* height) { + if (!self) return; + + if (x) *x = self->frame.origin.x; + if (y) *y = self->frame.origin.y; + if (width) *width = self->frame.size.width; + if (height) *height = self->frame.size.height; +} + +void viewcontroller_getViewSize(const struct ViewController* self, double* width, double* height) { + if (!self) return; + + if (width) *width = self->frame.size.width; + if (height) *height = self->frame.size.height; +} + +void viewcontroller_setViewFrame(struct ViewController* self, double x, double y, double width, double height) { + if (!self) return; + + self->frame.origin.x = x; + self->frame.origin.y = y; + self->frame.size.width = width; + self->frame.size.height = height; + + if (self->glkview) { + typedef void (*SetFrameFunc)(id, SEL, CGRect); + ((SetFrameFunc)objc_msgSend)(self->glkview, ObjectiveCSEL::setFrameSel, self->frame); } +} - BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } +void viewcontroller_setViewSize(struct ViewController* self, double width, double height) { + viewcontroller_setViewFrame(self, self->frame.origin.x, self->frame.origin.y, width, height); +} - CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); - CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); - CFRelease(pathStr); +// OpenGL ES Renderer API Implementation +struct OpenGLRenderer* opengl_init(void) { + struct OpenGLRenderer* renderer = (struct OpenGLRenderer*)malloc(sizeof(struct OpenGLRenderer)); + if (!renderer) return NULL; + + memset(renderer, 0, sizeof(struct OpenGLRenderer)); + return renderer; +} - if(!url) - { - printf("loadImageFromFile: bad path '%s'\n", filePath); - return NO; - } +void opengl_initialize(struct OpenGLRenderer* self, struct ViewController* viewController) { + if (!self || !viewController || !viewController->glkview) return; + + // Create EAGL context for OpenGL ES 3.0 + Class eaglClass = safe_objc_getClass(kEAGLContextClass); - CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); - CFRelease(url); + id allocatedContext = safe_objc_msgSend((id)eaglClass, ObjectiveCSEL::allocSel); + if (!allocatedContext) return; + + self->eaglContext = ((id(*)(id, SEL, int))objc_msgSend)(allocatedContext,ObjectiveCSEL::initWithAPISel, kEAGLRenderingAPIOpenGLES3); + if (!self->eaglContext) return; + + // Set context on GLKView + self->glkView = viewController->glkview; + safe_objc_msgSend_id(self->glkView, ObjectiveCSEL::setContextSel, self->eaglContext); +} - if(!src) - { - printf("loadImageFromFile: couldn't open '%s'\n", filePath); - return NO; - } - - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); - return imageloader_decodeImage(self, image); +void opengl_setupContext(struct OpenGLRenderer* self) { + if (!self || !self->eaglContext) return; + + // Set current context + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + safe_objc_msgSend_id((id)eaglClass, ObjectiveCSEL::setCurrentContextSel, self->eaglContext); + + GLint maxSamples = OLC_MSAA_SAMPLES; + if(maxSamples > 0) + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + + // Configure GLKView properties + if (self->glkView) { + + // Set color format to RGBA8 + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableColorFormatSel, kEAGLColorFormatRGBA8); + + // Set depth format to 24-bit + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableDepthFormatSel, kEAGLDepthFormat24); + + // Enable multisampling + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableMultisampleSel, maxSamples); + + // Enable automatic display updates (recommended for games) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glkView, ObjectiveCSEL::setEnableSetNeedsDisplaySel, NO); + } +} - BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { - - CFDataRef cfData = CFDataCreateWithBytesNoCopy( - nullptr, - reinterpret_cast(data), - (CFIndex)bytes, - kCFAllocatorNull // we own the buffer, CF must not free it - ); +void* opengl_getEAGLContext(const struct OpenGLRenderer* self) { + if (!self) return NULL; + return self->eaglContext; +} - if (!cfData) - { - printf("loadImageFromMemory: CFData creation failed\n"); - return NO; - } +void* opengl_getEAGLContextPtr(struct OpenGLRenderer* self) { + if (!self) return NULL; + return &(self->eaglContext); +} - CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); - CFRelease(cfData); +void opengl_makeCurrentContext(struct OpenGLRenderer* self) { + if (!self || !self->eaglContext) return; + + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + safe_objc_msgSend_id((id)eaglClass, ObjectiveCSEL::setCurrentContextSel, self->eaglContext); +} - if (!src) - { - printf("loadImageFromMemory: src creation failed\n"); - return NO; - } +void opengl_setVsync(struct OpenGLRenderer* self, BOOL enabled) { + if (!self) return; + self->vsyncEnabled = enabled; +} - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); + +void opengl_swapBuffers(struct OpenGLRenderer* self) { + if (!self || !self->glkView) return; + + // In GLKit, buffer swapping is handled automatically by GLKView + // We can trigger a display update instead + safe_objc_msgSend_void(self->glkView, ObjectiveCSEL::displaySel); +} + +void opengl_destroy(struct OpenGLRenderer* self) { + if (!self) return; + + if (self->eaglContext) { + Class eaglClass = safe_objc_getClass(kEAGLContextClass); + safe_objc_msgSend_id((id)eaglClass,ObjectiveCSEL::setCurrentContextSel, nil); - return imageloader_decodeImage(self, image); + safe_objc_msgSend_void(self->eaglContext, ObjectiveCSEL::releaseSel); } + + free(self); +} - // Get raw pixel data pointer - unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return (unsigned char*)self->pixelData; - } +char* imageloader_getApplicationPath(struct ImageLoader* self) +{ + return get_application_path(); +} - // Get image information - void imageloader_getImageInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel) { - if (width) *width = self->width; - if (height) *height = self->height; - if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; +// Load image from file path using NSImage and NSBitmapImageRep +BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; } - // Get detailed image format information - void imageloader_getDetailedInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha) { - if (width) *width = self->width; - if (height) *height = self->height; - if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; - if (bytesPerRow) *bytesPerRow = self->bytesPerRow; - if (hasAlpha) *hasAlpha = self->hasAlpha; - } + self->width = kZeroWidth; + self->height = kZeroHeight; + self->bytesPerPixel = kZeroBytes; + self->bytesPerRow = kZeroRows; + self->hasAlpha = NO; - // Check if image is loaded - BOOL imageloader_isLoaded(const struct ImageLoader* self) { - return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); + // Get required classes and selectors + Class NSStringClass = objc_getClass(kNSStringClass); + Class UIImageClass = objc_getClass(kUIImageClass); + + // Create NSString from file path + id pathString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, filePath); + + if (!pathString) { + return NO; + } + + // Create UIImage from file + id image = ((id(*)(id, SEL, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(UIImageClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithContentsOfFileSel, pathString); + + if (!image) { + return NO; } + + CGImageRef cgImage = ((CGImageRef(*)(id, SEL))objc_msgSend)(image, ObjectiveCSEL::cgImageSel); - // Get pixel at specific coordinates - BOOL imageloader_getPixel(const struct ImageLoader* self, int x, int y, - unsigned char* red, unsigned char* green, - unsigned char* blue, unsigned char* alpha) { - if (!imageloader_isLoaded(self) || x < kMinValidDimension || x >= self->width || y < kMinValidDimension || y >= self->height) { - return NO; - } - - // Calculate pixel offset (macOS uses bottom-left origin, so flip Y) - int flippedY = self->height - kFlippedOffset - y; - imageloader_pixel_t* pixel = self->pixelData + (flippedY * self->width) + x; + if (!cgImage) { + return NO; + } - // Extract color components (assuming RGBA or RGB format) - 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 + self->width = (int)CGImageGetWidth(cgImage); + self->height = (int)CGImageGetHeight(cgImage); + self->bytesPerPixel = (int)CGImageGetBitsPerPixel(cgImage) / kBitsPerByte; // 4 for RGBA, 3 for RGB + self->bytesPerRow = (int)CGImageGetBytesPerRow(cgImage); // self->width * self->bytesPerPixel; + self->hasAlpha = CGImageGetAlphaInfo(cgImage) != kCGImageAlphaNone; + + // Get pixel data directly from CGImage without drawing + CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage); + CFDataRef data = CGDataProviderCopyData(dataProvider); + + if (data) { + const unsigned char* bytes = CFDataGetBytePtr(data); + size_t dataLength = CFDataGetLength(data); + self->pixelData = (unsigned char*)malloc(dataLength); + memcpy(self->pixelData, bytes, dataLength); + CFRelease(data); return YES; } + + return NO; - // Cleanup ImageLoader resources - void imageloader_destroy(struct ImageLoader* self) { - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } - self->width = kZeroWidth; - self->height = kZeroHeight; - self->bytesPerPixel = kZeroBytes; - self->bytesPerRow = kZeroRows; - self->hasAlpha = NO; +} + +BOOL imageloader_loadFromMemory(struct ImageLoader* self, const unsigned char* data, size_t length) { + // 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; + + // Create NSData from memory + Class NSDataClass = objc_getClass(kNSDataClass); + id nsData = ((id(*)(Class, SEL, const void*, size_t))objc_msgSend)( NSDataClass, ObjectiveCSEL::dataWithBytesLengthSel, data, length); + + if (!nsData) { + return NO; + } + + // Create a UIImage from NSData + Class UIImageClass = objc_getClass(kUIImageClass); + id image = ((id(*)(Class, SEL, id))objc_msgSend)(UIImageClass, ObjectiveCSEL::imageWithDataSel, nsData); + + if (!image) { + return NO; + } + + CGImageRef cgImage = ((CGImageRef(*)(id, SEL))objc_msgSend)(image, ObjectiveCSEL::cgImageSel); + + if (!cgImage) { + return NO; } + + self->width = (int)CGImageGetWidth(cgImage); + self->height = (int)CGImageGetHeight(cgImage); + self->bytesPerPixel = (int)CGImageGetBitsPerPixel(cgImage) / kBitsPerByte; // 4 for RGBA, 3 for RGB + self->bytesPerRow = (int)CGImageGetBytesPerRow(cgImage); // self->width * self->bytesPerPixel; + self->hasAlpha = CGImageGetAlphaInfo(cgImage) != kCGImageAlphaNone; - // ImageLoader constructor - struct ImageLoader* imageloader_init() { - struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); - - // Initialize properties - loader->pixelData = NULL; - loader->width = kZeroWidth; - loader->height = kZeroHeight; - loader->bytesPerPixel = kZeroBytes; - loader->bytesPerRow = kZeroRows; - loader->hasAlpha = NO; - - // Assign method pointers - loader->loadFromFile = imageloader_loadFromFile; - loader->loadFromMemory = imageloader_loadFromMemory; - loader->destroy = imageloader_destroy; - loader->getPixelData = imageloader_getPixelData; - loader->getImageInfo = imageloader_getImageInfo; - loader->getDetailedInfo = imageloader_getDetailedInfo; - loader->isLoaded = imageloader_isLoaded; - loader->getPixel = imageloader_getPixel; + // Get pixel data directly from CGImage without drawing + CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage); + CFDataRef dataRef = CGDataProviderCopyData(dataProvider); + + if (dataRef) { + const unsigned char* bytes = CFDataGetBytePtr(dataRef); + size_t dataLength = CFDataGetLength(dataRef); + self->pixelData = (unsigned char*)malloc(dataLength); - return loader; + memcpy(self->pixelData, bytes, dataLength); + CFRelease(dataRef); + return YES; + } + + return NO; +} + + + + + +// Get raw pixel data pointer +unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return self->pixelData; +} + +// Get image information +void imageloader_getImageInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; +} + +// Get detailed image format information +void imageloader_getDetailedInfo(const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha) { + if (width) *width = self->width; + if (height) *height = self->height; + if (bytesPerPixel) *bytesPerPixel = self->bytesPerPixel; + if (bytesPerRow) *bytesPerRow = self->bytesPerRow; + if (hasAlpha) *hasAlpha = self->hasAlpha; +} + +// Check if image is loaded +BOOL imageloader_isLoaded(const struct ImageLoader* self) { + return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); +} + +// Get pixel at specific coordinates +BOOL imageloader_getPixel(const struct ImageLoader* self, int x, int y, + unsigned char* red, unsigned char* green, + unsigned char* blue, unsigned char* alpha) { + + if (!imageloader_isLoaded(self) || x < kMinValidDimension || x >= self->width || y < kMinValidDimension || y >= self->height) { + return NO; + } + + // 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); + + // 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]; + else if (alpha) *alpha = kFullyOpaque; // Fully opaque if no alpha channel + + return YES; +} + +// Cleanup ImageLoader resources +void imageloader_destroy(struct ImageLoader* self) { + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + self->width = kZeroWidth; + self->height = kZeroHeight; + self->bytesPerPixel = kZeroBytes; + self->bytesPerRow = kZeroRows; + self->hasAlpha = NO; +} + +// Create OpenGL texture from ImageLoader: TODO: Remove not needed +unsigned int imageloader_createOpenGLTexture(const struct ImageLoader* loader) { + if (!loader->isLoaded(const_cast(loader))) { + return 0; } + + unsigned int textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + + // Set texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Determine format + GLenum format = (loader->bytesPerPixel == kRGBABytesPerPixel) ? GL_RGBA : GL_RGB; + + // Upload texture data + glTexImage2D(GL_TEXTURE_2D, 0, format, loader->width, loader->height, + 0, format, GL_UNSIGNED_BYTE, loader->pixelData); + + // Generate mipmaps for better quality at different scales + glGenerateMipmap(GL_TEXTURE_2D); + + return textureID; +} + +// ImageLoader constructor +struct ImageLoader* imageloader_init() { + struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); + + // Initialize properties + loader->pixelData = NULL; + loader->width = kZeroWidth; + loader->height = kZeroHeight; + loader->bytesPerPixel = kZeroBytes; + loader->bytesPerRow = kZeroRows; + loader->hasAlpha = NO; + + // Assign method pointers + loader->loadFromFile = imageloader_loadFromFile; + loader->destroy = imageloader_destroy; + loader->getPixelData = imageloader_getPixelData; + loader->getImageInfo = imageloader_getImageInfo; + loader->getDetailedInfo = imageloader_getDetailedInfo; + loader->isLoaded = imageloader_isLoaded; + loader->getPixel = imageloader_getPixel; + loader->createOpenGLTexture = imageloader_createOpenGLTexture; + + return loader; +} + +// GLKView Event Handler API Implementation +void glkview_setTouchBeganCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchBeganCallback = callback; + self->touchBeganUserData = userData; +} + +void glkview_setTouchMovedCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchMovedCallback = callback; + self->touchMovedUserData = userData; +} + +void glkview_setTouchEndedCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchEndedCallback = callback; + self->touchEndedUserData = userData; +} + +void glkview_setTouchCancelledCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchCancelledCallback = callback; + self->touchCancelledUserData = userData; +} + +void glkview_setKeyDownCallback(struct GLKView* self, KeyEventCallback callback, void* userData) { + if (!self) return; + self->keyDownCallback = callback; + self->keyDownUserData = userData; +} + +void glkview_setKeyUpCallback(struct GLKView* self, KeyEventCallback callback, void* userData) { + if (!self) return; + self->keyUpCallback = callback; + self->keyUpUserData = userData; +} + + + +void glkview_setDrawCallback(struct GLKView* self, DrawCallback callback, void* userData) { + if (!self) return; + self->drawCallback = callback; + self->drawUserData = userData; +} - // Window constructor - Window* window_init(double x, double y, double width, double height) { - Window* window = (Window*)malloc(sizeof(Window)); - - // Initialize window properties - window->nsWindow = NULL; - window->delegate = NULL; - window->title = NULL; - - // Set initial window frame - window->windowFrame.x = x; - window->windowFrame.y = y; - window->windowFrame.width = width; - window->windowFrame.height = height; +// Application Delegate API Implementation +void application_setDidFinishLaunchingCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->didFinishLaunchingCallback = callback; + self->didFinishLaunchingUserData = userData; +} - // Set window content view frame - window->contentViewFrame.x = x; - window->contentViewFrame.y = y; - window->contentViewFrame.width = width; - window->contentViewFrame.height = height; +void application_setWillTerminateCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->willTerminateCallback = callback; + self->willTerminateUserData = userData; +} - // Initialize callback pointers - window->keyDownCallback = NULL; - window->keyUpCallback = NULL; - window->mouseDownCallback = NULL; - window->mouseUpCallback = NULL; - window->mouseMovedCallback = NULL; - window->mouseDraggedCallback = NULL; - window->rightMouseDownCallback = NULL; - window->rightMouseUpCallback = NULL; - window->otherMouseDownCallback = NULL; - window->otherMouseUpCallback = NULL; - window->scrollWheelCallback = NULL; +void application_setDidBecomeActiveCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->didBecomeActiveCallback = callback; + self->didBecomeActiveUserData = userData; +} - // Event handling disabled by default - window->acceptsInputEvents = NO; +void application_setWillResignActiveCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->willResignActiveCallback = callback; + self->willResignActiveUserData = userData; +} - // Initialize window delegate callback pointers - window->windowDidResizeCallback = NULL; - window->windowWillCloseCallback = NULL; - window->windowDidBecomeKeyCallback = NULL; - window->windowDidResignKeyCallback = NULL; - window->windowDidMiniaturizeCallback = NULL; - window->windowDidDeminiaturizeCallback = NULL; - window->windowDidResizeUserData = NULL; - window->windowWillCloseUserData = NULL; - window->windowDidBecomeKeyUserData = NULL; - window->windowDidResignKeyUserData = NULL; - window->windowDidMiniaturizeUserData = NULL; - window->windowDidDeminiaturizeUserData = NULL; +void application_setDidEnterBackgroundCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->didEnterBackgroundCallback = callback; + self->didEnterBackgroundUserData = userData; +} - // Assign method pointers - window->create = window_create; - window->show = window_show; - window->destroy = window_destroy; - window->setDelegate = window_setDelegate; - window->removeDelegate = window_removeDelegate; - window->getTitle = window_getTitle; - window->setTitle = window_setTitle; - window->setWindowSize = window_setWindowSize; - window->getWindowSize = window_getWindowSize; - window->setWindowPosition = window_setWindowPosition; - window->getWindowPosition = window_getWindowPosition; - window->setWindowFrame = window_setWindowFrame; - window->getWindowFrame = window_getWindowFrame; - window->getCurrentFrame = window_updateFrameFromOSX; - window->getContentViewFrame = window_getContentViewFrame; - window->setContentViewFrame = window_setContentViewFrame; +void application_setWillEnterForegroundCallback(struct Application* self, ApplicationDelegateCallback callback, void* userData) { + if (!self) return; + self->willEnterForegroundCallback = callback; + self->willEnterForegroundUserData = userData; +} - return window; - } +// View Controller Delegate API Implementation +void viewcontroller_setViewDidLoadCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidLoadCallback = callback; + self->viewDidLoadUserData = userData; +} - // Event callback setter functions - void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { - self->keyDownCallback = callback; - self->eventUserData = userData; - } +void viewcontroller_setViewWillAppearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewWillAppearCallback = callback; + self->viewWillAppearUserData = userData; +} - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { - self->keyUpCallback = callback; - self->eventUserData = userData; - } +void viewcontroller_setViewDidAppearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidAppearCallback = callback; + self->viewDidAppearUserData = userData; +} + +void viewcontroller_setViewWillDisappearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewWillDisappearCallback = callback; + self->viewWillDisappearUserData = userData; +} + +void viewcontroller_setViewDidDisappearCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidDisappearCallback = callback; + self->viewDidDisappearUserData = userData; +} + +void viewcontroller_setViewDidLayoutSubviewsCallback(struct ViewController* self, ViewControllerDelegateCallback callback, void* userData) { + if (!self) return; + self->viewDidLayoutSubviewsCallback = callback; + self->viewDidLayoutSubviewsUserData = userData; +} + +void viewcontroller_setUpdateCallback(struct ViewController* self, UpdateCallback callback, void* userData) { + if (!self) return; + self->updateCallback = callback; + self->updateUserData = userData; +} + +void viewcontroller_setOrientationChangedCallback(struct ViewController* self, OrientationChangedCallback callback, void* userData) { + if (!self) return; + self->orientationChangedCallback = callback; + self->orientationChangedUserData = userData; +} + +BOOL device_isMultitouchCapable(void) { + // Most iOS devices support multitouch + return YES; +} + +BOOL device_hasHardwareKeyboard(void) { + // This would require checking for connected external keyboards + // For now, return NO (most devices use on-screen keyboard) + return NO; +} + +void device_getScreenBounds(double* width, double* height) { + if (!width || !height) return; - void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { - self->flagsChangedCallback = callback; - self->eventUserData = userData; + // Get main screen bounds + Class screenClass = safe_objc_getClass(kUIScreenClass); + id mainScreen = safe_objc_msgSend((id)screenClass, ObjectiveCSEL::mainScreenSel); + if (!mainScreen) { + *width = *height = 0.0; + return; } + + CGRect bounds = ((CGRect(*)(id, SEL))objc_msgSend)(mainScreen, ObjectiveCSEL::boundsSel); + *width = bounds.size.width; + *height = bounds.size.height; +} - void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDownCallback = callback; - self->eventUserData = userData; - } - void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseUpCallback = callback; - self->eventUserData = userData; - } +#endif - void window_setMouseMovedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseMovedCallback = callback; - self->eventUserData = userData; - } - void window_setMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDraggedCallback = callback; - self->eventUserData = userData; - } +#if !defined(OLC_USE_WXWIDGETS) - void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDownCallback = callback; - self->eventUserData = userData; - } +#if OLC_HOST == OLC_HOST_NONE +namespace olc::host +{ + // Make OS Create a window frame, associated with olc::Window + bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + return true; + } - void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDraggedCallback = callback; - self->eventUserData = userData; - } + // Make OS Close a window frame, associated with olc::Window + bool Host_None::CloseWindowFrame(olc::Window* pWindow) + { + return true; + } - void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseUpCallback = callback; - self->eventUserData = userData; - } + // Make OS Update a window frame title, associated with olc::Window + bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) + { + return true; + } - void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDownCallback = callback; - self->eventUserData = userData; - } + // Get OS-specific window descriptor(s) for given olc::Window + std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) + { + return {}; + } - void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseUpCallback = callback; - self->eventUserData = userData; - } + // Wait for OS desktop refresh (for smooooth vsync) + bool Host_None::SyncWithDesktopComposite() + { + return true; + } - void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDraggedCallback = callback; - self->eventUserData = userData; - } + // Force the mouse position in pixels relative to window + bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return true; + } + + // Show or hide mouse cursor for given window + bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return true; + } - void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { - self->scrollWheelCallback = callback; - self->eventUserData = userData; - } + olc::KeyboardLayout Host_None::GetKeyboardLayout() const + { + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } - void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseEnteredCallback = callback; - self->eventUserData = userData; - } + // Called at very start of application + bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool Host_None::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + + systemActive = true; + + if(!OnSystemThreadStart()) + return pPrimaryPGE->OnPostContextEnd(); + + while(systemActive) + { + if(!OnSystemTick()) + { + StopSystem(); + } + } + + OnSystemThreadEnd(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + // Called to stop the host, and shutdown all resources + bool Host_None::StopSystem() + { + systemActive = false; + return true; + } + + // Called at start of system event loop + bool Host_None::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_None::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_None::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_None::OnApplicationEnd() + { + return true; + } +} +#endif - void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseExitedCallback = callback; - self->eventUserData = userData; - } +#if OLC_HOST == OLC_HOST_WINDOWS +namespace olc::host +{ + bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } - void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchBeganCallback = callback; - self->touchUserData = userData; - } + bool Host_Windows_WinAPI::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); - void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchMovedCallback = callback; - self->touchUserData = userData; - } + // Mark system as active + systemActive = true; - void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchEndedCallback = callback; - self->touchUserData = userData; - } + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } - void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchCancelledCallback = callback; - self->touchUserData = userData; - } + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } - void window_setStylusCallback(Window* self, - void (*callback)(uint32_t, double, double, float, float, float, float, bool, bool, bool, void*), - void* userData) - { - self->stylusCallback = callback; - self->stylusUserData = userData; - } + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - void window_enableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = YES; - gptrNSWindowEvents = self; // Set global reference for event handlers - } - } + // Blocking event loop on this thread - handles windows + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } - void window_disableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = NO; - if (gptrNSWindowEvents == self) { - gptrNSWindowEvents = NULL; - } - } - } + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); - // Window delegate callback setter functions - void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResizeCallback = callback; - self->windowDidResizeUserData = userData; + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); + } - } + bool Host_Windows_WinAPI::StopSystem() + { + systemActive = false; + return true; + } - void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowWillCloseCallback = callback; - self->windowWillCloseUserData = userData; - } + bool Host_Windows_WinAPI::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } - void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidBecomeKeyCallback = callback; - self->windowDidBecomeKeyUserData = userData; - } + bool Host_Windows_WinAPI::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } - void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResignKeyCallback = callback; - self->windowDidResignKeyUserData = userData; - } + bool Host_Windows_WinAPI::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } - void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidMiniaturizeCallback = callback; - self->windowDidMiniaturizeUserData = userData; - } + bool Host_Windows_WinAPI::OnApplicationEnd() + { + return true; + } - void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidDeminiaturizeCallback = callback; - self->windowDidDeminiaturizeUserData = userData; - } - // Application delegate callback setter functions - void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->willFinishLaunchingCallback = callback; - self->willFinishLaunchingUserData = userData; - } - void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->didFinishLaunchingCallback = callback; - self->didFinishLaunchingUserData = userData; - } + // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE - void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { - self->willTerminateCallback = callback; - self->willTerminateUserData = userData; - } - void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->didBecomeActiveCallback = callback; - self->didBecomeActiveUserData = userData; - } + // Forward Declaration + static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->willResignActiveCallback = callback; - self->willResignActiveUserData = userData; - } -} // extern "C" + // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get + // access to the actual host instance, which can more conveninetly process + // the event across multiple window instances + static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + // CreateWindowEx will fire a WM_CREATE event at the window, which we're + // not interested in, and this will occur before we've populated our + // linkage maps. We can detect for this condition here which means + // subsequent look-ups dont fail. NOTE: Do not assume WM_CREATE is the + // first message that is sent, its just one that we know is reliably sent. + if (uMsg == WM_CREATE) + { + // Associate the window's little blob of user memory with host iface + auto cfg = ((CREATESTRUCT*)lParam)->lpCreateParams; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cfg); + } + else + { + // If this returns a value, then the host iface should be responsible + // for handling the message. + auto host = (Host_Windows_WinAPI*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + if (host) + return host->OnWindowEvent(hWnd, uMsg, wParam, lParam); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + std::wstring Host_Windows_WinAPI::ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); #endif + std::wstring w(buffer); + delete[] buffer; + return w; + } -#if !defined(OLC_USE_WXWIDGETS) - -#if OLC_HOST == OLC_HOST_NONE -namespace olc::host -{ - // Make OS Create a window frame, associated with olc::Window - bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + Host_Windows_WinAPI::Host_Windows_WinAPI() { - return true; - } + // Map Windows Defined VK_ Codes to olc::KeyCodes + mapKeys[0x00] = Key::NONE; + + // Windows doesn't define A-Z + mapKeys[0x41] = Key::A; + mapKeys[0x42] = Key::B; + mapKeys[0x43] = Key::C; + mapKeys[0x44] = Key::D; + mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; + mapKeys[0x47] = Key::G; + mapKeys[0x48] = Key::H; + mapKeys[0x49] = Key::I; + mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; + mapKeys[0x4C] = Key::L; + mapKeys[0x4D] = Key::M; + mapKeys[0x4E] = Key::N; + mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; + mapKeys[0x51] = Key::Q; + mapKeys[0x52] = Key::R; + mapKeys[0x53] = Key::S; + mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; + mapKeys[0x56] = Key::V; + mapKeys[0x57] = Key::W; + mapKeys[0x58] = Key::X; + mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + // Windows doesnt define numeric keys + mapKeys[0x30] = Key::K0; + mapKeys[0x31] = Key::K1; + mapKeys[0x32] = Key::K2; + mapKeys[0x33] = Key::K3; + mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; + mapKeys[0x36] = Key::K6; + mapKeys[0x37] = Key::K7; + mapKeys[0x38] = Key::K8; + mapKeys[0x39] = Key::K9; - // Make OS Close a window frame, associated with olc::Window - bool Host_None::CloseWindowFrame(olc::Window* pWindow) - { - return true; - } + // Function Keys + mapKeys[VK_F1] = Key::F1; + mapKeys[VK_F2] = Key::F2; + mapKeys[VK_F3] = Key::F3; + mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; + mapKeys[VK_F6] = Key::F6; + mapKeys[VK_F7] = Key::F7; + mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; + mapKeys[VK_F10] = Key::F10; + mapKeys[VK_F11] = Key::F11; + mapKeys[VK_F12] = Key::F12; - // Make OS Update a window frame title, associated with olc::Window - bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) - { - return true; - } + // Arrow Keys + mapKeys[VK_DOWN] = Key::DOWN; + mapKeys[VK_LEFT] = Key::LEFT; + mapKeys[VK_RIGHT] = Key::RIGHT; + mapKeys[VK_UP] = Key::UP; - // Get OS-specific window descriptor(s) for given olc::Window - std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) - { - return {}; - } + // Other Keys + mapKeys[VK_BACK] = Key::BACK; + mapKeys[VK_ESCAPE] = Key::ESCAPE; + mapKeys[VK_RETURN] = Key::ENTER; + mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; + mapKeys[VK_TAB] = Key::TAB; + mapKeys[VK_DELETE] = Key::DEL; + mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; + mapKeys[VK_PRIOR] = Key::PGUP; + mapKeys[VK_NEXT] = Key::PGDN; + mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; + mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + mapKeys[VK_MENU] = Key::ALT; - // Wait for OS desktop refresh (for smooooth vsync) - bool Host_None::SyncWithDesktopComposite() - { - return true; - } + // Numpad + mapKeys[VK_NUMPAD0] = Key::NP0; + mapKeys[VK_NUMPAD1] = Key::NP1; + mapKeys[VK_NUMPAD2] = Key::NP2; + mapKeys[VK_NUMPAD3] = Key::NP3; + mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; + mapKeys[VK_NUMPAD6] = Key::NP6; + mapKeys[VK_NUMPAD7] = Key::NP7; + mapKeys[VK_NUMPAD8] = Key::NP8; + mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; + mapKeys[VK_ADD] = Key::NP_ADD; + mapKeys[VK_DIVIDE] = Key::NP_DIV; + mapKeys[VK_SUBTRACT] = Key::NP_SUB; + mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; - // Force the mouse position in pixels relative to window - bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - return true; - } - - // Show or hide mouse cursor for given window - bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - return true; + // OEM Keys + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard } - olc::KeyboardLayout Host_None::GetKeyboardLayout() const + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) { - return OLC_DEFAULT_KEYBOARD_LAYOUT; - } + olc_IgnoreUnused(bFullScreen); - // Called at very start of application - bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - pPrimaryPGE = pPrimary; - return true; - } + // The user created olc::Window object is the SSoT for what a window + // should look like, so get that sort of thing from there + olc::vi2d vWinPos = vWindowPos; + olc::vi2d vWinSize = vWindowSize; + + hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - bool Host_None::StartSystem() - { - pPrimaryPGE->OnPreContextStart(); + // Define WindowClass + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof(WNDCLASSEX); + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = hCursorDefault; + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = WINAPI_EventHandler; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0;// sizeof(this); // For static meta-info + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); + RegisterClassEx(&wc); - systemActive = true; + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); - if(!OnSystemThreadStart()) - return pPrimaryPGE->OnPostContextEnd(); - - while(systemActive) + olc::vi2d vTopLeft = vWindowPos; + + if (bFullScreen || pPrimaryPGE->config.bFullScreen) { - if(!OnSystemTick()) - { - StopSystem(); - } + 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; } - OnSystemThreadEnd(); - return pPrimaryPGE->OnPostContextEnd(); - } + // Keep client size as requested + RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - // Called to stop the host, and shutdown all resources - bool Host_None::StopSystem() - { - systemActive = false; - return true; - } + // +1 Hack to remove black bar between client and title bar for "perfect" window + // sizes anyway. This could be DPI related on later windows, but this makes it look + // tidier for "normal" applications + int width = rWndRect.right - rWndRect.left + 1; + int height = rWndRect.bottom - rWndRect.top; - // Called at start of system event loop - bool Host_None::OnSystemThreadStart() - { - return pPrimaryPGE->OnContextStart(); - } + // Create the actual OS window, return a handle + HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); - // Called to perform primary window update - bool Host_None::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } - - // Called at end of system event loop - bool Host_None::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } - - // Called at very end of application - bool Host_None::OnApplicationEnd() - { - return true; - } -} -#endif + // Update window size to match actual client area given. In situations where the window + // is clamped to the desktop, the client area may be smaller than requested. + RECT rClient; + GetClientRect(hWnd, &rClient); + pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); -#if OLC_HOST == OLC_HOST_WINDOWS -namespace olc::host -{ - bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - pPrimaryPGE = pPrimary; - return true; - } + // 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); + } - bool Host_Windows_WinAPI::StartSystem() - { - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); - // Mark system as active - systemActive = true; + pWindow->olc_OnFocus(true); - // Create system thread - handles gpu context - std::thread threadSystem([this]() - { - // Notify start of system thread - if (!this->OnSystemThreadStart()) - { - // PGE->OnContextStart() failed, or user aborted OnUserCreate() - return; - } + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); + //ShowWindow(hWnd, 1); + //UpdateWindow(hWnd); - // Main system loop - while (systemActive) - { - // Perform primary window update - if (!this->OnSystemTick()) - { - StopSystem(); - } - } + // Now... awkwardly, the above has already fired off some window messages + // and they arent necessarily in a consistent order. Whereas one might + // assume WM_CREATE would be the first, there are some others on more + // modern systems. This is awkward because we havent yet associated the + // source window with a long_ptr to this class, and therefore we can't + // call the appropriate event handler. - // Notify end of system thread - if (!this->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } - }); + // Store the link bewteen host resource and window + mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); + mapHWND2PTR.insert_or_assign(hWnd, pWindow); - // Blocking event loop on this thread - handles windows - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } + - systemActive = false; - if(threadSystem.joinable()) - threadSystem.join(); - // Post-context end hook - return pPrimaryPGE->OnPostContextEnd(); + //DragAcceptFiles(olc_hWnd, true); + + return true; } - bool Host_Windows_WinAPI::StopSystem() + bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) { - systemActive = false; - return true; + DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); + return false; } - bool Host_Windows_WinAPI::OnSystemThreadStart() + bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) { - return pPrimaryPGE->OnContextStart(); +#ifdef UNICODE + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); +#else + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); +#endif + return true; } - bool Host_Windows_WinAPI::OnSystemTick() + std::vector Host_Windows_WinAPI::GetHostWindowDescriptor(olc::Window* pWindow) { - return pPrimaryPGE->OnContextTick(); + return { mapUID2HWND[pWindow->GetUID()] }; } - bool Host_Windows_WinAPI::OnSystemThreadEnd() + bool Host_Windows_WinAPI::SyncWithDesktopComposite() { - return pPrimaryPGE->OnContextEnd(); + return DwmFlush() == S_OK; } - bool Host_Windows_WinAPI::OnApplicationEnd() + bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) { + POINT pt; + pt.x = vPos.x; + pt.y = vPos.y; + ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); + SetCursorPos(pt.x, pt.y); return true; } + bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + hCursorNow = bVisible ? hCursorDefault : NULL; - // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE - - - // Forward Declaration - static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - + // Fire fake move event to update cursor visibility immediately + POINT p; + GetCursorPos(&p); + SetCursorPos(p.x, p.y + 1); + SetCursorPos(p.x, p.y); + return true; + } - // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get - // access to the actual host instance, which can more conveninetly process - // the event across multiple window instances - static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) { - // CreateWindowEx will fire a WM_CREATE event at the window, which we're - // not interested in, and this will occur before we've populated our - // linkage maps. We can detect for this condition here which means - // subsequent look-ups dont fail. NOTE: Do not assume WM_CREATE is the - // first message that is sent, its just one that we know is reliably sent. - if (uMsg == WM_CREATE) + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); + + if (bFullScreen) { - // Associate the window's little blob of user memory with host iface - auto cfg = ((CREATESTRUCT*)lParam)->lpCreateParams; - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cfg); + // 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); } else { - // If this returns a value, then the host iface should be responsible - // for handling the message. - auto host = (Host_Windows_WinAPI*)GetWindowLongPtr(hWnd, GWLP_USERDATA); - if (host) - return host->OnWindowEvent(hWnd, uMsg, wParam, lParam); + // Restore original window style and position + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + // Get the style we should have based on the window config + 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); } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); + + UpdateWindow(hWnd); + SetForegroundWindow(hWnd); + SetFocus(hWnd); + SetActiveWindow(hWnd); + return true; } - std::wstring Host_Windows_WinAPI::ConvertS2W(std::string s) + DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) { -#ifdef __MINGW32__ - wchar_t* buffer = new wchar_t[s.length() + 1]; - mbstowcs(buffer, s.c_str(), s.length()); - buffer[s.length()] = L'\0'; -#else - int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); - wchar_t* buffer = new wchar_t[count]; - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); -#endif - std::wstring w(buffer); - delete[] buffer; - return w; - } + olc_IgnoreUnused(pWindow); - Host_Windows_WinAPI::Host_Windows_WinAPI() + 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) { - // Map Windows Defined VK_ Codes to olc::KeyCodes - mapKeys[0x00] = Key::NONE; + if (!mapHWND2PTR.contains(hWnd)) + return DefWindowProc(hWnd, uMsg, wParam, lParam);; - // Windows doesn't define A-Z - mapKeys[0x41] = Key::A; - mapKeys[0x42] = Key::B; - mapKeys[0x43] = Key::C; - mapKeys[0x44] = Key::D; - mapKeys[0x45] = Key::E; - mapKeys[0x46] = Key::F; - mapKeys[0x47] = Key::G; - mapKeys[0x48] = Key::H; - mapKeys[0x49] = Key::I; - mapKeys[0x4A] = Key::J; - mapKeys[0x4B] = Key::K; - mapKeys[0x4C] = Key::L; - mapKeys[0x4D] = Key::M; - mapKeys[0x4E] = Key::N; - mapKeys[0x4F] = Key::O; - mapKeys[0x50] = Key::P; - mapKeys[0x51] = Key::Q; - mapKeys[0x52] = Key::R; - mapKeys[0x53] = Key::S; - mapKeys[0x54] = Key::T; - mapKeys[0x55] = Key::U; - mapKeys[0x56] = Key::V; - mapKeys[0x57] = Key::W; - mapKeys[0x58] = Key::X; - mapKeys[0x59] = Key::Y; - mapKeys[0x5A] = Key::Z; + // Get target olc::Window + const auto& window = mapHWND2PTR.at(hWnd); - // Windows doesnt define numeric keys - mapKeys[0x30] = Key::K0; - mapKeys[0x31] = Key::K1; - mapKeys[0x32] = Key::K2; - mapKeys[0x33] = Key::K3; - mapKeys[0x34] = Key::K4; - mapKeys[0x35] = Key::K5; - mapKeys[0x36] = Key::K6; - mapKeys[0x37] = Key::K7; - mapKeys[0x38] = Key::K8; - mapKeys[0x39] = Key::K9; + // Many WinAPI events are literally ancient these days, so need some interpretation + // to get to the useful data. - // Function Keys - mapKeys[VK_F1] = Key::F1; - mapKeys[VK_F2] = Key::F2; - mapKeys[VK_F3] = Key::F3; - mapKeys[VK_F4] = Key::F4; - mapKeys[VK_F5] = Key::F5; - mapKeys[VK_F6] = Key::F6; - mapKeys[VK_F7] = Key::F7; - mapKeys[VK_F8] = Key::F8; - mapKeys[VK_F9] = Key::F9; - mapKeys[VK_F10] = Key::F10; - mapKeys[VK_F11] = Key::F11; - mapKeys[VK_F12] = Key::F12; + switch (uMsg) + { + case WM_MOUSEMOVE: // Mouse has moved within a window + { + // Extract mouse X & Y + uint16_t x = uint16_t(lParam & 0xFFFF); + uint16_t y = uint16_t((lParam >> 16) & 0xFFFF); + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + // Tell window new mouse location + window->olc_OnMouseMove(olc::vi2d{ ix, iy }); + break; + } + + + // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_SIZE: + { + window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); + break; + } + + case WM_MOUSEWHEEL: + { + window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; + } + + case WM_ACTIVATE: + { + window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); + return 0; + } + + case WM_MOUSEACTIVATE: + { + window->olc_OnFocus(true); + return MA_ACTIVATE; + } + + case WM_SETFOCUS: + { + window->olc_OnFocus(true); + return 0; + } - // Arrow Keys - mapKeys[VK_DOWN] = Key::DOWN; - mapKeys[VK_LEFT] = Key::LEFT; - mapKeys[VK_RIGHT] = Key::RIGHT; - mapKeys[VK_UP] = Key::UP; + case WM_KILLFOCUS: + { + window->olc_OnFocus(false); + return 0; + } - // Other Keys - mapKeys[VK_BACK] = Key::BACK; - mapKeys[VK_ESCAPE] = Key::ESCAPE; - mapKeys[VK_RETURN] = Key::ENTER; - mapKeys[VK_PAUSE] = Key::PAUSE; - mapKeys[VK_SCROLL] = Key::SCROLL; - mapKeys[VK_TAB] = Key::TAB; - mapKeys[VK_DELETE] = Key::DEL; - mapKeys[VK_HOME] = Key::HOME; - mapKeys[VK_END] = Key::END; - mapKeys[VK_PRIOR] = Key::PGUP; - mapKeys[VK_NEXT] = Key::PGDN; - mapKeys[VK_INSERT] = Key::INS; - mapKeys[VK_SHIFT] = Key::SHIFT; - mapKeys[VK_CONTROL] = Key::CTRL; - mapKeys[VK_SPACE] = Key::SPACE; - mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; - mapKeys[VK_MENU] = Key::ALT; + case WM_KEYDOWN: + { + window->olc_OnFocus(true); + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } - // Numpad - mapKeys[VK_NUMPAD0] = Key::NP0; - mapKeys[VK_NUMPAD1] = Key::NP1; - mapKeys[VK_NUMPAD2] = Key::NP2; - mapKeys[VK_NUMPAD3] = Key::NP3; - mapKeys[VK_NUMPAD4] = Key::NP4; - mapKeys[VK_NUMPAD5] = Key::NP5; - mapKeys[VK_NUMPAD6] = Key::NP6; - mapKeys[VK_NUMPAD7] = Key::NP7; - mapKeys[VK_NUMPAD8] = Key::NP8; - mapKeys[VK_NUMPAD9] = Key::NP9; - mapKeys[VK_MULTIPLY] = Key::NP_MUL; - mapKeys[VK_ADD] = Key::NP_ADD; - mapKeys[VK_DIVIDE] = Key::NP_DIV; - mapKeys[VK_SUBTRACT] = Key::NP_SUB; - mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + case WM_KEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } - // OEM Keys - mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key - mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key - mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key - mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key - mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. - mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key - mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard - mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard - mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard - mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard - mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard - } + case WM_SYSKEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } - bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - olc_IgnoreUnused(bFullScreen); + case WM_SYSKEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } - // The user created olc::Window object is the SSoT for what a window - // should look like, so get that sort of thing from there - olc::vi2d vWinPos = vWindowPos; - olc::vi2d vWinSize = vWindowSize; - - hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); + case WM_LBUTTONDOWN: + { + window->olc_OnMouseButton(0, true); + break; + } + case WM_LBUTTONUP: + { + window->olc_OnMouseButton(0, false); + break; + } + case WM_RBUTTONDOWN: + { + window->olc_OnMouseButton(1, true); + break; + } + case WM_RBUTTONUP: + { + window->olc_OnMouseButton(1, false); + break; + } + case WM_MBUTTONDOWN: + { + window->olc_OnMouseButton(2, true); + break; + } + case WM_MBUTTONUP: + { + window->olc_OnMouseButton(2, false); + break; + } + case WM_XBUTTONDOWN: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, true); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, true); + } + + break; + } + case WM_XBUTTONUP: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, false); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, false); + } + + break; + } - // Define WindowClass - WNDCLASSEX wc = { 0 }; - wc.cbSize = sizeof(WNDCLASSEX); - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = hCursorDefault; - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpfnWndProc = WINAPI_EventHandler; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0;// sizeof(this); // For static meta-info - wc.lpszMenuName = nullptr; - wc.hbrBackground = nullptr; - wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); - RegisterClassEx(&wc); + case WM_POINTERDOWN: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + true, + false, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + true, + false, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + + } - // Define window furniture - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); + break; + } - olc::vi2d vTopLeft = vWindowPos; + case WM_POINTERUP: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + true, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + true, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + } - 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; + break; } - - // Keep client size as requested - RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; - AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - - // +1 Hack to remove black bar between client and title bar for "perfect" window - // sizes anyway. This could be DPI related on later windows, but this makes it look - // tidier for "normal" applications - int width = rWndRect.right - rWndRect.left + 1; - int height = rWndRect.bottom - rWndRect.top; - - // Create the actual OS window, return a handle - HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, - vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); - - // Update window size to match actual client area given. In situations where the window - // is clamped to the desktop, the client area may be smaller than requested. - RECT rClient; - 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) + case WM_POINTERUPDATE: { - HMENU hMenu = GetSystemMenu(hWnd, FALSE); - DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + false, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + if (IS_POINTER_INCONTACT_WPARAM(wParam)) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + false, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + } + } + break; } - LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); - lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); - - pWindow->olc_OnFocus(true); - - //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); - //ShowWindow(hWnd, 1); - //UpdateWindow(hWnd); - - // Now... awkwardly, the above has already fired off some window messages - // and they arent necessarily in a consistent order. Whereas one might - // assume WM_CREATE would be the first, there are some others on more - // modern systems. This is awkward because we havent yet associated the - // source window with a long_ptr to this class, and therefore we can't - // call the appropriate event handler. - - // Store the link bewteen host resource and window - mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); - mapHWND2PTR.insert_or_assign(hWnd, pWindow); - - - - - //DragAcceptFiles(olc_hWnd, true); - - return true; - } - - bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) - { - DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); - return false; - } - bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) - { -#ifdef UNICODE - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); -#else - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); -#endif - return true; - } + // case WM_DROPFILES: + // { + // // This is all eww... + // HDROP drop = (HDROP)wParam; + // + // uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); + // std::vector vFiles; + // for (uint32_t i = 0; i < nFiles; i++) + // { + // TCHAR dfbuffer[256]{}; + // uint32_t len = DragQueryFile(drop, i, nullptr, 0); + // DragQueryFile(drop, i, dfbuffer, 256); + //#ifdef UNICODE + //#ifdef __MINGW32__ + // char* buffer = new char[len + 1]; + // wcstombs(buffer, dfbuffer, len); + // buffer[len] = '\0'; + //#else + // int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); + // char* buffer = new char[count]; + // WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); + //#endif + // vFiles.push_back(std::string(buffer)); + // delete[] buffer; + //#else + // vFiles.push_back(std::string(dfbuffer)); + //#endif + // } + // + // // Even more eww... + // POINT p; DragQueryPoint(drop, &p); + // ptrPGE->olc_DropFiles(p.x, p.y, vFiles); + // DragFinish(drop); + // return 0; + // } + // break; + // + // + case WM_CLOSE: + { + window->olc_OnWindowClose(); + break; + //return DefWindowProc(hWnd, uMsg, wParam, lParam); + } - std::vector Host_Windows_WinAPI::GetHostWindowDescriptor(olc::Window* pWindow) - { - return { mapUID2HWND[pWindow->GetUID()] }; - } + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(hCursorNow); + return TRUE; // Sigh ffs microsoft... + } - bool Host_Windows_WinAPI::SyncWithDesktopComposite() - { - return DwmFlush() == S_OK; - } + break; + } - bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - POINT pt; - pt.x = vPos.x; - pt.y = vPos.y; - ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); - SetCursorPos(pt.x, pt.y); - return true; - } + case WM_DESTROY: + PostQuitMessage(0); + DestroyWindow(hWnd); - bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - olc_IgnoreUnused(pWindow); + } - hCursorNow = bVisible ? hCursorDefault : NULL; - // Fire fake move event to update cursor visibility immediately - POINT p; - GetCursorPos(&p); - SetCursorPos(p.x, p.y + 1); - SetCursorPos(p.x, p.y); - return true; + return DefWindowProc(hWnd, uMsg, wParam, lParam); } - bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const { - HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); - - if (bFullScreen) - { - // 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); - } - else - { - // Restore original window style and position - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - // Get the style we should have based on the window config - DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); + HKL kbl = ::GetKeyboardLayout(0); + size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; - 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); - } + if (highWord == 0x00000409) // US + return olc::KeyboardLayout::QWERTY_US; + else if(highWord == 0x00000809) // UK + return olc::KeyboardLayout::QWERTY_UK; + else if(highWord == 0x00000407) // DE + return olc::KeyboardLayout::QWERTZ; + else if(highWord == 0x0000040C) // FR + return olc::KeyboardLayout::AZERTY; - UpdateWindow(hWnd); - SetForegroundWindow(hWnd); - SetFocus(hWnd); - SetActiveWindow(hWnd); - return true; + return OLC_DEFAULT_KEYBOARD_LAYOUT; } - DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) - { - olc_IgnoreUnused(pWindow); +}; +#endif - DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx +#if OLC_HOST == OLC_HOST_MACOS +namespace olc::host { - // 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; + // NSEventModifierFlags values + // constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 // Temp remove unused variable warning + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + // constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 // Temp remove unused variable warning + constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 + // constexpr unsigned int NSEventModifierFlagNumericPad = 1 << 21; // 0x200000 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 // Temp remove unused variable warning - // 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 + // 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 + }; - // Note: Close button is handled after dwStlyes are applied - return dwStyle; + Host_Apple_MacOS::Host_Apple_MacOS() + { + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; - } - - LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - if (!mapHWND2PTR.contains(hWnd)) - return DefWindowProc(hWnd, uMsg, wParam, lParam);; + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; - // Get target olc::Window - const auto& window = mapHWND2PTR.at(hWnd); + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; - // Many WinAPI events are literally ancient these days, so need some interpretation - // to get to the useful data. + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; - switch (uMsg) - { - case WM_MOUSEMOVE: // Mouse has moved within a window - { - // Extract mouse X & Y - uint16_t x = uint16_t(lParam & 0xFFFF); - uint16_t y = uint16_t((lParam >> 16) & 0xFFFF); - int16_t ix = *(int16_t*)&x; - int16_t iy = *(int16_t*)&y; - // Tell window new mouse location - window->olc_OnMouseMove(olc::vi2d{ ix, iy }); - break; - } - + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock - // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; - case WM_SIZE: - { - window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); - break; - } + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . - case WM_MOUSEWHEEL: - { - window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); - break; - } + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . - case WM_ACTIVATE: - { - window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); - return 0; - } + } - case WM_MOUSEACTIVATE: - { - window->olc_OnFocus(true); - return MA_ACTIVATE; - } - - case WM_SETFOCUS: - { - window->olc_OnFocus(true); - return 0; - } - case WM_KILLFOCUS: - { - window->olc_OnFocus(false); - return 0; - } + bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ + olc_IgnoreUnused(bFullScreen); + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); - case WM_KEYDOWN: - { - window->olc_OnFocus(true); - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); - } - break; - } + frameBounds.x = 0.0; + frameBounds.y = 0.0; + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); + + return true; + } - case WM_KEYUP: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); - } - break; - } + bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ + pWindow->olc_OnWindowClose(); + return true; + } + + bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ + if (!pMacOSWindow) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + }); + return true; + } - case WM_SYSKEYDOWN: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); - } - break; - } + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + olc_IgnoreUnused(pWindow); + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); - case WM_SYSKEYUP: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); - } - break; - } + return vMacOSWindowDescriptors; + + } - case WM_LBUTTONDOWN: - { - window->olc_OnMouseButton(0, true); - break; - } - case WM_LBUTTONUP: - { - window->olc_OnMouseButton(0, false); - break; - } - case WM_RBUTTONDOWN: - { - window->olc_OnMouseButton(1, true); - break; - } - case WM_RBUTTONUP: - { - window->olc_OnMouseButton(1, false); - break; - } - case WM_MBUTTONDOWN: - { - window->olc_OnMouseButton(2, true); - break; - } - case WM_MBUTTONUP: - { - window->olc_OnMouseButton(2, false); - break; - } - case WM_XBUTTONDOWN: - { - UINT button = GET_XBUTTON_WPARAM(wParam); - if(button == XBUTTON1) - { - window->olc_OnMouseButton(3, true); - } - else if(button == XBUTTON2) - { - window->olc_OnMouseButton(4, true); - } - - break; - } - case WM_XBUTTONUP: - { - UINT button = GET_XBUTTON_WPARAM(wParam); - if(button == XBUTTON1) - { - window->olc_OnMouseButton(3, false); - } - else if(button == XBUTTON2) - { - window->olc_OnMouseButton(4, false); - } - - break; - } - case WM_POINTERDOWN: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - true, - false, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - true, - false, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - - } + bool Host_Apple_MacOS::SyncWithDesktopComposite() + { + /* + core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, + the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, + set to false and toggle it on first call, so that vSync is only enabled once + */ + + if(!enableVSync) + { + pMacOSOpenGLRenderer->enableVsync(); + enableVSync = true; + } + + return enableVSync; + } - break; - } + bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorPosition(vPos.x, vPos.y); + }); + return false; + } - case WM_POINTERUP: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - true, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - true, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - } + bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; + } - break; - } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + olc_IgnoreUnused(pWindow); + // if we're already in the specified state, return early + if(pMacOSWindow->isFullScreen() == bFullScreen) + return true; - case WM_POINTERUPDATE: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - false, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - if (IS_POINTER_INCONTACT_WPARAM(wParam)) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - false, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - } - } - break; - } + 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); - // case WM_DROPFILES: - // { - // // This is all eww... - // HDROP drop = (HDROP)wParam; - // - // uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); - // std::vector vFiles; - // for (uint32_t i = 0; i < nFiles; i++) - // { - // TCHAR dfbuffer[256]{}; - // uint32_t len = DragQueryFile(drop, i, nullptr, 0); - // DragQueryFile(drop, i, dfbuffer, 256); - //#ifdef UNICODE - //#ifdef __MINGW32__ - // char* buffer = new char[len + 1]; - // wcstombs(buffer, dfbuffer, len); - // buffer[len] = '\0'; - //#else - // int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); - // char* buffer = new char[count]; - // WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); - //#endif - // vFiles.push_back(std::string(buffer)); - // delete[] buffer; - //#else - // vFiles.push_back(std::string(dfbuffer)); - //#endif - // } - // - // // Even more eww... - // POINT p; DragQueryPoint(drop, &p); - // ptrPGE->olc_DropFiles(p.x, p.y, vFiles); - // DragFinish(drop); - // return 0; - // } - // break; - // - // - case WM_CLOSE: - { - window->olc_OnWindowClose(); - break; - //return DefWindowProc(hWnd, uMsg, wParam, lParam); - } + // 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 - case WM_SETCURSOR: - { - if (LOWORD(lParam) == HTCLIENT) - { - SetCursor(hCursorNow); - return TRUE; // Sigh ffs microsoft... - } + return nsStyle; + + } - break; - } + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; + } - case WM_DESTROY: - PostQuitMessage(0); - DestroyWindow(hWnd); + bool Host_Apple_MacOS::StartSystem(){ + + // Create MacOS Application instance + pMacApplication = std::make_unique(); - } + // Set up application delegate event handlers + MacApplicationEventsHandler(); + // Initialize and activate application first + pMacApplication->initialize(); + pMacApplication->activate(); + + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Initialize the MacOS Window + pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); + pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); + pMacOSWindow->setContentViewPosition(0, 0); + + // Set up window event handlers + MacWindowEventsHandler(); + + // Create Input Event handler + pMacOSEventHandler = std::make_unique(*pMacOSWindow); + + // Setup Event handlers + MacEventsHandler(); + + // Create the window + unsigned long styleMask = ConvertPGE2WindowStyle(); + pMacOSWindow->show(styleMask); + pMacOSEventHandler->enable(); + + //--- Start up our engine threading system ---- + // Start the PGE context on the main thread + // Mark system as active + systemActive = true; - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } - olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const - { - HKL kbl = ::GetKeyboardLayout(0); - size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } - if (highWord == 0x00000409) // US - return olc::KeyboardLayout::QWERTY_US; - else if(highWord == 0x00000809) // UK - return olc::KeyboardLayout::QWERTY_UK; - else if(highWord == 0x00000407) // DE - return olc::KeyboardLayout::QWERTZ; - else if(highWord == 0x0000040C) // FR - return olc::KeyboardLayout::AZERTY; + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - return OLC_DEFAULT_KEYBOARD_LAYOUT; - } + + // Start the main event loop (this will block) + pMacApplication->run(); + + // Once the application run loop ends, join the system thread + if(threadSystem.joinable()) + threadSystem.join(); -}; -#endif + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); -#if OLC_HOST == OLC_HOST_MACOS -namespace olc::host { + } + bool Host_Apple_MacOS::StopSystem() + { + dispatch_sync(dispatch_get_main_queue(), ^{ + // clean up and close application + if (pMacOSOpenGLRenderer) + { + pMacOSOpenGLRenderer->destoryContext(); + pMacOSOpenGLRenderer = nullptr; + } + if (pMacOSWindow) + { + pMacOSWindow->destoryWindow(); + pMacOSWindow = nullptr; + } + if (pMacApplication) + { + pMacApplication->stop(); + } - // NSEventModifierFlags values - // constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 // Temp remove unused variable warning - constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 - constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 - // constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 // Temp remove unused variable warning - constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 - // constexpr unsigned int NSEventModifierFlagNumericPad = 1 << 21; // 0x200000 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 // Temp remove unused variable warning + }); + systemActive = false; + return true; + } - // 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 - }; + bool Host_Apple_MacOS::OnSystemThreadStart() + { + // Hold back threading until application is fully initialized + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Apple_MacOS::OnSystemTick() + { + // Execute any pending main thread tasks + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextTick(); + } + + bool Host_Apple_MacOS::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Apple_MacOS::OnApplicationEnd() + { + return true; + } + + +//-- OS Window Event Handling ----- + + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } +// ------- Priavate Main Thread Task Handling for MacOS Host ------- - Host_Apple_MacOS::Host_Apple_MacOS() + bool Host_Apple_MacOS::CreateCGLContextObj() { - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - mapKeys[0x00] = Key::NONE; - - // Map macOS key codes to olc::Key codes - mapKeys[0] = Key::A; - mapKeys[11] = Key::B; - mapKeys[8] = Key::C; - mapKeys[2] = Key::D; - mapKeys[14] = Key::E; - mapKeys[3] = Key::F; - mapKeys[5] = Key::G; - mapKeys[4] = Key::H; - mapKeys[34] = Key::I; - mapKeys[38] = Key::J; - mapKeys[40] = Key::K; - mapKeys[37] = Key::L; - mapKeys[46] = Key::M; - mapKeys[45] = Key::N; - mapKeys[31] = Key::O; - mapKeys[35] = Key::P; - mapKeys[12] = Key::Q; - mapKeys[15] = Key::R; - mapKeys[1] = Key::S; - mapKeys[17] = Key::T; - mapKeys[32] = Key::U; - mapKeys[9] = Key::V; - mapKeys[13] = Key::W; - mapKeys[7] = Key::X; - mapKeys[16] = Key::Y; - mapKeys[6] = Key::Z; - - // Numeric keys - mapKeys[29] = Key::K0; - mapKeys[18] = Key::K1; - mapKeys[19] = Key::K2; - mapKeys[20] = Key::K3; - mapKeys[21] = Key::K4; - mapKeys[23] = Key::K5; - mapKeys[22] = Key::K6; - mapKeys[26] = Key::K7; - mapKeys[28] = Key::K8; - mapKeys[25] = Key::K9; + // 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(); + + dispatch_sync(dispatch_get_main_queue(), ^{ + // Edge case for when the window is auto resize due to MacOS clamping to screen size + pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); - // Function Keys - mapKeys[122] = Key::F1; - mapKeys[120] = Key::F2; - mapKeys[99] = Key::F3; - mapKeys[118] = Key::F4; - mapKeys[96] = Key::F5; - mapKeys[97] = Key::F6; - mapKeys[98] = Key::F7; - mapKeys[100] = Key::F8; - mapKeys[101] = Key::F9; - mapKeys[109] = Key::F10; - mapKeys[103] = Key::F11; - mapKeys[111] = Key::F12; + pMacOSOpenGLRenderer->attachToWindow(*pMacOSWindow); + pMacOSOpenGLRenderer->setupContext(); + }); + + pMacGLConextObj = pMacOSOpenGLRenderer->getCGLContextObj(); + + pMacOSOpenGLRenderer->setVsync(false); + + vMacOSWindowDescriptors.push_back(pMacGLConextObj); // Pointer to CGLContextObj + vMacOSWindowDescriptors.push_back(&bSkipFrame); // Pointer to skip frame flag - // Arrow Keys - mapKeys[125] = Key::DOWN; - mapKeys[123] = Key::LEFT; - mapKeys[124] = Key::RIGHT; - mapKeys[126] = Key::UP; + // 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; + } - // Other Keys - mapKeys[51] = Key::BACK; // Delete (Backspace) - mapKeys[53] = Key::ESCAPE; // Escape - mapKeys[36] = Key::ENTER; // Return - mapKeys[113] = Key::PAUSE; // F16 (often used as pause) - mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) - mapKeys[48] = Key::TAB; // Tab - mapKeys[117] = Key::DEL; // Forward Delete - mapKeys[115] = Key::HOME; // Home - mapKeys[119] = Key::END; // End - mapKeys[116] = Key::PGUP; // Page Up - mapKeys[121] = Key::PGDN; // Page Down - mapKeys[114] = Key::INS; // Help (Insert equivalent) - mapKeys[56] = Key::SHIFT; // Left Shift - mapKeys[59] = Key::CTRL; // Left Control - mapKeys[49] = Key::SPACE; // Space - mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + bool Host_Apple_MacOS::ExecutePendingMainThreadTasks() + { + // 1: Check if main thread wants us to wait + std::unique_lock lock(pgeThreadPendingTasksMutex); + + if (isPGEThreadResetting.load()) { + + // 2. PGE Thread signals it's waiting + { + std::lock_guard mainLock(mainThreadPendingTasksMutex); + isMainThreadResetting = true; // Signal to main thread we're waiting + } + mainThreadResetCondition.notify_all(); // Wake up main thread - // Numpad - mapKeys[82] = Key::NP0; - mapKeys[83] = Key::NP1; - mapKeys[84] = Key::NP2; - mapKeys[85] = Key::NP3; - mapKeys[86] = Key::NP4; - mapKeys[87] = Key::NP5; - mapKeys[88] = Key::NP6; - mapKeys[89] = Key::NP7; - mapKeys[91] = Key::NP8; - mapKeys[92] = Key::NP9; - mapKeys[67] = Key::NP_MUL; // Numpad * - mapKeys[69] = Key::NP_ADD; // Numpad + - mapKeys[75] = Key::NP_DIV; // Numpad / - mapKeys[78] = Key::NP_SUB; // Numpad - - mapKeys[65] = Key::NP_DECIMAL; // Numpad . + // Note: MainThreadTasks(); will be called by the main thread to process tasks + + // 3. PGE Thread waits for main thread to finish + pgeThreadResetCondition.wait(lock, [this] { + return !isPGEThreadResetting.load(); + }); - // Symbol Keys (OEM equivalents) - mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key - mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key - mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) - mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key - mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. - mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key - mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) - mapKeys[24] = Key::EQUALS; // Equal sign = - mapKeys[43] = Key::COMMA; // Comma , - mapKeys[27] = Key::MINUS; // Minus - - mapKeys[47] = Key::PERIOD; // Period . + //4: return true indicating we processed tasks + return true; + } + else + { + // No pending tasks, just return + return false; + } + } + bool Host_Apple_MacOS::AddPendingMainThreadTask(MAINTASKS task) + { + // NOTE: Note: You should only add tasks that require main thread execution + vPendingMainThreadTasks.push_back(task); + MainThreadTasks(); + + return true; + } + + bool Host_Apple_MacOS::MainThreadTasks() + { + bool res = false; + if(vPendingMainThreadTasks.empty()) + return res; // edge case + + // 1. Main Thread locks PGE Thread + { + std::lock_guard lock(pgeThreadPendingTasksMutex); + isPGEThreadResetting = true; // Signal PGE to stop + } + pgeThreadResetCondition.notify_all(); // Wake up PGE thread to check flag + + // 2. Main Thread waits for PGE Thread to acknowledge and wait + std::unique_lock lock(mainThreadPendingTasksMutex); + mainThreadResetCondition.wait(lock, [this] { + return isMainThreadResetting.load(); // Wait until PGE signals it's waiting + }); + + // Process any pending main thread tasks + for (const auto& task : vPendingMainThreadTasks) + { + switch (task) + { + case CREATE_OPENGL_RENDERER: + { + // In this case, the PGE will be waiting for main thread to singal, so the ContextOBJ can be created + res = false; // No need to skip frame + break; + } + case RESIZE_WINDOW: + { + // Resize window on main thread + pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + pMacOSOpenGLRenderer->resetContextSize(frameBounds.width, frameBounds.height); + res = true; // Skip frame to allow resize to take effect + break; + } + case DEMINIMIZE_WINDOW: + case BECOME_ACTIVE: + { + pPGEwindow->olc_OnFocus(true); + break; + } + case MINIMIZE_WINDOW: + case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnFocus(false); + break; + } + case NONE: + default: + { + res = false; + break; + } + + } + } + vPendingMainThreadTasks.clear(); + + // 4. Main Thread unlocks PGE Thread + { + std::lock_guard lock(pgeThreadPendingTasksMutex); + isPGEThreadResetting = false; // Release PGE thread + isMainThreadResetting = false; // Reset main thread flag + } + pgeThreadResetCondition.notify_all(); // Wake up PGE thread + return res; } +//------ Events Handlers ----- - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ - olc_IgnoreUnused(bFullScreen); - pPGEwindow = pWindow; - pPGEwindow->SetWindowPosition(vWindowPos); - pPGEwindow->SetWindowSize(vWindowSize); - pPGEwindow->LinkToHost(this); - - frameBounds.x = 0.0; - frameBounds.y = 0.0; - frameBounds.width = static_cast(vWindowSize.x); - frameBounds.height = static_cast(vWindowSize.y); + void Host_Apple_MacOS::MacApplicationEventsHandler() + { + pMacApplication->setWillFinishLaunchingCallback([]() { }); + + pMacApplication->setDidFinishLaunchingCallback([&]() { + // Queue the Create OpenGL context task + vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); + // We need to wait until the application has launched to get the keyboard layout + pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); + + + }); + + pMacApplication->setWillTerminateCallback([&]() { + //todo : add any cleanup code here if needed + }); + + pMacApplication->setDidBecomeActiveCallback([]() { }); + + pMacApplication->setWillResignActiveCallback([]() { }); - return true; } - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ - pWindow->olc_OnWindowClose(); - return true; - } + void Host_Apple_MacOS::MacWindowEventsHandler() + { + pMacOSWindow->setWindowDidResizeCallback([&]() { + AddPendingMainThreadTask(RESIZE_WINDOW); + }); - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + pMacOSWindow->setWindowWillCloseCallback([&]() { + // NOTE: Do not add this event to PendingMainThreadTasks as it will cause deadlock since the main thread is required to process the close event but the close event is waiting on the main thread tasks to process it + pPGEwindow->olc_OnWindowClose(); + pPGEwindow->olc_ShouldRemove(); }); - return true; - } - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - olc_IgnoreUnused(pWindow); - // Ensure OpenGL renderer is created - if(pMacOSOpenGLRenderer == nullptr) - CreateCGLContextObj(); + pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { + AddPendingMainThreadTask(BECOME_ACTIVE); + }); - return vMacOSWindowDescriptors; + pMacOSWindow->setWindowDidResignKeyCallback([&]() { + AddPendingMainThreadTask(RESIGN_ACTIVE); + }); + + pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { + AddPendingMainThreadTask(MINIMIZE_WINDOW); + }); + pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { + AddPendingMainThreadTask(DEMINIMIZE_WINDOW); + }); + } - - - bool Host_Apple_MacOS::SyncWithDesktopComposite() + + // handles both down and up strokes for every supported key that isn't a modifier + void Host_Apple_MacOS::KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed) { - /* - core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, - the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, - set to false and toggle it on first call, so that vSync is only enabled once - */ + unsigned short keyCode = event.keyCode; - if(!enableVSync) + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; + bNumLockActive = !bNumLockActive; + return; } - - return enableVSync; - } - bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorPosition(vPos.x, vPos.y); - }); - return false; - } + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } + } - bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorVisibility(bVisible); - }); - return true; + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; + + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], isPressed); } - bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + void Host_Apple_MacOS::MacEventsHandler() { - olc_IgnoreUnused(pWindow); - // if we're already in the specified state, return early - if(pMacOSWindow->isFullScreen() == bFullScreen) - return true; + // General MacOS key event handling code here + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->toggleFullScreen(); + // Set up keyboard event handlers + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + KeyboardEventHandler(event, true); }); - 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 + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + KeyboardEventHandler(event, false); + }); - return nsStyle; - - } + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { - bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ - pPrimaryPGE = pPrimary; - return true; - } + static unsigned int prevFlags = 0; + unsigned int changedFlags = event.modifierFlags ^ prevFlags; + + // Check For Shift key + if (changedFlags & NSEventModifierFlagShift) { + bool isPressed = event.modifierFlags & NSEventModifierFlagShift; + pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); + } + + // Check for Control key + if (changedFlags & NSEventModifierFlagControl) { + bool isPressed = event.modifierFlags & NSEventModifierFlagControl; + pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); + } - bool Host_Apple_MacOS::StartSystem(){ + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; - // Create MacOS Application instance - pMacApplication = std::make_unique(); + // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); + } - // Set up application delegate event handlers - MacApplicationEventsHandler(); + // caps lock doesn't appear to trigger any event + prevFlags = event.modifierFlags; + }); - // Initialize and activate application first - pMacApplication->initialize(); - pMacApplication->activate(); - - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - - // Initialize the MacOS Window - pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); - pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); - pMacOSWindow->setContentViewPosition(0, 0); - - // Set up window event handlers - MacWindowEventsHandler(); + // Set up mouse event handlers + pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); - // Create Input Event handler - pMacOSEventHandler = std::make_unique(*pMacOSWindow); - - // Setup Event handlers - MacEventsHandler(); + pMacOSEventHandler->onMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); - // Create the window - unsigned long styleMask = ConvertPGE2WindowStyle(); - pMacOSWindow->show(styleMask); - pMacOSEventHandler->enable(); + pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - //--- Start up our engine threading system ---- - // Start the PGE context on the main thread - // Mark system as active - systemActive = true; - - // Create system thread - handles gpu context - std::thread threadSystem([this]() - { - // Notify start of system thread - if (!this->OnSystemThreadStart()) - { - // PGE->OnContextStart() failed, or user aborted OnUserCreate() - return; - } + pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - // Main system loop - while (systemActive) - { - // Perform primary window update - if (!this->OnSystemTick()) - { - StopSystem(); - } - } + pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - // Notify end of system thread - if (!this->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } + pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); }); + pMacOSEventHandler->onRightMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); - // Start the main event loop (this will block) - pMacApplication->run(); - - // Once the application run loop ends, join the system thread - if(threadSystem.joinable()) - threadSystem.join(); + pMacOSEventHandler->onRightMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + + }); - // Post-context end hook - return pPrimaryPGE->OnPostContextEnd(); + pMacOSEventHandler->onOtherMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); - } + pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); - bool Host_Apple_MacOS::StopSystem() - { - dispatch_sync(dispatch_get_main_queue(), ^{ - // clean up and close application - if (pMacOSOpenGLRenderer) - { - pMacOSOpenGLRenderer->destoryContext(); - pMacOSOpenGLRenderer = nullptr; - } - if (pMacOSWindow) - { - pMacOSWindow->destoryWindow(); - pMacOSWindow = nullptr; - } - if (pMacApplication) - { - pMacApplication->stop(); - } + pMacOSEventHandler->onOtherMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); + + pMacOSEventHandler->onScrollWheel([&](const olc::apis::macos::ScrollWheelEvent& event) { + // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling + pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); + }); + // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch + pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); }); - systemActive = false; - return true; - } - bool Host_Apple_MacOS::OnSystemThreadStart() - { - // Hold back threading until application is fully initialized - bSkipFrame = ExecutePendingMainThreadTasks(); - return pPrimaryPGE->OnContextStart(); - } + pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - bool Host_Apple_MacOS::OnSystemTick() - { - // Execute any pending main thread tasks - bSkipFrame = ExecutePendingMainThreadTasks(); - return pPrimaryPGE->OnContextTick(); - } + pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - bool Host_Apple_MacOS::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } + pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); - bool Host_Apple_MacOS::OnApplicationEnd() - { - return true; + // Stylus (tablet) events — full pressure, tilt and rotation data + pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + + pPGEwindow->olc_OnTouch( + event.touchID, + {static_cast(event.x), static_cast(event.y)}, + event.bPress, + event.bRelease, + {1.0f, 1.0f}, // stylus contact size — nominal 1x1 + true, // bStylus = true + event.pressure, + event.rotation, + {event.tiltX, event.tiltY} + ); + }); + } +} +#endif -//-- OS Window Event Handling ----- - - olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const - { - // Get system locale from MacOS Application - // We need to wait until the application has launched to get the keyboard layout - // Therefore this function is called again from setDidFinishLaunchingCallback event - if (pMacApplication) - { - std::string locale = pMacApplication->getSystemLocale(); - if (locale == "en_GB") - { - return olc::KeyboardLayout::QWERTY_UK; - } - else if (locale == "en_US") - { - return olc::KeyboardLayout::QWERTY_US; - } - else if (locale == "fr_FR") - { - return olc::KeyboardLayout::AZERTY; - } - else if (locale == "de_DE") - { - return olc::KeyboardLayout::QWERTZ; - } - } - // Default to QWERTY if unknown - return olc::KeyboardLayout::QWERTY_UK; - } +#if OLC_HOST == OLC_HOST_IOS +namespace olc::host { + -// ------- Priavate Main Thread Task Handling for MacOS Host ------- + // NSEventModifierFlags values + constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 - bool Host_Apple_MacOS::CreateCGLContextObj() + Host_Apple_iOS::Host_Apple_iOS() { - // 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(); - - dispatch_sync(dispatch_get_main_queue(), ^{ - // Edge case for when the window is auto resize due to MacOS clamping to screen size - pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); - pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; - pMacOSOpenGLRenderer->attachToWindow(*pMacOSWindow); - pMacOSOpenGLRenderer->setupContext(); - }); - - pMacGLConextObj = pMacOSOpenGLRenderer->getCGLContextObj(); - - pMacOSOpenGLRenderer->setVsync(false); - - vMacOSWindowDescriptors.push_back(pMacGLConextObj); // Pointer to CGLContextObj - vMacOSWindowDescriptors.push_back(&bSkipFrame); // Pointer to skip frame flag + // Map macOS key codes to olc::Key codes + mapKeys[4] = Key::A; + mapKeys[5] = Key::B; + mapKeys[6] = Key::C; + mapKeys[7] = Key::D; + mapKeys[8] = Key::E; + mapKeys[9] = Key::F; + mapKeys[10] = Key::G; + mapKeys[11] = Key::H; + mapKeys[12] = Key::I; + mapKeys[13] = Key::J; + mapKeys[14] = Key::K; + mapKeys[15] = Key::L; + mapKeys[16] = Key::M; + mapKeys[17] = Key::N; + mapKeys[18] = Key::O; + mapKeys[19] = Key::P; + mapKeys[20] = Key::Q; + mapKeys[21] = Key::R; + mapKeys[22] = Key::S; + mapKeys[23] = Key::T; + mapKeys[24] = Key::U; + mapKeys[25] = Key::V; + mapKeys[26] = Key::W; + mapKeys[27] = Key::X; + mapKeys[28] = Key::Y; + mapKeys[29] = Key::Z; - // 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); - } - - } + // Numeric keys + mapKeys[30] = Key::K1; + mapKeys[31] = Key::K2; + mapKeys[32] = Key::K3; + mapKeys[33] = Key::K4; + mapKeys[34] = Key::K5; + mapKeys[35] = Key::K6; + mapKeys[36] = Key::K7; + mapKeys[37] = Key::K8; + mapKeys[38] = Key::K9; + mapKeys[39] = Key::K0; - return true; - } + // Other Keys + mapKeys[40] = Key::ENTER; // Return + mapKeys[41] = Key::ESCAPE; // Escape + mapKeys[42] = Key::BACK; // Delete (Backspace) + mapKeys[43] = Key::TAB; // Tab + mapKeys[44] = Key::SPACE; // Space + mapKeys[45] = Key::DEL; // Forward Delete + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock + mapKeys[114] = Key::INS; // Help (Insert equivalent) - bool Host_Apple_MacOS::ExecutePendingMainThreadTasks() - { - // 1: Check if main thread wants us to wait - std::unique_lock lock(pgeThreadPendingTasksMutex); + // Function Keys + mapKeys[58] = Key::F1; + mapKeys[59] = Key::F2; + mapKeys[60] = Key::F3; + mapKeys[61] = Key::F4; + mapKeys[62] = Key::F5; + mapKeys[63] = Key::F6; + mapKeys[64] = Key::F7; + mapKeys[65] = Key::F8; + mapKeys[66] = Key::F9; + mapKeys[67] = Key::F10; + mapKeys[68] = Key::F11; + mapKeys[69] = Key::F12; + + mapKeys[74] = Key::HOME; // Home + mapKeys[75] = Key::PGUP; // Page Up + mapKeys[76] = Key::DEL; // Delete (Forward Delete) + mapKeys[77] = Key::END; // End + mapKeys[78] = Key::PGDN; // Page Down + - if (isPGEThreadResetting.load()) { - - // 2. PGE Thread signals it's waiting - { - std::lock_guard mainLock(mainThreadPendingTasksMutex); - isMainThreadResetting = true; // Signal to main thread we're waiting - } - mainThreadResetCondition.notify_all(); // Wake up main thread + // Arrow Keys + mapKeys[79] = Key::RIGHT; + mapKeys[80] = Key::LEFT; + mapKeys[81] = Key::DOWN; + mapKeys[82] = Key::UP; + + // CHRL, SHIFT, Instert (Help) Keys + mapKeys[117] = Key::INS; // Insert (Help key on Mac) + mapKeys[224] = Key::CTRL; // Left Control + mapKeys[225] = Key::SHIFT; // Left Shift + mapKeys[229] = Key::SHIFT; // Left Shift + + // Symbol Keys (OEM equivalents) + mapKeys[51] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[56] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[53] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[47] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[49] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[48] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[52] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . + + // Unknown and may not be supported, but included for completeness will come back to this + mapKeys[84] = Key::NP_DIV; // Numpad / + mapKeys[85] = Key::NP_MUL; // Numpad * + mapKeys[86] = Key::NP_SUB; // Numpad - + mapKeys[87] = Key::NP_ADD; // Numpad + + mapKeys[88] = Key::RETURN; // Numpad Enter + mapKeys[89] = Key::NP1; + mapKeys[90] = Key::NP2; + mapKeys[91] = Key::NP3; + mapKeys[92] = Key::NP4; + mapKeys[93] = Key::NP5; + mapKeys[94] = Key::NP6; + mapKeys[95] = Key::NP7; + mapKeys[96] = Key::NP8; + mapKeys[97] = Key::NP9; + mapKeys[98] = Key::NP0; + mapKeys[99] = Key::NP_DECIMAL; // Numpad . + mapKeys[915] = Key::PAUSE; // Not Supported, F16 (often used as pause) + mapKeys[916] = Key::SCROLL; // Not Supported, F14 (scroll lock equivalent) + mapKeys[917] = Key::OEM_8; // Section sign § (varies by keyboard) + } + + bool Host_Apple_iOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + olc_IgnoreUnused(bFullScreen); + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); - // Note: MainThreadTasks(); will be called by the main thread to process tasks - - // 3. PGE Thread waits for main thread to finish - pgeThreadResetCondition.wait(lock, [this] { - return !isPGEThreadResetting.load(); - }); + // iOS typically uses full screen, but we'll store the requested dimensions + frameBounds.x = static_cast(vWindowPos.x); + frameBounds.y = static_cast(vWindowPos.y); + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); + + return true; + } - //4: return true indicating we processed tasks - return true; - } - else - { - // No pending tasks, just return - return false; - } + bool Host_Apple_iOS::CloseWindowFrame(olc::Window* pWindow) + { + if (!pIOSViewController) return false; + if (!pWindow) return false; + pWindow->olc_OnWindowClose(); + return true; } - bool Host_Apple_MacOS::AddPendingMainThreadTask(MAINTASKS task) + bool Host_Apple_iOS::UpdateWindowFrameTitle(olc::Window* pWindow) { - // NOTE: Note: You should only add tasks that require main thread execution - vPendingMainThreadTasks.push_back(task); - MainThreadTasks(); - + if (!pIOSViewController) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pIOSViewController->setTitle(pWindow->GetWindowTitle().c_str()); + }); return true; } - - bool Host_Apple_MacOS::MainThreadTasks() + + std::vector Host_Apple_iOS::GetHostWindowDescriptor(olc::Window* pWindow) { - bool res = false; - if(vPendingMainThreadTasks.empty()) - return res; // edge case - - // 1. Main Thread locks PGE Thread + return vIOSWindowDescriptors; + } + + bool Host_Apple_iOS::SyncWithDesktopComposite() + { + if(!enableVSync) { - std::lock_guard lock(pgeThreadPendingTasksMutex); - isPGEThreadResetting = true; // Signal PGE to stop + pIOSOpenGLRenderer->enableVsync(); + enableVSync = true; } - pgeThreadResetCondition.notify_all(); // Wake up PGE thread to check flag - // 2. Main Thread waits for PGE Thread to acknowledge and wait - std::unique_lock lock(mainThreadPendingTasksMutex); - mainThreadResetCondition.wait(lock, [this] { - return isMainThreadResetting.load(); // Wait until PGE signals it's waiting + return enableVSync; + } + + bool Host_Apple_iOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + //pMacOSWindow->setCursorPosition(vPos.x, vPos.y); }); - - // Process any pending main thread tasks - for (const auto& task : vPendingMainThreadTasks) - { - switch (task) - { - case CREATE_OPENGL_RENDERER: - { - // In this case, the PGE will be waiting for main thread to singal, so the ContextOBJ can be created - res = false; // No need to skip frame - break; - } - case RESIZE_WINDOW: - { - // Resize window on main thread - pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); - pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); - pMacOSOpenGLRenderer->resetContextSize(frameBounds.width, frameBounds.height); - res = true; // Skip frame to allow resize to take effect - break; - } - case DEMINIMIZE_WINDOW: - case BECOME_ACTIVE: - { - pPGEwindow->olc_OnFocus(true); - break; - } - case MINIMIZE_WINDOW: - case RESIGN_ACTIVE: - { - pPGEwindow->olc_OnFocus(false); - break; - } - case NONE: - default: - { - res = false; - break; - } - - } - } - vPendingMainThreadTasks.clear(); - - // 4. Main Thread unlocks PGE Thread - { - std::lock_guard lock(pgeThreadPendingTasksMutex); - isPGEThreadResetting = false; // Release PGE thread - isMainThreadResetting = false; // Reset main thread flag - } - pgeThreadResetCondition.notify_all(); // Wake up PGE thread - return res; + return false; } -//------ Events Handlers ----- + bool Host_Apple_iOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + //pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; + } - void Host_Apple_MacOS::MacApplicationEventsHandler() + bool Host_Apple_iOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) { - pMacApplication->setWillFinishLaunchingCallback([]() { }); - - pMacApplication->setDidFinishLaunchingCallback([&]() { - // Queue the Create OpenGL context task - vPendingMainThreadTasks.push_back(CREATE_OPENGL_RENDERER); - // We need to wait until the application has launched to get the keyboard layout - pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); + olc_IgnoreUnused(pWindow); + dispatch_async(dispatch_get_main_queue(), ^{ + //pMacOSWindow->toggleFullScreen(); + }); + return true; + } + + bool Host_Apple_iOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Apple_iOS::StartSystem(){ - - }); - - pMacApplication->setWillTerminateCallback([&]() { - //todo : add any cleanup code here if needed - }); - - pMacApplication->setDidBecomeActiveCallback([]() { }); - - pMacApplication->setWillResignActiveCallback([]() { }); + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Create iOS Application instance + pIOSApplication = std::make_unique(); + // Set up application delegate event handlers + IOSApplicationEventsHandler(); + + // Initialize and activate application first + pIOSApplication->initialize(); + + return true; + } - void Host_Apple_MacOS::MacWindowEventsHandler() + bool Host_Apple_iOS::StopSystem() { - pMacOSWindow->setWindowDidResizeCallback([&]() { - AddPendingMainThreadTask(RESIZE_WINDOW); - }); + // Note: iOS applications typically don't have a "stop" method, but we can clean up resources here if needed + //dispatch_sync(dispatch_get_main_queue(), ^{ + + //}); + return true; + } - pMacOSWindow->setWindowWillCloseCallback([&]() { - // NOTE: Do not add this event to PendingMainThreadTasks as it will cause deadlock since the main thread is required to process the close event but the close event is waiting on the main thread tasks to process it - pPGEwindow->olc_OnWindowClose(); - pPGEwindow->olc_ShouldRemove(); - }); + bool Host_Apple_iOS::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } - pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { - AddPendingMainThreadTask(BECOME_ACTIVE); - }); + bool Host_Apple_iOS::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } - pMacOSWindow->setWindowDidResignKeyCallback([&]() { - AddPendingMainThreadTask(RESIGN_ACTIVE); - }); - - pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { - AddPendingMainThreadTask(MINIMIZE_WINDOW); - }); - - pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { - AddPendingMainThreadTask(DEMINIMIZE_WINDOW); - }); - + bool Host_Apple_iOS::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); } - - // handles both down and up strokes for every supported key that isn't a modifier - void Host_Apple_MacOS::KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed) + + bool Host_Apple_iOS::OnApplicationEnd() { - unsigned short keyCode = event.keyCode; - - // handle num clear/lock key only on the down stroke. - if(isPressed && keyCode == 71) + return true; + } + + std::string Host_Apple_iOS::GetApplicationPath() + { + if(pIOSApplication) { - bNumLockActive = !bNumLockActive; - return; + return pIOSApplication->getApplicationPath(); } + return "ACCESS-DEINED"; + } - if(!bNumLockActive) + olc::KeyboardLayout Host_Apple_iOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pIOSApplication) { - // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - switch(keyCode) + std::string locale = pIOSApplication->getSystemLocale(); + if (locale == "en_GB") { - case 84: keyCode = 125; break; - case 86: keyCode = 123; break; - case 88: keyCode = 124; break; - case 91: keyCode = 126; break; - default: break; + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; } } - - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) - keyCode = 50; - - pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], isPressed); + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; } - void Host_Apple_MacOS::MacEventsHandler() - { - // General MacOS key event handling code here - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - - // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - KeyboardEventHandler(event, true); - }); - pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - KeyboardEventHandler(event, false); - }); + void Host_Apple_iOS::UpdateIOSViewFrameBounds(olc::apis::ios::DeviceOrientation orientation) + { + // NOTE: iOS handles rotation automatically, but we need to adjust the PGE window size accordingly + // IMPORTANT: PortraitUpsideDown is not implemented, in short have a happy life and forget this exist ;) + CurrentOrientation = orientation; + switch (orientation) { + case olc::apis::ios::DeviceOrientation::Portrait: + pIOSViewController->getViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + break; + case olc::apis::ios::DeviceOrientation::LandscapeLeft: + case olc::apis::ios::DeviceOrientation::LandscapeRight: + pIOSViewController->getViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.height), static_cast(frameBounds.width)}); + break; + case olc::apis::ios::DeviceOrientation::FaceUp: + case olc::apis::ios::DeviceOrientation::FaceDown: + case olc::apis::ios::DeviceOrientation::PortraitUpsideDown: + case olc::apis::ios::DeviceOrientation::Unknown: + default: + break; + } + - // Set up keyboard flag event handlers - pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { + } - static unsigned int prevFlags = 0; - unsigned int changedFlags = event.modifierFlags ^ prevFlags; + void Host_Apple_iOS::IOSApplicationEventsHandler() + { + // Application event handling code here + // Set application delegate event handlers + pIOSApplication->setDidFinishLaunchingCallback([&]() { + // Tell the PGE 3.0 we have loaded the application + + pIOSApplication->activate(); - // Check For Shift key - if (changedFlags & NSEventModifierFlagShift) { - bool isPressed = event.modifierFlags & NSEventModifierFlagShift; - pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); - } + // Initialize the iOS View Controller + pIOSViewController = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 iOS Demo"); - // Check for Control key - if (changedFlags & NSEventModifierFlagControl) { - bool isPressed = event.modifierFlags & NSEventModifierFlagControl; - pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); - } - - if (changedFlags & NSEventModifierFlagCommand) { - bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; - if(isPressed) - std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + // Set up view controller event handlers + IOSViewControllerEventsHandler(); + + // Present the view controller + pIOSViewController->present(); + + // App became active (foreground) + if(pIOSOpenGLRenderer == nullptr) + { + vIOSWindowDescriptors.clear(); // ensure we are starting fresh + pIOSOpenGLRenderer = std::make_shared(); - // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); - } + pIOSOpenGLRenderer->attachToViewController(*pIOSViewController); + pIOSOpenGLRenderer->setupContext(); - // caps lock doesn't appear to trigger any event - prevFlags = event.modifierFlags; + pIOSGLContextObj = pIOSOpenGLRenderer->getEAGLContext(); + + pIOSOpenGLRenderer->setVsync(false); + + vIOSWindowDescriptors.push_back(pIOSGLContextObj); + } + + // Set up OpenGL renderer for visual feedback + pIOSOpenGLRenderer->makeCurrentContext(); + + //std::cout << "iOS Application did finish launching." << std::endl; }); - - // Set up mouse event handlers - pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + + pIOSApplication->setWillTerminateCallback([&]() { + // TODO: Implement olc_OnApplicationTerminate in window.h/cpp + //std::cout << "iOS Application will terminate." << std::endl; }); - pMacOSEventHandler->onMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + pIOSApplication->setDidBecomeActiveCallback([&]() { + // TODO: manage thread resuming + + if(pPGEwindow) + pPGEwindow->olc_OnFocus(true); + ///std::cout << "iOS Application did become active." << std::endl; }); - pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + pIOSApplication->setWillResignActiveCallback([&]() { + + if(pPGEwindow) + pPGEwindow->olc_OnFocus(false); + // TODO: manage thread pausing + //std::cout << "iOS Application will resign active." << std::endl; }); - pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + pIOSApplication->setDidEnterBackgroundCallback([&]() { + // App entered background + if(pPGEwindow) + pPGEwindow->olc_OnFocus(false); + std::cout << "iOS Application did enter background." << std::endl; }); - - pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + + pIOSApplication->setWillEnterForegroundCallback([&]() { + // App will enter foreground + if(pPGEwindow) + pPGEwindow->olc_OnFocus(true); + //std::cout << "iOS Application will enter foreground." << std::endl; }); + } - pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + void Host_Apple_iOS::IOSViewControllerEventsHandler() + { + // View controller event handling code here + pIOSViewController->setViewDidLoadCallback([&]() { + // View controller loaded + + // Set up GLKView after presentation + pIOSGLKView = std::make_unique(*pIOSViewController); + // Setup Event handlers (including touch events) + IOSGLKViewEventHandler(); + if (!pIOSGLKView->initialize()) + { + std::cerr << "Failed to initialize GLKView." << std::endl; + return; + } + pIOSGLKView->enableTouchHandling(); + + auto test = pIOSGLKView->isMultipleTouchEnabled(); + if(!test) + pIOSGLKView->setMultipleTouchEnabled(true); + pIOSGLKView->debugTouchSetup(); + //std::cout << "iOS View Controller did load." << std::endl; }); - pMacOSEventHandler->onRightMouseDown([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + pIOSViewController->setViewWillAppearCallback([&]() { + // View will appear + + //std::cout << "iOS View Controller will appear." << std::endl; }); - - pMacOSEventHandler->onRightMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + + pIOSViewController->setViewDidAppearCallback([&]() { + // View appeared + if(!bIOSDrawing) + { + pIOSViewController->setPaused(false); // Start drawing + } + //std::cout << "iOS View Controller did appear." << std::endl; }); - pMacOSEventHandler->onOtherMouseDown([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + pIOSViewController->setViewWillDisappearCallback([&]() { + // View will disappear + pPGEwindow->olc_OnWindowClose(); + //std::cout << "iOS View Controller will disappear." << std::endl; }); - pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + pIOSViewController->setViewDidDisappearCallback([&]() { + // View disappeared + + pPGEwindow->olc_ShouldRemove(); + //std::cout << "iOS View Controller did disappear." << std::endl; }); - - pMacOSEventHandler->onOtherMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + + pIOSViewController->setViewDidLayoutSubviewsCallback([&]() { + + UpdateIOSViewFrameBounds(CurrentOrientation); }); - - pMacOSEventHandler->onScrollWheel([&](const olc::apis::macos::ScrollWheelEvent& event) { - // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling - pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); + + pIOSViewController->setUpdateCallback( [&]() { + // This is where we will call the engine's main loop for rendering + + }); + + // Device orientation changes + pIOSViewController->setOrientationChangedCallback([&](olc::apis::ios::DeviceOrientation orientation) { + UpdateIOSViewFrameBounds(orientation); }); + + } - // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch - pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + // handles both down and up strokes for every supported key that isn't a modifier + void Host_Apple_iOS::KeyboardEventHandler(uint16_t nKeyCode, uint nModifierFlags, bool isPressed){ + // handle num clear/lock key only on the down stroke. + if(isPressed && nKeyCode == 83) + { + bNumLockActive = !bNumLockActive; + return; + } + + if(!bNumLockActive) + { + // 94 right, 92 left, 90 down, 96 up >>> 79 RIGHT, 80 LEFT, 81 DOWN, 82 UP + switch(nKeyCode) + { + case 94: nKeyCode = 79; break; + case 92: nKeyCode = 80; break; + case 90: nKeyCode = 81; break; + case 96: nKeyCode = 82; break; + default: break; + } + } + + // Check Modifier flags for Shift, Control, Caps, and Command keys + unsigned int changedFlags = nModifierFlags ^ prevFlags; + + // Check For Shift key + if (changedFlags & NSEventModifierFlagShift) { + bool isPressed = nModifierFlags & NSEventModifierFlagShift; + pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); + } + + // Check for Control key + if (changedFlags & NSEventModifierFlagControl) { + bool isPressed = nModifierFlags & NSEventModifierFlagControl; + pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); + } + + // Check for Option / ALT key + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = nModifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + } + + prevFlags = nModifierFlags; + + pPGEwindow->olc_OnKeyPress(mapKeys[nKeyCode], isPressed); + + } + + void Host_Apple_iOS::IOSGLKViewEventHandler() + { + + // Set up drawing callback for GLKView + pIOSGLKView->setDrawCallback([&](double x, double y, double width, double height) { + + pIOSOpenGLRenderer->makeCurrentContext(); + + if(!bPGEInitialized) + { + pPrimaryPGE->OnContextStart(); + bPGEInitialized = true; + } + pPrimaryPGE->OnContextTick(); + }); + + // Set up touch event handlers (iOS primary input method) + pIOSGLKView->setTouchBeganCallback([&](const olc::apis::ios::TouchEvent& event) { + // Convert touch to mouse button press for compatibility pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - true, false, - {static_cast(event.sizeX), static_cast(event.sizeY)}); + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Began at (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; }); - pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - false, false, - {static_cast(event.sizeX), static_cast(event.sizeY)}); + pIOSGLKView->setTouchMovedCallback([&](const olc::apis::ios::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Moved to (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; }); - pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - false, true, - {static_cast(event.sizeX), static_cast(event.sizeY)}); + pIOSGLKView->setTouchEndedCallback([&](const olc::apis::ios::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Ended at (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; }); - pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { - pPGEwindow->olc_OnTouch(event.touchID, - {static_cast(event.x), static_cast(event.y)}, - false, true, // treat cancel as release - {static_cast(event.sizeX), static_cast(event.sizeY)}); + pIOSGLKView->setTouchCancelledCallback([&](const olc::apis::ios::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + //std::cout << "ID: " << event.touchID << " Touch Cancelled at (" << event.x << ", " << event.y << ") with size (" << event.sizeX << ", " << event.sizeY << ")" << std::endl; }); - // Stylus (tablet) events — full pressure, tilt and rotation data - pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { - - pPGEwindow->olc_OnTouch( - event.touchID, - {static_cast(event.x), static_cast(event.y)}, - event.bPress, - event.bRelease, - {1.0f, 1.0f}, // stylus contact size — nominal 1x1 - true, // bStylus = true - event.pressure, - event.rotation, - {event.tiltX, event.tiltY} - ); + // Set up keyboard event handlers + pIOSGLKView->setKeyDownCallback([&](uint16_t keyCode, const std::string& characters, uint modifierFlags) { + //std::cout << "Key Down - Code: " << keyCode << ", Chars: '" << characters << "'" << std::endl; + KeyboardEventHandler(keyCode, modifierFlags, true); }); - - } + pIOSGLKView->setKeyUpCallback([&](uint16_t keyCode, const std::string& characters, uint modifierFlags) { + //std::cout << "Key Up - Code: " << keyCode << std::endl; + KeyboardEventHandler(keyCode, modifierFlags, false); + }); + } } + #endif #if OLC_HOST == OLC_HOST_LINUX_X11 @@ -16152,7 +20330,7 @@ namespace olc::apis::opengl bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffersEXT)) != nullptr; // bLoaded &= (_glTexImage2DMultisample = OGL_LOAD(glTexImage2DMultisample)) != nullptr; bLoaded &= (_glBlitFramebuffer = OGL_LOAD(glBlitFramebuffer)) != nullptr; -#elif OLC_HOST == OLC_HOST_ANDROID +#elif OLC_HOST == OLC_HOST_ANDROID || OLC_HOST == OLC_HOST_IOS bLoaded &= (_glBindVertexArray = OGL_LOAD(glBindVertexArray)) != nullptr; bLoaded &= (_glGenVertexArrays = OGL_LOAD(glGenVertexArrays)) != nullptr; bLoaded &= (_glDrawBuffers = OGL_LOAD(glDrawBuffers)) != nullptr; @@ -16232,7 +20410,7 @@ namespace olc::apis::opengl sLocation << "OGL33 Error: GL_INVALID_FRAMEBUFFER_OPERATION\n"; break; case GL_OUT_OF_MEMORY: sLocation << "OGL33 Error: GL_OUT_OF_MEMORY\n"; break; -#if OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS case GL_STACK_UNDERFLOW: sLocation << "OGL33 Error: GL_STACK_UNDERFLOW\n"; break; case GL_STACK_OVERFLOW: @@ -16270,7 +20448,7 @@ namespace olc::apis::opengl void gl::glTexEnvf(GLenum target, GLenum pname, GLfloat param) { -#if OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS ::glTexEnvf(target, pname, param); CheckError(); #endif @@ -16350,7 +20528,7 @@ namespace olc::apis::opengl void gl::glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS ::glGetTexImage(target, level, format, type, pixels); CheckError(); #endif @@ -16364,7 +20542,7 @@ namespace olc::apis::opengl void gl::glPolygonMode(GLenum face, GLenum mode) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS ::glPolygonMode(face, mode); CheckError(); #endif @@ -16569,7 +20747,7 @@ namespace olc::apis::opengl void gl::glTexImage2DMultisample(GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) { -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS _glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); CheckError(); #endif @@ -16646,7 +20824,7 @@ namespace olc::gpu // === PIXEL SHADER PGE DEFAULTS === std::string Shader::static_PS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS R"(#version 330 core )" #else @@ -16685,7 +20863,7 @@ void main() // === VERTEX SHADER PGE DEFAULTS === std::string Shader::static_VS_DefaultHeader = -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS R"(#version 330 core )" #else @@ -16960,6 +21138,17 @@ void main() #endif +#if OLC_HOST == OLC_HOST_IOS + + // os_win_id[0] is the OLC EAGL OpenGL Device Context + glRenderContext = (olc::apis::opengl::glRenderContext_t)os_win_id[0]; + if (!glRenderContext) { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } + +#endif + #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND || OLC_HOST == OLC_HOST_ANDROID #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID #if OLC_HOST == OLC_HOST_ANDROID @@ -17114,7 +21303,7 @@ void main() gl.glEnableVertexAttribArray(5); // Buffers are configured, unbind for now - gl.glBindBuffer(gl.GL_ARRAY_BUFFER_X, 0); + gl.glBindBuffer(gl.GL_ARRAY_BUFFER_X, nScreenFBO); // Updated to better support iOS GLKit, and other Platforms FBOs which are not 0 gl.glBindVertexArray(0); @@ -17169,7 +21358,7 @@ void main() // PGE Specific requirements // Texturing Enabled -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS gl.glEnable(GL_TEXTURE_2D); // Turn on texturing gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); #endif @@ -17201,6 +21390,10 @@ void main() CGLSetCurrentContext(NULL); CGLDestroyContext((CGLContextObj)glRenderContext); #endif +#if OLC_HOST == OLC_HOST_IOS + // GLKit manages the OpenGL context on iOS, so we don't need to destroy it manually. + // Left in for completeness and future-proofing, but should not be called on iOS. +#endif #if OLC_HOST == OLC_HOST_LINUX_X11 auto* display = X11::XOpenDisplay(nullptr); X11::glXMakeCurrent(display, 0, NULL); @@ -17238,6 +21431,14 @@ void main() return false; } #endif +#if OLC_HOST == OLC_HOST_IOS + // params[0] is the OLC EAGL OpenGL Device Context + glRenderContext = (olc::apis::opengl::glRenderContext_t)os_win_id[0]; + if (!glRenderContext) { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } +#endif #if OLC_HOST == OLC_HOST_LINUX_X11 const auto window = reinterpret_cast(os_win_id[0]); auto* display = reinterpret_cast(os_win_id[1]); @@ -17293,6 +21494,14 @@ void main() return false; } +#endif +#if OLC_HOST == OLC_HOST_IOS + // params[0] is the OLC EAGL OpenGL Device Context + glRenderContext = (olc::apis::opengl::glRenderContext_t)os_win_id[0]; + if (!glRenderContext) { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } #endif return true; } @@ -17353,7 +21562,7 @@ void main() mapTextureToRenderbuffer[id] = rboId; } -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_MACOS && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_MACOS && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif @@ -17389,7 +21598,7 @@ void main() ); // Unbind renderbuffer - gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, 0); + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nScreenFBO); // Updated to better support iOS GLKit, and other Platforms FBOs which are not 0 } // Update size tracking @@ -17409,7 +21618,7 @@ void main() // which has been blitted to via ResolveMSAA if its an MSAA texture gl.glBindTexture(GL_TEXTURE_2D, image.GetGPUID()); -#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID +#if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID && OLC_HOST != OLC_HOST_IOS gl.glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); #else gl.glReadPixels(0, 0, image.Size().x, image.Size().y, GL_RGBA, GL_UNSIGNED_BYTE, image.Data()); @@ -17962,6 +22171,10 @@ void main() #endif +#if OLC_HOST == OLC_HOST_IOS + olc_IgnoreUnused(bVerticalSyncNow); +#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 const auto window_handle = reinterpret_cast(os_win_id[0]); auto* display = reinterpret_cast(os_win_id[1]); @@ -18033,7 +22246,7 @@ void main() #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpessimizing-move" // Suppress clang C++20 warning about moves preventing copy elision on macOS #endif @@ -19302,7 +23515,7 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } -#if OLC_HOST == OLC_HOST_MACOS +#if OLC_HOST == OLC_HOST_MACOS || OLC_HOST == OLC_HOST_IOS #pragma clang diagnostic pop #endif @@ -21530,6 +25743,171 @@ namespace olc::imload } } #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_IOS +namespace olc::imload +{ + + bool ImageLoader_iOS::CreateImageFromFile(olc::Image& image, const std::string& sFileName) + { + try { + + // Create iOS API wrapper image loader + olc::apis::ios::ImageLoader loader; + + // OK Working with mobile storage, we need to check for the file in various locations + std::filesystem::path filePath(sFileName); + + // Lets check if the developer has provided a full path to the file + if (!std::filesystem::exists(filePath)) { + + // File does not exist at the provided path, try to locate it in the app bundle + char* cAppFilePath = imageloader_getApplicationPath(loader.getCHandle()); + if (cAppFilePath) { + + // we need the directory of the app bundle to search for the file + std::filesystem::path fsAppDir(cAppFilePath); + std::filesystem::path potentialPath = fsAppDir / sFileName; + std::filesystem::path normalizedPath = std::filesystem::weakly_canonical(potentialPath); + + // lets check if the file exists in the app bundle directory (Project Directory) + if(std::filesystem::exists(normalizedPath)) { + filePath = normalizedPath; + } + else + { + bool bFound = false; + // Try multiple locations + for(auto& entry : std::filesystem::directory_iterator(fsAppDir)) + { + if (entry.is_directory()) { + potentialPath = entry.path() / sFileName; + normalizedPath = std::filesystem::weakly_canonical(potentialPath); + + if (std::filesystem::exists(normalizedPath)) { + filePath = potentialPath; + bFound = true; + break; + } + + } // end is entry directory + } // end directory search + + if(!bFound) return false; // all done nothing found + + } // end (Project Directory) search + + } + else + { + return false; // Failed to get application path + } + + } + + + // Convert back to string for the loader + std::string fullPath = filePath.string(); + + // Load the image file + if (!loader.loadFromFile(fullPath) || !loader.isLoaded()) { + return false; // Failed to load file + } + + // Get image dimensions and info + int width =0; int height = 0; int bytesPerPixel = 0; + 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.CreateNoGPU({width, height})) { + return false; // Failed to create image + } + + // Clear and resize the pixel vector + image.GetPixels().clear(); + image.GetPixels().resize(width * height); + + // Copy pixel data - assuming the loader provides RGBA data + // The api_ios should provide RGBA format with 4 bytes per pixel + std::memcpy(image.GetPixels().data(), pixelData, width * height * 4); + + return true; + } + catch (const std::exception& e) { + // Handle any exceptions from the wrapper + printf("Exception in CreateImageFromFile: %s\n", e.what()); + return false; + } + } + + bool ImageLoader_iOS::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) + { + if(!data) return false; + + // Create macOS API wrapper image loader + olc::apis::ios::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.CreateNoGPU({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_iOS::CreateImageFromMemory(olc::Image& image, const std::vector& data) + { + return CreateImageFromMemory(image, data.data(), data.size()); + } + + bool ImageLoader_iOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + { + olc_IgnoreUnused(image, sFileName); + return false; + } + + bool ImageLoader_iOS::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) + { + olc_IgnoreUnused(image, data); + return false; + } +} +#endif #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG namespace olc::imload {