diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index c17379f..c694782 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -165,7 +165,11 @@ +#if OLC_HOST == OLC_HOST_MACOS +//! GRAB api_macos.h DECLARATION +//! GRAB api_macos_wrapper.hpp DECLARATION +#endif #if !defined(OLC_USE_WXWIDGETS) @@ -182,10 +186,6 @@ #endif #if OLC_HOST == OLC_HOST_MACOS -//! GRAB api_macos.h DECLARATION - -//! GRAB api_macos_wrapper.hpp DECLARATION - //! GRAB host_apple_macos.h MACOS_CONFIG //! GRAB host_apple_macos.h DECLARATION @@ -260,6 +260,10 @@ #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) +#if OLC_HOST == OLC_HOST_MACOS +//! GRAB api_macos.cpp IMPLEMENTATION +#endif + #if !defined(OLC_USE_WXWIDGETS) #if OLC_HOST == OLC_HOST_NONE @@ -272,8 +276,6 @@ #if OLC_HOST == OLC_HOST_MACOS //! GRAB host_apple_macos.cpp IMPLEMENTATION - -//! GRAB api_macos.cpp IMPLEMENTATION #endif #if OLC_HOST == OLC_HOST_LINUX_X11 diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index f0bbb81..b9f4874 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -84,7 +84,6 @@ 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 = ""; }; - 4610522030593E6300C2BC3A /* olcPixelGameEngine3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = olcPixelGameEngine3.h; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/olcPixelGameEngine3.h; 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 = ""; }; @@ -110,6 +109,7 @@ 46B1E5942E40F8B0009734EC /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; 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; }; @@ -124,7 +124,7 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 4610526430593E6D00C2BC3A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + 46E03B94305C0DC1003BFA1D /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( olcPGE3_AntiAliasing.cpp, @@ -162,9 +162,9 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 4610521930593E6300C2BC3A /* examples */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4610526430593E6D00C2BC3A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); name = examples; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/examples; sourceTree = ""; }; - 4610521F30593E6300C2BC3A /* extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = extensions; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/extensions; sourceTree = ""; }; - 4610522730593E6300C2BC3A /* utilities */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = utilities; path = /Users/mickymacm4/Documents/olcPixelGameEngine3_Issue293/utilities; sourceTree = ""; }; + 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 = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -213,11 +213,11 @@ 8998CE172E057F4E007DDD96 = { isa = PBXGroup; children = ( + 46E03B67305C0D9A003BFA1D /* examples */, + 46E03B6E305C0D9A003BFA1D /* extensions */, + 46E03B75305C0D9A003BFA1D /* utilities */, + 46E03AD8305C0D3A003BFA1D /* olcPixelGameEngine3.h */, 4610526530593EE400C2BC3A /* assets */, - 4610521930593E6300C2BC3A /* examples */, - 4610521F30593E6300C2BC3A /* extensions */, - 4610522030593E6300C2BC3A /* olcPixelGameEngine3.h */, - 4610522730593E6300C2BC3A /* utilities */, 8998CE322E057F92007DDD96 /* src */, 8998CE2A2E057F85007DDD96 /* tests */, 46E85DF52E39135300B74FA3 /* Frameworks */, @@ -316,9 +316,9 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - 4610521930593E6300C2BC3A /* examples */, - 4610521F30593E6300C2BC3A /* extensions */, - 4610522730593E6300C2BC3A /* utilities */, + 46E03B67305C0D9A003BFA1D /* examples */, + 46E03B6E305C0D9A003BFA1D /* extensions */, + 46E03B75305C0D9A003BFA1D /* utilities */, ); name = olcPGE3_SH; productName = olcPGE3; @@ -337,11 +337,6 @@ ); dependencies = ( ); - fileSystemSynchronizedGroups = ( - 4610521930593E6300C2BC3A /* examples */, - 4610521F30593E6300C2BC3A /* extensions */, - 4610522730593E6300C2BC3A /* utilities */, - ); name = olcPGE3; productName = olcPGE3; productReference = 8998CE202E057F4E007DDD96 /* olcPGE3 */; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 5e75791..2b919d7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4416,172 +4416,6 @@ namespace olc - - -#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 #include @@ -5830,54 +5664,220 @@ namespace olc { } // namespace apis } // namespace olc +#endif +#if !defined(OLC_USE_WXWIDGETS) +#if OLC_HOST == OLC_HOST_NONE -#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 olc::host { - 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; } + 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: - 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; + 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; - virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const 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: // 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 - public: // OS Specific Environment Information - virtual olc::KeyboardLayout GetKeyboardLayout() const override; +#if OLC_HOST == OLC_HOST_WINDOWS +#if defined(UNICODE) || defined(_UNICODE) + #define olcT(s) L##s +#else + #define olcT(s) s +#endif - 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 +#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; @@ -7499,4737 +7499,4739 @@ namespace olc::imload #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) -#if !defined(OLC_USE_WXWIDGETS) +#if OLC_HOST == OLC_HOST_MACOS -#if OLC_HOST == OLC_HOST_NONE -namespace olc::host -{ - // Make OS Create a window frame, associated with olc::Window - bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - return true; - } +// 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"; - // Make OS Close a window frame, associated with olc::Window - bool Host_None::CloseWindowFrame(olc::Window* pWindow) - { - return true; - } +// 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"; - // Make OS Update a window frame title, associated with olc::Window - bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) - { - return true; - } - // Get OS-specific window descriptor(s) for given olc::Window - std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) - { - return {}; - } +// 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:"; - // Wait for OS desktop refresh (for smooooth vsync) - bool Host_None::SyncWithDesktopComposite() - { - return true; - } +// 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:"; - // Force the mouse position in pixels relative to window - bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - return true; - } - - // Show or hide mouse cursor for given window - bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - return true; - } +// 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:"; - olc::KeyboardLayout Host_None::GetKeyboardLayout() const - { - return OLC_DEFAULT_KEYBOARD_LAYOUT; - } +// 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:"; - // Called at very start of application - bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - pPrimaryPGE = pPrimary; - return true; - } - - // Called to start the host - this may mean different things on different hosts - // It MUST block until system is requested to exit - bool Host_None::StartSystem() - { - pPrimaryPGE->OnPreContextStart(); +// 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:"; - systemActive = true; +// NSResponder keyboard and mouse event methods selectors +static constexpr const char* kKeyDownSel = "keyDown:"; +static constexpr const char* kKeyUpSel = "keyUp:"; +static constexpr const char* kFlagsChangedSel = "flagsChanged:"; +static constexpr const char* kMouseDownSel = "mouseDown:"; +static constexpr const char* kMouseUpSel = "mouseUp:"; +static constexpr const char* kMouseDraggedSel = "mouseDragged:"; +static constexpr const char* kMouseMovedSel = "mouseMoved:"; +static constexpr const char* kRightMouseDownSel = "rightMouseDown:"; +static constexpr const char* kRightMouseUpSel = "rightMouseUp:"; +static constexpr const char* kRightMouseDraggedSel = "rightMouseDragged:"; +static constexpr const char* kOtherMouseDownSel = "otherMouseDown:"; +static constexpr const char* kOtherMouseUpSel = "otherMouseUp:"; +static constexpr const char* kOtherMouseDraggedSel = "otherMouseDragged:"; +static constexpr const char* kScrollWheelSel = "scrollWheel:"; +static constexpr const char* kDeltaXSel = "deltaX"; +static constexpr const char* kDeltaYSel = "deltaY"; +static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; +static constexpr const char* kMouseEnteredSel = "mouseEntered:"; +static constexpr const char* kMouseExitedSel = "mouseExited:"; +static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; +static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; +static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; - if(!OnSystemThreadStart()) - return pPrimaryPGE->OnPostContextEnd(); - - while(systemActive) - { - if(!OnSystemTick()) - { - StopSystem(); - } - } +// 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:"; - OnSystemThreadEnd(); +// 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"; - return pPrimaryPGE->OnPostContextEnd(); - } +// 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"; - // Called to stop the host, and shutdown all resources - bool Host_None::StopSystem() - { - systemActive = false; - return true; - } +// 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"; - // Called at start of system event loop - bool Host_None::OnSystemThreadStart() - { - return pPrimaryPGE->OnContextStart(); - } - // Called to perform primary window update - bool Host_None::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } - - // Called at end of system event loop - bool Host_None::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } - - // Called at very end of application - bool Host_None::OnApplicationEnd() - { - return true; - } -} -#endif +// 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 OLC_HOST == OLC_HOST_WINDOWS -namespace olc::host -{ - bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - pPrimaryPGE = pPrimary; - return true; - } +// 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:"; - bool Host_Windows_WinAPI::StartSystem() - { - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); +// 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"; - // Mark system as active - systemActive = true; +// NSLocale class and method names +static constexpr const char* kNSLocaleClass = "NSLocale"; +static constexpr const char* kCurrentLocaleSel = "currentLocale"; +static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; - // Create system thread - handles gpu context - std::thread threadSystem([this]() - { - // Notify start of system thread - if (!this->OnSystemThreadStart()) - { - // PGE->OnContextStart() failed, or user aborted OnUserCreate() - return; - } - // Main system loop - while (systemActive) - { - // Perform primary window update - if (!this->OnSystemTick()) - { - StopSystem(); - } - } +// 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; - // Notify end of system thread - if (!this->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } - }); +// Objective-C method type encoding constants +// Type encoding for methods returning BOOL with no parameters: "c@:" +static constexpr const char* kBoolMethodTypeEncoding = "c@:"; - // Blocking event loop on this thread - handles windows - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } +// Type encoding for void methods with event/notification parameter: "v@:@" +static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; - systemActive = false; - if(threadSystem.joinable()) - threadSystem.join(); +// 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}}"; - // Post-context end hook - return pPrimaryPGE->OnPostContextEnd(); - } +// Type encoding for void methods with no parameters: "v@:" +static constexpr const char* kVoidMethodTypeEncoding = "v@:"; - bool Host_Windows_WinAPI::StopSystem() - { - systemActive = false; - return true; - } +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; - bool Host_Windows_WinAPI::OnSystemThreadStart() - { - return pPrimaryPGE->OnContextStart(); - } +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; - bool Host_Windows_WinAPI::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } + // 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; - bool Host_Windows_WinAPI::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); - } + // Application Screen management selectors + static SEL screenSel = nullptr; + static SEL unhideSel = nullptr; + static SEL hideSel = nullptr; + static SEL isHiddenSel = nullptr; - bool Host_Windows_WinAPI::OnApplicationEnd() - { - return true; - } + // NSApplicationDelegate lifecycle methods + static SEL applicationWillFinishLaunchingSel = nullptr; + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; + // 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; + // 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; - // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE + // 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; + // Mouse Tracking + static SEL addTrackingAreaSel = nullptr; + static SEL initWithRectSel = nullptr; - // Forward Declaration - static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + // 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; + // 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; - // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get - // access to the actual host instance, which can more conveninetly process - // the event across multiple window instances - static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - // CreateWindowEx will fire a WM_CREATE event at the window, which we're - // not interested in, and this will occur before we've populated our - // linkage maps. We can detect for this condition here which means - // subsequent look-ups dont fail. NOTE: Do not assume WM_CREATE is the - // first message that is sent, its just one that we know is reliably sent. - if (uMsg == WM_CREATE) - { - // Associate the window's little blob of user memory with host iface - auto cfg = ((CREATESTRUCT*)lParam)->lpCreateParams; - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cfg); - } - else - { - // If this returns a value, then the host iface should be responsible - // for handling the message. - auto host = (Host_Windows_WinAPI*)GetWindowLongPtr(hWnd, GWLP_USERDATA); - if (host) - return host->OnWindowEvent(hWnd, uMsg, wParam, lParam); - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } + // 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; - std::wstring Host_Windows_WinAPI::ConvertS2W(std::string s) - { -#ifdef __MINGW32__ - wchar_t* buffer = new wchar_t[s.length() + 1]; - mbstowcs(buffer, s.c_str(), s.length()); - buffer[s.length()] = L'\0'; -#else - int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); - wchar_t* buffer = new wchar_t[count]; - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); -#endif - std::wstring w(buffer); - delete[] buffer; - return w; - } + // 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; - Host_Windows_WinAPI::Host_Windows_WinAPI() - { - // Map Windows Defined VK_ Codes to olc::KeyCodes - mapKeys[0x00] = Key::NONE; + // 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; - // Windows doesn't define A-Z - mapKeys[0x41] = Key::A; - mapKeys[0x42] = Key::B; - mapKeys[0x43] = Key::C; - mapKeys[0x44] = Key::D; - mapKeys[0x45] = Key::E; - mapKeys[0x46] = Key::F; - mapKeys[0x47] = Key::G; - mapKeys[0x48] = Key::H; - mapKeys[0x49] = Key::I; - mapKeys[0x4A] = Key::J; - mapKeys[0x4B] = Key::K; - mapKeys[0x4C] = Key::L; - mapKeys[0x4D] = Key::M; - mapKeys[0x4E] = Key::N; - mapKeys[0x4F] = Key::O; - mapKeys[0x50] = Key::P; - mapKeys[0x51] = Key::Q; - mapKeys[0x52] = Key::R; - mapKeys[0x53] = Key::S; - mapKeys[0x54] = Key::T; - mapKeys[0x55] = Key::U; - mapKeys[0x56] = Key::V; - mapKeys[0x57] = Key::W; - mapKeys[0x58] = Key::X; - mapKeys[0x59] = Key::Y; - mapKeys[0x5A] = Key::Z; + // 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 - // Windows doesnt define numeric keys - mapKeys[0x30] = Key::K0; - mapKeys[0x31] = Key::K1; - mapKeys[0x32] = Key::K2; - mapKeys[0x33] = Key::K3; - mapKeys[0x34] = Key::K4; - mapKeys[0x35] = Key::K5; - mapKeys[0x36] = Key::K6; - mapKeys[0x37] = Key::K7; - mapKeys[0x38] = Key::K8; - mapKeys[0x39] = Key::K9; + // 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); - // Function Keys - mapKeys[VK_F1] = Key::F1; - mapKeys[VK_F2] = Key::F2; - mapKeys[VK_F3] = Key::F3; - mapKeys[VK_F4] = Key::F4; - mapKeys[VK_F5] = Key::F5; - mapKeys[VK_F6] = Key::F6; - mapKeys[VK_F7] = Key::F7; - mapKeys[VK_F8] = Key::F8; - mapKeys[VK_F9] = Key::F9; - mapKeys[VK_F10] = Key::F10; - mapKeys[VK_F11] = Key::F11; - mapKeys[VK_F12] = Key::F12; + // 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); - // Arrow Keys - mapKeys[VK_DOWN] = Key::DOWN; - mapKeys[VK_LEFT] = Key::LEFT; - mapKeys[VK_RIGHT] = Key::RIGHT; - mapKeys[VK_UP] = Key::UP; + // NSApplicationDelegate lifecycle methods + applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); + applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); + applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); + applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); + applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); - // Other Keys - mapKeys[VK_BACK] = Key::BACK; - mapKeys[VK_ESCAPE] = Key::ESCAPE; - mapKeys[VK_RETURN] = Key::ENTER; - mapKeys[VK_PAUSE] = Key::PAUSE; - mapKeys[VK_SCROLL] = Key::SCROLL; - mapKeys[VK_TAB] = Key::TAB; - mapKeys[VK_DELETE] = Key::DEL; - mapKeys[VK_HOME] = Key::HOME; - mapKeys[VK_END] = Key::END; - mapKeys[VK_PRIOR] = Key::PGUP; - mapKeys[VK_NEXT] = Key::PGDN; - mapKeys[VK_INSERT] = Key::INS; - mapKeys[VK_SHIFT] = Key::SHIFT; - mapKeys[VK_CONTROL] = Key::CTRL; - mapKeys[VK_SPACE] = Key::SPACE; - mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; - mapKeys[VK_MENU] = Key::ALT; + // 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); - // Numpad - mapKeys[VK_NUMPAD0] = Key::NP0; - mapKeys[VK_NUMPAD1] = Key::NP1; - mapKeys[VK_NUMPAD2] = Key::NP2; - mapKeys[VK_NUMPAD3] = Key::NP3; - mapKeys[VK_NUMPAD4] = Key::NP4; - mapKeys[VK_NUMPAD5] = Key::NP5; - mapKeys[VK_NUMPAD6] = Key::NP6; - mapKeys[VK_NUMPAD7] = Key::NP7; - mapKeys[VK_NUMPAD8] = Key::NP8; - mapKeys[VK_NUMPAD9] = Key::NP9; - mapKeys[VK_MULTIPLY] = Key::NP_MUL; - mapKeys[VK_ADD] = Key::NP_ADD; - mapKeys[VK_DIVIDE] = Key::NP_DIV; - mapKeys[VK_SUBTRACT] = Key::NP_SUB; - mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + // 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); - // OEM Keys - mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key - mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key - mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key - mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key - mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. - mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key - mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard - mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard - mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard - mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard - mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard - } + // 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); - bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) - { - olc_IgnoreUnused(bFullScreen); + // 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); - // The user created olc::Window object is the SSoT for what a window - // should look like, so get that sort of thing from there - olc::vi2d vWinPos = vWindowPos; - olc::vi2d vWinSize = vWindowSize; - - hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); + // 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); - // Define WindowClass - WNDCLASSEX wc = { 0 }; - wc.cbSize = sizeof(WNDCLASSEX); - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = hCursorDefault; - wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpfnWndProc = WINAPI_EventHandler; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0;// sizeof(this); // For static meta-info - wc.lpszMenuName = nullptr; - wc.hbrBackground = nullptr; - wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); - RegisterClassEx(&wc); + // 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); - // Define window furniture - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); + // NSLocale selectors + currentLocaleSel = sel_registerName(kCurrentLocaleSel); + localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); + } - olc::vi2d vTopLeft = vWindowPos; + // Ensures we only initialize selectors once (Thread-safe) + void ensureInitialized() { + static std::once_flag initialized; + std::call_once(initialized, [&]() { + initializeSelectors(); + }); + } +} - if (bFullScreen || pPrimaryPGE->config.bFullScreen) - { - dwExStyle = 0; - dwStyle = WS_VISIBLE | WS_POPUP; - POINT olc_pt = { vWinPos.x, vWinPos.y }; - HMONITOR hmon = MonitorFromPoint(olc_pt, MONITOR_DEFAULTTONEAREST); - MONITORINFO mi = { sizeof(mi) }; - if (!GetMonitorInfo(hmon, &mi)) return false; - vWinSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; - vTopLeft.x = 0; - vTopLeft.y = 0; - } +// Single initialization function called once at startup +void initialize_macos_api() { + ObjectiveCSEL::initializeSelectors(); +} +// Concepts safety templates for Objective-C interactions +template +concept CallbackType = std::is_function_v>; // Checks if T is a function type - // Keep client size as requested - RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; - AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); +template +concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) - // +1 Hack to remove black bar between client and title bar for "perfect" window - // sizes anyway. This could be DPI related on later windows, but this makes it look - // tidier for "normal" applications - int width = rWndRect.right - rWndRect.left + 1; - int height = rWndRect.bottom - rWndRect.top; +// Autorelease pool management functions +extern "C" { + extern void* objc_autoreleasePoolPush(void); + extern void objc_autoreleasePoolPop(void* pool); +} - // Create the actual OS window, return a handle - HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, - vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); +// 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; - // Update window size to match actual client area given. In situations where the window - // is clamped to the desktop, the client area may be smaller than requested. - RECT rClient; - GetClientRect(hWnd, &rClient); - pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); +private: + void* pool_; +}; - // Hide the close button if the user requested it, but only after styles are applied, - if (!pPrimaryPGE->config.bShowWindowCloseButton) - { - HMENU hMenu = GetSystemMenu(hWnd, FALSE); - DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); - } - - LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); - lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); - pWindow->olc_OnFocus(true); +using NSPoint = CGPoint; +using NSInteger = long; +using NSUInteger = unsigned long; - //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); - //ShowWindow(hWnd, 1); - //UpdateWindow(hWnd); +// Forward declarations +struct Application; +struct Window; +struct OpenGLRenderer; +struct ApplicationDelegate; +struct CustomOpenGLView; +struct WindowDelegate; +struct ImageLoader; - // Now... awkwardly, the above has already fired off some window messages - // and they arent necessarily in a consistent order. Whereas one might - // assume WM_CREATE would be the first, there are some others on more - // modern systems. This is awkward because we havent yet associated the - // source window with a long_ptr to this class, and therefore we can't - // call the appropriate event handler. +// 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 - // Store the link bewteen host resource and window - mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); - mapHWND2PTR.insert_or_assign(hWnd, pWindow); + // Buffer configuration + TripleBuffer = 3, // Triple buffering support + DoubleBuffer = 5, // Double buffering (recommended) + Stereo = 6, // Stereo buffering for 3D + AuxBuffers = 7, // Number of auxiliary buffers + BackingStore = 76, // Backing store configuration + + // Color and depth configuration + ColorSize = 8, // Total color buffer size (bits) + AlphaSize = 11, // Alpha channel size (bits) + DepthSize = 12, // Depth buffer size (bits) + StencilSize = 13, // Stencil buffer size (bits) + AccumSize = 14, // Accumulation buffer size (bits) + ColorFloat = 58, // Floating-point color buffer - + // Antialiasing configuration + Multisample = 59, // Multisampling antialiasing + Supersample = 60, // Supersampling antialiasing + SampleBuffers = 55, // Number of sample buffers + Samples = 56, // Samples per pixel + SampleAlpha = 61, // Sample alpha to coverage + AuxDepthStencil = 57, // Auxiliary depth/stencil buffer + // Policy configuration + MinimumPolicy = 51, // Minimum attribute policy + MaximumPolicy = 52, // Maximum attribute policy + ClosestPolicy = 74, // Closest match policy - //DragAcceptFiles(olc_hWnd, true); + // Advanced configuration + OpenGLProfile = 99, // OpenGL profile selection + ScreenMask = 84, // Screen mask for multi-display + VirtualScreenCount = 128, // Number of virtual screens +}; - return true; - } +// 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 - bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) - { - DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); - return false; - } +// 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 +}; - bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) - { -#ifdef UNICODE - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); -#else - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); -#endif - return true; - } +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; - std::vector Host_Windows_WinAPI::GetHostWindowDescriptor(olc::Window* pWindow) - { - return { mapUID2HWND[pWindow->GetUID()] }; - } +// Backward compatibility +//static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); +static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); - bool Host_Windows_WinAPI::SyncWithDesktopComposite() - { - return DwmFlush() == S_OK; - } +// 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 +}; - bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - POINT pt; - pt.x = vPos.x; - pt.y = vPos.y; - ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); - SetCursorPos(pt.x, pt.y); - return true; - } +// 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); - bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - olc_IgnoreUnused(pWindow); +// 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) +}; - hCursorNow = bVisible ? hCursorDefault : NULL; +static constexpr int NSBackingStoreBuffered = static_cast(NSBackingStoreType::Buffered); - // Fire fake move event to update cursor visibility immediately - POINT p; - GetCursorPos(&p); - SetCursorPos(p.x, p.y + 1); - SetCursorPos(p.x, p.y); - return true; - } +// 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) +}; - bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) - { - HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); +// Backward compatibility +static constexpr int NSWindowCreateNow = static_cast(NSWindowDefer::CreateNow); - if (bFullScreen) - { - // Maximise, make on top, remove border and titlebar - SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); - ShowWindow(hWnd, SW_MAXIMIZE); - } - else - { - // Restore original window style and position - DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; - // Get the style we should have based on the window config - DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); +// 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 +}; - LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); - SetWindowLongPtr(hWnd, GWL_STYLE, lp | dwStyle); - lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); - SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | dwExStyle); - ShowWindow(hWnd, SW_NORMAL); - } +// Backward compatibility +static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); +static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); - UpdateWindow(hWnd); - SetForegroundWindow(hWnd); - SetFocus(hWnd); - SetActiveWindow(hWnd); - return true; - } +// 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 +}; - DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) - { - olc_IgnoreUnused(pWindow); +// Backward compatibility +static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); - DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx +// Mouse Tracking const +static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; +static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; +static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; - // Note for Microsoft: if you hide the border, it hides the title bar too, and via versa +/* +* 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; - // For fullscreen,borderless/noTitlebar we want to skip all the window furniture and just have a big ol canvas - if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return dwStyle |= WS_POPUP; +// Handles NSApplication delegate methods and lifecycle events +struct ApplicationDelegate { + Class isa; // Objective-C class pointer (required) +}; - // If any max/min/close button(s) display the button menu - if (pPrimaryPGE->config.bShowWindowCloseButton || pPrimaryPGE->config.bShowWindowMaximiseButton || pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_SYSMENU; - if (pPrimaryPGE->config.bShowWindowTilebar) dwStyle |= WS_CAPTION; // Add a title bar - if (pPrimaryPGE->config.bShowWindowBorder) dwStyle |= WS_BORDER; // Add a border - if (pPrimaryPGE->config.bResizeable) dwStyle |= WS_THICKFRAME; // Enable resizing - if (pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_MINIMIZEBOX; // Add Min Button - if (pPrimaryPGE->config.bShowWindowMaximiseButton) dwStyle |= WS_MAXIMIZEBOX; // Add Max Button - - // Note: Close button is handled after dwStlyes are applied +// Custom NSOpenGLView subclass for handling input events +struct CustomOpenGLView { + Class isa; // Objective-C class pointer (required) + void* renderer; // C Pointer to associated renderer +}; - return dwStyle; +// Handles NSWindow delegate methods and window events +struct WindowDelegate { + Class isa; // Objective-C class pointer (required) + void* renderer; // Pointer to associated renderer +}; +// Application management with member initialization +struct Application { + id nsApp{nullptr}; // NSApplication instance + id delegate{nullptr}; // Application delegate instance - } - - LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - if (!mapHWND2PTR.contains(hWnd)) - return DefWindowProc(hWnd, uMsg, wParam, lParam);; + // Callback function pointers + std::function willFinishLaunchingCallback{nullptr}; + std::function didFinishLaunchingCallback{nullptr}; + std::function willTerminateCallback{nullptr}; + std::function didBecomeActiveCallback{nullptr}; + std::function willResignActiveCallback{nullptr}; - // Get target olc::Window - const auto& window = mapHWND2PTR.at(hWnd); + 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 - // Many WinAPI events are literally ancient these days, so need some interpretation - // to get to the useful data. + // 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; +}; - switch (uMsg) - { - case WM_MOUSEMOVE: // Mouse has moved within a window - { - // Extract mouse X & Y - uint16_t x = uint16_t(lParam & 0xFFFF); - uint16_t y = uint16_t((lParam >> 16) & 0xFFFF); - int16_t ix = *(int16_t*)&x; - int16_t iy = *(int16_t*)&y; - // Tell window new mouse location - window->olc_OnMouseMove(olc::vi2d{ ix, iy }); - break; - } - +// 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 - // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; - case WM_SIZE: - { - window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); - break; - } + // 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}; - case WM_MOUSEWHEEL: - { - window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); - break; - } + // 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}; - case WM_ACTIVATE: - { - window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); - return 0; - } + // 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}; - case WM_MOUSEACTIVATE: - { - window->olc_OnFocus(true); - return MA_ACTIVATE; - } - - case WM_SETFOCUS: - { - window->olc_OnFocus(true); - return 0; - } + 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}; - case WM_KILLFOCUS: - { - window->olc_OnFocus(false); - return 0; - } + 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}; +}; - case WM_KEYDOWN: - { - window->olc_OnFocus(true); - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); - } - break; - } +// 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 - case WM_KEYUP: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); - } - break; - } + // 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}; - case WM_SYSKEYDOWN: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); - } - break; - } +}; - case WM_SYSKEYUP: - { - if (mapKeys.contains(int32_t(wParam))) - { - window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); - } - break; - } +// 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}; +}; - case WM_LBUTTONDOWN: - { - window->olc_OnMouseButton(0, true); - break; - } - case WM_LBUTTONUP: - { - window->olc_OnMouseButton(0, false); - break; - } - case WM_RBUTTONDOWN: - { - window->olc_OnMouseButton(1, true); - break; - } - case WM_RBUTTONUP: - { - window->olc_OnMouseButton(1, false); - break; - } - case WM_MBUTTONDOWN: - { - window->olc_OnMouseButton(2, true); - break; - } - case WM_MBUTTONUP: - { - window->olc_OnMouseButton(2, false); - break; - } - case WM_XBUTTONDOWN: - { - UINT button = GET_XBUTTON_WPARAM(wParam); - if(button == XBUTTON1) - { - window->olc_OnMouseButton(3, true); - } - else if(button == XBUTTON2) - { - window->olc_OnMouseButton(4, true); - } - - break; - } - case WM_XBUTTONUP: - { - UINT button = GET_XBUTTON_WPARAM(wParam); - if(button == XBUTTON1) - { - window->olc_OnMouseButton(3, false); - } - else if(button == XBUTTON2) - { - window->olc_OnMouseButton(4, false); - } - - break; - } - - case WM_POINTERDOWN: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - true, - false, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - true, - false, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - - } - - break; - } - - case WM_POINTERUP: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - true, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - true, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - } - - break; - } - - case WM_POINTERUPDATE: - { - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) - { - if (pointerType == PT_TOUCH) - { - POINTER_TOUCH_INFO touchInfo; - if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - false, - olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); - } - } - else if (pointerType == PT_PEN) - { - if (IS_POINTER_INCONTACT_WPARAM(wParam)) - { - POINTER_PEN_INFO penInfo; - if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(hWnd, &pt); - window->olc_OnTouch( - GET_POINTERID_WPARAM(wParam), - olc::vf2d{ float(pt.x), float(pt.y) }, - false, - false, - { 1,1 }, - true, - float(penInfo.pressure) / 1024.0f, - float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, - { float(penInfo.tiltX) , float(penInfo.tiltY) } - ); - } - } - } - } - break; - } - - - // case WM_DROPFILES: - // { - // // This is all eww... - // HDROP drop = (HDROP)wParam; - // - // uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); - // std::vector vFiles; - // for (uint32_t i = 0; i < nFiles; i++) - // { - // TCHAR dfbuffer[256]{}; - // uint32_t len = DragQueryFile(drop, i, nullptr, 0); - // DragQueryFile(drop, i, dfbuffer, 256); - //#ifdef UNICODE - //#ifdef __MINGW32__ - // char* buffer = new char[len + 1]; - // wcstombs(buffer, dfbuffer, len); - // buffer[len] = '\0'; - //#else - // int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); - // char* buffer = new char[count]; - // WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); - //#endif - // vFiles.push_back(std::string(buffer)); - // delete[] buffer; - //#else - // vFiles.push_back(std::string(dfbuffer)); - //#endif - // } - // - // // Even more eww... - // POINT p; DragQueryPoint(drop, &p); - // ptrPGE->olc_DropFiles(p.x, p.y, vFiles); - // DragFinish(drop); - // return 0; - // } - // break; - // - // - case WM_CLOSE: - { - window->olc_OnWindowClose(); - break; - //return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - case WM_SETCURSOR: - { - if (LOWORD(lParam) == HTCLIENT) - { - SetCursor(hCursorNow); - return TRUE; // Sigh ffs microsoft... - } - - break; - } - - case WM_DESTROY: - PostQuitMessage(0); - DestroyWindow(hWnd); - - } - - - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - - olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const - { - HKL kbl = ::GetKeyboardLayout(0); - size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; - - if (highWord == 0x00000409) // US - return olc::KeyboardLayout::QWERTY_US; - else if(highWord == 0x00000809) // UK - return olc::KeyboardLayout::QWERTY_UK; - else if(highWord == 0x00000407) // DE - return olc::KeyboardLayout::QWERTZ; - else if(highWord == 0x0000040C) // FR - return olc::KeyboardLayout::AZERTY; - - return OLC_DEFAULT_KEYBOARD_LAYOUT; - } - -}; -#endif - -#if OLC_HOST == OLC_HOST_MACOS -namespace olc::host { - - - // NSEventModifierFlags values - // constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 // Temp remove unused variable warning - constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 - constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 - // constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 // Temp remove unused variable warning - constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 - // constexpr unsigned int NSEventModifierFlagNumericPad = 1 << 21; // 0x200000 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 // Temp remove unused variable warning - // constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 // Temp remove unused variable warning - - // enum for window appearance and behavior bit flags - enum class NSWindowStyleMask : uint16_t { - Titled = (1 << 0), // Window has a title bar - Closable = (1 << 1), // Window can be closed - Miniaturizable = (1 << 2), // Window can be minimized - Resizable = (1 << 3), // Window can be resized - UtilityWindow = (1 << 4), // Utility window style - DocModalWindow = (1 << 6), // Document-modal window - NonactivatingPanel = (1 << 7), // Non-activating panel - TexturedBackground = (1 << 8), // Textured background - HUDWindow = (1 << 13), // Heads-up display window - UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar - FullScreen = (1 << 14), // Full-screen window - FullSizeContentView = (1 << 15) // Full-size content view - }; - - - Host_Apple_MacOS::Host_Apple_MacOS() - { - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - mapKeys[0x00] = Key::NONE; - - // Map macOS key codes to olc::Key codes - mapKeys[0] = Key::A; - mapKeys[11] = Key::B; - mapKeys[8] = Key::C; - mapKeys[2] = Key::D; - mapKeys[14] = Key::E; - mapKeys[3] = Key::F; - mapKeys[5] = Key::G; - mapKeys[4] = Key::H; - mapKeys[34] = Key::I; - mapKeys[38] = Key::J; - mapKeys[40] = Key::K; - mapKeys[37] = Key::L; - mapKeys[46] = Key::M; - mapKeys[45] = Key::N; - mapKeys[31] = Key::O; - mapKeys[35] = Key::P; - mapKeys[12] = Key::Q; - mapKeys[15] = Key::R; - mapKeys[1] = Key::S; - mapKeys[17] = Key::T; - mapKeys[32] = Key::U; - mapKeys[9] = Key::V; - mapKeys[13] = Key::W; - mapKeys[7] = Key::X; - mapKeys[16] = Key::Y; - mapKeys[6] = Key::Z; - - // Numeric keys - mapKeys[29] = Key::K0; - mapKeys[18] = Key::K1; - mapKeys[19] = Key::K2; - mapKeys[20] = Key::K3; - mapKeys[21] = Key::K4; - mapKeys[23] = Key::K5; - mapKeys[22] = Key::K6; - mapKeys[26] = Key::K7; - mapKeys[28] = Key::K8; - mapKeys[25] = Key::K9; - - // Function Keys - mapKeys[122] = Key::F1; - mapKeys[120] = Key::F2; - mapKeys[99] = Key::F3; - mapKeys[118] = Key::F4; - mapKeys[96] = Key::F5; - mapKeys[97] = Key::F6; - mapKeys[98] = Key::F7; - mapKeys[100] = Key::F8; - mapKeys[101] = Key::F9; - mapKeys[109] = Key::F10; - mapKeys[103] = Key::F11; - mapKeys[111] = Key::F12; - - // Arrow Keys - mapKeys[125] = Key::DOWN; - mapKeys[123] = Key::LEFT; - mapKeys[124] = Key::RIGHT; - mapKeys[126] = Key::UP; - - // Other Keys - mapKeys[51] = Key::BACK; // Delete (Backspace) - mapKeys[53] = Key::ESCAPE; // Escape - mapKeys[36] = Key::ENTER; // Return - mapKeys[113] = Key::PAUSE; // F16 (often used as pause) - mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) - mapKeys[48] = Key::TAB; // Tab - mapKeys[117] = Key::DEL; // Forward Delete - mapKeys[115] = Key::HOME; // Home - mapKeys[119] = Key::END; // End - mapKeys[116] = Key::PGUP; // Page Up - mapKeys[121] = Key::PGDN; // Page Down - mapKeys[114] = Key::INS; // Help (Insert equivalent) - mapKeys[56] = Key::SHIFT; // Left Shift - mapKeys[59] = Key::CTRL; // Left Control - mapKeys[49] = Key::SPACE; // Space - mapKeys[57] = Key::CAPS_LOCK; // Caps Lock - - // Numpad - mapKeys[82] = Key::NP0; - mapKeys[83] = Key::NP1; - mapKeys[84] = Key::NP2; - mapKeys[85] = Key::NP3; - mapKeys[86] = Key::NP4; - mapKeys[87] = Key::NP5; - mapKeys[88] = Key::NP6; - mapKeys[89] = Key::NP7; - mapKeys[91] = Key::NP8; - mapKeys[92] = Key::NP9; - mapKeys[67] = Key::NP_MUL; // Numpad * - mapKeys[69] = Key::NP_ADD; // Numpad + - mapKeys[75] = Key::NP_DIV; // Numpad / - mapKeys[78] = Key::NP_SUB; // Numpad - - mapKeys[65] = Key::NP_DECIMAL; // Numpad . - - // Symbol Keys (OEM equivalents) - mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key - mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key - mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) - mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key - mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. - mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key - mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) - mapKeys[24] = Key::EQUALS; // Equal sign = - mapKeys[43] = Key::COMMA; // Comma , - mapKeys[27] = Key::MINUS; // Minus - - mapKeys[47] = Key::PERIOD; // Period . - - } - - - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ - olc_IgnoreUnused(bFullScreen); - pPGEwindow = pWindow; - pPGEwindow->SetWindowPosition(vWindowPos); - pPGEwindow->SetWindowSize(vWindowSize); - pPGEwindow->LinkToHost(this); - - frameBounds.x = 0.0; - frameBounds.y = 0.0; - frameBounds.width = static_cast(vWindowSize.x); - frameBounds.height = static_cast(vWindowSize.y); - - return true; - } - - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ - pWindow->olc_OnWindowClose(); - return true; - } - - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); - }); - return true; - } - - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - olc_IgnoreUnused(pWindow); - // Ensure OpenGL renderer is created - if(pMacOSOpenGLRenderer == nullptr) - CreateCGLContextObj(); - - return vMacOSWindowDescriptors; - - } - - - bool Host_Apple_MacOS::SyncWithDesktopComposite() - { - /* - core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, - the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, - set to false and toggle it on first call, so that vSync is only enabled once - */ - - if(!enableVSync) - { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; - } - - return enableVSync; - } - - bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) - { - olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorPosition(vPos.x, vPos.y); - }); - return false; - } - - bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) - { - olc_IgnoreUnused(pWindow); - dispatch_sync(dispatch_get_main_queue(), ^{ - pMacOSWindow->setCursorVisibility(bVisible); - }); - return true; - } - - bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) - { - olc_IgnoreUnused(pWindow); - // if we're already in the specified state, return early - if(pMacOSWindow->isFullScreen() == bFullScreen) - return true; - - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->toggleFullScreen(); - }); - return true; - } - - uint16_t Host_Apple_MacOS::ConvertPGE2WindowStyle() - { - uint16_t nsStyle = 0; - - // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window - if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); - - // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, - // For MacOS you can only disable the buttons, you can't hide them - if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window - if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar - if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border - if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing - if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button - if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button - - return nsStyle; - - } - - bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ - pPrimaryPGE = pPrimary; - return true; - } - - bool Host_Apple_MacOS::StartSystem(){ - - // Create MacOS Application instance - pMacApplication = std::make_unique(); - - // Set up application delegate event handlers - MacApplicationEventsHandler(); - - // Initialize and activate application first - pMacApplication->initialize(); - pMacApplication->activate(); - - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - - // Initialize the MacOS Window - pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); - pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); - pMacOSWindow->setContentViewPosition(0, 0); - - // Set up window event handlers - MacWindowEventsHandler(); - - // Create Input Event handler - pMacOSEventHandler = std::make_unique(*pMacOSWindow); - - // Setup Event handlers - MacEventsHandler(); - - // Create the window - unsigned long styleMask = ConvertPGE2WindowStyle(); - pMacOSWindow->show(styleMask); - pMacOSEventHandler->enable(); - - //--- Start up our engine threading system ---- - // Start the PGE context on the main thread - // Mark system as active - systemActive = true; - - // Create system thread - handles gpu context - std::thread threadSystem([this]() - { - // Notify start of system thread - if (!this->OnSystemThreadStart()) - { - // PGE->OnContextStart() failed, or user aborted OnUserCreate() - return; - } - - // Main system loop - while (systemActive) - { - // Perform primary window update - if (!this->OnSystemTick()) - { - StopSystem(); - } - } - - // Notify end of system thread - if (!this->OnSystemThreadEnd()) - { - // PGE->OnContextEnd() failed - return; - } - }); - - - // Start the main event loop (this will block) - pMacApplication->run(); - - // Once the application run loop ends, join the system thread - if(threadSystem.joinable()) - threadSystem.join(); - - // Post-context end hook - return pPrimaryPGE->OnPostContextEnd(); - - } - - bool Host_Apple_MacOS::StopSystem() - { - dispatch_sync(dispatch_get_main_queue(), ^{ - // clean up and close application - if (pMacOSOpenGLRenderer) - { - pMacOSOpenGLRenderer->destoryContext(); - pMacOSOpenGLRenderer = nullptr; - } - if (pMacOSWindow) - { - pMacOSWindow->destoryWindow(); - pMacOSWindow = nullptr; - } - if (pMacApplication) - { - pMacApplication->stop(); - } - - }); - systemActive = false; - return true; +// 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); } +} - bool Host_Apple_MacOS::OnSystemThreadStart() - { - // Hold back threading until application is fully initialized - bSkipFrame = ExecutePendingMainThreadTasks(); - return pPrimaryPGE->OnContextStart(); +// Called when application has finished launching +void applicationDidFinishLaunching(id self, SEL _cmd, id notification) { + (void)self;(void)_cmd;(void)notification; + if (gptrApplicationDelegate && gptrApplicationDelegate->didFinishLaunchingCallback) { + gptrApplicationDelegate->didFinishLaunchingCallback(gptrApplicationDelegate->didFinishLaunchingUserData); } +} - bool Host_Apple_MacOS::OnSystemTick() - { - // Execute any pending main thread tasks - bSkipFrame = ExecutePendingMainThreadTasks(); - return pPrimaryPGE->OnContextTick(); +// 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); } +} - bool Host_Apple_MacOS::OnSystemThreadEnd() - { - return pPrimaryPGE->OnContextEnd(); +// 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); } +} - bool Host_Apple_MacOS::OnApplicationEnd() - { - return true; +// 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); } +} +// Create and configure application delegate +id createApplicationDelegate() { -//-- OS Window Event Handling ----- - - olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const - { - // Get system locale from MacOS Application - // We need to wait until the application has launched to get the keyboard layout - // Therefore this function is called again from setDidFinishLaunchingCallback event - if (pMacApplication) - { - std::string locale = pMacApplication->getSystemLocale(); - if (locale == "en_GB") - { - return olc::KeyboardLayout::QWERTY_UK; - } - else if (locale == "en_US") - { - return olc::KeyboardLayout::QWERTY_US; - } - else if (locale == "fr_FR") - { - return olc::KeyboardLayout::AZERTY; - } - else if (locale == "de_DE") - { - return olc::KeyboardLayout::QWERTZ; - } - } - // Default to QWERTY if unknown - return olc::KeyboardLayout::QWERTY_UK; - } - -// ------- Priavate Main Thread Task Handling for MacOS Host ------- + // 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); - bool Host_Apple_MacOS::CreateCGLContextObj() - { - // This method should only be called on the PGE thread, use AddPendingMainThreadTask(CREATE_OPENGL_RENDERER); to queue it if needed - if(pMacOSOpenGLRenderer == nullptr) - { - vMacOSWindowDescriptors.clear(); // ensure we are starting fresh - pMacOSOpenGLRenderer = std::make_shared(); - - dispatch_sync(dispatch_get_main_queue(), ^{ - // Edge case for when the window is auto resize due to MacOS clamping to screen size - pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); - pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + // 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; +} - pMacOSOpenGLRenderer->attachToWindow(*pMacOSWindow); - pMacOSOpenGLRenderer->setupContext(); - }); - - pMacGLConextObj = pMacOSOpenGLRenderer->getCGLContextObj(); - - pMacOSOpenGLRenderer->setVsync(false); - - vMacOSWindowDescriptors.push_back(pMacGLConextObj); // Pointer to CGLContextObj - vMacOSWindowDescriptors.push_back(&bSkipFrame); // Pointer to skip frame flag +//====================================================================// +// Keyboard Event Handling - // Set up OpenGL renderer for visual feedback - pMacOSOpenGLRenderer->makeCurrentContext(); - - // Finally we set full screen if needed to ensure all out OpenGL setup is done before toggling full screen, - if(pPrimaryPGE->config.bFullScreen){ - SetFullScreen(pPGEwindow, true); - } - - } - - return true; - } +// Structure to hold common key event data +struct KeyEventData { + unsigned short keyCode = 0; + const char* characters = nullptr; + unsigned int modifierFlags = 0; +}; - bool Host_Apple_MacOS::ExecutePendingMainThreadTasks() - { - // 1: Check if main thread wants us to wait - std::unique_lock lock(pgeThreadPendingTasksMutex); - - if (isPGEThreadResetting.load()) { - - // 2. PGE Thread signals it's waiting - { - std::lock_guard mainLock(mainThreadPendingTasksMutex); - isMainThreadResetting = true; // Signal to main thread we're waiting - } - mainThreadResetCondition.notify_all(); // Wake up main thread +// 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; +} - // Note: MainThreadTasks(); will be called by the main thread to process tasks - - // 3. PGE Thread waits for main thread to finish - pgeThreadResetCondition.wait(lock, [this] { - return !isPGEThreadResetting.load(); - }); + // Handle key down events +void view_keyDown(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; // Remove unused parameter warnings - //4: return true indicating we processed tasks - return true; - } - else - { - // No pending tasks, just return - return false; - } - } + KeyEventData data = extractKeyEventData(event); - bool Host_Apple_MacOS::AddPendingMainThreadTask(MAINTASKS task) - { - // NOTE: Note: You should only add tasks that require main thread execution - vPendingMainThreadTasks.push_back(task); - MainThreadTasks(); - - return true; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyDownCallback) [[likely]] { + gptrNSWindowEvents->keyDownCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } +} + +// Handle key up events +void view_keyUp(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + KeyEventData data = extractKeyEventData(event); - bool Host_Apple_MacOS::MainThreadTasks() - { - bool res = false; - if(vPendingMainThreadTasks.empty()) - return res; // edge case - - // 1. Main Thread locks PGE Thread - { - std::lock_guard lock(pgeThreadPendingTasksMutex); - isPGEThreadResetting = true; // Signal PGE to stop - } - pgeThreadResetCondition.notify_all(); // Wake up PGE thread to check flag - - // 2. Main Thread waits for PGE Thread to acknowledge and wait - std::unique_lock lock(mainThreadPendingTasksMutex); - mainThreadResetCondition.wait(lock, [this] { - return isMainThreadResetting.load(); // Wait until PGE signals it's waiting - }); - - // Process any pending main thread tasks - for (const auto& task : vPendingMainThreadTasks) - { - switch (task) - { - case CREATE_OPENGL_RENDERER: - { - // In this case, the PGE will be waiting for main thread to singal, so the ContextOBJ can be created - res = false; // No need to skip frame - break; - } - case RESIZE_WINDOW: - { - // Resize window on main thread - pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); - pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); - pMacOSOpenGLRenderer->resetContextSize(frameBounds.width, frameBounds.height); - res = true; // Skip frame to allow resize to take effect - break; - } - case DEMINIMIZE_WINDOW: - case BECOME_ACTIVE: - { - pPGEwindow->olc_OnFocus(true); - break; - } - case MINIMIZE_WINDOW: - case RESIGN_ACTIVE: - { - pPGEwindow->olc_OnFocus(false); - break; - } - case NONE: - default: - { - res = false; - break; - } - - } - } - vPendingMainThreadTasks.clear(); - - // 4. Main Thread unlocks PGE Thread - { - std::lock_guard lock(pgeThreadPendingTasksMutex); - isPGEThreadResetting = false; // Release PGE thread - isMainThreadResetting = false; // Reset main thread flag - } - pgeThreadResetCondition.notify_all(); // Wake up PGE thread - return res; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->keyUpCallback) [[likely]] { + gptrNSWindowEvents->keyUpCallback(data.keyCode, data.characters, data.modifierFlags, gptrNSWindowEvents->eventUserData); } +} -//------ Events Handlers ----- +void view_flagsChanged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - 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([]() { }); - + unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { + gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); } +} - void Host_Apple_MacOS::MacWindowEventsHandler() +//====================================================================// +// Mouse Event Handling + +// 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(gptrNSWindowEvents && gptrNSWindowEvents->nsWindow) [[likely]] { - pMacOSWindow->setWindowDidResizeCallback([&]() { - AddPendingMainThreadTask(RESIZE_WINDOW); - }); + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - 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(); - }); + + // 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); - pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { - AddPendingMainThreadTask(BECOME_ACTIVE); - }); + // 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); - pMacOSWindow->setWindowDidResignKeyCallback([&]() { - AddPendingMainThreadTask(RESIGN_ACTIVE); - }); - - pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { - AddPendingMainThreadTask(MINIMIZE_WINDOW); - }); - - pMacOSWindow->setWindowDidDeminiaturizeCallback([&]() { - AddPendingMainThreadTask(DEMINIMIZE_WINDOW); - }); - + // Flip Y coordinate from bottom-left to top-left + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + + } } - - // 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) + else { - unsigned short keyCode = event.keyCode; - - // handle num clear/lock key only on the down stroke. - if(isPressed && keyCode == 71) - { - bNumLockActive = !bNumLockActive; - return; - } + // 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; +} - 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; - } - } +// Stylus event data structure to hold common stylus event information +struct StylusEventData { - // 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); - } + 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) - 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); - }); +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { - pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - KeyboardEventHandler(event, false); - }); + 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); - // Set up keyboard flag event handlers - pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity 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); - } + data.dPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointPressure); + data.dRotationRadians = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventRotation) * (M_PI / 180.0); + data.dTangentialPressure = CGEventGetDoubleValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventTangentialPressure); - if (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); - } + data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); - // caps lock doesn't appear to trigger any event - prevFlags = event.modifierFlags; - }); + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); - // 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)}); - }); + // 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)); - pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); - }); + // 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); - pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); - }); + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} - 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); - - }); +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) - 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); - }); +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; - pMacOSEventHandler->onOtherMouseDragged([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); - }); +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; - 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)); - }); +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; - // 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)}); - }); +// 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); - 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)}); - }); + // 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; +}; - 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)}); - }); +// 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) + +}; + +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 + +//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); + + 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; - 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)}); - }); + // 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); - // Stylus (tablet) events — full pressure, tilt and rotation data - pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) + { + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); + } - 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} - ); - }); + } } -} - - -// 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"; - -// Application Screen management selectors -//static constexpr const char* kNSScreenClass = "NSScreen"; // Temp remove unused variable warning -static constexpr const char* kScreenSel = "screen"; -static constexpr const char* kNSCursorClass = "NSCursor"; -static constexpr const char* kUnhideSel = "unhide"; -static constexpr const char* kHideSel = "hide"; -static constexpr const char* kIsHiddenSel = "isHidden"; - - -// Application memory management selectors -static constexpr const char* kAllocSel = "alloc"; -static constexpr const char* kInitSel = "init"; -static constexpr const char* kSetDelegateSel = "setDelegate:"; -static constexpr const char* kReleaseSel = "release"; -static constexpr const char* kIsKindOfClassSel = "isKindOfClass:"; - -// NSApplication lifecycle and management selectors -static constexpr const char* kSharedApplicationSel = "sharedApplication"; -static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; -static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; -static constexpr const char* kRunSel = "run"; -static constexpr const char* kStopSel = "stop:"; -static constexpr const char* kTerminateSel = "terminate:"; - -// NSApplicationDelegate lifecycle methods -static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; -static constexpr const char* kApplicationDidFinishLaunchingSel = "applicationDidFinishLaunching:"; -static constexpr const char* kApplicationWillTerminateSel = "applicationWillTerminate:"; -static constexpr const char* kApplicationDidBecomeActiveSel = "applicationDidBecomeActive:"; -static constexpr const char* kApplicationWillResignActiveSel = "applicationWillResignActive:"; - -// NSWindow creation, display, and management selectors -static constexpr const char* kInitWithContentRectSel = "initWithContentRect:styleMask:backing:defer:"; -static constexpr const char* kStringWithUTF8StringSel = "stringWithUTF8String:"; -static constexpr const char* kSetTitleSel = "setTitle:"; -static constexpr const char* kOrderFrontRegardlessSel = "orderFrontRegardless"; -static constexpr const char* kSetAcceptsMouseMovedEventsSel = "setAcceptsMouseMovedEvents:"; -static constexpr const char* kMakeFirstResponderSel = "makeFirstResponder:"; -static constexpr const char* kMakeKeyAndOrderFrontSel = "makeKeyAndOrderFront:"; -static constexpr const char* kMakeKeyWindowSel = "makeKeyWindow"; -static constexpr const char* kFrameSel = "frame"; -static constexpr const char* kSetFrameDisplaySel = "setFrame:display:"; -static constexpr const char* kSetFrameSel = "setFrame:"; -static constexpr const char* kStyleMaskSel = "styleMask"; -static constexpr const char* kToggleFullScreenSel = "toggleFullScreen:"; - -// 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:"; + // 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); -// 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:"; + } + } + + // 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; +} -// 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:"; +// 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) { -// 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"; + // KRespondsToSelector + BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); -// 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"; + 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; + } + + } + } +} -// 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"; +// 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); + // 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->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); -// NSOpenGL pixel format, view, and context management selectors -static constexpr const char* kInitWithAttributesSel = "initWithAttributes:"; -static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame:pixelFormat:"; -static constexpr const char* kSetContentViewSel = "setContentView:"; -static constexpr const char* kContentViewSel = "contentView"; -static constexpr const char* kBoundsSel = "bounds"; -static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru -static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:"; -static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:"; -static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:"; -static constexpr const char* kBackingScaleFactorSel = "backingScaleFactor"; -static constexpr const char* kOpenGLContextSel = "openGLContext"; -static constexpr const char* kMakeCurrentContextSel = "makeCurrentContext"; -static constexpr const char* kSetAutoresizingMaskSel = "setAutoresizingMask:"; -static constexpr const char* kFlushBufferSel = "flushBuffer"; -static constexpr const char* kDisplaySel = "display"; -static constexpr const char* kCGLContextObjSel = "CGLContextObj"; -static constexpr const char* kSetValuesSel = "setValues:forParameter:"; + } -// Extracting data from NSEvent objects selectors -static constexpr const char* kKeyCodeSel = "keyCode"; -static constexpr const char* kCharactersSel = "characters"; -static constexpr const char* kLocationInWindowSel = "locationInWindow"; -static constexpr const char* kButtonNumberSel = "buttonNumber"; -static constexpr const char* kClickCountSel = "clickCount"; -static constexpr const char* kModifierFlagsSel = "modifierFlags"; -static constexpr const char* kUTF8StringSel = "UTF8String"; +} -// NSLocale class and method names -static constexpr const char* kNSLocaleClass = "NSLocale"; -static constexpr const char* kCurrentLocaleSel = "currentLocale"; -static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; +// Handle left mouse up events +void view_mouseUp(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); -// 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; + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { -// Objective-C method type encoding constants -// Type encoding for methods returning BOOL with no parameters: "c@:" -static constexpr const char* kBoolMethodTypeEncoding = "c@:"; + 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); -// Type encoding for void methods with event/notification parameter: "v@:@" -static constexpr const char* kEventHandlerMethodTypeEncoding = "v@:@"; + } -// 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}}"; +} -// Type encoding for void methods with no parameters: "v@:" -static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// Handle mouse drag events +void view_mouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; -// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" -static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); -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; + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { - // 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; + 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); + } - // Application Screen management selectors - static SEL screenSel = nullptr; - static SEL unhideSel = nullptr; - static SEL hideSel = nullptr; - static SEL isHiddenSel = nullptr; +} - // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel = nullptr; - static SEL applicationDidFinishLaunchingSel = nullptr; - static SEL applicationWillTerminateSel = nullptr; - static SEL applicationDidBecomeActiveSel = nullptr; - static SEL applicationWillResignActiveSel = nullptr; - // 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; +// Handle mouse movement events +void view_mouseMoved(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - // 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; + 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); + } + + +} - // 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; +// 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); + } +} + +// 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); + } +} - // Mouse Tracking - static SEL addTrackingAreaSel = nullptr; - static SEL initWithRectSel = nullptr; +// Handle right mouse drag events +void view_rightMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - // Touch event selectors - static SEL setAcceptsTouchEventsSel = nullptr; - static SEL setWantsRestingTouchesSel = nullptr; - static SEL cgEventSel = nullptr; - static SEL touchesBeganSel = nullptr; - static SEL touchesMovedSel = nullptr; - static SEL touchesEndedSel = nullptr; - static SEL touchesCancelledSel = nullptr; - static SEL touchesMatchingPhaseSel = nullptr; - static SEL allObjectsSel = nullptr; - static SEL normalizedPositionSel = nullptr; - static SEL deviceSizeSel = nullptr; - static SEL identitySel = nullptr; - static SEL objectAtIndexSel = nullptr; - static SEL touchesCountSel = nullptr; + 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); + } + +} - // 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; +// 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); + } +} - // 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; +// 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); + } +} - // 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; +void view_otherMouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; - // 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; + 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); + } +} - // 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 +// 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); - // 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); + convertToContentViewCoordinates(location); - // 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); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { + gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} - // NSApplicationDelegate lifecycle methods - applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); - applicationDidFinishLaunchingSel = sel_registerName(kApplicationDidFinishLaunchingSel); - applicationWillTerminateSel = sel_registerName(kApplicationWillTerminateSel); - applicationDidBecomeActiveSel = sel_registerName(kApplicationDidBecomeActiveSel); - applicationWillResignActiveSel = sel_registerName(kApplicationWillResignActiveSel); +// 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); - // 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); + convertToContentViewCoordinates(location); - // 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); + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { + gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} + +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 - 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); + convertToContentViewCoordinates(location); - // 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); + 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 - 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); - // 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); +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 - currentLocaleSel = sel_registerName(kCurrentLocaleSel); - localeIdentifierSel = sel_registerName(kLocaleIdentifierSel); +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); } +} - // Ensures we only initialize selectors once (Thread-safe) - void ensureInitialized() { - static std::once_flag initialized; - std::call_once(initialized, [&]() { - initializeSelectors(); - }); +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); } } -// Single initialization function called once at startup -void initialize_macos_api() { - ObjectiveCSEL::initializeSelectors(); +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); + } } -// Concepts safety templates for Objective-C interactions -template -concept CallbackType = std::is_function_v>; // Checks if T is a function type +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) -template -concept ObjectiveC_Id = std::is_pointer_v; // Checks if T is a pointer type (Objective-C id types are pointers) +// 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); + } +} -// Autorelease pool management functions -extern "C" { - extern void* objc_autoreleasePoolPush(void); - extern void objc_autoreleasePoolPop(void* pool); +// 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 + } } -// Objective-C autorelease pools -class AutoreleasePool { -public: - AutoreleasePool() noexcept : pool_(objc_autoreleasePoolPush()) {} - ~AutoreleasePool() noexcept { objc_autoreleasePoolPop(pool_); } + +void view_updateTrackingAreas(id self, SEL _cmd) { + (void)self;(void)_cmd; + + // Create new tracking area + NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); - AutoreleasePool(const AutoreleasePool&) = delete; - AutoreleasePool& operator=(const AutoreleasePool&) = delete; - AutoreleasePool(AutoreleasePool&&) = delete; - AutoreleasePool& operator=(AutoreleasePool&&) = delete; + // 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); +} -private: - void* pool_; -}; +// 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 +} +// 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 +} -using NSPoint = CGPoint; -using NSInteger = long; -using NSUInteger = unsigned long; +// Determine if the view can become key view +id view_canBecomeKeyView(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; +} + +// Determine if the view needs the panel to become key +id view_needsPanelToBecomeKey(id self, SEL _cmd) { + (void)self;(void)_cmd; + return (id)(uintptr_t)YES; +} + +// Empty drawRect method - prevents automatic drawing on main thread +void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { + (void)self;(void)_cmd;(void)dirtyRect; +} + +// Empty reshape method - prevents automatic context reshaping on main thread +void view_reshape(id self, SEL _cmd) { + (void)self;(void)_cmd; +} + +// Empty update method - prevents automatic context updates on main thread +void view_update(id self, SEL _cmd) { + (void)self;(void)_cmd; +} + +// 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); -// 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) { -// Backward compatibility -static constexpr int NSViewWidthSizable = static_cast(NSAutoresizingMask::WidthSizable); -static constexpr int NSViewHeightSizable = static_cast(NSAutoresizingMask::HeightSizable); + // 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); -// 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 (!WindowDelegateClass) { + return NULL; + } -// Backward compatibility -static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); + // Window delegate methods + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResizeSel, (IMP)windowDidResize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowWillCloseSel, (IMP)windowWillClose, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidBecomeKeySel, (IMP)windowDidBecomeKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidResignKeySel, (IMP)windowDidResignKey, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidMiniaturizeSel, (IMP)windowDidMiniaturize, kEventHandlerMethodTypeEncoding); + class_addMethod(WindowDelegateClass, ObjectiveCSEL::windowDidDeminiaturizeSel, (IMP)windowDidDeminiaturize, kEventHandlerMethodTypeEncoding); + + // Register the class with the runtime + objc_registerClassPair(WindowDelegateClass); + } + + // Create an instance + id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); + delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); + + return delegate; +} + + // Forward declarations for C API functions used by internal helpers + extern "C" void window_setDelegate(Window* self, id delegate); -// Mouse Tracking const -static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; -static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; -static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; +extern "C" { + // Initialize the application + void application_initialize(Application* self) { -/* -* 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; + // 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); + } -// Handles NSApplication delegate methods and lifecycle events -struct ApplicationDelegate { - Class isa; // Objective-C class pointer (required) -}; + // Activate the application + void application_activate(Application* self) { + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); + } -// Custom NSOpenGLView subclass for handling input events -struct CustomOpenGLView { - Class isa; // Objective-C class pointer (required) - void* renderer; // C Pointer to associated renderer -}; + // Run the application event loop + void application_run(Application* self) { + ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); + } -// Handles NSWindow delegate methods and window events -struct WindowDelegate { - Class isa; // Objective-C class pointer (required) - void* renderer; // Pointer to associated renderer -}; + // Request to OS to gracefully terminate the application (RAII compatible) + void application_stop(Application* self) { + if (self && self->nsApp) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::stopSel, self->nsApp); + } + } -// Application management with member initialization -struct Application { - id nsApp{nullptr}; // NSApplication instance - id delegate{nullptr}; // Application delegate instance + // Destroy the application + void application_destroy(Application* self) { + if (self) { + delete self; + } + } - // Callback function pointers - std::function willFinishLaunchingCallback{nullptr}; - std::function didFinishLaunchingCallback{nullptr}; - std::function willTerminateCallback{nullptr}; - std::function didBecomeActiveCallback{nullptr}; - std::function willResignActiveCallback{nullptr}; + // 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; + } - 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 - // 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; -}; + // Ensure window has a delegate for window events + void ensureWindowDelegate(struct Window* self) { -// Window management with member initialization -struct Window { - id nsWindow{nullptr}; // NSWindow instance - id delegate{nullptr}; // Window delegate instance - OpenGLRenderer* renderer{nullptr}; // Associated OpenGL renderer - NSRect windowFrame{}; // Window frame rectangle - NSRect contentViewFrame{}; // Content view frame rectangle - const char* title{nullptr}; // Window title string + // If 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); + } + } + } - // 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}; + // Application constructor + Application* application_init() { - // 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}; + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - // 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}; + Application* app = new Application(); - 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}; + // Assign the method pointers + app->initialize = application_initialize; + app->activate = application_activate; + app->run = application_run; + app->destroy = application_destroy; + app->getSystemLocale = application_getSystemLocale; - 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}; -}; + return app; + } -// 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 + // Create the NSWindow instance + void window_create(Window* self, unsigned long styleMask) { + ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized - // 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}; + // 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); -// Modern image loading and pixel data extraction -struct ImageLoader { - imageloader_pixel_t* pixelData{nullptr}; // Raw pixel data (RGBA format) - int width{kMinValidDimension}; // Image width in pixels - int height{kMinValidDimension}; // Image height in pixels - int bytesPerPixel{kZeroBytes}; // Number of bytes per pixel (typically 4 for RGBA) - int bytesPerRow{kZeroRows}; // Number of bytes per row - BOOL hasAlpha{NO}; // Whether image has alpha channel - - // Method function pointers with nullptr initialization - BOOL (*loadFromFile) (struct ImageLoader* self, const char* filePath){nullptr}; - BOOL (*loadFromMemory) (struct ImageLoader* self, const uint8_t* data, size_t bytes); - void (*destroy) (struct ImageLoader* self){nullptr}; - unsigned char* (*getPixelData) (const struct ImageLoader* self){nullptr}; - void (*getImageInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel){nullptr}; - void (*getDetailedInfo) (const struct ImageLoader* self, int* width, int* height, int* bytesPerPixel, int* bytesPerRow, BOOL* hasAlpha){nullptr}; - BOOL (*isLoaded) (const struct ImageLoader* self){nullptr}; - BOOL (*getPixel) (const struct ImageLoader* self, int x, int y, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha){nullptr}; - unsigned int (*createOpenGLTexture)(const struct ImageLoader* self){nullptr}; -}; + // Set 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 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); + 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); + } + + // 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; - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { - gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + // 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) { - + + // 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); - // Convert from window coordinates to view coordinates (still logical points) - NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + // Get content bounds in points for clamping and Y-flip + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - // Scale from logical points to physical backing pixels - NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + // 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)); + + } + + } - // 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); + // 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); + } + } - // Flip Y coordinate from bottom-left to top-left - location.x = pixelLocation.x; - location.y = pixelBounds.height - pixelLocation.y; + void window_toggleFullScreen(Window* self) { + // Toggle fullscreen + ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); + } + + bool window_isFullScreen(Window* self) { + unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); + return (mask & NSWindowStyleMaskFullScreen); + } + + // Initialize OpenGL renderer + void opengl_initialize(OpenGLRenderer* self, Window* window) { + self->window = window; + window->renderer = self; + + // Get classes using const strings + Class NSOpenGLPixelFormatClass = objc_getClass(kNSOpenGLPixelFormatClass); + Class CustomOpenGLViewClass = createCustomOpenGLViewClass(); // Use our custom class + + unsigned int sampleBuffers = (self->MSAA_Samples > 0) ? 1 : 0; + std::vector preferredSamples = {32, 16, 8, 4, 2, 0}; + if(self->MSAA_Samples <= 0) { + // If no multisampling requested, only try 0 samples + preferredSamples.clear(); + preferredSamples.resize(1); + preferredSamples.push_back(0); + } + + for(unsigned int samples: preferredSamples) + { + // Create pixel format attributes array using enum values + const unsigned int attrs[] = { + NSOpenGLPFADoubleBuffer, // Enable double buffering + NSOpenGLPFADepthSize, 32, // 32-bit depth buffer + NSOpenGLPFAColorSize, 24, // 24-bit color + NSOpenGLPFAAccelerated, // Hardware acceleration + NSOpenGLAllowOfflineRenderers, // Allow offline renderers + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, // OpenGL 4.1 Core Profile + NSOpenGLPFAMultisample, samples, // 0x --> 32X Multisampling + NSOpenGLPFASampleBuffers, sampleBuffers, // Number of sample buffers + 0 // null terminator + }; + + // Create pixel format + self->pixelFormat = ((id(*)(id, SEL, const unsigned int*))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSOpenGLPixelFormatClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithAttributesSel, attrs); + // Check if pixel format was created successfully + if (self->pixelFormat) { + self->MSAA_Samples = samples; + break; // Successfully created pixel format + } } + + // Create custom OpenGL view with event handling + NSRect glViewFrame = {0.0, 0.0, window->contentViewFrame.width, window->contentViewFrame.height}; + self->glView = ((id(*)(id, SEL, NSRect, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(CustomOpenGLViewClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithFramePixelFormatSel, glViewFrame, self->pixelFormat); + + // Set autoresizing mask to make the view resize with the window + unsigned int autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + ((void(*)(id, SEL, unsigned int))objc_msgSend)(self->glView, ObjectiveCSEL::setAutoresizingMaskSel, autoresizingMask); + + // Enable touch events (NSTouch) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setAcceptsTouchEventsSel, YES); + + // Enable Listening for fingers that are resting but not moving (Ensure second touch point is detected as a touch not right click) + ((void(*)(id, SEL, BOOL))objc_msgSend)(self->glView, ObjectiveCSEL::setWantsRestingTouchesSel, YES); + + } - else - { - // Fallback: use default window height - location.y = kDefaultWindowHeight - location.y; + + // 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); } -} -// Mouse event data structure to hold common mouse event information -struct MouseEventData { - NSPoint location = {0.0, 0.0}; - NSUInteger modifierFlags = 0; - NSInteger buttonNumber = 0; -}; + // 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); -// 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; -} + return true; + } -// 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) + // Get the underlying OpenGL context + void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { + return (void*)self->glContext; + } -}; + // 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); + } + + // Get the underlying CGLContextObj (C API version) + void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { + return (void*)opengl_getCGLContextObj(self); + } + + // 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); + } -// Extract common mouse event data from NSEvent -StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + // 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); + } + } - 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); + // OpenGL renderer constructor + OpenGLRenderer* opengl_init() { - data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); - data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); + renderer->pixelFormat = NULL; + renderer->glView = NULL; + renderer->glContext = NULL; + renderer->window = NULL; - 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); + // 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; - data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); + return renderer; + } - double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); - double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + { + if(!image) return NO; - // 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)); + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = NULL; + } - // 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); + CGDataProviderRef provider = CGImageGetDataProvider(image); + CFDataRef rawData = CGDataProviderCopyData(provider); - // Convert to content view coordinates and flip Y coordinate - convertToContentViewCoordinates(data.nspLocation); - - return data; -} + //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning + //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning + //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning + + self->width = (int)CGImageGetWidth(image); + self->height = (int)CGImageGetHeight(image); + self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA + self->bytesPerRow = self->width * self->bytesPerPixel; + self->hasAlpha = YES; -//====================================================================// -// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + 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. + /* + 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; + } + } + */ -// NSTouchPhase bitmask values -enum NSTouchPhase : NSUInteger { - NSTouchPhaseBegan = 1 << 0, - NSTouchPhaseMoved = 1 << 1, - NSTouchPhaseStationary = 1 << 2, - NSTouchPhaseEnded = 1 << 3, - NSTouchPhaseCancelled = 1 << 4, - NSTouchPhaseAny = ULONG_MAX -}; + CFRelease(rawData); + CGImageRelease(image); -enum NSPressureStage : NSInteger { - NSPressureStageLight = 0, // Light press - NSPressureStageNormal = 1, // Normal press - NSPressureStageForce = 2 // Force click -}; + return YES; + } -enum class ForceTouchPressure : int { - Light = 25, // 0.25 - Light touch - Normal = 50, // 0.50 - Normal press - Force = 100, // 1.00 - Force click -}; + BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { + // Clear any existing data + if (self->pixelData) { + free(self->pixelData); + self->pixelData = 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); + CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); + CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); + CFRelease(pathStr); - // 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); + if(!url) + { + printf("loadImageFromFile: bad path '%s'\n", filePath); + return NO; } - } - 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) - -}; + CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); + CFRelease(url); -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(!src) + { + printf("loadImageFromFile: couldn't open '%s'\n", filePath); + return NO; + } + + CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); + CFRelease(src); -//Iterate through Touches, updates and fires the callback. -static void updateTouchData(id event, NSUInteger phase, - void (*callback)(uint32_t, double, double, double, double, void*), void* userData, bool bRemove = false) -{ - if (!callback) return; - - NSUInteger count = 0; - id touchArray = nullptr; - id touchCollection = ((id(*)(id, SEL, NSUInteger, id))objc_msgSend)( event, ObjectiveCSEL::touchesMatchingPhaseSel, phase, nil); - - // Edge case, there can be an touch event before a pressure event, therefore we need to check if the approximate touch size has been initialized - if(cgsApproxTouchSize.width == 0.0 || cgsApproxTouchSize.height == 0.0) - { - cgsApproxTouchSize = getApproxTrackPadTouchSize(NSPressureStageNormal); + return imageloader_decodeImage(self, image); } - - - if (touchCollection) - { - count = ((NSUInteger(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::touchesCountSel); - touchArray = ((id(*)(id, SEL))objc_msgSend)(touchCollection, ObjectiveCSEL::allObjectsSel); - - // Get current screen the window is on - id currentScreen = ((id(*)(id, SEL))objc_msgSend)(gptrWindowDelegate->nsWindow, ObjectiveCSEL::screenSel); - NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); - - for (NSUInteger i = 0; i < count; ++i) { - - id touch = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(touchArray, ObjectiveCSEL::objectAtIndexSel, i); - if (!touch) continue; - TouchEventData data; - - // normalizedPosition is NSPoint in [0,1] range on the trackpad surface - NSPoint normPos = ((NSPoint(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::normalizedPositionSel); - - // Convert normalized [0,1] → content-view pixel coordinates (flip Y to top-left origin) - data.nspLocation.x = normPos.x * screenFrame.width; - data.nspLocation.y = (1.0 - normPos.y) * screenFrame.height; - - // Get Device Size - data.cgDeviceSize = ((CGSize(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::deviceSizeSel); - // Approximate touch size based on pressure stage (macOS does not provide actual touch size) - data.nsTouchSize = cgsApproxTouchSize; - data.bIsActive = !bRemove; - // Assign a stable sequential uint32_t IDs same as iOS does - id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); - uintptr_t key = reinterpret_cast(identity); - - auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); - if (inserted) ++sNextTouchID; - uint32_t tid = it->second; - data.touchID = tid; - sTouchIDEvents.emplace(key, data); - callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + 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 + ); - // Clean up map entry once the touch has ended or been cancelled - if (bRemove) - { - sTouchIDEvents.erase(key); - sTouchIDMap.erase(key); - } - + 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); } - // 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); + // Get raw pixel data pointer + unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { + return (unsigned char*)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; } - - // Find the missing keys and fire the touchEndedCallback for them - for(auto& [key, data] : sTouchIDEvents) { + + // 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; + } - 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); + // 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; + + // 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; } - - // Remove the missing keys from the maps - for(auto& fKeys: vMissingKeys) { - sTouchIDEvents.erase(fKeys); - sTouchIDMap.erase(fKeys); + + // 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; } - - 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]] { + // Window constructor + Window* window_init(double x, double y, double width, double height) { + Window* window = (Window*)malloc(sizeof(Window)); - // 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) { + // 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; - // KRespondsToSelector - BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + // Set window content view frame + window->contentViewFrame.x = x; + window->contentViewFrame.y = y; + window->contentViewFrame.width = width; + window->contentViewFrame.height = height; - 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; - } - - } + // 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; + + // Event handling disabled by default + window->acceptsInputEvents = NO; + + // 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; + + // 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; } -} -// Handle left mouse down events -void view_mouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings + // Event callback setter functions + void window_setKeyDownCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyDownCallback = callback; + self->eventUserData = userData; + } + + void window_setKeyUpCallback(Window* self, void (*callback)(unsigned short, const char*, unsigned int, void*), void* userData) { + self->keyUpCallback = callback; + self->eventUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { + self->flagsChangedCallback = callback; + self->eventUserData = userData; + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseDownCallback = callback; + self->eventUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - MouseEventData data = extractMouseEventData(event); - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseUpCallback = callback; + self->eventUserData = userData; } - else if (nPointerEventType == 1 || nPointerEventType == 2) - { - // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) - gptrNSWindowEvents->bStylusInProximity = false; - StylusEventData data = extractStylusEventData(event, nPointerEventType); - gptrNSWindowEvents->stylusCallback( - data.nTabletDeviceID, - data.nspLocation.x, data.nspLocation.y, - data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, - true, false, true, - gptrNSWindowEvents->stylusUserData); + void window_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; + } -// Handle left mouse up events -void view_mouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDownCallback = callback; + self->eventUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseDraggedCallback = callback; + self->eventUserData = userData; + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->rightMouseUpCallback = callback; + self->eventUserData = userData; + } - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] { - gptrNSWindowEvents->mouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDownCallback = 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, - false, true, true, - gptrNSWindowEvents->stylusUserData); + void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseUpCallback = callback; + self->eventUserData = userData; } -} + void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->otherMouseDraggedCallback = callback; + self->eventUserData = userData; + } -// Handle mouse drag events -void view_mouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { + self->scrollWheelCallback = callback; + self->eventUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseEnteredCallback = callback; + self->eventUserData = userData; + } - // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? - if (nPointerEventType == 0) [[likely]] { + void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseExitedCallback = callback; + self->eventUserData = userData; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - MouseEventData data = extractMouseEventData(event); - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; } - else if (nPointerEventType == 1 || nPointerEventType == 2) + + void window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchMovedCallback = callback; + self->touchUserData = userData; + } + + void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchEndedCallback = callback; + self->touchUserData = userData; + } + + 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) { - // 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); - } + self->stylusCallback = callback; + self->stylusUserData = userData; + } -} + void window_enableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = YES; + gptrNSWindowEvents = self; // Set global reference for event handlers + } + } + void window_disableEventHandling(Window* self) { + if (self) { + self->acceptsInputEvents = NO; + if (gptrNSWindowEvents == self) { + gptrNSWindowEvents = NULL; + } + } + } -// Handle mouse movement events -void view_mouseMoved(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + // Window delegate callback setter functions + void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResizeCallback = callback; + self->windowDidResizeUserData = userData; - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - // 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); } - - -} -// Handle right mouse down events -void view_rightMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowWillCloseCallback = callback; + self->windowWillCloseUserData = userData; } -} -// Handle right mouse up events -void view_rightMouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] { - gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidBecomeKeyCallback = callback; + self->windowDidBecomeKeyUserData = userData; } -} -// Handle right mouse drag events -void view_rightMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { + self->windowDidResignKeyCallback = callback; + self->windowDidResignKeyUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void 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; + } + + // Application delegate callback setter functions + void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->willFinishLaunchingCallback = callback; + self->willFinishLaunchingUserData = userData; } - -} -// Handle other mouse button down events -void view_otherMouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; - - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { + self->didFinishLaunchingCallback = callback; + self->didFinishLaunchingUserData = userData; } -} -// 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); + void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { + self->willTerminateCallback = callback; + self->willTerminateUserData = userData; } -} -void view_otherMouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->didBecomeActiveCallback = callback; + self->didBecomeActiveUserData = userData; + } - CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); - uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); - if(nPointerEventType > 2) [[unlikely]] - return; // Ignore mouse events that are not mouse drag or tablet pointer events - - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { + self->willResignActiveCallback = callback; + self->willResignActiveUserData = userData; } -} -// Handle 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); +} // extern "C" - convertToContentViewCoordinates(location); +#endif - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->scrollWheelCallback) [[likely]] { - gptrNSWindowEvents->scrollWheelCallback(location.x, location.y, deltaX, deltaY, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} +#if !defined(OLC_USE_WXWIDGETS) -// 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); +#if OLC_HOST == OLC_HOST_NONE +namespace olc::host +{ + // Make OS Create a window frame, associated with olc::Window + bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + return true; + } - convertToContentViewCoordinates(location); + // Make OS Close a window frame, associated with olc::Window + bool Host_None::CloseWindowFrame(olc::Window* pWindow) + { + return true; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { - gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Make OS Update a window frame title, associated with olc::Window + bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) + { + return true; + } -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); + // Get OS-specific window descriptor(s) for given olc::Window + std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) + { + return {}; + } - convertToContentViewCoordinates(location); + // Wait for OS desktop refresh (for smooooth vsync) + bool Host_None::SyncWithDesktopComposite() + { + return true; + } - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { - gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Force the mouse position in pixels relative to window + bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return true; + } + + // Show or hide mouse cursor for given window + bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return true; + } + olc::KeyboardLayout Host_None::GetKeyboardLayout() const + { + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } -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); - } + // Called at very start of application + bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool Host_None::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + + systemActive = true; + + if(!OnSystemThreadStart()) + return pPrimaryPGE->OnPostContextEnd(); + + while(systemActive) + { + if(!OnSystemTick()) + { + StopSystem(); + } + } + + OnSystemThreadEnd(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + // Called to stop the host, and shutdown all resources + bool Host_None::StopSystem() + { + systemActive = false; + return true; + } + + // Called at start of system event loop + bool Host_None::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_None::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_None::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_None::OnApplicationEnd() + { + return true; + } } +#endif + +#if OLC_HOST == OLC_HOST_WINDOWS +namespace olc::host +{ + bool Host_Windows_WinAPI::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Windows_WinAPI::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Mark system as active + systemActive = true; + + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } + + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); -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); - } -} + // Blocking event loop on this thread - handles windows + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0 && systemActive) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } -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); - } -} + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); -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); - } -} + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); + } -//====================================================================// -// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + bool Host_Windows_WinAPI::StopSystem() + { + systemActive = false; + return true; + } -// 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); - } -} + bool Host_Windows_WinAPI::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } -// 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 - } -} + bool Host_Windows_WinAPI::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + bool Host_Windows_WinAPI::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } -void view_updateTrackingAreas(id self, SEL _cmd) { - (void)self;(void)_cmd; + bool Host_Windows_WinAPI::OnApplicationEnd() + { + return true; + } - // 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); -} -// 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 -} -// 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 -} + // ALL WINDOWS SPECIFIC GUBBINS BELOW HERE -// Determine if the view can become key view -id view_canBecomeKeyView(id self, SEL _cmd) { - (void)self;(void)_cmd; - return (id)(uintptr_t)YES; -} -// 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; -} + // Forward Declaration + static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); -// Empty drawRect method - prevents automatic drawing on main thread -void view_drawRect(id self, SEL _cmd, NSRect dirtyRect) { - (void)self;(void)_cmd;(void)dirtyRect; -} -// Empty reshape method - prevents automatic context reshaping on main thread -void view_reshape(id self, SEL _cmd) { - (void)self;(void)_cmd; -} + // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get + // access to the actual host instance, which can more conveninetly process + // the event across multiple window instances + static LRESULT CALLBACK WINAPI_EventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + // CreateWindowEx will fire a WM_CREATE event at the window, which we're + // not interested in, and this will occur before we've populated our + // linkage maps. We can detect for this condition here which means + // subsequent look-ups dont fail. NOTE: Do not assume WM_CREATE is the + // first message that is sent, its just one that we know is reliably sent. + if (uMsg == WM_CREATE) + { + // Associate the window's little blob of user memory with host iface + auto cfg = ((CREATESTRUCT*)lParam)->lpCreateParams; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cfg); + } + else + { + // If this returns a value, then the host iface should be responsible + // for handling the message. + auto host = (Host_Windows_WinAPI*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + if (host) + return host->OnWindowEvent(hWnd, uMsg, wParam, lParam); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } -// Empty update method - prevents automatic context updates on main thread -void view_update(id self, SEL _cmd) { - (void)self;(void)_cmd; -} + std::wstring Host_Windows_WinAPI::ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } -// 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); + Host_Windows_WinAPI::Host_Windows_WinAPI() + { + // Map Windows Defined VK_ Codes to olc::KeyCodes + mapKeys[0x00] = Key::NONE; + + // Windows doesn't define A-Z + mapKeys[0x41] = Key::A; + mapKeys[0x42] = Key::B; + mapKeys[0x43] = Key::C; + mapKeys[0x44] = Key::D; + mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; + mapKeys[0x47] = Key::G; + mapKeys[0x48] = Key::H; + mapKeys[0x49] = Key::I; + mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; + mapKeys[0x4C] = Key::L; + mapKeys[0x4D] = Key::M; + mapKeys[0x4E] = Key::N; + mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; + mapKeys[0x51] = Key::Q; + mapKeys[0x52] = Key::R; + mapKeys[0x53] = Key::S; + mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; + mapKeys[0x56] = Key::V; + mapKeys[0x57] = Key::W; + mapKeys[0x58] = Key::X; + mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; - // DrawRect method - class_addMethod(CustomViewClass, ObjectiveCSEL::drawRectSel, (IMP)view_drawRect, kDrawRectMethodTypeEncoding); + // Windows doesnt define numeric keys + mapKeys[0x30] = Key::K0; + mapKeys[0x31] = Key::K1; + mapKeys[0x32] = Key::K2; + mapKeys[0x33] = Key::K3; + mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; + mapKeys[0x36] = Key::K6; + mapKeys[0x37] = Key::K7; + mapKeys[0x38] = Key::K8; + mapKeys[0x39] = Key::K9; - // 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); + // Function Keys + mapKeys[VK_F1] = Key::F1; + mapKeys[VK_F2] = Key::F2; + mapKeys[VK_F3] = Key::F3; + mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; + mapKeys[VK_F6] = Key::F6; + mapKeys[VK_F7] = Key::F7; + mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; + mapKeys[VK_F10] = Key::F10; + mapKeys[VK_F11] = Key::F11; + mapKeys[VK_F12] = Key::F12; - // Register the class with the runtime - objc_registerClassPair(CustomViewClass); - - return CustomViewClass; -} + // Arrow Keys + mapKeys[VK_DOWN] = Key::DOWN; + mapKeys[VK_LEFT] = Key::LEFT; + mapKeys[VK_RIGHT] = Key::RIGHT; + mapKeys[VK_UP] = Key::UP; -// Forward declaration for C API function used by window event handlers -extern "C" void window_updateFrameFromOSX(Window* self); + // Other Keys + mapKeys[VK_BACK] = Key::BACK; + mapKeys[VK_ESCAPE] = Key::ESCAPE; + mapKeys[VK_RETURN] = Key::ENTER; + mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; + mapKeys[VK_TAB] = Key::TAB; + mapKeys[VK_DELETE] = Key::DEL; + mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; + mapKeys[VK_PRIOR] = Key::PGUP; + mapKeys[VK_NEXT] = Key::PGDN; + mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; + mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + mapKeys[VK_MENU] = Key::ALT; -// handle window resize events -void windowDidResize(id self, SEL _cmd, id notification) { - (void)self;(void)_cmd;(void)notification; + // Numpad + mapKeys[VK_NUMPAD0] = Key::NP0; + mapKeys[VK_NUMPAD1] = Key::NP1; + mapKeys[VK_NUMPAD2] = Key::NP2; + mapKeys[VK_NUMPAD3] = Key::NP3; + mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; + mapKeys[VK_NUMPAD6] = Key::NP6; + mapKeys[VK_NUMPAD7] = Key::NP7; + mapKeys[VK_NUMPAD8] = Key::NP8; + mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; + mapKeys[VK_ADD] = Key::NP_ADD; + mapKeys[VK_DIVIDE] = Key::NP_DIV; + mapKeys[VK_SUBTRACT] = Key::NP_SUB; + mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; - if (gptrWindowDelegate && gptrWindowDelegate->nsWindow) { + // OEM Keys + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + } - // Update frame data only - window_updateFrameFromOSX(gptrWindowDelegate); + bool Host_Windows_WinAPI::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + olc_IgnoreUnused(bFullScreen); - // Get the new content view size - double width, height; - window_getContentViewFrame(gptrWindowDelegate, nullptr, nullptr, &width, &height); + // The user created olc::Window object is the SSoT for what a window + // should look like, so get that sort of thing from there + olc::vi2d vWinPos = vWindowPos; + olc::vi2d vWinSize = vWindowSize; + + hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); - if (gptrWindowDelegate->windowDidResizeCallback) { - gptrWindowDelegate->windowDidResizeCallback(gptrWindowDelegate->windowDidResizeUserData); - } - - } -} + // Define WindowClass + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof(WNDCLASSEX); + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = hCursorDefault; + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = WINAPI_EventHandler; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0;// sizeof(this); // For static meta-info + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); + RegisterClassEx(&wc); -// 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); - } - }); -} + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); -// 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); - } -} + olc::vi2d vTopLeft = vWindowPos; -// 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); - } -} + if (bFullScreen || pPrimaryPGE->config.bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + POINT olc_pt = { vWinPos.x, vWinPos.y }; + HMONITOR hmon = MonitorFromPoint(olc_pt, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return false; + vWinSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } -// 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); - } -} -// 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); - } -} + // Keep client size as requested + RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); -// 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); + // +1 Hack to remove black bar between client and title bar for "perfect" window + // sizes anyway. This could be DPI related on later windows, but this makes it look + // tidier for "normal" applications + int width = rWndRect.right - rWndRect.left + 1; + int height = rWndRect.bottom - rWndRect.top; - // Register the class with the runtime - objc_registerClassPair(WindowDelegateClass); - - // Create an instance - id delegate = ((id(*)(Class, SEL))objc_msgSend)(WindowDelegateClass, ObjectiveCSEL::allocSel); - delegate = ((id(*)(id, SEL))objc_msgSend)(delegate, ObjectiveCSEL::initSel); - - // Store renderer reference in delegate - struct WindowDelegate* windowDelegate = (struct WindowDelegate*)delegate; - windowDelegate->renderer = renderer; - - return delegate; -} + // Create the actual OS window, return a handle + HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); -// Create a window delegate for general window events -id createWindowDelegateForWindow(void) { + // Update window size to match actual client area given. In situations where the window + // is clamped to the desktop, the client area may be smaller than requested. + RECT rClient; + GetClientRect(hWnd, &rClient); + pWindow->SetWindowSize({ rClient.right - rClient.left, rClient.bottom - rClient.top }); - // 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); + // Hide the close button if the user requested it, but only after styles are applied, + if (!pPrimaryPGE->config.bShowWindowCloseButton) + { + HMENU hMenu = GetSystemMenu(hWnd, FALSE); + DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND); + } - if (!WindowDelegateClass) { - return NULL; - } + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (dwStyle)); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); - // 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); + pWindow->olc_OnFocus(true); - // 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; -} + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); + //ShowWindow(hWnd, 1); + //UpdateWindow(hWnd); - // Forward declarations for C API functions used by internal helpers - extern "C" void window_setDelegate(Window* self, id delegate); + // Now... awkwardly, the above has already fired off some window messages + // and they arent necessarily in a consistent order. Whereas one might + // assume WM_CREATE would be the first, there are some others on more + // modern systems. This is awkward because we havent yet associated the + // source window with a long_ptr to this class, and therefore we can't + // call the appropriate event handler. -extern "C" { - // Initialize the application - void application_initialize(Application* self) { + // Store the link bewteen host resource and window + mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); + mapHWND2PTR.insert_or_assign(hWnd, pWindow); - // Initialize cached selectors for performance - initialize_macos_api(); - - // Set global application reference for delegate callbacks - gptrApplicationDelegate = self; - - // Get NSApplication - Class NSApplicationClass = objc_getClass(kNSApplicationClass); - self->nsApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, ObjectiveCSEL::sharedApplicationSel); - - // Set activation policy to regular - ((void(*)(id, SEL, NSInteger))objc_msgSend)(self->nsApp, ObjectiveCSEL::setActivationPolicySel, NSApplicationActivationPolicyRegular); - - // Create and set application delegate - self->delegate = createApplicationDelegate(); - ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::setDelegateSel, self->delegate); - } + - // Activate the application - void application_activate(Application* self) { - ((void(*)(id, SEL, BOOL))objc_msgSend)(self->nsApp, ObjectiveCSEL::activateIgnoringOtherAppsSel, YES); - } - // Run the application event loop - void application_run(Application* self) { - ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); - } + //DragAcceptFiles(olc_hWnd, true); - // 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); - } - } + return true; + } - // Destroy the application - void application_destroy(Application* self) { - if (self) { - delete self; - } - } + bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) + { + DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); + return false; + } - // 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; - } + bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) + { +#ifdef UNICODE + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); +#else + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); +#endif + return true; + } + std::vector Host_Windows_WinAPI::GetHostWindowDescriptor(olc::Window* pWindow) + { + return { mapUID2HWND[pWindow->GetUID()] }; + } - // Ensure window has a delegate for window events - void ensureWindowDelegate(struct Window* self) { + bool Host_Windows_WinAPI::SyncWithDesktopComposite() + { + return DwmFlush() == S_OK; + } - // 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); - } - } - } + bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + POINT pt; + pt.x = vPos.x; + pt.y = vPos.y; + ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); + SetCursorPos(pt.x, pt.y); + return true; + } - // Application constructor - Application* application_init() { + bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + hCursorNow = bVisible ? hCursorDefault : NULL; - Application* app = new Application(); + // Fire fake move event to update cursor visibility immediately + POINT p; + GetCursorPos(&p); + SetCursorPos(p.x, p.y + 1); + SetCursorPos(p.x, p.y); + return true; + } - // Assign the method pointers - app->initialize = application_initialize; - app->activate = application_activate; - app->run = application_run; - app->destroy = application_destroy; - app->getSystemLocale = application_getSystemLocale; + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); - return app; - } + if (bFullScreen) + { + // Maximise, make on top, remove border and titlebar + SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); + ShowWindow(hWnd, SW_MAXIMIZE); + } + else + { + // Restore original window style and position + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + // Get the style we should have based on the window config + DWORD dwStyle = ConvertPGE2WindowStyle(pWindow); - // Create the NSWindow instance - void window_create(Window* self, unsigned long styleMask) { - ObjectiveCSEL::ensureInitialized(); // Ensure selectors are initialized + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | dwStyle); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | dwExStyle); + ShowWindow(hWnd, SW_NORMAL); + } - // Get classes using const strings - Class NSWindowClass = objc_getClass(kNSWindowClass); - Class NSStringClass = objc_getClass(kNSStringClass); + UpdateWindow(hWnd); + SetForegroundWindow(hWnd); + SetFocus(hWnd); + SetActiveWindow(hWnd); + return true; + } - // 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); + DWORD Host_Windows_WinAPI::ConvertPGE2WindowStyle(const olc::Window* pWindow) + { + olc_IgnoreUnused(pWindow); - // 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); - } - } + DWORD dwStyle = WS_OVERLAPPED | WS_VISIBLE; // Default style for CreateWindowEx - window_getContentViewFrame(self, &self->contentViewFrame.x, &self->contentViewFrame.y, - &self->contentViewFrame.width, &self->contentViewFrame.height); - } + // Note for Microsoft: if you hide the border, it hides the title bar too, and via versa - // Show the window and set up event handling - void window_show(Window* self) { + // For fullscreen,borderless/noTitlebar we want to skip all the window furniture and just have a big ol canvas + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return dwStyle |= WS_POPUP; - // 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); - } + // If any max/min/close button(s) display the button menu + if (pPrimaryPGE->config.bShowWindowCloseButton || pPrimaryPGE->config.bShowWindowMaximiseButton || pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_SYSMENU; + if (pPrimaryPGE->config.bShowWindowTilebar) dwStyle |= WS_CAPTION; // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) dwStyle |= WS_BORDER; // Add a border + if (pPrimaryPGE->config.bResizeable) dwStyle |= WS_THICKFRAME; // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) dwStyle |= WS_MINIMIZEBOX; // Add Min Button + if (pPrimaryPGE->config.bShowWindowMaximiseButton) dwStyle |= WS_MAXIMIZEBOX; // Add Max Button + + // Note: Close button is handled after dwStlyes are applied - // Destroy the window - void window_destroy(Window* self) { - (void)self; - // NSWindow will be cleaned up by autorelease pool - } + return dwStyle; - // 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); - } - } - // 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; - - } + } + + LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + if (!mapHWND2PTR.contains(hWnd)) + return DefWindowProc(hWnd, uMsg, wParam, lParam);; - // Window title getter and setter - const char* window_getTitle(const Window* self) { - return self->title; - } + // Get target olc::Window + const auto& window = mapHWND2PTR.at(hWnd); - // Window title setter - void window_setTitle(Window* self, const char* title) { - self->title = title; + // Many WinAPI events are literally ancient these days, so need some interpretation + // to get to the useful data. - if (self->nsWindow) { - Class NSStringClass = objc_getClass(kNSStringClass); + switch (uMsg) + { + case WM_MOUSEMOVE: // Mouse has moved within a window + { + // Extract mouse X & Y + uint16_t x = uint16_t(lParam & 0xFFFF); + uint16_t y = uint16_t((lParam >> 16) & 0xFFFF); + int16_t ix = *(int16_t*)&x; + int16_t iy = *(int16_t*)&y; + // Tell window new mouse location + window->olc_OnMouseMove(olc::vi2d{ ix, iy }); + break; + } + - id titleString = ((id(*)(Class, SEL, const char*))objc_msgSend)( - NSStringClass, ObjectiveCSEL::stringWithUTF8StringSel, title); + // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_SIZE: + { + window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); + break; + } - ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setTitleSel, titleString); - } - } + case WM_MOUSEWHEEL: + { + window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; + } - // Refresh internal frame representation from actual NSWindow (OSX) - void window_updateFrameFromOSX(Window* self) { - self->windowFrame = ((NSRect(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::frameSel); - } + case WM_ACTIVATE: + { + window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); + return 0; + } - // 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; + case WM_MOUSEACTIVATE: + { + window->olc_OnFocus(true); + return MA_ACTIVATE; + } - } - - // 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; - } + case WM_SETFOCUS: + { + window->olc_OnFocus(true); + return 0; + } - // 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); - } - } - } + case WM_KILLFOCUS: + { + window->olc_OnFocus(false); + return 0; + } - // 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)); + case WM_KEYDOWN: + { + window->olc_OnFocus(true); + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } - 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; - } + case WM_KEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } - // Get window position (x, y) - void window_getWindowPosition(const struct Window* self, double* x, double* y) { - window_getWindowFrame(self, x, y, nullptr, nullptr); - } + case WM_SYSKEYDOWN: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); + } + break; + } - // Get Window size (width, height) - void window_getWindowSize(const Window* self, double* width, double* height) { - window_getWindowFrame(self, nullptr, nullptr, width, height); - } + case WM_SYSKEYUP: + { + if (mapKeys.contains(int32_t(wParam))) + { + window->olc_OnKeyPress(mapKeys[int32_t(wParam)], false); + } + break; + } - // 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; + case WM_LBUTTONDOWN: + { + window->olc_OnMouseButton(0, true); + break; + } + case WM_LBUTTONUP: + { + window->olc_OnMouseButton(0, false); + break; + } + case WM_RBUTTONDOWN: + { + window->olc_OnMouseButton(1, true); + break; + } + case WM_RBUTTONUP: + { + window->olc_OnMouseButton(1, false); + break; + } + case WM_MBUTTONDOWN: + { + window->olc_OnMouseButton(2, true); + break; + } + case WM_MBUTTONUP: + { + window->olc_OnMouseButton(2, false); + break; + } + case WM_XBUTTONDOWN: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, true); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, true); + } + + break; + } + case WM_XBUTTONUP: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, false); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, false); + } + + break; + } - // 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); - } - } - } + case WM_POINTERDOWN: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + true, + false, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + true, + false, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + + } - // 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); - } + break; + } - // 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); - } + case WM_POINTERUP: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + true, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + true, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + } - // 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)); - - } - - } + break; + } - // 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); - } - } + case WM_POINTERUPDATE: + { + POINTER_INPUT_TYPE pointerType; + if (GetPointerType(GET_POINTERID_WPARAM(wParam), &pointerType)) + { + if (pointerType == PT_TOUCH) + { + POINTER_TOUCH_INFO touchInfo; + if (GetPointerTouchInfo(GET_POINTERID_WPARAM(wParam), &touchInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + false, + olc::vf2d{ float(touchInfo.rcContact.right - touchInfo.rcContact.left), float(touchInfo.rcContact.bottom - touchInfo.rcContact.top) }); + } + } + else if (pointerType == PT_PEN) + { + if (IS_POINTER_INCONTACT_WPARAM(wParam)) + { + POINTER_PEN_INFO penInfo; + if (GetPointerPenInfo(GET_POINTERID_WPARAM(wParam), &penInfo)) + { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hWnd, &pt); + window->olc_OnTouch( + GET_POINTERID_WPARAM(wParam), + olc::vf2d{ float(pt.x), float(pt.y) }, + false, + false, + { 1,1 }, + true, + float(penInfo.pressure) / 1024.0f, + float(penInfo.rotation) / 360.0f * 2.0f * 3.14159265f, + { float(penInfo.tiltX) , float(penInfo.tiltY) } + ); + } + } + } + } + break; + } - void window_toggleFullScreen(Window* self) { - // Toggle fullscreen - ((void (*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::toggleFullScreenSel, nil); - } - bool window_isFullScreen(Window* self) { - unsigned long mask = ((unsigned long (*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::styleMaskSel); - return (mask & NSWindowStyleMaskFullScreen); - } + // case WM_DROPFILES: + // { + // // This is all eww... + // HDROP drop = (HDROP)wParam; + // + // uint32_t nFiles = DragQueryFile(drop, 0xFFFFFFFF, nullptr, 0); + // std::vector vFiles; + // for (uint32_t i = 0; i < nFiles; i++) + // { + // TCHAR dfbuffer[256]{}; + // uint32_t len = DragQueryFile(drop, i, nullptr, 0); + // DragQueryFile(drop, i, dfbuffer, 256); + //#ifdef UNICODE + //#ifdef __MINGW32__ + // char* buffer = new char[len + 1]; + // wcstombs(buffer, dfbuffer, len); + // buffer[len] = '\0'; + //#else + // int count = WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, NULL, 0, NULL, NULL); + // char* buffer = new char[count]; + // WideCharToMultiByte(CP_UTF8, 0, dfbuffer, -1, buffer, count, NULL, NULL); + //#endif + // vFiles.push_back(std::string(buffer)); + // delete[] buffer; + //#else + // vFiles.push_back(std::string(dfbuffer)); + //#endif + // } + // + // // Even more eww... + // POINT p; DragQueryPoint(drop, &p); + // ptrPGE->olc_DropFiles(p.x, p.y, vFiles); + // DragFinish(drop); + // return 0; + // } + // break; + // + // + case WM_CLOSE: + { + window->olc_OnWindowClose(); + break; + //return DefWindowProc(hWnd, uMsg, wParam, lParam); + } - // 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); - } + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(hCursorNow); + return TRUE; // Sigh ffs microsoft... + } - 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); - - - } + break; + } - // 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); - } + case WM_DESTROY: + PostQuitMessage(0); + DestroyWindow(hWnd); - // Implementation function - bool opengl_resetContextForSize(OpenGLRenderer* self, double width, double height) { - if (!self || !self->glContext || !self->glView) { - return false; - } - - // Make context current - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - - // Update the view frame - NSRect newFrame = {0.0, 0.0, width, height}; - ((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame); - - // Force context to reshape - ((void(*)(id, SEL))objc_msgSend)(self->glView, ObjectiveCSEL::reshapeSel); - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::updateSel); - - // Update viewport - glViewport(0, 0, (GLsizei)width, (GLsizei)height); - - // Clear buffers - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } - return true; - } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } - // Get the underlying OpenGL context - void* opengl_getOpenGLContext(const struct OpenGLRenderer* self) { - return (void*)self->glContext; - } + olc::KeyboardLayout Host_Windows_WinAPI::GetKeyboardLayout() const + { + HKL kbl = ::GetKeyboardLayout(0); + size_t highWord = ((size_t)kbl >> 16) & 0xFFFF; - // 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); - } - - // Get the underlying CGLContextObj (C API version) - void* opengl_getCGLContextObjPtr(struct OpenGLRenderer* self) { - return (void*)opengl_getCGLContextObj(self); - } + if (highWord == 0x00000409) // US + return olc::KeyboardLayout::QWERTY_US; + else if(highWord == 0x00000809) // UK + return olc::KeyboardLayout::QWERTY_UK; + else if(highWord == 0x00000407) // DE + return olc::KeyboardLayout::QWERTZ; + else if(highWord == 0x0000040C) // FR + return olc::KeyboardLayout::AZERTY; - // Make the OpenGL context current - void opengl_makeCurrentContext(struct OpenGLRenderer* self) { - if (self && self->glContext) { - ((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel); - } - } + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } - // 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); - } +}; +#endif - // 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); - } - } +#if OLC_HOST == OLC_HOST_MACOS +namespace olc::host { - // OpenGL renderer constructor - OpenGLRenderer* opengl_init() { - OpenGLRenderer* renderer = (OpenGLRenderer*)malloc(sizeof(OpenGLRenderer)); - renderer->pixelFormat = NULL; - renderer->glView = NULL; - renderer->glContext = NULL; - renderer->window = NULL; + // NSEventModifierFlags values + // constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 // Temp remove unused variable warning + constexpr unsigned int NSEventModifierFlagShift = 1 << 17; // 0x20000 + constexpr unsigned int NSEventModifierFlagControl = 1 << 18; // 0x40000 + // constexpr unsigned int NSEventModifierFlagOption = 1 << 19; // 0x80000 // Temp remove unused variable warning + constexpr unsigned int NSEventModifierFlagCommand = 1 << 20; // 0x100000 + // constexpr unsigned int NSEventModifierFlagNumericPad = 1 << 21; // 0x200000 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 // Temp remove unused variable warning + // constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 // Temp remove unused variable warning - // 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; + // enum for window appearance and behavior bit flags + enum class NSWindowStyleMask : uint16_t { + Titled = (1 << 0), // Window has a title bar + Closable = (1 << 1), // Window can be closed + Miniaturizable = (1 << 2), // Window can be minimized + Resizable = (1 << 3), // Window can be resized + UtilityWindow = (1 << 4), // Utility window style + DocModalWindow = (1 << 6), // Document-modal window + NonactivatingPanel = (1 << 7), // Non-activating panel + TexturedBackground = (1 << 8), // Textured background + HUDWindow = (1 << 13), // Heads-up display window + UnifiedTitleAndToolbar = (1 << 12), // Unified title and toolbar + FullScreen = (1 << 14), // Full-screen window + FullSizeContentView = (1 << 15) // Full-size content view + }; - return renderer; - } - static BOOL imageloader_decodeImage(struct ImageLoader* self, CGImageRef image) + Host_Apple_MacOS::Host_Apple_MacOS() { - if(!image) return NO; + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + mapKeys[0x00] = Key::NONE; + + // Map macOS key codes to olc::Key codes + mapKeys[0] = Key::A; + mapKeys[11] = Key::B; + mapKeys[8] = Key::C; + mapKeys[2] = Key::D; + mapKeys[14] = Key::E; + mapKeys[3] = Key::F; + mapKeys[5] = Key::G; + mapKeys[4] = Key::H; + mapKeys[34] = Key::I; + mapKeys[38] = Key::J; + mapKeys[40] = Key::K; + mapKeys[37] = Key::L; + mapKeys[46] = Key::M; + mapKeys[45] = Key::N; + mapKeys[31] = Key::O; + mapKeys[35] = Key::P; + mapKeys[12] = Key::Q; + mapKeys[15] = Key::R; + mapKeys[1] = Key::S; + mapKeys[17] = Key::T; + mapKeys[32] = Key::U; + mapKeys[9] = Key::V; + mapKeys[13] = Key::W; + mapKeys[7] = Key::X; + mapKeys[16] = Key::Y; + mapKeys[6] = Key::Z; - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } + // Numeric keys + mapKeys[29] = Key::K0; + mapKeys[18] = Key::K1; + mapKeys[19] = Key::K2; + mapKeys[20] = Key::K3; + mapKeys[21] = Key::K4; + mapKeys[23] = Key::K5; + mapKeys[22] = Key::K6; + mapKeys[26] = Key::K7; + mapKeys[28] = Key::K8; + mapKeys[25] = Key::K9; - CGDataProviderRef provider = CGImageGetDataProvider(image); - CFDataRef rawData = CGDataProviderCopyData(provider); + // Function Keys + mapKeys[122] = Key::F1; + mapKeys[120] = Key::F2; + mapKeys[99] = Key::F3; + mapKeys[118] = Key::F4; + mapKeys[96] = Key::F5; + mapKeys[97] = Key::F6; + mapKeys[98] = Key::F7; + mapKeys[100] = Key::F8; + mapKeys[101] = Key::F9; + mapKeys[109] = Key::F10; + mapKeys[103] = Key::F11; + mapKeys[111] = Key::F12; - //CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); // Temp remove unused variable warning - //CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); // Temp remove unused variable warning - //CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; // Temp remove unused variable warning - - self->width = (int)CGImageGetWidth(image); - self->height = (int)CGImageGetHeight(image); - self->bytesPerPixel = kRGBABytesPerPixel; // 4 bytes for RGBA - self->bytesPerRow = self->width * self->bytesPerPixel; - self->hasAlpha = YES; + // Arrow Keys + mapKeys[125] = Key::DOWN; + mapKeys[123] = Key::LEFT; + mapKeys[124] = Key::RIGHT; + mapKeys[126] = Key::UP; - 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. + // Other Keys + mapKeys[51] = Key::BACK; // Delete (Backspace) + mapKeys[53] = Key::ESCAPE; // Escape + mapKeys[36] = Key::ENTER; // Return + mapKeys[113] = Key::PAUSE; // F16 (often used as pause) + mapKeys[107] = Key::SCROLL; // F14 (scroll lock equivalent) + mapKeys[48] = Key::TAB; // Tab + mapKeys[117] = Key::DEL; // Forward Delete + mapKeys[115] = Key::HOME; // Home + mapKeys[119] = Key::END; // End + mapKeys[116] = Key::PGUP; // Page Up + mapKeys[121] = Key::PGDN; // Page Down + mapKeys[114] = Key::INS; // Help (Insert equivalent) + mapKeys[56] = Key::SHIFT; // Left Shift + mapKeys[59] = Key::CTRL; // Left Control + mapKeys[49] = Key::SPACE; // Space + mapKeys[57] = Key::CAPS_LOCK; // Caps Lock - /* - 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; - } - } - */ + // Numpad + mapKeys[82] = Key::NP0; + mapKeys[83] = Key::NP1; + mapKeys[84] = Key::NP2; + mapKeys[85] = Key::NP3; + mapKeys[86] = Key::NP4; + mapKeys[87] = Key::NP5; + mapKeys[88] = Key::NP6; + mapKeys[89] = Key::NP7; + mapKeys[91] = Key::NP8; + mapKeys[92] = Key::NP9; + mapKeys[67] = Key::NP_MUL; // Numpad * + mapKeys[69] = Key::NP_ADD; // Numpad + + mapKeys[75] = Key::NP_DIV; // Numpad / + mapKeys[78] = Key::NP_SUB; // Numpad - + mapKeys[65] = Key::NP_DECIMAL; // Numpad . - CFRelease(rawData); - CGImageRelease(image); + // Symbol Keys (OEM equivalents) + mapKeys[41] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[44] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[50] = Key::OEM_3; // On US and UK keyboards this is the '`~' key (Grave accent `) + mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) + mapKeys[24] = Key::EQUALS; // Equal sign = + mapKeys[43] = Key::COMMA; // Comma , + mapKeys[27] = Key::MINUS; // Minus - + mapKeys[47] = Key::PERIOD; // Period . - return YES; } - BOOL imageloader_loadFromFile(struct ImageLoader* self, const char* filePath) { - // Clear any existing data - if (self->pixelData) { - free(self->pixelData); - self->pixelData = NULL; - } - - CFStringRef pathStr = CFStringCreateWithCString(nullptr, filePath, kCFStringEncodingUTF8); - CFURLRef url = CFURLCreateWithFileSystemPath(nullptr, pathStr, kCFURLPOSIXPathStyle, false); - CFRelease(pathStr); - - if(!url) - { - printf("loadImageFromFile: bad path '%s'\n", filePath); - return NO; - } - CGImageSourceRef src = CGImageSourceCreateWithURL(url, nullptr); - CFRelease(url); + bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ + olc_IgnoreUnused(bFullScreen); + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); - if(!src) - { - printf("loadImageFromFile: couldn't open '%s'\n", filePath); - return NO; - } + frameBounds.x = 0.0; + frameBounds.y = 0.0; + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); + return true; + } - return imageloader_decodeImage(self, image); + bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ + pWindow->olc_OnWindowClose(); + return true; } - 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 - ); + bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ + if (!pMacOSWindow) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + }); + return true; + } - if (!cfData) - { - printf("loadImageFromMemory: CFData creation failed\n"); - return NO; - } + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + olc_IgnoreUnused(pWindow); + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); - CGImageSourceRef src = CGImageSourceCreateWithData(cfData, nullptr); - CFRelease(cfData); + return vMacOSWindowDescriptors; + + } - if (!src) + + bool Host_Apple_MacOS::SyncWithDesktopComposite() + { + /* + core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, + the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, + set to false and toggle it on first call, so that vSync is only enabled once + */ + + if(!enableVSync) { - printf("loadImageFromMemory: src creation failed\n"); - return NO; + pMacOSOpenGLRenderer->enableVsync(); + enableVSync = true; } - - CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, nullptr); - CFRelease(src); - return imageloader_decodeImage(self, image); + return enableVSync; } - // Get raw pixel data pointer - unsigned char* imageloader_getPixelData(const struct ImageLoader* self) { - return (unsigned char*)self->pixelData; + bool Host_Apple_MacOS::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorPosition(vPos.x, vPos.y); + }); + return false; } - // 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; + bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; } - // 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; - } + bool Host_Apple_MacOS::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + olc_IgnoreUnused(pWindow); + // if we're already in the specified state, return early + if(pMacOSWindow->isFullScreen() == bFullScreen) + return true; - // Check if image is loaded - BOOL imageloader_isLoaded(const struct ImageLoader* self) { - return (self->pixelData != NULL && self->width > kMinValidDimension && self->height > kMinValidDimension); + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->toggleFullScreen(); + }); + return true; } - // 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; - } + uint16_t Host_Apple_MacOS::ConvertPGE2WindowStyle() + { + uint16_t nsStyle = 0; - // 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; + // Note for MacOS: You cannot fully hide both the title bar and border, therefore we return titled when both are disabled, which is the closest we can get to a borderless window + if (!pPrimaryPGE->config.bShowWindowBorder || !pPrimaryPGE->config.bShowWindowTilebar) return static_cast(NSWindowStyleMask::Titled); - // 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 + // On MacOS, the maximize button is tied to the resizable style, therefore there is no need to implemenent a separate bShowWindowMaximiseButton config, + // For MacOS you can only disable the buttons, you can't hide them + if (pPrimaryPGE->config.bFullScreen) nsStyle |= static_cast(NSWindowStyleMask::FullSizeContentView); // Fullscreen window + if (pPrimaryPGE->config.bShowWindowTilebar) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a title bar + if (pPrimaryPGE->config.bShowWindowBorder) nsStyle |= static_cast(NSWindowStyleMask::Titled); // Add a border + if (pPrimaryPGE->config.bResizeable) nsStyle |= static_cast(NSWindowStyleMask::Resizable); // Enable resizing + if (pPrimaryPGE->config.bShowWindowMinimiseButton) nsStyle |= static_cast(NSWindowStyleMask::Miniaturizable); // Add Min Button + if (pPrimaryPGE->config.bShowWindowCloseButton) nsStyle |= static_cast(NSWindowStyleMask::Closable); // Add Close Button + + return nsStyle; - 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; + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; } - - // ImageLoader constructor - struct ImageLoader* imageloader_init() { - struct ImageLoader* loader = (struct ImageLoader*)malloc(sizeof(struct ImageLoader)); + + bool Host_Apple_MacOS::StartSystem(){ + + // Create MacOS Application instance + pMacApplication = std::make_unique(); + + // Set up application delegate event handlers + MacApplicationEventsHandler(); + + // Initialize and activate application first + pMacApplication->initialize(); + pMacApplication->activate(); - // Initialize properties - loader->pixelData = NULL; - loader->width = kZeroWidth; - loader->height = kZeroHeight; - loader->bytesPerPixel = kZeroBytes; - loader->bytesPerRow = kZeroRows; - loader->hasAlpha = NO; + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); - // 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; + // Initialize the MacOS Window + pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); + pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); + pMacOSWindow->setContentViewPosition(0, 0); - return loader; - } - - // Window constructor - Window* window_init(double x, double y, double width, double height) { - Window* window = (Window*)malloc(sizeof(Window)); + // Set up window event handlers + MacWindowEventsHandler(); - // Initialize window properties - window->nsWindow = NULL; - window->delegate = NULL; - window->title = NULL; + // Create Input Event handler + pMacOSEventHandler = std::make_unique(*pMacOSWindow); + + // Setup Event handlers + MacEventsHandler(); - // 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; + // Create the window + unsigned long styleMask = ConvertPGE2WindowStyle(); + pMacOSWindow->show(styleMask); + pMacOSEventHandler->enable(); + + //--- Start up our engine threading system ---- + // Start the PGE context on the main thread + // Mark system as active + systemActive = true; - // Event handling disabled by default - window->acceptsInputEvents = NO; + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } - // 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; + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } - // 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; + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - return window; - } + + // Start the main event loop (this will block) + pMacApplication->run(); + + // Once the application run loop ends, join the system thread + if(threadSystem.joinable()) + threadSystem.join(); - // 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; - } + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); - 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; } - void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDownCallback = callback; - self->eventUserData = userData; - } + bool Host_Apple_MacOS::StopSystem() + { + dispatch_sync(dispatch_get_main_queue(), ^{ + // clean up and close application + if (pMacOSOpenGLRenderer) + { + pMacOSOpenGLRenderer->destoryContext(); + pMacOSOpenGLRenderer = nullptr; + } + if (pMacOSWindow) + { + pMacOSWindow->destoryWindow(); + pMacOSWindow = nullptr; + } + if (pMacApplication) + { + pMacApplication->stop(); + } - void window_setMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseUpCallback = callback; - self->eventUserData = userData; + }); + systemActive = false; + return true; } - void window_setMouseMovedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseMovedCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::OnSystemThreadStart() + { + // Hold back threading until application is fully initialized + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextStart(); } - void window_setMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseDraggedCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::OnSystemTick() + { + // Execute any pending main thread tasks + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextTick(); } - void window_setRightMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDownCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); } - void window_setRightMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseDraggedCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::OnApplicationEnd() + { + return true; } - void window_setRightMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->rightMouseUpCallback = callback; - self->eventUserData = userData; - } - void window_setOtherMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDownCallback = callback; - self->eventUserData = userData; +//-- OS Window Event Handling ----- + + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const + { + // Get system locale from MacOS Application + // We need to wait until the application has launched to get the keyboard layout + // Therefore this function is called again from setDidFinishLaunchingCallback event + if (pMacApplication) + { + std::string locale = pMacApplication->getSystemLocale(); + if (locale == "en_GB") + { + return olc::KeyboardLayout::QWERTY_UK; + } + else if (locale == "en_US") + { + return olc::KeyboardLayout::QWERTY_US; + } + else if (locale == "fr_FR") + { + return olc::KeyboardLayout::AZERTY; + } + else if (locale == "de_DE") + { + return olc::KeyboardLayout::QWERTZ; + } + } + // Default to QWERTY if unknown + return olc::KeyboardLayout::QWERTY_UK; + } + +// ------- Priavate Main Thread Task Handling for MacOS Host ------- + + bool Host_Apple_MacOS::CreateCGLContextObj() + { + // This method should only be called on the PGE thread, use AddPendingMainThreadTask(CREATE_OPENGL_RENDERER); to queue it if needed + if(pMacOSOpenGLRenderer == nullptr) + { + vMacOSWindowDescriptors.clear(); // ensure we are starting fresh + pMacOSOpenGLRenderer = std::make_shared(); + + dispatch_sync(dispatch_get_main_queue(), ^{ + // Edge case for when the window is auto resize due to MacOS clamping to screen size + pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + + pMacOSOpenGLRenderer->attachToWindow(*pMacOSWindow); + pMacOSOpenGLRenderer->setupContext(); + }); + + pMacGLConextObj = pMacOSOpenGLRenderer->getCGLContextObj(); + + pMacOSOpenGLRenderer->setVsync(false); + + vMacOSWindowDescriptors.push_back(pMacGLConextObj); // Pointer to CGLContextObj + vMacOSWindowDescriptors.push_back(&bSkipFrame); // Pointer to skip frame flag + + // Set up OpenGL renderer for visual feedback + pMacOSOpenGLRenderer->makeCurrentContext(); + + // Finally we set full screen if needed to ensure all out OpenGL setup is done before toggling full screen, + if(pPrimaryPGE->config.bFullScreen){ + SetFullScreen(pPGEwindow, true); + } + + } + + return true; } - void window_setOtherMouseUpCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseUpCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::ExecutePendingMainThreadTasks() + { + // 1: Check if main thread wants us to wait + std::unique_lock lock(pgeThreadPendingTasksMutex); + + if (isPGEThreadResetting.load()) { + + // 2. PGE Thread signals it's waiting + { + std::lock_guard mainLock(mainThreadPendingTasksMutex); + isMainThreadResetting = true; // Signal to main thread we're waiting + } + mainThreadResetCondition.notify_all(); // Wake up main thread + + // Note: MainThreadTasks(); will be called by the main thread to process tasks + + // 3. PGE Thread waits for main thread to finish + pgeThreadResetCondition.wait(lock, [this] { + return !isPGEThreadResetting.load(); + }); + + //4: return true indicating we processed tasks + return true; + } + else + { + // No pending tasks, just return + return false; + } } - void window_setOtherMouseDraggedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->otherMouseDraggedCallback = callback; - self->eventUserData = userData; + bool Host_Apple_MacOS::AddPendingMainThreadTask(MAINTASKS task) + { + // NOTE: Note: You should only add tasks that require main thread execution + vPendingMainThreadTasks.push_back(task); + MainThreadTasks(); + + return true; + } + + bool Host_Apple_MacOS::MainThreadTasks() + { + bool res = false; + if(vPendingMainThreadTasks.empty()) + return res; // edge case + + // 1. Main Thread locks PGE Thread + { + std::lock_guard lock(pgeThreadPendingTasksMutex); + isPGEThreadResetting = true; // Signal PGE to stop + } + pgeThreadResetCondition.notify_all(); // Wake up PGE thread to check flag + + // 2. Main Thread waits for PGE Thread to acknowledge and wait + std::unique_lock lock(mainThreadPendingTasksMutex); + mainThreadResetCondition.wait(lock, [this] { + return isMainThreadResetting.load(); // Wait until PGE signals it's waiting + }); + + // Process any pending main thread tasks + for (const auto& task : vPendingMainThreadTasks) + { + switch (task) + { + case CREATE_OPENGL_RENDERER: + { + // In this case, the PGE will be waiting for main thread to singal, so the ContextOBJ can be created + res = false; // No need to skip frame + break; + } + case RESIZE_WINDOW: + { + // Resize window on main thread + pMacOSWindow->getContentViewSize(frameBounds.width, frameBounds.height); + pPGEwindow->olc_OnWindowSize({static_cast(frameBounds.width), static_cast(frameBounds.height)}); + pMacOSOpenGLRenderer->resetContextSize(frameBounds.width, frameBounds.height); + res = true; // Skip frame to allow resize to take effect + break; + } + case DEMINIMIZE_WINDOW: + case BECOME_ACTIVE: + { + pPGEwindow->olc_OnFocus(true); + break; + } + case MINIMIZE_WINDOW: + case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnFocus(false); + break; + } + case NONE: + default: + { + res = false; + break; + } + + } + } + vPendingMainThreadTasks.clear(); + + // 4. Main Thread unlocks PGE Thread + { + std::lock_guard lock(pgeThreadPendingTasksMutex); + isPGEThreadResetting = false; // Release PGE thread + isMainThreadResetting = false; // Reset main thread flag + } + pgeThreadResetCondition.notify_all(); // Wake up PGE thread + return res; } - void window_setScrollWheelCallback(Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData) { - self->scrollWheelCallback = callback; - self->eventUserData = userData; +//------ 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 window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseEnteredCallback = callback; - self->eventUserData = userData; + 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; + } - void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { - self->mouseExitedCallback = callback; - self->eventUserData = userData; - } + 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; + } + } - void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchBeganCallback = callback; - self->touchUserData = userData; + // 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 window_setTouchMovedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchMovedCallback = callback; - self->touchUserData = userData; - } + void Host_Apple_MacOS::MacEventsHandler() + { + // General MacOS key event handling code here + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes - void window_setTouchEndedCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchEndedCallback = callback; - self->touchUserData = userData; - } + // Set up keyboard event handlers + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + KeyboardEventHandler(event, true); + }); - void window_setTouchCancelledCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { - self->touchCancelledCallback = callback; - self->touchUserData = userData; - } + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + KeyboardEventHandler(event, false); + }); - 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; - } + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { - void window_enableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = YES; - gptrNSWindowEvents = self; // Set global reference for event handlers - } - } + 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); + } - void window_disableEventHandling(Window* self) { - if (self) { - self->acceptsInputEvents = NO; - if (gptrNSWindowEvents == self) { - gptrNSWindowEvents = NULL; + 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); } - } - } - // Window delegate callback setter functions - void window_setWindowDidResizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResizeCallback = callback; - self->windowDidResizeUserData = userData; + // 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)}); + }); - void window_setWindowWillCloseCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowWillCloseCallback = callback; - self->windowWillCloseUserData = userData; - } + pMacOSEventHandler->onRightMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - void window_setWindowDidBecomeKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidBecomeKeyCallback = callback; - self->windowDidBecomeKeyUserData = userData; - } + pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); - void window_setWindowDidResignKeyCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidResignKeyCallback = callback; - self->windowDidResignKeyUserData = userData; - } + 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); + + }); - void window_setWindowDidMiniaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidMiniaturizeCallback = callback; - self->windowDidMiniaturizeUserData = userData; - } + pMacOSEventHandler->onOtherMouseDown([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); + }); - void window_setWindowDidDeminiaturizeCallback(Window* self, void (*callback)(void*), void* userData) { - self->windowDidDeminiaturizeCallback = callback; - self->windowDidDeminiaturizeUserData = userData; - } + pMacOSEventHandler->onOtherMouseUp([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseButton(event.buttonNumber, false); + }); - // Application delegate callback setter functions - void application_setWillFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->willFinishLaunchingCallback = callback; - self->willFinishLaunchingUserData = userData; - } + pMacOSEventHandler->onOtherMouseDragged([&](const olc::apis::macos::MouseEvent& event) { + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + }); - void application_setDidFinishLaunchingCallback(Application* self, void (*callback)(void*), void* userData) { - self->didFinishLaunchingCallback = callback; - self->didFinishLaunchingUserData = userData; - } + 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)); + }); - void application_setWillTerminateCallback(Application* self, void (*callback)(void*), void* userData) { - self->willTerminateCallback = callback; - self->willTerminateUserData = userData; - } + // 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)}); + }); - void application_setDidBecomeActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->didBecomeActiveCallback = callback; - self->didBecomeActiveUserData = userData; - } + 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)}); + }); - void application_setWillResignActiveCallback(Application* self, void (*callback)(void*), void* userData) { - self->willResignActiveCallback = callback; - self->willResignActiveUserData = userData; - } + 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)}); + }); -} // extern "C" + 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} + ); + }); + + } +} #endif #if OLC_HOST == OLC_HOST_LINUX_X11