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 7730539..3e3e379 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..554b47f --- /dev/null +++ b/dev/src/api_ios_wrapper.hpp @@ -0,0 +1,934 @@ +#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_); + } + + void terminate() noexcept { + if (app_) application_destroy(app_); + app_ = nullptr; + } + + // 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 b170c9e..35ceb74 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 @@ -341,7 +349,7 @@ namespace olc olc::hw::Touch& PGEWindow::GetTouch() { return touch; } - + const olc::vi2d& PGEWindow::ScreenSize() { return GetScreen().Size(); diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index bd2e362..9dbbb98 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 @@ -1288,7 +1288,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 0ceec8a..2f86744 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 @@ -77,7 +77,7 @@ R"( void main() { gl_PointSize = 1.0; // Required for emscripten - + if (pgeDrawType == 2) // 3D { gl_Position = pgeMVP * vec4(aPos.x, aPos.y, aPos.z, 1.0); @@ -322,6 +322,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 @@ -476,7 +487,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); @@ -531,7 +542,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 @@ -563,6 +574,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); @@ -600,6 +615,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]); @@ -655,6 +678,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; } @@ -715,7 +746,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 @@ -751,7 +782,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 @@ -771,7 +802,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()); @@ -1324,6 +1355,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..aa6127e --- /dev/null +++ b/dev/src/host_apple_ios.cpp @@ -0,0 +1,600 @@ +#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) + { + if(!pPrimaryPGE->OnContextStart()) + throw std::runtime_error("PGE3 failed to initialize context, User aborted OnUserCreate()"); + else + bPGEInitialized = true; + } + if(bPGEInitialized) [[likely]] + if(!pPrimaryPGE->OnContextTick() + throw std::runtime_error("PGE3 failed to tick context, User aborted OnUserUpdate()"); + }); + + // 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 7422b02..ebf5ab3 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 @@ -5775,4787 +5792,8383 @@ 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_); + } + + void terminate() noexcept { + if (app_) application_destroy(app_); + app_ = nullptr; + } + + // 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 - // Map of system keycodes to olc::Keycodes - std::unordered_map mapKeys; - }; - } +#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 + + +#endif + +#if !defined(CALLSTYLE) + #define CALLSTYLE #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; } + namespace apis::opengl + { - 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; + 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); - virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; +#if OLC_HOST == OLC_HOST_WINDOWS + typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); +#endif - // 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; +#if OLC_HOST == OLC_HOST_LINUX_X11 + typedef GLint CALLSTYLE glXSwapIntervalEXT_t(X11::Display* dpy, X11::GLXDrawable drawable, GLint interval); +#endif - public: // OS Specific Environment Information - virtual olc::KeyboardLayout GetKeyboardLayout() const override; + // A little GL class (singleton) + class gl + { + public: + static gl& Get(); + inline bool HasLoaded() const { return bLoaded; } - 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; + private: + gl() = default; + static bool bLoaded; - protected: - HostError lastError = HostError::None; + private: + bool LoadAll(); - 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: + 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 - 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, - SET_MOUSE_POSITION - }; - - // 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 +#if OLC_HOST == OLC_HOST_LINUX_X11 + public: + glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; +#endif - // 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 + public: + // Proxies allow switchable, clutter-free error checking - 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 + // OpenGL3.3 Proxies - struct sFrameBounds - { - double x = 0.0; - double y = 0.0; - double width = 800.0; - double height = 600.0; - } frameBounds; + 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); - 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(); + // 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); - olc::vi2d vPositionMouse = {0, 0}; // Stores the desired mouse position for SET_MOUSE_POSITION task - - - }; - } -} -#endif + void glSwapInterval(GLsizei n); -#if OLC_HOST == OLC_HOST_LINUX_X11 + // 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()); -#include -#include -namespace X11 -{ -#include -#include -#include -#include -#include -#include -#undef None -constexpr int None = 0L; -} + }; + } + +} // olc namespace -namespace olc::host +#if !defined(PGE_RENDERER_OPENGL33_DECLARED) +namespace olc { - 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; + namespace gpu + { + class Shader_GLSL33 : public olc::gpu::Shader + { + public: + std::string Compile() override; + int32_t CreateUniform(const std::string& name) override; + }; - int xinput_extension_code{}; - public: - Host_Linux_X11(); + 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; - 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; + 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; - olc::KeyboardLayout GetKeyboardLayout() const override; - void UpdateKeyboardLayout(); + 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; - // Wait for entire host desktop refresh (for smooooth vsync) - bool SyncWithDesktopComposite() override; + public: // GPU Task Stuff + virtual bool DoGPUTask(const olc::GPUTask& task) 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: // 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: - 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}; + + 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 - std::unordered_map mapKeys; - std::unordered_map mapMouseButtons; + 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; - // Keyboard Layout Variables - olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; - bool kbExtensionsFound = false; - int xkbEventBase = 0; - int xkbErrorBase = 0; + uint32_t nCurrentTextureTarget = 0; + uint32_t nCurrentTextureSource = 0; - // Mouse Variables - std::unordered_map mapUID2X11Cursor; - bool bMouseIsVisible = true; - }; -} + //std::unordered_map mapMSAAToResolved; + uint32_t nResolveFBO_Read = 0; + uint32_t nResolveFBO_Draw = 0; -#endif + std::unordered_map mapTextureSizes; -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + std::unordered_map mapTextureToRenderbuffer; + const Shader* pCurrentShader = nullptr; -#include -#include -#include -#include "xdg-shell.h" + 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 "pointer-warp.h" -#include -#include -#include -#include -#include -#include +#if OLC_HOST == OLC_HOST_ANDROID + EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); +#endif -#include "libdecor.h" + }; + } +} +#define PGE_RENDERER_OPENGL33_DECLARED 1 +#endif +#endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif -#include -#include +#define _WINSOCKAPI_ -#ifndef WL_KEYBOARD_KEY_STATE_REPEATED -#define WL_KEYBOARD_KEY_STATE_REPEATED 2 +#if !defined(VC_EXTRALEAN) + #define VC_EXTRALEAN #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(); - }; +#if !defined(NOMINMAX) + #define NOMINMAX +#endif - 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 - }; +// 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 - 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}; +// 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_ - struct Axis { - wl_fixed_t value{}; - int32_t discrete{0}; - bool valid{false}; - }; - std::array axes{}; - uint32_t axis_source{0}; - }; +#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 TouchEventMask { - TouchEventDown = 1 << 0, - TouchEventUp = 1 << 1, - TouchEventMotion = 1 << 2, - TouchEventCancel = 1 << 3, - TouchEventShape = 1 << 4, - TouchEventOrientation = 1 << 5 - }; + // 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; - 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{}; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - uint32_t time{0}; - uint32_t serial{0}; - int32_t id{0}; - }; - } + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; - class Host_Linux_Wayland : public olc::host::Host + // 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_IMAGELOADER == OLC_IMAGELOADER_MACOS +#if !defined(PGE_IMAGELOADER_MACOS_DECLARED) +namespace olc +{ + namespace imload { - 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}; + 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; - size_t active_window_id; - - // libdecor support - bool using_libdecor{false}; - libdecor* decor_context{nullptr}; - std::mutex decor_mutex; + // 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; - public: - Host_Linux_Wayland(); - ~Host_Linux_Wayland(); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; - bool CloseWindowFrame(olc::Window* pWindow) override; - bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) 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; +#define PGE_IMAGELOADER_MACOS_DECLARED 1 +#endif +#endif - 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); +#if OLC_IMAGELOADER == OLC_IMAGELOADER_IOS +#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; - 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); + // 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 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); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - // 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); + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; - // 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); + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - // 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); +#define PGE_IMAGELOADER_IOS_DECLARED 1 +#endif +#endif - // 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); +#if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG +#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +#include - // 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); +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; - // 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); + // 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; - // 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); + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; - bool CreateEGLContext(WaylandWindow* window); + // 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; + + 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); - std::unordered_map mapUID2Window; - std::unordered_map mapUID2OlcWindow; - std::atomic systemActive {true}; - std::unordered_map mapKeys; - olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; + private: // libpng internals + bool DecodePNG(olc::Image& image, png_structp png, png_infop info); }; } +#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 +#endif #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - -#include -#include -#include +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER +#include -namespace olc::host +#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) +namespace olc::imload { - class Host_Web_Emscripten : public olc::host::Host + + // Create an image resource based on an image file asset + class ImageLoader_NDKImageDecoder : public ImageLoader { 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; + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) 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; + // 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; - public: // OS Specific Environment Information - olc::KeyboardLayout GetKeyboardLayout() const override; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) 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); + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; - 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); + // 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 - 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); +#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; - // 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(); + // 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; - public: // Callback data type - struct CallbackData { - Host_Web_Emscripten* pHost; - olc::Window* pWindow; - std::string canvasId; - }; + // 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; - 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}; }; - - } +#define PGE_IMAGELOADER_STB_DECLARED 1 +#endif #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(); +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) - 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; +#if OLC_HOST == OLC_HOST_MACOS - // 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; +// 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"; - // 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; +// 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"; - void OnAppCmd(AndroidApp* app, int32_t cmd); - int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); - bool IsInitialized() const { return initialized.load(); } +// 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:"; - void ShowKeyboard(bool bShow); +// 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:"; - // TODO: file loading support (temporary) - std::vector OpenFile(const std::string& sFileName); - std::string OpenTextFile(const std::string& sFileName); +// 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:"; - static AndroidApp* androidApp; - protected: - olc::Window* pgeWindow = nullptr; - std::atomic initialized{false}, systemActive{false}; - bool shiftOn = false; +// 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:"; - 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); +// 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:"; - void PollEvents( - const std::function& funcContinue, - bool bBlocking = false - ); - }; +// 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:"; - class JNI - { - public: - static void Init(AndroidApp* app); - static void Detach(); +// 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:"; - static JNIEnv* GetEnv(); +// 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"; - private: - static JavaVM* jvm; - }; +// 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"; - class JNIObject - { - public: - JNIObject() = default; - JNIObject(jobject obj); +// 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"; - ~JNIObject(); - JNIObject(const JNIObject&) = delete; - JNIObject& operator=(const JNIObject&) = delete; +// 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"; - JNIObject(JNIObject&& other) noexcept; +// 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:"; - JNIObject& operator=(JNIObject&& other) noexcept; +// 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"; - jobject Get() const { return obj; } - jobject operator *() const { return obj; } - operator bool() const { return obj != nullptr; } +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; - 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; - } +// 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; - private: - jobject obj = nullptr; - }; +// Objective-C method type encoding constants +// Type encoding for methods returning BOOL with no parameters: "c@:" +static constexpr const char* kBoolMethodTypeEncoding = "c@:"; - 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); - } +// Type encoding for void methods with event/notification parameter: "v@:@" +static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; - 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); - } +// 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}}"; - ~JNIString() - { - if (jstr) - { - JNI::GetEnv()->DeleteGlobalRef(jstr); - } - jstr = nullptr; - } +// Type encoding for void methods with no parameters: "v@:" +static constexpr const char* kVoidMethodTypeEncoding = "v@:"; - 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; - } +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; - jstring operator*() const - { - return jstr; - } +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; - private: - jstring jstr = nullptr; - }; + // 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; -} -#endif + // Application Screen management selectors + static SEL screenSel = nullptr; + static SEL unhideSel = nullptr; + static SEL hideSel = nullptr; + static SEL isHiddenSel = nullptr; -#else -#include -#include + // NSApplicationDelegate lifecycle methods + static SEL applicationWillFinishLaunchingSel = nullptr; + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; -// 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(); + // 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; - // 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); + // 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; - public: - wxGLContext* get() const; - olc::gpu::Renderer* GetRenderer(); - olc::imload::ImageLoader* GetImageLoader(); + // 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; - private: // wxWidgets Specific - wxGLContext* m_glContext = nullptr; + // Mouse Tracking + static SEL addTrackingAreaSel = nullptr; + static SEL initWithRectSel = nullptr; - private: // Global PGE3 Components (excluding host) - std::unique_ptr pRenderer; - std::unique_ptr pImageLoader; - }; + // 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; - class PGE3Panel : public wxGLCanvas - { - public: - PGE3Panel(wxWindow* parent); - PGE3Panel(wxWindow* parent, const olc::vi2d& vFixedSize); - virtual ~PGE3Panel(); + // 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; - void ResetDrawState(); + // 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; - protected: // Local PGE3 Components - olc::Draw draw; + // 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; - private: - // Same core is loaded across all panels in application - static olc::wx::PGE3Core* m_pCore; - olc::Image imgPrimary; + // 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; - bool bFixedSize = false; - olc::vi2d vFixedSizeImage; + // 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 - olc::vf2d ScaleMouse(wxMouseEvent& evt); + // 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); + // 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); - 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); + // NSApplicationDelegate lifecycle methods + applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); + applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); + applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); + applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); + applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); + // 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); - protected: // User Overrides - virtual void OnRender() {}; // Called via wxWidgets ->Refresh() + // 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); - public: - virtual void OnCreate() {}; - virtual void OnUpdate(const float fElapsedTime) {}; + // 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); - // 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 + // 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); + // 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); -#if OLC_GPU == OLC_GPU_NONE + // 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); -#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; + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + } + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); + } +} - 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; +// Single initialization function called once at startup +void initialize_macos_api() { + ObjectiveCSEL::initializeSelectors(); +} - 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; +// Concepts safety templates for Objective-C interactions +template +concept CallbackType = std::is_function_v>; // Checks if T is a function type - public: // GPU Task Stuff - bool DoGPUTask(const olc::GPUTask& task) override; +template +concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) - 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; - }; +// Autorelease pool management functions +extern "C" { + extern void* objc_autoreleasePoolPush(void); + extern void objc_autoreleasePoolPop(void* pool); } +// 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_; +}; -#define PGE_RENDERER_NONE_DECLARED 1 -#endif -#endif +using NSPoint = CGPoint; +using NSInteger = long; +using NSUInteger = unsigned long; -#if OLC_GPU == OLC_GPU_OPENGL33 +// Forward declarations +struct Application; +struct Window; +struct OpenGLRenderer; +struct ApplicationDelegate; +struct CustomOpenGLView; +struct WindowDelegate; +struct ImageLoader; -#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 +// 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 -#if OLC_HOST == OLC_HOST_LINUX_X11 - #include - #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) -#endif + // 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 -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - #include - #include + // 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 - #define OGL_LOAD(t) reinterpret_cast(eglGetProcAddress(#t)) -#endif + // Policy configuration + MinimumPolicy = 51, // Minimum attribute policy + MaximumPolicy = 52, // Maximum attribute policy + ClosestPolicy = 74, // Closest match policy -#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 + // Advanced configuration + OpenGLProfile = 99, // OpenGL profile selection + ScreenMask = 84, // Screen mask for multi-display + VirtualScreenCount = 128, // Number of virtual screens +}; -#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 +// 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 - #define OGL_LOAD(t) ::t -#endif +// 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 +}; -#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 +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; -#if OLC_HOST == OLC_HOST_WXWIDGETS +// Backward compatibility +//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); +static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); +// 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 +}; -#endif +// 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); -#if !defined(CALLSTYLE) - #define CALLSTYLE -#endif +// 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) +}; +static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); +// 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) +}; -namespace olc -{ - namespace apis::opengl - { +// Backward compatibility +static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); - 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 +// 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 +}; -#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 +// Backward compatibility +static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); +static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); -#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 +// 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 +}; -#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 +// Backward compatibility +static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); - 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); +// 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_HOST == OLC_HOST_WINDOWS - typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); -#endif +// Handles NSApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; -#if OLC_HOST == OLC_HOST_LINUX_X11 - typedef GLint CALLSTYLE glXSwapIntervalEXT_t(X11::Display* dpy, X11::GLXDrawable drawable, GLint interval); -#endif +// Custom NSOpenGLView subclass for handling input events +struct CustomOpenGLView { + Class isa; // Objective-C class pointer (required) + void* renderer; // C Pointer to associated renderer +}; - // A little GL class (singleton) - class gl - { - public: - static gl& Get(); - inline bool HasLoaded() const { return bLoaded; } +// Handles NSWindow delegate methods and window events +struct WindowDelegate { + Class isa; // Objective-C class pointer (required) + void* renderer; // Pointer to associated renderer +}; - private: - gl() = default; - static bool bLoaded; +// Application management with member initialization +struct Application { + id nsApp{nullptr}; // NSApplication instance + id delegate{nullptr}; // Application delegate instance - private: - bool LoadAll(); + // Callback function pointers + std::function willFinishLaunchingCallback{nullptr}; + std::function didFinishLaunchingCallback{nullptr}; + std::function willTerminateCallback{nullptr}; + std::function didBecomeActiveCallback{nullptr}; + std::function willResignActiveCallback{nullptr}; - 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 + 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_LINUX_X11 - public: - glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; -#endif + // 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; +}; - public: - // Proxies allow switchable, clutter-free error checking +// 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 - // OpenGL3.3 Proxies + // 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}; - 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); + // 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}; - // 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); + // 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}; + + 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}; + + 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 - void glSwapInterval(GLsizei n); + // 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}; - // 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()); +}; - }; - } - -} // olc namespace +// 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}; +}; -#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; - }; +// 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); + } +} - 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; +// 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); + } +} +// 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); + } +} - 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; +// 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); + } +} - 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; +// 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); + } +} - public: // GPU Task Stuff - virtual bool DoGPUTask(const olc::GPUTask& task) override; +// Create and configure application delegate +id createApplicationDelegate() { - 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 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: // 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 + // 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; +} - 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; +//====================================================================// +// Keyboard Event Handling - uint32_t nCurrentTextureTarget = 0; - uint32_t nCurrentTextureSource = 0; +// Structure to hold common key event data +struct KeyEventData { + unsigned short keyCode = 0; + const char* characters = nullptr; + unsigned int modifierFlags = 0; +}; - //std::unordered_map mapMSAAToResolved; - uint32_t nResolveFBO_Read = 0; - uint32_t nResolveFBO_Draw = 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; +} - std::unordered_map mapTextureSizes; + // Handle key down events +void view_keyDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; // Remove unused parameter warnings - std::unordered_map mapTextureToRenderbuffer; + KeyEventData data = extractKeyEventData(event); - const Shader* pCurrentShader = nullptr; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - 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 +// Handle key up events +void view_keyUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -#if OLC_HOST == OLC_HOST_ANDROID - EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); -#endif + KeyEventData data = extractKeyEventData(event); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + } +} + +void view_flagsChanged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - }; - } + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { + gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + } } -#define PGE_RENDERER_OPENGL33_DECLARED 1 -#endif -#endif -#if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI -#if defined(UNICODE) || defined(_UNICODE) - #define olcT(s) L##s -#else - #define olcT(s) s -#endif +//====================================================================// +// Mouse Event Handling -#define _WINSOCKAPI_ +// 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) { -#if !defined(VC_EXTRALEAN) - #define VC_EXTRALEAN -#endif + if(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] + { + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { -#if !defined(NOMINMAX) - #define NOMINMAX -#endif + + // 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); -// 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 + // 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); -// 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_ + // 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; + } +} +// Mouse event data structure to hold common mouse event information +struct MouseEventData { + NSPoint location = {0.0, 0.0}; + NSUInteger modifierFlags = 0; + NSInteger buttonNumber = 0; +}; -#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; +// 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; +} - // 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; +// Stylus event data structure to hold common stylus event information +struct StylusEventData { - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + 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) - // 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); - }; - } -} +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { -#define PGE_IMAGELOADER_WINGDI_DECLARED 1 -#endif -#endif + 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_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; + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event - // 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; + 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); - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); - // Store an image as a file asset in memory - bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + // 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)); - }; - } -} + // 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); -#define PGE_IMAGELOADER_MACOS_DECLARED 1 -#endif -#endif + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} -#if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG -#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) -#include +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) -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; +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; - // Create an image resource based on an image file asset in memory - bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; - // Store an image as a file asset on disk - bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; - // Store an image as a file asset in memory - bool WriteImageToMemoryFile(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){ - 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); + 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); - private: // libpng internals - bool DecodePNG(olc::Image& image, png_structp png, png_infop info); - }; -} + // 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; +}; -#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 -#endif -#endif +// 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) + +}; -#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER -#include +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 !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) -namespace olc::imload +//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) { - - // Create an image resource based on an image file asset - class ImageLoader_NDKImageDecoder : public ImageLoader + 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) { - public: - // Create an image resource based on an image file asset on disk - bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + 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) { + + 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; } -#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; - - // 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; +// 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; }; -} - -#define PGE_IMAGELOADER_STB_DECLARED 1 -#endif -#endif - + + 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) { -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) + // KRespondsToSelector + BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); -#if OLC_HOST == OLC_HOST_MACOS + 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; + } + + } + } +} -// 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 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); -// 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"; + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { + 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); -// 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:"; +// Handle left mouse up events +void view_mouseUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -// 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:"; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); -// 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:"; + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { -// 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:"; + 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) + { + // 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); -// 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:"; + } -// 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"; +} -// 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"; +// Handle mouse drag events +void view_mouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -// 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"; + 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]] { -// 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->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); + } -// 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* kConvertRectFromBackingSel = "convertRectFromBacking:"; // Thank you - Mr B the Ultimate Teacher!!! -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"; -// NSLocale class and method names -static constexpr const char* kNSLocaleClass = "NSLocale"; -static constexpr const char* kCurrentLocaleSel = "currentLocale"; -static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; +// Handle mouse movement events +void view_mouseMoved(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 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); + } + + +} -// 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; +// 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); + } +} -// Objective-C method type encoding constants -// Type encoding for methods returning BOOL with no parameters: "c@:" -static constexpr const char* kBoolMethodTypeEncoding = "c@:"; +// 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); + } +} -// Type encoding for void methods with event/notification parameter: "v@:@" -static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; +// Handle right mouse drag events +void view_rightMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -// 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}}"; + 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); + } + +} -// Type encoding for void methods with no parameters: "v@:" -static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// 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); + } +} -// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" -static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; +// 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); + } +} -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; +void view_otherMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - // 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; + 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); + } +} - // Application Screen management selectors - static SEL screenSel = nullptr; - static SEL unhideSel = nullptr; - static SEL hideSel = nullptr; - static SEL isHiddenSel = nullptr; +// 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); - // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel = nullptr; - static SEL applicationDidFinishLaunchingSel = nullptr; - static SEL applicationWillTerminateSel = nullptr; - static SEL applicationDidBecomeActiveSel = nullptr; - static SEL applicationWillResignActiveSel = nullptr; + convertToContentViewCoordinates(location); - // 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; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { + gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - // 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; +// 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); - // 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; + convertToContentViewCoordinates(location); - // Mouse Tracking - static SEL addTrackingAreaSel = nullptr; - static SEL initWithRectSel = nullptr; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { + gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - // 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; +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); - // 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; + convertToContentViewCoordinates(location); - // 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; + 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 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 convertRectFromBackingSel = nullptr; // Thank you - Mr B the Ultimate Teacher!!! - 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; - // 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; +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 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 +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); + } +} - // 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); +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); + } +} - // 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); +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); + } +} - // NSApplicationDelegate lifecycle methods - applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); - applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); - applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); - applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); - applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) - // 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); +// 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); + } +} - // 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); +// 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 + } +} - // 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); - // 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); +void view_updateTrackingAreas(id self, SEL _cmd) { + (void)self;(void)_cmd; - // 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 - convertRectFromBackingSel = sel_registerName(kConvertRectFromBackingSel); // Thank you - Mr B the Ultimate Teacher!!! - 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 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); +} - // 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); +// 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 +} - // NSLocale selectors - currentLocaleSel = sel_registerName(kCurrentLocaleSel); - localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); - } +// 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 +} - // Ensures we only initialize selectors once (Thread-safe) - void ensureInitialized() { - static std::once_flag initialized; - std::call_once(initialized, [&]() { - initializeSelectors(); - }); - } +// Determine if the view can become key view +id view_canBecomeKeyView(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; } -// Single initialization function called once at startup -void initialize_macos_api() { - ObjectiveCSEL::initializeSelectors(); +// 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; } -// Concepts safety templates for Objective-C interactions -template -concept CallbackType = std::is_function_v>; // Checks if T is a function type +// Empty drawRect method - prevents automatic drawing on main thread +void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { + (void)self;(void)_cmd;(void)dirtyRect; +} -template -concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) +// Empty reshape method - prevents automatic context reshaping on main thread +void view_reshape(id self, SEL _cmd) { + (void)self;(void)_cmd; +} -// Autorelease pool management functions -extern "C" { - extern void* objc_autoreleasePoolPush(void); - extern void objc_autoreleasePoolPop(void* pool); +// Empty update method - prevents automatic context updates on main thread +void view_update(id self, SEL _cmd) { + (void)self;(void)_cmd; } -// Objective-C autorelease pools -class AutoreleasePool { -public: - AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} - ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } +// 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); - AutoreleasePool(const AutoreleasePool&) = delete; - AutoreleasePool& operator=(const AutoreleasePool&) = delete; - AutoreleasePool(AutoreleasePool&&) = delete; - AutoreleasePool& operator=(AutoreleasePool&&) = delete; - -private: - void* pool_; -}; - - -using NSPoint = CGPoint; -using NSInteger = long; -using NSUInteger = unsigned long; + // 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); -// Forward declarations -struct Application; -struct Window; -struct OpenGLRenderer; -struct ApplicationDelegate; -struct CustomOpenGLView; -struct WindowDelegate; -struct ImageLoader; + // DrawRect method + class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); -// 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 + // 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); - // 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 + // Register the class with the runtime + objc_registerClassPair(CustomViewClass); - // 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 + return CustomViewClass; +} - // 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 +// Forward declaration for C API function used by window event handlers +extern "C" void window_updateFrameFromOSX(Window* self); - // Policy configuration - MinimumPolicy = 51, // Minimum attribute policy - MaximumPolicy = 52, // Maximum attribute policy - ClosestPolicy = 74, // Closest match policy +// handle window resize events +void windowDidResize(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; - // Advanced configuration - OpenGLProfile = 99, // OpenGL profile selection - ScreenMask = 84, // Screen mask for multi-display - VirtualScreenCount = 128, // Number of virtual screens -}; + if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { -// 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 + // Update frame data only + window_updateFrameFromOSX(gptrWindowDelegate); -// 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 -}; + // Get the new content view size + double width, height; + window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); -// enum for OpenGL context parameters -enum NSOpenGLContextParameter : int { - NSOpenGLContextParameterSwapInterval = 222, - NSOpenGLContextParameterSurfaceOrder = 235, - NSOpenGLContextParameterSurfaceOpacity = 236, - NSOpenGLContextParameterSurfaceBackingSize = 237 -}; + if (gptrWindowDelegate->windowDidResizeCallback) { + gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); + } + + } +} -// Backward compatibility -//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); -static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); +// 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); + } + }); +} -// 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 -}; +// 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); + } +} -// 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); +// 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); + } +} -// 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) -}; +// 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); + } +} -static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); +// 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); + } +} -// 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) -}; +// 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); -// Backward compatibility -static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); + // 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; +} -// 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 -}; +// 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); -// Backward compatibility -static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); -static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); + if (!WindowDelegateClass) { + return NULL; + } -// 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 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); -// Backward compatibility -static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); + // 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; +} -// Mouse Tracking const -static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; -static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; -static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; + // Forward declarations for C API functions used by internal helpers + extern "C" void window_setDelegate(Window* self, id delegate); -/* -* 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; +extern "C" { + // Initialize the application + void application_initialize(Application* self) { -// Handles NSApplication delegate methods and lifecycle events -struct ApplicationDelegate { - Class isa; // Objective-C class pointer (required) -}; + // 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); + } -// Custom NSOpenGLView subclass for handling input events -struct CustomOpenGLView { - Class isa; // Objective-C class pointer (required) - void* renderer; // C Pointer to associated renderer -}; + // Activate the application + void application_activate(Application* self) { + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); + } -// Handles NSWindow delegate methods and window events -struct WindowDelegate { - Class isa; // Objective-C class pointer (required) - void* renderer; // Pointer to associated renderer -}; + // Run the application event loop + void application_run(Application* self) { + ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); + } -// Application management with member initialization -struct Application { - id nsApp{nullptr}; // NSApplication instance - id delegate{nullptr}; // Application delegate instance + // 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); + } + } - // Callback function pointers - std::function willFinishLaunchingCallback{nullptr}; - std::function didFinishLaunchingCallback{nullptr}; - std::function willTerminateCallback{nullptr}; - std::function didBecomeActiveCallback{nullptr}; - std::function willResignActiveCallback{nullptr}; + // Destroy the application + void application_destroy(Application* self) { + if (self) { + delete self; + } + } - 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 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; + } - // 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; -}; -// 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 + // Ensure window has a delegate for window events + void ensureWindowDelegate(struct Window* 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}; + // 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); + } + } + } - // 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}; + // Application constructor + Application* application_init() { - // 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}; + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - 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}; + Application* app = new Application(); - 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}; -}; + // Assign the method pointers + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; -// 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 + return app; + } - // 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}; + // Create the NSWindow instance + void window_create(Window* self, unsigned long styleMask) { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized -}; + // Get classes using const strings + Class NSWindowClass = objc_getClass(kNSWindowClass); + Class NSStringClass = objc_getClass(kNSStringClass); -// 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}; -}; + // 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); -// 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); + // 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); } -} -// 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); + // Show the window and set up event handling + void window_show(Window* self) { + + // 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); } -} -// 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); + // Destroy the window + void window_destroy(Window* self) { + (void)self; + // NSWindow will be cleaned up by autorelease pool } -} -// 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); + // 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); + } } -} -// 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); + // 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; + } -} -// Create and configure application delegate -id createApplicationDelegate() { + // Window title getter and setter + const char* window_getTitle(const Window* self) { + return self->title; + } - // 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); + // Window title setter + void window_setTitle(Window* self, const char* title) { + self->title = title; - // 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 (self->nsWindow) { + Class NSStringClass = objc_getClass(kNSStringClass); -//====================================================================// -// Keyboard Event Handling + id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( + NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); -// Structure to hold common key event data -struct KeyEventData { - unsigned short keyCode = 0; - const char* characters = nullptr; - unsigned int modifierFlags = 0; -}; + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); + } + } -// 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; -} + // Refresh internal frame representation from actual NSWindow (OSX) + void window_updateFrameFromOSX(Window* self) { + self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); + } - // Handle key down events -void view_keyDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings + // 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; + + } - KeyEventData data = extractKeyEventData(event); + // 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; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { - gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + // 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); + } + } } -} -// Handle key up events -void view_keyUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // 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)); - KeyEventData data = extractKeyEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { - gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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; } -} -void view_flagsChanged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // Get window position (x, y) + void window_getWindowPosition(const struct Window* self, double* x, double* y) { + window_getWindowFrame(self, x, y, nullptr, nullptr); + } - unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + // Get Window size (width, height) + void window_getWindowSize(const Window* self, double* width, double* height) { + window_getWindowFrame(self, nullptr, nullptr, width, height); + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { - gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + // 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; + + // 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); + } + } } -} -//====================================================================// -// Mouse Event Handling + // 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); + } -// 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) { + // 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); + } - if(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] - { + // 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) { - - - // Convert from window coordinates to view coordinates (still logical points) - NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + + // 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); - // Scale from logical points to physical backing pixels - NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); - - // Get content bounds in physical pixels for Y-flip + // Get content bounds in points for clamping and Y-flip NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); - - // Flip Y coordinate from bottom-left to top-left - location.x = pixelLocation.x; - location.y = pixelBounds.height - pixelLocation.y; + + // 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)); } + } - 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; -}; - -// 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; -} - -// Stylus event data structure to hold common stylus event information -struct StylusEventData { - 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) + // 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); + } -// Extract common mouse event data from NSEvent -StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + bool window_isFullScreen(Window* self) { + unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); + return (mask & NSWindowStyleMaskFullScreen); + } - 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); + // 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); + } - data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); - data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + 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); + + + } - 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); + // 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); + } - data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); + // 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); - double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); - double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + return true; + } - // 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)); - // 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 the underlying OpenGL context + void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { + return (void*)self->glContext; + } - // Convert to content view coordinates and flip Y coordinate - convertToContentViewCoordinates(data.nspLocation); + // 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 data; -} + // Get the underlying CGLContextObj (C API version) + void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { + return (void*)opengl_getCGLContextObj(self); + } -//====================================================================// -// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + // Make the OpenGL context current + void opengl_makeCurrentContext(struct OpenGLRenderer* self) { + if (self && self->glContext) { + ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + } + } + // 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); + } -// NSTouchPhase bitmask values -enum NSTouchPhase : NSUInteger { - NSTouchPhaseBegan = 1 << 0, - NSTouchPhaseMoved = 1 << 1, - NSTouchPhaseStationary = 1 << 2, - NSTouchPhaseEnded = 1 << 3, - NSTouchPhaseCancelled = 1 << 4, - NSTouchPhaseAny = ULONG_MAX -}; + // 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); + } + } -enum NSPressureStage : NSInteger { - NSPressureStageLight = 0, // Light press - NSPressureStageNormal = 1, // Normal press - NSPressureStageForce = 2 // Force click -}; + // OpenGL renderer constructor + OpenGLRenderer* opengl_init() { -enum class ForceTouchPressure : int { - Light = 25, // 0.25 - Light touch - Normal = 50, // 0.50 - Normal press - Force = 100, // 1.00 - Force click -}; + OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); + renderer->pixelFormat = NULL; + renderer->glView = NULL; + renderer->glContext = NULL; + renderer->window = NULL; -// 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); + // 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; - // 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); - } + return renderer; } - cgTouchSize = CGSizeMake(touchSizeInPoints * pixelScale, touchSizeInPoints * pixelScale); - return cgTouchSize; -}; -// 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) - -}; + static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + { + if(!image) return NO; -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 + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } -//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); - } - - - 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); + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); + + //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 - 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; + 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; - // 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); + 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. - // Clean up map entry once the touch has ended or been cancelled - if (bRemove) + /* + if(byteOrder != kCGBitmapByteOrder32Little) + { + int pixelCount = self->width * self->height; + for(int i = 0; i < pixelCount; ++i) { - sTouchIDEvents.erase(key); - sTouchIDMap.erase(key); + auto p = self->pixelData[i]; + + // TODO: detect byte order, adjust as appropriate + + self->pixelData[i] = p; } - } - + */ + + CFRelease(rawData); + CGImageRelease(image); + + return YES; } - // 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); + BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } + CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); + CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); + CFRelease(pathStr); + + if(!url) + { + printf("loadImageFromFile: bad path '%s'\n", filePath); + return NO; + } + + 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); + + return imageloader_decodeImage(self, image); } - - // Find the missing keys and fire the touchEndedCallback for them - for(auto& [key, data] : sTouchIDEvents) { + + BOOL imageloader_loadFromMemory(struct ImageLoader* self, const uint8_t* data, size_t bytes) { - 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); + CFDataRef cfData = CFDataCreateWithBytesNoCopy( + nullptr, + reinterpret_cast(data), + (CFIndex)bytes, + kCFAllocatorNull // we own the buffer, CF must not free it + ); + + if (!cfData) + { + printf("loadImageFromMemory: CFData creation failed\n"); + return NO; } - + + CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); + CFRelease(cfData); + + if (!src) + { + printf("loadImageFromMemory: src creation failed\n"); + return NO; + } + + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); + + return imageloader_decodeImage(self, image); } - - // Remove the missing keys from the maps - for(auto& fKeys: vMissingKeys) { - sTouchIDEvents.erase(fKeys); - sTouchIDMap.erase(fKeys); + + // Get raw pixel data pointer + unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return (unsigned char*)self->pixelData; } - - 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) { + // 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; + } - // KRespondsToSelector - BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + // 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; + } - 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; - } - - } + // Check if image is loaded + BOOL imageloader_isLoaded(const struct ImageLoader* self) { + return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); } -} -// 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); + // 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; - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B - if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { + // 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; + } - 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); + // 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; } - 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); + + // 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; + } + + // 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; - } + // 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; -// Handle left mouse up events -void view_mouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // Event handling disabled by default + window->acceptsInputEvents = NO; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + // 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; - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B - if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { + // 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; - 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); - } + return window; } - 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, - false, true, true, - gptrNSWindowEvents->stylusUserData); + // 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; } -} - -// Handle mouse drag events -void view_mouseDragged(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); - - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B - if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] { - - 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_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyUpCallback = callback; + self->eventUserData = userData; + } + + void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { + self->flagsChangedCallback = 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); - } - -} - -// Handle mouse movement events -void view_mouseMoved(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - // 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); + void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDownCallback = callback; + self->eventUserData = userData; } - - -} -// Handle right mouse down events -void view_rightMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseUpCallback = callback; + self->eventUserData = userData; } -} -// Handle right mouse up events -void view_rightMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { - gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setMouseMovedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseMovedCallback = callback; + self->eventUserData = userData; } -} -// Handle right mouse drag events -void view_rightMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDraggedCallback = callback; + self->eventUserData = userData; } - -} -// Handle other mouse button down events -void view_otherMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDownCallback = callback; + self->eventUserData = userData; } -} -// Handle other mouse button up events -void view_otherMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) { - gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDraggedCallback = callback; + self->eventUserData = userData; } -} -void view_otherMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseUpCallback = callback; + self->eventUserData = userData; } -} -// 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); + void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDownCallback = callback; + self->eventUserData = userData; + } - convertToContentViewCoordinates(location); + void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseUpCallback = callback; + self->eventUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { - gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDraggedCallback = callback; + self->eventUserData = userData; } -} -// 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); + void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { + self->scrollWheelCallback = callback; + self->eventUserData = userData; + } - convertToContentViewCoordinates(location); + void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseEnteredCallback = callback; + self->eventUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { - gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseExitedCallback = callback; + self->eventUserData = userData; + } + + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; } -} -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); + void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchMovedCallback = callback; + self->touchUserData = userData; + } - convertToContentViewCoordinates(location); + void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchEndedCallback = callback; + self->touchUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { - gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)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; } -} + 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; + } -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); + void window_enableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = YES; + gptrNSWindowEvents = self; // Set global reference for event handlers + } } -} -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 window_disableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = NO; + if (gptrNSWindowEvents == self) { + gptrNSWindowEvents = NULL; + } + } } -} -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); + // Window delegate callback setter functions + void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResizeCallback = callback; + self->windowDidResizeUserData = userData; + } -} -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); + void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowWillCloseCallback = callback; + self->windowWillCloseUserData = userData; } -} -//====================================================================// -// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidBecomeKeyCallback = callback; + self->windowDidBecomeKeyUserData = userData; + } -// 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); + void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResignKeyCallback = callback; + self->windowDidResignKeyUserData = userData; } -} -// 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 + void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidMiniaturizeCallback = callback; + self->windowDidMiniaturizeUserData = userData; } -} + void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidDeminiaturizeCallback = callback; + self->windowDidDeminiaturizeUserData = userData; + } -void view_updateTrackingAreas(id self, SEL _cmd) { - (void)self;(void)_cmd; + // Application delegate callback setter functions + void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->willFinishLaunchingCallback = callback; + self->willFinishLaunchingUserData = userData; + } - // 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); -} + void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->didFinishLaunchingCallback = callback; + self->didFinishLaunchingUserData = userData; + } -// 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 -} + void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { + self->willTerminateCallback = callback; + self->willTerminateUserData = userData; + } -// 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 -} + void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->didBecomeActiveCallback = callback; + self->didBecomeActiveUserData = userData; + } -// Determine if the view can become key view -id view_canBecomeKeyView(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; -} + void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->willResignActiveCallback = callback; + self->willResignActiveUserData = userData; + } -// 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; -} +} // extern "C" -// Empty drawRect method - prevents automatic drawing on main thread -void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { - (void)self;(void)_cmd;(void)dirtyRect; -} +#endif -// Empty reshape method - prevents automatic context reshaping on main thread -void view_reshape(id self, SEL _cmd) { - (void)self;(void)_cmd; -} +#if OLC_HOST == OLC_HOST_IOS -// Empty update method - prevents automatic context updates on main thread -void view_update(id self, SEL _cmd) { - (void)self;(void)_cmd; -} +// 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:"; -// 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); +// 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:"; - // DrawRect method - class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); +// 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:"; - // 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); +// 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:"; - // Register the class with the runtime - objc_registerClassPair(CustomViewClass); - - return CustomViewClass; -} +// 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"; -// Forward declaration for C API function used by window event handlers -extern "C" void window_updateFrameFromOSX(Window* self); +// GLKView drawing methods selectors +static constexpr const char* kDrawRectSel = "drawRect:"; -// handle window resize events -void windowDidResize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; +// 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"; - if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { +// 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:"; - // Update frame data only - window_updateFrameFromOSX(gptrWindowDelegate); - // Get the new content view size - double width, height; - window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); +// 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"; - if (gptrWindowDelegate->windowDidResizeCallback) { - gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); - } - - } -} +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; -// 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); - } - }); -} +// 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; -// 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); +// 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); + } -} -// 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); + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); } + } -// 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); - } +// Single initialization function called once at startup +void initialize_ios_api() { + ObjectiveCSEL::initializeSelectors(); } -// 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); - } +// 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); } -// 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); +// Objective-C autorelease pools +class AutoreleasePool { +public: + AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} + ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } - // 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); + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + AutoreleasePool(AutoreleasePool&&) = delete; + AutoreleasePool& operator=(AutoreleasePool&&) = delete; - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); +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 - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + // 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}; - // Store renderer reference in delegate - struct WindowDelegate* windowDelegate = (struct WindowDelegate*)delegate; - windowDelegate->renderer = renderer; + 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 - return delegate; -} + // 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; + +}; -// Create a window delegate for general window events -id createWindowDelegateForWindow(void) { +// Handles UIApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; - // 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); +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 + +}; - if (!WindowDelegateClass) { - return NULL; - } +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}; - // 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); + + 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 - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); - } + // Event callback function pointers with nullptr initialization + std::function keyDownCallback{nullptr}; + std::function keyUpCallback{nullptr}; - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + void* keyDownUserData{nullptr}; // User data for key down callback + void* keyUpUserData {nullptr}; // User data for key up callback - return delegate; -} + // Draw callback function pointer + std::function drawCallback{nullptr}; + void* drawUserData{nullptr}; // User data for draw callback +}; - // Forward declarations for C API functions used by internal helpers - extern "C" void window_setDelegate(Window* self, id delegate); +struct ViewControllerDelegate { + Class isa; // Objective-C class pointer (required) +}; -extern "C" { - // Initialize the application - void application_initialize(Application* self) { +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 - // 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); - } +// 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 - // Run the application event loop - void application_run(Application* self) { - ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); - } + // 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}; +}; - // 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); - } +// 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; +} - // Destroy the application - void application_destroy(Application* self) { - if (self) { - delete self; - } +// 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; +} - // 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; - } +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); +} - // Ensure window has a delegate for window events - void ensureWindowDelegate(struct Window* self) { +static void safe_objc_msgSend_void(id self, SEL op) { + if (!self || !op) return; + ((void(*)(id, SEL))objc_msgSend)(self, op); +} - // 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); - } - } - } +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); +} - // Application constructor - Application* application_init() { +// TODO: End - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - Application* app = new Application(); +// ============================================================================ +// IMPLEMENTATION OF C API FUNCTIONS +// ============================================================================ - // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; - app->getSystemLocale = application_getSystemLocale; - return app; +// 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); } +} - // Create the NSWindow instance - void window_create(Window* self, unsigned long styleMask) { - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - - // Get classes using const strings - Class NSWindowClass = objc_getClass(kNSWindowClass); - Class NSStringClass = objc_getClass(kNSStringClass); - // 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); - // 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); - } - } +// 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); + } +} - window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, - &self->contentViewFrame.width, &self->contentViewFrame.height); +// 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); } +} - // Show the window and set up event handling - void window_show(Window* self) { - // 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); +// 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); } +} - // Destroy the window - void window_destroy(Window* self) { - - // Thanks Mr B! - if (self) { - self->acceptsInputEvents = NO; - if (gptrNSWindowEvents == self) { - gptrNSWindowEvents = NULL; - } - if (gptrWindowDelegate == self) { - gptrWindowDelegate = NULL; - } - } - // NSWindow will be cleaned up by autorelease pool +// 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); } +} - // 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); - } + + +// 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 +} - // 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; - + + +// 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; +} - // Window title getter and setter - const char* window_getTitle(const Window* self) { - return self->title; - } - // Window title setter - void window_setTitle(Window* self, const char* title) { - self->title = title; - if (self->nsWindow) { - Class NSStringClass = objc_getClass(kNSStringClass); +// Application API Implementation +struct Application* application_init(void) { + + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); + Application* app = new Application(); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); - } - } + // 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; +} - // Refresh internal frame representation from actual NSWindow (OSX) - void window_updateFrameFromOSX(Window* self) { - self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); - } - // 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; - } + + +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); - // 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; + if (bundlePath) { + // Convert NSString to C string + pathCString = ((const char*(*)(id, SEL))objc_msgSend)(bundlePath, ObjectiveCSEL::UTF8StringSel); } - - // 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; - } + return const_cast(pathCString); +} - // 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; - } +// 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); - // 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 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 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)); + // Get PGE AppDelegate class so that iOS Event fires on our host_apple_ios + Class appDelegateClass = objc_getClass(kolcPGE3AppDelegateClass); + id delegateClassName = (id)NSStringFromClass(appDelegateClass); - 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; - } + // Call UIApplicationMain to start running with our delegate + int result = UIApplicationMain(argc, argv, nil, delegateClassName); - // Get window position (x, y) - void window_getWindowPosition(const struct Window* self, double* x, double* y) { - window_getWindowFrame(self, x, y, nullptr, nullptr); - } + // Clean up + ((void(*)(id, SEL))objc_msgSend)(pool, ObjectiveCSEL::drainSel); - // Get Window size (width, height) - void window_getWindowSize(const Window* self, double* width, double* height) { - window_getWindowFrame(self, nullptr, nullptr, width, height); - } + return result; +} - // 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 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 + + } - // 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); - } - } - } - // 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); - } - // 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); - } +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); - // 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)); - - } + // 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); } + - // 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 application_run(struct Application* self) { + if (!self || !self->uiApp) return; - void window_toggleFullScreen(Window* self) { - // Toggle fullscreen - ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); - } + // 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 +} - bool window_isFullScreen(Window* self) { - unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); - return (mask & NSWindowStyleMaskFullScreen); +void application_destroy(struct Application* self) { + if (!self) return; + + if (self->delegate) { + safe_objc_msgSend_void(self->delegate, ObjectiveCSEL::releaseSel); } + + free(self); +} - // 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); - } +const char* application_getApplicationPath(Application* self) { + (void)self; + return get_application_path(); +} - 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 - } - } +// 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; - // 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); + iOSTouchEventData data; - // 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); + // Get the view from the touch + id touchView = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::viewSel); - // Enable touch events (NSTouch) - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + // Get touch location in view coordinates (logical points) + CGPoint location = ((CGPoint(*)(id, SEL, id))objc_msgSend)(touch, ObjectiveCSEL::locationInViewSel, touchView); - // 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); + // 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); + } - } - - // 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); + + data.location = location; + data.touchSize = {majorRadius, majorRadius}; + data.bIsActive = !bRemove; - // 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); + // Assign a stable sequential uint32_t ID using the UITouch object pointer as key + uintptr_t key = reinterpret_cast(touch); - // Make sure the window is key first - ((void(*)(id, SEL))objc_msgSend)(self->window->nsWindow, ObjectiveCSEL::makeKeyWindowSel); + auto [it, inserted] = siOSTouchIDMap.emplace(key, siOSNextTouchID); + if (inserted) ++siOSNextTouchID; + uint32_t tid = it->second; - // 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); + data.touchID = tid; + siOSTouchIDEvents.emplace(key, data); - // Additional debug: check if view can become key view - ((BOOL(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::canBecomeKeyViewSel); + // 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; } +} - // Implementation function - bool opengl_resetContextForSize(OpenGLRenderer* self, double width, double height) { - if (!self || !self->glContext || !self->glView) { - return false; - } +// 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); - // Make context current - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); + KeyEventData data = extractKeyEventData(event, press); - // Update the view frame Without folks like Mr B the Ultimate Teacher, this would have been tricky - NSRect pixelFrame = {0.0, 0.0, width, height}; - NSRect newFrame = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::convertRectFromBackingSel, pixelFrame); - ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); + 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); - // Force context to reshape - ((void(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::reshapeSel); - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::updateSel); + KeyEventData data = extractKeyEventData(event, press); - // Update viewport - glViewport(0, 0, (GLsizei)width, (GLsizei)height); + if (gptrGLKViewEvents->keyUpCallback) { + gptrGLKViewEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrGLKViewEvents->keyUpUserData); + + } - // Clear buffers - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - return true; } +} - // Get the underlying OpenGL context - void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { - return (void*)self->glContext; +// 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"); } +} - // 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); +void device_getScreenBounds(CGRect* rect) { + + Class screenClass = safe_objc_getClass(kUIScreenClass); + + id mainScreen = safe_objc_msgSend((id)screenClass, ObjectiveCSEL::mainScreenSel); + if (!mainScreen) { + return; } - // Get the underlying CGLContextObj (C API version) - void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { - return (void*)opengl_getCGLContextObj(self); + // 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; } +} - // Make the OpenGL context current - void opengl_makeCurrentContext(struct OpenGLRenderer* self) { - if (self && self->glContext) { - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); +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"); } +} - // 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 viewWillAppear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewWillAppearCallback) { + gptrViewControllerDelegate->viewWillAppearCallback(gptrViewControllerDelegate->viewWillAppearUserData); } +} - // 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 viewDidAppear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidAppearCallback) { + gptrViewControllerDelegate->viewDidAppearCallback(gptrViewControllerDelegate->viewDidAppearUserData); } +} - // OpenGL renderer constructor - OpenGLRenderer* opengl_init() { +void viewWillDisappear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewWillDisappearCallback) { + gptrViewControllerDelegate->viewWillDisappearCallback(gptrViewControllerDelegate->viewWillDisappearUserData); + } +} - OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); - renderer->pixelFormat = NULL; - renderer->glView = NULL; - renderer->glContext = NULL; - renderer->window = NULL; +void viewDidDisappear(id self, SEL _cmd, BOOL animated) { + (void)self; (void)_cmd; (void)animated; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidDisappearCallback) { + gptrViewControllerDelegate->viewDidDisappearCallback(gptrViewControllerDelegate->viewDidDisappearUserData); + } +} - // 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 viewDidLayoutSubviews(id self, SEL _cmd) { + (void)self; (void)_cmd; - return renderer; + device_getScreenBounds(&gptrVeiwControllerEvents->frame); + if (gptrViewControllerDelegate && gptrViewControllerDelegate->viewDidLayoutSubviewsCallback) { + gptrViewControllerDelegate->viewDidLayoutSubviewsCallback(gptrViewControllerDelegate->viewDidLayoutSubviewsUserData); } +} - static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) - { - if(!image) return NO; +void updateView(id self, SEL _cmd) { + (void)self; (void)_cmd; + + if (gptrViewControllerDelegate && gptrViewControllerDelegate->updateCallback) { + gptrViewControllerDelegate->updateCallback(gptrViewControllerDelegate->updateUserData); + } +} - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; +// 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; +} - CGDataProviderRef provider = CGImageGetDataProvider(image); - CFDataRef rawData = CGDataProviderCopyData(provider); +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); + } +} - //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 +// 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); - 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; - - const uint8_t* imageData = CFDataGetBytePtr(rawData); - self->pixelData = (imageloader_pixel_t*)malloc(self->width * self->height * self->bytesPerPixel); - if(!self->pixelData) - { - return NO; + if (!CustomGLKViewClass) { + printf("Failed to create custom GLKView class\n"); + return nil; } - 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. + // 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); - /* - 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; - } - } - */ + // 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; +} - CFRelease(rawData); - CGImageRelease(image); +// 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; - return YES; + // 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); +} - BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } +void* glkview_getNativeView(const struct GLKView* self) { + if (!self) return nullptr; + return self->glkView; +} - CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); - CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); - CFRelease(pathStr); +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); +} - if(!url) - { - printf("loadImageFromFile: bad path '%s'\n", filePath); - return NO; - } +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; +} - CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); - CFRelease(url); +void glkview_setMultipleTouchEnabled(struct GLKView* self, BOOL enabled) { + if (!self || !self->glkView) return; + safe_objc_msgSend_void_bool(self->glkView, ObjectiveCSEL::setMultipleTouchEnabledSel, enabled); +} - if(!src) - { - printf("loadImageFromFile: couldn't open '%s'\n", filePath); - return NO; - } +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; + } - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); + 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"); +} - return imageloader_decodeImage(self, image); +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; +} - 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 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); +} - if (!cfData) - { - printf("loadImageFromMemory: CFData creation failed\n"); - return NO; - } +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; +} - CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); - CFRelease(cfData); +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); +} - if (!src) - { - printf("loadImageFromMemory: src creation failed\n"); - return NO; - } +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); +} - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); - - return imageloader_decodeImage(self, image); - } - // Get raw pixel data pointer - unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return (unsigned char*)self->pixelData; +void viewcontroller_present(struct ViewController* self) { + if (!self || !self->viewController) { + printf("Error: Invalid view controller for presentation\n"); + return; } - // 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; + 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); - // 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; + } else { + printf("Warning: No main window found for presentation\n"); } + + +} - // Check if image is loaded - BOOL imageloader_isLoaded(const struct ImageLoader* self) { - return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); +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); +} - // 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; +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); - // 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; - } +} - // 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; +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); +} - // 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; + +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); } +} - // Window constructor - Window* window_init(double x, double y, double width, double height) { - Window* window = (Window*)malloc(sizeof(Window)); +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); - // Initialize window properties - window->nsWindow = NULL; - window->delegate = NULL; - window->title = NULL; + // Set depth format to 24-bit + ((void(*)(id, SEL, int))objc_msgSend)(self->glkView, ObjectiveCSEL::setDrawableDepthFormatSel, kEAGLDepthFormat24); - // Set initial window frame - window->windowFrame.x = x; - window->windowFrame.y = y; - window->windowFrame.width = width; - window->windowFrame.height = height; + // 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); + + } +} - // Set window content view frame - window->contentViewFrame.x = x; - window->contentViewFrame.y = y; - window->contentViewFrame.width = width; - window->contentViewFrame.height = height; +void* opengl_getEAGLContext(const struct OpenGLRenderer* self) { + if (!self) return NULL; + return self->eaglContext; +} - // 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* opengl_getEAGLContextPtr(struct OpenGLRenderer* self) { + if (!self) return NULL; + return &(self->eaglContext); +} - // Event handling disabled by default - window->acceptsInputEvents = NO; +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); +} - // 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 opengl_setVsync(struct OpenGLRenderer* self, BOOL enabled) { + if (!self) return; + self->vsyncEnabled = enabled; +} - // 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; - return window; +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); +} - // 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; +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; } - void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { - self->keyUpCallback = callback; - self->eventUserData = userData; + 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; } - void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { - self->flagsChangedCallback = callback; - self->eventUserData = userData; + // 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); - void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDownCallback = callback; - self->eventUserData = userData; + if (!cgImage) { + return NO; } - void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseUpCallback = callback; - self->eventUserData = userData; + 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; - 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; +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; } - - void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDownCallback = callback; - self->eventUserData = userData; + + 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; } - - void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDraggedCallback = callback; - self->eventUserData = userData; + + // 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; } - void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseUpCallback = callback; - self->eventUserData = userData; - } + CGImageRef cgImage = ((CGImageRef(*)(id, SEL))objc_msgSend)(image, ObjectiveCSEL::cgImageSel); - void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDownCallback = callback; - self->eventUserData = userData; + if (!cgImage) { + return NO; } - void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseUpCallback = callback; - self->eventUserData = userData; + 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; +} - void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDraggedCallback = callback; - self->eventUserData = userData; - } - void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { - self->scrollWheelCallback = callback; - self->eventUserData = userData; - } - 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; - } - void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchBeganCallback = callback; - self->touchUserData = userData; - } +// Get raw pixel data pointer +unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return self->pixelData; +} - void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchMovedCallback = callback; - self->touchUserData = userData; - } +// 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; +} - void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchEndedCallback = callback; - self->touchUserData = userData; - } +// 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; +} - void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchCancelledCallback = callback; - self->touchUserData = userData; - } +// Check if image is loaded +BOOL imageloader_isLoaded(const struct ImageLoader* self) { + return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); +} - 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; +// 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; +} - void window_enableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = YES; - gptrNSWindowEvents = self; // Set global reference for event handlers - } +// 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; +} - void window_disableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = NO; - if (gptrNSWindowEvents == self) { - gptrNSWindowEvents = NULL; - } - } +// 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; - // Window delegate callback setter functions - void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResizeCallback = callback; - self->windowDidResizeUserData = userData; + // 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; +} - void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowWillCloseCallback = callback; - self->windowWillCloseUserData = userData; - } +// 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 window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidBecomeKeyCallback = callback; - self->windowDidBecomeKeyUserData = userData; - } +void glkview_setTouchMovedCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchMovedCallback = callback; + self->touchMovedUserData = userData; +} - void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResignKeyCallback = callback; - self->windowDidResignKeyUserData = userData; - } +void glkview_setTouchEndedCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchEndedCallback = callback; + self->touchEndedUserData = userData; +} - void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidMiniaturizeCallback = callback; - self->windowDidMiniaturizeUserData = userData; - } +void glkview_setTouchCancelledCallback(struct GLKView* self, TouchEventCallback callback, void* userData) { + if (!self) return; + self->touchCancelledCallback = callback; + self->touchCancelledUserData = userData; +} - void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidDeminiaturizeCallback = callback; - self->windowDidDeminiaturizeUserData = 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; +} - // Application delegate callback setter functions - void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->willFinishLaunchingCallback = callback; - self->willFinishLaunchingUserData = userData; - } +void viewcontroller_setUpdateCallback(struct ViewController* self, UpdateCallback callback, void* userData) { + if (!self) return; + self->updateCallback = callback; + self->updateUserData = userData; +} - void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->didFinishLaunchingCallback = callback; - self->didFinishLaunchingUserData = userData; - } +void viewcontroller_setOrientationChangedCallback(struct ViewController* self, OrientationChangedCallback callback, void* userData) { + if (!self) return; + self->orientationChangedCallback = callback; + self->orientationChangedUserData = userData; +} - void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { - self->willTerminateCallback = callback; - self->willTerminateUserData = userData; - } +BOOL device_isMultitouchCapable(void) { + // Most iOS devices support multitouch + return YES; +} - void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->didBecomeActiveCallback = callback; - self->didBecomeActiveUserData = userData; - } +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 application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->willResignActiveCallback = callback; - self->willResignActiveUserData = userData; +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; +} -} // extern "C" #endif + #if !defined(OLC_USE_WXWIDGETS) #if OLC_HOST == OLC_HOST_NONE @@ -12095,246 +15708,839 @@ namespace olc::host { } 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; + // 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 ----- + + 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([]() { }); + + } + + void Host_Apple_MacOS::MacWindowEventsHandler() + { + pMacOSWindow->setWindowDidResizeCallback([&]() { + AddPendingMainThreadTask(RESIZE_WINDOW); + }); + + 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(); + }); + + pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { + AddPendingMainThreadTask(BECOME_ACTIVE); + }); + + pMacOSWindow->setWindowDidResignKeyCallback([&]() { + AddPendingMainThreadTask(RESIGN_ACTIVE); + }); + + pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { + AddPendingMainThreadTask(MINIMIZE_WINDOW); + }); + + pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { + AddPendingMainThreadTask(DEMINIMIZE_WINDOW); + }); + + } + + // 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) + { + unsigned short keyCode = event.keyCode; + + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) + { + bNumLockActive = !bNumLockActive; + return; + } + + 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; + } + } + + // 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); + } + + 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); + }); + + // 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; + + // 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); + } + + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + + // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); + } + + // caps lock doesn't appear to trigger any event + prevFlags = event.modifierFlags; + }); + + // Set up mouse event handlers + pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); + + pMacOSEventHandler->onMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); + + pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); + + pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); + + pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); + + 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); + }); + + pMacOSEventHandler->onRightMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + + }); + + 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); + }); + + 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)}); + }); + + 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)}); + }); + + 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)}); + }); + + 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)}); + }); + + // 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} + ); + }); + } -//------ Events Handlers ----- +} +#endif - void Host_Apple_MacOS::MacApplicationEventsHandler() +#if OLC_HOST == OLC_HOST_IOS +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() { - 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()); + // 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; - - }); - - pMacApplication->setWillTerminateCallback([&]() { - //todo : add any cleanup code here if needed - }); + // 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 - pMacApplication->setDidBecomeActiveCallback([]() { }); + + // 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 - pMacApplication->setWillResignActiveCallback([]() { }); + // 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; } - void Host_Apple_MacOS::MacWindowEventsHandler() + bool Host_Apple_iOS::CloseWindowFrame(olc::Window* pWindow) { - pMacOSWindow->setWindowDidResizeCallback([&]() { - AddPendingMainThreadTask(RESIZE_WINDOW); - }); + if (!pIOSViewController) return false; + if (!pWindow) return false; + pWindow->olc_OnWindowClose(); + 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::UpdateWindowFrameTitle(olc::Window* pWindow) + { + if (!pIOSViewController) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pIOSViewController->setTitle(pWindow->GetWindowTitle().c_str()); }); + return true; + } - pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { - AddPendingMainThreadTask(BECOME_ACTIVE); - }); + std::vector Host_Apple_iOS::GetHostWindowDescriptor(olc::Window* pWindow) + { + return vIOSWindowDescriptors; + } - pMacOSWindow->setWindowDidResignKeyCallback([&]() { - AddPendingMainThreadTask(RESIGN_ACTIVE); + 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); }); - - pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { - AddPendingMainThreadTask(MINIMIZE_WINDOW); + 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); }); - - pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { - AddPendingMainThreadTask(DEMINIMIZE_WINDOW); + 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; + } - - // 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::StopSystem() { - unsigned short keyCode = event.keyCode; - - // handle num clear/lock key only on the down stroke. - if(isPressed && keyCode == 71) + // 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) { - 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) + { + if(!pPrimaryPGE->OnContextStart()) + throw std::runtime_error("PGE3 failed to initialize context, User aborted OnUserCreate()"); + else + bPGEInitialized = true; + } + if(bPGEInitialized) [[likely]] + if(!pPrimaryPGE->OnContextTick() + throw std::runtime_error("PGE3 failed to tick context, User aborted OnUserUpdate()"); + }); + + // 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 @@ -16153,7 +20359,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; @@ -16233,7 +20439,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: @@ -16271,7 +20477,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 @@ -16351,7 +20557,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 @@ -16365,7 +20571,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 @@ -16570,7 +20776,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 @@ -16647,7 +20853,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 @@ -16686,7 +20892,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 @@ -16718,7 +20924,7 @@ R"( void main() { gl_PointSize = 1.0; // Required for emscripten - + if (pgeDrawType == 2) // 3D { gl_Position = pgeMVP * vec4(aPos.x, aPos.y, aPos.z, 1.0); @@ -16963,6 +21169,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 @@ -17117,7 +21334,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); @@ -17172,7 +21389,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 @@ -17204,6 +21421,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); @@ -17241,6 +21462,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]); @@ -17296,6 +21525,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; } @@ -17356,7 +21593,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 @@ -17392,7 +21629,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 @@ -17412,7 +21649,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()); @@ -17965,6 +22202,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]); @@ -18036,7 +22277,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 @@ -19321,7 +23562,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 @@ -20228,7 +24469,7 @@ namespace olc olc::hw::Touch& PGEWindow::GetTouch() { return touch; } - + const olc::vi2d& PGEWindow::ScreenSize() { return GetScreen().Size(); @@ -21558,6 +25799,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 {