├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── TODO.md ├── addon_config.mk ├── docs ├── Doxyfile └── ios_screenshot.png ├── examples ├── advanced │ ├── multi_window │ │ ├── addons.make │ │ └── src │ │ │ ├── SecondApp.cpp │ │ │ ├── SecondApp.h │ │ │ ├── main.cpp │ │ │ ├── ofApp.cpp │ │ │ └── ofApp.h │ └── multi_window_one_app │ │ ├── addons.make │ │ └── src │ │ ├── main.cpp │ │ ├── ofApp.cpp │ │ └── ofApp.h ├── basic │ ├── multi_callback_functions │ │ ├── addons.make │ │ └── src │ │ │ ├── main.cpp │ │ │ ├── ofApp.cpp │ │ │ └── ofApp.h │ └── single_callback_function │ │ ├── Makefile │ │ ├── addons.make │ │ ├── config.make │ │ ├── single_callback_function.qbs │ │ └── src │ │ ├── main.cpp │ │ ├── ofApp.cpp │ │ └── ofApp.h └── ios │ ├── advanced │ ├── addons.make │ └── src │ │ ├── main.mm │ │ ├── ofApp.h │ │ └── ofApp.mm │ └── basic │ ├── addons.make │ └── src │ ├── main.mm │ ├── ofApp.h │ └── ofApp.mm ├── libs └── ofxPointer │ ├── include │ └── ofx │ │ ├── PointerEvents.h │ │ └── PointerEventsiOS.h │ └── src │ ├── PointerEvents.cpp │ └── PointerEventsiOS.mm ├── scripts ├── ci │ └── docs │ │ ├── after_success.sh │ │ ├── build.sh │ │ └── install.sh └── install.sh └── src └── ofxPointer.h /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | os: Visual Studio 2015 RC 3 | 4 | environment: 5 | global: 6 | APPVEYOR_OS_NAME: windows 7 | matrix: 8 | #MSYS2 Building 9 | - platform: x86 10 | BUILDER: MSYS2 11 | 12 | #VisualStudio Building 13 | - platform: x86 14 | BUILDER : VS 15 | BITS: 32 16 | - platform: x64 17 | BUILDER : VS 18 | BITS: 64 19 | 20 | configuration: Debug 21 | shallow_clone: true 22 | clone_depth: 10 23 | init: 24 | - set MSYS2_PATH=c:\msys64 25 | - set CHERE_INVOKING=1 26 | - if "%BUILDER%_%PLATFORM%"=="MSYS2_x86" set MSYSTEM=MINGW32 27 | - if "%BUILDER%_%PLATFORM%"=="MSYS2_x64" set MSYSTEM=MINGW64 28 | - if "%BUILDER%"=="VS" set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% 29 | 30 | install: 31 | - cd .. 32 | - git clone --depth=1 --branch=master https://github.com/openframeworks/openFrameworks 33 | - call openFrameworks\scripts\ci\addons\install.cmd 34 | 35 | build_script: 36 | - cd %OF_PATH% 37 | - scripts\ci\addons\build.cmd 38 | 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # A .gitignore for openFrameworks addons. 2 | 3 | ################################################################################ 4 | # Documentation 5 | ################################################################################ 6 | 7 | # Doxygen-Related 8 | /docs/* 9 | !/docs/Doxyfile 10 | 11 | # Instructions and other documentation files. 12 | !/docs/*.md 13 | !/docs/*.txt 14 | 15 | ################################################################################ 16 | # Examples 17 | ################################################################################ 18 | 19 | # bin/data Folder 20 | /example*/**/bin/data/* 21 | 22 | # Include data.txt ... 23 | # TODO 24 | 25 | # mediaAssets 26 | /example*/**/mediaAssets/* 27 | 28 | # Project files 29 | /example*/**/*.plist 30 | /example*/**/*.pch 31 | /example*/**/*.xcconfig 32 | /example*/**/*.xcodeproj 33 | 34 | 35 | ################################################################################ 36 | # Tests 37 | ################################################################################ 38 | 39 | # bin/data Folder 40 | /tests/*/bin/data/* 41 | /!tests/*/bin/data/data.txt 42 | 43 | # Project files 44 | /tests/*/* 45 | !/tests/*/src/ 46 | !/tests/*/addons.make 47 | 48 | ################################################################################ 49 | # Libraries 50 | ################################################################################ 51 | 52 | # Exclude 3rd party libs by default. 53 | libs/* 54 | 55 | # Don't exclude ofx libs by default. 56 | !libs/ofx* 57 | 58 | ################################################################################ 59 | # Shared Data 60 | ################################################################################ 61 | 62 | /shared/data/* 63 | !/shared/data/*.sh 64 | 65 | ################################################################################ 66 | # Scripts 67 | ################################################################################ 68 | 69 | # Everything included by default. 70 | 71 | ################################################################################ 72 | # Build Artifacts 73 | ################################################################################ 74 | 75 | # Default Qt Creator Build Folders 76 | build-example*/ 77 | 78 | ################################################################################ 79 | # Utility Stuff 80 | ################################################################################ 81 | 82 | # Things to exclude quickly. 83 | _* 84 | 85 | # Local folders, use for temporary storage, etc. 86 | __*/* 87 | 88 | # Temporary files. 89 | **.autosave 90 | 91 | # OSX 92 | **.DS_Store 93 | **.AppleDouble 94 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c++ 2 | compiler: gcc 3 | sudo: true 4 | matrix: 5 | include: 6 | # fully specify builds, include can't dynamically expand matrix entries 7 | # relative order of sudo and env is important so that addons: is recognized 8 | 9 | # Linux 64bit, OF master 10 | - os: linux 11 | dist: trusty 12 | sudo: required 13 | env: TARGET="linux64" OF_BRANCH="master" 14 | addons: 15 | apt: 16 | sources: 17 | - ubuntu-toolchain-r-test 18 | packages: 19 | - gcc-4.9 20 | - g++-4.9 21 | - gdb 22 | 23 | # OSX, OF master 24 | - os: osx 25 | osx_image: xcode8 26 | compiler: clang 27 | env: TARGET="osx" OF_BRANCH="master" 28 | 29 | # Exclude the default build that would otherwise be generated 30 | # see https://github.com/travis-ci/travis-ci/issues/1228 31 | exclude: 32 | - compiler: gcc 33 | before_install: 34 | - curl -SLsO https://raw.githubusercontent.com/bakercp/ofxAddonScripts/${OF_BRANCH}/scripts/ci/tools/install.sh 35 | - source install.sh 36 | install: 37 | - do_install 38 | script: 39 | - do_script 40 | 41 | git: 42 | depth: 10 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christopher Baker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ofxPointer 2 | 3 | ## Description 4 | 5 | This frameworks adapts the [W3C Pointer Event](http://www.w3.org/TR/pointerevents/) specification to work with openFrameworks. A compatibility layer for iOS / `UITouch` includes support for high-frequency coalesced events, predicted events, event property updates, tilt, pressure and other advanced properties. 6 | 7 | ## Features 8 | 9 | Currently the openFrameworks pointer model separates touch and mouse events, making cross platform development a little tricky. 10 | 11 | Simply put, `ofxPointer` merges pointer, touch and pen input into a single extensible interface. 12 | 13 | A typical program will now look like this: 14 | 15 | 16 | ### All Platforms 17 | 18 | ```c++ 19 | 20 | #pragma once 21 | 22 | 23 | #include "ofMain.h" 24 | #include "ofxPointer.h" 25 | 26 | 27 | class ofApp: public ofBaseApp 28 | { 29 | public: 30 | void setup() override 31 | { 32 | // Register the pointer events. 33 | ofx::RegisterPointerEvents(this); 34 | } 35 | 36 | void update() override 37 | { 38 | // Update 39 | } 40 | 41 | 42 | void draw() override 43 | { 44 | // Draw 45 | } 46 | 47 | 48 | void onPointerUp(ofx::PointerEventArgs& evt) 49 | { 50 | ofLogNotice("ofApp::onPointerUp") << evt; 51 | } 52 | 53 | 54 | void onPointerDown(ofx::PointerEventArgs& evt) 55 | { 56 | ofLogNotice("ofApp::onPointerDown") << evt; 57 | } 58 | 59 | 60 | void pointerMove(ofx::PointerEventArgs& evt) 61 | { 62 | ofLogNotice("ofApp::onPointerMove") << evt; 63 | } 64 | 65 | 66 | void onPointerCancel(ofx::PointerEventArgs& evt) 67 | { 68 | ofLogNotice("ofApp::onPointerCancel") << evt; 69 | } 70 | 71 | }; 72 | 73 | ``` 74 | 75 | Or even more simply, like this: 76 | 77 | ```c++ 78 | 79 | #pragma once 80 | 81 | 82 | #include "ofMain.h" 83 | #include "ofxPointer.h" 84 | 85 | 86 | class ofApp: public ofBaseApp 87 | { 88 | public: 89 | void setup() override 90 | { 91 | // Register the pointer events. 92 | ofx::RegisterPointerEvent(this); 93 | } 94 | 95 | void update() override 96 | { 97 | // Update 98 | } 99 | 100 | 101 | void draw() override 102 | { 103 | // Draw 104 | } 105 | 106 | // All pointer events are returned to a single callback. 107 | void onPointerEvent(ofx::PointerEventArgs& evt) 108 | { 109 | ofLogNotice("ofApp::onPointerEvent") << evt; 110 | } 111 | }; 112 | 113 | ``` 114 | 115 | ### iOS 116 | 117 | iOS Currently supports all advanced `UITouch` features including tiltX/Y, elevation, azimuth, precise location, pressure, predicted and coalesced events, estimated properties and estimated property updates. See the iOS examples for more. 118 | 119 | ![Screenshot](https://github.com/bakercp/ofxPointer/raw/master/docs/ios_screenshot.png) 120 | 121 | ## Getting Started 122 | 123 | To get started, generate the example project files using the openFrameworks [Project Generator](http://openframeworks.cc/learning/01_basics/how_to_add_addon_to_project/). 124 | 125 | ## Documentation 126 | 127 | API documentation can be found here. 128 | 129 | ## Build Status 130 | 131 | Linux, macOS [![Build Status](https://travis-ci.org/bakercp/ofxPointer.svg?branch=master)](https://travis-ci.org/bakercp/ofxPointer) 132 | 133 | Visual Studio, MSYS [![Build status](https://ci.appveyor.com/api/projects/status/p2v0cy2yy8gli402/branch/master?svg=true)](https://ci.appveyor.com/project/bakercp/ofxpointer/branch/master) 134 | 135 | 136 | ## Compatibility 137 | 138 | The `stable` branch of this repository is meant to be compatible with the openFrameworks [stable branch](https://github.com/openframeworks/openFrameworks/tree/stable), which corresponds to the latest official openFrameworks release. 139 | 140 | The `master` branch of this repository is meant to be compatible with the openFrameworks [master branch](https://github.com/openframeworks/openFrameworks/tree/master). 141 | 142 | Some past openFrameworks releases are supported via tagged versions, but only `stable` and `master` branches are actively supported. 143 | 144 | ## Versioning 145 | 146 | This project uses Semantic Versioning, although strict adherence will only come into effect at version 1.0.0. 147 | 148 | ## Licensing 149 | 150 | See `LICENSE.md`. 151 | 152 | ## Contributing 153 | 154 | Pull Requests are always welcome, so if you make any improvements please feel free to float them back upstream :) 155 | 156 | 1. Fork this repository. 157 | 2. Create your feature branch (`git checkout -b my-new-feature`). 158 | 3. Commit your changes (`git commit -am 'Add some feature'`). 159 | 4. Push to the branch (`git push origin my-new-feature`). 160 | 5. Create new Pull Request. 161 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - `static PointerEventArgs::toPointerEvent(...)` 2 | - Needs context in order to correctly set isPrimary. 3 | 4 | - Add support for Android advanced pointer features. 5 | - [link](https://developer.android.com/training/gestures/movement) 6 | 7 | - `ofxiOSWindowControllerType` of `METAL_KIT` and `GL_KIT` result in distorted pointer coordinates. 8 | 9 | - The `button` property throughout is not to spec, but `buttons` shoudl be. 10 | -------------------------------------------------------------------------------- /addon_config.mk: -------------------------------------------------------------------------------- 1 | meta: 2 | ADDON_NAME = ofxPointer 3 | ADDON_DESCRIPTION = A cross-platform pointer event unification framework. 4 | ADDON_AUTHOR = bakercp 5 | ADDON_TAGS = "mouse" "touch" "multitouch" "pointer" "cross-platform" "pen" 6 | ADDON_URL = http://github.com/bakercp/ofxPointer 7 | -------------------------------------------------------------------------------- /docs/ios_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bakercp/ofxPointer/3fdc0da772f8df6a9b44225fbf73c06a67a2d49a/docs/ios_screenshot.png -------------------------------------------------------------------------------- /examples/advanced/multi_window/addons.make: -------------------------------------------------------------------------------- 1 | ofxPointer 2 | -------------------------------------------------------------------------------- /examples/advanced/multi_window/src/SecondApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "SecondApp.h" 9 | 10 | 11 | void SecondApp::setup() 12 | { 13 | ofSetLogLevel(OF_LOG_VERBOSE); 14 | ofSetWindowTitle("SecondApp"); 15 | 16 | 17 | ofx::PointerDebugRenderer::Settings settings; 18 | settings.pointColor = ofColor::red; 19 | renderer.setup(settings); 20 | } 21 | 22 | 23 | void SecondApp::update() 24 | { 25 | renderer.update(); 26 | } 27 | 28 | 29 | void SecondApp::draw() 30 | { 31 | ofDrawBitmapString(ofGetFrameRate(), 20, 20); 32 | renderer.draw(); 33 | } 34 | 35 | 36 | void SecondApp::onPointerEvent(ofx::PointerEventArgs& evt) 37 | { 38 | renderer.add(evt); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /examples/advanced/multi_window/src/SecondApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "ofMain.h" 12 | #include "ofxPointer.h" 13 | 14 | 15 | class SecondApp: public ofBaseApp 16 | { 17 | public: 18 | void setup() override; 19 | void update() override; 20 | void draw() override; 21 | 22 | void onPointerEvent(ofx::PointerEventArgs& evt); 23 | 24 | ofx::PointerDebugRenderer renderer; 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /examples/advanced/multi_window/src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | #include "SecondApp.h" 10 | #include "ofAppGLFWWindow.h" 11 | 12 | 13 | int main() 14 | { 15 | ofGLFWWindowSettings settings; 16 | 17 | settings.setSize(600, 600); 18 | settings.setPosition(glm::vec2(300, 0)); 19 | settings.resizable = true; 20 | auto mainWindow = ofCreateWindow(settings); 21 | 22 | settings.setSize(300, 300); 23 | settings.setPosition(glm::vec2(0, 0)); 24 | settings.resizable = false; 25 | auto secondWindow = ofCreateWindow(settings); 26 | 27 | auto mainApp = std::make_shared(); 28 | auto secondApp = std::make_shared(); 29 | 30 | ofx::RegisterPointerEventForWindow(mainWindow.get(), mainApp.get()); 31 | ofx::RegisterPointerEventForWindow(secondWindow.get(), secondApp.get()); 32 | 33 | mainApp->secondApp = secondApp; 34 | 35 | ofRunApp(secondWindow, secondApp); 36 | ofRunApp(mainWindow, mainApp); 37 | 38 | return ofRunMainLoop(); 39 | } 40 | -------------------------------------------------------------------------------- /examples/advanced/multi_window/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | void ofApp::setup() 12 | { 13 | ofSetLogLevel(OF_LOG_VERBOSE); 14 | ofSetWindowTitle("ofApp"); 15 | } 16 | 17 | 18 | void ofApp::update() 19 | { 20 | renderer.update(); 21 | } 22 | 23 | 24 | void ofApp::draw() 25 | { 26 | ofDrawBitmapString(ofGetFrameRate(), 20, 20); 27 | renderer.draw(); 28 | } 29 | 30 | 31 | void ofApp::onPointerEvent(ofx::PointerEventArgs& evt) 32 | { 33 | renderer.add(evt); 34 | } 35 | -------------------------------------------------------------------------------- /examples/advanced/multi_window/src/ofApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "ofMain.h" 12 | #include "ofxPointer.h" 13 | #include "SecondApp.h" 14 | 15 | 16 | class ofApp: public ofBaseApp 17 | { 18 | public: 19 | void setup() override; 20 | void update() override; 21 | void draw() override; 22 | 23 | // Pointer Events 24 | void onPointerEvent(ofx::PointerEventArgs& evt); 25 | 26 | std::shared_ptr secondApp; 27 | 28 | ofx::PointerDebugRenderer renderer; 29 | }; 30 | -------------------------------------------------------------------------------- /examples/advanced/multi_window_one_app/addons.make: -------------------------------------------------------------------------------- 1 | ofxPointer 2 | -------------------------------------------------------------------------------- /examples/advanced/multi_window_one_app/src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | #include "ofAppGLFWWindow.h" 10 | 11 | 12 | int main() 13 | { 14 | ofGLFWWindowSettings settings; 15 | 16 | settings.setSize(600, 600); 17 | settings.setPosition(glm::vec2(300, 0)); 18 | settings.resizable = true; 19 | auto mainWindow = ofCreateWindow(settings); 20 | 21 | 22 | settings.setSize(300, 300); 23 | settings.setPosition(glm::vec2(0, 0)); 24 | settings.resizable = false; 25 | auto secondWindow = ofCreateWindow(settings); 26 | secondWindow->setVerticalSync(false); 27 | secondWindow->setWindowTitle("Second Window"); 28 | 29 | auto mainApp = std::make_shared(); 30 | 31 | ofx::RegisterPointerEventForWindow(mainWindow.get(), mainApp.get()); 32 | ofx::RegisterPointerEventForWindow(secondWindow.get(), mainApp.get()); 33 | 34 | // Set up renderers for each window. 35 | ofx::PointerDebugRenderer::Settings rendererSettings; 36 | rendererSettings.strokeWidth = 50; 37 | mainApp.get()->renderers[mainWindow.get()] = ofx::PointerDebugRenderer(rendererSettings); 38 | rendererSettings.strokeWidth = 100; 39 | rendererSettings.pointColor = ofColor::red; 40 | mainApp.get()->renderers[secondWindow.get()] = ofx::PointerDebugRenderer(rendererSettings); 41 | 42 | ofAddListener(secondWindow->events().draw, 43 | mainApp.get(), 44 | &ofApp::drawSecondWindow); 45 | 46 | ofRunApp(mainWindow, mainApp); 47 | 48 | return ofRunMainLoop(); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /examples/advanced/multi_window_one_app/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | void ofApp::setup() 12 | { 13 | ofSetLogLevel(OF_LOG_VERBOSE); 14 | ofSetWindowTitle("ofApp"); 15 | } 16 | 17 | 18 | void ofApp::update() 19 | { 20 | for (auto& renderer: renderers) 21 | renderer.second.update(); 22 | } 23 | 24 | 25 | void ofApp::draw() 26 | { 27 | ofDrawBitmapString(ofGetFrameRate(), 20, 20); 28 | renderers[ofGetCurrentWindow().get()].draw(); 29 | } 30 | 31 | 32 | void ofApp::drawSecondWindow(ofEventArgs& args) 33 | { 34 | ofDrawBitmapString(ofGetFrameRate(), 20, 20); 35 | renderers[ofGetCurrentWindow().get()].draw(); 36 | } 37 | 38 | 39 | void ofApp::onPointerEvent(ofx::PointerEventArgs& evt) 40 | { 41 | renderers[reinterpret_cast(evt.eventSource())].add(evt); 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/advanced/multi_window_one_app/src/ofApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "ofMain.h" 12 | #include "ofxPointer.h" 13 | 14 | 15 | class ofApp: public ofBaseApp 16 | { 17 | public: 18 | void setup() override; 19 | void update() override; 20 | void draw() override; 21 | 22 | void drawSecondWindow(ofEventArgs& args); 23 | 24 | // All pointer events for both windows arrive here. 25 | void onPointerEvent(ofx::PointerEventArgs& evt); 26 | 27 | std::map renderers; 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /examples/basic/multi_callback_functions/addons.make: -------------------------------------------------------------------------------- 1 | ofxPointer 2 | -------------------------------------------------------------------------------- /examples/basic/multi_callback_functions/src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | int main() 12 | { 13 | ofSetupOpenGL(1024, 768, OF_WINDOW); 14 | return ofRunApp(std::make_shared()); 15 | } 16 | -------------------------------------------------------------------------------- /examples/basic/multi_callback_functions/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | void ofApp::setup() 12 | { 13 | ofBackground(255); 14 | ofx::RegisterPointerEvents(this); 15 | } 16 | 17 | 18 | void ofApp::update() 19 | { 20 | renderer.update(); 21 | } 22 | 23 | 24 | void ofApp::draw() 25 | { 26 | renderer.draw(); 27 | } 28 | 29 | 30 | void ofApp::onPointerUp(ofx::PointerEventArgs& evt) 31 | { 32 | renderer.add(evt); 33 | } 34 | 35 | 36 | void ofApp::onPointerDown(ofx::PointerEventArgs& evt) 37 | { 38 | renderer.add(evt); 39 | } 40 | 41 | 42 | void ofApp::onPointerMove(ofx::PointerEventArgs& evt) 43 | { 44 | renderer.add(evt); 45 | } 46 | 47 | 48 | void ofApp::onPointerCancel(ofx::PointerEventArgs& evt) 49 | { 50 | renderer.add(evt); 51 | } 52 | -------------------------------------------------------------------------------- /examples/basic/multi_callback_functions/src/ofApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "ofMain.h" 12 | #include "ofxPointer.h" 13 | 14 | 15 | class ofApp: public ofBaseApp 16 | { 17 | public: 18 | void setup() override; 19 | void update() override; 20 | void draw() override; 21 | 22 | void onPointerUp(ofx::PointerEventArgs& e); 23 | void onPointerDown(ofx::PointerEventArgs& e); 24 | void onPointerMove(ofx::PointerEventArgs& e); 25 | void onPointerCancel(ofx::PointerEventArgs& e); 26 | 27 | ofx::PointerDebugRenderer renderer; 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/Makefile: -------------------------------------------------------------------------------- 1 | # Attempt to load a config.make file. 2 | # If none is found, project defaults in config.project.make will be used. 3 | ifneq ($(wildcard config.make),) 4 | include config.make 5 | endif 6 | 7 | # make sure the the OF_ROOT location is defined 8 | ifndef OF_ROOT 9 | OF_ROOT=$(realpath ../../..) 10 | endif 11 | 12 | # call the project makefile! 13 | include $(OF_ROOT)/libs/openFrameworksCompiled/project/makefileCommon/compile.project.mk 14 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/addons.make: -------------------------------------------------------------------------------- 1 | ofxPointer 2 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/config.make: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # CONFIGURE PROJECT MAKEFILE (optional) 3 | # This file is where we make project specific configurations. 4 | ################################################################################ 5 | 6 | ################################################################################ 7 | # OF ROOT 8 | # The location of your root openFrameworks installation 9 | # (default) OF_ROOT = ../../.. 10 | ################################################################################ 11 | # OF_ROOT = ../../.. 12 | 13 | ################################################################################ 14 | # PROJECT ROOT 15 | # The location of the project - a starting place for searching for files 16 | # (default) PROJECT_ROOT = . (this directory) 17 | # 18 | ################################################################################ 19 | # PROJECT_ROOT = . 20 | 21 | ################################################################################ 22 | # PROJECT SPECIFIC CHECKS 23 | # This is a project defined section to create internal makefile flags to 24 | # conditionally enable or disable the addition of various features within 25 | # this makefile. For instance, if you want to make changes based on whether 26 | # GTK is installed, one might test that here and create a variable to check. 27 | ################################################################################ 28 | # None 29 | 30 | ################################################################################ 31 | # PROJECT EXTERNAL SOURCE PATHS 32 | # These are fully qualified paths that are not within the PROJECT_ROOT folder. 33 | # Like source folders in the PROJECT_ROOT, these paths are subject to 34 | # exlclusion via the PROJECT_EXLCUSIONS list. 35 | # 36 | # (default) PROJECT_EXTERNAL_SOURCE_PATHS = (blank) 37 | # 38 | # Note: Leave a leading space when adding list items with the += operator 39 | ################################################################################ 40 | # PROJECT_EXTERNAL_SOURCE_PATHS = 41 | 42 | ################################################################################ 43 | # PROJECT EXCLUSIONS 44 | # These makefiles assume that all folders in your current project directory 45 | # and any listed in the PROJECT_EXTERNAL_SOURCH_PATHS are are valid locations 46 | # to look for source code. The any folders or files that match any of the 47 | # items in the PROJECT_EXCLUSIONS list below will be ignored. 48 | # 49 | # Each item in the PROJECT_EXCLUSIONS list will be treated as a complete 50 | # string unless teh user adds a wildcard (%) operator to match subdirectories. 51 | # GNU make only allows one wildcard for matching. The second wildcard (%) is 52 | # treated literally. 53 | # 54 | # (default) PROJECT_EXCLUSIONS = (blank) 55 | # 56 | # Will automatically exclude the following: 57 | # 58 | # $(PROJECT_ROOT)/bin% 59 | # $(PROJECT_ROOT)/obj% 60 | # $(PROJECT_ROOT)/%.xcodeproj 61 | # 62 | # Note: Leave a leading space when adding list items with the += operator 63 | ################################################################################ 64 | # PROJECT_EXCLUSIONS = 65 | 66 | ################################################################################ 67 | # PROJECT LINKER FLAGS 68 | # These flags will be sent to the linker when compiling the executable. 69 | # 70 | # (default) PROJECT_LDFLAGS = -Wl,-rpath=./libs 71 | # 72 | # Note: Leave a leading space when adding list items with the += operator 73 | # 74 | # Currently, shared libraries that are needed are copied to the 75 | # $(PROJECT_ROOT)/bin/libs directory. The following LDFLAGS tell the linker to 76 | # add a runtime path to search for those shared libraries, since they aren't 77 | # incorporated directly into the final executable application binary. 78 | ################################################################################ 79 | # PROJECT_LDFLAGS=-Wl,-rpath=./libs 80 | 81 | ################################################################################ 82 | # PROJECT DEFINES 83 | # Create a space-delimited list of DEFINES. The list will be converted into 84 | # CFLAGS with the "-D" flag later in the makefile. 85 | # 86 | # (default) PROJECT_DEFINES = (blank) 87 | # 88 | # Note: Leave a leading space when adding list items with the += operator 89 | ################################################################################ 90 | # PROJECT_DEFINES = 91 | 92 | ################################################################################ 93 | # PROJECT CFLAGS 94 | # This is a list of fully qualified CFLAGS required when compiling for this 95 | # project. These CFLAGS will be used IN ADDITION TO the PLATFORM_CFLAGS 96 | # defined in your platform specific core configuration files. These flags are 97 | # presented to the compiler BEFORE the PROJECT_OPTIMIZATION_CFLAGS below. 98 | # 99 | # (default) PROJECT_CFLAGS = (blank) 100 | # 101 | # Note: Before adding PROJECT_CFLAGS, note that the PLATFORM_CFLAGS defined in 102 | # your platform specific configuration file will be applied by default and 103 | # further flags here may not be needed. 104 | # 105 | # Note: Leave a leading space when adding list items with the += operator 106 | ################################################################################ 107 | # PROJECT_CFLAGS = 108 | 109 | ################################################################################ 110 | # PROJECT OPTIMIZATION CFLAGS 111 | # These are lists of CFLAGS that are target-specific. While any flags could 112 | # be conditionally added, they are usually limited to optimization flags. 113 | # These flags are added BEFORE the PROJECT_CFLAGS. 114 | # 115 | # PROJECT_OPTIMIZATION_CFLAGS_RELEASE flags are only applied to RELEASE targets. 116 | # 117 | # (default) PROJECT_OPTIMIZATION_CFLAGS_RELEASE = (blank) 118 | # 119 | # PROJECT_OPTIMIZATION_CFLAGS_DEBUG flags are only applied to DEBUG targets. 120 | # 121 | # (default) PROJECT_OPTIMIZATION_CFLAGS_DEBUG = (blank) 122 | # 123 | # Note: Before adding PROJECT_OPTIMIZATION_CFLAGS, please note that the 124 | # PLATFORM_OPTIMIZATION_CFLAGS defined in your platform specific configuration 125 | # file will be applied by default and further optimization flags here may not 126 | # be needed. 127 | # 128 | # Note: Leave a leading space when adding list items with the += operator 129 | ################################################################################ 130 | # PROJECT_OPTIMIZATION_CFLAGS_RELEASE = 131 | # PROJECT_OPTIMIZATION_CFLAGS_DEBUG = 132 | 133 | ################################################################################ 134 | # PROJECT COMPILERS 135 | # Custom compilers can be set for CC and CXX 136 | # (default) PROJECT_CXX = (blank) 137 | # (default) PROJECT_CC = (blank) 138 | # Note: Leave a leading space when adding list items with the += operator 139 | ################################################################################ 140 | # PROJECT_CXX = 141 | # PROJECT_CC = 142 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/single_callback_function.qbs: -------------------------------------------------------------------------------- 1 | import qbs 2 | import qbs.Process 3 | import qbs.File 4 | import qbs.FileInfo 5 | import qbs.TextFile 6 | import "/home/bakercp/Programming/openFrameworks/libs/openFrameworksCompiled/project/qtcreator/ofApp.qbs" as ofApp 7 | 8 | Project{ 9 | property string of_root: '/home/bakercp/Programming/openFrameworks' 10 | 11 | ofApp { 12 | name: { return FileInfo.baseName(sourceDirectory) } 13 | 14 | files: [ 15 | 'src/main.cpp', 16 | 'src/ofApp.cpp', 17 | 'src/ofApp.h', 18 | ] 19 | 20 | // This project is using addons.make to include the addons 21 | // since it was imported from old code. To change it to include 22 | // the addons from the qbs file change the following lines to 23 | // the list of used addons in array format. eg: 24 | // 25 | // of.addons: [ 26 | // 'ofxGui', 27 | // 'ofxOpenCv', 28 | // ] 29 | 30 | // additional flags for the project. the of module sets some 31 | // flags by default to add the core libraries, search paths... 32 | // this flags can be augmented through the following properties: 33 | of.pkgConfigs: [] // list of additional system pkgs to include 34 | of.includePaths: [] // include search paths 35 | of.cFlags: [] // flags passed to the c compiler 36 | of.cxxFlags: [] // flags passed to the c++ compiler 37 | of.linkerFlags: [] // flags passed to the linker 38 | of.defines: [] // defines are passed as -D to the compiler 39 | // and can be checked with #ifdef or #if in the code 40 | of.frameworks: [] // osx only, additional frameworks to link with the project 41 | of.staticLibraries: [] // static libraries 42 | of.dynamicLibraries: [] // dynamic libraries 43 | 44 | // create a console window when the application start 45 | consoleApplication: true 46 | 47 | // other flags can be set through the cpp module: http://doc.qt.io/qbs/cpp-module.html 48 | // eg: this will enable ccache when compiling 49 | // 50 | // cpp.compilerWrapper: 'ccache' 51 | 52 | Depends{ 53 | name: "cpp" 54 | } 55 | 56 | // common rules that parse the include search paths, core libraries... 57 | Depends{ 58 | name: "of" 59 | } 60 | 61 | // dependency with the OF library 62 | Depends{ 63 | name: "openFrameworks" 64 | } 65 | } 66 | 67 | property bool makeOF: true // use makfiles to compile the OF library 68 | // will compile OF only once for all your projects 69 | // otherwise compiled per project with qbs 70 | 71 | property bool precompileOfMain: false // precompile ofMain.h 72 | // faster to recompile when including ofMain.h 73 | // but might use a lot of space per project 74 | 75 | references: [FileInfo.joinPaths(of_root, "/libs/openFrameworksCompiled/project/qtcreator/openFrameworks.qbs")] 76 | } 77 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | int main() 12 | { 13 | ofSetupOpenGL(1024, 768, OF_WINDOW); 14 | return ofRunApp(std::make_shared()); 15 | } 16 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | void ofApp::setup() 12 | { 13 | ofSetBackgroundColor(255); 14 | ofSetLogLevel(OF_LOG_VERBOSE); 15 | 16 | // Calling ofx::RegisterPointerEvent, will register a single callback with 17 | // the following method signature: 18 | // 19 | // void onPointerEvent(ofx::PointerEventArgs& evt) 20 | // 21 | ofx::RegisterPointerEvent(this); 22 | } 23 | 24 | 25 | void ofApp::update() 26 | { 27 | renderer.update(); 28 | } 29 | 30 | 31 | void ofApp::draw() 32 | { 33 | renderer.draw(); 34 | } 35 | 36 | 37 | void ofApp::onPointerEvent(ofx::PointerEventArgs& evt) 38 | { 39 | renderer.add(evt); 40 | } 41 | -------------------------------------------------------------------------------- /examples/basic/single_callback_function/src/ofApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "ofMain.h" 12 | #include "ofxPointer.h" 13 | 14 | 15 | class ofApp: public ofBaseApp 16 | { 17 | public: 18 | void setup() override; 19 | void update() override; 20 | void draw() override; 21 | 22 | // All pointer events are sent to this single callback. 23 | void onPointerEvent(ofx::PointerEventArgs& e); 24 | 25 | ofx::PointerDebugRenderer renderer; 26 | }; 27 | -------------------------------------------------------------------------------- /examples/ios/advanced/addons.make: -------------------------------------------------------------------------------- 1 | ofxPointer 2 | -------------------------------------------------------------------------------- /examples/ios/advanced/src/main.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "ofApp.h" 8 | 9 | 10 | int main() 11 | { 12 | ofiOSWindowSettings settings; 13 | settings.enableMultiTouch = true; 14 | 15 | auto mainWindow = ofCreateWindow(settings); 16 | auto mainApp = std::make_shared(); 17 | 18 | ofRunApp(mainWindow, mainApp); 19 | 20 | return ofRunMainLoop(); 21 | } 22 | -------------------------------------------------------------------------------- /examples/ios/advanced/src/ofApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | 10 | #include "ofxiOS.h" 11 | #include "ofxPointer.h" 12 | 13 | 14 | class ofApp: public ofxiOSApp 15 | { 16 | public: 17 | void setup() override; 18 | void update() override; 19 | void draw() override; 20 | 21 | void onPointerEvent(ofx::PointerEventArgs& evt); 22 | 23 | ofx::PointerDebugRenderer renderer; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /examples/ios/advanced/src/ofApp.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | void ofApp::setup() 12 | { 13 | ofSetLogLevel(OF_LOG_VERBOSE); 14 | 15 | ofBackground(255); 16 | 17 | // This example demonstrates the simplest way to get all advanced 18 | // PointerEvents. This method will creates a subview window that intercepts 19 | // all touch events before they are passed to the standard ofxiOSApp window. 20 | // Thus, in this advanced method, no events will be passed to the standard 21 | // ofxiOSApp::touch*(...) callbacks. 22 | // 23 | // Like the basic mode (e.g. ofx::RegisterPointerEvent(...) and 24 | // ofx::RegisterPointerEvents(...), one can call one can receve all pointer 25 | // events in seperate callbacks or in one unified callback. 26 | // 27 | // When calling ofx::RegisterAdvancedPointerEventsiOS(this), it is expected 28 | // that the following standard ofxPointer event methods are present in the 29 | // listener class: 30 | // 31 | // void onPointerUp(ofx::PointerEventArgs& evt); 32 | // void onPointerDown(ofx::PointerEventArgs& evt); 33 | // void onPointerMove(ofx::PointerEventArgs& evt); 34 | // void onPointerCancel(ofx::PointerEventArgs& evt); 35 | // 36 | // Additionally the following method must also be included: 37 | // 38 | // void onPointerUpdate(ofx::PointerEventArgs& evt); 39 | // 40 | // The onPointerUpdate(...) is called when iOS updates previously estimated 41 | // point data. If the previous points are not preserved, then these events 42 | // should be ignored. Otherwise, they can be matched with previous events 43 | // by examining the pointerId() and sequenceIndex() of the the stored 44 | // events. The PointerDebugRenderer::add(...) method demonstrates one way 45 | // this can be done. 46 | // 47 | // Alternatively, by calling ofx::RegisterAdvancedPointerEventiOS(this), 48 | // a single method with the signature of: 49 | // 50 | // void onPointerEvent(ofx::PointerEventArgs& evt) 51 | // 52 | // will receive all events, including pointerUpdate events. 53 | 54 | // Register a single advanced iOS PointerEvents callback. 55 | ofx::RegisterAdvancedPointerEventiOS(this); 56 | 57 | // There are several settings that can be set in the debug renderer. 58 | ofx::PointerDebugRenderer::Settings settings; 59 | settings.timeoutMillis = 2000; 60 | settings.strokeWidth = 200; 61 | renderer.setup(settings); 62 | } 63 | 64 | 65 | void ofApp::update() 66 | { 67 | renderer.update(); 68 | } 69 | 70 | 71 | void ofApp::draw() 72 | { 73 | renderer.draw(); 74 | } 75 | 76 | 77 | void ofApp::onPointerEvent(ofx::PointerEventArgs& evt) 78 | { 79 | renderer.add(evt); 80 | } 81 | -------------------------------------------------------------------------------- /examples/ios/basic/addons.make: -------------------------------------------------------------------------------- 1 | ofxPointer 2 | -------------------------------------------------------------------------------- /examples/ios/basic/src/main.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "ofApp.h" 8 | 9 | 10 | int main() 11 | { 12 | ofiOSWindowSettings settings; 13 | settings.enableMultiTouch = true; 14 | 15 | auto mainWindow = ofCreateWindow(settings); 16 | auto mainApp = std::make_shared(); 17 | 18 | ofRunApp(mainWindow, mainApp); 19 | 20 | return ofRunMainLoop(); 21 | } 22 | -------------------------------------------------------------------------------- /examples/ios/basic/src/ofApp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | 10 | #include "ofxiOS.h" 11 | #include "ofxPointer.h" 12 | 13 | 14 | class ofApp: public ofxiOSApp 15 | { 16 | public: 17 | void setup() override; 18 | void draw() override; 19 | 20 | void onPointerUp(ofx::PointerEventArgs& evt); 21 | void onPointerDown(ofx::PointerEventArgs& evt); 22 | void onPointerMove(ofx::PointerEventArgs& evt); 23 | void onPointerCancel(ofx::PointerEventArgs& evt); 24 | 25 | std::map strokes; 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /examples/ios/basic/src/ofApp.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofApp.h" 9 | 10 | 11 | void ofApp::setup() 12 | { 13 | ofSetLogLevel(OF_LOG_VERBOSE); 14 | ofSetBackgroundColor(255); 15 | 16 | // The simplest way to get basic PointerEvents. This method will capture 17 | // the default touch events and convert them into pointer events. 18 | // 19 | // Due to a limitation in openFrameworks, these pointer events will not 20 | // have a high temporal resolution or other useful touch data available 21 | // on iOS. To receive advanced touch data in the pointer events, see the 22 | // `example_ios_advanced` example. 23 | // 24 | // If the pointer events are consumed, then the the normal touch events 25 | // will not be called, otherwise, they will continued to be received via: 26 | // 27 | // void touchDown(ofTouchEventArgs& touch) override; 28 | // void touchMoved(ofTouchEventArgs& touch) override; 29 | // void touchUp(ofTouchEventArgs& touch) override; 30 | // void touchDoubleTap(ofTouchEventArgs& touch) override; 31 | // void touchCancelled(ofTouchEventArgs& touch) override; 32 | // 33 | // These standard callbacks are not included in this example for simplicity. 34 | // 35 | // When calling ofx::RegisterPointerEvents(this), it is expected that the 36 | // following methods are present in the listener class. 37 | // 38 | // void onPointerUp(ofx::PointerEventArgs& evt); 39 | // void onPointerDown(ofx::PointerEventArgs& evt); 40 | // void onPointerMove(ofx::PointerEventArgs& evt); 41 | // void onPointerCancel(ofx::PointerEventArgs& evt); 42 | // 43 | // or if pointer events can be consumed: 44 | // 45 | // bool onPointerUp(ofx::PointerEventArgs& evt); 46 | // bool onPointerDown(ofx::PointerEventArgs& evt); 47 | // bool onPointerMove(ofx::PointerEventArgs& evt); 48 | // bool onPointerCancel(ofx::PointerEventArgs& evt); 49 | // 50 | // For this method, use: 51 | 52 | ofx::RegisterPointerEvents(this); 53 | 54 | // Alternatively, a single callback function of the form: 55 | // 56 | // void ofApp::onPointerUp(ofx::PointerEventArgs& evt) 57 | // 58 | // ofx::RegisterPointerEvents(this); 59 | 60 | } 61 | 62 | 63 | void ofApp::draw() 64 | { 65 | ofSetColor(0); 66 | for (auto& stroke: strokes) 67 | stroke.second.draw(); 68 | } 69 | 70 | 71 | void ofApp::onPointerUp(ofx::PointerEventArgs& evt) 72 | { 73 | strokes[evt.pointerId()].addVertex(evt.position().x, 74 | evt.position().y); 75 | } 76 | 77 | 78 | void ofApp::onPointerDown(ofx::PointerEventArgs& evt) 79 | { 80 | strokes[evt.pointerId()] = ofPolyline(); 81 | strokes[evt.pointerId()].addVertex(evt.position().x, 82 | evt.position().y); 83 | } 84 | 85 | 86 | void ofApp::onPointerMove(ofx::PointerEventArgs& evt) 87 | { 88 | strokes[evt.pointerId()].addVertex(evt.position().x, 89 | evt.position().y); 90 | } 91 | 92 | 93 | void ofApp::onPointerCancel(ofx::PointerEventArgs& evt) 94 | { 95 | // Nothing to do. 96 | } 97 | -------------------------------------------------------------------------------- /libs/ofxPointer/include/ofx/PointerEvents.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2009 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include "json.hpp" 15 | #include "ofEvents.h" 16 | #include "ofColor.h" 17 | #include "ofUtils.h" 18 | #include "ofAppBaseWindow.h" 19 | #include "ofAppRunner.h" 20 | #include "ofRectangle.h" 21 | #include "ofLog.h" 22 | 23 | 24 | namespace ofx { 25 | 26 | 27 | class PointShape; 28 | class Point; 29 | class PointerEventArgs; 30 | 31 | 32 | /// \brief A base class describing the basic components of event arguments. 33 | /// 34 | /// Loosely based on DOM events. 35 | /// 36 | /// \sa https://dom.spec.whatwg.org/ 37 | class EventArgs: public ofEventArgs 38 | { 39 | public: 40 | /// \brief Create a default EventArgs. 41 | EventArgs(); 42 | 43 | /// \brief Create a BaseEventArgs with the given paramaters. 44 | /// \param eventSource The source of the event. 45 | /// \param eventType A string describing the type of the event. 46 | /// \param timestampMicros The timestamp of the event in microseconds. 47 | /// \param detail Optional event detail. 48 | EventArgs(const void* eventSource, 49 | const std::string& eventType, 50 | uint64_t timestampMicros, 51 | uint64_t detail); 52 | 53 | /// \brief Destroy the EventArgs. 54 | virtual ~EventArgs(); 55 | 56 | /// \returns the source of the event if available. 57 | const void* eventSource() const; 58 | 59 | /// \returns the event type. 60 | std::string eventType() const; 61 | 62 | /// \returns the timestamp of this event in milliseconds. 63 | uint64_t timestampMillis() const; 64 | 65 | /// \returns the timestamp of this event in microseconds. 66 | uint64_t timestampMicros() const; 67 | 68 | /// \returns the optional event detail. 69 | uint64_t detail() const; 70 | 71 | /// \brief An unknown event type. 72 | static const std::string EVENT_TYPE_UNKNOWN; 73 | 74 | private: 75 | /// \brief A pointer to the event source. 76 | const void* _eventSource = nullptr; 77 | 78 | /// \brief The name of this event type. 79 | /// \sa https://dom.spec.whatwg.org/#dom-event-type 80 | std::string _eventType; 81 | 82 | /// \brief The timestamp of this event in microseconds. 83 | uint64_t _timestampMicros = 0; 84 | 85 | /// \brief Detail for the event. 86 | /// 87 | /// Specifies some detail information about the event, depending on the type 88 | /// of event. 89 | /// 90 | /// \sa https://dom.spec.whatwg.org/#dom-customevent-detail 91 | uint64_t _detail = 0; 92 | 93 | }; 94 | 95 | 96 | /// \brief A PointShape describes the shape of a pointer. 97 | /// 98 | /// For standard pointers, such as a mouse or a pen, the width and height of the 99 | /// PointShape will be set to 1 while some touch pointers may define the 100 | /// size of the touch or even an ellipse describing the size and angle of a 101 | /// finger tip. Pens or other input may define a width and height smaller than 102 | /// 1. 103 | class PointShape 104 | { 105 | public: 106 | /// \brief The type of the point shape. 107 | enum class ShapeType 108 | { 109 | ELLIPSE, /// \brief Interpret width, height and angle as a rotated ellipse. 110 | RECTANGLE /// \brief Interpret width, height and angle as a rotated rectangle. 111 | }; 112 | 113 | /// \brief Create a default PointShape. 114 | PointShape(); 115 | 116 | /// \brief Create a square or circular PointShape with parameters. 117 | /// \param shapeType The type of the shape. 118 | /// \param size The diameter or width / height of the shape. 119 | /// \param sizeTolerance The tolerance of the diameter or width / height. 120 | PointShape(ShapeType shapeType, 121 | float size, 122 | float sizeTolerance); 123 | 124 | /// \brief Create a PointShape with parameters. 125 | /// \param shapeType The type of the shape. 126 | /// \param width The width of the shape. 127 | /// \param height The height of the shape. 128 | /// \param widthTolerance The width tolerance of the shape. 129 | /// \param heightTolerance The height tolerance of the shape. 130 | /// \param angleDeg The shape angle in degrees. 131 | PointShape(ShapeType shapeType, 132 | float width, 133 | float height, 134 | float widthTolerance, 135 | float heightTolerance, 136 | float angleDeg); 137 | 138 | /// \brief Destroy the PointShape. 139 | virtual ~PointShape(); 140 | 141 | /// \returns the ShapeType, which determines how to intepret dimensions. 142 | ShapeType shapeType() const; 143 | 144 | /// \returns the width the shape. 145 | float width() const; 146 | 147 | /// \returns the height of the shape. 148 | float height() const; 149 | 150 | /// \brief Get the tolerance of the shape width. 151 | /// 152 | /// The width range of the shape will be: 153 | /// width() + / - widthTolerance() 154 | /// 155 | /// \returns the tolerance of the shape width. 156 | float widthTolerance() const; 157 | 158 | /// \brief Get the tolerance of the shape height. 159 | /// 160 | /// The width range of the shape will be: 161 | /// height() + / - heightTolerance() 162 | /// 163 | /// \returns the tolerance of the shape height. 164 | float heightTolerance() const; 165 | 166 | /// \returns the the angle of the shape in degrees. 167 | float angleDeg() const; 168 | 169 | /// \returns the the angle of the shape in radians. 170 | float angleRad() const; 171 | 172 | /// \brief Get the axis-aligned width of the shape. 173 | /// 174 | /// This will be equal to width if the shape or rotation is 0. 175 | /// 176 | /// \returns the axis-aligned width of the shape. 177 | float axisAlignedWidth() const; 178 | 179 | /// \brief Get the axis-aligned height of the shape. 180 | /// 181 | /// This will be equal to height if the shape or rotation is 0. 182 | /// 183 | /// \returns the axis-aligned width of the shape. 184 | float axisAlignedHeight() const; 185 | 186 | protected: 187 | /// \brief Shape type for the pointer. 188 | ShapeType _shapeType = ShapeType::ELLIPSE; 189 | 190 | /// \brief The width of the rotated shape. 191 | float _width = 1; 192 | 193 | /// \brief The height of the rotated shape. 194 | float _height = 1; 195 | 196 | /// \brief Width tolerance. 197 | float _widthTolerance = 0; 198 | 199 | /// \brief Height tolerance. 200 | float _heightTolerance = 0; 201 | 202 | /// \brief Shape angle in degrees. 203 | float _angleDeg = 0; 204 | 205 | /// \brief Caclulate AABB for the shape on demand. 206 | void _calculateAxisAlignedSize() const; 207 | 208 | /// \brief True if the axis alignment was cached. 209 | mutable bool _axisAlignedSizeCached = false; 210 | 211 | /// \brief Axis-aligned bounding box width. 212 | mutable float _axisAlignedWidth = 0; 213 | 214 | /// \brief Axis-aligned box height. 215 | /// 216 | /// _height == 0 means the _height has not yet been calculated. 217 | mutable float _axisAlignedHeight = 0; 218 | 219 | friend Point; 220 | friend PointerEventArgs; 221 | 222 | }; 223 | 224 | 225 | inline std::string to_string(PointShape::ShapeType v) 226 | { 227 | switch (v) 228 | { 229 | case PointShape::ShapeType::ELLIPSE: 230 | return "ELLIPSE"; 231 | case PointShape::ShapeType::RECTANGLE: 232 | return "RECTANGLE"; 233 | } 234 | 235 | return "ELLIPSE"; 236 | } 237 | 238 | 239 | inline void to_json(nlohmann::json& j, const PointShape::ShapeType& v) 240 | { 241 | j = to_string(v); 242 | } 243 | 244 | 245 | inline void from_json(const nlohmann::json& j, PointShape::ShapeType& v) 246 | { 247 | std::string s = j.get(); 248 | if (!s.empty()) 249 | { 250 | if (s == to_string(PointShape::ShapeType::ELLIPSE)) 251 | { 252 | v = PointShape::ShapeType::ELLIPSE; 253 | return; 254 | } 255 | else if (s == to_string(PointShape::ShapeType::RECTANGLE)) 256 | { 257 | v = PointShape::ShapeType::RECTANGLE; 258 | return; 259 | } 260 | } 261 | 262 | ofLogWarning("from_json") << "Unknown value: " << s; 263 | v = PointShape::ShapeType::ELLIPSE; 264 | return; 265 | } 266 | 267 | 268 | inline void to_json(nlohmann::json& j, const PointShape& v) 269 | { 270 | j = { 271 | { "shape_type", v.shapeType() }, 272 | { "width", v.width() }, 273 | { "height", v.height() }, 274 | { "width_tolerance", v.widthTolerance() }, 275 | { "height_tolerance", v.heightTolerance() }, 276 | { "angle_deg", v.angleDeg() } 277 | }; 278 | } 279 | 280 | 281 | inline void from_json(const nlohmann::json& j, PointShape& v) 282 | { 283 | v = PointShape(j.value("shape_type", PointShape::ShapeType::ELLIPSE), 284 | j.value("width", float(1)), 285 | j.value("height", float(1)), 286 | j.value("width_tolerance", float(0)), 287 | j.value("height_tolerance", float(0)), 288 | j.value("angle_deg", float(0))); 289 | } 290 | 291 | 292 | /// \brief A class representing a pointer's point. 293 | /// 294 | /// Captures all position, shape, tilt rotation and pressure information. 295 | class Point 296 | { 297 | public: 298 | /// \brief Construct a Point 299 | Point(); 300 | 301 | /// \brief Construct a Point 302 | /// \param position The position in screen coordinates. 303 | Point(const glm::vec2& position); 304 | 305 | /// \brief Construct a Point 306 | /// \param position The position in screen coordinates. 307 | /// \param shape The point shape. 308 | Point(const glm::vec2& position, const PointShape& shape); 309 | 310 | /// \brief Construct a Point 311 | /// \param position The position in screen coordinates. 312 | /// \param pressure The normalized pressure. 313 | /// \param tiltXDeg The tilt X angle in degrees. 314 | /// \param tiltYDeg The tilt Y angle in degrees. 315 | Point(const glm::vec2& position, float pressure, float tiltXDeg, float tiltYDeg); 316 | 317 | /// \brief Construct a Point 318 | /// \param position The position in screen coordinates. 319 | /// \param shape The point shape. 320 | /// \param pressure The normalized pressure. 321 | Point(const glm::vec2& position, const PointShape& shape, float pressure); 322 | 323 | /// \brief Construct a Point 324 | /// \param position The position in screen coordinates. 325 | /// \param precisePosition The precise position in screen coordinates. 326 | /// \param shape The point shape. 327 | /// \param pressure The normalized pressure. 328 | /// \param tangentialPressure The tangential pressure (aka barrel pressure). 329 | /// \param twistDeg The twist angle in degrees. 330 | /// \param tiltXDeg The tilt X angle in degrees. 331 | /// \param tiltYDeg The tilt Y angle in degrees. 332 | Point(const glm::vec2& position, 333 | const glm::vec2& precisePosition, 334 | const PointShape& shape, 335 | float pressure, 336 | float tangentialPressure, 337 | float twistDeg, 338 | float tiltXDeg, 339 | float tiltYDeg); 340 | 341 | /// \brief Destroy the Point. 342 | virtual ~Point(); 343 | 344 | /// \returns the position in screen coordinates. 345 | glm::vec2 position() const; 346 | 347 | /// \returns the precise position in screen coordinates. 348 | glm::vec2 precisePosition() const; 349 | 350 | /// \brief Get the normalized point pressure. 351 | /// 352 | /// The normalized pressure is in the range [0, 1]. For devices that do not 353 | /// support pressure, the value is 0.5 when a button is active or 0 354 | /// otherwise. 355 | /// 356 | /// \returns the normalized point pressure [0, 1]. 357 | float pressure() const; 358 | 359 | /// \brief Get the Point's normalized tangential pressure. 360 | /// 361 | /// The normalized tangential pressure (aka the barrel pressure) is in the 362 | /// range [0, 1]. For devices that do not support tangential pressure, the 363 | /// value is 0. 364 | /// 365 | /// \returns the normalized tangential pressure [0, 1]. 366 | float tangentialPressure() const; 367 | 368 | /// \brief Get the Point's twist in degrees. 369 | /// 370 | /// The clockwise rotation (in degrees, in the range of [0,359]) of a 371 | /// transducer (e.g. pen stylus) around its own major axis. For hardware and 372 | /// platforms that do not report twist, the value MUST be 0. 373 | /// 374 | /// \returns the twist in degrees. 375 | float twistDeg() const; 376 | 377 | /// \brief Get the Point's twist in radians. 378 | /// 379 | /// The clockwise rotation (in degrees, in the range of [0,2*PI]) of a 380 | /// transducer (e.g. pen stylus) around its own major axis. For hardware and 381 | /// platforms that do not report twist, the value MUST be 0. 382 | /// 383 | /// \returns the twist in radians. 384 | float twistRad() const; 385 | 386 | /// \brief Get the Tilt X angle in degrees. 387 | /// 388 | /// Tilt X is given in degrees [-90, 90] between the Y-Z plane and the plane 389 | /// containing both the transducer (e.g. pen stylus) axis and the Y axis. A 390 | /// positive tilt X is to the right. The value is 0 if the tilt X is 391 | /// undefined. 392 | /// 393 | /// \returns the Tilt X angle in degrees. 394 | float tiltXDeg() const; 395 | 396 | /// \brief Get the Tilt X angle in radians. 397 | /// 398 | /// Tilt X is given in degrees [-PI/2, PI/2] between the Y-Z plane and the plane 399 | /// containing both the transducer (e.g. pen stylus) axis and the Y axis. A 400 | /// positive tilt X is to the right. The value is 0 if the tilt X is 401 | /// undefined. 402 | /// 403 | /// \returns the Tilt X angle in radians. 404 | float tiltXRad() const; 405 | 406 | /// \brief Get the Tilt Y angle in degrees. 407 | /// 408 | /// Tilt Y is given in degrees [-90, 90] between the X-Z plane and the plane 409 | /// containing both the transducer (e.g. pen stylus) axis and the X axis. A 410 | /// positive tilt Y is toward the user. The value is 0 if the tilt Y is 411 | /// undefined. 412 | /// 413 | /// \returns the Tilt Y angle in degrees. 414 | float tiltYDeg() const; 415 | 416 | /// \brief Get the Tilt Y angle in radians. 417 | /// 418 | /// Tilt Y is given in degrees [-PI/2, PI/2] between the X-Z plane and the plane 419 | /// containing both the transducer (e.g. pen stylus) axis and the X axis. A 420 | /// positive tilt Y is toward the user. The value is 0 if the tilt Y is 421 | /// undefined. 422 | /// 423 | /// \returns the Tilt Y angle in radians. 424 | float tiltYRad() const; 425 | 426 | /// \brief Get the azimuth angle in degress. 427 | /// 428 | /// The azimuth angle, in the range 0 to 360 degrees. 0 represents a stylus 429 | /// whose cap is pointing in the direction of increasing screen X values. 430 | /// 180 degrees represents a stylus whose cap is pointing in the direction 431 | /// of increasing screen Y values. The value is 0 if undefined. 432 | /// 433 | /// \returns the azimuth in degress. 434 | float azimuthDeg() const; 435 | 436 | /// \brief Get the azimuth angle in radians. 437 | /// 438 | /// The azimuth angle, in the range 0 to 2π radians. 0 represents a stylus 439 | /// whose cap is pointing in the direction of increasing screen X values. 440 | /// π/2 radians represents a stylus whose cap is pointing in the direction 441 | /// of increasing screen Y values. The value is 0 if undefined. 442 | /// 443 | /// \returns the azimuth in degress. 444 | float azimuthRad() const; 445 | 446 | /// \brief Get the altitude angle in degrees. 447 | /// 448 | /// The altitude angle, in the range 0 degrees (parallel to the surface) to 449 | /// 90 degrees (perpendicular to the surface). The value is 0 if undefined. 450 | /// 451 | /// \returns the altitude in degrees. 452 | float altitudeDeg() const; 453 | 454 | /// \brief Get the altitude angle in degrees. 455 | /// 456 | /// The altitude angle, in the range 0 radians (parallel to the surface) to 457 | /// π/2 radians (perpendicular to the surface). The value is 0 if undefined. 458 | /// 459 | /// \returns the altitude in radians. 460 | float altitudeRad() const; 461 | 462 | /// \brief Get the shape of the Point. 463 | /// \returns The PointShape. 464 | const PointShape& shape() const; 465 | 466 | /// \brief A debug utility for viewing the contents of Point. 467 | /// \returns A string representation of the Point. 468 | std::string toString() const 469 | { 470 | std::stringstream ss; 471 | 472 | ss << "------------" << std::endl; 473 | ss << " Position: " << position().x << "," << position().y << std::endl; 474 | ss << "Precise Position: " << precisePosition().x << "," << precisePosition().y << std::endl; 475 | ss << " Pressure: " << pressure() << std::endl; 476 | ss << " Tan. Pressure: " << tangentialPressure() << std::endl; 477 | ss << " Twist: " << twistDeg() << std::endl; 478 | ss << " TiltX: " << tiltXDeg() << std::endl; 479 | ss << " TiltY: " << tiltYDeg() << std::endl; 480 | // ss << " Shape: " << std::endl << shape().toString(); 481 | 482 | return ss.str(); 483 | } 484 | 485 | 486 | private: 487 | /// \brief The position in screen coordinates. 488 | glm::vec2 _position; 489 | 490 | /// \brief The Point's precise position if available. 491 | /// 492 | /// If a high resolution position is available, it will be set. Otherwise, 493 | /// this position will be equal to the _position variable. 494 | /// 495 | /// Typically, the _precisePosition should not be used for hit-testing, but 496 | /// rather the _position should. 497 | /// 498 | /// For example, this is available from Apple's UITouch. 499 | glm::vec2 _precisePosition; 500 | 501 | /// \brief The Point shape. 502 | PointShape _shape; 503 | 504 | /// \brief The Point's normalized pressure. 505 | float _pressure = 0; 506 | 507 | /// \brief The Point's tangential pressure (aka barrel pressure). 508 | float _tangentialPressure = 0; 509 | 510 | /// \brief The Point's twist in degrees. 511 | float _twistDeg = 0; 512 | 513 | /// \brief The Point tilt X angle in degrees. 514 | float _tiltXDeg = 0; 515 | 516 | /// \brief The Point tilt Y angle in degrees. 517 | float _tiltYDeg = 0; 518 | 519 | /// \brief A utility function for caching the azimuth and altitude. 520 | void _cacheAzimuthAltitude() const; 521 | 522 | /// \brief True if the azimuth and altitude are cached. 523 | mutable bool _azimuthAltitudeCached = false; 524 | 525 | /// \brief The cached azimuth in degrees. 526 | mutable float _azimuthDeg = 0; 527 | 528 | /// \brief The cached altitude in degrees. 529 | mutable float _altitudeDeg = 0; 530 | 531 | friend PointerEventArgs; 532 | }; 533 | 534 | 535 | // \todo This function is a replacement for automatic glm serialization using ofxSerializer. 536 | inline glm::vec2 to_vec_2_temp(const nlohmann::json& j) 537 | { 538 | return glm::vec2(j.value("x", float(0)), 539 | j.value("y", float(0))); 540 | } 541 | 542 | 543 | // \todo This function is a replacement for automatic glm serialization using ofxSerializer. 544 | inline nlohmann::json to_json_temp(const glm::vec2& v) 545 | { 546 | nlohmann::json j = { 547 | { "x", v.x }, 548 | { "y", v.y } 549 | }; 550 | return j; 551 | } 552 | 553 | 554 | inline void to_json(nlohmann::json& j, const Point& v) 555 | { 556 | j = 557 | { 558 | { "position", to_json_temp(v.position()) }, 559 | { "precise_position", to_json_temp(v.precisePosition()) }, 560 | { "shape", v.shape() }, 561 | { "pressure", v.pressure() }, 562 | { "tangential_pressure", v.tangentialPressure() }, 563 | { "twist_deg", v.twistDeg() }, 564 | { "tilt_x_deg", v.tiltXDeg() }, 565 | { "tilt_y_deg", v.tiltYDeg() } 566 | }; 567 | } 568 | 569 | 570 | inline void from_json(const nlohmann::json& j, Point& v) 571 | { 572 | nlohmann::json jp = j.value("position", nlohmann::json()); 573 | nlohmann::json jpp = j.value("precise_position", nlohmann::json()); 574 | 575 | v = Point(to_vec_2_temp(jp), 576 | to_vec_2_temp(jpp), 577 | j.value("shape", PointShape()), 578 | j.value("pressure", float(0)), 579 | j.value("tangential_pressure", float(0)), 580 | j.value("twist_deg", float(0)), 581 | j.value("tilt_x_deg", float(0)), 582 | j.value("tilt_y_deg", float(0))); 583 | } 584 | 585 | 586 | /// \brief A class representing all of the arguments in a pointer event. 587 | /// 588 | /// PointerEventArgs are usually passed as arguments in the openFrameworks event 589 | /// system, for example, POINTER_UP events might be dispatched to an event like: 590 | /// 591 | /// ofEvent onPointerUp; 592 | /// 593 | /// \sa https://w3c.github.io/pointerevents/ 594 | /// \sa https://w3c.github.io/pointerevents/extension.html 595 | class PointerEventArgs: public EventArgs 596 | { 597 | public: 598 | /// \brief Create a default PointerEventArgs. 599 | PointerEventArgs(); 600 | 601 | /// \brief Create a copy of the event with a new event type. 602 | /// \param eventType The new event type. 603 | /// \param event the event to copy. 604 | PointerEventArgs(const std::string& eventType, 605 | const PointerEventArgs& event); 606 | 607 | /// \brief Create a PointerEventArgs with parameters. 608 | /// \param eventSource The event source if available. 609 | /// \param eventType The pointer event type. 610 | /// \param timestampMicros The timestamp of this event in microseconds 611 | /// \param detail The optional event details. 612 | /// \param point The point. 613 | /// \param pointerId The unique pointer id. 614 | /// \param deviceId The unique input device id. 615 | /// \param pointerIndex The unique pointer index for the given device id. 616 | /// \param sequenceIndex The sequence index for this event or zero if not supported.. 617 | /// \param deviceType The device type string. 618 | /// \param isCoalesced Is this event delivered as coalesced. 619 | /// \param isPredicted Is this event predicted rather than measured. 620 | /// \param isPrimary True if this pointer is the primary pointer. 621 | /// \param button The button id for this event. 622 | /// \param buttons All pressed buttons for this pointer. 623 | /// \param modifiers All modifiers for this pointer. 624 | /// \param buttons All pressed buttons for this pointer. 625 | /// \param modifiers All modifiers for this pointer. 626 | /// \param coalescedPointerEvents Pointer events not delivered since the last frame, including a copy of the current event. 627 | /// \param predictedPointerEvents Predicted pointer events that will arrive between now and the next frame. 628 | /// \param estimatedProperties A set of estimated properties. 629 | /// \param estimatedPropertiesExpectingUpdates A set of estimated properties that are expecting updates. 630 | PointerEventArgs(const void* eventSource, 631 | const std::string& eventType, 632 | uint64_t timestampMicros, 633 | uint64_t detail, 634 | const Point& point, 635 | std::size_t pointerId, 636 | int64_t deviceId, 637 | int64_t pointerIndex, 638 | uint64_t sequenceIndex, 639 | const std::string& deviceType, 640 | bool isCoalesced, 641 | bool isPredicted, 642 | bool isPrimary, 643 | int16_t button, 644 | uint16_t buttons, 645 | uint16_t modifiers, 646 | const std::vector& coalescedPointerEvents, 647 | const std::vector& predictedPointerEvents, 648 | const std::set& estimatedProperties, 649 | const std::set& estimatedPropertiesExpectingUpdates); 650 | 651 | 652 | /// \brief Destroy the pointer event args. 653 | virtual ~PointerEventArgs(); 654 | 655 | /// \returns the Point data associated with this event. 656 | Point point() const; 657 | 658 | /// \brief Get the position of the event in screen coordinates. 659 | /// 660 | /// A convenience method equivalent to point().position(); 661 | /// 662 | /// \returns the position in screen coordinates. 663 | glm::vec2 position() const; 664 | 665 | /// \brief Get a single unique id for a device id and Pointer index. 666 | /// \sa https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid 667 | /// \returns a single unique id for a device id and Pointer index. 668 | std::size_t pointerId() const; 669 | 670 | /// \brief Get the unique input device id. 671 | /// \returns the unique input device id. 672 | int64_t deviceId() const; 673 | 674 | /// \brief Get the unique pointer index. 675 | /// 676 | /// This index should correspend to different touches for a multi-touch 677 | /// device. 678 | /// 679 | /// \returns the unique pointer index for the given device id or -1 if not supported. 680 | int64_t pointerIndex() const; 681 | 682 | /// \brief Get sequence index for this event. 683 | /// 684 | /// When supported it is a monotonically number. When unsupported, it is 685 | /// it is zero. 686 | /// 687 | /// \returns the sequence index for this event or -1 if not supported. 688 | uint64_t sequenceIndex() const; 689 | 690 | // /// \return a unique event key. 691 | // PointerKey pointerKey() const; 692 | 693 | /// \brief Get the device type string. 694 | /// 695 | /// This string may be TYPE_MOUSE, TYPE_TOUCH, TYPE_PEN, or a custom string. 696 | /// 697 | /// \returns a device description string. 698 | std::string deviceType() const; 699 | 700 | /// \returns true if the event was delivered as a coalesced event. 701 | bool isCoalesced() const; 702 | 703 | /// \returns true if this event was predicted rather than measured. 704 | bool isPredicted() const; 705 | 706 | /// \brief Determine if this pointer is the primary pointer. 707 | /// \returns true if this pointer is the primary pointer. 708 | /// \sa https://w3c.github.io/pointerevents/#the-primary-pointer 709 | bool isPrimary() const; 710 | 711 | /// \returns true if estimatedProperties().size() > 0. 712 | bool isEstimated() const; 713 | 714 | /// \brief Get the button id for this event. 715 | /// \returns the button id for this event. 716 | /// \todo This is not currently behaving to spec. 717 | /// \sa https://w3c.github.io/pointerevents/#the-button-property 718 | int16_t button() const; 719 | 720 | /// \brief Get all pressed buttons for this pointer. 721 | /// \return all pressed buttons for this pointer. 722 | /// \sa https://w3c.github.io/pointerevents/#button-states 723 | uint16_t buttons() const; 724 | 725 | /// \brief Get all modifiers for this pointer. 726 | /// \returns all modifiers for this pointer. 727 | uint16_t modifiers() const; 728 | 729 | /// \returns pointer events not delivered since the last frame, including a copy of the current event. 730 | std::vector coalescedPointerEvents() const; 731 | 732 | /// \returns predicted pointer events that will arrive between now and the next frame. 733 | std::vector predictedPointerEvents() const; 734 | 735 | /// \returns a set of estimated properties. 736 | std::set estimatedProperties() const; 737 | 738 | /// \returns a set of estimated properties that are expecting updates. 739 | std::set estimatedPropertiesExpectingUpdates() const; 740 | 741 | /// \brief Attempt to update properties with the given event. 742 | /// 743 | /// A property will be updated if: 744 | /// 745 | /// - The sequence() of both events is the same. 746 | /// - The updated property name is in the estimatedProperties() set. 747 | /// - The updated property name is in the estimatedPropertiesExpectingUpdates() set. 748 | /// 749 | /// \returns true if this event was successfully updated. 750 | bool updateEstimatedPropertiesWithEvent(const PointerEventArgs& e); 751 | 752 | /// \brief Utility to convert ofTouchEventArgs events to PointerEventArgs. 753 | /// \todo Does not set "isPrimary" correctly since it has no context. 754 | /// \param source The event source. 755 | /// \param e The touch event to convert. 756 | /// \returns a PointerEventArgs. 757 | static PointerEventArgs toPointerEventArgs(const void* source, 758 | const ofTouchEventArgs& e); 759 | 760 | /// \brief Utility to convert ofTouchEventArgs events to PointerEventArgs. 761 | /// \todo Does not set "isPrimary" correctly since it has no context. 762 | /// \param e The touch event to convert. 763 | /// \param source The event source. 764 | /// \returns a PointerEventArgs. 765 | static PointerEventArgs toPointerEventArgs(const void* source, 766 | const ofMouseEventArgs& e); 767 | 768 | /// \brief A debug utility for viewing the contents of PointerEventArgs. 769 | /// \returns A string representation of the PointerEventArgs. 770 | std::string toString() const 771 | { 772 | std::stringstream ss; 773 | 774 | ss << "------------" << std::endl; 775 | ss << " Source: " << eventSource() << std::endl; 776 | ss << " Event: " << eventType() << std::endl; 777 | ss << " Timestamp: " << timestampMillis() << std::endl; 778 | ss << " Pointer Id: " << pointerId() << std::endl; 779 | ss << " Device Id: " << deviceId() << std::endl; 780 | ss << "Device Type: " << deviceType() << std::endl; 781 | ss << " Button: " << button() << std::endl; 782 | ss << " Buttons: " << ofToBinary(buttons()) << std::endl; 783 | ss << " Modifiers: " << ofToBinary(modifiers()) << std::endl; 784 | ss << "Touch Index: " << pointerIndex() << std::endl; 785 | ss << "Sequence Id: " << sequenceIndex() << std::endl; 786 | 787 | return ss.str(); 788 | } 789 | 790 | /// \brief An event that is called when a property is updated. 791 | /// 792 | /// The name of the property is sent with the event. 793 | ofEvent pointerPropertyUpdate; 794 | 795 | /// \brief The mouse pointer type. 796 | static const std::string TYPE_MOUSE; 797 | 798 | /// \brief The pen pointer type. 799 | static const std::string TYPE_PEN; 800 | 801 | /// \brief The touch pointer type. 802 | static const std::string TYPE_TOUCH; 803 | 804 | /// \brief The unknown pointer type. 805 | static const std::string TYPE_UNKNOWN; 806 | 807 | /// \brief The pointer over event type. 808 | static const std::string POINTER_OVER; 809 | 810 | /// \brief The pointer enter event type. 811 | static const std::string POINTER_ENTER; 812 | 813 | /// \brief The pointer down event type. 814 | static const std::string POINTER_DOWN; 815 | 816 | /// \brief The pointer move event type. 817 | static const std::string POINTER_MOVE; 818 | 819 | /// \brief The pointer up event type. 820 | static const std::string POINTER_UP; 821 | 822 | /// \brief The pointer cancel event type. 823 | static const std::string POINTER_CANCEL; 824 | 825 | /// \brief The pointer update event type. 826 | static const std::string POINTER_UPDATE; 827 | 828 | /// \brief The pointer out event type. 829 | static const std::string POINTER_OUT; 830 | 831 | /// \brief The pointer leave event type. 832 | static const std::string POINTER_LEAVE; 833 | 834 | /// \brief The pointer scroll type. 835 | /// \todo This is not part of the PointerEvents spec. 836 | /// It should be an extension of a mouse event. 837 | static const std::string POINTER_SCROLL; 838 | 839 | /// \brief The got pointer capture event type. 840 | static const std::string GOT_POINTER_CAPTURE; 841 | 842 | /// \brief The lost pointer capture event type. 843 | static const std::string LOST_POINTER_CAPTURE; 844 | 845 | /// \brief Property key for position. 846 | static const std::string PROPERTY_POSITION; 847 | 848 | /// \brief Property key for pressure. 849 | static const std::string PROPERTY_PRESSURE; 850 | 851 | /// \brief Property key for tilt x. 852 | static const std::string PROPERTY_TILT_X; 853 | 854 | /// \brief Property key for tilt y. 855 | static const std::string PROPERTY_TILT_Y; 856 | 857 | friend std::ostream& operator << (std::ostream& os, const PointerEventArgs& e); 858 | 859 | private: 860 | /// \brief The location and orientation of the pointer. 861 | Point _point; 862 | 863 | /// \brief A unique pointer ID. 864 | /// 865 | /// This value must be unique from all other active pointers at any given 866 | /// time. Pointer indexes can be reused and are implementation specific. 867 | std::size_t _pointerId = 0; 868 | 869 | /// \brief The id of the device producing the pointer events. 870 | uint64_t _deviceId = 0; 871 | 872 | /// \brief The index of the pointer. 873 | int64_t _pointerIndex = 0; 874 | 875 | /// \brief The monotonically increasing sequence index for this event. 876 | uint64_t _sequenceIndex = 0; 877 | 878 | /// \brief The type of device that generated this Point. 879 | std::string _deviceType = TYPE_UNKNOWN; 880 | 881 | /// \brief Indicates if the event was delivered as a coalesced event. 882 | bool _isCoalesced = false; 883 | 884 | /// \brief Indicates if the event is predicted rather than measured. 885 | bool _isPredicted = false; 886 | 887 | /// \brief Indicates if the pointer is a primary pointer. 888 | /// 889 | /// In a multi-pointer (e.g. multi-touch) scenario, the isPrimary property 890 | /// is used to identify a master pointer amongst the set of active pointers 891 | /// for each pointer type. Only a primary pointer will produce compatibility 892 | /// mouse events. Authors who desire single-pointer interaction can achieve 893 | /// this by ignoring non-primary pointers. 894 | /// 895 | /// \sa http://www.w3.org/TR/pointerevents/#the-primary-pointer 896 | bool _isPrimary = false; 897 | 898 | /// \brief The current button associated with this event. 899 | int16_t _button = 0; 900 | 901 | /// \brief The current buttons being pressed. 902 | uint16_t _buttons = 0; 903 | 904 | /// \brief The current modifiers being pressed. 905 | uint16_t _modifiers = 0; 906 | 907 | /// \brief Pointer events not delivered since the last frame, including a copy of the current event. 908 | std::vector _coalescedPointerEvents; 909 | 910 | /// \brief Predicted pointer events that will arrive between now and the next frame. 911 | std::vector _predictedPointerEvents; 912 | 913 | /// \brief A set of estimated properties. 914 | std::set _estimatedProperties; 915 | 916 | /// \param A set of estimated properties that are expecting updates. 917 | std::set _estimatedPropertiesExpectingUpdates; 918 | 919 | friend class PointerEvents; 920 | 921 | }; 922 | 923 | 924 | /// \brief Write a PointerEventArgs to a std::ostream. 925 | /// \param os The std::ostream to write to. 926 | /// \param e The event to write. 927 | /// \returns a reference to the provided std::ostream. 928 | inline std::ostream& operator << (std::ostream& os, const PointerEventArgs& e) 929 | { 930 | os << e.toString(); 931 | return os; 932 | } 933 | 934 | 935 | 936 | inline void to_json(nlohmann::json& j, const PointerEventArgs& v) 937 | { 938 | j = 939 | { 940 | { "event_type", v.eventType() }, 941 | { "timestamp_micros", v.timestampMicros() }, 942 | { "detail", v.detail() }, 943 | { "point", v.point() }, 944 | { "pointer_id", v.pointerId() }, 945 | { "device_id", v.deviceId() }, 946 | { "pointer_index", v.pointerIndex() }, 947 | { "sequence_index", v.sequenceIndex() }, 948 | { "device_type", v.deviceType() }, 949 | { "is_coalesced", v.isCoalesced() }, 950 | { "is_predicted", v.isPredicted() }, 951 | { "is_primary", v.isPrimary() }, 952 | { "button", v.button() }, 953 | { "buttons", v.buttons() }, 954 | { "modifiers", v.modifiers() }, 955 | { "coalesced_pointer_events", v.coalescedPointerEvents() }, 956 | { "predicted_pointer_events", v.predictedPointerEvents() }, 957 | { "estimated_properties", v.estimatedProperties() }, 958 | { "estimated_properties_expecting_updates", v.estimatedPropertiesExpectingUpdates() }, 959 | }; 960 | } 961 | 962 | 963 | inline void from_json(const nlohmann::json& j, PointerEventArgs& v) 964 | { 965 | v = PointerEventArgs(nullptr, 966 | j.value("event_type", EventArgs::EVENT_TYPE_UNKNOWN), 967 | j.value("timestamp_micros", uint64_t(0)), 968 | j.value("detail", uint64_t(0)), 969 | j.value("point", Point()), 970 | j.value("pointer_id", std::size_t(0)), 971 | j.value("device_id", uint64_t(0)), 972 | j.value("pointer_index", uint64_t(0)), 973 | j.value("sequence_index", uint64_t(0)), 974 | j.value("device_type", PointerEventArgs::TYPE_UNKNOWN), 975 | j.value("is_coalesced", false), 976 | j.value("is_predicted", false), 977 | j.value("is_primary", false), 978 | j.value("button", int16_t(0)), 979 | j.value("buttons", uint16_t(0)), 980 | j.value("modifiers", uint16_t(0)), 981 | j.value("coalesced_pointer_events", std::vector()), 982 | j.value("predicted_pointer_events", std::vector()), 983 | j.value("estimated_properties", std::set()), 984 | j.value("estimated_properties_expecting_updatess", std::set())); 985 | } 986 | 987 | 988 | /// \brief A class for converting touch and mouse events into pointer events. 989 | /// 990 | /// This class is a source of pointer events. It captures mouse and touch 991 | /// events from openFrameworks or an external source and repackages and 992 | /// distributes them as pointer events. 993 | /// 994 | /// This should not be accessed directly. 995 | class PointerEvents 996 | { 997 | public: 998 | // enum EventSource 999 | // { 1000 | // LEGACY_EVENTS, 1001 | // EXTERNAL_EVENTS 1002 | // }; 1003 | 1004 | /// \brief Create a PointerEvents object with the given source. 1005 | /// \param source The window that will provide the events. 1006 | PointerEvents(ofAppBaseWindow* window); 1007 | 1008 | /// \brief Destroy the PointerEvents. 1009 | ~PointerEvents(); 1010 | 1011 | /// \brief Pointer event callback. 1012 | /// \param source The event source. 1013 | /// \param e the event arguments. 1014 | /// \returns true of the event was consumed. 1015 | bool onPointerEvent(const void* source, PointerEventArgs& e); 1016 | 1017 | /// \brief Mouse event callback. 1018 | /// \param source The event source. 1019 | /// \param e the event arguments. 1020 | /// \returns true of the event was consumed. 1021 | bool onMouseEvent(const void* source, ofMouseEventArgs& e); 1022 | 1023 | /// \brief Touch event callback. 1024 | /// \param source The event source. 1025 | /// \param e the event arguments. 1026 | /// \returns true of the event was handled. 1027 | bool onTouchEvent(const void* source, ofTouchEventArgs& e); 1028 | 1029 | // /// \brief Disable legacy mouse / touch events. 1030 | // /// 1031 | // /// If legacy mouse / touch events are disabled, they will be automatically 1032 | // /// consumed if not already consumed by another callback. 1033 | // /// 1034 | // /// For legacy addons and other legacy user interface code, this should not 1035 | // /// users should think carefully about disabling legacy events. 1036 | // /// 1037 | // /// Alternatively users that wish to use PointerEvents should simply not 1038 | // /// register listeners for mouse or touch events. 1039 | // void disableLegacyEvents(); 1040 | // 1041 | // /// \brief Enable legacy mouse / touch events. 1042 | // /// 1043 | // /// If legacy mouse / touch events are enabled, event propagation will not 1044 | // /// artificially halted. If a consumer handles the event manually it will 1045 | // /// not be propagated. 1046 | // void enableLegacyEvents(); 1047 | 1048 | // void setEnableLegacyEvents(bool enable); 1049 | // bool getEnableLegacyEvents() const; 1050 | 1051 | /// \brief Register a pointer event listener. 1052 | /// 1053 | /// Event listeners registered via this function must have the following 1054 | /// ofEvent callbacks defined: 1055 | /// 1056 | /// `void onPointerDown(PointerEventArgs& evt)` 1057 | /// `void onPointerUp(PointerEventArgs& evt)` 1058 | /// `void onPointerMove(PointerEventArgs& evt)` 1059 | /// `void onPointerCancel(PointerEventArgs& evt)` 1060 | /// 1061 | /// Other method signatures event signatures are also supported. 1062 | /// 1063 | /// \tparam ListenerClass The class of the listener. 1064 | /// \param listener A pointer to the listener class. 1065 | /// \param prio The event priority. 1066 | template 1067 | void registerPointerEvents(ListenerClass* listener, 1068 | int prio = OF_EVENT_ORDER_AFTER_APP); 1069 | 1070 | /// \brief Unregister a pointer event listener. 1071 | /// 1072 | /// Event listeners unregistered via this function must have the following 1073 | /// ofEvent callbacks defined: 1074 | /// 1075 | /// `void onPointerDown(PointerEventArgs& evt)` 1076 | /// `void onPointerUp(PointerEventArgs& evt)` 1077 | /// `void onPointerMove(PointerEventArgs& evt)` 1078 | /// `void onPointerCancel(PointerEventArgs& evt)` 1079 | /// 1080 | /// Other method signatures event signatures are also supported. 1081 | /// 1082 | /// \tparam ListenerClass The class of the listener. 1083 | /// \param listener A pointer to the listener class. 1084 | /// \param prio The event priority. 1085 | template 1086 | void unregisterPointerEvents(ListenerClass* listener, 1087 | int prio = OF_EVENT_ORDER_AFTER_APP); 1088 | 1089 | /// \brief Event that is triggered for any pointer event. 1090 | /// 1091 | /// All specific events below are triggered for matching events types if the 1092 | /// event was not handled by an pointerEvent listener. 1093 | ofEvent pointerEvent; 1094 | 1095 | /// \brief Event that is triggered when a point is introduced. 1096 | /// 1097 | /// Triggered after pointerEvent, if pointerEvent is not consumed. 1098 | ofEvent pointerDown; 1099 | 1100 | /// \brief Event that is triggered when a point is removed. 1101 | /// 1102 | /// Triggered after pointerEvent, if pointerEvent is not consumed. 1103 | ofEvent pointerUp; 1104 | 1105 | /// \brief Event that is triggered when a point moves. 1106 | /// 1107 | /// Triggered after pointerEvent, if pointerEvent is not consumed. 1108 | ofEvent pointerMove; 1109 | 1110 | /// \brief Event when the system cancels a pointer event. 1111 | /// 1112 | /// This event occurs when the pointer (touch or pen contact) is removed 1113 | /// from the system. Here are common reasons why this might happen: 1114 | /// - A touch contact is CANCELLED by a pen coming into range of the 1115 | /// surface. 1116 | /// - The device doesn't report an active contact for more than 100ms. 1117 | /// - A mapping for a device's monitor changes while contacts are 1118 | /// active. For example, the user changes the position of a screen in a 1119 | /// two screen configuration. 1120 | /// - The desktop is locked or the user logged off. 1121 | /// - The number of simultaneous contacts exceeds the number that the 1122 | /// device can support. For example, if a device supports only two 1123 | /// contact points, if the user has two fingers on a surface, and then 1124 | /// touches it with a third finger, this event is raised. 1125 | ofEvent pointerCancel; 1126 | 1127 | /// \brief Event that is triggered when a point has been updated. 1128 | /// 1129 | /// This event can be called in systems that offer updates to estimated 1130 | /// pointer property data. For instance, iOS offers updates when using the 1131 | /// iPencil. To update the point, the user can compare the 1132 | /// 1133 | /// PointerEventArgs::estimatedProperties() 1134 | /// 1135 | /// Triggered after pointerEvent, if pointerEvent is not consumed. 1136 | ofEvent pointerUpdate; 1137 | 1138 | protected: 1139 | /// \brief Dispatch the pointer events. 1140 | /// \param source The event source. 1141 | /// \param e the event arguments. 1142 | /// \returns true of the event was handled. 1143 | bool _dispatchPointerEvent(const void* source, PointerEventArgs& e); 1144 | 1145 | /// \brief True if the PointerEvents should consume mouse / touch events. 1146 | bool _consumeLegacyEvents = false; 1147 | 1148 | /// \brief If true, the legacy events will be intercepted and converted to pointer events. 1149 | bool _interceptLegacyEvents = true; 1150 | 1151 | #if !defined(TARGET_OF_IOS) && !defined(TARGET_ANDROID) 1152 | /// \brief Mouse moved event listener. 1153 | ofEventListener _mouseMovedListener; 1154 | 1155 | /// \brief Mouse dragged event listener. 1156 | ofEventListener _mouseDraggedListener; 1157 | 1158 | /// \brief Mouse pressed event listener. 1159 | ofEventListener _mousePressedListener; 1160 | 1161 | /// \brief Mouse released event listener. 1162 | ofEventListener _mouseReleasedListener; 1163 | 1164 | /// \brief Mouse scrolled event listener. 1165 | ofEventListener _mouseScrolledListener; 1166 | 1167 | /// \brief Mouse entered the source window event listener. 1168 | ofEventListener _mouseEnteredListener; 1169 | 1170 | /// \brief Mouse exited the source window event listener. 1171 | ofEventListener _mouseExitedListener; 1172 | #endif 1173 | 1174 | /// \brief Touch downevent listener. 1175 | ofEventListener _touchDownListener; 1176 | 1177 | /// \brief Touch up event listener. 1178 | ofEventListener _touchUpListener; 1179 | 1180 | /// \brief Touch moved event listener. 1181 | ofEventListener _touchMovedListener; 1182 | 1183 | /// \brief Touch double tap event listener. 1184 | ofEventListener _touchDoubleTapListener; 1185 | 1186 | /// \brief Touch cancelled event listener. 1187 | ofEventListener _touchCancelledListener; 1188 | 1189 | /// \brief The default source if the callback is missing. 1190 | ofAppBaseWindow* _source = nullptr; 1191 | 1192 | }; 1193 | 1194 | 1195 | template 1196 | void PointerEvents::registerPointerEvents(ListenerClass* listener, int prio) 1197 | { 1198 | ofAddListener(pointerDown, listener, &ListenerClass::onPointerDown, prio); 1199 | ofAddListener(pointerUp, listener, &ListenerClass::onPointerUp, prio); 1200 | ofAddListener(pointerMove, listener, &ListenerClass::onPointerMove, prio); 1201 | ofAddListener(pointerCancel, listener, &ListenerClass::onPointerCancel, prio); 1202 | } 1203 | 1204 | 1205 | template 1206 | void PointerEvents::unregisterPointerEvents(ListenerClass* listener, int prio) 1207 | { 1208 | ofRemoveListener(pointerDown, listener, &ListenerClass::onPointerDown, prio); 1209 | ofRemoveListener(pointerUp, listener, &ListenerClass::onPointerUp, prio); 1210 | ofRemoveListener(pointerMove, listener, &ListenerClass::onPointerMove, prio); 1211 | ofRemoveListener(pointerCancel, listener, &ListenerClass::onPointerCancel, prio); 1212 | } 1213 | 1214 | 1215 | /// \brief Manages PointerEvents objects based on their ofAppBaseWindow source. 1216 | class PointerEventsManager 1217 | { 1218 | public: 1219 | /// \returns a PointerEvents instance registered to listen to the global events or nullptr. 1220 | PointerEvents* events(); 1221 | 1222 | /// \returns a PointerEvents instance registered to listen to the given window or nullptr. 1223 | PointerEvents* eventsForWindow(ofAppBaseWindow* window); 1224 | 1225 | /// \brief Get the singleton instance of the PointerEventsManager. 1226 | /// \returns an instance of PointerEventsManager. 1227 | static PointerEventsManager& instance(); 1228 | 1229 | private: 1230 | /// \brief Create a default PointerEventsManager object. 1231 | PointerEventsManager(); 1232 | 1233 | /// \brief Destroy the PointerEventsManager. 1234 | ~PointerEventsManager(); 1235 | 1236 | /// \brief A map of windows to their pointer events. 1237 | std::map> _windowEventMap; 1238 | 1239 | }; 1240 | 1241 | 1242 | template 1243 | void RegisterPointerEventsForWindow(ofAppBaseWindow* window, ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1244 | { 1245 | PointerEvents* events = PointerEventsManager::instance().eventsForWindow(window); 1246 | 1247 | if (events) 1248 | { 1249 | events->registerPointerEvents(listener, prio); 1250 | } 1251 | else 1252 | { 1253 | ofLogError("RegisterPointerEventsForWindow") << "No PointerEvents available for given window."; 1254 | } 1255 | } 1256 | 1257 | 1258 | template 1259 | void UnregisterPointerEventsForWindow(ofAppBaseWindow* window, ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1260 | { 1261 | PointerEvents* events = PointerEventsManager::instance().eventsForWindow(window); 1262 | 1263 | if (events) 1264 | { 1265 | events->unregisterPointerEvents(listener, prio); 1266 | } 1267 | else 1268 | { 1269 | ofLogError("UnregisterPointerEventsForWindow") << "No PointerEvents available for given window."; 1270 | } 1271 | } 1272 | 1273 | 1274 | template 1275 | void RegisterPointerEvents(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1276 | { 1277 | RegisterPointerEventsForWindow(ofGetWindowPtr(), listener, prio); 1278 | } 1279 | 1280 | 1281 | template 1282 | void UnregisterPointerEvents(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1283 | { 1284 | UnregisterPointerEventsForWindow(ofGetWindowPtr(), listener, prio); 1285 | } 1286 | 1287 | 1288 | template 1289 | void RegisterPointerEventForWindow(ofAppBaseWindow* window, ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1290 | { 1291 | PointerEvents* events = PointerEventsManager::instance().eventsForWindow(window); 1292 | 1293 | if (events) 1294 | { 1295 | ofAddListener(events->pointerEvent, listener, &ListenerClass::onPointerEvent, prio); 1296 | } 1297 | else 1298 | { 1299 | ofLogError("RegisterPointerEventForWindow") << "No PointerEvents available for given window."; 1300 | } 1301 | } 1302 | 1303 | 1304 | template 1305 | void UnregisterPointerEventForWindow(ofAppBaseWindow* window, ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1306 | { 1307 | PointerEvents* events = PointerEventsManager::instance().eventsForWindow(window); 1308 | 1309 | if (events) 1310 | { 1311 | ofRemoveListener(events->pointerEvent, listener, &ListenerClass::onPointerEvent, prio); 1312 | } 1313 | else 1314 | { 1315 | ofLogError("UnregisterPointerEventForWindow") << "No PointerEvents available for given window."; 1316 | } 1317 | } 1318 | 1319 | 1320 | 1321 | template 1322 | void RegisterPointerEvent(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1323 | { 1324 | RegisterPointerEventForWindow(ofGetWindowPtr(), listener, prio); 1325 | } 1326 | 1327 | 1328 | template 1329 | void UnregisterPointerEvent(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 1330 | { 1331 | UnregisterPointerEventForWindow(ofGetWindowPtr(), listener, prio); 1332 | } 1333 | 1334 | 1335 | /// \brief A PointerStroke is a collection of events with the same pointer id. 1336 | /// 1337 | /// A pointer stroke begins with a pointerdown event and ends with a pointerup 1338 | /// or pointercancel event. 1339 | class PointerStroke 1340 | { 1341 | public: 1342 | /// \brief Construct a default PointerStroke. 1343 | PointerStroke(); 1344 | 1345 | /// \brief Destroy the PointerStroke. 1346 | ~PointerStroke(); 1347 | 1348 | /// \brief Add the given pointer event to the stroke. 1349 | /// \returns true if the event was successfully added. 1350 | bool add(const PointerEventArgs& e); 1351 | 1352 | /// \returns the PointerId of this PointerStroke. 1353 | std::size_t pointerId() const; 1354 | 1355 | /// \returns the minimum sequence index. 1356 | uint64_t minSequenceIndex() const; 1357 | 1358 | /// \returns the maximum sequence index. 1359 | uint64_t maxSequenceIndex() const; 1360 | 1361 | /// \returns the minimum timestamp in microseconds. 1362 | uint64_t minTimestampMicros() const; 1363 | 1364 | /// \returns the maximum timestamp in microseconds. 1365 | uint64_t maxTimestampMicros() const; 1366 | 1367 | /// \returns true if the last event is a pointerup or pointercancel event. 1368 | bool isFinished() const; 1369 | 1370 | /// \returns true if the last event is a pointercancel. 1371 | bool isCancelled() const; 1372 | 1373 | /// \returns true if any events are still expecting updates. 1374 | bool isExpectingUpdates() const; 1375 | 1376 | /// \returns the number of events. 1377 | std::size_t size() const; 1378 | 1379 | /// \returns true if size() == 0. 1380 | bool empty() const; 1381 | 1382 | /// \returns the events. 1383 | const std::vector& events() const; 1384 | 1385 | private: 1386 | /// \brief The pointer id of all events in this stroke. 1387 | std::size_t _pointerId = -1; 1388 | 1389 | /// \brief The minimum update sequence index. 1390 | uint64_t _minSequenceIndex = std::numeric_limits::max(); 1391 | 1392 | /// \brief The maximum update sequence index. 1393 | uint64_t _maxSequenceIndex = std::numeric_limits::lowest(); 1394 | 1395 | /// \brief All events associated with this stroke. 1396 | std::vector _events; 1397 | 1398 | }; 1399 | 1400 | 1401 | /// \brief A utility class for visualizing Pointer events. 1402 | class PointerDebugRenderer 1403 | { 1404 | public: 1405 | struct Settings; 1406 | 1407 | /// \brief Create a default debug renderer. 1408 | PointerDebugRenderer(); 1409 | 1410 | /// \brief Create a default debug renderer with the given settings. 1411 | /// \p settings The settings values to set. 1412 | PointerDebugRenderer(const Settings& settings); 1413 | 1414 | /// \brief Destroy the renderer. 1415 | ~PointerDebugRenderer(); 1416 | 1417 | /// \brief Configure the PointerDebugRenderer with the given settings. 1418 | /// \p settings The settings values to set. 1419 | void setup(const Settings& settings); 1420 | 1421 | /// \brief Update the strokes. 1422 | /// 1423 | /// This will remove strokes that have timed out. 1424 | void update(); 1425 | 1426 | /// \brief Draw the strokes. 1427 | void draw() const; 1428 | 1429 | void draw(const PointerStroke& stroke) const; 1430 | 1431 | /// \brief Reset all data. 1432 | void clear(); 1433 | 1434 | /// \returns the Settings. 1435 | Settings settings() const; 1436 | 1437 | /// \brief A callback for all Pointer Events. 1438 | /// \param e The Pointer Event arguments. 1439 | void add(const PointerEventArgs& e); 1440 | 1441 | // \returns the stroke map. 1442 | const std::map>& strokes() const; 1443 | 1444 | struct Settings 1445 | { 1446 | Settings(); 1447 | 1448 | /// \brief Set the amount of time before the stroke is removed. 1449 | uint64_t timeoutMillis = 5000; 1450 | 1451 | /// \brief The maximum width of the stroke in pixels. 1452 | float strokeWidth = 100; 1453 | 1454 | /// \brief The color of normal points. 1455 | ofColor pointColor; 1456 | 1457 | /// \brief The color of coalesced points. 1458 | ofColor coalescedPointColor; 1459 | 1460 | /// \brief The color of predicted points. 1461 | ofColor predictedPointColor; 1462 | 1463 | }; 1464 | 1465 | private: 1466 | /// \brief The Settings. 1467 | Settings _settings; 1468 | 1469 | /// \brief A map of strokes. 1470 | std::map> _strokes; 1471 | 1472 | }; 1473 | 1474 | 1475 | /// \brief A class for organiaing and querying collections of pointers. 1476 | class PointerEventCollection 1477 | { 1478 | public: 1479 | /// \brief Create an empty PointerEventCollection. 1480 | PointerEventCollection(); 1481 | 1482 | /// \brief Destroy the PointerEventCollection. 1483 | virtual ~PointerEventCollection(); 1484 | 1485 | /// \returns the number of events in the collection. 1486 | std::size_t size() const; 1487 | 1488 | /// \returns true if the size == 0. 1489 | bool empty() const; 1490 | 1491 | /// \brief Clear all events in the collection. 1492 | void clear(); 1493 | 1494 | /// \returns the number of pointers currently tracked. 1495 | std::size_t numPointers() const; 1496 | 1497 | /// \brief Determine if the PointerEventCollection has the given pointer event key. 1498 | /// \param pointerEventKey The pointer event key to query. 1499 | /// \returns true if the set already has the pointer event key. 1500 | bool hasPointerId(std::size_t pointerId); 1501 | 1502 | /// \brief Add a pointer event to the set. 1503 | /// \param pointerEvent The pointer event to add. 1504 | void add(const PointerEventArgs& pointerEvent); 1505 | 1506 | /// \brief Remove all events for the given pointerId. 1507 | /// \param pointerId The pointer events to remove. 1508 | void removeEventsForPointerId(std::size_t pointerId); 1509 | 1510 | /// \returns all pointer events in the collection. 1511 | std::vector events() const; 1512 | 1513 | /// \brief Get the pointer events for a given key. 1514 | /// \param pointerId The pointer id to query. 1515 | /// \returns the pointer events for the given key or an empty set if none. 1516 | std::vector eventsForPointerId(std::size_t pointerId) const; 1517 | 1518 | /// \brief Get a pointer to the first event for a given pointer id. 1519 | /// \param pointerId The pointer id to query. 1520 | /// \returns a const pointer to the first event or nullptr if none. 1521 | const PointerEventArgs* firstEventForPointerId(std::size_t pointerId) const; 1522 | 1523 | /// \brief Get a pointer to the last event for a given pointer id. 1524 | /// \param pointerId The pointer id to query. 1525 | /// \returns a const pointer to the last event or nullptr if none. 1526 | const PointerEventArgs* lastEventForPointerId(std::size_t pointerId) const; 1527 | 1528 | private: 1529 | /// \brief The set of pointer events in order of addition. 1530 | std::vector _events; 1531 | 1532 | /// \brief A mapping between the pointer event keys and the pointer events. 1533 | std::map> _eventsForPointerId; 1534 | 1535 | }; 1536 | 1537 | 1538 | } // namespace ofx 1539 | 1540 | 1541 | /// \brief A hash function for combining std::hash<> values. 1542 | /// \param seed The hash to append to. 1543 | /// \param v The value to append. 1544 | /// \tparam T The class to hash. Must be compatible with std::hash<>. 1545 | template 1546 | inline void hash_combine(std::size_t& seed, const T& v) 1547 | { 1548 | std::hash hasher; 1549 | seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); 1550 | } 1551 | -------------------------------------------------------------------------------- /libs/ofxPointer/include/ofx/PointerEventsiOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "ofConstants.h" 12 | 13 | 14 | #if defined(TARGET_OF_IOS) 15 | 16 | 17 | #include "ofxiOS.h" 18 | #include "ofEvents.h" 19 | #include "ofAppBaseWindow.h" 20 | #include "ofAppRunner.h" 21 | #include "ofx/PointerEvents.h" 22 | 23 | 24 | /// \brief PointerView is a UIView subclass that intercepts and transforms touches as PointerEvents. 25 | /// \todo Update the UIView size in response to size changes. 26 | @interface PointerView: UIView { 27 | 28 | @protected 29 | /// \brief A pointer to the current iOS window. 30 | /// 31 | /// This may not be necessary as there can only be one window currently 32 | /// accessible by calling ofxiOSGetOFWindow(). 33 | ofAppiOSWindow* _window; 34 | 35 | ofxiOSGLKView* _viewGLK; 36 | ofxiOSEAGLView* _viewEAGL; 37 | 38 | /// \brief The start time of the program to calculate set timestamps based on elapsed time. 39 | NSTimeInterval _startTimeSeconds; 40 | 41 | /// \brief Keep track of active pointers based on UITouchType. 42 | std::map> _activePointerIndices; 43 | 44 | /// \brief Keep track of the primary pointer for each UITouchType. 45 | std::map _primaryPointerIndices; 46 | 47 | } 48 | 49 | - (void)resetTouches; 50 | 51 | /// \brief Convert UITouch data to PointerEventArgs values. 52 | - (ofx::PointerEventArgs)toPointerEventArgs:(UIView*) view 53 | withTouch:(UITouch*) touch 54 | withEvent:(UIEvent*) event 55 | withPointerIndex:(int64_t) pointerIndex 56 | withCoalesced:(bool)_isCoalesced 57 | withPredicted:(bool)_isPredicted 58 | withUpdate:(bool)_isUpdate; 59 | 60 | /// \brief Orient the touch point depending on the current orientation. 61 | - (CGPoint)orientateTouchPoint:(CGPoint)touchPoint; 62 | 63 | @end 64 | 65 | 66 | namespace ofx { 67 | 68 | 69 | /// \brief This must be called to add the PointerView to the primary iOS window. 70 | void EnableAdvancedPointerEventsiOS(); 71 | 72 | /// \brief This must be called to remove the PointerView from the primary iOS window. 73 | void DisableAdvancedPointerEventsiOS(); 74 | 75 | 76 | template 77 | void RegisterAdvancedPointerEventsiOS(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 78 | { 79 | EnableAdvancedPointerEventsiOS(); 80 | RegisterPointerEventsForWindow(ofGetWindowPtr(), listener, prio); 81 | ofAddListener(ofx::PointerEventsManager::instance().eventsForWindow(ofGetWindowPtr())->pointerUpdate, 82 | listener, 83 | &ListenerClass::onPointerUpdate, prio); 84 | } 85 | 86 | 87 | template 88 | void UnregisterAdvancedPointerEventsiOS(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 89 | { 90 | UnregisterPointerEventsForWindow(ofGetWindowPtr(), listener, prio); 91 | ofRemoveListener(ofx::PointerEventsManager::instance().eventsForWindow(ofGetWindowPtr())->pointerUpdate, 92 | listener, 93 | &ListenerClass::onPointerUpdate, prio); 94 | DisableAdvancedPointerEventsiOS(); 95 | } 96 | 97 | 98 | template 99 | void RegisterAdvancedPointerEventiOS(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 100 | { 101 | EnableAdvancedPointerEventsiOS(); 102 | RegisterPointerEventForWindow(ofGetWindowPtr(), listener, prio); 103 | ofAddListener(ofx::PointerEventsManager::instance().eventsForWindow(ofGetWindowPtr())->pointerUpdate, 104 | listener, 105 | &ListenerClass::onPointerEvent, prio); 106 | } 107 | 108 | 109 | template 110 | void UnregisterAdvancedPointerEventiOS(ListenerClass* listener, int prio = OF_EVENT_ORDER_AFTER_APP) 111 | { 112 | UnregisterPointerEventForWindow(ofGetWindowPtr(), listener, prio); 113 | ofRemoveListener(ofx::PointerEventsManager::instance().eventsForWindow(ofGetWindowPtr())->pointerUpdate, 114 | listener, 115 | &ListenerClass::onPointerEvent, prio); 116 | DisableAdvancedPointerEventsiOS(); 117 | } 118 | 119 | 120 | } // namespace ofx 121 | 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /libs/ofxPointer/src/PointerEvents.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2009 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofx/PointerEvents.h" 9 | #include 10 | #include "ofGraphics.h" 11 | #include "ofMesh.h" 12 | 13 | 14 | namespace ofx { 15 | 16 | 17 | const std::string EventArgs::EVENT_TYPE_UNKNOWN = "EVENT_TYPE_UNKNOWN"; 18 | 19 | 20 | EventArgs::EventArgs(): EventArgs(nullptr, 21 | EVENT_TYPE_UNKNOWN, 22 | ofGetElapsedTimeMicros(), 23 | 0) 24 | { 25 | } 26 | 27 | 28 | EventArgs::EventArgs(const void* eventSource, 29 | const std::string& eventType, 30 | uint64_t timestampMicros, 31 | uint64_t detail): 32 | _eventSource(eventSource), 33 | _eventType(eventType), 34 | _timestampMicros(timestampMicros), 35 | _detail(detail) 36 | { 37 | } 38 | 39 | 40 | EventArgs::~EventArgs() 41 | { 42 | } 43 | 44 | 45 | const void* EventArgs::eventSource() const 46 | { 47 | return _eventSource; 48 | } 49 | 50 | 51 | std::string EventArgs::eventType() const 52 | { 53 | return _eventType; 54 | } 55 | 56 | 57 | uint64_t EventArgs::timestampMillis() const 58 | { 59 | return timestampMicros() / 1000; 60 | } 61 | 62 | 63 | uint64_t EventArgs::timestampMicros() const 64 | { 65 | return _timestampMicros; 66 | } 67 | 68 | 69 | uint64_t EventArgs::detail() const 70 | { 71 | return _detail; 72 | } 73 | 74 | 75 | PointShape::PointShape() 76 | { 77 | } 78 | 79 | 80 | PointShape::PointShape(ShapeType shapeType, 81 | float size, 82 | float sizeTolerance): 83 | PointShape(shapeType, 84 | size, 85 | size, 86 | sizeTolerance, 87 | sizeTolerance, 88 | 0) 89 | { 90 | } 91 | 92 | PointShape::PointShape(ShapeType shapeType, 93 | float width, 94 | float height, 95 | float widthTolerance, 96 | float heightTolerance, 97 | float angleDeg): 98 | _width(width), 99 | _height(height), 100 | _widthTolerance(widthTolerance), 101 | _heightTolerance(heightTolerance), 102 | _angleDeg(angleDeg) 103 | { 104 | } 105 | 106 | 107 | PointShape::~PointShape() 108 | { 109 | } 110 | 111 | 112 | PointShape::ShapeType PointShape::shapeType() const 113 | { 114 | return _shapeType; 115 | } 116 | 117 | 118 | float PointShape::width() const 119 | { 120 | return _width; 121 | } 122 | 123 | 124 | float PointShape::height() const 125 | { 126 | return _height; 127 | } 128 | 129 | 130 | float PointShape::widthTolerance() const 131 | { 132 | return _widthTolerance; 133 | } 134 | 135 | 136 | float PointShape::heightTolerance() const 137 | { 138 | return _heightTolerance; 139 | } 140 | 141 | 142 | float PointShape::angleDeg() const 143 | { 144 | return _angleDeg; 145 | } 146 | 147 | 148 | float PointShape::angleRad() const 149 | { 150 | return glm::radians(_angleDeg); 151 | } 152 | 153 | 154 | float PointShape::axisAlignedWidth() const 155 | { 156 | if (!_axisAlignedSizeCached) 157 | _calculateAxisAlignedSize(); 158 | 159 | return _axisAlignedWidth; 160 | } 161 | 162 | 163 | float PointShape::axisAlignedHeight() const 164 | { 165 | if (!_axisAlignedSizeCached) 166 | _calculateAxisAlignedSize(); 167 | 168 | return _axisAlignedHeight; 169 | } 170 | 171 | 172 | void PointShape::_calculateAxisAlignedSize() const 173 | { 174 | float _angleRad = angleRad(); 175 | 176 | switch (_shapeType) 177 | { 178 | case ShapeType::ELLIPSE: 179 | { 180 | // via http://www.iquilezles.org/www/articles/ellipses/ellipses.htm 181 | auto u = glm::rotate(glm::vec2(1, 0) * _width / 2.0f, _angleRad); 182 | auto v = glm::rotate(glm::vec2(0, 1) * _height / 2.0f, _angleRad); 183 | glm::vec2 size = glm::sqrt(u * u + v * v) * 2; 184 | _axisAlignedWidth = size.x; 185 | _axisAlignedHeight = size.y; 186 | break; 187 | } 188 | case ShapeType::RECTANGLE: 189 | { 190 | // via https://stackoverflow.com/a/6657768/1518329 191 | float _cos = std::cos(_angleRad); 192 | float _sin = std::sin(_angleRad); 193 | _axisAlignedWidth = _height * _sin + _width * _cos; 194 | _axisAlignedHeight = _height * _cos + _width * _sin; 195 | break; 196 | } 197 | } 198 | 199 | _axisAlignedSizeCached = true; 200 | } 201 | 202 | 203 | Point::Point(): Point(glm::vec2(0, 0)) 204 | { 205 | } 206 | 207 | 208 | Point::Point(const glm::vec2& position): Point(position, PointShape()) 209 | { 210 | } 211 | 212 | 213 | Point::Point(const glm::vec2& position, const PointShape& shape): 214 | Point(position, PointShape(), 0) 215 | { 216 | } 217 | 218 | 219 | Point::Point(const glm::vec2& position, float pressure, float tiltXDeg, float tiltYDeg): 220 | Point(position, position, PointShape(), pressure, 0, 0, tiltXDeg, tiltYDeg) 221 | { 222 | } 223 | 224 | 225 | Point::Point(const glm::vec2& position, 226 | const PointShape& shape, 227 | float pressure): 228 | Point(position, position, shape, pressure, 0, 0, 0, 0) 229 | { 230 | } 231 | 232 | 233 | Point::Point(const glm::vec2& position, 234 | const glm::vec2& precisePosition, 235 | const PointShape& shape, 236 | float pressure, 237 | float tangentialPressure, 238 | float twistDeg, 239 | float tiltXDeg, 240 | float tiltYDeg): 241 | _position(position), 242 | _precisePosition(precisePosition), 243 | _shape(shape), 244 | _pressure(pressure), 245 | _tangentialPressure(tangentialPressure), 246 | _twistDeg(twistDeg), 247 | _tiltXDeg(tiltXDeg), 248 | _tiltYDeg(tiltYDeg) 249 | { 250 | } 251 | 252 | 253 | Point::~Point() 254 | { 255 | } 256 | 257 | 258 | glm::vec2 Point::position() const 259 | { 260 | return _position; 261 | } 262 | 263 | 264 | glm::vec2 Point::precisePosition() const 265 | { 266 | return _precisePosition; 267 | } 268 | 269 | 270 | float Point::pressure() const 271 | { 272 | return _pressure; 273 | } 274 | 275 | 276 | float Point::tangentialPressure() const 277 | { 278 | return _tangentialPressure; 279 | } 280 | 281 | 282 | float Point::twistDeg() const 283 | { 284 | return _twistDeg; 285 | } 286 | 287 | 288 | float Point::twistRad() const 289 | { 290 | return glm::radians(_twistDeg); 291 | } 292 | 293 | 294 | float Point::tiltXDeg() const 295 | { 296 | return _tiltXDeg; 297 | } 298 | 299 | 300 | float Point::tiltXRad() const 301 | { 302 | return glm::radians(_tiltXDeg); 303 | } 304 | 305 | 306 | float Point::tiltYDeg() const 307 | { 308 | return _tiltYDeg; 309 | } 310 | 311 | 312 | float Point::tiltYRad() const 313 | { 314 | return glm::radians(_tiltYDeg); 315 | } 316 | 317 | 318 | float Point::azimuthDeg() const 319 | { 320 | if (!_azimuthAltitudeCached) 321 | _cacheAzimuthAltitude(); 322 | return _azimuthDeg; 323 | } 324 | 325 | 326 | float Point::azimuthRad() const 327 | { 328 | return glm::radians(azimuthDeg()); 329 | } 330 | 331 | 332 | float Point::altitudeDeg() const 333 | { 334 | if (!_azimuthAltitudeCached) 335 | _cacheAzimuthAltitude(); 336 | return _altitudeDeg; 337 | } 338 | 339 | 340 | float Point::altitudeRad() const 341 | { 342 | return glm::radians(altitudeDeg()); 343 | } 344 | 345 | 346 | void Point::_cacheAzimuthAltitude() const 347 | { 348 | double _azimuthRad = 0; 349 | double _altitudeRad = 0; 350 | 351 | bool tiltXIsZero = ofIsFloatEqual(_tiltXDeg, 0.0f); 352 | bool tiltYIsZero = ofIsFloatEqual(_tiltYDeg, 0.0f); 353 | 354 | // Take care of edge cases where std::tan(...) is undefined. 355 | if (!tiltXIsZero && !tiltYIsZero) 356 | { 357 | double _tanTy = std::tan(tiltYRad()); 358 | _azimuthRad = std::atan2(_tanTy, std::tan(tiltXRad())); 359 | _altitudeRad = std::atan(std::sin(_azimuthRad) / _tanTy); 360 | } 361 | else if (tiltXIsZero && tiltYIsZero) 362 | { 363 | _azimuthRad = 0; 364 | _altitudeRad = 0; 365 | } 366 | else if (tiltXIsZero) 367 | { 368 | _azimuthRad = tiltYRad() > 0 ? glm::half_pi() : glm::three_over_two_pi(); 369 | _altitudeRad = tiltYRad(); 370 | } 371 | else if (tiltYIsZero) 372 | { 373 | _azimuthRad = tiltXRad() > 0 ? 0 : glm::pi(); 374 | _altitudeRad = tiltXRad(); 375 | } 376 | 377 | // Put into range 0 - 2PI. 378 | if (_azimuthRad < 0) 379 | _azimuthRad += glm::two_pi(); 380 | 381 | _azimuthDeg = glm::degrees(_azimuthRad); 382 | _altitudeDeg = glm::degrees(_altitudeRad); 383 | 384 | _azimuthAltitudeCached = true; 385 | } 386 | 387 | const PointShape& Point::shape() const 388 | { 389 | return _shape; 390 | } 391 | 392 | 393 | const std::string PointerEventArgs::TYPE_MOUSE = "mouse"; 394 | const std::string PointerEventArgs::TYPE_PEN = "pen"; 395 | const std::string PointerEventArgs::TYPE_TOUCH = "touch"; 396 | const std::string PointerEventArgs::TYPE_UNKNOWN = "unknown"; 397 | 398 | const std::string PointerEventArgs::POINTER_OVER = "pointerover"; 399 | const std::string PointerEventArgs::POINTER_ENTER = "pointerenter"; 400 | const std::string PointerEventArgs::POINTER_DOWN = "pointerdown"; 401 | const std::string PointerEventArgs::POINTER_MOVE = "pointermove"; 402 | const std::string PointerEventArgs::POINTER_UP = "pointerup"; 403 | const std::string PointerEventArgs::POINTER_CANCEL = "pointercancel"; 404 | const std::string PointerEventArgs::POINTER_UPDATE = "pointerupdate"; 405 | const std::string PointerEventArgs::POINTER_OUT = "pointerout"; 406 | const std::string PointerEventArgs::POINTER_LEAVE = "pointerleave"; 407 | const std::string PointerEventArgs::POINTER_SCROLL = "pointerscroll"; 408 | 409 | const std::string PointerEventArgs::GOT_POINTER_CAPTURE = "gotpointercapture"; 410 | const std::string PointerEventArgs::LOST_POINTER_CAPTURE = "lostpointercapture"; 411 | 412 | const std::string PointerEventArgs::PROPERTY_POSITION = "PROPERTY_POSITION"; 413 | const std::string PointerEventArgs::PROPERTY_PRESSURE = "PROPERTY_PRESSURE"; 414 | const std::string PointerEventArgs::PROPERTY_TILT_X = "PROPERTY_TILT_X"; 415 | const std::string PointerEventArgs::PROPERTY_TILT_Y = "PROPERTY_TILT_Y"; 416 | 417 | 418 | PointerEventArgs::PointerEventArgs() 419 | { 420 | } 421 | 422 | 423 | PointerEventArgs::PointerEventArgs(const std::string& eventType, 424 | const PointerEventArgs& event): 425 | PointerEventArgs(event.eventSource(), 426 | eventType, 427 | event.timestampMicros(), 428 | event.detail(), 429 | event.point(), 430 | event.pointerId(), 431 | event.deviceId(), 432 | event.pointerIndex(), 433 | event.sequenceIndex(), 434 | event.deviceType(), 435 | event.isCoalesced(), 436 | event.isPredicted(), 437 | event.isPrimary(), 438 | event.button(), 439 | event.buttons(), 440 | event.modifiers(), 441 | event.coalescedPointerEvents(), 442 | event.predictedPointerEvents(), 443 | event.estimatedProperties(), 444 | event.estimatedPropertiesExpectingUpdates()) 445 | { 446 | } 447 | 448 | 449 | PointerEventArgs::PointerEventArgs(const void* eventSource, 450 | const std::string& eventType, 451 | uint64_t timestampMicros, 452 | uint64_t detail, 453 | const Point& point, 454 | std::size_t pointerId, 455 | int64_t deviceId, 456 | int64_t pointerIndex, 457 | uint64_t sequenceIndex, 458 | const std::string& deviceType, 459 | bool isCoalesced, 460 | bool isPredicted, 461 | bool isPrimary, 462 | int16_t button, 463 | uint16_t buttons, 464 | uint16_t modifiers, 465 | const std::vector& coalescedPointerEvents, 466 | const std::vector& predictedPointerEvents, 467 | const std::set& estimatedProperties, 468 | const std::set& estimatedPropertiesExpectingUpdates): 469 | EventArgs(eventSource, eventType, timestampMicros, detail), 470 | _point(point), 471 | _pointerId(pointerId), 472 | _deviceId(deviceId), 473 | _pointerIndex(pointerIndex), 474 | _sequenceIndex(sequenceIndex), 475 | _deviceType(deviceType), 476 | _isCoalesced(isCoalesced), 477 | _isPredicted(isPredicted), 478 | _isPrimary(isPrimary), 479 | _button(button), 480 | _buttons(buttons), 481 | _modifiers(modifiers), 482 | _coalescedPointerEvents(coalescedPointerEvents), 483 | _predictedPointerEvents(predictedPointerEvents), 484 | _estimatedProperties(estimatedProperties), 485 | _estimatedPropertiesExpectingUpdates(estimatedPropertiesExpectingUpdates) 486 | { 487 | } 488 | 489 | 490 | PointerEventArgs::~PointerEventArgs() 491 | { 492 | } 493 | 494 | 495 | Point PointerEventArgs::point() const 496 | { 497 | return _point; 498 | } 499 | 500 | 501 | glm::vec2 PointerEventArgs::position() const 502 | { 503 | return point().position(); 504 | } 505 | 506 | 507 | int64_t PointerEventArgs::deviceId() const 508 | { 509 | return _deviceId; 510 | } 511 | 512 | 513 | int64_t PointerEventArgs::pointerIndex() const 514 | { 515 | return _pointerIndex; 516 | } 517 | 518 | 519 | uint64_t PointerEventArgs::sequenceIndex() const 520 | { 521 | return _sequenceIndex; 522 | } 523 | 524 | 525 | std::size_t PointerEventArgs::pointerId() const 526 | { 527 | return _pointerId; 528 | } 529 | 530 | 531 | //PointerEventArgs::PointerKey PointerEventArgs::pointerKey() const 532 | //{ 533 | // return PointerEventArgs::PointerKey(pointerId(), deviceId(), pointerIndex(), button()); 534 | //} 535 | 536 | 537 | std::string PointerEventArgs::deviceType() const 538 | { 539 | return _deviceType; 540 | } 541 | 542 | 543 | bool PointerEventArgs::isCoalesced() const 544 | { 545 | return _isCoalesced; 546 | } 547 | 548 | 549 | bool PointerEventArgs::isPredicted() const 550 | { 551 | return _isPredicted; 552 | } 553 | 554 | 555 | bool PointerEventArgs::isPrimary() const 556 | { 557 | return _isPrimary; 558 | } 559 | 560 | 561 | bool PointerEventArgs::isEstimated() const 562 | { 563 | return !_estimatedProperties.empty(); 564 | } 565 | 566 | 567 | int16_t PointerEventArgs::button() const 568 | { 569 | return _button; 570 | } 571 | 572 | 573 | uint16_t PointerEventArgs::buttons() const 574 | { 575 | return _buttons; 576 | } 577 | 578 | 579 | uint16_t PointerEventArgs::modifiers() const 580 | { 581 | return _modifiers; 582 | } 583 | 584 | 585 | std::vector PointerEventArgs::coalescedPointerEvents() const 586 | { 587 | return _coalescedPointerEvents; 588 | } 589 | 590 | 591 | std::vector PointerEventArgs::predictedPointerEvents() const 592 | { 593 | return _predictedPointerEvents; 594 | } 595 | 596 | 597 | std::set PointerEventArgs::estimatedProperties() const 598 | { 599 | return _estimatedProperties; 600 | } 601 | 602 | 603 | std::set PointerEventArgs::estimatedPropertiesExpectingUpdates() const 604 | { 605 | return _estimatedPropertiesExpectingUpdates; 606 | } 607 | 608 | 609 | bool PointerEventArgs::updateEstimatedPropertiesWithEvent(const PointerEventArgs& e) 610 | { 611 | if (e.sequenceIndex() == 0 || sequenceIndex() == 0) 612 | { 613 | ofLogVerbose("PointerEventArgs::updateEstimatedPropertiesWithEvent") << "One or more of the sequence indices are zero."; 614 | return false; 615 | } 616 | 617 | if (e.sequenceIndex() != sequenceIndex()) 618 | { 619 | ofLogVerbose("PointerEventArgs::updateEstimatedPropertiesWithEvent") << "Sequence indices do not match."; 620 | return false; 621 | } 622 | 623 | auto newEstimatedProperties = e.estimatedProperties(); 624 | std::vector propertiesToUpdate; 625 | 626 | std::set_difference(_estimatedPropertiesExpectingUpdates.begin(), 627 | _estimatedPropertiesExpectingUpdates.end(), 628 | newEstimatedProperties.begin(), 629 | newEstimatedProperties.end(), 630 | std::inserter(propertiesToUpdate, propertiesToUpdate.begin())); 631 | 632 | for (auto& property: propertiesToUpdate) 633 | { 634 | if (property == PointerEventArgs::PROPERTY_PRESSURE) 635 | { 636 | _point._pressure = e.point().pressure(); 637 | ofNotifyEvent(pointerPropertyUpdate, property, this); 638 | } 639 | else if (property == PointerEventArgs::PROPERTY_TILT_X) 640 | { 641 | _point._tiltXDeg = e.point().tiltXDeg(); 642 | _point._azimuthAltitudeCached = false; 643 | ofNotifyEvent(pointerPropertyUpdate, property, this); 644 | } 645 | else if (property == PointerEventArgs::PROPERTY_TILT_Y) 646 | { 647 | _point._tiltYDeg = e.point().tiltYDeg(); 648 | _point._azimuthAltitudeCached = false; 649 | ofNotifyEvent(pointerPropertyUpdate, property, this); 650 | } 651 | else if (property == PointerEventArgs::PROPERTY_POSITION) 652 | { 653 | _point._position = e.point().position(); 654 | _point._precisePosition = e.point().precisePosition(); 655 | ofNotifyEvent(pointerPropertyUpdate, property, this); 656 | } 657 | else 658 | { 659 | ofLogWarning("PointerEventArgs::updateEstimatedPropertiesWithEvent") << "Unknown property to update: " << property; 660 | } 661 | 662 | _estimatedPropertiesExpectingUpdates.erase(property); 663 | } 664 | 665 | return true; 666 | } 667 | 668 | 669 | PointerEventArgs PointerEventArgs::toPointerEventArgs(const void* eventSource, 670 | const ofTouchEventArgs& e) 671 | { 672 | // If major or minor axis is defined, then use them, otherwise, use width 673 | // and height. If neither are defined, use 1 and 1. 674 | float majorAxis = e.majoraxis > 0 ? e.majoraxis : e.width; 675 | float minorAxis = e.minoraxis > 0 ? e.minoraxis : e.height; 676 | 677 | majorAxis = std::max(majorAxis, 1.f); 678 | minorAxis = std::max(minorAxis, 1.f); 679 | 680 | PointShape shape(PointShape::ShapeType::ELLIPSE, 681 | majorAxis, 682 | minorAxis, 683 | 0, 684 | 0, 685 | e.angle); 686 | 687 | uint16_t modifiers = 0; 688 | 689 | modifiers |= ofGetKeyPressed(OF_KEY_CONTROL) ? OF_KEY_CONTROL : 0; 690 | modifiers |= ofGetKeyPressed(OF_KEY_ALT) ? OF_KEY_ALT : 0; 691 | modifiers |= ofGetKeyPressed(OF_KEY_SHIFT) ? OF_KEY_SHIFT : 0; 692 | modifiers |= ofGetKeyPressed(OF_KEY_SUPER) ? OF_KEY_SUPER : 0; 693 | 694 | uint64_t timestampMicros = ofGetElapsedTimeMicros(); 695 | 696 | std::string eventType = EVENT_TYPE_UNKNOWN; 697 | 698 | uint64_t detail = 0; 699 | 700 | // TODO https://www.w3.org/TR/pointerevents/#the-button-property 701 | // This is not correctly implemented. 702 | int16_t button = -1; 703 | 704 | uint16_t buttons = 0; 705 | 706 | std::size_t deviceId = 0; 707 | 708 | switch (e.type) 709 | { 710 | case ofTouchEventArgs::doubleTap: 711 | // Pointers don't use this event. We use gestures for this. 712 | break; 713 | case ofTouchEventArgs::down: 714 | eventType = POINTER_DOWN; 715 | buttons |= (1 << OF_MOUSE_BUTTON_1); 716 | break; 717 | case ofTouchEventArgs::up: 718 | eventType = POINTER_UP; 719 | break; 720 | case ofTouchEventArgs::move: 721 | buttons |= (1 << OF_MOUSE_BUTTON_1); 722 | eventType = POINTER_MOVE; 723 | break; 724 | case ofTouchEventArgs::cancel: 725 | eventType = POINTER_CANCEL; 726 | break; 727 | } 728 | 729 | // If pressure is not reported and a button is pressed, the pressure is 730 | // 0.5. If no pressure is reported and no button is pressed, then the 731 | // pressure is 0. 732 | float pressure = e.pressure > 0 ? e.pressure : (buttons > 0 ? 0.5 : 0); 733 | 734 | Point point(glm::vec2(e.x, e.y), shape, pressure); 735 | 736 | // Since we can't know for sure, we assume TOUCH because it came from a 737 | // ofTouchEventArgs. 738 | std::string deviceType = PointerEventArgs::TYPE_TOUCH; 739 | 740 | bool isCoalesced = false; 741 | bool isPredicted = false; 742 | bool isPrimary = (e.id == 0); 743 | 744 | std::size_t pointerId = 0; 745 | hash_combine(pointerId, deviceId); 746 | hash_combine(pointerId, e.id); 747 | hash_combine(pointerId, deviceType); 748 | 749 | int64_t sequenceIndex = 0; 750 | 751 | PointerEventArgs event(eventSource, 752 | eventType, 753 | timestampMicros, 754 | detail, 755 | point, 756 | pointerId, 757 | deviceId, 758 | e.id, 759 | sequenceIndex, 760 | deviceType, 761 | isCoalesced, 762 | isPredicted, 763 | isPrimary, 764 | button, 765 | buttons, 766 | modifiers, 767 | {}, 768 | {}, 769 | {}, 770 | {}); 771 | 772 | return PointerEventArgs(eventSource, 773 | eventType, 774 | timestampMicros, 775 | detail, 776 | point, 777 | pointerId, 778 | deviceId, 779 | e.id, 780 | sequenceIndex, 781 | deviceType, 782 | isCoalesced, 783 | isPredicted, 784 | isPrimary, 785 | button, 786 | buttons, 787 | modifiers, 788 | { event }, 789 | {}, 790 | {}, 791 | {}); 792 | } 793 | 794 | 795 | PointerEventArgs PointerEventArgs::toPointerEventArgs(const void* eventSource, 796 | const ofMouseEventArgs& e) 797 | { 798 | // We begin with an unknown event type. 799 | std::string eventType = EVENT_TYPE_UNKNOWN; 800 | uint64_t detail = 0; 801 | 802 | // Convert the ofMouseEventArgs type to a string event type. 803 | switch (e.type) 804 | { 805 | case ofMouseEventArgs::Pressed: 806 | eventType = POINTER_DOWN; 807 | break; 808 | case ofMouseEventArgs::Dragged: 809 | case ofMouseEventArgs::Moved: 810 | eventType = POINTER_MOVE; 811 | break; 812 | case ofMouseEventArgs::Released: 813 | eventType = POINTER_UP; 814 | break; 815 | case ofMouseEventArgs::Scrolled: 816 | eventType = POINTER_SCROLL; 817 | break; 818 | case ofMouseEventArgs::Entered: 819 | // This is with respect to the source window. 820 | eventType = POINTER_ENTER; 821 | break; 822 | case ofMouseEventArgs::Exited: 823 | // This is with respect to the source window. 824 | eventType = POINTER_LEAVE; 825 | break; 826 | } 827 | 828 | // Record a timestamp. 829 | uint64_t timestampMicros = ofGetElapsedTimeMicros(); 830 | 831 | // TODO https://www.w3.org/TR/pointerevents/#the-button-property 832 | // This is not correctly implemented. 833 | // Note the mouse button associated with this event. 834 | int16_t button = -1; 835 | 836 | // Calculate buttons. 837 | uint16_t buttons = 0; 838 | 839 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_1) ? (1 << OF_MOUSE_BUTTON_1) : 0; 840 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_2) ? (1 << OF_MOUSE_BUTTON_2) : 0; 841 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_3) ? (1 << OF_MOUSE_BUTTON_3) : 0; 842 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_4) ? (1 << OF_MOUSE_BUTTON_4) : 0; 843 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_5) ? (1 << OF_MOUSE_BUTTON_5) : 0; 844 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_6) ? (1 << OF_MOUSE_BUTTON_6) : 0; 845 | buttons |= ofGetMousePressed(OF_MOUSE_BUTTON_7) ? (1 << OF_MOUSE_BUTTON_7) : 0; 846 | 847 | // Create the point, if a button is pressed, the pressure is 0.5. 848 | Point point(glm::vec2(e.x, e.y), PointShape(), (buttons > 0 ? 0.5 : 0)); 849 | 850 | bool isCoalesced = false; 851 | bool isPredicted = false; 852 | bool isPrimary = true; // A mouse is primary. 853 | 854 | // Calculate modifiers. 855 | uint16_t modifiers = 0; 856 | 857 | modifiers |= ofGetKeyPressed(OF_KEY_CONTROL) ? OF_KEY_CONTROL : 0; 858 | modifiers |= ofGetKeyPressed(OF_KEY_ALT) ? OF_KEY_ALT : 0; 859 | modifiers |= ofGetKeyPressed(OF_KEY_SHIFT) ? OF_KEY_SHIFT : 0; 860 | modifiers |= ofGetKeyPressed(OF_KEY_SUPER) ? OF_KEY_SUPER : 0; 861 | 862 | std::size_t deviceId = 0; 863 | int64_t pointerIndex = 0; 864 | uint64_t sequenceIndex = 0; 865 | 866 | std::string deviceType = PointerEventArgs::TYPE_MOUSE; 867 | 868 | std::size_t pointerId = 0; 869 | hash_combine(pointerId, deviceId); 870 | hash_combine(pointerId, pointerIndex); 871 | hash_combine(pointerId, deviceType); 872 | 873 | PointerEventArgs event(eventSource, 874 | eventType, 875 | timestampMicros, 876 | detail, 877 | point, 878 | pointerId, 879 | deviceId, 880 | pointerIndex, 881 | sequenceIndex, 882 | deviceType, 883 | isCoalesced, 884 | isPredicted, 885 | isPrimary, 886 | button, 887 | buttons, 888 | modifiers, 889 | {}, 890 | {}, 891 | {}, 892 | {}); 893 | 894 | return PointerEventArgs(eventSource, 895 | eventType, 896 | timestampMicros, 897 | detail, 898 | point, 899 | pointerId, 900 | deviceId, 901 | pointerIndex, 902 | sequenceIndex, 903 | deviceType, 904 | isCoalesced, 905 | isPredicted, 906 | isPrimary, 907 | button, 908 | buttons, 909 | modifiers, 910 | { event }, 911 | {}, 912 | {}, 913 | {}); 914 | } 915 | 916 | 917 | PointerEvents::PointerEvents(ofAppBaseWindow* source): _source(source) 918 | { 919 | ofCoreEvents* eventSource = nullptr; 920 | 921 | if (_source) 922 | { 923 | eventSource = &_source->events(); 924 | } 925 | else 926 | { 927 | eventSource = &ofEvents(); 928 | } 929 | 930 | #if !defined(TARGET_OF_IOS) && !defined(TARGET_ANDROID) 931 | _mouseMovedListener = eventSource->mouseMoved.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 932 | _mouseDraggedListener = eventSource->mouseDragged.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 933 | _mousePressedListener = eventSource->mousePressed.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 934 | _mouseReleasedListener = eventSource->mouseReleased.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 935 | _mouseScrolledListener = eventSource->mouseScrolled.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 936 | _mouseEnteredListener = eventSource->mouseEntered.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 937 | _mouseExitedListener = eventSource->mouseExited.newListener(this, &PointerEvents::onMouseEvent, OF_EVENT_ORDER_BEFORE_APP); 938 | #endif 939 | _touchDownListener = eventSource->touchDown.newListener(this, &PointerEvents::onTouchEvent, OF_EVENT_ORDER_BEFORE_APP); 940 | _touchUpListener = eventSource->touchUp.newListener(this, &PointerEvents::onTouchEvent, OF_EVENT_ORDER_BEFORE_APP); 941 | _touchMovedListener = eventSource->touchMoved.newListener(this, &PointerEvents::onTouchEvent, OF_EVENT_ORDER_BEFORE_APP); 942 | _touchDoubleTapListener = eventSource->touchDoubleTap.newListener(this, &PointerEvents::onTouchEvent, OF_EVENT_ORDER_BEFORE_APP); 943 | _touchCancelledListener = eventSource->touchCancelled.newListener(this, &PointerEvents::onTouchEvent, OF_EVENT_ORDER_BEFORE_APP); 944 | 945 | } 946 | 947 | 948 | PointerEvents::~PointerEvents() 949 | { 950 | } 951 | 952 | 953 | bool PointerEvents::onPointerEvent(const void* source, PointerEventArgs& e) 954 | { 955 | return _dispatchPointerEvent(source, e); 956 | } 957 | 958 | 959 | bool PointerEvents::onMouseEvent(const void* source, ofMouseEventArgs& e) 960 | { 961 | // We use _source here because ofMouseEventArgs events aren't currently 962 | // delivered with a source. 963 | auto p = PointerEventArgs::toPointerEventArgs(_source, e); 964 | return _dispatchPointerEvent(source, p); 965 | } 966 | 967 | 968 | bool PointerEvents::onTouchEvent(const void* source, ofTouchEventArgs& e) 969 | { 970 | // We use _source here because ofTouchEventArgs events aren't currently 971 | // delivered with a source. 972 | auto p = PointerEventArgs::toPointerEventArgs(_source, e); 973 | return _dispatchPointerEvent(source, p); 974 | } 975 | 976 | 977 | //void PointerEvents::disableLegacyEvents() 978 | //{ 979 | // _consumeLegacyEvents = true; 980 | //} 981 | // 982 | // 983 | //void PointerEvents::enableLegacyEvents() 984 | //{ 985 | // _consumeLegacyEvents = false; 986 | //} 987 | 988 | 989 | bool PointerEvents::_dispatchPointerEvent(const void* source, PointerEventArgs& e) 990 | { 991 | // TODO: Update this when openFrameworks core supports source on events. 992 | if (source && source != _source) 993 | { 994 | // "Event source sent, but does not match window. PointerEvents should be updated to respect source." 995 | assert(false); 996 | ofLogError("PointerEvents::_dispatchPointerEvent") << "Mismatched source."; 997 | return false; 998 | } 999 | 1000 | if (e.eventType() == PointerEventArgs::EVENT_TYPE_UNKNOWN) 1001 | { 1002 | // We don't deliver unknown event types. 1003 | // These are usually double-tap events from OF core. 1004 | return true; 1005 | } 1006 | 1007 | // All pointer events get dispatched via pointerEvent. 1008 | bool consumed = ofNotifyEvent(pointerEvent, e, _source); 1009 | 1010 | // If the pointer was not consumed, then send it along to the standard five. 1011 | if (!consumed) 1012 | { 1013 | if (e.eventType() == PointerEventArgs::POINTER_DOWN) 1014 | { 1015 | consumed = ofNotifyEvent(pointerDown, e, _source); 1016 | } 1017 | else if (e.eventType() == PointerEventArgs::POINTER_UP) 1018 | { 1019 | consumed = ofNotifyEvent(pointerUp, e, _source); 1020 | } 1021 | else if (e.eventType() == PointerEventArgs::POINTER_MOVE) 1022 | { 1023 | consumed = ofNotifyEvent(pointerMove, e, _source); 1024 | } 1025 | else if (e.eventType() == PointerEventArgs::POINTER_CANCEL) 1026 | { 1027 | consumed = ofNotifyEvent(pointerCancel, e, _source); 1028 | } 1029 | else if (e.eventType() == PointerEventArgs::POINTER_UPDATE) 1030 | { 1031 | consumed = ofNotifyEvent(pointerUpdate, e, _source); 1032 | } 1033 | } 1034 | 1035 | return _consumeLegacyEvents || consumed; 1036 | } 1037 | 1038 | 1039 | PointerEvents* PointerEventsManager::events() 1040 | { 1041 | return eventsForWindow(nullptr); 1042 | } 1043 | 1044 | 1045 | PointerEvents* PointerEventsManager::eventsForWindow(ofAppBaseWindow* window) 1046 | { 1047 | auto iter = _windowEventMap.find(window); 1048 | 1049 | if (iter != _windowEventMap.end()) 1050 | { 1051 | return iter->second.get(); 1052 | } 1053 | 1054 | _windowEventMap[window] = std::make_unique(window); 1055 | return _windowEventMap[window].get(); 1056 | } 1057 | 1058 | 1059 | PointerEventsManager& PointerEventsManager::instance() 1060 | { 1061 | static PointerEventsManager instance; 1062 | return instance; 1063 | } 1064 | 1065 | 1066 | PointerEventsManager::PointerEventsManager() 1067 | { 1068 | } 1069 | 1070 | 1071 | PointerEventsManager::~PointerEventsManager() 1072 | { 1073 | } 1074 | 1075 | 1076 | 1077 | PointerStroke::PointerStroke() 1078 | { 1079 | } 1080 | 1081 | 1082 | PointerStroke::~PointerStroke() 1083 | { 1084 | } 1085 | 1086 | 1087 | bool PointerStroke::add(const PointerEventArgs& e) 1088 | { 1089 | if (_events.empty()) 1090 | _pointerId = e.pointerId(); 1091 | 1092 | if (_pointerId != e.pointerId()) 1093 | return false; 1094 | 1095 | if (e.eventType() == PointerEventArgs::POINTER_UPDATE) 1096 | { 1097 | auto riter = _events.rbegin(); 1098 | while (riter != _events.rend()) 1099 | { 1100 | if (riter->sequenceIndex() == e.sequenceIndex()) 1101 | { 1102 | if (!riter->updateEstimatedPropertiesWithEvent(e)) 1103 | ofLogError("PointerStroke::add") << "Error updating matching property."; 1104 | return true; 1105 | } 1106 | ++riter; 1107 | } 1108 | 1109 | return false; 1110 | } 1111 | 1112 | // Remove predicted events. 1113 | _events.erase(std::remove_if(_events.begin(), 1114 | _events.end(), 1115 | [](const PointerEventArgs& x) { return x.isPredicted(); }), 1116 | _events.end()); 1117 | 1118 | // Add coalesced events, this includes the current event. 1119 | auto coalesced = e.coalescedPointerEvents(); 1120 | _events.insert(_events.end(), coalesced.begin(), coalesced.end()); 1121 | 1122 | if (coalesced.empty()) 1123 | ofLogError("PointerStroke::add") << "No coalesced events!"; 1124 | 1125 | // Add predicted events. 1126 | auto predicted = e.predictedPointerEvents(); 1127 | _events.insert(_events.end(), predicted.begin(), predicted.end()); 1128 | 1129 | _minSequenceIndex = std::min(e.sequenceIndex(), _minSequenceIndex); 1130 | _maxSequenceIndex = std::max(e.sequenceIndex(), _maxSequenceIndex); 1131 | 1132 | return true; 1133 | } 1134 | 1135 | 1136 | std::size_t PointerStroke::pointerId() const 1137 | { 1138 | return _pointerId; 1139 | } 1140 | 1141 | 1142 | uint64_t PointerStroke::minSequenceIndex() const 1143 | { 1144 | return _minSequenceIndex; 1145 | } 1146 | 1147 | 1148 | uint64_t PointerStroke::maxSequenceIndex() const 1149 | { 1150 | return _maxSequenceIndex; 1151 | } 1152 | 1153 | 1154 | uint64_t PointerStroke::minTimestampMicros() const 1155 | { 1156 | if (_events.empty()) 1157 | return 0; 1158 | 1159 | return _events.front().timestampMicros(); 1160 | } 1161 | 1162 | 1163 | uint64_t PointerStroke::maxTimestampMicros() const 1164 | { 1165 | if (_events.empty()) 1166 | return 0; 1167 | 1168 | return _events.back().timestampMicros(); 1169 | } 1170 | 1171 | 1172 | bool PointerStroke::isFinished() const 1173 | { 1174 | return !_events.empty() && (_events.back().eventType() == PointerEventArgs::POINTER_CANCEL 1175 | || _events.back().eventType() == PointerEventArgs::POINTER_UP); 1176 | } 1177 | 1178 | 1179 | bool PointerStroke::isCancelled() const 1180 | { 1181 | return !_events.empty() && _events.back().eventType() == PointerEventArgs::POINTER_CANCEL; 1182 | } 1183 | 1184 | 1185 | bool PointerStroke::isExpectingUpdates() const 1186 | { 1187 | // Start at the end, because newer events are likely the ones with estimated 1188 | // properties. 1189 | auto riter = _events.rbegin(); 1190 | while (riter != _events.rend()) 1191 | { 1192 | if (!riter->estimatedProperties().empty()) 1193 | return true; 1194 | 1195 | ++riter; 1196 | } 1197 | 1198 | return false; 1199 | } 1200 | 1201 | 1202 | std::size_t PointerStroke::size() const 1203 | { 1204 | return _events.size(); 1205 | } 1206 | 1207 | 1208 | bool PointerStroke::empty() const 1209 | { 1210 | return _events.empty(); 1211 | } 1212 | 1213 | 1214 | const std::vector& PointerStroke::events() const 1215 | { 1216 | return _events; 1217 | } 1218 | 1219 | 1220 | PointerDebugRenderer::Settings::Settings(): 1221 | pointColor(ofColor::blue), 1222 | coalescedPointColor(ofColor::red), 1223 | predictedPointColor(ofColor::darkGray) 1224 | { 1225 | } 1226 | 1227 | 1228 | PointerDebugRenderer::PointerDebugRenderer() 1229 | { 1230 | } 1231 | 1232 | 1233 | PointerDebugRenderer::PointerDebugRenderer(const Settings& settings): 1234 | _settings(settings) 1235 | { 1236 | } 1237 | 1238 | 1239 | PointerDebugRenderer::~PointerDebugRenderer() 1240 | { 1241 | } 1242 | 1243 | 1244 | void PointerDebugRenderer::setup(const Settings& settings) 1245 | { 1246 | _settings = settings; 1247 | } 1248 | 1249 | 1250 | void PointerDebugRenderer::update() 1251 | { 1252 | if (!_strokes.empty()) 1253 | { 1254 | auto now = ofGetElapsedTimeMillis(); 1255 | 1256 | // Avoid rollover by subtracting from an unsigned now. 1257 | if (now < _settings.timeoutMillis) 1258 | return; 1259 | 1260 | auto lastValidTime = now - _settings.timeoutMillis; 1261 | 1262 | auto iter = _strokes.begin(); 1263 | 1264 | while (iter != _strokes.end()) 1265 | { 1266 | if (iter->second.empty()) 1267 | iter = _strokes.erase(iter); 1268 | else 1269 | { 1270 | iter->second.erase(std::remove_if(iter->second.begin(), 1271 | iter->second.end(), 1272 | [&](const PointerStroke& x) 1273 | { return lastValidTime > x.events().back().timestampMillis(); }), 1274 | iter->second.end()); 1275 | ++iter; 1276 | } 1277 | } 1278 | } 1279 | } 1280 | 1281 | 1282 | void PointerDebugRenderer::draw() const 1283 | { 1284 | for (auto& strokes: _strokes) 1285 | for (auto& stroke: strokes.second) 1286 | draw(stroke); 1287 | } 1288 | 1289 | 1290 | void PointerDebugRenderer::draw(const PointerStroke& stroke) const 1291 | { 1292 | auto nowMillis = ofGetElapsedTimeMillis(); 1293 | 1294 | auto lastValidTimeMillis = nowMillis - _settings.timeoutMillis; 1295 | 1296 | if (nowMillis < _settings.timeoutMillis) 1297 | lastValidTimeMillis = 0; 1298 | 1299 | ofMesh mesh; 1300 | mesh.setMode(OF_PRIMITIVE_TRIANGLE_STRIP); 1301 | 1302 | bool useZ = false; 1303 | float R = _settings.strokeWidth; 1304 | auto fadeTimeMillis = std::min(uint64_t(50), _settings.timeoutMillis); 1305 | 1306 | const auto& events = stroke.events(); 1307 | 1308 | for (std::size_t i = 0; i < events.size(); ++i) 1309 | { 1310 | const auto& e = events[i]; 1311 | 1312 | // Pen tip. 1313 | glm::vec3 p0 = { e.position().x, e.position().y, 0 }; 1314 | glm::vec3 p1 = p0; 1315 | 1316 | float az = e.point().azimuthRad(); 1317 | float al = e.point().altitudeRad(); 1318 | 1319 | if (!ofIsFloatEqual(az, 0.0f) || !ofIsFloatEqual(al, 0.0f)) 1320 | { 1321 | float cosAl = std::cos(al); 1322 | p1.x += R * std::cos(az) * cosAl; 1323 | p1.y += R * std::sin(az) * cosAl; 1324 | 1325 | if (useZ) 1326 | p1.z += R * std::sin(al); 1327 | } 1328 | else 1329 | { 1330 | // If no altitude / azimuth are available, use tangents to simulate. 1331 | if (i > 0 && i < events.size() - 1) 1332 | { 1333 | std::size_t i1 = i - 1; 1334 | std::size_t i2 = i; 1335 | std::size_t i3 = i + 1; 1336 | const auto& p_1 = events[i1].position(); 1337 | const auto& p_2 = events[i2].position(); 1338 | const auto& p_3 = events[i3].position(); 1339 | auto v1(p_1 - p_2); // vector to previous point 1340 | auto v2(p_3 - p_2); // vector to next point 1341 | v1 = glm::normalize(v1); 1342 | v2 = glm::normalize(v2); 1343 | glm::vec2 tangent = glm::length2(v2 - v1) > 0 ? glm::normalize(v2 - v1) : -v1; 1344 | glm::vec3 normal = glm::cross(glm::vec3(tangent, 0), { 0, 0, 1 }); 1345 | auto pp0 = p_2 + normal * R / 2; 1346 | auto pp1 = p_2 - normal * R / 2; 1347 | p0 = { pp0.x, pp0.y, 0 }; 1348 | p1 = { pp1.x, pp1.y, 0 }; 1349 | } 1350 | } 1351 | 1352 | // Here we combine the age of the line and the pressure to fade out 1353 | // the line via an opacity change. 1354 | float pressure = e.point().pressure(); 1355 | 1356 | double timeRemainingMillis = double(e.timestampMillis()) - double(lastValidTimeMillis); 1357 | float fader = ofMap(timeRemainingMillis, fadeTimeMillis, 0, 1, 0, true); 1358 | float pressureFader = pressure * fader; 1359 | 1360 | ofColor c0, c1; 1361 | 1362 | // Here we color the points based on the point type. 1363 | if (e.isCoalesced()) 1364 | c0 = c1 = ofColor(_settings.coalescedPointColor, pressureFader * 255); 1365 | else if (e.isPredicted()) 1366 | c0 = c1 = ofColor(_settings.predictedPointColor); 1367 | else 1368 | c0 = c1 = ofColor(_settings.pointColor, pressureFader * 255); 1369 | 1370 | mesh.addVertex(p0); 1371 | mesh.addColor(c0); 1372 | mesh.addVertex(p1); 1373 | mesh.addColor(c1); 1374 | } 1375 | 1376 | mesh.draw(); 1377 | } 1378 | 1379 | 1380 | PointerDebugRenderer::Settings PointerDebugRenderer::settings() const 1381 | { 1382 | return _settings; 1383 | } 1384 | 1385 | 1386 | void PointerDebugRenderer::clear() 1387 | { 1388 | _strokes.clear(); 1389 | } 1390 | 1391 | 1392 | void PointerDebugRenderer::add(const PointerEventArgs& e) 1393 | { 1394 | // Ignore mouse just rolling around. 1395 | if (e.deviceType() == PointerEventArgs::TYPE_MOUSE 1396 | && e.eventType() == PointerEventArgs::POINTER_MOVE 1397 | && e.buttons() == 0) 1398 | return; 1399 | 1400 | auto strokesIter = _strokes.find(e.pointerId()); 1401 | 1402 | if (e.eventType() == PointerEventArgs::POINTER_UPDATE) 1403 | { 1404 | bool foundIt = false; 1405 | if (strokesIter != _strokes.end()) 1406 | { 1407 | for (auto& stroke: strokesIter->second) 1408 | { 1409 | foundIt = stroke.add(e); 1410 | if (foundIt) 1411 | break; 1412 | } 1413 | } 1414 | 1415 | if (!foundIt) 1416 | ofLogError("PointerDebugRenderer::add") << "The sequence to be updated was nowhere to be found. This probably should not happen."; 1417 | 1418 | return; 1419 | } 1420 | 1421 | // Process all events. 1422 | if (strokesIter == _strokes.end()) 1423 | { 1424 | bool result; 1425 | std::tie(strokesIter, result) = _strokes.insert(std::make_pair(e.pointerId(), std::vector())); 1426 | } 1427 | 1428 | if (strokesIter->second.empty() || strokesIter->second.back().isFinished()) 1429 | { 1430 | strokesIter->second.push_back(PointerStroke()); 1431 | } 1432 | 1433 | // Get a reference to the current stroke. 1434 | auto& stroke = strokesIter->second.back(); 1435 | 1436 | if (!stroke.add(e)) 1437 | { 1438 | ofLogError("PointerDebugRenderer::add") << "Could not add event."; 1439 | } 1440 | } 1441 | 1442 | 1443 | const std::map>& PointerDebugRenderer::strokes() const 1444 | { 1445 | return _strokes; 1446 | } 1447 | 1448 | 1449 | PointerEventCollection::PointerEventCollection() 1450 | { 1451 | } 1452 | 1453 | 1454 | PointerEventCollection::~PointerEventCollection() 1455 | { 1456 | } 1457 | 1458 | 1459 | std::size_t PointerEventCollection::size() const 1460 | { 1461 | return _events.size(); 1462 | } 1463 | 1464 | 1465 | bool PointerEventCollection::empty() const 1466 | { 1467 | return _events.empty(); 1468 | } 1469 | 1470 | 1471 | void PointerEventCollection::clear() 1472 | { 1473 | _events.clear(); 1474 | _eventsForPointerId.clear(); 1475 | } 1476 | 1477 | 1478 | std::size_t PointerEventCollection::numPointers() const 1479 | { 1480 | return _eventsForPointerId.size(); 1481 | } 1482 | 1483 | 1484 | bool PointerEventCollection::hasPointerId(std::size_t pointerId) 1485 | { 1486 | return _eventsForPointerId.find(pointerId) != _eventsForPointerId.end(); 1487 | } 1488 | 1489 | 1490 | void PointerEventCollection::add(const PointerEventArgs& pointerEvent) 1491 | { 1492 | _events.push_back(pointerEvent); 1493 | 1494 | auto iter = _eventsForPointerId.find(pointerEvent.pointerId()); 1495 | 1496 | if (iter != _eventsForPointerId.end()) 1497 | iter->second.push_back(&_events.back()); 1498 | else 1499 | _eventsForPointerId[pointerEvent.pointerId()] = { &_events.back() }; 1500 | 1501 | } 1502 | 1503 | 1504 | void PointerEventCollection::removeEventsForPointerId(std::size_t pointerId) 1505 | { 1506 | _eventsForPointerId.erase(pointerId); 1507 | 1508 | auto iter = _events.begin(); 1509 | 1510 | while (iter != _events.end()) 1511 | { 1512 | if (iter->pointerId() == pointerId) 1513 | iter = _events.erase(iter); 1514 | else 1515 | ++iter; 1516 | } 1517 | } 1518 | 1519 | 1520 | std::vector PointerEventCollection::events() const 1521 | { 1522 | return _events; 1523 | } 1524 | 1525 | 1526 | std::vector PointerEventCollection::eventsForPointerId(std::size_t pointerId) const 1527 | { 1528 | std::vector results; 1529 | 1530 | auto iter = _eventsForPointerId.find(pointerId); 1531 | 1532 | if (iter != _eventsForPointerId.end()) 1533 | { 1534 | for (auto* event: iter->second) 1535 | results.push_back(*event); 1536 | } 1537 | 1538 | return results; 1539 | } 1540 | 1541 | 1542 | const PointerEventArgs* PointerEventCollection::firstEventForPointerId(std::size_t pointerId) const 1543 | { 1544 | auto iter = _eventsForPointerId.find(pointerId); 1545 | 1546 | if (iter != _eventsForPointerId.end()) 1547 | { 1548 | return iter->second.front(); 1549 | } 1550 | 1551 | return nullptr; 1552 | } 1553 | 1554 | 1555 | const PointerEventArgs* PointerEventCollection::lastEventForPointerId(std::size_t pointerId) const 1556 | { 1557 | auto iter = _eventsForPointerId.find(pointerId); 1558 | 1559 | if (iter != _eventsForPointerId.end()) 1560 | { 1561 | return iter->second.back(); 1562 | } 1563 | 1564 | return nullptr; 1565 | } 1566 | 1567 | 1568 | } // namespace ofx 1569 | -------------------------------------------------------------------------------- /libs/ofxPointer/src/PointerEventsiOS.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "ofConstants.h" 9 | 10 | 11 | #if defined(TARGET_OF_IOS) 12 | 13 | 14 | #include "ofx/PointerEventsiOS.h" 15 | #include "ofx/PointerEvents.h" 16 | #include "ofMath.h" 17 | 18 | 19 | using namespace ofx; 20 | 21 | 22 | UITouchProperties toUITouchProperties(const std::set& properties) 23 | { 24 | UITouchProperties result = 0; 25 | 26 | for (const auto& property: properties) 27 | { 28 | if (property == PointerEventArgs::PROPERTY_PRESSURE) 29 | result &= UITouchPropertyForce; 30 | else if (property == PointerEventArgs::PROPERTY_TILT_X || property == PointerEventArgs::PROPERTY_TILT_Y) 31 | result &= (UITouchPropertyAzimuth & UITouchPropertyAltitude); 32 | else if (property == PointerEventArgs::PROPERTY_POSITION) 33 | result &= UITouchPropertyLocation; 34 | else 35 | ofLogWarning("toUITouchProperties") << "Unknown UITouchProperty: " << property; 36 | } 37 | 38 | return result; 39 | } 40 | 41 | 42 | std::set toPopertySet(UITouchProperties properties) 43 | { 44 | std::set result; 45 | 46 | if (properties & UITouchPropertyForce) 47 | result.insert(PointerEventArgs::PROPERTY_PRESSURE); 48 | 49 | if (properties & UITouchPropertyAzimuth || properties & UITouchPropertyAltitude) 50 | result.insert({ PointerEventArgs::PROPERTY_TILT_X, PointerEventArgs::PROPERTY_TILT_Y }); 51 | 52 | if (properties & UITouchPropertyLocation) 53 | result.insert(PointerEventArgs::PROPERTY_POSITION); 54 | 55 | return result; 56 | } 57 | 58 | 59 | bool dispatchPointerEvent(ofAppBaseWindow* window, PointerEventArgs& e) 60 | { 61 | bool consumed = false; 62 | 63 | ofx::PointerEvents* events = ofx::PointerEventsManager::instance().eventsForWindow(window); 64 | 65 | if (events) 66 | { 67 | // All pointer events get dispatched via pointerEvent. 68 | consumed = ofNotifyEvent(events->pointerEvent, e, window); 69 | 70 | // If the pointer was not consumed, then send it along to the standard five. 71 | if (!consumed) 72 | { 73 | if (e.eventType() == PointerEventArgs::POINTER_DOWN) 74 | { 75 | consumed = ofNotifyEvent(events->pointerDown, e, window); 76 | } 77 | else if (e.eventType() == PointerEventArgs::POINTER_UP) 78 | { 79 | consumed = ofNotifyEvent(events->pointerUp, e, window); 80 | } 81 | else if (e.eventType() == PointerEventArgs::POINTER_MOVE) 82 | { 83 | consumed = ofNotifyEvent(events->pointerMove, e, window); 84 | } 85 | else if (e.eventType() == PointerEventArgs::POINTER_CANCEL) 86 | { 87 | consumed = ofNotifyEvent(events->pointerCancel, e, window); 88 | } 89 | else if (e.eventType() == PointerEventArgs::POINTER_UPDATE) 90 | { 91 | consumed = ofNotifyEvent(events->pointerUpdate, e, window); 92 | } 93 | } 94 | } 95 | else 96 | { 97 | ofLogError("PointerViewIOS::touchesEnded") << "Invalid event, passing."; 98 | } 99 | 100 | // TODO address consumeLegacy events. 101 | return /*events->consumeLegacyEvents ||*/ consumed; 102 | } 103 | 104 | 105 | 106 | 107 | @implementation PointerView 108 | 109 | - (id)initWithFrame:(CGRect)frame 110 | { 111 | self = [super initWithFrame:frame]; 112 | self.multipleTouchEnabled = true; 113 | 114 | _window = ofxiOSGetOFWindow(); 115 | 116 | if(_window->getWindowControllerType() == METAL_KIT 117 | || _window->getWindowControllerType() == GL_KIT) 118 | { 119 | if ([ofxiOSGLKView getInstance]) 120 | _viewGLK = [ofxiOSGLKView getInstance]; 121 | } 122 | else 123 | { 124 | if ([ofxiOSEAGLView getInstance]) 125 | _viewEAGL = [ofxiOSEAGLView getInstance]; 126 | } 127 | 128 | _startTimeSeconds = [[NSProcessInfo processInfo] systemUptime]; 129 | 130 | [self resetTouches]; 131 | 132 | return self; 133 | } 134 | 135 | - (void)dealloc 136 | { 137 | [super dealloc]; 138 | } 139 | 140 | 141 | -(void) resetTouches 142 | { 143 | _activePointerIndices[UITouchTypeDirect] = {}; 144 | _activePointerIndices[UITouchTypeIndirect] = {}; 145 | #if defined(__IPHONE_9_1) 146 | _activePointerIndices[UITouchTypeStylus] = {}; 147 | #endif 148 | _primaryPointerIndices[UITouchTypeDirect] = -1; 149 | _primaryPointerIndices[UITouchTypeIndirect] = -1; 150 | #if defined(__IPHONE_9_1) 151 | _primaryPointerIndices[UITouchTypeStylus] = -1; 152 | #endif 153 | } 154 | 155 | 156 | - (void)willMoveToSuperview:(UIView *)newSuperview; 157 | { 158 | [self resetTouches]; 159 | } 160 | 161 | 162 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 163 | { 164 | ofx::PointerEvents* events = ofx::PointerEventsManager::instance().events(); 165 | 166 | if (events) 167 | { 168 | for (UITouch* touch in touches) 169 | { 170 | auto evt = [self toPointerEventArgs:(_viewGLK ? _viewGLK : _viewEAGL) 171 | withTouch:touch 172 | withEvent:event 173 | withPointerIndex:[touch hash] 174 | withCoalesced:false 175 | withPredicted:false 176 | withUpdate:false]; 177 | dispatchPointerEvent(_window, evt); 178 | } 179 | } 180 | else 181 | { 182 | ofLogError("PointerViewIOS::touchesBegan") << "Invalid event, passing."; 183 | [super touchesBegan:touches withEvent:event]; 184 | } 185 | } 186 | 187 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 188 | { 189 | ofx::PointerEvents* events = ofx::PointerEventsManager::instance().events(); 190 | 191 | if (events) 192 | { 193 | for (UITouch* touch in touches) 194 | { 195 | auto evt = [self toPointerEventArgs:(_viewGLK ? _viewGLK : _viewEAGL) 196 | withTouch:touch 197 | withEvent:event 198 | withPointerIndex:[touch hash] 199 | withCoalesced:false 200 | withPredicted:false 201 | withUpdate:false]; 202 | dispatchPointerEvent(_window, evt); 203 | } 204 | } 205 | else 206 | { 207 | ofLogError("PointerViewIOS::touchesMoved") << "Invalid event, passing."; 208 | [super touchesMoved:touches withEvent:event]; 209 | } 210 | } 211 | 212 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 213 | { 214 | ofx::PointerEvents* events = ofx::PointerEventsManager::instance().events(); 215 | 216 | if (events) 217 | { 218 | for (UITouch* touch in touches) 219 | { 220 | auto evt = [self toPointerEventArgs:(_viewGLK ? _viewGLK : _viewEAGL) 221 | withTouch:touch 222 | withEvent:event 223 | withPointerIndex:[touch hash] 224 | withCoalesced:false 225 | withPredicted:false 226 | withUpdate:false]; 227 | dispatchPointerEvent(_window, evt); 228 | } 229 | } 230 | else 231 | { 232 | ofLogError("PointerViewIOS::touchesEnded") << "Invalid event, passing."; 233 | [super touchesEnded:touches withEvent:event]; 234 | } 235 | } 236 | 237 | 238 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 239 | { 240 | ofx::PointerEvents* events = ofx::PointerEventsManager::instance().events(); 241 | 242 | if (events) 243 | { 244 | for (UITouch* touch in touches) 245 | { 246 | auto evt = [self toPointerEventArgs:(_viewGLK ? _viewGLK : _viewEAGL) 247 | withTouch:touch 248 | withEvent:event 249 | withPointerIndex:[touch hash] 250 | withCoalesced:false 251 | withPredicted:false 252 | withUpdate:false]; 253 | dispatchPointerEvent(_window, evt); 254 | } 255 | } 256 | else 257 | { 258 | ofLogError("PointerViewIOS::touchesCancelled") << "Invalid event, passing."; 259 | [super touchesCancelled:touches withEvent:event]; 260 | } 261 | } 262 | 263 | 264 | #ifdef __IPHONE_9_1 265 | - (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches NS_AVAILABLE_IOS(9_1); 266 | { 267 | ofx::PointerEvents* events = ofx::PointerEventsManager::instance().events(); 268 | 269 | if (events) 270 | { 271 | for (UITouch* touch in touches) 272 | { 273 | auto evt = [self toPointerEventArgs:(_viewGLK ? _viewGLK : _viewEAGL) 274 | withTouch:touch 275 | withEvent:nil 276 | withPointerIndex:[touch hash] 277 | withCoalesced:false 278 | withPredicted:false 279 | withUpdate:true 280 | ]; 281 | dispatchPointerEvent(_window, evt); 282 | } 283 | } 284 | else 285 | { 286 | ofLogError("PointerViewIOS::touchesEstimatedPropertiesUpdated") << "Invalid event, passing."; 287 | [super touchesEstimatedPropertiesUpdated:touches]; 288 | } 289 | } 290 | #endif 291 | 292 | 293 | - (ofx::PointerEventArgs)toPointerEventArgs:(UIView*)view 294 | withTouch:(UITouch*)touch 295 | withEvent:(UIEvent*)event 296 | withPointerIndex:(int64_t)_pointerIndex 297 | withCoalesced:(bool)_isCoalesced 298 | withPredicted:(bool)_isPredicted 299 | withUpdate:(bool)_isUpdate 300 | { 301 | CGFloat majorRadius = 0.5; 302 | CGFloat majorRadiusTolerance = 0; 303 | 304 | #if defined(__IPHONE_8_0) 305 | majorRadius = [touch majorRadius]; 306 | majorRadiusTolerance = [touch majorRadiusTolerance]; 307 | #endif 308 | 309 | PointShape shape(PointShape::ShapeType::ELLIPSE, 310 | majorRadius * 2, 311 | majorRadiusTolerance * 2); 312 | 313 | CGPoint position; 314 | CGPoint precisePosition; 315 | 316 | position = [touch locationInView:view]; 317 | position.x *= view.contentScaleFactor; 318 | position.y *= view.contentScaleFactor; 319 | position = [self orientateTouchPoint:position]; 320 | 321 | #if defined(__IPHONE_9_1) 322 | precisePosition = [touch preciseLocationInView:view]; 323 | precisePosition.x *= view.contentScaleFactor; 324 | precisePosition.y *= view.contentScaleFactor; 325 | precisePosition = [self orientateTouchPoint:precisePosition]; 326 | #else 327 | precisePosition = position; 328 | #endif 329 | 330 | uint64_t buttons = 0; 331 | 332 | std::string eventType = PointerEventArgs::EVENT_TYPE_UNKNOWN; 333 | uint64_t detail = 0; 334 | 335 | uint64_t sequenceIndex = [[touch estimationUpdateIndex] unsignedLongLongValue]; 336 | 337 | std::set estimatedProperties = toPopertySet([touch estimatedProperties]); 338 | 339 | std::set estimatedPropertiesExpectingUpdates = toPopertySet([touch estimatedPropertiesExpectingUpdates]); 340 | 341 | int64_t pointerIndex = _pointerIndex; 342 | 343 | switch ([touch phase]) 344 | { 345 | case UITouchPhaseBegan: 346 | { 347 | eventType = PointerEventArgs::POINTER_DOWN; 348 | buttons |= (1 << OF_MOUSE_BUTTON_1); 349 | if (_activePointerIndices[[touch type]].empty()) 350 | _primaryPointerIndices[[touch type]] = pointerIndex; 351 | _activePointerIndices[[touch type]].insert(pointerIndex); 352 | break; 353 | } 354 | case UITouchPhaseMoved: 355 | case UITouchPhaseStationary: 356 | { 357 | eventType = PointerEventArgs::POINTER_MOVE; 358 | buttons |= (1 << OF_MOUSE_BUTTON_1); 359 | break; 360 | } 361 | case UITouchPhaseEnded: 362 | { 363 | eventType = PointerEventArgs::POINTER_UP; 364 | _activePointerIndices[[touch type]].erase(pointerIndex); 365 | break; 366 | } 367 | case UITouchPhaseCancelled: 368 | { 369 | eventType = PointerEventArgs::POINTER_CANCEL; 370 | _activePointerIndices[[touch type]].erase(pointerIndex); 371 | break; 372 | } 373 | } 374 | 375 | // If this is an update, we change its event type. 376 | if (_isUpdate) 377 | { 378 | eventType = PointerEventArgs::POINTER_UPDATE; 379 | // TODO ... this shouldn't happen. 380 | if ([touch estimatedPropertiesExpectingUpdates] > 0) 381 | assert(false); 382 | } 383 | 384 | // By default our pressure depends on if a "button" is pressed. 385 | CGFloat pressure = buttons > 0 ? 0.5 : 0; 386 | 387 | #if defined(__IPHONE_9_0) 388 | // If force is supported, then we try to update it. 389 | CGFloat maximumPossibleForce = [touch maximumPossibleForce]; 390 | 391 | if (maximumPossibleForce > 0) 392 | pressure = [touch force] / maximumPossibleForce; 393 | #endif 394 | 395 | float twistDeg = 0; 396 | float tangentialPressure = 0; 397 | float tiltXDeg = 0; 398 | float tiltYDeg = 0; 399 | 400 | bool isPredicted = _isPredicted; 401 | bool isCoalesced = _isCoalesced; 402 | bool isPrimary = (pointerIndex == _primaryPointerIndices[[touch type]]); 403 | 404 | std::string deviceType = PointerEventArgs::TYPE_UNKNOWN; 405 | 406 | switch ([touch type]) 407 | { 408 | case UITouchTypeDirect: 409 | { 410 | deviceType = PointerEventArgs::TYPE_TOUCH; 411 | break; 412 | } 413 | case UITouchTypeIndirect: 414 | { 415 | deviceType = PointerEventArgs::TYPE_MOUSE; 416 | break; 417 | } 418 | #if defined(__IPHONE_9_1) 419 | case UITouchTypeStylus: 420 | { 421 | deviceType = PointerEventArgs::TYPE_PEN; 422 | // Azimuth angle. Valid only for stylus touch types. Zero radians points along the positive X axis. 423 | // Passing a nil for the view parameter will return the azimuth relative to the touch's window. 424 | CGFloat azimuthRad = [touch azimuthAngleInView:view]; 425 | 426 | // Put into range 0 - 2PI 427 | if (azimuthRad < 0) 428 | azimuthRad += glm::two_pi(); 429 | 430 | // Zero radians indicates that the stylus is parallel to the screen surface, 431 | // while M_PI/2 radians indicates that it is normal to the screen surface. 432 | CGFloat altitudeRad = [touch altitudeAngle]; 433 | 434 | // Alternative way to calculate tiltX, tiltY. 435 | // Get the unit tilt vector then scale to degrees +/- 90 degrees. 436 | // - Reference: https://books.google.com/books?id=iYALAAAAQBAJ&pg=PA471&lpg=PA471&dq=azimuth+unit+vector+to+tiltX+tiltY&source=bl&ots=Z_M3-2caR8&sig=ACfU3U1vPG4qwkXBMqd9eT3k65wGLhbA3A&hl=en&sa=X&ved=2ahUKEwjGh-bbvargAhWmj4MKHR1sCRwQ6AEwDnoECAgQAQ#v=onepage&q=azimuth%20unit%20vector%20to%20tiltX%20tiltY&f=false 437 | // double lengthXY = std::cos(altitudeAngleRad); 438 | // tiltXDeg = std::cos(az) * lengthXY * 90; 439 | // tiltYDeg = std::sin(az) * lengthXY * 90; 440 | // tiltZDeg = std::cos(altitudeAngleRad) * 90; 441 | 442 | // Instead we use a conversion that is easily reversible. See: 443 | // Point::_cacheAzimuthAltitude(). 444 | double tanAltitude = std::tan(altitudeRad); 445 | tiltXDeg = glm::degrees(std::atan(std::cos(azimuthRad) / tanAltitude)); 446 | tiltYDeg = glm::degrees(std::atan(std::sin(azimuthRad) / tanAltitude)); 447 | 448 | break; 449 | } 450 | #endif 451 | } 452 | 453 | ofx::Point point({ position.x, position.y }, 454 | { precisePosition.x, precisePosition.y }, 455 | shape, 456 | pressure, 457 | tangentialPressure, 458 | twistDeg, 459 | tiltXDeg, 460 | tiltYDeg); 461 | 462 | uint64_t modifiers = 0; 463 | 464 | modifiers |= ofGetKeyPressed(OF_KEY_CONTROL) ? OF_KEY_CONTROL : 0; 465 | modifiers |= ofGetKeyPressed(OF_KEY_ALT) ? OF_KEY_ALT : 0; 466 | modifiers |= ofGetKeyPressed(OF_KEY_SHIFT) ? OF_KEY_SHIFT : 0; 467 | modifiers |= ofGetKeyPressed(OF_KEY_SUPER) ? OF_KEY_SUPER : 0; 468 | 469 | // Convert seconds to milliseconds. 470 | NSTimeInterval timestampSeconds = [touch timestamp]; 471 | NSTimeInterval elapsedTimestampSeconds = timestampSeconds - _startTimeSeconds; 472 | uint64_t timestampMicros = elapsedTimestampSeconds * 1000000.0; 473 | 474 | std::size_t deviceId = 0; 475 | uint64_t button = 0; 476 | 477 | std::vector coalescedPointerEvents; 478 | 479 | if (event) 480 | { 481 | NSArray* coalescedTouchesForTouch = [event coalescedTouchesForTouch:touch]; 482 | UITouch* thisTouch = [coalescedTouchesForTouch lastObject]; 483 | for (UITouch* _touch in coalescedTouchesForTouch) 484 | { 485 | coalescedPointerEvents.push_back([self toPointerEventArgs:view 486 | withTouch:_touch 487 | withEvent:event 488 | withPointerIndex:pointerIndex 489 | withCoalesced:(_touch != thisTouch) 490 | withPredicted:false 491 | withUpdate:false]); 492 | } 493 | } 494 | 495 | std::vector predictedPointerEvents; 496 | 497 | if (event) 498 | { 499 | for (UITouch* _touch in [event predictedTouchesForTouch:touch]) 500 | predictedPointerEvents.push_back([self toPointerEventArgs:view 501 | withTouch:_touch 502 | withEvent:event 503 | withPointerIndex:pointerIndex 504 | withCoalesced:false 505 | withPredicted:true 506 | withUpdate:false]); 507 | } 508 | 509 | const void* eventSource = self->_window; 510 | 511 | std::size_t pointerId = 0; 512 | hash_combine(pointerId, deviceId); 513 | hash_combine(pointerId, pointerIndex); 514 | hash_combine(pointerId, deviceType); 515 | 516 | return PointerEventArgs(eventSource, 517 | eventType, 518 | timestampMicros, 519 | detail, 520 | point, 521 | pointerId, 522 | deviceId, 523 | pointerIndex, 524 | sequenceIndex, 525 | deviceType, 526 | isCoalesced, 527 | isPredicted, 528 | isPrimary, 529 | button, 530 | buttons, 531 | modifiers, 532 | coalescedPointerEvents, 533 | predictedPointerEvents, 534 | estimatedProperties, 535 | estimatedPropertiesExpectingUpdates); 536 | } 537 | 538 | - (CGPoint)orientateTouchPoint:(CGPoint)touchPoint 539 | { 540 | if (!_window) 541 | { 542 | ofLogError("PointerView::orientateTouchPoint") << "Window is nil."; 543 | return touchPoint; 544 | } 545 | 546 | if (_window->doesHWOrientation()) 547 | return touchPoint; 548 | 549 | ofOrientation orientation = _window->getOrientation(); 550 | CGPoint touchPointOriented = CGPointZero; 551 | 552 | switch(orientation) 553 | { 554 | case OF_ORIENTATION_180: 555 | touchPointOriented.x = _window->getWidth() - touchPoint.x; 556 | touchPointOriented.y = _window->getHeight() - touchPoint.y; 557 | break; 558 | case OF_ORIENTATION_90_LEFT: 559 | touchPointOriented.x = touchPoint.y; 560 | touchPointOriented.y = _window->getHeight() - touchPoint.x; 561 | break; 562 | case OF_ORIENTATION_90_RIGHT: 563 | touchPointOriented.x = _window->getWidth() - touchPoint.y; 564 | touchPointOriented.y = touchPoint.x; 565 | break; 566 | case OF_ORIENTATION_DEFAULT: 567 | default: 568 | touchPointOriented = touchPoint; 569 | break; 570 | } 571 | 572 | return touchPointOriented; 573 | } 574 | 575 | 576 | @end 577 | 578 | 579 | namespace ofx { 580 | 581 | 582 | ///// \brief The global PointerView associated with the single iOS window. 583 | ///// \note Currently we only support one window. In the future we might support more if iOS allows it. 584 | static PointerView* pointerView = nullptr; 585 | 586 | 587 | void EnableAdvancedPointerEventsiOS() 588 | { 589 | if (!pointerView) 590 | { 591 | // Since iOS can only have one window, we initialize our PointerView on 592 | // that window. 593 | pointerView = [[PointerView alloc] initWithFrame:CGRectMake(0, 594 | 0, 595 | ofxiOSGetOFWindow()->getWidth(), 596 | ofxiOSGetOFWindow()->getHeight())]; 597 | 598 | [[ofxiOSGetAppDelegate() uiViewController].view addSubview:pointerView]; 599 | 600 | } 601 | } 602 | 603 | 604 | void DisableAdvancedPointerEventsiOS() 605 | { 606 | if (pointerView) 607 | { 608 | [pointerView removeFromSuperview]; 609 | [pointerView release]; 610 | pointerView = nullptr; 611 | } 612 | } 613 | 614 | 615 | } // namespace ofx 616 | 617 | 618 | #endif 619 | -------------------------------------------------------------------------------- /scripts/ci/docs/after_success.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ev 3 | echo "Publishing docs." 4 | 5 | if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then 6 | 7 | # Generate doxygen files. 8 | cd docs/; 9 | doxygen Doxyfile; 10 | 11 | # Publish html. 12 | git config --global user.name "Travis-CI" 13 | git config --global user.email ${GIT_EMAIL} 14 | 15 | # rm -rf gh-pages || exit 0; 16 | git clone --branch=gh-pages https://github.com/${TRAVIS_REPO_SLUG}.git gh-pages 17 | 18 | # Copy the tagfile. 19 | cp tagfile.xml gh-pages/ 20 | 21 | # Copy the documentation html. 22 | cp -R html/* gh-pages/ 23 | ( 24 | cd gh-pages; 25 | git add --all .; 26 | git commit -m "Travis ofxAddon documentation generation."; 27 | git remote set-url origin "https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git" 28 | # git push -fq origin gh-pages > /dev/null 2>&1 29 | git push -fq origin gh-pages 30 | ) 31 | 32 | cd .. 33 | 34 | else 35 | echo "Skipping document generation since this is a pull request."; 36 | fi 37 | -------------------------------------------------------------------------------- /scripts/ci/docs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | -------------------------------------------------------------------------------- /scripts/ci/docs/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # brew update 4 | # brew install git 5 | # brew install doxygen 6 | 7 | mkdir ~/doxygen && cd ~/doxygen 8 | wget http://ftp.stack.nl/pub/users/dimitri/doxygen-1.8.10.linux.bin.tar.gz 9 | tar xzf doxygen-1.8.10.linux.bin.tar.gz; 10 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | if [ "$CI" = true ]; 5 | then 6 | OF_ROOT=~/openFrameworks 7 | OF_ADDONS_DIR=$OF_ROOT/addons 8 | 9 | THIS_ADDON_NAME=${TRAVIS_REPO_SLUG#*/} 10 | THIS_ADDON_DIR=$TRAVIS_BUILD_DIR 11 | THIS_USERNAME=${TRAVIS_REPO_SLUG%/*} 12 | THIS_BRANCH=$TRAVIS_BRANCH 13 | else 14 | OF_ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../../.." && pwd ) 15 | OF_ADDONS_DIR=$OF_ROOT/addons 16 | 17 | THIS_ADDON_NAME=$(basename $( cd "$( dirname "${BASH_SOURCE[0]}")/../.." && pwd )) 18 | THIS_ADDON_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}")/../.." && pwd ) 19 | THIS_USERNAME=$(whoami) 20 | THIS_BRANCH=`git rev-parse --abbrev-ref HEAD` 21 | fi 22 | 23 | echo " OF_ROOT: ${OF_ROOT}" 24 | echo " OF_ADDONS_DIR: ${OF_ADDONS_DIR}" 25 | echo "THIS_ADDON_NAME: ${THIS_ADDON_NAME}" 26 | echo " THIS_ADDON_DIR: ${THIS_ADDON_DIR}" 27 | echo " THIS_USERNAME: ${THIS_USERNAME}" 28 | echo " THIS_BRANCH: ${THIS_BRANCH}" 29 | 30 | extract_required_addons() 31 | { 32 | if [ -z "$1" ]; then 33 | echo "Usage: extract_addons " 34 | return 1 35 | fi 36 | # Extract ADDON_DEPENDENCIES from THIS ADDON's addon_config.mk file. 37 | if [ -f ${1}/addon_config.mk ]; then 38 | REQUIRED_ADDONS="" 39 | while read line; do 40 | if [[ $line == ADDON_DEPENDENCIES* ]] ; 41 | then 42 | line=${line#*=} 43 | IFS=' ' read -ra ADDR <<< "$line" 44 | for i in "${ADDR[@]}"; do 45 | REQUIRED_ADDONS="${REQUIRED_ADDONS} ${i}" 46 | done 47 | fi 48 | done < ${1}/addon_config.mk 49 | echo $REQUIRED_ADDONS 50 | fi 51 | return 0 52 | } 53 | 54 | download_required_addons() 55 | { 56 | if [ -z "$1" ]; then 57 | echo "Usage: List of addons to download." 58 | return 1 59 | fi 60 | 61 | for addon in "$@" 62 | do 63 | if [ ! -d ${OF_ADDONS_DIR}/${addon} ]; then 64 | echo "Installing: ${OF_ADDONS_DIR}/${addon}" 65 | git clone --depth=1 -b ${THIS_BRANCH} https://github.com/${THIS_USERNAME}/${addon}.git ${OF_ADDONS_DIR}/${addon} 66 | 67 | REQUIRED_ADDONS=$(extract_required_addons ${OF_ADDONS_DIR}/${addon}) 68 | 69 | for required_addon in $REQUIRED_ADDONS 70 | do 71 | if [ ! -d ${OF_ADDONS_DIR}/${required_addon} ]; then 72 | download_required_addons ${required_addon} 73 | else 74 | echo "Dependency satisfied: ${required_addon}" 75 | fi 76 | done 77 | fi 78 | done 79 | return 0 80 | } 81 | 82 | 83 | # Gather required addons for THIS_ADDON. 84 | REQUIRED_ADDONS=$(extract_required_addons ${THIS_ADDON_DIR}) 85 | 86 | # Gather required addons from all examples. 87 | for addons_make in ${THIS_ADDON_DIR}/example*/addons.make; do 88 | while read addon; do 89 | if [ ${addon} != ${THIS_ADDON_NAME} ] ; 90 | then 91 | REQUIRED_ADDONS="${REQUIRED_ADDONS} ${addon}" 92 | fi 93 | done < $addons_make 94 | done 95 | 96 | REQUIRED_ADDONS=$(echo ${REQUIRED_ADDONS} | tr ' ' '\n' | sort -u | tr '\n' ' ') 97 | 98 | download_required_addons $REQUIRED_ADDONS 99 | -------------------------------------------------------------------------------- /src/ofxPointer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2009 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | #include "ofConstants.h" 11 | #include "ofx/PointerEvents.h" 12 | 13 | #if defined(TARGET_OF_IOS) 14 | #include "ofx/PointerEventsiOS.h" 15 | #endif 16 | 17 | /// \brief An alias for easier future core integration. 18 | typedef ofx::PointerEventArgs ofPointerEventArgs; 19 | --------------------------------------------------------------------------------