diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 00000000..1d5c073a --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,31 @@ +# License (OLC-3) + +Copyright 2018-2026 OneLoneCoder.com + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 643a3d9e..c7363ef7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,113 @@ +

+ +

+ +# Pre-Launch BETA Testing +`olcPixelGameEngine v3.00 Beta B` is currently in a Beta testing phase, and has been made available to those that may want to experiment during olc::CodeJam2026. As such, this repo does not represent the final form of olcPixelGameEngine3, and presumably feedback from the Jam may change a few things prior to the official launch later this year. + +# olcPixelGameEngine 3 (olcPGE3) +This is the official distribution of olcPixelGameEngine3, the successor to the popular [olcPixelGameEngine2](https://github.com/OneLoneCoder/olcPixelGameEngine/tree/master). + +Purposes + * Game Development + * Tool Development + * Visualisation + * Real-Time Interactive Applications + * Rapid Idea Prototyping + * Kiosks and Browsers + * Cross Platform Development + * Cohesive User Experience + * Algorithm Study + * Classroom Exercises + * Having Fun! + +Easy To Use + * Single Header File (as always) + * CMAKE optional but provided + * Entire project source for the curious + +Capabilities + * 2D Rendering + * 3D Rendering + * Batch Rendering + * Custom GPU Shaders + * Vector / Matrix Types + * Polygon Construction + * 2D Animation Package + * 2D Camera Control + * 2D Geometry Analysis & Interrogation + * 2D Quad-Tree Data Structure + * Flexible Game Creation + +Tested On Platforms + * Windows + * Linux X11/Wayland + * MacOS + * Emscripten + * Android + +Input Features + * Regional Keyboard + * Mouse + * Touch + * Stylus / Pen + +Extendable + * Window Hooks + * System Hooks + + +# Err.. I have questions! +* Is it compatible with olcPGE2? I've done loads of stuff in that. + * No. PGE3 has built upon the years of development and community contributions and has outgrown the interfaces that olcPGE2 provided. They are however very similar, so if you have familiarity with PGE2, you should easily get going with PGE3. +* Are there ports to Rust, C# or Java? + * No. Over the past years PGE2 was ported to other environments only by fantatsic community contributors. The official release of PGE3 targets C++20. +* Have you used AI to make this? + * No. Well, not knowingly or intentionally. This project has been developed for fun by enthusiasts who enjoy coding as a rewarding challenge. That said, no guarantees can be made that an IDE hasn't presented a useful auto-complete, or a contributer has used AI in pursuit of understanding a bug. The philosophy of OneLoneCoder is coding for fun, not prompt engineering. +* Can I use olcPixelGameEngine3 commercially? + * Yes. The licence below simply requires that your end users know they are using a product that uses OneLoneCoder's olcPixelGameEngine. +* Can I contribute? + * Yes. A primary goal of PGE3 was to be more open as a project. However, anonymous and/or AI created PRs will be ignored until you have introduced yourself and your ideas to the development team on our Discord server. + + +# Licence (OLC-3) +Copyright 2018 - 2026 OneLoneCoder.com + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Examples +As part of the development and testing process we have many examples that illustrate individual features. You can find interactive examples hosted here. + # Building +By far the easiest way to use olcPixelGameEngine3 is to simply include the header file provided in the root of this repository - `olcPixelGameEngine3.h`. As much as possible it will self configure for your chosen environment. On Microsoft platforms, using Visual Studio is our recommended way forward. For all other platforms, a CMake instruction is available. + ## CMake Configure with: @@ -46,6 +154,7 @@ emcc olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads.html -sASYNCIFY -sALLOW_MEMORY ``` # Using STB Image +Image loading is complex. For all the supported platforms we have ensured that as a minimum you can load a ".png" file as an asset. For a wider variety of formats its much better to use existing libraries, and we have decided to support "stb_image.h" which is pretty much the go-to image loader in C++. olcPGE3 can be instructed to use "stb_image" by declaring `OLC_USE_STB_IMAGE=1` before including the header file, or better yet, as a compiler predefined directive. ## Get the headers Aquire [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) and [stb_image_write.h](https://github.com/nothings/stb/blob/master/stb_image_write.h). Place them in the root directory of the repo. diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index d7b7a70b..598c8157 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -180,6 +180,7 @@ + true true @@ -316,6 +317,7 @@ + true @@ -350,6 +352,13 @@ false + + + + + + + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 58f63f12..2323331a 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -46,6 +46,9 @@ {b5b02d3b-7b70-4ef1-a2e2-516cbaa9da51} + + {76f094ac-5176-4402-9395-6187374c5d64} + @@ -174,6 +177,9 @@ Header Files + + Utilities + @@ -248,5 +254,21 @@ Source Files + + Hardware + + + + + Extras + + + Extras + + + + + Extras + \ No newline at end of file diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index d809d4dc..2e2a6919 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -22,8 +22,8 @@ true true - true - true + false + false true @@ -43,11 +43,15 @@ true true + + true + true + true true - false - false + true + true true @@ -163,6 +167,10 @@ true true + + true + true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 6ebcfc8f..1da31eaa 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -93,6 +93,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 40188d23..cdad635b 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -2,10 +2,12 @@ #include #include #include +#include //! START IMPLEMENTATION // 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"; @@ -18,7 +20,7 @@ static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLV static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; // Application Screen management selectors -static constexpr const char* kNSScreenClass = "NSScreen"; +//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"; @@ -95,6 +97,39 @@ static constexpr const char* kTrackingAreaClass = "NSTrackingAre static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; +// Copied a lot of this code from iOS, will need to be tested +// NSResponder touch event method selectors +static constexpr const char* kSetAcceptsTouchEventsSel = "setAcceptsTouchEvents:"; +static constexpr const char* kSetWantsRestingTouchesSel = "setWantsRestingTouches:"; +static constexpr const char* kCGEventSel = "CGEvent"; +static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; + +// NSTouch / NSSet data extraction selectors +static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; +static constexpr const char* kAllObjectsSel = "allObjects"; +static constexpr const char* kNormalizedPositionSel = "normalizedPosition"; +static constexpr const char* kDeviceSizeSel = "deviceSize"; +static constexpr const char* kIdentitySel = "identity"; +static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; +static constexpr const char* kTouchCountSel = "count"; + +// NSResponder tablet / stylus event method selectors +static constexpr const char* kTabletPointSel = "tabletPoint:"; +static constexpr const char* kTabletProximitySel = "tabletProximity:"; +static constexpr const char* kPressureChangeSel = "pressureChangeWithEvent:"; +static constexpr const char* kStageSel = "stage"; + +// 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"; + // Managing first responder status and keyboard focus selectors static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; @@ -111,7 +146,11 @@ static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame 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:"; @@ -139,7 +178,7 @@ static constexpr const char* kLocaleIdentifierSel = "localeIdentif 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; +// 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; @@ -149,7 +188,7 @@ 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; +// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning bool bAllowHideCursor = true; bool bHideCursor = false; @@ -166,6 +205,9 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; + namespace ObjectiveCSEL { // Application memory management selectors @@ -244,6 +286,34 @@ namespace ObjectiveCSEL { static SEL addTrackingAreaSel = nullptr; static SEL initWithRectSel = nullptr; + // 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; + // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; static SEL becomeFirstResponderSel = nullptr; @@ -259,6 +329,9 @@ namespace ObjectiveCSEL { 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; @@ -267,6 +340,7 @@ namespace ObjectiveCSEL { static SEL displaySel = nullptr; static SEL CGLContextObjSel = nullptr; static SEL setValuesSel = nullptr; + static SEL backingScaleFactorSel = nullptr; // Extracting data from NSEvent objects selectors static SEL keyCodeSel = nullptr; @@ -280,8 +354,8 @@ namespace ObjectiveCSEL { // NSLocale selectors static SEL currentLocaleSel = nullptr; static SEL localeIdentifierSel = nullptr; - static SEL currentInputContextSel = nullptr; - static SEL localizedNameSel = nullptr; + // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning + // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning // Initialize all selectors - called once at startup void initializeSelectors() { @@ -361,6 +435,34 @@ namespace ObjectiveCSEL { 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); + + // Tablet / stylus event selectors + tabletPointSel = sel_registerName(kTabletPointSel); + tabletProximitySel = sel_registerName(kTabletProximitySel); + penPressureSel = sel_registerName(kPenPressureSel); + penRotationSel = sel_registerName(kPenRotationSel); + penTiltSel = sel_registerName(kPenTiltSel); + penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); + pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); + penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); + pressureChangeSel = sel_registerName(kPressureChangeSel); + stageSel = sel_registerName(kStageSel); + // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); @@ -376,6 +478,9 @@ namespace ObjectiveCSEL { 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); @@ -384,6 +489,7 @@ namespace ObjectiveCSEL { 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); @@ -508,7 +614,7 @@ static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFo 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); +// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning // enum for OpenGL profile versions enum class NSOpenGLProfile : int { @@ -517,6 +623,14 @@ enum class NSOpenGLProfile : int { Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile }; +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; + // Backward compatibility //static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); @@ -538,10 +652,10 @@ enum class NSWindowStyleMask : uint16_t { }; // Backward compatibility -static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); -static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); -static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); -static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); +// static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); // Temp remove unused variable warning static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); // enum for backing store types @@ -660,12 +774,12 @@ struct Application { // 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 + 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 // Event callback function pointers with nullptr initialization void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; @@ -685,6 +799,21 @@ struct Window { 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}; + // Touch event callback function pointers with nullptr initialization + void (*touchBeganCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchMovedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchEndedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchCancelledCallback)(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void* touchUserData{nullptr}; + + // Stylus (tablet) event callback function pointer + void (*stylusCallback)(uint32_t touchID, double x, double y, + float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData){nullptr}; + void* stylusUserData{nullptr}; + bool bStylusInProximity{false}; // true while stylus is hovering near the surface void* eventUserData{nullptr}; // User data for event callbacks BOOL acceptsInputEvents{NO}; // Whether the window accepts input events @@ -889,8 +1018,9 @@ void view_flagsChanged(id self, SEL _cmd, id event) { //====================================================================// // Mouse Event Handling -// Convert from window coordinates to content view coordinates and flip Y coordinate -// from bottom-left (macOS format) to top-left (standard format) +// 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]] @@ -899,16 +1029,23 @@ void convertToContentViewCoordinates(NSPoint& location) { id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); if (contentView) { - // Convert from window coordinates to view coordinates + + // Convert from window coordinates to view coordinates (still logical points) NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); - // Get the content view bounds to flip Y coordinate + // Scale from logical points to physical backing pixels + NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + + // Get content bounds in physical pixels for Y-flip NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + // Flip Y coordinate from bottom-left to top-left - location.x = contentLocation.x; - location.y = contentBounds.height - contentLocation.y; + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + } } else @@ -918,6 +1055,7 @@ void convertToContentViewCoordinates(NSPoint& location) { } } +// Mouse event data structure to hold common mouse event information struct MouseEventData { NSPoint location = {0.0, 0.0}; NSUInteger modifierFlags = 0; @@ -936,58 +1074,414 @@ MouseEventData extractMouseEventData(id event) { return data; } +// Stylus event data structure to hold common stylus event information +struct StylusEventData { + + uint32_t nTabletDeviceID = 0; // Unique identifier for the tablet device + uint8_t nPointerType = 0; // Pointer type 0 = Mouse, 1 = Pen, 2 = Eraser + uint8_t nPointerEventType = 0; // Type of pointer 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + NSPoint nspLocation = {0.0, 0.0}; // Location of the stylus event + double dPressure = 0.0; // Pressure applied by the stylus + double dTangentialPressure = 0.0; // Tangential pressure applied by the stylus + double dRotationRadians = 0.0; // Rotation of the stylus in radians + double dTiltX_radians = 0.0; // Tilt of the stylus in the X direction in radians + double dTiltY_radians = 0.0; // Tilt of the stylus in the Y direction in radians + uint16_t buttonMask = 0; // Bitmask for stylus buttons (MAX 16 buttons, 1 = pressed, 0 = released) + +}; + +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { + + 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); + + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event + + 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); + + data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); + + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); + + // Clamp to safe math bounds (-1.0 to 1.0) to avoid domain errors in asin + tiltX_raw = std::fmax(-1.0, std::fmin(1.0, tiltX_raw)); + tiltY_raw = std::fmax(-1.0, std::fmin(1.0, tiltY_raw)); + + // Convert to true physical angles in RADIANS -PI/2 to +PI/2 + data.dTiltX_radians = std::asin(tiltX_raw); + data.dTiltY_radians = std::asin(tiltY_raw); + + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} + +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + + +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; + +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; + +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; + +// 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); + + // 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; +}; + +// 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; + + // Assign a stable sequential uint32_t IDs same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + data.touchID = tid; + sTouchIDEvents.emplace(key, data); + callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) + { + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); + } + + } + + } + + // 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); + + } + } + + // Find the missing keys and fire the touchEndedCallback for them + for(auto& [key, data] : sTouchIDEvents) { + + if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { + gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); + vMissingKeys.push_back(key); + } + + } + + // Remove the missing keys from the maps + for(auto& fKeys: vMissingKeys) { + sTouchIDEvents.erase(fKeys); + sTouchIDMap.erase(fKeys); + } + + vFoundKeys.clear(); vMissingKeys.clear(); + + // Finally reset the next touch ID if there are no active touches remaining + if (sTouchIDMap.size() == 0) sNextTouchID = 0; +} + +// TODO move to new location +// Handle Force Touch pressure changes on trackpad (single-finger press) +void view_pressureChange(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + auto toNormalizedPressure = [](ForceTouchPressure pressure) -> double { + return static_cast(static_cast(pressure)) / 100.0; + }; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + + // Get CGEvent to determine event subtype + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + + // A force touch trackpad event will have a subtype of 0 (default) or greater than 2 (tablet proximity) + if (nPointerEventType == kCGEventMouseSubtypeDefault || nPointerEventType > kCGEventMouseSubtypeTabletProximity) { + + // KRespondsToSelector + BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + + NSInteger stage = NSPressureStageNormal; + if(supportsStage) + { + // stage returns: 0 = normal click, 1 = light press, 2 = force click + stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); + } + + // Map stage to normalized pressure values as requested + double pressure = toNormalizedPressure(ForceTouchPressure::Normal); + + switch (stage) { + case NSPressureStageLight: + pressure = toNormalizedPressure(ForceTouchPressure::Light); + break; + case NSPressureStageNormal: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + case NSPressureStageForce: + pressure = toNormalizedPressure(ForceTouchPressure::Force); + break; + default: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + } + + // now we need to work out the size + cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); + + for(auto& [key, data] : sTouchIDEvents) { + data.pressure = pressure; + data.nsTouchSize = cgsApproxTouchSize; + } + + } + } +} + // Handle left mouse down events void view_mouseDown(id self, SEL _cmd, id event) { (void)self;(void)_cmd; // Remove unused parameter warnings - MouseEventData data = extractMouseEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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]] { + + 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); + } + +} // Handle left mouse up events void view_mouseUp(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - 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); + 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]] { + + 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); + + } + } // Handle mouse drag events void view_mouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + 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]] { + + 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); + } + } + // Handle mouse movement events void view_mouseMoved(id self, SEL _cmd, id event) { (void)self;(void)_cmd; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + // if it is a trackpad event, ignore it. + if (nPointerEventType > 2) return; + + // a Mouse move event and Stlus roximity Event have the same properties. NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); - convertToContentViewCoordinates(location); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); } + + } // 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); @@ -997,7 +1491,12 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { // 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); @@ -1008,6 +1507,11 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { void view_rightMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1018,7 +1522,12 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { // 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); @@ -1028,7 +1537,12 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { // 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); @@ -1038,6 +1552,11 @@ void view_otherMouseUp(id self, SEL _cmd, id event) { void view_otherMouseDragged(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->otherMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -1098,6 +1617,73 @@ void view_mouseExited(id self, SEL _cmd, id event) { } } + +void view_touchesBegan(id self, SEL _cmd, id event) { + (void)_cmd; + + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseBegan, + gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesMoved(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseMoved, + gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesEnded(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseEnded, + gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); + } +} + +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); + } +} + +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + +// 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); + } +} + +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletPoint(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet point events here + } +} + + void view_updateTrackingAreas(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -1191,6 +1777,17 @@ Class createCustomOpenGLViewClass() { 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); @@ -1574,13 +2171,19 @@ extern "C" { return; } - // Get the content view bounds + // 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 = contentBounds.x; - if (y) *y = contentBounds.y; - if (width) *width = contentBounds.width; - if (height) *height = contentBounds.height; + if (x) *x = pixelBounds.x; + if (y) *y = pixelBounds.y; + if (width) *width = pixelBounds.width; + if (height) *height = pixelBounds.height; } @@ -1672,17 +2275,25 @@ extern "C" { // Get the content view id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - // Get the content view bounds to flip Y coordinate + // 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: we need to ensure the new cursor position is within the window bounds to prevent unexpected behavior - auto posX = std::clamp(location.x +x, location.x, location.x + contentBounds.width); - auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + y, - screenFrame.height - location.y - contentBounds.height, - screenFrame.height - location.y); + + // NOTE: clamp to keep the cursor within the window content area + auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + } } @@ -1763,6 +2374,13 @@ extern "C" { 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); + + } // Set up OpenGL context and make view first responder @@ -1846,7 +2464,7 @@ extern "C" { GLint swapInterval = enabled ? 1 : 0; // Use NSOpenGLContext setValues:forParameter: to set swap interval - const GLint parameter = 222; // NSOpenGLContextParameterSwapInterval + const GLint parameter = NSOpenGLContextParameterSwapInterval; ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } @@ -1897,13 +2515,13 @@ extern "C" { CGDataProviderRef provider = CGImageGetDataProvider(image); CFDataRef rawData = CGDataProviderCopyData(provider); - CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); - CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); - CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; + //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 = CGImageGetWidth(image); - self->height = CGImageGetHeight(image); - self->bytesPerPixel = 4; + 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; @@ -2244,6 +2862,34 @@ extern "C" { self->eventUserData = userData; } + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; + } + + void 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) + { + self->stylusCallback = callback; + self->stylusUserData = userData; + } + void window_enableEventHandling(Window* self) { if (self) { self->acceptsInputEvents = YES; diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index 62c17049..3729f2c1 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -114,7 +114,12 @@ extern "C" { // Event callback function types typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); - + typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); + typedef void (*StylusEventCallback) (uint32_t touchID, double x, double y,float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData); + // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); @@ -137,6 +142,16 @@ extern "C" { void window_enableEventHandling (struct Window* self); void window_disableEventHandling (struct Window* self); + // Touch event handler setup + void window_setTouchBeganCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchMovedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchEndedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchCancelledCallback (struct Window* self, TouchEventCallback callback, void* userData); + + // Stylus (tablet) event handler setup + void window_setStylusCallback (struct Window* self, StylusEventCallback callback, void* userData); + + // Application delegate callback function type typedef void (*ApplicationDelegateCallback)(void* userData); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index a346b9cc..987b585c 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -756,10 +756,46 @@ namespace olc { : x(x), y(y), deltaX(deltaX), deltaY(deltaY), modifierFlags(flags) {} }; + // Touch event data structure + struct TouchEvent { + uint32_t touchID; + double x, y; + double sizeX, sizeY; + + TouchEvent(uint32_t id, double x, double y, double sx, double sy) noexcept + : touchID(id), x(x), y(y), sizeX(sx), sizeY(sy) {} + + TouchEvent(TouchEvent&&) noexcept = default; + TouchEvent& operator=(TouchEvent&&) noexcept = default; + TouchEvent(const TouchEvent&) = default; + TouchEvent& operator=(const TouchEvent&) = default; + }; + + // Stylus (tablet) event data structure + struct StylusEvent { + uint32_t touchID; + double x, y; + float pressure, rotation; + float tiltX, tiltY; + bool bPress, bRelease; + bool bIsStylus; + + StylusEvent(uint32_t id, double x, double y, float pressure, float rotation, float tiltX, float tiltY, bool bPress, bool bRelease, bool bIsStylus) noexcept + : touchID(id), x(x), y(y), pressure(pressure), rotation(rotation), tiltX(tiltX), tiltY(tiltY), + bPress(bPress), bRelease(bRelease), bIsStylus(bIsStylus) {} + + StylusEvent(StylusEvent&&) noexcept = default; + StylusEvent& operator=(StylusEvent&&) noexcept = default; + StylusEvent(const StylusEvent&) = default; + StylusEvent& operator=(const StylusEvent&) = default; + }; + // Event handler class for keyboard and mouse events class EventHandler { private: Window& window_; + + // Key and mouse event handlers std::function keyDownHandler_; std::function keyUpHandler_; std::function flagsChangedHandler_; @@ -777,6 +813,15 @@ namespace olc { std::function mouseMovedEnteredHandler_; std::function mouseMovedExitedHandler_; + // Touch event handlers + std::function touchBeganHandler_; + std::function touchMovedHandler_; + std::function touchEndedHandler_; + std::function touchCancelledHandler_; + + // Stylus event handler + std::function stylusHandler_; + // Template helpers for static callbacks to reduce code duplication template static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { @@ -888,6 +933,37 @@ namespace olc { } } + static void touchBeganCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchBeganHandler_) + eventHandler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + static void touchMovedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchMovedHandler_) + eventHandler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchEndedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchEndedHandler_) + eventHandler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchCancelledCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchCancelledHandler_) + eventHandler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static 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) + { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->stylusHandler_) + eventHandler->stylusHandler_(StylusEvent(touchID, x, y, pressure, rotation, tiltX, tiltY, bPress, bRelease, bIsStylus)); + } + // Template helper for setting event handlers to reduce repetition template void setEventHandler(HandlerType EventHandler::*member, SetterFunc setter, CallbackFunc callback, std::function handler) { @@ -981,6 +1057,33 @@ namespace olc { window_setMouseExitedCallback(window_.getCHandle(), mouseExitedCallback, this); } + // Touch event handler setters + void onTouchBegan(std::function handler) { + touchBeganHandler_ = std::move(handler); + window_setTouchBeganCallback(window_.getCHandle(), touchBeganCallback, this); + } + + void onTouchMoved(std::function handler) { + touchMovedHandler_ = std::move(handler); + window_setTouchMovedCallback(window_.getCHandle(), touchMovedCallback, this); + } + + void onTouchEnded(std::function handler) { + touchEndedHandler_ = std::move(handler); + window_setTouchEndedCallback(window_.getCHandle(), touchEndedCallback, this); + } + + void onTouchCancelled(std::function handler) { + touchCancelledHandler_ = std::move(handler); + window_setTouchCancelledCallback(window_.getCHandle(), touchCancelledCallback, this); + } + + // Stylus event handler setter + void onStylus(std::function handler) { + stylusHandler_ = std::move(handler); + window_setStylusCallback(window_.getCHandle(), stylusCallback, this); + } + // Enable/disable event handling void enable() noexcept { window_enableEventHandling(window_.getCHandle()); diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 4923ef82..578a8473 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -265,6 +265,8 @@ namespace olc::apis::opengl { #if OLC_HOST == OLC_HOST_WINDOWS _wglSwapIntervalEXT(n); +#else + olc_IgnoreUnused(n); #endif } diff --git a/dev/src/config.h b/dev/src/config.h index b193a529..60136e88 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -142,6 +142,8 @@ #define OLC_MOUSE_BUTTONS 5 +#define OLC_MAX_TOUCHPOINTS 4 + #define OLC_DEFAULT_KEYBOARD_LAYOUT olc::KeyboardLayout::QWERTY_UK #define OLC_GPU_MAX_VERTICES 8192 @@ -152,11 +154,20 @@ #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " +#if OLC_HOST == OLC_HOST_MACOS +// De-Noise in clang (C++20) MacOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" // Allow unknown pragmas for compatibility with different compilers +#pragma clang diagnostic ignored "-Wgnu-anonymous-struct" // Allow anonymous structs in unions + +#endif + // De-Noise in MSVC (C++20) /Wall #pragma warning(disable:4820) // Disable Padding Warnings #pragma warning(disable:5045) // Disable Spectre Mitigation Warnings #pragma warning(disable:4514) // Disable Unreferenced Inline Function Warnings + template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} @@ -188,4 +199,4 @@ inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #define OLC_FRIENDLY_HOST Host_Android #endif -//! END CONFIGURATION \ No newline at end of file +//! END CONFIGURATION diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 7ac6568a..8b39c45a 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -104,6 +104,7 @@ namespace olc // Input Changes mouse.UpdateState(); keyboard.UpdateState(); + touch.UpdateState(); draw.SetGPU(pRenderer); draw.SetTarget(GetScreen()); @@ -174,6 +175,7 @@ namespace olc // fit within the window client area float fAspectScreen = float(GetScreen().Size().x) / float(GetScreen().Size().y); + vViewSize.x = (int32_t)vWindowSize.x; vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); @@ -201,6 +203,8 @@ namespace olc // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.ImageRect(GetScreen().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); @@ -220,14 +224,14 @@ namespace olc bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image - if (!image.Create(size, cfg)) + if (!image.CreateNoGPU(size, cfg)) return false; // Create GPU Image auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -246,7 +250,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -268,7 +272,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -297,7 +301,7 @@ namespace olc } // Free any cpu memory associated with image - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); } void PGEWindow::LinkToRenderer(olc::gpu::Renderer* gpu) @@ -358,6 +362,19 @@ namespace olc return true; } + bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) + { + olc::vf2d pos = vPos; + pos.x -= float(vViewPos.x); + pos.y -= float(vViewPos.y); + + olc::vf2d vScale = (1.0f / olc::vf2d(vWindowSize - (vViewPos * 2))) * GetScreen().Size(); + + touch.UpdateTouch(nID, pos * vScale, + bPress, bRelease, vSize * vScale, stylus, pressure, orientation, tilt); + return true; + } + @@ -514,6 +531,8 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.SetTarget(GetScreen()); draw.WorldReset(); gpu->ApplyDefaultShader(); diff --git a/dev/src/core.h b/dev/src/core.h index 3cb282a4..231a0d4b 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -42,11 +42,11 @@ namespace olc // These three are inherited from WindowConfig // Start in full-screen mode - bool bFullScreen = false; - // Allow full screen as an option with ALT-ENTER - bool bFullScreenable = true; - // Allow the window to be resized by user - bool bResizeable = true; + // bool bFullScreen = false; + // // Allow full screen as an option with ALT-ENTER + // bool bFullScreenable = true; + // // Allow the window to be resized by user + // bool bResizeable = true; // Allow the window border to be hidden by user bool bShowWindowBorder = true; // Allow the window title bar to be hidden by user @@ -93,6 +93,9 @@ namespace olc public: // olc::Image Handling + + // These are the preferred methods to create olc::Image objects + // Create an image resource bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); // Create an image resource based on an image file asset on disk @@ -128,6 +131,7 @@ namespace olc protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; + bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }) override; public: virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index a239b244..56fb76cc 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -3,6 +3,10 @@ #include "gpu_iface.h" //! START IMPLEMENTATION +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpessimizing-move" // Suppress clang C++20 warning about moves preventing copy elision on macOS +#endif using namespace olc; // Some local pools to reduce allocations @@ -256,63 +260,74 @@ void olc::Draw::Clear(const olc::Pixel& col) pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = olc::Structure::Line; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; task.vertexBuffer.resize((vPoints.size()-1) * 2); for (size_t i = 0; i < vPoints.size() - 1; i++) { task.vertexBuffer[i*2+0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; task.vertexBuffer[i*2+1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, vColours[i + 1], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; } + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } -GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; + task.vertexBuffer.resize((vPoints.size() - 1) * 2); + for (size_t i = 0; i < vPoints.size() - 1; i++) + { + task.vertexBuffer[i * 2 + 0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.vertexBuffer[i * 2 + 1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + } + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; + +} + +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + olc_IgnoreUnused(structure); + return TaskDrawLine(vPoints, vColours, tint, false, true); } GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { - GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = {{vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - task.tint = tint; - return task; + olc_IgnoreUnused(structure); + return TaskDrawLine(vPoints, colour, tint, false, true); } -GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; task.vertexBuffer.resize(vPoints.size()); for (size_t i = 0; i < vPoints.size(); i++) task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } -GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; task.vertexBuffer.resize(vPoints.size()); for (size_t i = 0; i < vPoints.size(); i++) task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } @@ -324,6 +339,7 @@ GPUTask olc::Draw::TaskTexturedPolygon(olc::Structure structure, const std::vect for (size_t i = 0; i({ p1, p2 }), { c1, c2 }, tint - ))); + ))); } @@ -502,7 +521,12 @@ void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) for (int32_t i = 0; i <= nFacets; i++) { float theta = float(i) / float(nFacets) * 2.0f * 3.14159265358979323846f; - buffUnitCirclePoints.data[i] = { cosf(theta), sinf(theta) }; + + float c = cos(theta); + float s = sin(theta); + + + buffUnitCirclePoints.data[i] = { c, s }; } } @@ -537,16 +561,24 @@ const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const f for (int32_t i = 0; i <= nFacets; i++) { - buffPoints.data[i] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i] = col; } - return vecGPUTasks.data.emplace_back(std::move( + /*return vecGPUTasks.data.emplace_back(std::move( TaskDrawPolygon( olc::Structure::Line, buffPoints.data, buffColours.data, tint + )));*/ + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + buffPoints.data, + buffColours.data, + tint, + false // Don't constrain ))); } @@ -573,7 +605,7 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c for (int32_t i = 0; i <= nFacets; i++) { - buffPoints.data[i + 1] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i + 1] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i + 1] = colOuter; } @@ -582,7 +614,8 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c olc::Structure::Fan, buffPoints.data, buffColours.data, - tint + tint, + false // Don't constrain ))); } @@ -1093,11 +1126,16 @@ const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos } -void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) +void olc::Draw::SetCullMode(const olc::CullMode mode) { cullMode = mode; } +void olc::Draw::SetBlendMode(const olc::BlendMode mode) +{ + blendMode = mode; +} + void olc::Draw::EnableDepth(const bool bEnable) { bDepth = bEnable; @@ -1161,4 +1199,8 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } -//! END IMPLEMENTATION \ No newline at end of file +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + +//! END IMPLEMENTATION diff --git a/dev/src/draw.h b/dev/src/draw.h index 4ad24138..cf7af215 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -826,7 +826,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE); public: // Applied Rendering Modes - void SetCullMode(const olc::GPUTask::CullMode mode); + void SetCullMode(const olc::CullMode mode); + void SetBlendMode(const olc::BlendMode mode); void EnableDepth(const bool bEnable); void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); @@ -907,7 +908,16 @@ namespace olc GPUTask TaskDrawLine( const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true, + const bool looped = false); + + GPUTask TaskDrawLine( + const std::vector& vPoints, + const olc::Pixel colour, + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true, + const bool looped = false); GPUTask TaskDrawPolygon( olc::Structure structure, @@ -925,13 +935,15 @@ namespace olc olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskFillPolygon( olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskTexturedPolygon( olc::Structure structure, @@ -1026,7 +1038,8 @@ namespace olc olc::vi2d vViewportSize = { 0, 0 }; - olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + olc::CullMode cullMode = olc::CullMode::None; + olc::BlendMode blendMode = olc::BlendMode::Alpha; bool bDepth = true; diff --git a/dev/src/font.cpp b/dev/src/font.cpp index 928f9d7d..be13d212 100644 --- a/dev/src/font.cpp +++ b/dev/src/font.cpp @@ -83,7 +83,8 @@ namespace olc fontClassicPGE.glyphs.push_back(glyph); } else - fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f} }); + // Added missing vMonoSize for non-printable characters, which was causing -Wmissing-field-initializers + fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f}, { 0.0f, 0.0f } }); } fontClassicPGE.fLineHeight = 8.0f; diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 3c1a792f..66bc14bc 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -89,6 +89,20 @@ void main() oTex = aTex; } + else if (pgeDrawType == 3) // Unconstrained 2D Line + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = aTex; + } + + else if (pgeDrawType == 4) // Unconstrained 2D Polygon + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = p * vec2(aTex.x, aTex.y); + } + else if (pgeDrawType == 0) // 2D Polygon { float p = 1.0 / aPos.z; @@ -346,6 +360,9 @@ void main() EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; /* create an EGL rendering context */ +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + eglBindAPI(EGL_OPENGL_API); +#endif glRenderContext.context = eglCreateContext(glRenderContext.display, glRenderContext.config, EGL_NO_CONTEXT, context_config); glRenderContext.surface = eglCreateWindowSurface(glRenderContext.display, glRenderContext.config, window_handle, nullptr); if(glRenderContext.surface == EGL_NO_SURFACE) { @@ -461,7 +478,7 @@ void main() // Create a null-texture so sampler doesnt fail. We don't have some of the core's helper // functions here, so we construct it manually - imgBlank.Create({ 1,1 }); + imgBlank.CreateNoGPU({ 1,1 }); imgBlank.SetGPUID(CreateTexture(imgBlank.Size())); imgBlank.BindCPU(); imgBlank.Pixel({ 0,0 }) = olc::Colour::WHITE; @@ -1124,33 +1141,59 @@ void main() // Apply Culling modes - if (task.cullmode == GPUTask::CullMode::None) + if (task.cullmode == olc::CullMode::None) { gl.glDisable(GL_CULL_FACE); } - else if (task.cullmode == GPUTask::CullMode::ClockWise) + else if (task.cullmode == olc::CullMode::ClockWise) { gl.glCullFace(GL_FRONT); gl.glEnable(GL_CULL_FACE); } - else if (task.cullmode == GPUTask::CullMode::CounterClockWise) + else if (task.cullmode == olc::CullMode::CounterClockWise) { gl.glCullFace(GL_BACK); gl.glEnable(GL_CULL_FACE); } - //// Apply Depth Testing (if required) + // Apply Depth Testing (if required) if (task.bDepth) + { gl.glEnable(GL_DEPTH_TEST); + gl.glDepthFunc(GL_LESS); + } + + + // Apply Blending Mode + if (task.blendmode == olc::BlendMode::Alpha) + { + gl.glEnable(GL_BLEND); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + else if(task.blendmode == olc::BlendMode::Additive) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + else if(task.blendmode == olc::BlendMode::Multiplicative) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + } + else if (task.blendmode == olc::BlendMode::None) + { + gl.glDisable(GL_BLEND); + } - glDepthFunc(GL_LESS); - gl.glEnable(GL_BLEND); + //gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + //gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //// Apply Rendering Mode + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (task.bIs3D) { @@ -1159,16 +1202,32 @@ void main() } else { - if (task.structure == olc::Structure::Point) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::Line) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineLoop) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineList) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + if (task.bPixelConstrained) + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + } else - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 4); + } } if (task.structure == olc::Structure::Fan) @@ -1187,8 +1246,8 @@ void main() gl.glDrawArrays(GL_POINTS, 0, (GLsizei)task.vertexBuffer.size()); - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (task.bDepth) gl.glDisable(GL_DEPTH_TEST); @@ -1242,6 +1301,7 @@ void main() #if OLC_HOST == OLC_HOST_MACOS // The pointer value in os_win_id[1] will be set to true, when the OS requests to skip the frame swap + olc_IgnoreUnused(bVerticalSyncNow); const bool* bSkipFrame = static_cast(os_win_id[1]); if (*bSkipFrame) return true; CGLContextObj cglContext = static_cast(os_win_id[0]); diff --git a/dev/src/gputask.h b/dev/src/gputask.h index 251230de..12e094a9 100644 --- a/dev/src/gputask.h +++ b/dev/src/gputask.h @@ -37,6 +37,29 @@ namespace olc // Vertex buffer is a series of discrete triangles List, }; + + // Define blend modes for drawing operations + enum class BlendMode : uint8_t + { + // Alpha blend source and destination pixels based on source alpha + Alpha = 0, + // Additively blend source and destination pixels together + Additive, + // Multiplicatively blend source and destination pixels together + Multiplicative, + // No blending, just overwrite pixels with source colour + None, + }; + + enum class CullMode : uint8_t + { + // No + None = 0, + // Cull if vertices are listed in clockwise order + ClockWise, + // Cull if vertices are listed in anticlockwise order + CounterClockWise + }; // This is the default "packet" of work that is sent to // a GPU for drawing. Various drawing operations throughout @@ -76,8 +99,8 @@ namespace olc // Use depth components bool bDepth = false; - // Use hardware wire drawing - bool bWireframe = false; + // Constrain with pixel biases + bool bPixelConstrained = true; // Define how to interpret vertex buffer bool bIs3D = false; @@ -85,21 +108,17 @@ namespace olc // Overall biasing colour (great for blends) olc::Pixel tint = olc::Colour::WHITE; + // Texture to be used for drawing (if any) (in slot #0) olc::Image* pImage = nullptr; // Define super structure to be drawn Structure structure = Structure::Fan; // Define if GPU should face cull based on winding order - enum class CullMode : uint8_t - { - // No - None = 0, - // Cull if vertices are listed in clockwise order - ClockWise, - // Cull if vertices are listed in anticlockwise order - CounterClockWise - } cullmode = CullMode::None; + CullMode cullmode = CullMode::None; + + // Define blend mode for drawing + BlendMode blendmode = BlendMode::Alpha; }; } #define PGE_GPUTASK_DECLARED 1 diff --git a/dev/src/host_android.cpp b/dev/src/host_android.cpp index 45e55e5b..1e2c7acc 100644 --- a/dev/src/host_android.cpp +++ b/dev/src/host_android.cpp @@ -279,33 +279,20 @@ namespace olc::host auto type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) { - pgeWindow->olc_OnMouseMove({ - static_cast(AMotionEvent_getX(event, 0)), - static_cast(AMotionEvent_getY(event, 0)), - }); - - auto action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; - - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - pgeWindow->olc_OnMouseButton(0, true); // Left button - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - pgeWindow->olc_OnMouseButton(0, false); // Left button - break; - case AMOTION_EVENT_AXIS_WHEEL: - // Handle mouse wheel - { - float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, 0); - if (vScroll != 0.0f) { - pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); - } - } - break; - default: - break; + auto actionRaw = AMotionEvent_getAction(event); + auto actionMasked = actionRaw & AMOTION_EVENT_ACTION_MASK; + auto pointerIndex = (actionRaw & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + auto pointerCount = AMotionEvent_getPointerCount(event); + + for (size_t pi = 0; pi < pointerCount; pi++) { + auto toolType = AMotionEvent_getToolType(event, pi); + switch (toolType) { + case AMOTION_EVENT_TOOL_TYPE_MOUSE: handleMouse(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_ERASER: + case AMOTION_EVENT_TOOL_TYPE_STYLUS: handleStylus(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_FINGER: handleFinger(pi, actionMasked, event); break; + default: break; + } } return 1; @@ -595,8 +582,175 @@ namespace olc::host return content; } - - void Host_Android::PollEvents(const std::function& funcContinue, bool bBlocking) + + void Host_Android::handleMouse(size_t id, int32_t action, AInputEvent *event) + { + auto fnGetButton = [&event]() { + int32_t buttons = AMotionEvent_getButtonState(event); + if (buttons & AMOTION_EVENT_BUTTON_PRIMARY) return 0; + if (buttons & AMOTION_EVENT_BUTTON_SECONDARY) return 1; + if (buttons & AMOTION_EVENT_BUTTON_TERTIARY) return 2; + if (buttons & AMOTION_EVENT_BUTTON_BACK) return 3; + if (buttons & AMOTION_EVENT_BUTTON_FORWARD) return 4; + return -1; + }; + + pgeWindow->olc_OnMouseMove({ + static_cast(AMotionEvent_getX(event, id)), + static_cast(AMotionEvent_getY(event, id)), + }); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnMouseButton(fnGetButton(), true); + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnMouseButton(fnGetButton(), false); + break; + case AMOTION_EVENT_AXIS_WHEEL: + { + float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, id); + if (vScroll != 0.0f) { + pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); + } + } + break; + default: + break; + } + } + + void Host_Android::handleStylus(size_t id, int32_t action, AInputEvent *event) + { + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + float ellipseX = AMotionEvent_getToolMajor(event, id); + float ellipseY = AMotionEvent_getToolMinor(event, id); + + // radians, 0 = vertical, positive = clockwise tilt in the plane of the screen + float orientation = AMotionEvent_getOrientation(event, id); + + // angle of the stylus away from perpendicular to the screen, radians, 0 = straight up + float axisTilt = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, id); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + default: + break; + } + } + + void Host_Android::handleFinger(size_t id, int32_t action, AInputEvent *event) + { + float size = AMotionEvent_getSize(event, id); + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { size, size }, + false, + pressure + ); + break; + default: + break; + } + } + + void Host_Android::PollEvents(const std::function &funcContinue, bool bBlocking) { while (funcContinue()) { int events; diff --git a/dev/src/host_android.h b/dev/src/host_android.h index 19a08cac..779c5a1a 100644 --- a/dev/src/host_android.h +++ b/dev/src/host_android.h @@ -82,6 +82,10 @@ namespace olc::host std::atomic initialized{false}, systemActive{false}; bool shiftOn = false; + void handleMouse(size_t id, int32_t action, AInputEvent* event); + void handleStylus(size_t id, int32_t action, AInputEvent* event); + void handleFinger(size_t id, int32_t action, AInputEvent* event); + void PollEvents( const std::function& funcContinue, bool bBlocking = false diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index f2bde38c..b4759995 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -9,15 +9,15 @@ namespace olc::host { // NSEventModifierFlags values - constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 - constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + // 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 + // 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 - constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 - constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 + // 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 { @@ -154,6 +154,7 @@ namespace olc::host { 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); @@ -181,7 +182,7 @@ namespace olc::host { } std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - + olc_IgnoreUnused(pWindow); // Ensure OpenGL renderer is created if(pMacOSOpenGLRenderer == nullptr) CreateCGLContextObj(); @@ -210,6 +211,7 @@ namespace olc::host { 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); }); @@ -218,6 +220,7 @@ namespace olc::host { bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + olc_IgnoreUnused(pWindow); dispatch_sync(dispatch_get_main_queue(), ^{ pMacOSWindow->setCursorVisibility(bVisible); }); @@ -226,6 +229,7 @@ namespace olc::host { 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; @@ -273,6 +277,9 @@ namespace olc::host { 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); @@ -292,10 +299,7 @@ namespace olc::host { pMacOSWindow->show(styleMask); pMacOSEventHandler->enable(); - //--- Start up our engine threading system ----- - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - + //--- Start up our engine threading system ---- // Start the PGE context on the main thread // Mark system as active systemActive = true; @@ -753,9 +757,55 @@ namespace olc::host { // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); }); + + // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch + pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + // Stylus (tablet) events — full pressure, tilt and rotation data + pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + + pPGEwindow->olc_OnTouch( + event.touchID, + {static_cast(event.x), static_cast(event.y)}, + event.bPress, + event.bRelease, + {1.0f, 1.0f}, // stylus contact size — nominal 1x1 + true, // bStylus = true + event.pressure, + event.rotation, + {event.tiltX, event.tiltY} + ); + }); } } //! END IMPLEMENTATION + #endif /* OLC_HOST == OLC_HOST_MACOS */ diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 3e6f6db2..831cef3d 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -37,6 +37,16 @@ namespace olc::host .modifiers = Host_Linux_Wayland::keyboard_modifiers_callback, .repeat_info = Host_Linux_Wayland::keyboard_repeat_info_callback }; + + static const wl_touch_listener touch_listener { + .down = Host_Linux_Wayland::touch_down_callback, + .up = Host_Linux_Wayland::touch_up_callback, + .motion = Host_Linux_Wayland::touch_motion_callback, + .frame = Host_Linux_Wayland::touch_frame_callback, + .cancel = Host_Linux_Wayland::touch_cancel_callback, + .shape = Host_Linux_Wayland::touch_shape_callback, + .orientation = Host_Linux_Wayland::touch_orientation_callback + }; } namespace xdg { @@ -47,22 +57,8 @@ namespace olc::host static const xdg_surface_listener surface_listener { .configure = Host_Linux_Wayland::xdg_surface_configure_callback }; - - static const xdg_toplevel_listener xdg_top_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_configure_callback, - .close = Host_Linux_Wayland::xdg_toplevel_close_callback, - .configure_bounds = Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback, - .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback - }; - - #ifdef ENABLE_DECORATION_PROTOCOL - static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback - }; - #endif } - #ifdef ENABLE_LIBDECOR namespace decor { static libdecor_interface libdecor_error_listener = { .error = Host_Linux_Wayland::libdecor_error_callback, @@ -75,7 +71,6 @@ namespace olc::host .dismiss_popup = Host_Linux_Wayland::libdecor_dismiss_popup_callback }; } - #endif Host_Linux_Wayland::Host_Linux_Wayland() { @@ -90,29 +85,7 @@ namespace olc::host throw; } - // If only the decoration protocol is enabled, then not having the protocol is a hard error - #if defined(ENABLE_DECORATION_PROTOCOL) && !defined(ENABLE_LIBDECOR) - if(decoration_manager == nullptr) { - throw; - } - // If only libdecor is enabled, then flag "using_libdecor" - #elif !defined(ENABLE_DECORATION_PROTOCOL) && defined(ENABLE_LIBDECOR) - using_libdecor = true; - - // If both are enabled, use libdecor if the decoration protocol is not present - #else - using_libdecor = (decoration_manager == nullptr); - #endif - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - } else { - decor_context = libdecor_new(display, &decor::libdecor_error_listener); - } - #else - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - #endif + decor_context = libdecor_new(display, &decor::libdecor_error_listener); // Load the default cursor cursor_theme = wl_cursor_theme_load(NULL, 24, shm); @@ -194,20 +167,12 @@ namespace olc::host if(window) { wl_egl_window_destroy(window); } - if(toplevel) { - xdg_toplevel_destroy(toplevel); - } if(surface_xdg) { xdg_surface_destroy(surface_xdg); } - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1_destroy(decorations); - #endif - #ifdef ENABLE_LIBDECOR if(decor_frame) { libdecor_frame_unref(decor_frame); } - #endif wl_surface_destroy(surface); } @@ -216,15 +181,10 @@ namespace olc::host mapUID2OlcWindow.clear(); mapUID2Window.clear(); - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1_destroy(decoration_manager); - #endif - #ifdef ENABLE_LIBDECOR if(decor_context) { libdecor_unref(decor_context); decor_context = nullptr; } - #endif xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); @@ -237,6 +197,9 @@ namespace olc::host { wp_pointer_warp_v1_destroy(pointer_warp); } + if(touch) { + wl_touch_destroy(touch); + } wl_keyboard_destroy(keyboard); wl_pointer_destroy(pointer); wl_seat_destroy(seat); @@ -284,21 +247,13 @@ namespace olc::host } }); - #if !defined(ENABLE_LIBDECOR) - while(systemActive && wl_display_dispatch_pending(display) != -1) { } - #else bool keep_running = true; while(systemActive && keep_running) { - if(using_libdecor) { - if(decor_context) { - std::lock_guard l{decor_mutex}; - keep_running = libdecor_dispatch(decor_context, 0) >= 0; - } - } else { - keep_running = wl_display_dispatch_pending(display) != -1; + if(decor_context) { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; } } - #endif systemActive = false; if(threadSystem.joinable()) @@ -341,43 +296,19 @@ namespace olc::host wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); w.surface = wl_compositor_create_surface(compositor); - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - #endif - w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); - - xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); - w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); - xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); - xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); - if (!pWindow->config.bResizeable) { - xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); - xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); - } - - #ifdef ENABLE_DECORATION_PROTOCOL - w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); - zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); - zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); - #endif - #ifdef ENABLE_LIBDECOR - } else { - std::lock_guard l{decor_mutex}; - w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); - w.floating_width = vWindowSize.x; - w.floating_height = vWindowSize.y; - libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); - libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); - - if(!pWindow->config.bResizeable) { - libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); - } - libdecor_frame_map(w.decor_frame); + std::lock_guard l{decor_mutex}; + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); } - #endif + + libdecor_frame_map(w.decor_frame); wl_surface_set_opaque_region(w.surface, region); w.window = wl_egl_window_create(w.surface, vWindowSize.x, vWindowSize.y); @@ -405,16 +336,8 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - std::lock_guard l{decor_mutex}; - libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); - } else { - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - } - #else - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - #endif + std::lock_guard l{decor_mutex}; + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); } return true; } @@ -491,25 +414,10 @@ namespace olc::host if(itr != mapUID2Window.end()) { pWindow->bWindowIsFullscreen = bFullScreen; if(bFullScreen) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); - } else { - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - } - #else - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - #endif + libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); + } else { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_unset_fullscreen(itr->second.decor_frame); - } else { - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - } - #else - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - #endif + libdecor_frame_unset_fullscreen(itr->second.decor_frame); } } return true; @@ -530,11 +438,6 @@ namespace olc::host seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); wl_seat_add_listener(seat, &wayland::seat_listener, this); } - #ifdef ENABLE_DECORATION_PROTOCOL - if(std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - decoration_manager = static_cast(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, version)); - } - #endif if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } @@ -560,68 +463,10 @@ namespace olc::host keyboard_version = wl_keyboard_get_version(keyboard); wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); } - } - - void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { - const auto& itr = std::find_if(mapUID2Window.begin(), mapUID2Window.end(), [=](const auto& w){return w.second.toplevel == toplevel;}); - if(itr == mapUID2Window.end()) - { - return; - } - auto& [uid, window] = *itr; - const auto& olc_window = mapUID2OlcWindow.at(uid); - - bool attempt_fullscreen {false}; - auto* state = reinterpret_cast(states->data); - auto* end = static_cast(states->data) + states->size; - for(;reinterpret_cast(state) < end; state++) - { - // The window.fullscreen is set any time the user commands via ShowFullscreen(), so we should allow this - if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) - { - attempt_fullscreen = true; - } - } - - // If we're not going fullscreen, try to obey the bounds that have been configured by the compositor - if(!attempt_fullscreen) { - if(window.bounds_x != 0) { - width = std::min(width, window.bounds_x); - } - - if(window.bounds_y != 0) { - height = std::min(height, window.bounds_y); - } - } - - olc_window->bWindowIsFullscreen = attempt_fullscreen; - olc_window->olc_OnWindowSize({width, height}); - wl_egl_window_resize(window.window, width, height, 0, 0); - wl_surface_commit(window.surface); - } - - void Host_Linux_Wayland::xdg_toplevel_close(xdg_toplevel* toplevel) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - auto itr = mapUID2OlcWindow.find(i.second.olc_window_uid); - if(itr != mapUID2OlcWindow.end()) { - auto* ptr = itr->second; - ptr->olc_OnWindowClose(); - } - } - } - } - - void Host_Linux_Wayland::xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - i.second.bounds_x = width; - i.second.bounds_y = height; - } + if (capabilities & WL_SEAT_CAPABILITY_TOUCH && touch == nullptr) { + touch = wl_seat_get_touch(seat); + wl_touch_add_listener(touch, &wayland::touch_listener, this); } } @@ -961,48 +806,180 @@ namespace olc::host // host->keyboard_repeat_info(keyboard, rate, delay); } - // XDG Callbacks - void Host_Linux_Wayland::xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial) { - xdg_wm_base_pong(wm, serial); + // Touch Callbacks + void Host_Linux_Wayland::touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) + { + auto* host = reinterpret_cast(data); + host->touch_down(touch, serial, time, surface, id, x, y); } - void Host_Linux_Wayland::xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial) - { - xdg_surface_ack_configure(surface, serial); + void Host_Linux_Wayland::touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventDown; + touch_state.surface = surface; + touch_state.id = id; + touch_state.surface_x = x; + touch_state.surface_y = y; + touch_state.serial = serial; + touch_state.time = time; } - void Host_Linux_Wayland::xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { + void Host_Linux_Wayland::touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure(toplevel, width, height, states); + host->touch_up(touch, serial, time, id); } - void Host_Linux_Wayland::xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel) - { + void Host_Linux_Wayland::touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventUp; + touch_state.serial = serial; + touch_state.time = time; + } + + void Host_Linux_Wayland::touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { auto* host = reinterpret_cast(data); - host->xdg_toplevel_close(toplevel); + host->touch_motion(touch, time, id, x, y); + } + + void Host_Linux_Wayland::touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventMotion; + touch_state.time = time; + touch_state.surface_x = x; + touch_state.surface_y = y; } - void Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height) { + void Host_Linux_Wayland::touch_frame_callback(void* data, wl_touch* touch) { auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure_bounds(toplevel, width, height); - return; + host->touch_frame(touch); } - void Host_Linux_Wayland::xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities) - { - return; + void Host_Linux_Wayland::touch_frame(wl_touch* touch) { + // Commit all of the touches + for(auto itr = touches.begin(); itr != touches.end();) { + const auto& id = itr->first; + const auto& touch = itr->second; + + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + touch.event_mask & wayland::TouchEventMask::TouchEventUp, + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + + if(touch.event_mask & wayland::TouchEventMask::TouchEventUp) { + itr = touches.erase(itr); + } else { + ++itr; + } + } + } + + void Host_Linux_Wayland::touch_cancel_callback(void* data, wl_touch* touch) { + auto* host = reinterpret_cast(data); + host->touch_cancel(touch); } - #ifdef ENABLE_DECORATION_PROTOCOL - void Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode) + void Host_Linux_Wayland::touch_cancel(wl_touch* touch) { + // according to the protocol, this ends all touch events, so send an Up and clear the whole thing + for(const auto& [id, touch] : touches) { + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + true, // Forced Up event to clear all these IDs on the PGE side + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + } + + touches.clear(); + } + + void Host_Linux_Wayland::touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto* host = reinterpret_cast(data); + host->touch_shape(touch, id, major, minor); + } + + void Host_Linux_Wayland::touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventShape; + touch_state.major = major; + touch_state.minor = minor; + } + + void Host_Linux_Wayland::touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto* host = reinterpret_cast(data); + host->touch_orientation(touch, id, orientation); + } + + void Host_Linux_Wayland::touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventOrientation; + touch_state.orientation = orientation; + } + + + // XDG Callbacks + void Host_Linux_Wayland::xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial) { + xdg_wm_base_pong(wm, serial); + } + + void Host_Linux_Wayland::xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial) { - // auto* host = reinterpret_cast(data); - // fprintf(stderr, "zxdg_decoration_manager_v1 mode %d\n", mode); + xdg_surface_ack_configure(surface, serial); } - #endif - #ifdef ENABLE_LIBDECOR + void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) { std::cerr << "libdecor: " << error << ": " << message << "\n"; @@ -1046,7 +1023,6 @@ namespace olc::host olc_window->bWindowIsFullscreen = (window->decor_window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); - wl_surface_commit(window->surface); } } } @@ -1072,24 +1048,20 @@ namespace olc::host void Host_Linux_Wayland::libdecor_commit_callback(libdecor_frame* frame, void* data) { - auto* host = reinterpret_cast(data); - host->libdecor_commit(frame); + // Don't want to actually do anything here because it messes with the EGL surface swapping for refresh + // and causes the application to close (not crash) due to a wayland protocol violation + //auto* host = reinterpret_cast(data); + //host->libdecor_commit(frame); } void Host_Linux_Wayland::libdecor_commit(libdecor_frame* frame) { - for(auto& i : mapUID2Window) { - if(i.second.decor_frame == frame) { - wl_surface_commit(i.second.surface); - } - } } void Host_Linux_Wayland::libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data) { } - #endif std::vector Host_Linux_Wayland::GetHostWindowDescriptor(olc::Window* pWindow) { diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index b7286027..31967d91 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -16,28 +16,12 @@ //! END CUSTOMHEADER //! START DECLARATION -#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_LIBDECOR -#endif - -#if !defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_DECORATION_PROTOCOL -#endif - -#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) -#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." -#endif #include #include #include #include "xdg-shell.h" -// Only include the decoration protocol if we are not forcing libdecor -#ifdef ENABLE_DECORATION_PROTOCOL -#include "xdg-decoration.h" -#endif - #include "pointer-warp.h" #include #include @@ -45,9 +29,8 @@ #include #include -#ifdef ENABLE_LIBDECOR #include "libdecor.h" -#endif + #include #include @@ -61,10 +44,6 @@ namespace olc::host struct WaylandWindow { wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; - xdg_toplevel* toplevel{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1* decorations{nullptr}; - #endif wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -73,7 +52,6 @@ namespace olc::host // Ignore window size bounds for fullscreen events bool fullscreen{false}; - #ifdef ENABLE_LIBDECOR // libdecor support libdecor_frame* decor_frame{nullptr}; int configured_width{}; @@ -81,7 +59,6 @@ namespace olc::host libdecor_window_state decor_window_state; int floating_width{}; int floating_height{}; - #endif ~WaylandWindow(); }; @@ -116,6 +93,29 @@ namespace olc::host std::array axes{}; uint32_t axis_source{0}; }; + + enum TouchEventMask { + TouchEventDown = 1 << 0, + TouchEventUp = 1 << 1, + TouchEventMotion = 1 << 2, + TouchEventCancel = 1 << 3, + TouchEventShape = 1 << 4, + TouchEventOrientation = 1 << 5 + }; + + struct TouchState { + uint32_t event_mask{0}; + wl_surface* surface{nullptr}; + wl_fixed_t surface_x{}; + wl_fixed_t surface_y{}; + wl_fixed_t major{}; + wl_fixed_t minor{}; + wl_fixed_t orientation{}; + + uint32_t time{0}; + uint32_t serial{0}; + int32_t id{0}; + }; } class Host_Linux_Wayland : public olc::host::Host @@ -128,31 +128,28 @@ namespace olc::host wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; wl_keyboard* keyboard{nullptr}; + wl_touch* touch{nullptr}; uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; xkb_keymap* kb_keymap{nullptr}; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1* decoration_manager{nullptr}; - #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; wayland::PointerState pointer_state; + std::unordered_map touches; wl_surface* cursor_surface{nullptr}; wl_cursor_image* cursor_image{nullptr}; wl_cursor_theme* cursor_theme{nullptr}; size_t active_window_id; - #ifdef ENABLE_LIBDECOR // libdecor support bool using_libdecor{false}; libdecor* decor_context{nullptr}; std::mutex decor_mutex; - #endif public: Host_Linux_Wayland(); @@ -210,27 +207,27 @@ namespace olc::host static void keyboard_modifiers_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); static void keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay); + // Touch callbacks + static void touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + static void touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_frame_callback(void* data, wl_touch* touch); + static void touch_cancel_callback(void* data, wl_touch* touch); + static void touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + static void touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation); + // xdg callbacks static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); - static void xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); - static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); - static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); - #ifdef ENABLE_DECORATION_PROTOCOL - static void xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode); - #endif - - #ifdef ENABLE_LIBDECOR + // libdecor callbacks static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); static void libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data); static void libdecor_close_callback(libdecor_frame* frame, void* data); static void libdecor_commit_callback(libdecor_frame* frame, void* data); static void libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data); - #endif - - private: + + private: // Wayland callback functions void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void registry_handle_global_remove(wl_registry* registry, uint32_t name); @@ -256,17 +253,19 @@ namespace olc::host void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); - // XDG toplevel callback functions - void xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - void xdg_toplevel_close(xdg_toplevel* toplevel); - void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); + // Touch callbacks + void touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + void touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_frame(wl_touch* touch); + void touch_cancel(wl_touch* touch); + void touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + void touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation); - #ifdef ENABLE_LIBDECOR // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); void libdecor_commit(libdecor_frame* frame); - #endif bool CreateEGLContext(WaylandWindow* window); diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 6b1b715e..cb6866c5 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -10,6 +10,9 @@ namespace olc::host olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + // If supported, enable receiving touch events + enableTouch(olc_Display, olc_WindowRoot); + if(XkbQueryExtension(olc_Display, nullptr, &xkbEventBase, &xkbErrorBase, nullptr, nullptr)) { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); @@ -283,6 +286,16 @@ namespace olc::host pge_window->olc_OnWindowClose(); } } + else if (xev.type == GenericEvent) + { + X11::XGenericEventCookie& xgec = xev.xcookie; + + if(xgec.extension == xinput_extension_code && X11::XGetEventData(olc_Display, &xgec)) { + // Should be one of our touch events + handleTouchEvent(static_cast(xgec.data)); + X11::XFreeEventData(olc_Display, &xgec); + } + } } } @@ -544,5 +557,88 @@ namespace olc::host return true; } + + void Host_Linux_X11::enableTouch(X11::Display* display, X11::Window window) + { + // Check if we have the XInput extension + int extension_code; + int ev; + int err; + + // If we have the extension, check for version 2.3 or higher + if (X11::XQueryExtension(display, "XInputExtension", &extension_code, &ev, &err)) { + int major{2}; + int minor{3}; + + if(X11::XIQueryVersion(display, &major, &minor) != Success) { + // Version is incorrect, do not select touch events + return; + } + } else { + // Do not have the extension, do not select touch events + return; + } + + // If we get down here, we have the extension and version is correct + xinput_extension_code = extension_code; + + X11::XIEventMask mask{}; + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = static_cast(std::calloc(mask.mask_len, sizeof(char))); + + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + X11::XISelectEvents(display, window, &mask, 1); + + X11::XSync(display, False); + std::free(mask.mask); + } + + void Host_Linux_X11::handleTouchEvent(X11::XIDeviceEvent* event) + { + auto itr_child = mapX11Window2PTR.find(event->child); + if(itr_child == mapX11Window2PTR.end()) { + + return; + } + + olc::Window* pge_window = itr_child->second; + + switch(event->evtype) { + case XI_TouchBegin: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + true, + false, + olc::vf2d{1.0f, .0f} + ); + break; + case XI_TouchUpdate: + { + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + false, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + case XI_TouchEnd: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + true, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + } + } //! END IMPLEMENTATION diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 0f7c343a..832f268f 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -22,6 +22,7 @@ namespace X11 #include #include #include +#include #include #undef None constexpr int None = 0L; @@ -37,6 +38,8 @@ namespace olc::host X11::XVisualInfo* olc_VisualInfo; X11::Colormap olc_ColourMap; X11::XSetWindowAttributes olc_SetWindowAttribs; + + int xinput_extension_code{}; public: Host_Linux_X11(); @@ -68,6 +71,8 @@ namespace olc::host bool OnApplicationEnd() override; private: + void enableTouch(X11::Display* display, X11::Window window); + void handleTouchEvent(X11::XIDeviceEvent* event); std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; std::atomic systemActive {true}; diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 98d37119..ab20ff6f 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -209,6 +209,7 @@ namespace olc::host emscripten_set_touchstart_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchmove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchend_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + emscripten_set_touchcancel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); // Canvas Focus Callbacks emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); @@ -495,31 +496,41 @@ namespace olc::host return EM_TRUE; } - //TY Bispoo EM_BOOL Host_Web_Emscripten::touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) { - // TODO: Implement touch more effectively. - // For now, emulate single pointer mouse. - CallbackData* pCallbackData = reinterpret_cast(userData); - // Move - if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) - { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - } - - // Start - if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + for(int i = 0; i < e->numTouches; ++i) { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - olc_OnMouseButton(pCallbackData->pWindow, 0, true); + const EmscriptenTouchPoint& touch = e->touches[i]; + + if(touch.isChanged) { + bool is_start = (eventType == EMSCRIPTEN_EVENT_TOUCHSTART); + bool is_end = ((eventType == EMSCRIPTEN_EVENT_TOUCHEND) || (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)); + pCallbackData->pWindow->olc_OnTouch( + touch.identifier, + olc::vf2d(touch.targetX, touch.targetY), + is_start, + is_end, + olc::vf2d(1.0f, 1.0f) + ); + } } - // End - if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) - { - olc_OnMouseButton(pCallbackData->pWindow, 0, false); + // Emscripten seems to sometimes "leak" touches and not report them as ending. Iterate through the touches in the system and end + // any touches that are no longer being reported + for(const auto touch_id : pCallbackData->pWindow->touch.GetTouchIDs()) { + // if this touch is not reported anymore + if (std::find_if(e->touches, e->touches + e->numTouches, [=](const EmscriptenTouchPoint& x){return x.identifier == touch_id;}) == e->touches + e->numTouches) { + const auto& touch = pCallbackData->pWindow->touch.GetTouch(touch_id); + pCallbackData->pWindow->olc_OnTouch( + touch_id, + touch.position, + false, + true, + touch.size + ); + } } return EM_TRUE; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index b95c0048..8340cc54 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -306,7 +306,11 @@ namespace olc::host // Keep client size as requested RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - int width = rWndRect.right - rWndRect.left; + + // +1 Hack to remove black bar between client and title bar for "perfect" window + // sizes anyway. This could be DPI related on later windows, but this makes it look + // tidier for "normal" applications + int width = rWndRect.right - rWndRect.left + 1; int height = rWndRect.bottom - rWndRect.top; // Create the actual OS window, return a handle @@ -622,6 +626,146 @@ namespace olc::host 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... diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 469e309f..cbd8c299 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -33,7 +33,11 @@ #ifdef HAVE_MSMF #define _WIN32_WINNT 0x0600 // Windows Vista #else - #define _WIN32_WINNT 0x0500 // Windows 2000 + //#define _WIN32_WINNT 0x0603 // Windows 8.1 + + // Windows 10 Minimum + #define WINVER 0x0A00 + #define _WIN32_WINNT 0x0A00 #endif #endif @@ -44,6 +48,8 @@ #include #include +#include +#include #undef _WINSOCKAPI_ //! END WINAPI_CONFIG diff --git a/dev/src/hw_touch.cpp b/dev/src/hw_touch.cpp new file mode 100644 index 00000000..8da7cd20 --- /dev/null +++ b/dev/src/hw_touch.cpp @@ -0,0 +1,57 @@ +#include "hw_touch.h" + +//! START IMPLEMENTATION +namespace olc::hw +{ + const std::vector& Touch::GetTouchIDs() const + { + static std::vector touchIDs; + touchIDs.clear(); + for (const auto& [id, index] : touches_cache) + touchIDs.push_back(id); + return touchIDs; + } + + const bool Touch::IsTouch(const uint32_t nID) const + { + return touches_cache.contains(nID); + } + + const TouchPoint& Touch::GetTouch(const uint32_t nID) const + { + return touches_cache.at(nID); + } + + void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) + { + // If touch doesnt exist, this will create one or + // it will update an existing one + touches_live[nID].position = vPos; + touches_live[nID].size = vSize; + touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; + touches_live[nID].bReleased = bRelease; + touches_live[nID].bHeld = !bRelease; + touches_live[nID].pressure = pressure; + touches_live[nID].orientation = orientation; + touches_live[nID].tilt = tilt; + touches_live[nID].bStylus = stylus; + } + + void Touch::UpdateState() + { + touches_cache = touches_live; + + // Remove released touches from live state + for (auto it = touches_live.begin(); it != touches_live.end(); ) + { + if (it->second.bPressed) + it->second.bPressed = false; + + if (it->second.bReleased) + it = touches_live.erase(it); + else + ++it; + } + } +} +//! END IMPLEMENTATION diff --git a/dev/src/hw_touch.h b/dev/src/hw_touch.h index 6f70f09b..ed2076ae 100644 --- a/dev/src/hw_touch.h +++ b/dev/src/hw_touch.h @@ -1 +1,64 @@ #pragma once + +//! START STDHEADER +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "config.h" +#include "hw_input.h" +#include "vector2d.h" +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_HW_TOUCH_DECLARED) +namespace olc +{ + // Forward declare for friendship + class Window; + class PGEWindow; + + + namespace hw + { + struct TouchPoint + { + olc::vf2d position; + olc::vf2d size; + float pressure = 0; + float orientation = 0; + olc::vf2d tilt; + bool bPressed = false; + bool bReleased = false; + bool bHeld = false; + bool bStylus = false; + }; + + class Touch + { + friend class olc::Window; + friend class olc::PGEWindow; + + public: + Touch() = default; + + public: + const std::vector& GetTouchIDs() const; + const bool IsTouch(const uint32_t nID) const; + const TouchPoint& GetTouch(const uint32_t nID) const; + + protected: + std::unordered_map touches_cache{}; + std::unordered_map touches_live{}; + + + private: + void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); + void UpdateState(); + }; + } +} +#define PGE_HW_TOUCH_DECLARED 1 +#endif diff --git a/dev/src/image.cpp b/dev/src/image.cpp index b701c502..beadcc5e 100644 --- a/dev/src/image.cpp +++ b/dev/src/image.cpp @@ -11,7 +11,7 @@ namespace olc { }*/ - bool Image::Create(const olc::vi2d& size, const ImageConfig& cfg) + bool Image::CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg) { dimensions = size; config = cfg; diff --git a/dev/src/image.h b/dev/src/image.h index d03181b5..cab7215b 100644 --- a/dev/src/image.h +++ b/dev/src/image.h @@ -31,6 +31,13 @@ namespace olc struct ImageRegion; + class PGEWindow; + + namespace imload + { + class ImageLoader; + } + class Image { public: @@ -39,7 +46,12 @@ namespace olc virtual ~Image() = default; public: - bool Create(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + // Creates nothing but an array of pixels in system memory. Normal users + // should never need to call this method. If you want to construct an + // olc::Image object, use factory methods in olc::PGEWindow + // CreateImage(...) + bool CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + public: // Returns size (x, y) in pixels diff --git a/dev/src/imload_android.cpp b/dev/src/imload_android.cpp index 3226115c..ee40b34e 100644 --- a/dev/src/imload_android.cpp +++ b/dev/src/imload_android.cpp @@ -39,7 +39,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); @@ -95,7 +95,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); diff --git a/dev/src/imload_lib_png.cpp b/dev/src/imload_lib_png.cpp index 5fadfefa..8bb10f4f 100644 --- a/dev/src/imload_lib_png.cpp +++ b/dev/src/imload_lib_png.cpp @@ -94,7 +94,7 @@ namespace olc::imload png_read_info(png, info); png_byte color_type; png_byte bit_depth; - image.Create( + image.CreateNoGPU( { static_cast(png_get_image_width(png, info)), static_cast(png_get_image_height(png, info)) diff --git a/dev/src/imload_macos.cpp b/dev/src/imload_macos.cpp index 94734e6e..6fb551a1 100644 --- a/dev/src/imload_macos.cpp +++ b/dev/src/imload_macos.cpp @@ -33,7 +33,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -73,7 +73,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -94,11 +94,13 @@ namespace olc::imload bool ImageLoader_MacOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { + olc_IgnoreUnused(image, sFileName); return false; } bool ImageLoader_MacOS::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) { + olc_IgnoreUnused(image, data); return false; } } diff --git a/dev/src/imload_stb_image.cpp b/dev/src/imload_stb_image.cpp index 39ff2baa..85e61c9e 100644 --- a/dev/src/imload_stb_image.cpp +++ b/dev/src/imload_stb_image.cpp @@ -31,7 +31,7 @@ namespace olc::imload return false; } - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), bytes, width * height * 4); delete[] bytes; @@ -48,7 +48,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; @@ -65,7 +65,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; diff --git a/dev/src/imload_wingdi.cpp b/dev/src/imload_wingdi.cpp index b947721c..e6c57512 100644 --- a/dev/src/imload_wingdi.cpp +++ b/dev/src/imload_wingdi.cpp @@ -95,7 +95,7 @@ namespace olc::imload bool ImageLoader_WinGDI::DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp) { // Need to swizzle each pixel... - image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); + image.CreateNoGPU(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); for (int y = 0; y < image.Size().y; y++) for (int x = 0; x < image.Size().x; x++) { diff --git a/dev/src/pixel.h b/dev/src/pixel.h index 54d7b154..95bb5de5 100644 --- a/dev/src/pixel.h +++ b/dev/src/pixel.h @@ -17,6 +17,10 @@ //! START DECLARATION #if !defined(PGE_PIXEL_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" // Silence warnings about implicitly generated copy constructor for Pixel +#endif namespace olc { class Pixel @@ -324,6 +328,10 @@ namespace olc } } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + #define PGE_PIXEL_DECLARED 1 #endif //! END DECLARATION \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 99c88e02..21767228 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -8,13 +8,13 @@ olcPixelGameEngine3.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v3.00 Beta A | + | OneLoneCoder Pixel Game Engine v3.00 Beta B | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ What is this? ~~~~~~~~~~~~~ - olc::PixelGameEngine is a single file, cross platform graphics and userinput + olc::PixelGameEngine is a single file, cross platform graphics and user input framework used for games, visualisations, algorithm exploration and learning. It was developed by YouTuber "javidx9" as an assistive tool for many of his videos. The goal of this project is to provide high speed graphics with @@ -83,13 +83,15 @@ AI Disclosure ~~~~~~~~~~~~~ Parts of this code may have been generated with the assistance of AI tools. Instances - of such usage have typically been restricted to the tedious and repetitious through + of such usage would have typically been restricted to the tedious and repetitious through the use of auto-completion and other small code generation helpers. This community driven project has been developed on numerous platforms, across countless tools and environments, by different people over a number of years. As such, it is impossible - to categorically state which sections of code may have had AI assistance. Regardless, - the entire codebase has been architected, reviewed and tested by human developers - mostly for fun and learning purposes, and is intended to be used in that spirit. + to know which sections of code may have had AI assistance. Regardless, the entire codebase + has been architected, implemented, reviewed and tested by human developers mostly for fun + and learning purposes, and it is intended to be used in that spirit. + + "You know you don't have to use AI if you like coding right?" - javidx9 Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @@ -143,6 +145,8 @@ //! GRAB hw_keyboard.h DECLARATION +//! GRAB hw_touch.h DECLARATION + //! GRAB window.h DECLARATION //! GRAB host_iface.h DECLARATION @@ -321,6 +325,11 @@ #define PGE_HW_KEYBOARD_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_TOUCH_IMPLEMENTED) +//! GRAB hw_touch.cpp IMPLEMENTATION +#define PGE_HW_TOUCH_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) //! GRAB window.cpp IMPLEMENTATION #define PGE_WINDOW_IMPLEMENTED 1 diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index c4cec130..68b92edb 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -17,6 +17,10 @@ //! START DECLARATION #if !defined(PGE_TRANSFORM2D_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wignored-qualifiers" // Silence warnings about ignored qualifiers in olc::t_2d +#endif namespace olc { namespace internal @@ -259,6 +263,9 @@ namespace olc typedef t_2d tf2d; typedef t_2d td2d; } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif #define PGE_TRANSFORM2D_DECLARED 1 #endif -//! END DECLARATION \ No newline at end of file +//! END DECLARATION diff --git a/dev/src/window.cpp b/dev/src/window.cpp index 466f7eca..9b70a3de 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -38,6 +38,7 @@ namespace olc bool Window::olc_OnMouseMove(const olc::vi2d& vMousePos) { + // Handled by PGEWindow mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize())); return true; } @@ -77,6 +78,12 @@ namespace olc return true; } + bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) + { + // Handled by PGEWindow + return true; + } + bool Window::olc_ShouldRemove() const { return bShouldRemove; diff --git a/dev/src/window.h b/dev/src/window.h index dbc20c37..46adb50f 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -16,6 +16,7 @@ #include "draw.h" #include "hw_mouse.h" #include "hw_keyboard.h" +#include "hw_touch.h" //! END CUSTOMHEADER //! START DECLARATION @@ -42,6 +43,7 @@ namespace olc { class Mouse; class Keyboard; + class Touch; } struct WindowConfig @@ -84,6 +86,10 @@ namespace olc // Set Keyboard State virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); + // Touch Handler + virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); + public: @@ -133,6 +139,7 @@ namespace olc protected: olc::hw::Mouse mouse; olc::hw::Keyboard keyboard; + olc::hw::Touch touch; }; } diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index b2fb1a3b..058008d6 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -364,8 +364,8 @@ class Example : public olc::PixelGameEngine - // Draw 3 triangle points - for (int i = 0; i < vecTestPoints.size(); i++) + // Draw 3 triangle points (change to size_t to remove warning -Wsign-compare) + for (size_t i = 0; i < vecTestPoints.size(); i++) { //draw.Rect(vecTestPoints[i] - (vTestPointSize * 0.5f), vTestPointSize, olc::Colour::MAGENTA); @@ -398,8 +398,8 @@ class Example : public olc::PixelGameEngine t2.translate(0.0f, 6.0f, 0.0f); t3.translate(7.0f, 0.0f, 0.0f); - olc::vf4d v1 = { 1,0,0,1 }; - olc::vf4d v2 = t3 * t2 * t1 * v1; + //olc::vf4d v1 = { 1,0,0,1 }; // Temp remove unused variable warning + //olc::vf4d v2 = t3 * t2 * t1 * v1; // Temp remove unused variable warning olc::mf4d matProj; @@ -452,7 +452,8 @@ class Example : public olc::PixelGameEngine if (mouse.GetButton(0).bPressed) { nSelectedPoint = -1; - for (int i = 0; i < vecTestPoints.size(); i++) + // change to size_t to remove warning -Wsign-compare + for (int i = 0; i < (int)vecTestPoints.size(); i++) { if ((mouse.GetPosition() - vecTestPoints[i]).mag2() < 9) { @@ -473,6 +474,13 @@ class Example : public olc::PixelGameEngine draw.String({ 10, 10 }, mouse.GetPosition().str(), olc::Colour::BLACK); + auto touches = touch.GetTouchIDs(); + draw.String({ 10.0f, 20.0f }, "Touch Points: " + std::to_string(touches.size()), olc::Colour::WHITE); + + + if(!touches.empty()) + draw.FilledCircle(touch.GetTouch(touches[0]).position, 10, olc::Colour::MAGENTA, olc::Pixel(255, 0, 255, 0), olc::Colour::WHITE, 16); + if (keyboard.GetKey(olc::Key::ESCAPE).bPressed) return false; // End application diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index 904e7939..f8fd6154 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 460496082E362F190047E223 /* test_mh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495FB2E300E0F0047E223 /* test_mh.cpp */; }; + 46152BD72FE2DD1E00DCAC34 /* hw_touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */; }; 462680A42E69D03800799C36 /* image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495EC2E300E000047E223 /* image.cpp */; }; 462680A52E69D15C00799C36 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; @@ -66,6 +67,8 @@ 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 = ""; }; + 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hw_touch.h; sourceTree = ""; }; + 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_touch.cpp; sourceTree = ""; }; 462680D02E6C3C4500799C36 /* gpu_iface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpu_iface.cpp; sourceTree = ""; }; 466C38492F3A0B0E00F75AF4 /* matrix4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix4d.h; sourceTree = ""; }; 466C384A2F3A0B0E00F75AF4 /* vector4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vector4d.h; sourceTree = ""; }; @@ -161,6 +164,8 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( + 46152BD52FE2DD1E00DCAC34 /* hw_touch.h */, + 46152BD62FE2DD1E00DCAC34 /* hw_touch.cpp */, 466C385F2F41D69000F75AF4 /* draw_batch.cpp */, 466C385B2F41D5D400F75AF4 /* draw.h */, 466C385C2F41D5D400F75AF4 /* draw.cpp */, @@ -283,6 +288,7 @@ 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */, 46C8F5B92E7DA67900C11019 /* imload_macos.cpp in Sources */, 460496082E362F190047E223 /* test_mh.cpp in Sources */, + 46152BD72FE2DD1E00DCAC34 /* hw_touch.cpp in Sources */, 466C385E2F41D5D400F75AF4 /* draw.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b81dc40c..9e351a6e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -28,10 +28,7 @@ if(UNIX AND BUILD_WAYLAND) pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) pkg_check_modules(EGL REQUIRED egl) pkg_check_modules(WAYLAND_CURSOR REQUIRED wayland-cursor) - - if(NOT DISABLE_LIBDECOR) pkg_check_modules(LIBDECOR REQUIRED IMPORTED_TARGET libdecor-0) - endif() find_package(OpenGL REQUIRED) find_package(PNG REQUIRED) @@ -69,22 +66,13 @@ if(UNIX AND BUILD_WAYLAND) generate_wayland_protocol("xdg-shell" "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") generate_wayland_protocol("pointer-warp" "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") - generate_wayland_protocol("tablet" "/usr/share/wayland-protocols/stable/tablet/tablet-v2.xml") - + # Create a library for the wayland protocol files add_library(wayland_protocols INTERFACE) target_link_libraries(wayland_protocols INTERFACE wayland_xdg-shell wayland_pointer-warp - wayland_tablet ) - - if(NOT FORCE_WAYLAND_LIBDECOR) - generate_wayland_protocol("xdg-decoration" "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") - target_link_libraries(wayland_protocols INTERFACE - wayland_xdg-decoration - ) - endif() target_include_directories(wayland_protocols INTERFACE ${CMAKE_CURRENT_BINARY_DIR} @@ -116,12 +104,6 @@ foreach(source ${SOURCES}) if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) if(BUILD_WAYLAND) target_compile_definitions(${EXE_NAME} PRIVATE OLC_HOST=3) - if(FORCE_WAYLAND_LIBDECOR) - target_compile_definitions(${EXE_NAME} PRIVATE FORCE_WAYLAND_LIBDECOR=1) - endif() - if(DISABLE_LIBDECOR) - target_compile_definitions(${EXE_NAME} PRIVATE DISABLE_LIBDECOR=1) - endif() target_link_libraries(${EXE_NAME} PRIVATE wayland_protocols @@ -135,17 +117,14 @@ foreach(source ${SOURCES}) Threads::Threads ) - if(NOT DISABLE_LIBDECOR) - target_link_libraries(${EXE_NAME} PRIVATE - PkgConfig::LIBDECOR - ) - endif() + target_link_libraries(${EXE_NAME} PRIVATE PkgConfig::LIBDECOR ) else() # x11 target_link_libraries(${EXE_NAME} PRIVATE png) target_link_libraries(${EXE_NAME} PRIVATE GL) target_link_libraries(${EXE_NAME} PRIVATE pthread) target_link_libraries(${EXE_NAME} PRIVATE X11) + target_link_libraries(${EXE_NAME} PRIVATE Xi) endif() endif() diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index a51cdbc3..1839d6de 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -110,7 +110,7 @@ class Example_3DCube : public olc::PixelGameEngine // so cull counter-clockwise faces to show it off in all its glory! This is // counter to OpenGL's default culling mode, so it's a good test of the culling // system as well. - draw.SetCullMode(olc::GPUTask::CullMode::CounterClockWise); + draw.SetCullMode(olc::CullMode::CounterClockWise); // Draw the cube using the sanity cube's layout, and vectors of vertices, colours // and texture coordinates. diff --git a/examples/olcPGE3_BlendingModes.cpp b/examples/olcPGE3_BlendingModes.cpp new file mode 100644 index 00000000..e3b6d0b7 --- /dev/null +++ b/examples/olcPGE3_BlendingModes.cpp @@ -0,0 +1,172 @@ +/* + olc::PixelGameEngine3 Example - Blending Modes + + Draws a few lines using different styles and techniques + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Example application demonstrating various blend modes. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_BlendingModes : public olc::PixelGameEngine +{ +public: + Example_BlendingModes() + { + sAppName = "Example - Blending Modes"; + } + + struct sGel + { + olc::vf2d vPos; + olc::Pixel col; + }; + + std::vector vecGels; + olc::vf2d vGelSize = { 32.0f, 32.0f }; + sGel* pSelectedGel = nullptr; + olc::BlendMode currentBlendMode = olc::BlendMode::Alpha; + olc::Image imgTest; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Create "gels" - these are just coloured rectangles that we + // will draw with different blend modes + vecGels.push_back({ { 98.0f, 74.0f }, olc::Colour::RED }); + vecGels.push_back({ { 114.0f, 57.0f }, olc::Colour::GREEN }); + vecGels.push_back({ { 118.0f, 80.0f }, olc::Colour::BLUE }); + vecGels.push_back({ { 145.0f, 178.0f }, olc::Colour::WHITE }); + vecGels.push_back({ { 113.0f, 135.0f }, olc::Colour::BLACK }); + vecGels.push_back({ { 77.0f, 176.0f }, olc::Pixel(128, 128, 128) }); + vecGels.push_back({ { 47.0f, 128.0f }, olc::Pixel(255, 255, 255, 64) }); + + // Last gel uses an image for complexity + vecGels.push_back({ { 18.0f, 169.0f }, olc::Pixel(255,255,255,64)}); + CreateImageFromFile(imgTest, "./assets/minsanity_texture.png"); + + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Draw Colour Bars + draw.FilledRect({ 0.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::VERY_DARK_RED, olc::Colour::VERY_DARK_RED, + olc::Colour::RED, olc::Colour::RED); + draw.FilledRect({ 32.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::VERY_DARK_GREEN, olc::Colour::VERY_DARK_GREEN, + olc::Colour::GREEN, olc::Colour::GREEN); + draw.FilledRect({ 64.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::VERY_DARK_BLUE, olc::Colour::VERY_DARK_BLUE, + olc::Colour::BLUE, olc::Colour::BLUE); + draw.FilledRect({ 96.0f, 0.0f }, { 32.0f, 240.0f }, + olc::Colour::BLACK); + draw.FilledRect({ 128.0f, 0.0f }, { 128.0f, 240.0f }, + olc::Colour::WHITE, olc::Colour::WHITE, + olc::Colour::BLACK, olc::Colour::BLACK); + + // TADA!! Setting the Blend Mode changes how the GPU draws pixels to the screen. + // This is a global state, so all drawing operations will be affected by this + // setting until it is changed again + draw.SetBlendMode(currentBlendMode); + + // Note drawing happens as normal, the GPU has been instructed to + // use the current blend mode when drawing pixels + + // Draw Gels with appropriate mode + int gelCount = 0; + for (auto& gel : vecGels) + { + if (gelCount == vecGels.size() - 1) + { + // Gel is a texture + draw.ImageRect(imgTest, gel.vPos, vGelSize); + } + else // Gel is just a coloured rectangle + draw.FilledRect(gel.vPos, vGelSize, gel.col); + + gelCount++; + } + + + + // Switch back to alpha mode (aka Normal) for UI + draw.SetBlendMode(olc::BlendMode::Alpha); + + // Hilioght frame of gel and detect if mouse is over it + for (auto& gel : vecGels) + { + draw.Rect(gel.vPos, vGelSize, olc::Colour::WHITE); + + if (mouse.GetPosition().x >= gel.vPos.x && mouse.GetPosition().x < gel.vPos.x + vGelSize.x && + mouse.GetPosition().y >= gel.vPos.y && mouse.GetPosition().y < gel.vPos.y + vGelSize.y) + { + if (mouse.GetButton(0).bPressed) + { + pSelectedGel = ⋛ + } + } + } + + // Handle mouse + if(pSelectedGel && mouse.GetButton(0).bHeld) + { + pSelectedGel->vPos = mouse.GetPosition() - vGelSize / 2.0f; + } + + if (mouse.GetButton(0).bReleased) + { + pSelectedGel = nullptr; + } + + + // Change blend mode with number keys + if (keyboard.GetKey(olc::Key::K1).bPressed) + currentBlendMode = olc::BlendMode::Alpha; + if (keyboard.GetKey(olc::Key::K2).bPressed) + currentBlendMode = olc::BlendMode::Multiplicative; + if (keyboard.GetKey(olc::Key::K3).bPressed) + currentBlendMode = olc::BlendMode::Additive; + + + // Draw UI + draw.String({ 10.0f, 2.0f }, "1: Alpha Blend", + currentBlendMode == olc::BlendMode::Alpha ? olc::Colour::YELLOW : olc::Colour::WHITE); + draw.String({ 10.0f, 12.0f }, "2: Multiplicative Blend", + currentBlendMode == olc::BlendMode::Multiplicative ? olc::Colour::YELLOW : olc::Colour::WHITE); + draw.String({ 10.0f, 22.0f }, "3: Additive Blend", + currentBlendMode == olc::BlendMode::Additive ? olc::Colour::YELLOW : olc::Colour::WHITE); + draw.String({ 10.0f, 32.0f }, "Mouse to drag gels", olc::Colour::WHITE); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_BlendingModes demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/examples/olcPGE3_CirclesEllipses.cpp b/examples/olcPGE3_CirclesEllipses.cpp index 51a6fabf..dbd1e58f 100644 --- a/examples/olcPGE3_CirclesEllipses.cpp +++ b/examples/olcPGE3_CirclesEllipses.cpp @@ -28,6 +28,8 @@ class Example_RoundThings : public olc::PixelGameEngine std::vector vecBubbleVel; std::vector vecBubbleCol; + float fRadius = 4.0f; + public: // Called once at the start, so create things here bool OnUserCreate() override @@ -138,6 +140,18 @@ class Example_RoundThings : public olc::PixelGameEngine } + if (mouse.GetWheel() < 0) + fRadius -= 1.0f; + if (mouse.GetWheel() > 0) + fRadius += 1.0f; + + fRadius = std::clamp(fRadius, 0.5f, 120.0f); + + // Draw a small circle at mouse location + draw.FilledCircle(mouse.GetPosition().round(), fRadius, olc::Colour::RED); + draw.Circle(mouse.GetPosition().round(), fRadius, olc::Colour::WHITE); + + // Successful frame return true; } diff --git a/examples/olcPGE3_Extensions.cpp b/examples/olcPGE3_Extensions.cpp index 582e5633..b6a36757 100644 --- a/examples/olcPGE3_Extensions.cpp +++ b/examples/olcPGE3_Extensions.cpp @@ -24,7 +24,7 @@ class ExamplePGEX : public olc::PGEWindowExtension { // Called when extension is installed, usually in PGEWindow Constructor // Return true to continue application - virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) + virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) override { return true; } @@ -98,6 +98,8 @@ class Example_Extensions : public olc::PixelGameEngine // We need to create an instance of our extension class, and then install it in the PGE constructor ExamplePGEX pgex; + float fTotalTime = 0.0f; + public: // Called once at the start, so create things here bool OnUserCreate() override @@ -115,7 +117,7 @@ class Example_Extensions : public olc::PixelGameEngine draw.String({ 10.0f, 10.0f }, "This text is drawn from the\nmain application!\n\nMouse Move in X To Warp Time", olc::Colour::WHITE); // Draw a clocking line to illustrate the passage of time... - float fTotalTime = TotalTimeElapsed(); + fTotalTime += fElapsedTime; draw.Line(GetScreen().Size() / 2, olc::vf2d(cos(fTotalTime), sin(fTotalTime)) * 50.0f + GetScreen().Size() / 2, olc::Colour::GREEN); // Successful frame @@ -139,4 +141,4 @@ int main() } return 0; -} \ No newline at end of file +} diff --git a/examples/olcPGE3_Polygons.cpp b/examples/olcPGE3_Polygons.cpp index 7f291a08..300330b2 100644 --- a/examples/olcPGE3_Polygons.cpp +++ b/examples/olcPGE3_Polygons.cpp @@ -137,11 +137,16 @@ class Example_Pixels : public olc::PixelGameEngine std::vector polygon3_points(vecPolygonPoints.begin() + 16, vecPolygonPoints.end()); draw.FilledPolygon(olc::Structure::Strip, polygon3_points, vecPolygonColours); + // Draw polygon 3, a strip, filled, gradient + draw.Polygon(olc::Structure::Strip, polygon3_points, olc::Colour::WHITE); + + + // Draw Control Points for (int i = 0; i < vecPolygonPoints.size(); i++) { - draw.Circle(vecPolygonPoints[i], 4, olc::Colour::RED); + draw.Circle(vecPolygonPoints[i].floor(), 4, olc::Colour::RED); } // Successful frame diff --git a/examples/olcPGE3_Touch.cpp b/examples/olcPGE3_Touch.cpp new file mode 100644 index 00000000..c611e66e --- /dev/null +++ b/examples/olcPGE3_Touch.cpp @@ -0,0 +1,92 @@ +/* + olc::PixelGameEngine3 Example - Touch + + Demonstrates touch input handling + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Example application demonstrating touch input handling. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_Touch : public olc::PixelGameEngine +{ +public: + Example_Touch() + { + sAppName = "Example - Touch"; + } + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Nothing to do here, so return true + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // All "touches" get given a unique ID and are tracked by the OS. This ID + // could be anything, so we never really want to work with t directly. + auto touches = touch.GetTouchIDs(); + + // Display total number of touches + draw.String({ 2.0f, 2.0f }, "Touch Points: " + std::to_string(touches.size()), olc::Colour::WHITE); + + + // Draw Touches + for (auto id : touches) + { + // Get an olc::TouchPoint for this touch + auto& t = touch.GetTouch(id); + + if (t.bStylus) + { + // Touch comes from a stylus, so draw a small circle at the tip position, with size based on the touch size + draw.Circle(t.position, t.pressure * 10.0f, olc::Colour::WHITE); + + // Stylus *MAY* also have an orientation, so draw a line from the tip in the direction of the stylus + draw.Line(t.position, t.position + olc::vf2d{ std::cos(t.orientation), std::sin(t.orientation) } * 10.0f * t.pressure, olc::Colour::WHITE); + } + else + { + // Touch comes from a finger, so draw a circle at the touch position, with size based on the touch size + draw.FilledRect(t.position, t.size, olc::Colour::YELLOW); + // Draw a smaller circle at the touch position, with size based on the touch pressure + draw.FilledRect(t.position, t.size, olc::Colour::YELLOW); + } + } + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Touch demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 37104252..a8a4da8b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8,13 +8,13 @@ olcPixelGameEngine3.h +-------------------------------------------------------------+ - | OneLoneCoder Pixel Game Engine v3.00 Beta A | + | OneLoneCoder Pixel Game Engine v3.00 Beta B | | "What do you need? Pixels... Lots of Pixels..." - javidx9 | +-------------------------------------------------------------+ What is this? ~~~~~~~~~~~~~ - olc::PixelGameEngine is a single file, cross platform graphics and userinput + olc::PixelGameEngine is a single file, cross platform graphics and user input framework used for games, visualisations, algorithm exploration and learning. It was developed by YouTuber "javidx9" as an assistive tool for many of his videos. The goal of this project is to provide high speed graphics with @@ -83,13 +83,15 @@ AI Disclosure ~~~~~~~~~~~~~ Parts of this code may have been generated with the assistance of AI tools. Instances - of such usage have typically been restricted to the tedious and repetitious through + of such usage would have typically been restricted to the tedious and repetitious through the use of auto-completion and other small code generation helpers. This community driven project has been developed on numerous platforms, across countless tools and environments, by different people over a number of years. As such, it is impossible - to categorically state which sections of code may have had AI assistance. Regardless, - the entire codebase has been architected, reviewed and tested by human developers - mostly for fun and learning purposes, and is intended to be used in that spirit. + to know which sections of code may have had AI assistance. Regardless, the entire codebase + has been architected, implemented, reviewed and tested by human developers mostly for fun + and learning purposes, and it is intended to be used in that spirit. + + "You know you don't have to use AI if you like coding right?" - javidx9 Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @@ -280,6 +282,8 @@ #define OLC_MOUSE_BUTTONS 5 +#define OLC_MAX_TOUCHPOINTS 4 + #define OLC_DEFAULT_KEYBOARD_LAYOUT olc::KeyboardLayout::QWERTY_UK #define OLC_GPU_MAX_VERTICES 8192 @@ -290,11 +294,20 @@ #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " +#if OLC_HOST == OLC_HOST_MACOS +// De-Noise in clang (C++20) MacOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" // Allow unknown pragmas for compatibility with different compilers +#pragma clang diagnostic ignored "-Wgnu-anonymous-struct" // Allow anonymous structs in unions + +#endif + // De-Noise in MSVC (C++20) /Wall #pragma warning(disable:4820) // Disable Padding Warnings #pragma warning(disable:5045) // Disable Spectre Mitigation Warnings #pragma warning(disable:4514) // Disable Unreferenced Inline Function Warnings + template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} @@ -328,6 +341,10 @@ inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} #if !defined(PGE_PIXEL_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" // Silence warnings about implicitly generated copy constructor for Pixel +#endif namespace olc { class Pixel @@ -635,6 +652,10 @@ namespace olc } } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + #define PGE_PIXEL_DECLARED 1 #endif @@ -1965,6 +1986,10 @@ namespace olc #endif #if !defined(PGE_TRANSFORM2D_DECLARED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wignored-qualifiers" // Silence warnings about ignored qualifiers in olc::t_2d +#endif namespace olc { namespace internal @@ -2207,6 +2232,9 @@ namespace olc typedef t_2d tf2d; typedef t_2d td2d; } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif #define PGE_TRANSFORM2D_DECLARED 1 #endif @@ -2225,6 +2253,13 @@ namespace olc struct ImageRegion; + class PGEWindow; + + namespace imload + { + class ImageLoader; + } + class Image { public: @@ -2233,7 +2268,12 @@ namespace olc virtual ~Image() = default; public: - bool Create(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + // Creates nothing but an array of pixels in system memory. Normal users + // should never need to call this method. If you want to construct an + // olc::Image object, use factory methods in olc::PGEWindow + // CreateImage(...) + bool CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); + public: // Returns size (x, y) in pixels @@ -2479,6 +2519,29 @@ namespace olc // Vertex buffer is a series of discrete triangles List, }; + + // Define blend modes for drawing operations + enum class BlendMode : uint8_t + { + // Alpha blend source and destination pixels based on source alpha + Alpha = 0, + // Additively blend source and destination pixels together + Additive, + // Multiplicatively blend source and destination pixels together + Multiplicative, + // No blending, just overwrite pixels with source colour + None, + }; + + enum class CullMode : uint8_t + { + // No + None = 0, + // Cull if vertices are listed in clockwise order + ClockWise, + // Cull if vertices are listed in anticlockwise order + CounterClockWise + }; // This is the default "packet" of work that is sent to // a GPU for drawing. Various drawing operations throughout @@ -2518,8 +2581,8 @@ namespace olc // Use depth components bool bDepth = false; - // Use hardware wire drawing - bool bWireframe = false; + // Constrain with pixel biases + bool bPixelConstrained = true; // Define how to interpret vertex buffer bool bIs3D = false; @@ -2527,21 +2590,17 @@ namespace olc // Overall biasing colour (great for blends) olc::Pixel tint = olc::Colour::WHITE; + // Texture to be used for drawing (if any) (in slot #0) olc::Image* pImage = nullptr; // Define super structure to be drawn Structure structure = Structure::Fan; // Define if GPU should face cull based on winding order - enum class CullMode : uint8_t - { - // No - None = 0, - // Cull if vertices are listed in clockwise order - ClockWise, - // Cull if vertices are listed in anticlockwise order - CounterClockWise - } cullmode = CullMode::None; + CullMode cullmode = CullMode::None; + + // Define blend mode for drawing + BlendMode blendmode = BlendMode::Alpha; }; } #define PGE_GPUTASK_DECLARED 1 @@ -3349,7 +3408,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE); public: // Applied Rendering Modes - void SetCullMode(const olc::GPUTask::CullMode mode); + void SetCullMode(const olc::CullMode mode); + void SetBlendMode(const olc::BlendMode mode); void EnableDepth(const bool bEnable); void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); @@ -3430,7 +3490,16 @@ namespace olc GPUTask TaskDrawLine( const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true, + const bool looped = false); + + GPUTask TaskDrawLine( + const std::vector& vPoints, + const olc::Pixel colour, + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true, + const bool looped = false); GPUTask TaskDrawPolygon( olc::Structure structure, @@ -3448,13 +3517,15 @@ namespace olc olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskFillPolygon( olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, - const olc::Pixel tint = olc::Colour::WHITE); + const olc::Pixel tint = olc::Colour::WHITE, + const bool constrain = true); GPUTask TaskTexturedPolygon( olc::Structure structure, @@ -3549,7 +3620,8 @@ namespace olc olc::vi2d vViewportSize = { 0, 0 }; - olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + olc::CullMode cullMode = olc::CullMode::None; + olc::BlendMode blendMode = olc::BlendMode::Alpha; bool bDepth = true; @@ -3749,6 +3821,57 @@ namespace olc #define PGE_HW_KEYBOARD_DECLARED 1 #endif +#if !defined(PGE_HW_TOUCH_DECLARED) +namespace olc +{ + // Forward declare for friendship + class Window; + class PGEWindow; + + + namespace hw + { + struct TouchPoint + { + olc::vf2d position; + olc::vf2d size; + float pressure = 0; + float orientation = 0; + olc::vf2d tilt; + bool bPressed = false; + bool bReleased = false; + bool bHeld = false; + bool bStylus = false; + }; + + class Touch + { + friend class olc::Window; + friend class olc::PGEWindow; + + public: + Touch() = default; + + public: + const std::vector& GetTouchIDs() const; + const bool IsTouch(const uint32_t nID) const; + const TouchPoint& GetTouch(const uint32_t nID) const; + + protected: + std::unordered_map touches_cache{}; + std::unordered_map touches_live{}; + + + private: + void UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); + void UpdateState(); + }; + } +} +#define PGE_HW_TOUCH_DECLARED 1 +#endif + #if !defined(PGE_WINDOW_DECLARED) namespace olc { @@ -3772,6 +3895,7 @@ namespace olc { class Mouse; class Keyboard; + class Touch; } struct WindowConfig @@ -3814,6 +3938,10 @@ namespace olc // Set Keyboard State virtual bool olc_OnKeyPress(const olc::Key key, const bool bPressed); + // Touch Handler + virtual bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, + const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }); + public: @@ -3863,6 +3991,7 @@ namespace olc protected: olc::hw::Mouse mouse; olc::hw::Keyboard keyboard; + olc::hw::Touch touch; }; } @@ -4041,11 +4170,11 @@ namespace olc // These three are inherited from WindowConfig // Start in full-screen mode - bool bFullScreen = false; - // Allow full screen as an option with ALT-ENTER - bool bFullScreenable = true; - // Allow the window to be resized by user - bool bResizeable = true; + // bool bFullScreen = false; + // // Allow full screen as an option with ALT-ENTER + // bool bFullScreenable = true; + // // Allow the window to be resized by user + // bool bResizeable = true; // Allow the window border to be hidden by user bool bShowWindowBorder = true; // Allow the window title bar to be hidden by user @@ -4092,6 +4221,9 @@ namespace olc public: // olc::Image Handling + + // These are the preferred methods to create olc::Image objects + // Create an image resource bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); // Create an image resource based on an image file asset on disk @@ -4127,6 +4259,7 @@ namespace olc protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; + bool olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus = false, const float pressure = 0.0f, const float orientation = 0, const olc::vf2d& tilt = { 0,0 }) override; public: virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); @@ -4308,7 +4441,11 @@ namespace olc::host #ifdef HAVE_MSMF #define _WIN32_WINNT 0x0600 // Windows Vista #else - #define _WIN32_WINNT 0x0500 // Windows 2000 + //#define _WIN32_WINNT 0x0603 // Windows 8.1 + + // Windows 10 Minimum + #define WINVER 0x0A00 + #define _WIN32_WINNT 0x0A00 #endif #endif @@ -4319,6 +4456,8 @@ namespace olc::host #include #include +#include +#include #undef _WINSOCKAPI_ namespace olc @@ -4491,7 +4630,12 @@ extern "C" { // Event callback function types typedef void (*KeyEventCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData); typedef void (*MouseEventCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData); - + typedef void (*TouchEventCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData); + typedef void (*StylusEventCallback) (uint32_t touchID, double x, double y,float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData); + // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); @@ -4514,6 +4658,16 @@ extern "C" { void window_enableEventHandling (struct Window* self); void window_disableEventHandling (struct Window* self); + // Touch event handler setup + void window_setTouchBeganCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchMovedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchEndedCallback (struct Window* self, TouchEventCallback callback, void* userData); + void window_setTouchCancelledCallback (struct Window* self, TouchEventCallback callback, void* userData); + + // Stylus (tablet) event handler setup + void window_setStylusCallback (struct Window* self, StylusEventCallback callback, void* userData); + + // Application delegate callback function type typedef void (*ApplicationDelegateCallback)(void* userData); @@ -5278,10 +5432,46 @@ namespace olc { : x(x), y(y), deltaX(deltaX), deltaY(deltaY), modifierFlags(flags) {} }; + // Touch event data structure + struct TouchEvent { + uint32_t touchID; + double x, y; + double sizeX, sizeY; + + TouchEvent(uint32_t id, double x, double y, double sx, double sy) noexcept + : touchID(id), x(x), y(y), sizeX(sx), sizeY(sy) {} + + TouchEvent(TouchEvent&&) noexcept = default; + TouchEvent& operator=(TouchEvent&&) noexcept = default; + TouchEvent(const TouchEvent&) = default; + TouchEvent& operator=(const TouchEvent&) = default; + }; + + // Stylus (tablet) event data structure + struct StylusEvent { + uint32_t touchID; + double x, y; + float pressure, rotation; + float tiltX, tiltY; + bool bPress, bRelease; + bool bIsStylus; + + StylusEvent(uint32_t id, double x, double y, float pressure, float rotation, float tiltX, float tiltY, bool bPress, bool bRelease, bool bIsStylus) noexcept + : touchID(id), x(x), y(y), pressure(pressure), rotation(rotation), tiltX(tiltX), tiltY(tiltY), + bPress(bPress), bRelease(bRelease), bIsStylus(bIsStylus) {} + + StylusEvent(StylusEvent&&) noexcept = default; + StylusEvent& operator=(StylusEvent&&) noexcept = default; + StylusEvent(const StylusEvent&) = default; + StylusEvent& operator=(const StylusEvent&) = default; + }; + // Event handler class for keyboard and mouse events class EventHandler { private: Window& window_; + + // Key and mouse event handlers std::function keyDownHandler_; std::function keyUpHandler_; std::function flagsChangedHandler_; @@ -5299,6 +5489,15 @@ namespace olc { std::function mouseMovedEnteredHandler_; std::function mouseMovedExitedHandler_; + // Touch event handlers + std::function touchBeganHandler_; + std::function touchMovedHandler_; + std::function touchEndedHandler_; + std::function touchCancelledHandler_; + + // Stylus event handler + std::function stylusHandler_; + // Template helpers for static callbacks to reduce code duplication template static void keyCallback(unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { @@ -5410,6 +5609,37 @@ namespace olc { } } + static void touchBeganCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchBeganHandler_) + eventHandler->touchBeganHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + static void touchMovedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchMovedHandler_) + eventHandler->touchMovedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchEndedCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchEndedHandler_) + eventHandler->touchEndedHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static void touchCancelledCallback(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->touchCancelledHandler_) + eventHandler->touchCancelledHandler_(TouchEvent(touchID, x, y, sizeX, sizeY)); + } + + static 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) + { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->stylusHandler_) + eventHandler->stylusHandler_(StylusEvent(touchID, x, y, pressure, rotation, tiltX, tiltY, bPress, bRelease, bIsStylus)); + } + // Template helper for setting event handlers to reduce repetition template void setEventHandler(HandlerType EventHandler::*member, SetterFunc setter, CallbackFunc callback, std::function handler) { @@ -5503,6 +5733,33 @@ namespace olc { window_setMouseExitedCallback(window_.getCHandle(), mouseExitedCallback, this); } + // Touch event handler setters + void onTouchBegan(std::function handler) { + touchBeganHandler_ = std::move(handler); + window_setTouchBeganCallback(window_.getCHandle(), touchBeganCallback, this); + } + + void onTouchMoved(std::function handler) { + touchMovedHandler_ = std::move(handler); + window_setTouchMovedCallback(window_.getCHandle(), touchMovedCallback, this); + } + + void onTouchEnded(std::function handler) { + touchEndedHandler_ = std::move(handler); + window_setTouchEndedCallback(window_.getCHandle(), touchEndedCallback, this); + } + + void onTouchCancelled(std::function handler) { + touchCancelledHandler_ = std::move(handler); + window_setTouchCancelledCallback(window_.getCHandle(), touchCancelledCallback, this); + } + + // Stylus event handler setter + void onStylus(std::function handler) { + stylusHandler_ = std::move(handler); + window_setStylusCallback(window_.getCHandle(), stylusCallback, this); + } + // Enable/disable event handling void enable() noexcept { window_enableEventHandling(window_.getCHandle()); @@ -5664,6 +5921,7 @@ namespace X11 #include #include #include +#include #include #undef None constexpr int None = 0L; @@ -5679,6 +5937,8 @@ namespace olc::host X11::XVisualInfo* olc_VisualInfo; X11::Colormap olc_ColourMap; X11::XSetWindowAttributes olc_SetWindowAttribs; + + int xinput_extension_code{}; public: Host_Linux_X11(); @@ -5710,6 +5970,8 @@ namespace olc::host bool OnApplicationEnd() override; private: + void enableTouch(X11::Display* display, X11::Window window); + void handleTouchEvent(X11::XIDeviceEvent* event); std::unordered_map mapUID2X11Window; std::unordered_map mapX11Window2PTR; std::atomic systemActive {true}; @@ -5733,28 +5995,12 @@ namespace olc::host #if OLC_HOST == OLC_HOST_LINUX_WAYLAND -#if !defined(DISABLE_LIBDECOR) || defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_LIBDECOR -#endif - -#if !defined(FORCE_WAYLAND_LIBDECOR) -#define ENABLE_DECORATION_PROTOCOL -#endif - -#if !defined(ENABLE_LIBDECOR) && !defined(ENABLE_DECORATION_PROTOCOL) -#error "Incorrect build configuration. Either xdg-decoration or libdecor (or both) must be enabled." -#endif #include #include #include #include "xdg-shell.h" -// Only include the decoration protocol if we are not forcing libdecor -#ifdef ENABLE_DECORATION_PROTOCOL -#include "xdg-decoration.h" -#endif - #include "pointer-warp.h" #include #include @@ -5762,9 +6008,8 @@ namespace olc::host #include #include -#ifdef ENABLE_LIBDECOR #include "libdecor.h" -#endif + #include #include @@ -5778,10 +6023,6 @@ namespace olc::host struct WaylandWindow { wl_surface* surface{nullptr}; xdg_surface* surface_xdg{nullptr}; - xdg_toplevel* toplevel{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1* decorations{nullptr}; - #endif wl_egl_window* window{nullptr}; size_t olc_window_uid{0}; int32_t bounds_x{0}; @@ -5790,7 +6031,6 @@ namespace olc::host // Ignore window size bounds for fullscreen events bool fullscreen{false}; - #ifdef ENABLE_LIBDECOR // libdecor support libdecor_frame* decor_frame{nullptr}; int configured_width{}; @@ -5798,7 +6038,6 @@ namespace olc::host libdecor_window_state decor_window_state; int floating_width{}; int floating_height{}; - #endif ~WaylandWindow(); }; @@ -5833,6 +6072,29 @@ namespace olc::host std::array axes{}; uint32_t axis_source{0}; }; + + enum TouchEventMask { + TouchEventDown = 1 << 0, + TouchEventUp = 1 << 1, + TouchEventMotion = 1 << 2, + TouchEventCancel = 1 << 3, + TouchEventShape = 1 << 4, + TouchEventOrientation = 1 << 5 + }; + + struct TouchState { + uint32_t event_mask{0}; + wl_surface* surface{nullptr}; + wl_fixed_t surface_x{}; + wl_fixed_t surface_y{}; + wl_fixed_t major{}; + wl_fixed_t minor{}; + wl_fixed_t orientation{}; + + uint32_t time{0}; + uint32_t serial{0}; + int32_t id{0}; + }; } class Host_Linux_Wayland : public olc::host::Host @@ -5845,31 +6107,28 @@ namespace olc::host wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; wl_keyboard* keyboard{nullptr}; + wl_touch* touch{nullptr}; uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; xkb_keymap* kb_keymap{nullptr}; uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1* decoration_manager{nullptr}; - #endif wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; wayland::PointerState pointer_state; + std::unordered_map touches; wl_surface* cursor_surface{nullptr}; wl_cursor_image* cursor_image{nullptr}; wl_cursor_theme* cursor_theme{nullptr}; size_t active_window_id; - #ifdef ENABLE_LIBDECOR // libdecor support bool using_libdecor{false}; libdecor* decor_context{nullptr}; std::mutex decor_mutex; - #endif public: Host_Linux_Wayland(); @@ -5927,27 +6186,27 @@ namespace olc::host static void keyboard_modifiers_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); static void keyboard_repeat_info_callback(void* data, wl_keyboard* keyboard, int32_t rate, int32_t delay); + // Touch callbacks + static void touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + static void touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + static void touch_frame_callback(void* data, wl_touch* touch); + static void touch_cancel_callback(void* data, wl_touch* touch); + static void touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + static void touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation); + // xdg callbacks static void xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial); static void xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial); - static void xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - static void xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel); - static void xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height); - static void xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities); - #ifdef ENABLE_DECORATION_PROTOCOL - static void xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode); - #endif - - #ifdef ENABLE_LIBDECOR + // libdecor callbacks static void libdecor_error_callback(libdecor* context, libdecor_error error, const char* message); static void libdecor_frame_configure_callback(libdecor_frame* frame, libdecor_configuration* config, void* data); static void libdecor_close_callback(libdecor_frame* frame, void* data); static void libdecor_commit_callback(libdecor_frame* frame, void* data); static void libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data); - #endif - - private: + + private: // Wayland callback functions void registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void registry_handle_global_remove(wl_registry* registry, uint32_t name); @@ -5973,17 +6232,19 @@ namespace olc::host void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void keyboard_modifiers(wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); - // XDG toplevel callback functions - void xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states); - void xdg_toplevel_close(xdg_toplevel* toplevel); - void xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height); + // Touch callbacks + void touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id); + void touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y); + void touch_frame(wl_touch* touch); + void touch_cancel(wl_touch* touch); + void touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor); + void touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation); - #ifdef ENABLE_LIBDECOR // libdecor callback functions void libdecor_frame_configure(libdecor_frame* frame, libdecor_configuration* config); void libdecor_close(libdecor_frame* frame); void libdecor_commit(libdecor_frame* frame); - #endif bool CreateEGLContext(WaylandWindow* window); @@ -6173,6 +6434,10 @@ namespace olc::host std::atomic initialized{false}, systemActive{false}; bool shiftOn = false; + void handleMouse(size_t id, int32_t action, AInputEvent* event); + void handleStylus(size_t id, int32_t action, AInputEvent* event); + void handleFinger(size_t id, int32_t action, AInputEvent* event); + void PollEvents( const std::function& funcContinue, bool bBlocking = false @@ -7500,7 +7765,11 @@ namespace olc::host // Keep client size as requested RECT rWndRect = { 0, 0, vWinSize.x, vWinSize.y }; AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); - int width = rWndRect.right - rWndRect.left; + + // +1 Hack to remove black bar between client and title bar for "perfect" window + // sizes anyway. This could be DPI related on later windows, but this makes it look + // tidier for "normal" applications + int width = rWndRect.right - rWndRect.left + 1; int height = rWndRect.bottom - rWndRect.top; // Create the actual OS window, return a handle @@ -7816,6 +8085,146 @@ namespace olc::host 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... @@ -7906,15 +8315,15 @@ namespace olc::host { // NSEventModifierFlags values - constexpr unsigned int NSEventModifierNoFlags = 1 << 8; // 0x100 - constexpr unsigned int NSEventModifierFlagCapsLock = 1 << 16; // 0x10000 + // 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 + // 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 - constexpr unsigned int NSEventModifierFlagHelp = 1 << 22; // 0x400000 - constexpr unsigned int NSEventModifierFlagFunction = 1 << 23; // 0x800000 + // 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 { @@ -8051,6 +8460,7 @@ namespace olc::host { 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); @@ -8078,7 +8488,7 @@ namespace olc::host { } std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - + olc_IgnoreUnused(pWindow); // Ensure OpenGL renderer is created if(pMacOSOpenGLRenderer == nullptr) CreateCGLContextObj(); @@ -8107,6 +8517,7 @@ namespace olc::host { 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); }); @@ -8115,6 +8526,7 @@ namespace olc::host { bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + olc_IgnoreUnused(pWindow); dispatch_sync(dispatch_get_main_queue(), ^{ pMacOSWindow->setCursorVisibility(bVisible); }); @@ -8123,6 +8535,7 @@ namespace olc::host { 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; @@ -8170,6 +8583,9 @@ namespace olc::host { 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); @@ -8189,10 +8605,7 @@ namespace olc::host { pMacOSWindow->show(styleMask); pMacOSEventHandler->enable(); - //--- Start up our engine threading system ----- - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - + //--- Start up our engine threading system ---- // Start the PGE context on the main thread // Mark system as active systemActive = true; @@ -8650,6 +9063,51 @@ namespace olc::host { // Although MacOS provides both deltaX and deltaY, we will only use deltaY for vertical scrolling pPGEwindow->olc_OnMouseWheel(static_cast(event.deltaY)); }); + + // Touch events — map trackpad multi-touch to hw::Touch via olc_OnTouch + pMacOSEventHandler->onTouchBegan([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + true, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchMoved([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, false, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchEnded([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + pMacOSEventHandler->onTouchCancelled([&](const olc::apis::macos::TouchEvent& event) { + pPGEwindow->olc_OnTouch(event.touchID, + {static_cast(event.x), static_cast(event.y)}, + false, true, // treat cancel as release + {static_cast(event.sizeX), static_cast(event.sizeY)}); + }); + + // Stylus (tablet) events — full pressure, tilt and rotation data + pMacOSEventHandler->onStylus([&](const olc::apis::macos::StylusEvent& event) { + + pPGEwindow->olc_OnTouch( + event.touchID, + {static_cast(event.x), static_cast(event.y)}, + event.bPress, + event.bRelease, + {1.0f, 1.0f}, // stylus contact size — nominal 1x1 + true, // bStylus = true + event.pressure, + event.rotation, + {event.tiltX, event.tiltY} + ); + }); } @@ -8657,6 +9115,7 @@ namespace olc::host { // 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"; @@ -8669,7 +9128,7 @@ static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLV static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; // Application Screen management selectors -static constexpr const char* kNSScreenClass = "NSScreen"; +//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"; @@ -8746,6 +9205,39 @@ static constexpr const char* kTrackingAreaClass = "NSTrackingAre static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; +// Copied a lot of this code from iOS, will need to be tested +// NSResponder touch event method selectors +static constexpr const char* kSetAcceptsTouchEventsSel = "setAcceptsTouchEvents:"; +static constexpr const char* kSetWantsRestingTouchesSel = "setWantsRestingTouches:"; +static constexpr const char* kCGEventSel = "CGEvent"; +static constexpr const char* kTouchesBeganSel = "touchesBeganWithEvent:"; +static constexpr const char* kTouchesMovedSel = "touchesMovedWithEvent:"; +static constexpr const char* kTouchesEndedSel = "touchesEndedWithEvent:"; +static constexpr const char* kTouchesCancelledSel = "touchesCancelledWithEvent:"; + +// NSTouch / NSSet data extraction selectors +static constexpr const char* kTouchesMatchingPhaseSel = "touchesMatchingPhase:inView:"; +static constexpr const char* kAllObjectsSel = "allObjects"; +static constexpr const char* kNormalizedPositionSel = "normalizedPosition"; +static constexpr const char* kDeviceSizeSel = "deviceSize"; +static constexpr const char* kIdentitySel = "identity"; +static constexpr const char* kObjectAtIndexSel = "objectAtIndex:"; +static constexpr const char* kTouchCountSel = "count"; + +// NSResponder tablet / stylus event method selectors +static constexpr const char* kTabletPointSel = "tabletPoint:"; +static constexpr const char* kTabletProximitySel = "tabletProximity:"; +static constexpr const char* kPressureChangeSel = "pressureChangeWithEvent:"; +static constexpr const char* kStageSel = "stage"; + +// 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"; + // Managing first responder status and keyboard focus selectors static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; @@ -8762,7 +9254,11 @@ static constexpr const char* kInitWithFramePixelFormatSel = "initWithFrame 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:"; @@ -8790,7 +9286,7 @@ static constexpr const char* kLocaleIdentifierSel = "localeIdentif 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; +// 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; @@ -8800,7 +9296,7 @@ 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; +// static constexpr int kDefaultScreenNumber = 1; // Temp remove unused variable warning bool bAllowHideCursor = true; bool bHideCursor = false; @@ -8817,6 +9313,9 @@ static constexpr const char* kDrawRectMethodTypeEncoding = "v@:{NSRect={NSPoint= // Type encoding for void methods with no parameters: "v@:" static constexpr const char* kVoidMethodTypeEncoding = "v@:"; +// Type encoding for touch event methods: two id params (touches set + event) → "v@:@" +static constexpr const char* kTouchEventMethodTypeEncoding = "v@:@"; + namespace ObjectiveCSEL { // Application memory management selectors @@ -8895,6 +9394,34 @@ namespace ObjectiveCSEL { static SEL addTrackingAreaSel = nullptr; static SEL initWithRectSel = nullptr; + // 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; + // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; static SEL becomeFirstResponderSel = nullptr; @@ -8910,6 +9437,9 @@ namespace ObjectiveCSEL { 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; @@ -8918,6 +9448,7 @@ namespace ObjectiveCSEL { static SEL displaySel = nullptr; static SEL CGLContextObjSel = nullptr; static SEL setValuesSel = nullptr; + static SEL backingScaleFactorSel = nullptr; // Extracting data from NSEvent objects selectors static SEL keyCodeSel = nullptr; @@ -8931,8 +9462,8 @@ namespace ObjectiveCSEL { // NSLocale selectors static SEL currentLocaleSel = nullptr; static SEL localeIdentifierSel = nullptr; - static SEL currentInputContextSel = nullptr; - static SEL localizedNameSel = nullptr; + // static SEL currentInputContextSel = nullptr; // Temp remove unused variable warning + // static SEL localizedNameSel = nullptr; // Temp remove unused variable warning // Initialize all selectors - called once at startup void initializeSelectors() { @@ -9012,6 +9543,34 @@ namespace ObjectiveCSEL { 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); + + // Tablet / stylus event selectors + tabletPointSel = sel_registerName(kTabletPointSel); + tabletProximitySel = sel_registerName(kTabletProximitySel); + penPressureSel = sel_registerName(kPenPressureSel); + penRotationSel = sel_registerName(kPenRotationSel); + penTiltSel = sel_registerName(kPenTiltSel); + penIsEnteringProximitySel = sel_registerName(kPenIsEnteringProximitySel); + pointingDeviceTypeSel = sel_registerName(kPointingDeviceTypeSel); + penTangentialPressureSel = sel_registerName(kPenTangentialPressureSel); + pressureChangeSel = sel_registerName(kPressureChangeSel); + stageSel = sel_registerName(kStageSel); + // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); @@ -9027,6 +9586,9 @@ namespace ObjectiveCSEL { 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); @@ -9035,6 +9597,7 @@ namespace ObjectiveCSEL { 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); @@ -9159,7 +9722,7 @@ static constexpr int NSOpenGLPFAOpenGLProfile = static_cast(NSOpenGLPixelFo 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); +// static constexpr int NSOpenGLPFAAcceleratedCompute = static_cast(NSOpenGLPixelFormatAttribute::AcceleratedCompute); // Temp remove unused variable warning // enum for OpenGL profile versions enum class NSOpenGLProfile : int { @@ -9168,6 +9731,14 @@ enum class NSOpenGLProfile : int { Version4_1Core = 0x4100, // OpenGL 4.1 Core Profile }; +// enum for OpenGL context parameters +enum NSOpenGLContextParameter : int { + NSOpenGLContextParameterSwapInterval = 222, + NSOpenGLContextParameterSurfaceOrder = 235, + NSOpenGLContextParameterSurfaceOpacity = 236, + NSOpenGLContextParameterSurfaceBackingSize = 237 +}; + // Backward compatibility //static constexpr int NSOpenGLProfileVersion3_2Core = static_cast(NSOpenGLProfile::Version3_2Core); static constexpr int NSOpenGLProfileVersion4_1Core = static_cast(NSOpenGLProfile::Version4_1Core); @@ -9189,10 +9760,10 @@ enum class NSWindowStyleMask : uint16_t { }; // Backward compatibility -static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); -static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); -static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); -static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); +// static constexpr int NSWindowStyleMaskTitled = static_cast(NSWindowStyleMask::Titled); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskClosable = static_cast(NSWindowStyleMask::Closable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskMiniaturizable = static_cast(NSWindowStyleMask::Miniaturizable); // Temp remove unused variable warning +// static constexpr int NSWindowStyleMaskResizable = static_cast(NSWindowStyleMask::Resizable); // Temp remove unused variable warning static constexpr int NSWindowStyleMaskFullScreen = static_cast(NSWindowStyleMask::FullScreen); // enum for backing store types @@ -9311,12 +9882,12 @@ struct Application { // 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 + 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 // Event callback function pointers with nullptr initialization void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; @@ -9336,6 +9907,21 @@ struct Window { 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}; + // Touch event callback function pointers with nullptr initialization + void (*touchBeganCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchMovedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchEndedCallback) (uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void (*touchCancelledCallback)(uint32_t touchID, double x, double y, double sizeX, double sizeY, void* userData){nullptr}; + void* touchUserData{nullptr}; + + // Stylus (tablet) event callback function pointer + void (*stylusCallback)(uint32_t touchID, double x, double y, + float pressure, float rotation, + float tiltX, float tiltY, + bool bPress, bool bRelease, bool bIsStylus, + void* userData){nullptr}; + void* stylusUserData{nullptr}; + bool bStylusInProximity{false}; // true while stylus is hovering near the surface void* eventUserData{nullptr}; // User data for event callbacks BOOL acceptsInputEvents{NO}; // Whether the window accepts input events @@ -9540,8 +10126,9 @@ void view_flagsChanged(id self, SEL _cmd, id event) { //====================================================================// // Mouse Event Handling -// Convert from window coordinates to content view coordinates and flip Y coordinate -// from bottom-left (macOS format) to top-left (standard format) +// 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]] @@ -9550,16 +10137,23 @@ void convertToContentViewCoordinates(NSPoint& location) { id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); if (contentView) { - // Convert from window coordinates to view coordinates + + // Convert from window coordinates to view coordinates (still logical points) NSPoint contentLocation = ((NSPoint(*)(id, SEL, NSPoint, id))objc_msgSend)( - contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); + contentView, ObjectiveCSEL::convertPointFromViewSel, location, nil); - // Get the content view bounds to flip Y coordinate + // Scale from logical points to physical backing pixels + NSPoint pixelLocation = ((NSPoint(*)(id, SEL, NSPoint))objc_msgSend)( + contentView, ObjectiveCSEL::convertPointToBackingSel, contentLocation); + + // Get content bounds in physical pixels for Y-flip NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); - + NSRect pixelBounds = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(contentView, ObjectiveCSEL::convertRectToBackingSel, contentBounds); + // Flip Y coordinate from bottom-left to top-left - location.x = contentLocation.x; - location.y = contentBounds.height - contentLocation.y; + location.x = pixelLocation.x; + location.y = pixelBounds.height - pixelLocation.y; + } } else @@ -9569,6 +10163,7 @@ void convertToContentViewCoordinates(NSPoint& location) { } } +// Mouse event data structure to hold common mouse event information struct MouseEventData { NSPoint location = {0.0, 0.0}; NSUInteger modifierFlags = 0; @@ -9587,58 +10182,414 @@ MouseEventData extractMouseEventData(id event) { return data; } -// Handle left mouse down events -void view_mouseDown(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; // Remove unused parameter warnings - - MouseEventData data = extractMouseEventData(event); - - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] { - gptrNSWindowEvents->mouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, - (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} +// 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) -// Handle left mouse up events -void view_mouseUp(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; +}; - 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); - } -} +// Extract common mouse event data from NSEvent +StylusEventData extractStylusEventData(id event, uint8_t nPointerEventType) { -// Handle mouse drag events -void view_mouseDragged(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + 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); - MouseEventData data = extractMouseEventData(event); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { - gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + data.buttonMask = (uint16_t)CGEventGetIntegerValueField((CGEventRef)cgEvent, (CGEventField)kCGTabletEventPointButtons); + data.nPointerEventType = (uint8_t)nPointerEventType; // 0 = Mouse Event, 1 = Pen Event, 2 = Pen Proximity Event -// Handle mouse movement events -void view_mouseMoved(id self, SEL _cmd, id event) { - (void)self;(void)_cmd; + 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); - NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); - NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + data.nPointerType = ((uint8_t(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::pointingDeviceTypeSel); - convertToContentViewCoordinates(location); + double tiltX_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltX); + double tiltY_raw = CGEventGetDoubleValueField((CGEventRef)cgEvent, kCGTabletEventTiltY); - if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseMovedCallback) [[likely]] { - gptrNSWindowEvents->mouseMovedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); - } -} + // Clamp to safe math bounds (-1.0 to 1.0) to avoid domain errors in asin + tiltX_raw = std::fmax(-1.0, std::fmin(1.0, tiltX_raw)); + tiltY_raw = std::fmax(-1.0, std::fmin(1.0, tiltY_raw)); + + // Convert to true physical angles in RADIANS -PI/2 to +PI/2 + data.dTiltX_radians = std::asin(tiltX_raw); + data.dTiltY_radians = std::asin(tiltY_raw); + + // Convert to content view coordinates and flip Y coordinate + convertToContentViewCoordinates(data.nspLocation); + + return data; +} + +//====================================================================// +// Touch Event Handling (macOS trackpad multi-touch via NSTouchPhase) + + +// NSTouchPhase bitmask values +enum NSTouchPhase : NSUInteger { + NSTouchPhaseBegan = 1 << 0, + NSTouchPhaseMoved = 1 << 1, + NSTouchPhaseStationary = 1 << 2, + NSTouchPhaseEnded = 1 << 3, + NSTouchPhaseCancelled = 1 << 4, + NSTouchPhaseAny = ULONG_MAX +}; + +enum NSPressureStage : NSInteger { + NSPressureStageLight = 0, // Light press + NSPressureStageNormal = 1, // Normal press + NSPressureStageForce = 2 // Force click +}; + +enum class ForceTouchPressure : int { + Light = 25, // 0.25 - Light touch + Normal = 50, // 0.50 - Normal press + Force = 100, // 1.00 - Force click +}; + +// 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); + + // 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; +}; + +// 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; + + // Assign a stable sequential uint32_t IDs same as iOS does + id identity = ((id(*)(id, SEL))objc_msgSend)(touch, ObjectiveCSEL::identitySel); + uintptr_t key = reinterpret_cast(identity); + + auto [it, inserted] = sTouchIDMap.emplace(key, sNextTouchID); + if (inserted) ++sNextTouchID; + uint32_t tid = it->second; + data.touchID = tid; + sTouchIDEvents.emplace(key, data); + callback(tid, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width,data.nsTouchSize.height, userData); + + // Clean up map entry once the touch has ended or been cancelled + if (bRemove) + { + sTouchIDEvents.erase(key); + sTouchIDMap.erase(key); + } + + } + + } + + // 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); + + } + } + + // Find the missing keys and fire the touchEndedCallback for them + for(auto& [key, data] : sTouchIDEvents) { + + if(vFoundKeys.size() == 0 || std::find(vFoundKeys.begin(), vFoundKeys.end(), key) == vFoundKeys.end()) { + gptrNSWindowEvents->touchEndedCallback(data.touchID, data.nspLocation.x, data.nspLocation.y, data.nsTouchSize.width ,data.nsTouchSize.height, userData); + vMissingKeys.push_back(key); + } + + } + + // Remove the missing keys from the maps + for(auto& fKeys: vMissingKeys) { + sTouchIDEvents.erase(fKeys); + sTouchIDMap.erase(fKeys); + } + + vFoundKeys.clear(); vMissingKeys.clear(); + + // Finally reset the next touch ID if there are no active touches remaining + if (sTouchIDMap.size() == 0) sNextTouchID = 0; +} + +// TODO move to new location +// Handle Force Touch pressure changes on trackpad (single-finger press) +void view_pressureChange(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + auto toNormalizedPressure = [](ForceTouchPressure pressure) -> double { + return static_cast(static_cast(pressure)) / 100.0; + }; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + + // Get CGEvent to determine event subtype + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + + // A force touch trackpad event will have a subtype of 0 (default) or greater than 2 (tablet proximity) + if (nPointerEventType == kCGEventMouseSubtypeDefault || nPointerEventType > kCGEventMouseSubtypeTabletProximity) { + + // KRespondsToSelector + BOOL supportsStage = ((BOOL(*)(id, SEL, SEL))objc_msgSend)(event,sel_getUid(kRespondsToSelector),ObjectiveCSEL::stageSel); + + NSInteger stage = NSPressureStageNormal; + if(supportsStage) + { + // stage returns: 0 = normal click, 1 = light press, 2 = force click + stage = ((NSInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::stageSel); + } + + // Map stage to normalized pressure values as requested + double pressure = toNormalizedPressure(ForceTouchPressure::Normal); + + switch (stage) { + case NSPressureStageLight: + pressure = toNormalizedPressure(ForceTouchPressure::Light); + break; + case NSPressureStageNormal: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + case NSPressureStageForce: + pressure = toNormalizedPressure(ForceTouchPressure::Force); + break; + default: + pressure = toNormalizedPressure(ForceTouchPressure::Normal); + break; + } + + // now we need to work out the size + cgsApproxTouchSize = getApproxTrackPadTouchSize(pressure); + + for(auto& [key, data] : sTouchIDEvents) { + data.pressure = pressure; + data.nsTouchSize = cgsApproxTouchSize; + } + + } + } +} + +// 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]] { + + 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); + + } + +} + +// 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); + + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { + + 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); + + } + +} + +// Handle mouse drag events +void view_mouseDragged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + + // Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? + if (nPointerEventType == 0) [[likely]] { + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] { + MouseEventData data = extractMouseEventData(event); + gptrNSWindowEvents->mouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); + } + } + else if (nPointerEventType == 1 || nPointerEventType == 2) + { + // Tablet Pointer is Subtype 1 or 2 (1: Pen Event, 2: Proximity Event) + gptrNSWindowEvents->bStylusInProximity = false; + StylusEventData data = extractStylusEventData(event, nPointerEventType); + gptrNSWindowEvents->stylusCallback( + data.nTabletDeviceID, + data.nspLocation.x, data.nspLocation.y, + data.dPressure, data.dRotationRadians, data.dTiltX_radians, data.dTiltY_radians, + true, false, true, + gptrNSWindowEvents->stylusUserData); + } + +} + + +// Handle mouse movement events +void view_mouseMoved(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + 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); @@ -9648,7 +10599,12 @@ void view_rightMouseDown(id self, SEL _cmd, id event) { // 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); @@ -9659,6 +10615,11 @@ void view_rightMouseUp(id self, SEL _cmd, id event) { void view_rightMouseDragged(id self, SEL _cmd, id event) { (void)self;(void)_cmd; + CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel); + uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype); + if(nPointerEventType > 2) [[unlikely]] + return; // Ignore mouse events that are not mouse drag or tablet pointer events + MouseEventData data = extractMouseEventData(event); if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->rightMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -9669,7 +10630,12 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) { // 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); @@ -9679,7 +10645,12 @@ void view_otherMouseDown(id self, SEL _cmd, id event) { // 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); @@ -9689,6 +10660,11 @@ void view_otherMouseUp(id self, SEL _cmd, id event) { void view_otherMouseDragged(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->otherMouseDraggedCallback) [[likely]] { gptrNSWindowEvents->otherMouseDraggedCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData); @@ -9749,6 +10725,73 @@ void view_mouseExited(id self, SEL _cmd, id event) { } } + +void view_touchesBegan(id self, SEL _cmd, id event) { + (void)_cmd; + + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseBegan, + gptrNSWindowEvents->touchBeganCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesMoved(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseMoved, + gptrNSWindowEvents->touchMovedCallback, gptrNSWindowEvents->touchUserData); + } +} + +void view_touchesEnded(id self, SEL _cmd, id event) { + (void)_cmd; + // Ensure event is not null before parsing + if (!event) return; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + updateTouchData(event, NSTouchPhaseEnded, + gptrNSWindowEvents->touchEndedCallback, gptrNSWindowEvents->touchUserData, true); + } +} + +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); + } +} + +//====================================================================// +// Stylus / Tablet Event Handling (NSTabletPoint / NSTabletProximity) + +// 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); + } +} + +// This should only be called when a Stylus driver is not installed or it an Apple device +void view_tabletPoint(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents) [[likely]] { + // Handle tablet point events here + } +} + + void view_updateTrackingAreas(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -9842,6 +10885,17 @@ Class createCustomOpenGLViewClass() { 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); @@ -10225,13 +11279,19 @@ extern "C" { return; } - // Get the content view bounds + // 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 = contentBounds.x; - if (y) *y = contentBounds.y; - if (width) *width = contentBounds.width; - if (height) *height = contentBounds.height; + if (x) *x = pixelBounds.x; + if (y) *y = pixelBounds.y; + if (width) *width = pixelBounds.width; + if (height) *height = pixelBounds.height; } @@ -10323,17 +11383,25 @@ extern "C" { // Get the content view id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { - // Get the content view bounds to flip Y coordinate + // 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: we need to ensure the new cursor position is within the window bounds to prevent unexpected behavior - auto posX = std::clamp(location.x +x, location.x, location.x + contentBounds.width); - auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + y, - screenFrame.height - location.y - contentBounds.height, - screenFrame.height - location.y); + + // NOTE: clamp to keep the cursor within the window content area + auto posX = std::clamp(location.x + pointPos.x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + pointPos.y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + } } @@ -10414,6 +11482,13 @@ extern "C" { 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); + + } // Set up OpenGL context and make view first responder @@ -10497,7 +11572,7 @@ extern "C" { GLint swapInterval = enabled ? 1 : 0; // Use NSOpenGLContext setValues:forParameter: to set swap interval - const GLint parameter = 222; // NSOpenGLContextParameterSwapInterval + const GLint parameter = NSOpenGLContextParameterSwapInterval; ((void(*)(id, SEL, const GLint*, GLint))objc_msgSend)(self->glContext, ObjectiveCSEL::setValuesSel, &swapInterval, parameter); } @@ -10548,13 +11623,13 @@ extern "C" { CGDataProviderRef provider = CGImageGetDataProvider(image); CFDataRef rawData = CGDataProviderCopyData(provider); - CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image); - CGImageAlphaInfo alphaInfo = (CGImageAlphaInfo)(bitmapInfo & kCGBitmapAlphaInfoMask); - CGBitmapInfo byteOrder = bitmapInfo & kCGBitmapByteOrderMask; + //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 = CGImageGetWidth(image); - self->height = CGImageGetHeight(image); - self->bytesPerPixel = 4; + 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; @@ -10895,6 +11970,34 @@ extern "C" { self->eventUserData = userData; } + void window_setTouchBeganCallback(Window* self, void (*callback)(uint32_t, double, double, double, double, void*), void* userData) { + self->touchBeganCallback = callback; + self->touchUserData = userData; + } + + void 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) + { + self->stylusCallback = callback; + self->stylusUserData = userData; + } + void window_enableEventHandling(Window* self) { if (self) { self->acceptsInputEvents = YES; @@ -10984,6 +12087,9 @@ namespace olc::host olc_Display = XOpenDisplay(NULL); olc_WindowRoot = DefaultRootWindow(olc_Display); + // If supported, enable receiving touch events + enableTouch(olc_Display, olc_WindowRoot); + if(XkbQueryExtension(olc_Display, nullptr, &xkbEventBase, &xkbErrorBase, nullptr, nullptr)) { XkbSelectEventDetails(olc_Display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); @@ -11257,6 +12363,16 @@ namespace olc::host pge_window->olc_OnWindowClose(); } } + else if (xev.type == GenericEvent) + { + X11::XGenericEventCookie& xgec = xev.xcookie; + + if(xgec.extension == xinput_extension_code && X11::XGetEventData(olc_Display, &xgec)) { + // Should be one of our touch events + handleTouchEvent(static_cast(xgec.data)); + X11::XFreeEventData(olc_Display, &xgec); + } + } } } @@ -11518,6 +12634,89 @@ namespace olc::host return true; } + + void Host_Linux_X11::enableTouch(X11::Display* display, X11::Window window) + { + // Check if we have the XInput extension + int extension_code; + int ev; + int err; + + // If we have the extension, check for version 2.3 or higher + if (X11::XQueryExtension(display, "XInputExtension", &extension_code, &ev, &err)) { + int major{2}; + int minor{3}; + + if(X11::XIQueryVersion(display, &major, &minor) != Success) { + // Version is incorrect, do not select touch events + return; + } + } else { + // Do not have the extension, do not select touch events + return; + } + + // If we get down here, we have the extension and version is correct + xinput_extension_code = extension_code; + + X11::XIEventMask mask{}; + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_TouchEnd); + mask.mask = static_cast(std::calloc(mask.mask_len, sizeof(char))); + + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + X11::XISelectEvents(display, window, &mask, 1); + + X11::XSync(display, False); + std::free(mask.mask); + } + + void Host_Linux_X11::handleTouchEvent(X11::XIDeviceEvent* event) + { + auto itr_child = mapX11Window2PTR.find(event->child); + if(itr_child == mapX11Window2PTR.end()) { + + return; + } + + olc::Window* pge_window = itr_child->second; + + switch(event->evtype) { + case XI_TouchBegin: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + true, + false, + olc::vf2d{1.0f, .0f} + ); + break; + case XI_TouchUpdate: + { + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + false, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + case XI_TouchEnd: + pge_window->olc_OnTouch( + event->detail, + olc::vf2d(event->event_x, event->event_y), + false, + true, + olc::vf2d{1.0f, 1.0f} + ); + break; + } + } + } #endif @@ -11557,6 +12756,16 @@ namespace olc::host .modifiers = Host_Linux_Wayland::keyboard_modifiers_callback, .repeat_info = Host_Linux_Wayland::keyboard_repeat_info_callback }; + + static const wl_touch_listener touch_listener { + .down = Host_Linux_Wayland::touch_down_callback, + .up = Host_Linux_Wayland::touch_up_callback, + .motion = Host_Linux_Wayland::touch_motion_callback, + .frame = Host_Linux_Wayland::touch_frame_callback, + .cancel = Host_Linux_Wayland::touch_cancel_callback, + .shape = Host_Linux_Wayland::touch_shape_callback, + .orientation = Host_Linux_Wayland::touch_orientation_callback + }; } namespace xdg { @@ -11567,22 +12776,8 @@ namespace olc::host static const xdg_surface_listener surface_listener { .configure = Host_Linux_Wayland::xdg_surface_configure_callback }; - - static const xdg_toplevel_listener xdg_top_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_configure_callback, - .close = Host_Linux_Wayland::xdg_toplevel_close_callback, - .configure_bounds = Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback, - .wm_capabilities = Host_Linux_Wayland::xdg_toplevel_capabilities_callback - }; - - #ifdef ENABLE_DECORATION_PROTOCOL - static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener { - .configure = Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback - }; - #endif } - #ifdef ENABLE_LIBDECOR namespace decor { static libdecor_interface libdecor_error_listener = { .error = Host_Linux_Wayland::libdecor_error_callback, @@ -11595,7 +12790,6 @@ namespace olc::host .dismiss_popup = Host_Linux_Wayland::libdecor_dismiss_popup_callback }; } - #endif Host_Linux_Wayland::Host_Linux_Wayland() { @@ -11610,29 +12804,7 @@ namespace olc::host throw; } - // If only the decoration protocol is enabled, then not having the protocol is a hard error - #if defined(ENABLE_DECORATION_PROTOCOL) && !defined(ENABLE_LIBDECOR) - if(decoration_manager == nullptr) { - throw; - } - // If only libdecor is enabled, then flag "using_libdecor" - #elif !defined(ENABLE_DECORATION_PROTOCOL) && defined(ENABLE_LIBDECOR) - using_libdecor = true; - - // If both are enabled, use libdecor if the decoration protocol is not present - #else - using_libdecor = (decoration_manager == nullptr); - #endif - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - } else { - decor_context = libdecor_new(display, &decor::libdecor_error_listener); - } - #else - xdg_wm_base_add_listener(xdg_wm, &xdg::xdg_base_listener, this); - #endif + decor_context = libdecor_new(display, &decor::libdecor_error_listener); // Load the default cursor cursor_theme = wl_cursor_theme_load(NULL, 24, shm); @@ -11714,20 +12886,12 @@ namespace olc::host if(window) { wl_egl_window_destroy(window); } - if(toplevel) { - xdg_toplevel_destroy(toplevel); - } if(surface_xdg) { xdg_surface_destroy(surface_xdg); } - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_toplevel_decoration_v1_destroy(decorations); - #endif - #ifdef ENABLE_LIBDECOR if(decor_frame) { libdecor_frame_unref(decor_frame); } - #endif wl_surface_destroy(surface); } @@ -11736,15 +12900,10 @@ namespace olc::host mapUID2OlcWindow.clear(); mapUID2Window.clear(); - #ifdef ENABLE_DECORATION_PROTOCOL - zxdg_decoration_manager_v1_destroy(decoration_manager); - #endif - #ifdef ENABLE_LIBDECOR if(decor_context) { libdecor_unref(decor_context); decor_context = nullptr; } - #endif xkb_state_unref(kb_state); xkb_keymap_unref(kb_keymap); @@ -11757,6 +12916,9 @@ namespace olc::host { wp_pointer_warp_v1_destroy(pointer_warp); } + if(touch) { + wl_touch_destroy(touch); + } wl_keyboard_destroy(keyboard); wl_pointer_destroy(pointer); wl_seat_destroy(seat); @@ -11804,21 +12966,13 @@ namespace olc::host } }); - #if !defined(ENABLE_LIBDECOR) - while(systemActive && wl_display_dispatch_pending(display) != -1) { } - #else bool keep_running = true; while(systemActive && keep_running) { - if(using_libdecor) { - if(decor_context) { - std::lock_guard l{decor_mutex}; - keep_running = libdecor_dispatch(decor_context, 0) >= 0; - } - } else { - keep_running = wl_display_dispatch_pending(display) != -1; + if(decor_context) { + std::lock_guard l{decor_mutex}; + keep_running = libdecor_dispatch(decor_context, 0) >= 0; } } - #endif systemActive = false; if(threadSystem.joinable()) @@ -11861,43 +13015,19 @@ namespace olc::host wl_region_add(region, vWindowPos.x, vWindowPos.y, vWindowSize.x, vWindowSize.y); w.surface = wl_compositor_create_surface(compositor); - - #ifdef ENABLE_LIBDECOR - if(!using_libdecor) { - #endif - w.surface_xdg = xdg_wm_base_get_xdg_surface(xdg_wm, w.surface); - - xdg_surface_add_listener(w.surface_xdg, &xdg::surface_listener, this); - w.toplevel = xdg_surface_get_toplevel(w.surface_xdg); - xdg_toplevel_set_title(w.toplevel, "OneLoneCoder.com - Pixel Game Engine"); - xdg_toplevel_add_listener(w.toplevel, &xdg::xdg_top_listener, this); - if (!pWindow->config.bResizeable) { - xdg_toplevel_set_max_size(w.toplevel, vWindowSize.x, vWindowSize.y); - xdg_toplevel_set_min_size(w.toplevel, vWindowSize.x, vWindowSize.y); - } - - #ifdef ENABLE_DECORATION_PROTOCOL - w.decorations = zxdg_decoration_manager_v1_get_toplevel_decoration(decoration_manager, w.toplevel); - zxdg_toplevel_decoration_v1_add_listener(w.decorations, &xdg::toplevel_decoration_listener, this); - zxdg_toplevel_decoration_v1_set_mode(w.decorations, 2); - #endif - #ifdef ENABLE_LIBDECOR - } else { - std::lock_guard l{decor_mutex}; - w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); - w.floating_width = vWindowSize.x; - w.floating_height = vWindowSize.y; - libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); - libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); - - if(!pWindow->config.bResizeable) { - libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); - } - libdecor_frame_map(w.decor_frame); + std::lock_guard l{decor_mutex}; + w.decor_frame = libdecor_decorate(decor_context, w.surface, &decor::libdecor_frame_listener, this); + w.floating_width = vWindowSize.x; + w.floating_height = vWindowSize.y; + libdecor_frame_set_app_id(w.decor_frame, "olcPixelGameEngine"); + libdecor_frame_set_title(w.decor_frame, "OneLoneCoder.com - Pixel Game Engine"); + if(!pWindow->config.bResizeable) { + libdecor_frame_unset_capabilities(w.decor_frame, LIBDECOR_ACTION_RESIZE); } - #endif + + libdecor_frame_map(w.decor_frame); wl_surface_set_opaque_region(w.surface, region); w.window = wl_egl_window_create(w.surface, vWindowSize.x, vWindowSize.y); @@ -11925,16 +13055,8 @@ namespace olc::host { auto itr = mapUID2Window.find(pWindow->GetUID()); if(itr != mapUID2Window.end()) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - std::lock_guard l{decor_mutex}; - libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); - } else { - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - } - #else - xdg_toplevel_set_title(itr->second.toplevel, pWindow->GetWindowTitle().c_str()); - #endif + std::lock_guard l{decor_mutex}; + libdecor_frame_set_title(itr->second.decor_frame, pWindow->GetWindowTitle().c_str()); } return true; } @@ -12011,25 +13133,10 @@ namespace olc::host if(itr != mapUID2Window.end()) { pWindow->bWindowIsFullscreen = bFullScreen; if(bFullScreen) { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); - } else { - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - } - #else - xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); - #endif + libdecor_frame_set_fullscreen(itr->second.decor_frame, nullptr); + } else { - #ifdef ENABLE_LIBDECOR - if(using_libdecor) { - libdecor_frame_unset_fullscreen(itr->second.decor_frame); - } else { - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - } - #else - xdg_toplevel_unset_fullscreen(itr->second.toplevel); - #endif + libdecor_frame_unset_fullscreen(itr->second.decor_frame); } } return true; @@ -12050,11 +13157,6 @@ namespace olc::host seat = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); wl_seat_add_listener(seat, &wayland::seat_listener, this); } - #ifdef ENABLE_DECORATION_PROTOCOL - if(std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - decoration_manager = static_cast(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, version)); - } - #endif if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } @@ -12080,68 +13182,10 @@ namespace olc::host keyboard_version = wl_keyboard_get_version(keyboard); wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); } - } - - void Host_Linux_Wayland::xdg_toplevel_configure(xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { - const auto& itr = std::find_if(mapUID2Window.begin(), mapUID2Window.end(), [=](const auto& w){return w.second.toplevel == toplevel;}); - if(itr == mapUID2Window.end()) - { - return; - } - - auto& [uid, window] = *itr; - const auto& olc_window = mapUID2OlcWindow.at(uid); - - bool attempt_fullscreen {false}; - auto* state = reinterpret_cast(states->data); - auto* end = static_cast(states->data) + states->size; - for(;reinterpret_cast(state) < end; state++) - { - // The window.fullscreen is set any time the user commands via ShowFullscreen(), so we should allow this - if (*state == xdg_toplevel_state::XDG_TOPLEVEL_STATE_FULLSCREEN) - { - attempt_fullscreen = true; - } - } - - // If we're not going fullscreen, try to obey the bounds that have been configured by the compositor - if(!attempt_fullscreen) { - if(window.bounds_x != 0) { - width = std::min(width, window.bounds_x); - } - - if(window.bounds_y != 0) { - height = std::min(height, window.bounds_y); - } - } - - olc_window->bWindowIsFullscreen = attempt_fullscreen; - olc_window->olc_OnWindowSize({width, height}); - wl_egl_window_resize(window.window, width, height, 0, 0); - wl_surface_commit(window.surface); - } - - void Host_Linux_Wayland::xdg_toplevel_close(xdg_toplevel* toplevel) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - auto itr = mapUID2OlcWindow.find(i.second.olc_window_uid); - if(itr != mapUID2OlcWindow.end()) { - auto* ptr = itr->second; - ptr->olc_OnWindowClose(); - } - } - } - } - void Host_Linux_Wayland::xdg_toplevel_configure_bounds(xdg_toplevel* toplevel, int32_t width, int32_t height) - { - for(auto& i : mapUID2Window) { - if(i.second.toplevel == toplevel) { - i.second.bounds_x = width; - i.second.bounds_y = height; - } + if (capabilities & WL_SEAT_CAPABILITY_TOUCH && touch == nullptr) { + touch = wl_seat_get_touch(seat); + wl_touch_add_listener(touch, &wayland::touch_listener, this); } } @@ -12481,48 +13525,180 @@ namespace olc::host // host->keyboard_repeat_info(keyboard, rate, delay); } - // XDG Callbacks - void Host_Linux_Wayland::xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial) { - xdg_wm_base_pong(wm, serial); + // Touch Callbacks + void Host_Linux_Wayland::touch_down_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) + { + auto* host = reinterpret_cast(data); + host->touch_down(touch, serial, time, surface, id, x, y); } - void Host_Linux_Wayland::xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial) - { - xdg_surface_ack_configure(surface, serial); + void Host_Linux_Wayland::touch_down(wl_touch* touch, uint32_t serial, uint32_t time, wl_surface* surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventDown; + touch_state.surface = surface; + touch_state.id = id; + touch_state.surface_x = x; + touch_state.surface_y = y; + touch_state.serial = serial; + touch_state.time = time; } - void Host_Linux_Wayland::xdg_toplevel_configure_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states) - { + void Host_Linux_Wayland::touch_up_callback(void* data, wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure(toplevel, width, height, states); + host->touch_up(touch, serial, time, id); } - void Host_Linux_Wayland::xdg_toplevel_close_callback(void* data, xdg_toplevel* toplevel) - { + void Host_Linux_Wayland::touch_up(wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventUp; + touch_state.serial = serial; + touch_state.time = time; + } + + void Host_Linux_Wayland::touch_motion_callback(void* data, wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { auto* host = reinterpret_cast(data); - host->xdg_toplevel_close(toplevel); + host->touch_motion(touch, time, id, x, y); + } + + void Host_Linux_Wayland::touch_motion(wl_touch* touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventMotion; + touch_state.time = time; + touch_state.surface_x = x; + touch_state.surface_y = y; } - void Host_Linux_Wayland::xdg_toplevel_configure_bounds_callback(void* data, xdg_toplevel* toplevel, int32_t width, int32_t height) { + void Host_Linux_Wayland::touch_frame_callback(void* data, wl_touch* touch) { auto* host = reinterpret_cast(data); - host->xdg_toplevel_configure_bounds(toplevel, width, height); - return; + host->touch_frame(touch); } - void Host_Linux_Wayland::xdg_toplevel_capabilities_callback(void* data, xdg_toplevel* toplevel, wl_array* capabilities) - { - return; + void Host_Linux_Wayland::touch_frame(wl_touch* touch) { + // Commit all of the touches + for(auto itr = touches.begin(); itr != touches.end();) { + const auto& id = itr->first; + const auto& touch = itr->second; + + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + touch.event_mask & wayland::TouchEventMask::TouchEventUp, + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + + if(touch.event_mask & wayland::TouchEventMask::TouchEventUp) { + itr = touches.erase(itr); + } else { + ++itr; + } + } + } + + void Host_Linux_Wayland::touch_cancel_callback(void* data, wl_touch* touch) { + auto* host = reinterpret_cast(data); + host->touch_cancel(touch); + } + + void Host_Linux_Wayland::touch_cancel(wl_touch* touch) { + // according to the protocol, this ends all touch events, so send an Up and clear the whole thing + for(const auto& [id, touch] : touches) { + olc::Window* pge_window {nullptr}; + size_t uid {0}; + for(auto& uid_itr : mapUID2Window) { + if(uid_itr.second.surface == touch.surface) { + uid = uid_itr.second.olc_window_uid; + } + } + + if(const auto& pge_itr = mapUID2OlcWindow.find(uid); pge_itr != mapUID2OlcWindow.end()) { + pge_window = pge_itr->second; + } else { + continue; + } + + auto p_x = wl_fixed_to_double(touch.surface_x); + auto p_y = wl_fixed_to_double(touch.surface_y); + auto s_x = wl_fixed_to_double(touch.major); + auto s_y = wl_fixed_to_double(touch.minor); + olc::vf2d size (std::max(s_x, 1.0), std::max(s_y, 1.0)); + pge_window->olc_OnTouch( + static_cast(touch.id), + olc::vf2d(p_x, p_y), + touch.event_mask & wayland::TouchEventMask::TouchEventDown, + true, // Forced Up event to clear all these IDs on the PGE side + size, + false, + 0.0f, + wl_fixed_to_double(touch.orientation) + ); + } + + touches.clear(); + } + + void Host_Linux_Wayland::touch_shape_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto* host = reinterpret_cast(data); + host->touch_shape(touch, id, major, minor); + } + + void Host_Linux_Wayland::touch_shape(wl_touch* touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventShape; + touch_state.major = major; + touch_state.minor = minor; + } + + void Host_Linux_Wayland::touch_orientation_callback(void* data, wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto* host = reinterpret_cast(data); + host->touch_orientation(touch, id, orientation); + } + + void Host_Linux_Wayland::touch_orientation(wl_touch* touch, int32_t id, wl_fixed_t orientation) { + auto& touch_state = touches[id]; + + touch_state.event_mask |= wayland::TouchEventMask::TouchEventOrientation; + touch_state.orientation = orientation; + } + + + // XDG Callbacks + void Host_Linux_Wayland::xdg_wm_ping_callback(void* data, xdg_wm_base* wm, uint32_t serial) { + xdg_wm_base_pong(wm, serial); } - #ifdef ENABLE_DECORATION_PROTOCOL - void Host_Linux_Wayland::xdg_toplevel_decoration_configure_callback(void* data, zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode) + void Host_Linux_Wayland::xdg_surface_configure_callback(void* data, xdg_surface* surface, uint32_t serial) { - // auto* host = reinterpret_cast(data); - // fprintf(stderr, "zxdg_decoration_manager_v1 mode %d\n", mode); + xdg_surface_ack_configure(surface, serial); } - #endif - #ifdef ENABLE_LIBDECOR + void Host_Linux_Wayland::libdecor_error_callback(libdecor* context, libdecor_error error, const char* message) { std::cerr << "libdecor: " << error << ": " << message << "\n"; @@ -12566,7 +13742,6 @@ namespace olc::host olc_window->bWindowIsFullscreen = (window->decor_window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; mapUID2OlcWindow[i.first]->olc_OnWindowSize({window->configured_width, window->configured_height}); wl_egl_window_resize(window->window, window->configured_width, window->configured_height, 0, 0); - wl_surface_commit(window->surface); } } } @@ -12592,24 +13767,20 @@ namespace olc::host void Host_Linux_Wayland::libdecor_commit_callback(libdecor_frame* frame, void* data) { - auto* host = reinterpret_cast(data); - host->libdecor_commit(frame); + // Don't want to actually do anything here because it messes with the EGL surface swapping for refresh + // and causes the application to close (not crash) due to a wayland protocol violation + //auto* host = reinterpret_cast(data); + //host->libdecor_commit(frame); } void Host_Linux_Wayland::libdecor_commit(libdecor_frame* frame) { - for(auto& i : mapUID2Window) { - if(i.second.decor_frame == frame) { - wl_surface_commit(i.second.surface); - } - } } void Host_Linux_Wayland::libdecor_dismiss_popup_callback(libdecor_frame* frame, const char* seat_name, void* data) { } - #endif std::vector Host_Linux_Wayland::GetHostWindowDescriptor(olc::Window* pWindow) { @@ -12839,6 +14010,7 @@ namespace olc::host emscripten_set_touchstart_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchmove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); emscripten_set_touchend_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + emscripten_set_touchcancel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); // Canvas Focus Callbacks emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); @@ -13125,31 +14297,41 @@ namespace olc::host return EM_TRUE; } - //TY Bispoo EM_BOOL Host_Web_Emscripten::touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) { - // TODO: Implement touch more effectively. - // For now, emulate single pointer mouse. - CallbackData* pCallbackData = reinterpret_cast(userData); - // Move - if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) - { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - } - - // Start - if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + for(int i = 0; i < e->numTouches; ++i) { - olc_OnMouseMove(pCallbackData->pWindow, {e->touches->targetX, e->touches->targetY}); - olc_OnMouseButton(pCallbackData->pWindow, 0, true); + const EmscriptenTouchPoint& touch = e->touches[i]; + + if(touch.isChanged) { + bool is_start = (eventType == EMSCRIPTEN_EVENT_TOUCHSTART); + bool is_end = ((eventType == EMSCRIPTEN_EVENT_TOUCHEND) || (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)); + pCallbackData->pWindow->olc_OnTouch( + touch.identifier, + olc::vf2d(touch.targetX, touch.targetY), + is_start, + is_end, + olc::vf2d(1.0f, 1.0f) + ); + } } - // End - if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) - { - olc_OnMouseButton(pCallbackData->pWindow, 0, false); + // Emscripten seems to sometimes "leak" touches and not report them as ending. Iterate through the touches in the system and end + // any touches that are no longer being reported + for(const auto touch_id : pCallbackData->pWindow->touch.GetTouchIDs()) { + // if this touch is not reported anymore + if (std::find_if(e->touches, e->touches + e->numTouches, [=](const EmscriptenTouchPoint& x){return x.identifier == touch_id;}) == e->touches + e->numTouches) { + const auto& touch = pCallbackData->pWindow->touch.GetTouch(touch_id); + pCallbackData->pWindow->olc_OnTouch( + touch_id, + touch.position, + false, + true, + touch.size + ); + } } return EM_TRUE; @@ -13575,33 +14757,20 @@ namespace olc::host auto type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) { - pgeWindow->olc_OnMouseMove({ - static_cast(AMotionEvent_getX(event, 0)), - static_cast(AMotionEvent_getY(event, 0)), - }); - - auto action = AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK; - - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - pgeWindow->olc_OnMouseButton(0, true); // Left button - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - pgeWindow->olc_OnMouseButton(0, false); // Left button - break; - case AMOTION_EVENT_AXIS_WHEEL: - // Handle mouse wheel - { - float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, 0); - if (vScroll != 0.0f) { - pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); - } - } - break; - default: - break; + auto actionRaw = AMotionEvent_getAction(event); + auto actionMasked = actionRaw & AMOTION_EVENT_ACTION_MASK; + auto pointerIndex = (actionRaw & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + auto pointerCount = AMotionEvent_getPointerCount(event); + + for (size_t pi = 0; pi < pointerCount; pi++) { + auto toolType = AMotionEvent_getToolType(event, pi); + switch (toolType) { + case AMOTION_EVENT_TOOL_TYPE_MOUSE: handleMouse(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_ERASER: + case AMOTION_EVENT_TOOL_TYPE_STYLUS: handleStylus(pi, actionMasked, event); break; + case AMOTION_EVENT_TOOL_TYPE_FINGER: handleFinger(pi, actionMasked, event); break; + default: break; + } } return 1; @@ -13891,8 +15060,175 @@ namespace olc::host return content; } - - void Host_Android::PollEvents(const std::function& funcContinue, bool bBlocking) + + void Host_Android::handleMouse(size_t id, int32_t action, AInputEvent *event) + { + auto fnGetButton = [&event]() { + int32_t buttons = AMotionEvent_getButtonState(event); + if (buttons & AMOTION_EVENT_BUTTON_PRIMARY) return 0; + if (buttons & AMOTION_EVENT_BUTTON_SECONDARY) return 1; + if (buttons & AMOTION_EVENT_BUTTON_TERTIARY) return 2; + if (buttons & AMOTION_EVENT_BUTTON_BACK) return 3; + if (buttons & AMOTION_EVENT_BUTTON_FORWARD) return 4; + return -1; + }; + + pgeWindow->olc_OnMouseMove({ + static_cast(AMotionEvent_getX(event, id)), + static_cast(AMotionEvent_getY(event, id)), + }); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnMouseButton(fnGetButton(), true); + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnMouseButton(fnGetButton(), false); + break; + case AMOTION_EVENT_AXIS_WHEEL: + { + float vScroll = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_VSCROLL, id); + if (vScroll != 0.0f) { + pgeWindow->olc_OnMouseWheel(static_cast(vScroll * 120.0f)); + } + } + break; + default: + break; + } + } + + void Host_Android::handleStylus(size_t id, int32_t action, AInputEvent *event) + { + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + float ellipseX = AMotionEvent_getToolMajor(event, id); + float ellipseY = AMotionEvent_getToolMinor(event, id); + + // radians, 0 = vertical, positive = clockwise tilt in the plane of the screen + float orientation = AMotionEvent_getOrientation(event, id); + + // angle of the stylus away from perpendicular to the screen, radians, 0 = straight up + float axisTilt = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_TILT, id); + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { ellipseX, ellipseY }, + true, + pressure, + orientation, + { axisTilt, axisTilt } + ); + break; + default: + break; + } + } + + void Host_Android::handleFinger(size_t id, int32_t action, AInputEvent *event) + { + float size = AMotionEvent_getSize(event, id); + float pressure = AMotionEvent_getPressure(event, id); + pressure = std::clamp(pressure, 0.0f, 1.0f); // It can exceed 1.0f on some devices + + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + true, + false, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + true, + { size, size }, + false, + pressure + ); + break; + case AMOTION_EVENT_ACTION_MOVE: + pgeWindow->olc_OnTouch( + id, + { + AMotionEvent_getX(event, id), + AMotionEvent_getY(event, id) + }, + false, + false, + { size, size }, + false, + pressure + ); + break; + default: + break; + } + } + + void Host_Android::PollEvents(const std::function &funcContinue, bool bBlocking) { while (funcContinue()) { int events; @@ -14441,6 +15777,8 @@ namespace olc::apis::opengl { #if OLC_HOST == OLC_HOST_WINDOWS _wglSwapIntervalEXT(n); +#else + olc_IgnoreUnused(n); #endif } @@ -14788,6 +16126,20 @@ void main() oTex = aTex; } + else if (pgeDrawType == 3) // Unconstrained 2D Line + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = aTex; + } + + else if (pgeDrawType == 4) // Unconstrained 2D Polygon + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * (aPos.xy) * pgeInverseTargetSizeInPixels - 1.0), 0.0, 1.0); + oTex = p * vec2(aTex.x, aTex.y); + } + else if (pgeDrawType == 0) // 2D Polygon { float p = 1.0 / aPos.z; @@ -15045,6 +16397,9 @@ void main() EGLint const context_config[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; /* create an EGL rendering context */ +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND + eglBindAPI(EGL_OPENGL_API); +#endif glRenderContext.context = eglCreateContext(glRenderContext.display, glRenderContext.config, EGL_NO_CONTEXT, context_config); glRenderContext.surface = eglCreateWindowSurface(glRenderContext.display, glRenderContext.config, window_handle, nullptr); if(glRenderContext.surface == EGL_NO_SURFACE) { @@ -15160,7 +16515,7 @@ void main() // Create a null-texture so sampler doesnt fail. We don't have some of the core's helper // functions here, so we construct it manually - imgBlank.Create({ 1,1 }); + imgBlank.CreateNoGPU({ 1,1 }); imgBlank.SetGPUID(CreateTexture(imgBlank.Size())); imgBlank.BindCPU(); imgBlank.Pixel({ 0,0 }) = olc::Colour::WHITE; @@ -15823,33 +17178,59 @@ void main() // Apply Culling modes - if (task.cullmode == GPUTask::CullMode::None) + if (task.cullmode == olc::CullMode::None) { gl.glDisable(GL_CULL_FACE); } - else if (task.cullmode == GPUTask::CullMode::ClockWise) + else if (task.cullmode == olc::CullMode::ClockWise) { gl.glCullFace(GL_FRONT); gl.glEnable(GL_CULL_FACE); } - else if (task.cullmode == GPUTask::CullMode::CounterClockWise) + else if (task.cullmode == olc::CullMode::CounterClockWise) { gl.glCullFace(GL_BACK); gl.glEnable(GL_CULL_FACE); } - //// Apply Depth Testing (if required) + // Apply Depth Testing (if required) if (task.bDepth) + { gl.glEnable(GL_DEPTH_TEST); + gl.glDepthFunc(GL_LESS); + } + + + // Apply Blending Mode + if (task.blendmode == olc::BlendMode::Alpha) + { + gl.glEnable(GL_BLEND); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + else if(task.blendmode == olc::BlendMode::Additive) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + else if(task.blendmode == olc::BlendMode::Multiplicative) + { + gl.glEnable(GL_BLEND); + gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + } + else if (task.blendmode == olc::BlendMode::None) + { + gl.glDisable(GL_BLEND); + } - glDepthFunc(GL_LESS); - gl.glEnable(GL_BLEND); + //gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + //gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //// Apply Rendering Mode + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (task.bIs3D) { @@ -15858,16 +17239,32 @@ void main() } else { - if (task.structure == olc::Structure::Point) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::Line) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineLoop) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineList) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + if (task.bPixelConstrained) + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + } else - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 3); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 4); + } } if (task.structure == olc::Structure::Fan) @@ -15886,8 +17283,8 @@ void main() gl.glDrawArrays(GL_POINTS, 0, (GLsizei)task.vertexBuffer.size()); - if (task.bWireframe) - gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + //if (task.bWireframe) + // gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (task.bDepth) gl.glDisable(GL_DEPTH_TEST); @@ -15941,6 +17338,7 @@ void main() #if OLC_HOST == OLC_HOST_MACOS // The pointer value in os_win_id[1] will be set to true, when the OS requests to skip the frame swap + olc_IgnoreUnused(bVerticalSyncNow); const bool* bSkipFrame = static_cast(os_win_id[1]); if (*bSkipFrame) return true; CGLContextObj cglContext = static_cast(os_win_id[0]); @@ -16019,6 +17417,10 @@ void main() #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpessimizing-move" // Suppress clang C++20 warning about moves preventing copy elision on macOS +#endif using namespace olc; // Some local pools to reduce allocations @@ -16272,63 +17674,74 @@ void olc::Draw::Clear(const olc::Pixel& col) pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = olc::Structure::Line; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; task.vertexBuffer.resize((vPoints.size()-1) * 2); for (size_t i = 0; i < vPoints.size() - 1; i++) { task.vertexBuffer[i*2+0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; task.vertexBuffer[i*2+1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, vColours[i + 1], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; } + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } -GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain, const bool looped) { GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.structure = looped ? olc::Structure::LineLoop : olc::Structure::Line; + task.vertexBuffer.resize((vPoints.size() - 1) * 2); + for (size_t i = 0; i < vPoints.size() - 1; i++) + { + task.vertexBuffer[i * 2 + 0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.vertexBuffer[i * 2 + 1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + } + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; + +} + +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + olc_IgnoreUnused(structure); + return TaskDrawLine(vPoints, vColours, tint, false, true); } GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { - GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = {{vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - task.tint = tint; - return task; + olc_IgnoreUnused(structure); + return TaskDrawLine(vPoints, colour, tint, false, true); } -GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; task.vertexBuffer.resize(vPoints.size()); for (size_t i = 0; i < vPoints.size(); i++) task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } -GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint, const bool constrain) { GPUTask task; task.structure = structure; task.vertexBuffer.resize(vPoints.size()); for (size_t i = 0; i < vPoints.size(); i++) task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.blendmode = blendMode; task.tint = tint; + task.bPixelConstrained = constrain; return task; } @@ -16340,6 +17753,7 @@ GPUTask olc::Draw::TaskTexturedPolygon(olc::Structure structure, const std::vect for (size_t i = 0; i({ p1, p2 }), { c1, c2 }, tint - ))); + ))); } @@ -16518,7 +17935,12 @@ void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) for (int32_t i = 0; i <= nFacets; i++) { float theta = float(i) / float(nFacets) * 2.0f * 3.14159265358979323846f; - buffUnitCirclePoints.data[i] = { cosf(theta), sinf(theta) }; + + float c = cos(theta); + float s = sin(theta); + + + buffUnitCirclePoints.data[i] = { c, s }; } } @@ -16553,16 +17975,24 @@ const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const f for (int32_t i = 0; i <= nFacets; i++) { - buffPoints.data[i] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i] = col; } - return vecGPUTasks.data.emplace_back(std::move( + /*return vecGPUTasks.data.emplace_back(std::move( TaskDrawPolygon( olc::Structure::Line, buffPoints.data, buffColours.data, tint + )));*/ + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + buffPoints.data, + buffColours.data, + tint, + false // Don't constrain ))); } @@ -16589,7 +18019,7 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c for (int32_t i = 0; i <= nFacets; i++) { - buffPoints.data[i + 1] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffPoints.data[i + 1] = transformAffine.forward({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); buffColours.data[i + 1] = colOuter; } @@ -16598,7 +18028,8 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c olc::Structure::Fan, buffPoints.data, buffColours.data, - tint + tint, + false // Don't constrain ))); } @@ -17109,11 +18540,16 @@ const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos } -void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) +void olc::Draw::SetCullMode(const olc::CullMode mode) { cullMode = mode; } +void olc::Draw::SetBlendMode(const olc::BlendMode mode) +{ + blendMode = mode; +} + void olc::Draw::EnableDepth(const bool bEnable) { bDepth = bEnable; @@ -17177,6 +18613,10 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } +#if OLC_HOST == OLC_HOST_MACOS +#pragma clang diagnostic pop +#endif + using namespace olc; ImageBatch olc::Draw::CreateImageBatch(olc::Image& image) @@ -17847,6 +19287,7 @@ namespace olc // Input Changes mouse.UpdateState(); keyboard.UpdateState(); + touch.UpdateState(); draw.SetGPU(pRenderer); draw.SetTarget(GetScreen()); @@ -17917,6 +19358,7 @@ namespace olc // fit within the window client area float fAspectScreen = float(GetScreen().Size().x) / float(GetScreen().Size().y); + vViewSize.x = (int32_t)vWindowSize.x; vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); @@ -17944,6 +19386,8 @@ namespace olc // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.ImageRect(GetScreen().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); @@ -17963,14 +19407,14 @@ namespace olc bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image - if (!image.Create(size, cfg)) + if (!image.CreateNoGPU(size, cfg)) return false; // Create GPU Image auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -17989,7 +19433,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -18011,7 +19455,7 @@ namespace olc auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); return false; } @@ -18040,7 +19484,7 @@ namespace olc } // Free any cpu memory associated with image - image.Create({ 0,0 }); + image.CreateNoGPU({ 0,0 }); } void PGEWindow::LinkToRenderer(olc::gpu::Renderer* gpu) @@ -18101,6 +19545,19 @@ namespace olc return true; } + bool PGEWindow::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) + { + olc::vf2d pos = vPos; + pos.x -= float(vViewPos.x); + pos.y -= float(vViewPos.y); + + olc::vf2d vScale = (1.0f / olc::vf2d(vWindowSize - (vViewPos * 2))) * GetScreen().Size(); + + touch.UpdateTouch(nID, pos * vScale, + bPress, bRelease, vSize * vScale, stylus, pressure, orientation, tilt); + return true; + } + @@ -18257,6 +19714,8 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state + draw.SetBlendMode(olc::BlendMode::Alpha); + draw.SetCullMode(olc::CullMode::None); draw.SetTarget(GetScreen()); draw.WorldReset(); gpu->ApplyDefaultShader(); @@ -18443,7 +19902,7 @@ namespace olc { }*/ - bool Image::Create(const olc::vi2d& size, const ImageConfig& cfg) + bool Image::CreateNoGPU(const olc::vi2d& size, const ImageConfig& cfg) { dimensions = size; config = cfg; @@ -18629,7 +20088,8 @@ namespace olc fontClassicPGE.glyphs.push_back(glyph); } else - fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f} }); + // Added missing vMonoSize for non-printable characters, which was causing -Wmissing-field-initializers + fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f}, { 0.0f, 0.0f } }); } fontClassicPGE.fLineHeight = 8.0f; @@ -18955,6 +20415,63 @@ namespace olc::hw #define PGE_HW_KEYBOARD_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_TOUCH_IMPLEMENTED) +namespace olc::hw +{ + const std::vector& Touch::GetTouchIDs() const + { + static std::vector touchIDs; + touchIDs.clear(); + for (const auto& [id, index] : touches_cache) + touchIDs.push_back(id); + return touchIDs; + } + + const bool Touch::IsTouch(const uint32_t nID) const + { + return touches_cache.contains(nID); + } + + const TouchPoint& Touch::GetTouch(const uint32_t nID) const + { + return touches_cache.at(nID); + } + + void Touch::UpdateTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) + { + // If touch doesnt exist, this will create one or + // it will update an existing one + touches_live[nID].position = vPos; + touches_live[nID].size = vSize; + touches_live[nID].bPressed = bPress || touches_live[nID].bPressed; + touches_live[nID].bReleased = bRelease; + touches_live[nID].bHeld = !bRelease; + touches_live[nID].pressure = pressure; + touches_live[nID].orientation = orientation; + touches_live[nID].tilt = tilt; + touches_live[nID].bStylus = stylus; + } + + void Touch::UpdateState() + { + touches_cache = touches_live; + + // Remove released touches from live state + for (auto it = touches_live.begin(); it != touches_live.end(); ) + { + if (it->second.bPressed) + it->second.bPressed = false; + + if (it->second.bReleased) + it = touches_live.erase(it); + else + ++it; + } + } +} +#define PGE_HW_TOUCH_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) namespace olc { @@ -18989,6 +20506,7 @@ namespace olc bool Window::olc_OnMouseMove(const olc::vi2d& vMousePos) { + // Handled by PGEWindow mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize())); return true; } @@ -19028,6 +20546,12 @@ namespace olc return true; } + bool Window::olc_OnTouch(const uint32_t nID, const olc::vf2d& vPos, const bool bPress, const bool bRelease, const olc::vf2d& vSize, const bool stylus, const float pressure, const float orientation, const olc::vf2d& tilt) + { + // Handled by PGEWindow + return true; + } + bool Window::olc_ShouldRemove() const { return bShouldRemove; @@ -19197,7 +20721,7 @@ namespace olc::imload bool ImageLoader_WinGDI::DecodeBMP(olc::Image& image, Gdiplus::Bitmap* bmp) { // Need to swizzle each pixel... - image.Create(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); + image.CreateNoGPU(olc::vi2d(bmp->GetWidth(), bmp->GetHeight())); for (int y = 0; y < image.Size().y; y++) for (int x = 0; x < image.Size().x; x++) { @@ -19242,7 +20766,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -19282,7 +20806,7 @@ namespace olc::imload } // Create our olc::Image - if (!image.Create({width, height})) { + if (!image.CreateNoGPU({width, height})) { return false; // Failed to create image } @@ -19303,11 +20827,13 @@ namespace olc::imload bool ImageLoader_MacOS::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { + olc_IgnoreUnused(image, sFileName); return false; } bool ImageLoader_MacOS::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) { + olc_IgnoreUnused(image, data); return false; } } @@ -19406,7 +20932,7 @@ namespace olc::imload png_read_info(png, info); png_byte color_type; png_byte bit_depth; - image.Create( + image.CreateNoGPU( { static_cast(png_get_image_width(png, info)), static_cast(png_get_image_height(png, info)) @@ -19486,7 +21012,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); @@ -19542,7 +21068,7 @@ namespace olc::imload return false; } - image.Create({ + image.CreateNoGPU({ AImageDecoderHeaderInfo_getWidth(info), AImageDecoderHeaderInfo_getHeight(info) }); @@ -19626,7 +21152,7 @@ namespace olc::imload return false; } - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), bytes, width * height * 4); delete[] bytes; @@ -19643,7 +21169,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; @@ -19660,7 +21186,7 @@ namespace olc::imload if(!pixelData) return false; - image.Create({width, height}); + image.CreateNoGPU({width, height}); std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); delete[] pixelData; diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index b6839154..f645510f 100644 --- a/utilities/olcUTIL3_Camera2D.h +++ b/utilities/olcUTIL3_Camera2D.h @@ -223,13 +223,13 @@ namespace olc::utils case Mode::FixedScreens: { - m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f); + m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vViewSize) * olc::vi2d(m_vViewSize)) + (m_vViewSize * 0.5f); } break; case Mode::SlideScreens: { - olc::vf2d vScreen = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f); + olc::vf2d vScreen = olc::vf2d(olc::vi2d(GetTarget() / m_vViewSize) * olc::vi2d(m_vViewSize)) + (m_vViewSize * 0.5f); m_vPosition += (vScreen - m_vPosition) * m_fLazyFollowRate * 2.0f * fElapsedTime; } break; diff --git a/utilities/olcUTIL3_QuadTree.h b/utilities/olcUTIL3_QuadTree.h new file mode 100644 index 00000000..69f34c24 --- /dev/null +++ b/utilities/olcUTIL3_QuadTree.h @@ -0,0 +1,351 @@ +/* + OneLoneCoder - QuadTree v2.0 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A Dynamic Quad-Tree implementation to store objects in a 2D space + with a fast retrieval. + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2026 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ©OneLoneCoder 2019 - 2026 + + + Changes + ~~~~~~~ + v1.01: Bug fix clear() function + v1.02: File Restructure + +Added insertion return iterator + v2.0 : PGE 3.0 Refactor + +*/ + + +/* + WARNING! VECTORS OF QUAD-TREES + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + This quad tree implementation will suffer when stored inside a container + that can reallocate its contents. Upon reallocation, all of the iterators + used to link the quad tree to its items will be invalidated. + + Currently, I advise caution when using std::vector of this quad tree, and + should you choose to, then reserve space in advance so reallocation cannot + occur. +*/ + + +#pragma once + +#include "utilities/olcUTIL3_Geometry2D.h" + +namespace olc::utils +{ + template + struct QuadTreeItemLocation + { + // Pointer to Top-Level Container + typename std::list, T>>* container = nullptr; + // Iterator that points to item in Top-Level Container + typename std::list, T>>::iterator iterator; + }; + + template + class DynamicQuadTree + { + public: + DynamicQuadTree(const geom2d::rect& size, const size_t nDepth = 0, const size_t nMaxDepth = 8) + { + m_depth = nDepth; + m_rect = size; + m_maxdepth = nMaxDepth; + resize(m_rect); + } + + // Insert a region into this area + QuadTreeItemLocation insert(const pT item, const geom2d::rect& itemsize) + { + for (int i = 0; i < 4; i++) + { + if (geom2d::contains(m_rChild[i], itemsize)) + { + // Have we reached depth limit? + if (m_depth + 1 < m_maxdepth) + { + // No, so does child exist? + if (!m_pChild[i]) + { + // No, so create it + m_pChild[i] = std::make_shared>(m_rChild[i], m_depth + 1, m_maxdepth); + } + + // Yes, so add item to it + return m_pChild[i]->insert(item, itemsize); + } + } + } + + // It didnt fit, so item must belong to this geom2d::rect + m_pItems.push_back({ itemsize, item }); + return { &m_pItems, std::prev(m_pItems.end()) }; + } + + void relocate(pT item, const geom2d::rect& rArea) + { + // Remove it + remove(item); + + // Reinsert it with new location + insert(item, rArea); + } + + size_t size() const + { + size_t nCount = m_pItems.size(); + for (int i = 0; i < 4; i++) + if (m_pChild[i]) nCount += m_pChild[i]->size(); + return nCount; + } + + void search(const geom2d::rect& rArea, std::list& listItems) const + { + // First, check for items belonging to this area, add them to the list + // if there is overlap + for (const auto& p : m_pItems) + { + if (geom2d::overlaps(rArea,p.first)) + listItems.push_back(p.second); + } + + // Second, recurse through children and see if they can + // add to the list + for (int i = 0; i < 4; i++) + { + if (m_pChild[i]) + { + // If child is entirely contained within area, recursively + // add all of its children, no need to check boundaries + if (geom2d::contains(rArea,m_rChild[i])) + m_pChild[i]->items(listItems); + + // If child overlaps with search area then checks need + // to be made + else if (geom2d::overlaps(m_rChild[i],rArea)) + m_pChild[i]->search(rArea, listItems); + } + } + + + } + + void items(std::list& listItems) const + { + // No questions asked, just return child items + for (const auto& p : m_pItems) + listItems.push_back(p.second); + + for (int i = 0; i < 4; i++) + if (m_pChild[i]) m_pChild[i]->items(listItems); + } + + void clear() + { + m_pItems.clear(); + + for (int i = 0; i < 4; i++) + { + if (m_pChild[i]) + m_pChild[i]->clear(); + else + m_pChild[i].reset(); + } + } + + void resize(const geom2d::rect& rArea) + { + clear(); + m_rect = rArea; + olc::v_2d vChildSize = m_rect.size / CTYPE(2); + m_rChild = + { + geom2d::rect(m_rect.pos, vChildSize), + geom2d::rect({m_rect.pos.x + vChildSize.x, m_rect.pos.y}, vChildSize), + geom2d::rect({m_rect.pos.x, m_rect.pos.y + vChildSize.y}, vChildSize), + geom2d::rect(m_rect.pos + vChildSize, vChildSize) + }; + } + + + const geom2d::rect& area() + { + return m_rect; + } + + protected: + size_t m_depth = 0; + size_t m_maxdepth = 8; + + // Area of this quadnode + geom2d::rect m_rect; + + // 4 child areas of this quadnode + std::array, 4> m_rChild{}; + + // 4 potential children of this quadnode + std::array>, 4> m_pChild{}; + + // Items which belong to this quadnode + std::list, pT>> m_pItems; + + }; + + template + struct QuadTreeItem + { + // The item Itself + T item; + + // A "locator" to the container/iterator that points to this item's iterator in the + // top level list - phew + QuadTreeItemLocation>::iterator> pItem; + }; + + template + class QuadTreeContainer + { + using IQuadtreeContainer = std::list>; + + public: + QuadTreeContainer(const geom2d::rect& size = { {0.0f, 0.0f}, { 100.0f, 100.0f } }, const size_t nDepth = 0, const size_t nMaxDepth = 8) : root(size, nDepth, nMaxDepth) + { + + } + + // Sets the spatial coverage area of teh quadtree + void resize(const geom2d::rect& rArea) + { + root.resize(rArea); + } + + // Inserts an item into the quadtree + typename IQuadtreeContainer::iterator insert(const T& item, const geom2d::rect& itemsize) + { + QuadTreeItem newItem; + newItem.item = item; + + // Item i stored in container + m_allItems.emplace_back(newItem); + + // Pointer/Area of item is stored in geom2d::rect tree + m_allItems.back().pItem = root.insert(std::prev(m_allItems.end()), itemsize); + + return std::prev(m_allItems.end()); + } + + // Returns a std::list of pointers to items within the search area + std::list search(const geom2d::rect& rArea) const + { + std::list listItemPointers; + root.search(rArea, listItemPointers); + return listItemPointers; + } + + void remove(typename IQuadtreeContainer::iterator& item) + { + // Iterator points to a QuadTreeItem + item->pItem.container->erase(item->pItem.iterator); + + // Remove actual item from container + m_allItems.erase(item); + } + + void relocate(typename IQuadtreeContainer::iterator& item, const geom2d::rect& itemsize) + { + // Remove pointer to item from whichever container its stored in + item->pItem.container->erase(item->pItem.iterator); + + // Update the items pointer by reinsertion into geom2d::rect tree + item->pItem = root.insert(item, itemsize); + + } + + typename IQuadtreeContainer::iterator begin() + { + return m_allItems.begin(); + } + + typename IQuadtreeContainer::iterator end() + { + return m_allItems.end(); + } + + typename IQuadtreeContainer::const_iterator cbegin() + { + return m_allItems.cbegin(); + } + + typename IQuadtreeContainer::const_iterator cend() + { + return m_allItems.cend(); + } + + size_t size() const + { + return root.size(); + } + + void clear() + { + m_allItems.clear(); + root.clear(); + } + + const geom2d::rect& area() + { + return root.area(); + } + + protected: + DynamicQuadTree root; + IQuadtreeContainer m_allItems; + }; +}