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