├── LICENSE.md ├── README.md ├── external ├── crossguid │ ├── LICENSE.txt │ ├── crossguid.cpp │ └── crossguid.hpp └── sigslot │ ├── LICENSE.txt │ └── sigslot.hpp └── src └── ga ├── color.h ├── defines.h ├── easing.h ├── events.h ├── fbo.h ├── font.h ├── gl.h ├── graph ├── component.h ├── components │ ├── bounds_component.h │ ├── image_component.h │ ├── paragraph_component.cpp │ ├── paragraph_component.h │ ├── timeline_component.h │ ├── tint_component.h │ └── touchzone_component.h ├── node.cpp ├── node.h ├── scene.cpp └── scene.h ├── json.h ├── json_types.h ├── layout.h ├── math.h ├── render.cpp ├── render.h ├── resource.h ├── signal.h ├── texture.h ├── timeout.h ├── timer.cpp ├── timer.h ├── transform.h ├── tween.h ├── util.h └── uuid.h /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gallagher & Associates, LLC 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 | # GA::kit 2 | 3 | | [GA::kit Repo](https://github.com/gallagher-tech/GAkit/) | [ofxGAkit Repo](https://github.com/gallagher-tech/ofxGAkit) | [Documentation](https://gallagher-tech.github.io/GAkit-doc/) | 4 | 5 | `GA::kit` is G&A's in-house C++ application framework for building interactive experiences. 6 | We use it to create anything from 2D UI prototypes to high-performance 3D immersive installations. 7 | 8 | We designed it from scratch to be: 9 | 10 | - _fast_ - 11 | Our scenegraph lets you intuitively craft complex 3D scenes without sacrificing performance. 12 | 13 | - _flexible_ - 14 | Every app is different. We use modular components, templated classes, and helpful utilities to support iterative, agile development. 15 | 16 | - _familiar_ - 17 | As creative coders, we love the clarity and efficacy of existing toolkits like openFrameworks and Cinder. 18 | That's why we designed `GA::kit` to work as an oF addon - and a Cinder Block is on the roadmap. 19 | Or bring your own rendering engine - we strive to be framework agnostic. 20 | 21 | ## How To Use 22 | 23 | ### ofxGAkit openFrameworks addon 24 | 25 | Currently, the simplest way to use `GA::kit` is through [ofxGAkit](https://github.com/gallagher-tech/ofxGAkit) openFrameworks addon. 26 | 27 | Simply `git clone --recursive` the addon repo inside of your `openFrameworks/addons/` directory, then use the oF Project Generator to generate a project with the addon selected. 28 | 29 | The addon works for both Windows and Mac. Full setup instructions can be found [here](https://github.com/gallagher-tech/ofxGAkit). 30 | 31 | In this case, openFrameworks provides the windowing and rendering system (`glfw / openGL`), and automatically includes the necessary `nlohmann::json` and `glm` libraries. 32 | 33 | ### Bring Your Own Backend 34 | 35 | We aim to make `GA::kit` "framework agnostic". This means that we would like it to play nicely with other C++ creative coding frameworks like Cinder - or it could be used on its own by including a few supporting libraries. 36 | 37 | The only requirements beyond C++14 are including the `glm` and `nlohmann::json` libraries, and adding an openGL windowing library, like `glfw`. 38 | 39 | We could use your help: 40 | 41 | - creating a Cinder block. _Todo: Cinder block repo link_ 42 | - creating a "framework-free" example project, showing how to include `glm`, `nlohman::json` and `glfw` 43 | 44 | ## Features 45 | 46 | - Hierarchical 3D **Scenegraph** with `Scene`, `Node`, and `Component` classes 47 | - Innovative **tween animation system** with `Tween`, `Timeline` and easing functions 48 | - `Timer` and `Timeout` classes for triggering timed callbacks 49 | - Universal app **asset management** with templated `ResourceCache` class 50 | - `KeyEvent`, `MouseEvent` and `TouchEvent` handling 51 | - **Layout** utitilies to automatically align and scale to fit 52 | - Math utilities built on [**glm**](https://github.com/g-truc/glm) 53 | - JSON serialization, using [**nlohmann::json**](https://github.com/nlohmann/json) 54 | - Signal-based event system, using [**sigslot**](https://github.com/palacaze/sigslot) (included) 55 | - Universal Unique ID system, using [**crossguid**](https://github.com/graeme-hill/crossguid) (included) 56 | 57 | ## Todo 58 | 59 | - GLSL `Shader` class 60 | - `Material` (shader + textures + parameters) component 61 | - `Mesh` VBO component 62 | - Cinder block 63 | - Framework-free example using GLFW 64 | 65 | ## Requirements 66 | 67 | - C++14 68 | - OpenGL 3+ 69 | - [**glm**](https://github.com/g-truc/glm) 70 | - [**nlohmann::json**](https://github.com/nlohmann/json) 71 | 72 | ## About G&A 73 | We are the industry’s best strategic problem solvers. A team of boundless creative individuals. As experts in our own craft, relentless curiosity fuels our passion for working with the best partners to build community experiences led by their profound stories. Bring your story and become part of ours. 74 | 75 | Visit G&A [website](https://gallagherdesign.com/) 76 | -------------------------------------------------------------------------------- /external/crossguid/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Graeme Hill (http://graemehill.ca) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /external/crossguid/crossguid.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Graeme Hill (http://graemehill.ca) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include "crossguid.hpp" 27 | 28 | #ifdef GUID_LIBUUID 29 | #include 30 | #endif 31 | 32 | #ifdef GUID_CFUUID 33 | #include 34 | #endif 35 | 36 | #ifdef GUID_WINDOWS 37 | #include 38 | #endif 39 | 40 | #ifdef GUID_ANDROID 41 | #include 42 | #include 43 | #endif 44 | 45 | BEGIN_XG_NAMESPACE 46 | 47 | #ifdef GUID_ANDROID 48 | AndroidGuidInfo androidInfo; 49 | 50 | AndroidGuidInfo AndroidGuidInfo::fromJniEnv( JNIEnv* env ) 51 | { 52 | AndroidGuidInfo info; 53 | info.env = env; 54 | auto localUuidClass = env->FindClass( "java/util/UUID" ); 55 | info.uuidClass = ( jclass )env->NewGlobalRef( localUuidClass ); 56 | env->DeleteLocalRef( localUuidClass ); 57 | info.newGuidMethod = env->GetStaticMethodID( 58 | info.uuidClass, "randomUUID", "()Ljava/util/UUID;" ); 59 | info.mostSignificantBitsMethod = env->GetMethodID( 60 | info.uuidClass, "getMostSignificantBits", "()J" ); 61 | info.leastSignificantBitsMethod = env->GetMethodID( 62 | info.uuidClass, "getLeastSignificantBits", "()J" ); 63 | info.initThreadId = std::this_thread::get_id(); 64 | return info; 65 | } 66 | 67 | void initJni( JNIEnv* env ) 68 | { 69 | androidInfo = AndroidGuidInfo::fromJniEnv( env ); 70 | } 71 | #endif 72 | 73 | // overload << so that it's easy to convert to a string 74 | std::ostream& operator<<( std::ostream& s, const Guid& guid ) 75 | { 76 | std::ios_base::fmtflags f( s.flags() ); // politely don't leave the ostream in hex mode 77 | s << std::hex << std::setfill( '0' ) 78 | << std::setw( 2 ) << ( int )guid._bytes[0] 79 | << std::setw( 2 ) << ( int )guid._bytes[1] 80 | << std::setw( 2 ) << ( int )guid._bytes[2] 81 | << std::setw( 2 ) << ( int )guid._bytes[3] 82 | << "-" 83 | << std::setw( 2 ) << ( int )guid._bytes[4] 84 | << std::setw( 2 ) << ( int )guid._bytes[5] 85 | << "-" 86 | << std::setw( 2 ) << ( int )guid._bytes[6] 87 | << std::setw( 2 ) << ( int )guid._bytes[7] 88 | << "-" 89 | << std::setw( 2 ) << ( int )guid._bytes[8] 90 | << std::setw( 2 ) << ( int )guid._bytes[9] 91 | << "-" 92 | << std::setw( 2 ) << ( int )guid._bytes[10] 93 | << std::setw( 2 ) << ( int )guid._bytes[11] 94 | << std::setw( 2 ) << ( int )guid._bytes[12] 95 | << std::setw( 2 ) << ( int )guid._bytes[13] 96 | << std::setw( 2 ) << ( int )guid._bytes[14] 97 | << std::setw( 2 ) << ( int )guid._bytes[15]; 98 | s.flags( f ); 99 | return s; 100 | } 101 | 102 | bool operator<( const xg::Guid& lhs, const xg::Guid& rhs ) 103 | { 104 | return lhs.bytes() < rhs.bytes(); 105 | } 106 | 107 | bool Guid::isValid() const 108 | { 109 | xg::Guid empty; 110 | return *this != empty; 111 | } 112 | 113 | // convert to string using std::snprintf() and std::string 114 | std::string Guid::str() const 115 | { 116 | char one[10], two[6], three[6], four[6], five[14]; 117 | 118 | snprintf( one, 10, "%02x%02x%02x%02x", 119 | _bytes[0], _bytes[1], _bytes[2], _bytes[3] ); 120 | snprintf( two, 6, "%02x%02x", 121 | _bytes[4], _bytes[5] ); 122 | snprintf( three, 6, "%02x%02x", 123 | _bytes[6], _bytes[7] ); 124 | snprintf( four, 6, "%02x%02x", 125 | _bytes[8], _bytes[9] ); 126 | snprintf( five, 14, "%02x%02x%02x%02x%02x%02x", 127 | _bytes[10], _bytes[11], _bytes[12], _bytes[13], _bytes[14], _bytes[15] ); 128 | const std::string sep( "-" ); 129 | std::string out( one ); 130 | 131 | out += sep + two; 132 | out += sep + three; 133 | out += sep + four; 134 | out += sep + five; 135 | 136 | return out; 137 | } 138 | 139 | // conversion operator for std::string 140 | Guid::operator std::string() const 141 | { 142 | return str(); 143 | } 144 | 145 | // Access underlying bytes 146 | const std::array& Guid::bytes() const 147 | { 148 | return _bytes; 149 | } 150 | 151 | // create a guid from vector of bytes 152 | Guid::Guid( const std::array& bytes ) 153 | : _bytes( bytes ) 154 | {} 155 | 156 | // create a guid from vector of bytes 157 | Guid::Guid( std::array&& bytes ) 158 | : _bytes( std::move( bytes ) ) 159 | {} 160 | 161 | // converts a single hex char to a number (0 - 15) 162 | unsigned char hexDigitToChar( char ch ) 163 | { 164 | // 0-9 165 | if ( ch > 47 && ch < 58 ) 166 | return ch - 48; 167 | 168 | // a-f 169 | if ( ch > 96 && ch < 103 ) 170 | return ch - 87; 171 | 172 | // A-F 173 | if ( ch > 64 && ch < 71 ) 174 | return ch - 55; 175 | 176 | return 0; 177 | } 178 | 179 | bool isValidHexChar( char ch ) 180 | { 181 | // 0-9 182 | if ( ch > 47 && ch < 58 ) 183 | return true; 184 | 185 | // a-f 186 | if ( ch > 96 && ch < 103 ) 187 | return true; 188 | 189 | // A-F 190 | if ( ch > 64 && ch < 71 ) 191 | return true; 192 | 193 | return false; 194 | } 195 | 196 | // converts the two hexadecimal characters to an unsigned char (a byte) 197 | unsigned char hexPairToChar( char a, char b ) 198 | { 199 | return hexDigitToChar( a ) * 16 + hexDigitToChar( b ); 200 | } 201 | 202 | // create a guid from string 203 | Guid::Guid( const std::string& fromString ) 204 | { 205 | char charOne = '\0'; 206 | char charTwo = '\0'; 207 | bool lookingForFirstChar = true; 208 | unsigned nextByte = 0; 209 | 210 | for ( const char& ch : fromString ) { 211 | if ( ch == '-' ) 212 | continue; 213 | 214 | if ( nextByte >= 16 || !isValidHexChar( ch ) ) { 215 | // Invalid string so bail 216 | zeroify(); 217 | return; 218 | } 219 | 220 | if ( lookingForFirstChar ) { 221 | charOne = ch; 222 | lookingForFirstChar = false; 223 | } else { 224 | charTwo = ch; 225 | auto byte = hexPairToChar( charOne, charTwo ); 226 | _bytes[nextByte++] = byte; 227 | lookingForFirstChar = true; 228 | } 229 | } 230 | 231 | // if there were fewer than 16 bytes in the string then guid is bad 232 | if ( nextByte < 16 ) { 233 | zeroify(); 234 | return; 235 | } 236 | } 237 | 238 | // create empty guid 239 | Guid::Guid() 240 | : _bytes{ { 0 } } 241 | {} 242 | 243 | // set all bytes to zero 244 | void Guid::zeroify() 245 | { 246 | std::fill( _bytes.begin(), _bytes.end(), static_cast( 0 ) ); 247 | } 248 | 249 | // overload equality operator 250 | bool Guid::operator==( const Guid& other ) const 251 | { 252 | return _bytes == other._bytes; 253 | } 254 | 255 | // overload inequality operator 256 | bool Guid::operator!=( const Guid& other ) const 257 | { 258 | return !( ( *this ) == other ); 259 | } 260 | 261 | // member swap function 262 | void Guid::swap( Guid& other ) 263 | { 264 | _bytes.swap( other._bytes ); 265 | } 266 | 267 | // This is the linux friendly implementation, but it could work on other 268 | // systems that have libuuid available 269 | #ifdef GUID_LIBUUID 270 | Guid newGuid() 271 | { 272 | std::array data; 273 | static_assert( std::is_same::value, "Wrong type!" ); 274 | uuid_generate( data.data() ); 275 | return Guid{ std::move( data ) }; 276 | } 277 | #endif 278 | 279 | // this is the mac and ios version 280 | #ifdef GUID_CFUUID 281 | Guid newGuid() 282 | { 283 | auto newId = CFUUIDCreate( NULL ); 284 | auto bytes = CFUUIDGetUUIDBytes( newId ); 285 | CFRelease( newId ); 286 | 287 | std::array byteArray = 288 | { { bytes.byte0, 289 | bytes.byte1, 290 | bytes.byte2, 291 | bytes.byte3, 292 | bytes.byte4, 293 | bytes.byte5, 294 | bytes.byte6, 295 | bytes.byte7, 296 | bytes.byte8, 297 | bytes.byte9, 298 | bytes.byte10, 299 | bytes.byte11, 300 | bytes.byte12, 301 | bytes.byte13, 302 | bytes.byte14, 303 | bytes.byte15 } }; 304 | return Guid{ std::move( byteArray ) }; 305 | } 306 | #endif 307 | 308 | // obviously this is the windows version 309 | #ifdef GUID_WINDOWS 310 | Guid newGuid() 311 | { 312 | GUID newId; 313 | CoCreateGuid( &newId ); 314 | 315 | std::array bytes = 316 | { 317 | ( unsigned char )( ( newId.Data1 >> 24 ) & 0xFF ), 318 | ( unsigned char )( ( newId.Data1 >> 16 ) & 0xFF ), 319 | ( unsigned char )( ( newId.Data1 >> 8 ) & 0xFF ), 320 | ( unsigned char )( ( newId.Data1 ) & 0xff ), 321 | 322 | ( unsigned char )( ( newId.Data2 >> 8 ) & 0xFF ), 323 | ( unsigned char )( ( newId.Data2 ) & 0xff ), 324 | 325 | ( unsigned char )( ( newId.Data3 >> 8 ) & 0xFF ), 326 | ( unsigned char )( ( newId.Data3 ) & 0xFF ), 327 | 328 | ( unsigned char )newId.Data4[0], 329 | ( unsigned char )newId.Data4[1], 330 | ( unsigned char )newId.Data4[2], 331 | ( unsigned char )newId.Data4[3], 332 | ( unsigned char )newId.Data4[4], 333 | ( unsigned char )newId.Data4[5], 334 | ( unsigned char )newId.Data4[6], 335 | ( unsigned char )newId.Data4[7] }; 336 | 337 | return Guid{ std::move( bytes ) }; 338 | } 339 | #endif 340 | 341 | // android version that uses a call to a java api 342 | #ifdef GUID_ANDROID 343 | Guid newGuid( JNIEnv* env ) 344 | { 345 | assert( env != androidInfo.env || std::this_thread::get_id() == androidInfo.initThreadId ); 346 | 347 | jobject javaUuid = env->CallStaticObjectMethod( 348 | androidInfo.uuidClass, androidInfo.newGuidMethod ); 349 | jlong mostSignificant = env->CallLongMethod( javaUuid, 350 | androidInfo.mostSignificantBitsMethod ); 351 | jlong leastSignificant = env->CallLongMethod( javaUuid, 352 | androidInfo.leastSignificantBitsMethod ); 353 | 354 | std::array bytes = 355 | { 356 | ( unsigned char )( ( mostSignificant >> 56 ) & 0xFF ), 357 | ( unsigned char )( ( mostSignificant >> 48 ) & 0xFF ), 358 | ( unsigned char )( ( mostSignificant >> 40 ) & 0xFF ), 359 | ( unsigned char )( ( mostSignificant >> 32 ) & 0xFF ), 360 | ( unsigned char )( ( mostSignificant >> 24 ) & 0xFF ), 361 | ( unsigned char )( ( mostSignificant >> 16 ) & 0xFF ), 362 | ( unsigned char )( ( mostSignificant >> 8 ) & 0xFF ), 363 | ( unsigned char )( ( mostSignificant )&0xFF ), 364 | ( unsigned char )( ( leastSignificant >> 56 ) & 0xFF ), 365 | ( unsigned char )( ( leastSignificant >> 48 ) & 0xFF ), 366 | ( unsigned char )( ( leastSignificant >> 40 ) & 0xFF ), 367 | ( unsigned char )( ( leastSignificant >> 32 ) & 0xFF ), 368 | ( unsigned char )( ( leastSignificant >> 24 ) & 0xFF ), 369 | ( unsigned char )( ( leastSignificant >> 16 ) & 0xFF ), 370 | ( unsigned char )( ( leastSignificant >> 8 ) & 0xFF ), 371 | ( unsigned char )( ( leastSignificant )&0xFF ) }; 372 | 373 | env->DeleteLocalRef( javaUuid ); 374 | 375 | return Guid{ std::move( bytes ) }; 376 | } 377 | 378 | Guid newGuid() 379 | { 380 | return newGuid( androidInfo.env ); 381 | } 382 | #endif 383 | 384 | END_XG_NAMESPACE 385 | 386 | // Specialization for std::swap() -- 387 | // call member swap function of lhs, passing rhs 388 | namespace std { 389 | template <> 390 | void swap( xg::Guid& lhs, xg::Guid& rhs ) noexcept 391 | { 392 | lhs.swap( rhs ); 393 | } 394 | } // namespace std -------------------------------------------------------------------------------- /external/crossguid/crossguid.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Graeme Hill (http://graemehill.ca) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | #pragma once 25 | 26 | /* platform / guid lib defines: 27 | GUID_WINDOWS = windows / win32 28 | GUID_LIBUUID = linux / libuuid 29 | GUID_CFUUID = apple / core foundation 30 | GUID_ANDROID = android / Java Native Interface 31 | */ 32 | 33 | // clang-format off 34 | #if !defined( GUID_WINDOWS ) && !defined( GUID_LIBUUID ) && !defined( GUID_CFUUID ) && !defined( GUID_ANDROID ) 35 | // try to detect platform: 36 | #ifdef _WIN32 37 | #define GUID_WINDOWS 38 | #elif defined(__APPLE__) 39 | #define GUID_CFUUID 40 | #elif defined(__ANDROID__) 41 | #define GUID_ANDROID 42 | #elif defined(__linux) || defined(__posix) 43 | #define GUID_LIBUUID 44 | #endif 45 | #endif 46 | // clang-format on 47 | 48 | #ifdef GUID_ANDROID 49 | #include 50 | #include 51 | #endif 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | #define BEGIN_XG_NAMESPACE namespace xg { 62 | #define END_XG_NAMESPACE } 63 | 64 | BEGIN_XG_NAMESPACE 65 | 66 | // Class to represent a GUID/UUID. Each instance acts as a wrapper around a 67 | // 16 byte value that can be passed around by value. It also supports 68 | // conversion to string (via the stream operator <<) and conversion from a 69 | // string via constructor. 70 | class Guid 71 | { 72 | public: 73 | explicit Guid( const std::array& bytes ); 74 | explicit Guid( std::array&& bytes ); 75 | 76 | explicit Guid( const std::string& fromString ); 77 | Guid(); 78 | 79 | Guid( const Guid& other ) = default; 80 | Guid& operator=( const Guid& other ) = default; 81 | Guid( Guid&& other ) = default; 82 | Guid& operator=( Guid&& other ) = default; 83 | 84 | bool operator==( const Guid& other ) const; 85 | bool operator!=( const Guid& other ) const; 86 | 87 | std::string str() const; 88 | operator std::string() const; 89 | const std::array& bytes() const; 90 | void swap( Guid& other ); 91 | bool isValid() const; 92 | 93 | private: 94 | void zeroify(); 95 | 96 | // actual data 97 | std::array _bytes; 98 | 99 | // make the << operator a friend so it can access _bytes 100 | friend std::ostream& operator<<( std::ostream& s, const Guid& guid ); 101 | friend bool operator<( const Guid& lhs, const Guid& rhs ); 102 | }; 103 | 104 | Guid newGuid(); 105 | 106 | #ifdef GUID_ANDROID 107 | struct AndroidGuidInfo 108 | { 109 | static AndroidGuidInfo fromJniEnv( JNIEnv* env ); 110 | 111 | JNIEnv* env; 112 | jclass uuidClass; 113 | jmethodID newGuidMethod; 114 | jmethodID mostSignificantBitsMethod; 115 | jmethodID leastSignificantBitsMethod; 116 | std::thread::id initThreadId; 117 | }; 118 | 119 | extern AndroidGuidInfo androidInfo; 120 | 121 | void initJni( JNIEnv* env ); 122 | 123 | // overloading for multi-threaded calls 124 | Guid newGuid( JNIEnv* env ); 125 | #endif 126 | 127 | namespace details { 128 | template 129 | struct hash; 130 | 131 | template 132 | struct hash : public std::hash 133 | { 134 | using std::hash::hash; 135 | }; 136 | 137 | template 138 | struct hash 139 | { 140 | inline std::size_t operator()( const T& v, const Rest&... rest ) 141 | { 142 | std::size_t seed = hash{}( rest... ); 143 | seed ^= hash{}( v ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 ); 144 | return seed; 145 | } 146 | }; 147 | } // namespace details 148 | 149 | END_XG_NAMESPACE 150 | 151 | namespace std { 152 | // Template specialization for std::swap() -- 153 | // See guid.cpp for the function definition 154 | template <> 155 | void swap( xg::Guid& guid0, xg::Guid& guid1 ) noexcept; 156 | 157 | // Specialization for std::hash -- this implementation 158 | // uses std::hash on the stringification of the guid 159 | // to calculate the hash 160 | template <> 161 | struct hash 162 | { 163 | std::size_t operator()( xg::Guid const& guid ) const 164 | { 165 | const uint64_t* p = reinterpret_cast( guid.bytes().data() ); 166 | return xg::details::hash{}( p[0], p[1] ); 167 | } 168 | }; 169 | } // namespace std -------------------------------------------------------------------------------- /external/sigslot/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pierre-Antoine Lacaze 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 | -------------------------------------------------------------------------------- /external/sigslot/sigslot.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Pierre-Antoine Lacaze 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #if defined __clang__ || (__GNUC__ > 5) 35 | #define SIGSLOT_MAY_ALIAS __attribute__((__may_alias__)) 36 | #else 37 | #define SIGSLOT_MAY_ALIAS 38 | #endif 39 | 40 | #if defined(__GXX_RTTI) || defined(__cpp_rtti) || defined(_CPPRTTI) 41 | #define SIGSLOT_RTTI_ENABLED 1 42 | #include 43 | #endif 44 | 45 | namespace sigslot { 46 | 47 | namespace detail { 48 | 49 | // Used to detect an object of observer type 50 | struct observer_type {}; 51 | 52 | } // namespace detail 53 | 54 | namespace trait { 55 | 56 | /// represent a list of types 57 | template struct typelist {}; 58 | 59 | /** 60 | * Pointers that can be converted to a weak pointer concept for tracking 61 | * purpose must implement the to_weak() function in order to make use of 62 | * ADL to convert that type and make it usable 63 | */ 64 | 65 | template 66 | std::weak_ptr to_weak(std::weak_ptr w) { 67 | return w; 68 | } 69 | 70 | template 71 | std::weak_ptr to_weak(std::shared_ptr s) { 72 | return s; 73 | } 74 | 75 | // tools 76 | namespace detail { 77 | 78 | template 79 | struct voider { using type = void; }; 80 | 81 | // void_t from c++17 82 | template 83 | using void_t = typename detail::voider::type; 84 | 85 | template 86 | struct has_call_operator : std::false_type {}; 87 | 88 | template 89 | struct has_call_operator::type::operator())>> 90 | : std::true_type {}; 91 | 92 | template 93 | struct is_callable : std::false_type {}; 94 | 95 | template 96 | struct is_callable, 97 | void_t()).*std::declval())(std::declval()...))>> 98 | : std::true_type {}; 99 | 100 | template 101 | struct is_callable, 102 | void_t()(std::declval()...))>> 103 | : std::true_type {}; 104 | 105 | 106 | template 107 | struct is_weak_ptr : std::false_type {}; 108 | 109 | template 110 | struct is_weak_ptr().expired()), 111 | decltype(std::declval().lock()), 112 | decltype(std::declval().reset())>> 113 | : std::true_type {}; 114 | 115 | template 116 | struct is_weak_ptr_compatible : std::false_type {}; 117 | 118 | template 119 | struct is_weak_ptr_compatible()))>> 120 | : is_weak_ptr()))> {}; 121 | 122 | } // namespace detail 123 | 124 | static constexpr bool with_rtti = 125 | #ifdef SIGSLOT_RTTI_ENABLED 126 | true; 127 | #else 128 | false; 129 | #endif 130 | 131 | /// determine if a pointer is convertible into a "weak" pointer 132 | template 133 | constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible>::value; 134 | 135 | /// determine if a type T (Callable or Pmf) is callable with supplied arguments 136 | template 137 | constexpr bool is_callable_v = detail::is_callable::value; 138 | 139 | template 140 | constexpr bool is_weak_ptr_v = detail::is_weak_ptr::value; 141 | 142 | template 143 | constexpr bool has_call_operator_v = detail::has_call_operator::value; 144 | 145 | template 146 | constexpr bool is_pointer_v = std::is_pointer::value; 147 | 148 | template 149 | constexpr bool is_func_v = std::is_function::value; 150 | 151 | template 152 | constexpr bool is_pmf_v = std::is_member_function_pointer::value; 153 | 154 | template 155 | constexpr bool is_observer_v = std::is_base_of<::sigslot::detail::observer_type, 156 | std::remove_pointer_t>::value; 157 | 158 | } // namespace trait 159 | 160 | template 161 | class signal_base; 162 | 163 | /** 164 | * A group_id is used to identify a group of slots 165 | */ 166 | using group_id = std::int32_t; 167 | 168 | namespace detail { 169 | 170 | /** 171 | * The following function_traits and object_pointer series of templates are 172 | * used to circumvent the type-erasing that takes place in the slot_base 173 | * implementations. They are used to compare the stored functions and objects 174 | * with another one for disconnection purpose. 175 | */ 176 | 177 | /* 178 | * Function pointers and member function pointers size differ from compiler to 179 | * compiler, and for virtual members compared to non virtual members. On some 180 | * compilers, multiple inheritance has an impact too. Hence, we form an union 181 | * big enough to store any kind of function pointer. 182 | */ 183 | namespace mock { 184 | 185 | struct a { virtual ~a() = default; void f(); virtual void g(); }; 186 | struct b { virtual ~b() = default; virtual void h(); }; 187 | struct c : a, b { void g() override; }; 188 | 189 | union fun_types { 190 | decltype(&c::g) m; 191 | decltype(&a::g) v; 192 | decltype(&a::f) d; 193 | void (*f)(); 194 | void *o; 195 | }; 196 | 197 | } // namespace mock 198 | 199 | /* 200 | * This union is used to compare function pointers 201 | * Generic callables cannot be compared. Here we compare pointers but there is 202 | * no guarantee that this always works. 203 | */ 204 | union SIGSLOT_MAY_ALIAS func_ptr { 205 | void* value() { 206 | return &data[0]; 207 | } 208 | 209 | const void* value() const { 210 | return &data[0]; 211 | } 212 | 213 | template 214 | T& value() { 215 | return *static_cast(value()); 216 | } 217 | 218 | template 219 | const T& value() const { 220 | return *static_cast(value()); 221 | } 222 | 223 | inline explicit operator bool() const { 224 | return value() != nullptr; 225 | } 226 | 227 | inline bool operator==(const func_ptr &o) const { 228 | return std::equal(std::begin(data), std::end(data), std::begin(o.data)); 229 | } 230 | 231 | mock::fun_types _; 232 | char data[sizeof(mock::fun_types)]; 233 | }; 234 | 235 | 236 | template 237 | struct function_traits { 238 | static void ptr(const T &/*t*/, func_ptr &d) { 239 | d.value() = nullptr; 240 | } 241 | 242 | static constexpr bool is_disconnectable = false; 243 | static constexpr bool must_check_object = true; 244 | }; 245 | 246 | template 247 | struct function_traits>> { 248 | static void ptr(T &t, func_ptr &d) { 249 | d.value() = &t; 250 | } 251 | 252 | static constexpr bool is_disconnectable = true; 253 | static constexpr bool must_check_object = false; 254 | }; 255 | 256 | template 257 | struct function_traits>> { 258 | static void ptr(T *t, func_ptr &d) { 259 | d.value() = t; 260 | } 261 | 262 | static constexpr bool is_disconnectable = true; 263 | static constexpr bool must_check_object = false; 264 | }; 265 | 266 | template 267 | struct function_traits>> { 268 | static void ptr(const T &t, func_ptr &d) { 269 | d.value() = t; 270 | } 271 | 272 | static constexpr bool is_disconnectable = trait::with_rtti; 273 | static constexpr bool must_check_object = true; 274 | }; 275 | 276 | // for function objects, the assumption is that we are looking for the call operator 277 | template 278 | struct function_traits>> { 279 | using call_type = decltype(&std::remove_reference::type::operator()); 280 | 281 | static void ptr(const T &/*t*/, func_ptr &d) { 282 | function_traits::ptr(&T::operator(), d); 283 | } 284 | 285 | static constexpr bool is_disconnectable = function_traits::is_disconnectable; 286 | static constexpr bool must_check_object = function_traits::must_check_object; 287 | }; 288 | 289 | template 290 | func_ptr get_function_ptr(const T &t) { 291 | func_ptr d; 292 | std::uninitialized_fill(std::begin(d.data), std::end(d.data), '\0'); 293 | function_traits>::ptr(t, d); 294 | return d; 295 | } 296 | 297 | /* 298 | * obj_ptr is used to store a pointer to an object. 299 | * The object_pointer traits are needed to handle trackable objects correctly, 300 | * as they are likely to not be pointers. 301 | */ 302 | using obj_ptr = const void*; 303 | 304 | template 305 | obj_ptr get_object_ptr(const T &t); 306 | 307 | template 308 | struct object_pointer { 309 | static obj_ptr get(const T&) { 310 | return nullptr; 311 | } 312 | }; 313 | 314 | template 315 | struct object_pointer>> { 316 | static obj_ptr get(const T *t) { 317 | return reinterpret_cast(t); 318 | } 319 | }; 320 | 321 | template 322 | struct object_pointer>> { 323 | static obj_ptr get(const T &t) { 324 | auto p = t.lock(); 325 | return get_object_ptr(p); 326 | } 327 | }; 328 | 329 | template 330 | struct object_pointer && 331 | !trait::is_weak_ptr_v && 332 | trait::is_weak_ptr_compatible_v>> 333 | { 334 | static obj_ptr get(const T &t) { 335 | return t ? reinterpret_cast(t.get()) : nullptr; 336 | } 337 | }; 338 | 339 | template 340 | obj_ptr get_object_ptr(const T &t) { 341 | return object_pointer::get(t); 342 | } 343 | 344 | 345 | // noop mutex for thread-unsafe use 346 | struct null_mutex { 347 | null_mutex() noexcept = default; 348 | ~null_mutex() noexcept = default; 349 | null_mutex(const null_mutex &) = delete; 350 | null_mutex& operator=(const null_mutex &) = delete; 351 | null_mutex(null_mutex &&) = delete; 352 | null_mutex& operator=(null_mutex &&) = delete; 353 | 354 | inline bool try_lock() noexcept { return true; } 355 | inline void lock() noexcept {} 356 | inline void unlock() noexcept {} 357 | }; 358 | 359 | /** 360 | * A spin mutex that yields, mostly for use in benchmarks and scenarii that invoke 361 | * slots at a very high pace. 362 | * One should almost always prefer a standard mutex over this. 363 | */ 364 | struct spin_mutex { 365 | spin_mutex() noexcept = default; 366 | ~spin_mutex() noexcept = default; 367 | spin_mutex(spin_mutex const&) = delete; 368 | spin_mutex& operator=(const spin_mutex &) = delete; 369 | spin_mutex(spin_mutex &&) = delete; 370 | spin_mutex& operator=(spin_mutex &&) = delete; 371 | 372 | void lock() noexcept { 373 | while (true) { 374 | while (!state.load(std::memory_order_relaxed)) { 375 | std::this_thread::yield(); 376 | } 377 | 378 | if (try_lock()) { 379 | break; 380 | } 381 | } 382 | } 383 | 384 | bool try_lock() noexcept { 385 | return state.exchange(false, std::memory_order_acquire); 386 | } 387 | 388 | void unlock() noexcept { 389 | state.store(true, std::memory_order_release); 390 | } 391 | 392 | private: 393 | std::atomic state {true}; 394 | }; 395 | 396 | /** 397 | * A simple copy on write container that will be used to improve slot lists 398 | * access efficiency in a multithreaded context. 399 | */ 400 | template 401 | class copy_on_write { 402 | struct payload { 403 | payload() = default; 404 | 405 | template 406 | explicit payload(Args && ...args) 407 | : value(std::forward(args)...) 408 | {} 409 | 410 | std::atomic count{1}; 411 | T value; 412 | }; 413 | 414 | public: 415 | using element_type = T; 416 | 417 | copy_on_write() 418 | : m_data(new payload) 419 | {} 420 | 421 | template 422 | explicit copy_on_write(U && x, std::enable_if_t, 423 | copy_on_write>::value>* = nullptr) 424 | : m_data(new payload(std::forward(x))) 425 | {} 426 | 427 | copy_on_write(const copy_on_write &x) noexcept 428 | : m_data(x.m_data) 429 | { 430 | ++m_data->count; 431 | } 432 | 433 | copy_on_write(copy_on_write && x) noexcept 434 | : m_data(x.m_data) 435 | { 436 | x.m_data = nullptr; 437 | } 438 | 439 | ~copy_on_write() { 440 | if (m_data && (--m_data->count == 0)) { 441 | delete m_data; 442 | } 443 | } 444 | 445 | copy_on_write& operator=(const copy_on_write &x) noexcept { 446 | if (&x != this) { 447 | *this = copy_on_write(x); 448 | } 449 | return *this; 450 | } 451 | 452 | copy_on_write& operator=(copy_on_write && x) noexcept { 453 | auto tmp = std::move(x); 454 | swap(*this, tmp); 455 | return *this; 456 | } 457 | 458 | element_type& write() { 459 | if (!unique()) { 460 | *this = copy_on_write(read()); 461 | } 462 | return m_data->value; 463 | } 464 | 465 | const element_type& read() const noexcept { 466 | return m_data->value; 467 | } 468 | 469 | friend inline void swap(copy_on_write &x, copy_on_write &y) noexcept { 470 | using std::swap; 471 | swap(x.m_data, y.m_data); 472 | } 473 | 474 | private: 475 | bool unique() const noexcept { 476 | return m_data->count == 1; 477 | } 478 | 479 | private: 480 | payload *m_data; 481 | }; 482 | 483 | /** 484 | * Specializations for thread-safe code path 485 | */ 486 | template 487 | const T& cow_read(const T &v) { 488 | return v; 489 | } 490 | 491 | template 492 | const T& cow_read(copy_on_write &v) { 493 | return v.read(); 494 | } 495 | 496 | template 497 | T& cow_write(T &v) { 498 | return v; 499 | } 500 | 501 | template 502 | T& cow_write(copy_on_write &v) { 503 | return v.write(); 504 | } 505 | 506 | /** 507 | * std::make_shared instantiates a lot a templates, and makes both compilation time 508 | * and executable size far bigger than they need to be. We offer a make_shared 509 | * equivalent that will avoid most instantiations with the following tradeoffs: 510 | * - Not exception safe, 511 | * - Allocates a separate control block, and will thus make the code slower. 512 | */ 513 | #ifdef SIGSLOT_REDUCE_COMPILE_TIME 514 | template 515 | inline std::shared_ptr make_shared(Arg && ... arg) { 516 | return std::shared_ptr(static_cast(new D(std::forward(arg)...))); 517 | } 518 | #else 519 | template 520 | inline std::shared_ptr make_shared(Arg && ... arg) { 521 | return std::static_pointer_cast(std::make_shared(std::forward(arg)...)); 522 | } 523 | #endif 524 | 525 | /* slot_state holds slot type independent state, to be used to interact with 526 | * slots indirectly through connection and scoped_connection objects. 527 | */ 528 | class slot_state { 529 | public: 530 | constexpr slot_state(group_id gid) noexcept 531 | : m_index(0) 532 | , m_group(gid) 533 | , m_connected(true) 534 | , m_blocked(false) 535 | {} 536 | 537 | virtual ~slot_state() = default; 538 | 539 | virtual bool connected() const noexcept { return m_connected; } 540 | 541 | bool disconnect() noexcept { 542 | bool ret = m_connected.exchange(false); 543 | if (ret) { 544 | do_disconnect(); 545 | } 546 | return ret; 547 | } 548 | 549 | bool blocked() const noexcept { return m_blocked.load(); } 550 | void block() noexcept { m_blocked.store(true); } 551 | void unblock() noexcept { m_blocked.store(false); } 552 | 553 | protected: 554 | virtual void do_disconnect() {} 555 | 556 | auto index() const { 557 | return m_index; 558 | } 559 | 560 | auto& index() { 561 | return m_index; 562 | } 563 | 564 | group_id group() const { 565 | return m_group; 566 | } 567 | 568 | private: 569 | template 570 | friend class ::sigslot::signal_base; 571 | 572 | std::size_t m_index; // index into the array of slot pointers inside the signal 573 | const group_id m_group; // slot group this slot belongs to 574 | std::atomic m_connected; 575 | std::atomic m_blocked; 576 | }; 577 | 578 | } // namespace detail 579 | 580 | /** 581 | * connection_blocker is a RAII object that blocks a connection until destruction 582 | */ 583 | class connection_blocker { 584 | public: 585 | connection_blocker() = default; 586 | ~connection_blocker() noexcept { release(); } 587 | 588 | connection_blocker(const connection_blocker &) = delete; 589 | connection_blocker & operator=(const connection_blocker &) = delete; 590 | 591 | connection_blocker(connection_blocker && o) noexcept 592 | : m_state{std::move(o.m_state)} 593 | {} 594 | 595 | connection_blocker & operator=(connection_blocker && o) noexcept { 596 | release(); 597 | m_state.swap(o.m_state); 598 | return *this; 599 | } 600 | 601 | private: 602 | friend class connection; 603 | explicit connection_blocker(std::weak_ptr s) noexcept 604 | : m_state{std::move(s)} 605 | { 606 | if (auto d = m_state.lock()) { 607 | d->block(); 608 | } 609 | } 610 | 611 | void release() noexcept { 612 | if (auto d = m_state.lock()) { 613 | d->unblock(); 614 | } 615 | } 616 | 617 | private: 618 | std::weak_ptr m_state; 619 | }; 620 | 621 | 622 | /** 623 | * A connection object allows interaction with an ongoing slot connection 624 | * 625 | * It allows common actions such as connection blocking and disconnection. 626 | * Note that connection is not a RAII object, one does not need to hold one 627 | * such object to keep the signal-slot connection alive. 628 | */ 629 | class connection { 630 | public: 631 | connection() = default; 632 | virtual ~connection() = default; 633 | 634 | connection(const connection &) noexcept = default; 635 | connection & operator=(const connection &) noexcept = default; 636 | connection(connection &&) noexcept = default; 637 | connection & operator=(connection &&) noexcept = default; 638 | 639 | bool valid() const noexcept { 640 | return !m_state.expired(); 641 | } 642 | 643 | bool connected() const noexcept { 644 | const auto d = m_state.lock(); 645 | return d && d->connected(); 646 | } 647 | 648 | bool disconnect() noexcept { 649 | auto d = m_state.lock(); 650 | return d && d->disconnect(); 651 | } 652 | 653 | bool blocked() const noexcept { 654 | const auto d = m_state.lock(); 655 | return d && d->blocked(); 656 | } 657 | 658 | void block() noexcept { 659 | if (auto d = m_state.lock()) { 660 | d->block(); 661 | } 662 | } 663 | 664 | void unblock() noexcept { 665 | if (auto d = m_state.lock()) { 666 | d->unblock(); 667 | } 668 | } 669 | 670 | connection_blocker blocker() const noexcept { 671 | return connection_blocker{m_state}; 672 | } 673 | 674 | protected: 675 | template friend class signal_base; 676 | explicit connection(std::weak_ptr s) noexcept 677 | : m_state{std::move(s)} 678 | {} 679 | 680 | protected: 681 | std::weak_ptr m_state; 682 | }; 683 | 684 | /** 685 | * scoped_connection is a RAII version of connection 686 | * It disconnects the slot from the signal upon destruction. 687 | */ 688 | class scoped_connection final : public connection { 689 | public: 690 | scoped_connection() = default; 691 | ~scoped_connection() override { 692 | disconnect(); 693 | } 694 | 695 | /*implicit*/ scoped_connection(const connection &c) noexcept : connection(c) {} 696 | /*implicit*/ scoped_connection(connection &&c) noexcept : connection(std::move(c)) {} 697 | 698 | scoped_connection(const scoped_connection &) noexcept = delete; 699 | scoped_connection & operator=(const scoped_connection &) noexcept = delete; 700 | 701 | scoped_connection(scoped_connection && o) noexcept 702 | : connection{std::move(o.m_state)} 703 | {} 704 | 705 | scoped_connection & operator=(scoped_connection && o) noexcept { 706 | disconnect(); 707 | m_state.swap(o.m_state); 708 | return *this; 709 | } 710 | 711 | private: 712 | template friend class signal_base; 713 | explicit scoped_connection(std::weak_ptr s) noexcept 714 | : connection{std::move(s)} 715 | {} 716 | }; 717 | 718 | /** 719 | * Observer is a base class for intrusive lifetime tracking of objects. 720 | * 721 | * This is an alternative to trackable pointers, such as std::shared_ptr, 722 | * and manual connection management by keeping connection objects in scope. 723 | * Deriving from this class allows automatic disconnection of all the slots 724 | * connected to any signal when an instance is destroyed. 725 | */ 726 | template 727 | struct observer_base : private detail::observer_type { 728 | virtual ~observer_base() = default; 729 | 730 | protected: 731 | /** 732 | * Disconnect all signals connected to this object. 733 | * 734 | * To avoid invocation of slots on a semi-destructed instance, which may happen 735 | * in multi-threaded contexts, derived classes should call this method in their 736 | * destructor. This will ensure proper disconnection prior to the destruction. 737 | */ 738 | void disconnect_all() { 739 | std::unique_lock _{m_mutex}; 740 | m_connections.clear(); 741 | } 742 | 743 | private: 744 | template 745 | friend class signal_base; 746 | 747 | void add_connection(connection conn) { 748 | std::unique_lock _{m_mutex}; 749 | m_connections.emplace_back(std::move(conn)); 750 | } 751 | 752 | Lockable m_mutex; 753 | std::vector m_connections; 754 | }; 755 | 756 | /** 757 | * Specialization of observer_base to be used in single threaded contexts. 758 | */ 759 | using observer_st = observer_base; 760 | 761 | /** 762 | * Specialization of observer_base to be used in multi-threaded contexts. 763 | */ 764 | using observer = observer_base; 765 | 766 | 767 | namespace detail { 768 | 769 | // interface for cleanable objects, used to cleanup disconnected slots 770 | struct cleanable { 771 | virtual ~cleanable() = default; 772 | virtual void clean(slot_state *) = 0; 773 | }; 774 | 775 | template 776 | class slot_base; 777 | 778 | template 779 | using slot_ptr = std::shared_ptr>; 780 | 781 | 782 | /* A base class for slot objects. This base type only depends on slot argument 783 | * types, it will be used as an element in an intrusive singly-linked list of 784 | * slots, hence the public next member. 785 | */ 786 | template 787 | class slot_base : public slot_state { 788 | public: 789 | using base_types = trait::typelist; 790 | 791 | explicit slot_base(cleanable &c, group_id gid) 792 | : slot_state(gid) 793 | , cleaner(c) 794 | {} 795 | ~slot_base() override = default; 796 | 797 | // method effectively responsible for calling the "slot" function with 798 | // supplied arguments whenever emission happens. 799 | virtual void call_slot(Args...) = 0; 800 | 801 | template 802 | void operator()(U && ...u) { 803 | if (slot_state::connected() && !slot_state::blocked()) { 804 | call_slot(std::forward(u)...); 805 | } 806 | } 807 | 808 | // check if we are storing callable c 809 | template 810 | bool has_callable(const C &c) const { 811 | auto cp = get_function_ptr(c); 812 | auto p = get_callable(); 813 | return cp && p && cp == p; 814 | } 815 | 816 | template 817 | std::enable_if_t::must_check_object, bool> 818 | has_full_callable(const C &c) const { 819 | return has_callable(c) && check_class_type>(); 820 | } 821 | 822 | template 823 | std::enable_if_t::must_check_object, bool> 824 | has_full_callable(const C &c) const { 825 | return has_callable(c); 826 | } 827 | 828 | // check if we are storing object o 829 | template 830 | bool has_object(const O &o) const { 831 | return get_object() == get_object_ptr(o); 832 | } 833 | 834 | protected: 835 | void do_disconnect() final { 836 | cleaner.clean(this); 837 | } 838 | 839 | // retieve a pointer to the object embedded in the slot 840 | virtual obj_ptr get_object() const noexcept { 841 | return nullptr; 842 | } 843 | 844 | // retieve a pointer to the callable embedded in the slot 845 | virtual func_ptr get_callable() const noexcept { 846 | return get_function_ptr(nullptr); 847 | } 848 | 849 | #ifdef SIGSLOT_RTTI_ENABLED 850 | // retieve a pointer to the callable embedded in the slot 851 | virtual const std::type_info& get_callable_type() const noexcept { 852 | return typeid(nullptr); 853 | } 854 | 855 | private: 856 | template 857 | bool check_class_type() const { 858 | return typeid(U) == get_callable_type(); 859 | } 860 | 861 | #else 862 | template 863 | bool check_class_type() const { 864 | return false; 865 | } 866 | #endif 867 | 868 | private: 869 | cleanable &cleaner; 870 | }; 871 | 872 | /* 873 | * A slot object holds state information, and a callable to to be called 874 | * whenever the function call operator of its slot_base base class is called. 875 | */ 876 | template 877 | class slot final : public slot_base { 878 | public: 879 | template 880 | constexpr slot(cleanable &c, F && f, Gid gid) 881 | : slot_base(c, gid) 882 | , func{std::forward(f)} {} 883 | 884 | protected: 885 | void call_slot(Args ...args) override { 886 | func(args...); 887 | } 888 | 889 | func_ptr get_callable() const noexcept override { 890 | return get_function_ptr(func); 891 | } 892 | 893 | #ifdef SIGSLOT_RTTI_ENABLED 894 | const std::type_info& get_callable_type() const noexcept override { 895 | return typeid(func); 896 | } 897 | #endif 898 | 899 | private: 900 | std::decay_t func; 901 | }; 902 | 903 | /* 904 | * Variation of slot that prepends a connection object to the callable 905 | */ 906 | template 907 | class slot_extended final : public slot_base { 908 | public: 909 | template 910 | constexpr slot_extended(cleanable &c, F && f, group_id gid) 911 | : slot_base(c, gid) 912 | , func{std::forward(f)} {} 913 | 914 | connection conn; 915 | 916 | protected: 917 | void call_slot(Args ...args) override { 918 | func(conn, args...); 919 | } 920 | 921 | func_ptr get_callable() const noexcept override { 922 | return get_function_ptr(func); 923 | } 924 | 925 | #ifdef SIGSLOT_RTTI_ENABLED 926 | const std::type_info& get_callable_type() const noexcept override { 927 | return typeid(func); 928 | } 929 | #endif 930 | 931 | private: 932 | std::decay_t func; 933 | }; 934 | 935 | /* 936 | * A slot object holds state information, an object and a pointer over member 937 | * function to be called whenever the function call operator of its slot_base 938 | * base class is called. 939 | */ 940 | template 941 | class slot_pmf final : public slot_base { 942 | public: 943 | template 944 | constexpr slot_pmf(cleanable &c, F && f, P && p, group_id gid) 945 | : slot_base(c, gid) 946 | , pmf{std::forward(f)} 947 | , ptr{std::forward

(p)} {} 948 | 949 | protected: 950 | void call_slot(Args ...args) override { 951 | ((*ptr).*pmf)(args...); 952 | } 953 | 954 | func_ptr get_callable() const noexcept override { 955 | return get_function_ptr(pmf); 956 | } 957 | 958 | obj_ptr get_object() const noexcept override { 959 | return get_object_ptr(ptr); 960 | } 961 | 962 | #ifdef SIGSLOT_RTTI_ENABLED 963 | const std::type_info& get_callable_type() const noexcept override { 964 | return typeid(pmf); 965 | } 966 | #endif 967 | 968 | private: 969 | std::decay_t pmf; 970 | std::decay_t ptr; 971 | }; 972 | 973 | /* 974 | * Variation of slot that prepends a connection object to the callable 975 | */ 976 | template 977 | class slot_pmf_extended final : public slot_base { 978 | public: 979 | template 980 | constexpr slot_pmf_extended(cleanable &c, F && f, P && p, group_id gid) 981 | : slot_base(c, gid) 982 | , pmf{std::forward(f)} 983 | , ptr{std::forward

(p)} {} 984 | 985 | connection conn; 986 | 987 | protected: 988 | void call_slot(Args ...args) override { 989 | ((*ptr).*pmf)(conn, args...); 990 | } 991 | 992 | func_ptr get_callable() const noexcept override { 993 | return get_function_ptr(pmf); 994 | } 995 | obj_ptr get_object() const noexcept override { 996 | return get_object_ptr(ptr); 997 | } 998 | 999 | #ifdef SIGSLOT_RTTI_ENABLED 1000 | const std::type_info& get_callable_type() const noexcept override { 1001 | return typeid(pmf); 1002 | } 1003 | #endif 1004 | 1005 | private: 1006 | std::decay_t pmf; 1007 | std::decay_t ptr; 1008 | }; 1009 | 1010 | /* 1011 | * An implementation of a slot that tracks the life of a supplied object 1012 | * through a weak pointer in order to automatically disconnect the slot 1013 | * on said object destruction. 1014 | */ 1015 | template 1016 | class slot_tracked final : public slot_base { 1017 | public: 1018 | template 1019 | constexpr slot_tracked(cleanable &c, F && f, P && p, group_id gid) 1020 | : slot_base(c, gid) 1021 | , func{std::forward(f)} 1022 | , ptr{std::forward

(p)} 1023 | {} 1024 | 1025 | bool connected() const noexcept override { 1026 | return !ptr.expired() && slot_state::connected(); 1027 | } 1028 | 1029 | protected: 1030 | void call_slot(Args ...args) override { 1031 | auto sp = ptr.lock(); 1032 | if (!sp) { 1033 | slot_state::disconnect(); 1034 | return; 1035 | } 1036 | if (slot_state::connected()) { 1037 | func(args...); 1038 | } 1039 | } 1040 | 1041 | func_ptr get_callable() const noexcept override { 1042 | return get_function_ptr(func); 1043 | } 1044 | 1045 | obj_ptr get_object() const noexcept override { 1046 | return get_object_ptr(ptr); 1047 | } 1048 | 1049 | #ifdef SIGSLOT_RTTI_ENABLED 1050 | const std::type_info& get_callable_type() const noexcept override { 1051 | return typeid(func); 1052 | } 1053 | #endif 1054 | 1055 | private: 1056 | std::decay_t func; 1057 | std::decay_t ptr; 1058 | }; 1059 | 1060 | /* 1061 | * An implementation of a slot as a pointer over member function, that tracks 1062 | * the life of a supplied object through a weak pointer in order to automatically 1063 | * disconnect the slot on said object destruction. 1064 | */ 1065 | template 1066 | class slot_pmf_tracked final : public slot_base { 1067 | public: 1068 | template 1069 | constexpr slot_pmf_tracked(cleanable &c, F && f, P && p, group_id gid) 1070 | : slot_base(c, gid) 1071 | , pmf{std::forward(f)} 1072 | , ptr{std::forward

(p)} 1073 | {} 1074 | 1075 | bool connected() const noexcept override { 1076 | return !ptr.expired() && slot_state::connected(); 1077 | } 1078 | 1079 | protected: 1080 | void call_slot(Args ...args) override { 1081 | auto sp = ptr.lock(); 1082 | if (!sp) { 1083 | slot_state::disconnect(); 1084 | return; 1085 | } 1086 | if (slot_state::connected()) { 1087 | ((*sp).*pmf)(args...); 1088 | } 1089 | } 1090 | 1091 | func_ptr get_callable() const noexcept override { 1092 | return get_function_ptr(pmf); 1093 | } 1094 | 1095 | obj_ptr get_object() const noexcept override { 1096 | return get_object_ptr(ptr); 1097 | } 1098 | 1099 | #ifdef SIGSLOT_RTTI_ENABLED 1100 | const std::type_info& get_callable_type() const noexcept override { 1101 | return typeid(pmf); 1102 | } 1103 | #endif 1104 | 1105 | private: 1106 | std::decay_t pmf; 1107 | std::decay_t ptr; 1108 | }; 1109 | 1110 | } // namespace detail 1111 | 1112 | 1113 | /** 1114 | * signal_base is an implementation of the observer pattern, through the use 1115 | * of an emitting object and slots that are connected to the signal and called 1116 | * with supplied arguments when a signal is emitted. 1117 | * 1118 | * signal_base is the general implementation, whose locking policy must be 1119 | * set in order to decide thread safety guarantees. signal and signal_st 1120 | * are partial specializations for multi-threaded and single-threaded use. 1121 | * 1122 | * It does not allow slots to return a value. 1123 | * 1124 | * Slot execution order can be constrained by assigning group ids to the slots. 1125 | * The execution order of slots in a same group is unspecified and should not be 1126 | * relied upon, however groups are executed in ascending group ids order. When 1127 | * the group id of a slot is not set, it is assigned to the group 0. Group ids 1128 | * can have any value in the range of signed 32 bit integers. 1129 | * 1130 | * @tparam Lockable a lock type to decide the lock policy 1131 | * @tparam T... the argument types of the emitting and slots functions. 1132 | */ 1133 | template 1134 | class signal_base final : public detail::cleanable { 1135 | template 1136 | using is_thread_safe = std::integral_constant::value>; 1137 | 1138 | template 1139 | using cow_type = std::conditional_t::value, 1140 | detail::copy_on_write, U>; 1141 | 1142 | template 1143 | using cow_copy_type = std::conditional_t::value, 1144 | detail::copy_on_write, const U&>; 1145 | 1146 | using lock_type = std::unique_lock; 1147 | using slot_base = detail::slot_base; 1148 | using slot_ptr = detail::slot_ptr; 1149 | using slots_type = std::vector; 1150 | struct group_type { slots_type slts; group_id gid; }; 1151 | using list_type = std::vector; // kept ordered by ascending gid 1152 | 1153 | public: 1154 | using arg_list = trait::typelist; 1155 | using ext_arg_list = trait::typelist; 1156 | 1157 | signal_base() noexcept : m_block(false) {} 1158 | ~signal_base() override { 1159 | disconnect_all(); 1160 | } 1161 | 1162 | signal_base(const signal_base&) = delete; 1163 | signal_base & operator=(const signal_base&) = delete; 1164 | 1165 | signal_base(signal_base && o) /* not noexcept */ 1166 | : m_block{o.m_block.load()} 1167 | { 1168 | lock_type lock(o.m_mutex); 1169 | using std::swap; 1170 | swap(m_slots, o.m_slots); 1171 | } 1172 | 1173 | signal_base & operator=(signal_base && o) /* not noexcept */ { 1174 | lock_type lock1(m_mutex, std::defer_lock); 1175 | lock_type lock2(o.m_mutex, std::defer_lock); 1176 | std::lock(lock1, lock2); 1177 | 1178 | using std::swap; 1179 | swap(m_slots, o.m_slots); 1180 | m_block.store(o.m_block.exchange(m_block.load())); 1181 | return *this; 1182 | } 1183 | 1184 | /** 1185 | * Emit a signal 1186 | * 1187 | * Effect: All non blocked and connected slot functions will be called 1188 | * with supplied arguments. 1189 | * Safety: With proper locking (see pal::signal), emission can happen from 1190 | * multiple threads simultaneously. The guarantees only apply to the 1191 | * signal object, it does not cover thread safety of potentially 1192 | * shared state used in slot functions. 1193 | * 1194 | * @param a... arguments to emit 1195 | */ 1196 | template 1197 | void operator()(U && ...a) { 1198 | if (m_block) { 1199 | return; 1200 | } 1201 | 1202 | // Reference to the slots to execute them out of the lock 1203 | // a copy may occur if another thread writes to it. 1204 | cow_copy_type ref = slots_reference(); 1205 | 1206 | for (const auto &group : detail::cow_read(ref)) { 1207 | for (const auto &s : group.slts) { 1208 | s->operator()(a...); 1209 | } 1210 | } 1211 | } 1212 | 1213 | /** 1214 | * Connect a callable of compatible arguments 1215 | * 1216 | * Effect: Creates and stores a new slot responsible for executing the 1217 | * supplied callable for every subsequent signal emission. 1218 | * Safety: Thread-safety depends on locking policy. 1219 | * 1220 | * @param c a callable 1221 | * @param gid an identifier that can be used to order slot execution 1222 | * @return a connection object that can be used to interact with the slot 1223 | */ 1224 | template 1225 | std::enable_if_t, connection> 1226 | connect(Callable && c, group_id gid = 0) { 1227 | using slot_t = detail::slot; 1228 | auto s = make_slot(std::forward(c), gid); 1229 | connection conn(s); 1230 | add_slot(std::move(s)); 1231 | return conn; 1232 | } 1233 | 1234 | /** 1235 | * Connect a callable with an additional connection argument 1236 | * 1237 | * The callable's first argument must be of type connection. This overload 1238 | * the callable to manage it's own connection through this argument. 1239 | * 1240 | * @param c a callable 1241 | * @param gid an identifier that can be used to order slot execution 1242 | * @return a connection object that can be used to interact with the slot 1243 | */ 1244 | template 1245 | std::enable_if_t, connection> 1246 | connect_extended(Callable && c, group_id gid = 0) { 1247 | using slot_t = detail::slot_extended; 1248 | auto s = make_slot(std::forward(c), gid); 1249 | connection conn(s); 1250 | std::static_pointer_cast(s)->conn = conn; 1251 | add_slot(std::move(s)); 1252 | return conn; 1253 | } 1254 | 1255 | /** 1256 | * Overload of connect for pointers over member functions derived from 1257 | * observer 1258 | * 1259 | * @param pmf a pointer over member function 1260 | * @param ptr an object pointer derived from observer 1261 | * @param gid an identifier that can be used to order slot execution 1262 | * @return a connection object that can be used to interact with the slot 1263 | */ 1264 | template 1265 | std::enable_if_t && 1266 | trait::is_observer_v, connection> 1267 | connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1268 | using slot_t = detail::slot_pmf; 1269 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1270 | connection conn(s); 1271 | add_slot(std::move(s)); 1272 | ptr->add_connection(conn); 1273 | return conn; 1274 | } 1275 | 1276 | /** 1277 | * Overload of connect for pointers over member functions 1278 | * 1279 | * @param pmf a pointer over member function 1280 | * @param ptr an object pointer 1281 | * @param gid an identifier that can be used to order slot execution 1282 | * @return a connection object that can be used to interact with the slot 1283 | */ 1284 | template 1285 | std::enable_if_t && 1286 | !trait::is_observer_v && 1287 | !trait::is_weak_ptr_compatible_v, connection> 1288 | connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1289 | using slot_t = detail::slot_pmf; 1290 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1291 | connection conn(s); 1292 | add_slot(std::move(s)); 1293 | return conn; 1294 | } 1295 | 1296 | /** 1297 | * Overload of connect for pointer over member functions and 1298 | * 1299 | * @param pmf a pointer over member function 1300 | * @param ptr an object pointer 1301 | * @param gid an identifier that can be used to order slot execution 1302 | * @return a connection object that can be used to interact with the slot 1303 | */ 1304 | template 1305 | std::enable_if_t && 1306 | !trait::is_weak_ptr_compatible_v, connection> 1307 | connect_extended(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1308 | using slot_t = detail::slot_pmf_extended; 1309 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1310 | connection conn(s); 1311 | std::static_pointer_cast(s)->conn = conn; 1312 | add_slot(std::move(s)); 1313 | return conn; 1314 | } 1315 | 1316 | /** 1317 | * Overload of connect for lifetime object tracking and automatic disconnection 1318 | * 1319 | * Ptr must be convertible to an object following a loose form of weak pointer 1320 | * concept, by implementing the ADL-detected conversion function to_weak(). 1321 | * 1322 | * This overload covers the case of a pointer over member function and a 1323 | * trackable pointer of that class. 1324 | * 1325 | * Note: only weak references are stored, a slot does not extend the lifetime 1326 | * of a suppied object. 1327 | * 1328 | * @param pmf a pointer over member function 1329 | * @param ptr a trackable object pointer 1330 | * @param gid an identifier that can be used to order slot execution 1331 | * @return a connection object that can be used to interact with the slot 1332 | */ 1333 | template 1334 | std::enable_if_t && 1335 | trait::is_weak_ptr_compatible_v, connection> 1336 | connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1337 | using trait::to_weak; 1338 | auto w = to_weak(std::forward(ptr)); 1339 | using slot_t = detail::slot_pmf_tracked; 1340 | auto s = make_slot(std::forward(pmf), w, gid); 1341 | connection conn(s); 1342 | add_slot(std::move(s)); 1343 | return conn; 1344 | } 1345 | 1346 | /** 1347 | * Overload of connect for lifetime object tracking and automatic disconnection 1348 | * 1349 | * Trackable must be convertible to an object following a loose form of weak 1350 | * pointer concept, by implementing the ADL-detected conversion function to_weak(). 1351 | * 1352 | * This overload covers the case of a standalone callable and unrelated trackable 1353 | * object. 1354 | * 1355 | * Note: only weak references are stored, a slot does not extend the lifetime 1356 | * of a suppied object. 1357 | * 1358 | * @param c a callable 1359 | * @param ptr a trackable object pointer 1360 | * @param gid an identifier that can be used to order slot execution 1361 | * @return a connection object that can be used to interact with the slot 1362 | */ 1363 | template 1364 | std::enable_if_t && 1365 | trait::is_weak_ptr_compatible_v, connection> 1366 | connect(Callable && c, Trackable && ptr, group_id gid = 0) { 1367 | using trait::to_weak; 1368 | auto w = to_weak(std::forward(ptr)); 1369 | using slot_t = detail::slot_tracked; 1370 | auto s = make_slot(std::forward(c), w, gid); 1371 | connection conn(s); 1372 | add_slot(std::move(s)); 1373 | return conn; 1374 | } 1375 | 1376 | /** 1377 | * Creates a connection whose duration is tied to the return object 1378 | * Use the same semantics as connect 1379 | */ 1380 | template 1381 | scoped_connection connect_scoped(CallArgs && ...args) { 1382 | return connect(std::forward(args)...); 1383 | } 1384 | 1385 | /** 1386 | * Disconnect slots bound to a callable 1387 | * 1388 | * Effect: Disconnects all the slots bound to the callable in argument. 1389 | * Safety: Thread-safety depends on locking policy. 1390 | * 1391 | * If the callable is a free or static member function, this overload is always 1392 | * available. However, RTTI is needed for it to work for pointer to member 1393 | * functions, function objects or and (references to) lambdas, because the 1394 | * C++ spec does not mandate the pointers to member functions to be unique. 1395 | * 1396 | * @param c a callable 1397 | * @return the number of disconnected slots 1398 | */ 1399 | template 1400 | std::enable_if_t<(trait::is_callable_v || 1401 | trait::is_callable_v || 1402 | trait::is_pmf_v) && 1403 | detail::function_traits::is_disconnectable, size_t> 1404 | disconnect(const Callable &c) { 1405 | return disconnect_if([&] (const auto &s) { 1406 | return s->has_full_callable(c); 1407 | }); 1408 | } 1409 | 1410 | /** 1411 | * Disconnect slots bound to this object 1412 | * 1413 | * Effect: Disconnects all the slots bound to the object or tracked object 1414 | * in argument. 1415 | * Safety: Thread-safety depends on locking policy. 1416 | * 1417 | * The object may be a pointer or trackable object. 1418 | * 1419 | * @param obj an object 1420 | * @return the number of disconnected slots 1421 | */ 1422 | template 1423 | std::enable_if_t && 1424 | !trait::is_callable_v && 1425 | !trait::is_pmf_v, size_t> 1426 | disconnect(const Obj &obj) { 1427 | return disconnect_if([&] (const auto &s) { 1428 | return s->has_object(obj); 1429 | }); 1430 | } 1431 | 1432 | /** 1433 | * Disconnect slots bound both to a callable and object 1434 | * 1435 | * Effect: Disconnects all the slots bound to the callable and object in argument. 1436 | * Safety: Thread-safety depends on locking policy. 1437 | * 1438 | * For naked pointers, the Callable is expected to be a pointer over member 1439 | * function. If obj is trackable, any kind of Callable can be used. 1440 | * 1441 | * @param c a callable 1442 | * @param obj an object 1443 | * @return the number of disconnected slots 1444 | */ 1445 | template 1446 | size_t disconnect(const Callable &c, const Obj &obj) { 1447 | return disconnect_if([&] (const auto &s) { 1448 | return s->has_object(obj) && s->has_callable(c); 1449 | }); 1450 | } 1451 | 1452 | /** 1453 | * Disconnect slots in a particular group 1454 | * 1455 | * Effect: Disconnects all the slots in the group id in argument. 1456 | * Safety: Thread-safety depends on locking policy. 1457 | * 1458 | * @param gid a group id 1459 | * @return the number of disconnected slots 1460 | */ 1461 | size_t disconnect(group_id gid) { 1462 | lock_type lock(m_mutex); 1463 | for (auto &group : detail::cow_write(m_slots)) { 1464 | if (group.gid == gid) { 1465 | size_t count = group.slts.size(); 1466 | group.slts.clear(); 1467 | return count; 1468 | } 1469 | } 1470 | return 0; 1471 | } 1472 | 1473 | /** 1474 | * Disconnects all the slots 1475 | * Safety: Thread safety depends on locking policy 1476 | */ 1477 | void disconnect_all() { 1478 | lock_type lock(m_mutex); 1479 | clear(); 1480 | } 1481 | 1482 | /** 1483 | * Blocks signal emission 1484 | * Safety: thread safe 1485 | */ 1486 | void block() noexcept { 1487 | m_block.store(true); 1488 | } 1489 | 1490 | /** 1491 | * Unblocks signal emission 1492 | * Safety: thread safe 1493 | */ 1494 | void unblock() noexcept { 1495 | m_block.store(false); 1496 | } 1497 | 1498 | /** 1499 | * Tests blocking state of signal emission 1500 | */ 1501 | bool blocked() const noexcept { 1502 | return m_block.load(); 1503 | } 1504 | 1505 | /** 1506 | * Get number of connected slots 1507 | * Safety: thread safe 1508 | */ 1509 | size_t slot_count() noexcept { 1510 | cow_copy_type ref = slots_reference(); 1511 | size_t count = 0; 1512 | for (const auto &g : detail::cow_read(ref)) { 1513 | count += g.slts.size(); 1514 | } 1515 | return count; 1516 | } 1517 | 1518 | protected: 1519 | /** 1520 | * remove disconnected slots 1521 | */ 1522 | void clean(detail::slot_state *state) override { 1523 | lock_type lock(m_mutex); 1524 | const auto idx = state->index(); 1525 | const auto gid = state->group(); 1526 | 1527 | // find the group 1528 | for (auto &group : detail::cow_write(m_slots)) { 1529 | if (group.gid == gid) { 1530 | auto &slts = group.slts; 1531 | 1532 | // ensure we have the right slot, in case of concurrent cleaning 1533 | if (idx < slts.size() && slts[idx] && slts[idx].get() == state) { 1534 | std::swap(slts[idx], slts.back()); 1535 | slts[idx]->index() = idx; 1536 | slts.pop_back(); 1537 | } 1538 | 1539 | return; 1540 | } 1541 | } 1542 | } 1543 | 1544 | private: 1545 | // used to get a reference to the slots for reading 1546 | inline cow_copy_type slots_reference() { 1547 | lock_type lock(m_mutex); 1548 | return m_slots; 1549 | } 1550 | 1551 | // create a new slot 1552 | template 1553 | inline auto make_slot(A && ...a) { 1554 | return detail::make_shared(*this, std::forward(a)...); 1555 | } 1556 | 1557 | // add the slot to the list of slots of the right group 1558 | void add_slot(slot_ptr &&s) { 1559 | const group_id gid = s->group(); 1560 | 1561 | lock_type lock(m_mutex); 1562 | auto &groups = detail::cow_write(m_slots); 1563 | 1564 | // find the group 1565 | auto it = groups.begin(); 1566 | while (it != groups.end() && it->gid < gid) { 1567 | it++; 1568 | } 1569 | 1570 | // create a new group if necessary 1571 | if (it == groups.end() || it->gid != gid) { 1572 | it = groups.insert(it, {{}, gid}); 1573 | } 1574 | 1575 | // add the slot 1576 | s->index() = it->slts.size(); 1577 | it->slts.push_back(std::move(s)); 1578 | } 1579 | 1580 | // disconnect a slot if a condition occurs 1581 | template 1582 | size_t disconnect_if(Cond && cond) { 1583 | lock_type lock(m_mutex); 1584 | auto &groups = detail::cow_write(m_slots); 1585 | 1586 | size_t count = 0; 1587 | 1588 | for (auto &group : groups) { 1589 | auto &slts = group.slts; 1590 | size_t i = 0; 1591 | while (i < slts.size()) { 1592 | if (cond(slts[i])) { 1593 | std::swap(slts[i], slts.back()); 1594 | slts[i]->index() = i; 1595 | slts.pop_back(); 1596 | ++count; 1597 | } else { 1598 | ++i; 1599 | } 1600 | } 1601 | } 1602 | 1603 | return count; 1604 | } 1605 | 1606 | // to be called under lock: remove all the slots 1607 | void clear() { 1608 | detail::cow_write(m_slots).clear(); 1609 | } 1610 | 1611 | private: 1612 | Lockable m_mutex; 1613 | cow_type m_slots; 1614 | std::atomic m_block; 1615 | }; 1616 | 1617 | /** 1618 | * Specialization of signal_base to be used in single threaded contexts. 1619 | * Slot connection, disconnection and signal emission are not thread-safe. 1620 | * The performance improvement over the thread-safe variant is not impressive, 1621 | * so this is not very useful. 1622 | */ 1623 | template 1624 | using signal_st = signal_base; 1625 | 1626 | /** 1627 | * Specialization of signal_base to be used in multi-threaded contexts. 1628 | * Slot connection, disconnection and signal emission are thread-safe. 1629 | * 1630 | * Recursive signal emission and emission cycles are supported too. 1631 | */ 1632 | template 1633 | using signal = signal_base; 1634 | 1635 | } // namespace sigslot -------------------------------------------------------------------------------- /src/ga/color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include "ga/math.h" 4 | 5 | #ifdef GA_OPENFRAMEWORKS 6 | #include "ofColor.h" 7 | #endif 8 | 9 | namespace ga { 10 | 11 | // color is simply a vec4 - r,g,b,a 12 | using Color = ga::vec4; 13 | 14 | // TODO: hsb lerp 15 | 16 | #ifdef GA_OPENFRAMEWORKS 17 | inline Color toGa( const ofFloatColor& color ) 18 | { 19 | return Color( color.r, color.g, color.b, color.a ); 20 | } 21 | 22 | inline ofFloatColor toOf( const Color& color ) 23 | { 24 | return ofFloatColor( color.r, color.g, color.b, color.a ); 25 | } 26 | #endif 27 | 28 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/defines.h: -------------------------------------------------------------------------------- 1 | 2 | #define GA_OPENFRAMEWORKS 3 | // #define GA_CINDER 4 | 5 | // This enables glm's old behavior of initializing with non garbage values 6 | // see openFrameworks issue #6530: https://github.com/openframeworks/openFrameworks/issues/6530 7 | #define GLM_FORCE_CTOR_INIT 8 | 9 | // ----------------------- 10 | // MACROS FOR CONVENIENCE: 11 | // ---- DO NOT ADJUST ---- 12 | 13 | #define CLASS_INHERITS( TYPE, BASE ) class TYPE, typename = std::enable_if_t::value> 14 | -------------------------------------------------------------------------------- /src/ga/easing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/math.h" 3 | #include 4 | #include 5 | 6 | namespace ga { 7 | 8 | using EasingFn = std::function; 9 | 10 | namespace ease { 11 | 12 | // 13 | // standardized easing / tweening functions - 14 | // p = f(t) 15 | // 16 | // convert 't' time (percent 0-1) to 'p' position (percent 0-1) 17 | // based on an easing curve - see http://robertpenner.com/easing/penner_chapter7_tweening.pdf 18 | // 19 | 20 | // exponential curves 21 | // ------------------ 22 | // https://github.com/jesusgollonet/ofpennereasing/blob/master/PennerEasing/Expo.cpp 23 | 24 | inline float expoIn( float t ) 25 | { // t is percent (0.-1.) of transition 26 | return ( t == 0.0f ) ? 0.0f : pow( 2.0f, 10.0f * ( t - 1.0f ) ); 27 | } 28 | 29 | inline float expoOut( float t ) 30 | { 31 | return ( t == 1.0f ) ? 1.0f : 1.0f - pow( 2.0f, -10.0f * t ); 32 | } 33 | 34 | inline float expoInOut( float t ) 35 | { 36 | if ( t == 0.0f ) { 37 | return 0.0f; 38 | } 39 | 40 | if ( t == 1.0f ) { 41 | return 1.0f; 42 | } 43 | 44 | if ( ( t /= 0.5f ) < 1.0f ) { 45 | return 0.5f * pow( 2.0f, 10.0f * ( t - 1.0f ) ); 46 | } 47 | 48 | return 0.5f * ( -pow( 2.0f, -10.0f * --t ) + 2.0f ); 49 | } 50 | 51 | // cubic / quadratic curves 52 | // ------------------------ 53 | 54 | inline float cubeInQuadOut( float t ) 55 | { 56 | if ( t < 0.5f ) { 57 | return 4.0f * t * t * t; 58 | } // cubic in 59 | 60 | return ( -2.0f * t * t ) + ( 4.0f * t ) - 1.0f; // quadratic out 61 | } 62 | 63 | // material design curves 64 | // ---------------------- 65 | // https://material.io/design/motion/speed.html#easing 66 | 67 | // Material "standard ease" (decelerate in, accelerate out) 68 | inline float material( float t ) 69 | { 70 | return cubicBezier( t, 0.4f, 0.0f, 0.2f, 1.0f ); 71 | } 72 | 73 | // Material "accelerate" (e.g. exit screen) 74 | inline float materialEnter( float t ) 75 | { 76 | return cubicBezier( t, 0.0f, 0.0f, 0.2f, 1.0f ); 77 | } 78 | 79 | // Material "decelerate" (e.g. enter screen) 80 | inline float materialExit( float t ) 81 | { 82 | return cubicBezier( t, 0.4f, 0.0f, 1.0f, 1.0f ); 83 | } 84 | } // namespace ease 85 | 86 | enum class EaseType 87 | { 88 | LINEAR, 89 | EXPO_IN, 90 | EXPO_OUT, 91 | EXPO_IN_OUT, 92 | CUBE_IN_QUAD_OUT, 93 | MATERIAL, 94 | MATERIAL_ENTER, 95 | MATERIAL_EXIT, 96 | CUSTOM, 97 | // for loops: 98 | NUM_TYPES, 99 | DEFAULT = LINEAR, 100 | }; 101 | 102 | inline std::function easeFn( const EaseType& type ) 103 | { 104 | using namespace ease; 105 | switch ( type ) { 106 | case EaseType::EXPO_IN: 107 | return expoIn; 108 | case EaseType::EXPO_OUT: 109 | return expoOut; 110 | case EaseType::EXPO_IN_OUT: 111 | return expoInOut; 112 | case EaseType::CUBE_IN_QUAD_OUT: 113 | return cubeInQuadOut; 114 | case EaseType::MATERIAL: 115 | return material; 116 | case EaseType::MATERIAL_ENTER: 117 | return materialEnter; 118 | case EaseType::MATERIAL_EXIT: 119 | return materialExit; 120 | case EaseType::LINEAR: 121 | default: 122 | return []( float t ) { return t; }; 123 | } 124 | } 125 | 126 | } // namespace ga 127 | -------------------------------------------------------------------------------- /src/ga/events.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/math.h" 3 | #include "ga/timer.h" 4 | 5 | namespace ga { 6 | 7 | struct Event 8 | { 9 | Event() 10 | : time( ga::Clock::now() ) 11 | , captured( false ) 12 | { 13 | } 14 | 15 | TimePoint time; // time when event occured 16 | bool captured; // flag to stop event propagation 17 | }; 18 | 19 | struct KeyEvent : public Event 20 | { 21 | enum class Type 22 | { 23 | PRESS, 24 | RELEASE 25 | }; 26 | Type type; 27 | int key; 28 | }; 29 | 30 | struct MouseEvent : public Event 31 | { 32 | enum class Type 33 | { 34 | MOVE, 35 | PRESS, 36 | DRAG, 37 | RELEASE, 38 | SCROLL_UP, // wheel 39 | SCROLL_DOWN 40 | }; 41 | Type type; 42 | vec2 position; 43 | int button; // LEFT = 0, RIGHT = 1, CENTER = 2, ... 44 | }; 45 | 46 | struct TouchEvent : public Event 47 | { 48 | enum class Type 49 | { 50 | PRESS, 51 | DRAG, 52 | RELEASE, 53 | CANCEL 54 | }; 55 | 56 | Type type; 57 | ga::vec2 position; // window space 58 | ga::vec2 size; 59 | float angle; // degrees 0-359 60 | float pressure; // 0-1024 61 | 62 | /* 63 | ofTouchEventArgs: 64 | ----------------- 65 | int id; 66 | int time; 67 | int numTouches; 68 | float minoraxis, majoraxis; 69 | float xspeed, yspeed; 70 | float xaccel, yaccel; 71 | */ 72 | }; 73 | 74 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/fbo.h: -------------------------------------------------------------------------------- 1 | #include "ga/defines.h" 2 | 3 | #ifdef GA_OPENFRAMEWORKS 4 | #include "ofFbo.h" 5 | #endif 6 | 7 | namespace ga { 8 | 9 | #ifdef GA_OPENFRAMEWORKS 10 | 11 | using Fbo = ofFbo; 12 | 13 | #endif 14 | } // namespace ga 15 | -------------------------------------------------------------------------------- /src/ga/font.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include "ga/math.h" 4 | #include "ga/resource.h" 5 | #include 6 | #include 7 | 8 | #ifdef GA_OPENFRAMEWORKS 9 | #include "ofTrueTypeFont.h" 10 | #endif 11 | 12 | namespace ga { 13 | 14 | #ifdef GA_OPENFRAMEWORKS 15 | class Font : public ofTrueTypeFont 16 | { 17 | public: 18 | const ofTrueTypeFontSettings& getSettings() 19 | { 20 | return ofTrueTypeFont::settings; 21 | } 22 | float getWidth( const std::string& str ) const 23 | { 24 | return getStringBoundingBox( str, 0, 0 ).width; 25 | } 26 | float getHeight( const std::string& str ) const 27 | { 28 | return getStringBoundingBox( str, 0, 0 ).width; 29 | } 30 | Rect getBounds( const std::string& str ) const 31 | { 32 | return getStringBoundingBox( str, 0, 0 ); 33 | } 34 | }; 35 | 36 | inline bool load( Font& font, const ofTrueTypeFontSettings& settings ) 37 | { 38 | return font.load( settings ); 39 | } 40 | 41 | #endif 42 | // todo: Font for Cinder 43 | 44 | // --------------------------- 45 | // Global Texture Cache 46 | // --------------------------- 47 | using FontCache = ResourceCache; 48 | 49 | inline FontCache& fontCache() 50 | { 51 | static FontCache fontCache; 52 | return fontCache; 53 | } 54 | 55 | // --------------------------- 56 | 57 | struct FontStyle 58 | { 59 | FontStyle() {} 60 | FontStyle( const std::string& file_, int size_, const std::string& name = "" ) 61 | : file( file_ ) 62 | , size( size_ ) 63 | { 64 | #ifdef GA_OPENFRAMEWORKS 65 | ofTrueTypeFontSettings settings( file, size ); 66 | settings.antialiased = true; 67 | settings.contours = false; 68 | settings.simplifyAmt = 0.3f; 69 | settings.dpi = 72; 70 | settings.ranges = { 71 | ofUnicode::Latin1Supplement, 72 | ofUnicode::LatinExtendedAdditional, 73 | ofUnicode::Latin, 74 | ofUnicode::LatinA, 75 | ofUnicode::GeneralPunctuation, 76 | }; 77 | 78 | m_name = name; 79 | if ( m_name.empty() ) { 80 | std::stringstream ss; 81 | ss << file.substr( file.find_last_of( "/\\" ) + 1 ) << "@" << size; 82 | m_name = ss.str(); 83 | } 84 | 85 | if ( !fontCache().load( m_name, settings ) ) { 86 | m_name = ""; 87 | } 88 | #endif 89 | } 90 | 91 | bool loaded() { return !m_name.empty(); } 92 | const std::string& name() { return m_name; } 93 | const std::shared_ptr font() { return fontCache().get( m_name ); } 94 | 95 | std::string file; 96 | int size; 97 | 98 | protected: 99 | std::string m_name; 100 | }; 101 | 102 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/gl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include -------------------------------------------------------------------------------- /src/ga/graph/component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include 4 | 5 | namespace ga { 6 | 7 | class Node; 8 | class Scene; 9 | 10 | class Component 11 | { 12 | public: 13 | Component() = default; 14 | virtual ~Component() = default; 15 | 16 | Component( const Component& other ) 17 | { 18 | // do not copy m_node 19 | } 20 | 21 | Component& operator=(const Component& other) 22 | { 23 | // do not copy m_node 24 | return *this; 25 | } 26 | 27 | std::shared_ptr getNode() const 28 | { 29 | return m_node.lock(); 30 | } 31 | 32 | protected: 33 | friend class Node; 34 | 35 | // called by Node 36 | virtual void update() {} 37 | virtual void draw() {} 38 | virtual void setNode( std::shared_ptr node ) 39 | { 40 | m_node = node; 41 | } 42 | virtual void setScene( std::shared_ptr scene ) {} 43 | 44 | std::weak_ptr m_node; // node owner 45 | }; 46 | 47 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/components/bounds_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/graph/component.h" 3 | #include "ga/math.h" 4 | #include 5 | 6 | namespace ga { 7 | 8 | // 3D axis aligned bounding box 9 | class Bounds : public Component 10 | { 11 | public: 12 | vec3 min = vec3( 0.f ); 13 | vec3 max = vec3( 0.f ); 14 | 15 | Bounds() {} 16 | Bounds( vec2 min_, vec2 max_ ) 17 | : min( min_, 0. ) 18 | , max( max_, 0. ) 19 | { 20 | } 21 | Bounds( vec3 min_, vec3 max_ ) 22 | : min( min_ ) 23 | , max( max_ ) 24 | { 25 | } 26 | Bounds( const Rect& rect ) 27 | : min( rect.min(), 0 ) 28 | , max( rect.max(), 0 ) 29 | { 30 | } 31 | Bounds( const std::vector& points ) 32 | { 33 | min = vec3( std::numeric_limits::max() ); 34 | max = vec3( std::numeric_limits::min() ); 35 | for ( auto& pt : points ) { 36 | include( pt ); 37 | } 38 | } 39 | 40 | inline Bounds& include( vec3 pt ) 41 | { 42 | for ( int i = 0; i < 3; ++i ) { 43 | min[i] = std::min( pt[i], min[i] ); 44 | max[i] = std::max( pt[i], max[i] ); 45 | } 46 | return *this; 47 | } 48 | 49 | inline Bounds& translate( const vec3& delta ) 50 | { 51 | min += delta; 52 | max += delta; 53 | } 54 | 55 | inline vec3 center() const 56 | { 57 | return min + ( max - min ) * .5f; 58 | } 59 | 60 | inline vec3 size() const 61 | { 62 | return max - min; 63 | } 64 | 65 | bool contains( const vec3& pos ) const 66 | { 67 | return glm::all( glm::lessThanEqual( min, pos ) ) && glm::all( glm::greaterThanEqual( max, pos ) ); 68 | } 69 | }; 70 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/components/image_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/graph/component.h" 3 | #include "ga/layout.h" 4 | #include "ga/math.h" 5 | #include "ga/texture.h" 6 | 7 | namespace ga { 8 | 9 | class Image : public ga::Component 10 | { 11 | public: 12 | std::string textureName = ""; 13 | ga::Rect bounds2D = { 0, 0, 0, 0 }; 14 | ga::FitMode fitMode = ga::FitMode::NONE; 15 | HorzAlign horzAlign = HorzAlign::LEFT; 16 | VertAlign vertAlign = VertAlign::TOP; 17 | bool crop = false; 18 | 19 | Image() = default; 20 | 21 | Image( const std::string& texName, 22 | const ga::Rect& bounds, 23 | const ga::FitMode& fit, 24 | const HorzAlign& hAlign = HorzAlign::LEFT, 25 | const VertAlign& vAlign = VertAlign::TOP, 26 | const bool& bCrop = false ) 27 | : textureName( texName ) 28 | , bounds2D( bounds ) 29 | , fitMode( fit ) 30 | , horzAlign( hAlign ) 31 | , vertAlign( vAlign ) 32 | , crop( bCrop ) 33 | { 34 | } 35 | 36 | void draw() 37 | { 38 | if ( auto tex = ga::textureCache().get( textureName ) ) { 39 | if ( tex->isAllocated() ) { 40 | ga::vec2 texDims { tex->getWidth(), tex->getHeight() }; 41 | auto texScale = ga::calcScaleToFit( texDims, bounds2D.size(), fitMode ); 42 | auto texSize = texDims * texScale; 43 | auto boundsAnchor = bounds2D.position() + anchor( horzAlign, vertAlign ) * bounds2D.size(); 44 | auto texPos = boundsAnchor - anchor( horzAlign, vertAlign ) * texSize; 45 | 46 | if ( !crop ) { 47 | tex->draw( texPos, texSize.x, texSize.y ); 48 | 49 | } else { 50 | auto cropPtA = glm::max( bounds2D.min(), texPos ); 51 | auto cropPtB = glm::min( bounds2D.max(), texPos + texSize ); 52 | auto cropSz = cropPtB - cropPtA; 53 | auto subPtA = ( cropPtA - texPos ) / texScale; 54 | auto subPtB = ( cropPtB - texPos ) / texScale; 55 | auto subSz = subPtB - subPtA; 56 | tex->drawSubsection( cropPtA.x, cropPtA.y, cropSz.x, cropSz.y, subPtA.x, subPtA.y, subSz.x, subSz.y ); 57 | // tex->drawSubsection(ofRectangle(cropPtA, cropPtB), ofRectangle(subPtA, subPtB)); 58 | } 59 | } else { 60 | // texture is unallocated 61 | // todo: log warning? 62 | } 63 | } else { 64 | // texture not found in cache 65 | // todo: log warning? 66 | } 67 | } 68 | 69 | ga::Rect getDrawBounds() 70 | { 71 | auto drawBounds = bounds2D; 72 | if ( auto img = ga::textureCache().get( textureName ) ) { 73 | if ( img->isAllocated() ) { 74 | ga::vec2 texDims { img->getWidth(), img->getHeight() }; 75 | auto texScale = ga::calcScaleToFit( texDims, bounds2D.size(), fitMode ); 76 | auto texSize = texDims * texScale; 77 | auto boundsAnchor = bounds2D.position() + anchor( horzAlign, vertAlign ) * bounds2D.size(); 78 | auto texPos = boundsAnchor - anchor( horzAlign, vertAlign ) * texSize; 79 | if ( !crop ) { 80 | drawBounds = ga::Rect { texPos, texPos + texSize }; 81 | } else { 82 | drawBounds = ga::Rect { glm::max( bounds2D.min(), texPos ), 83 | glm::min( bounds2D.max(), texPos + texSize ) }; 84 | } 85 | } 86 | } 87 | return drawBounds; 88 | } 89 | }; 90 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/components/paragraph_component.cpp: -------------------------------------------------------------------------------- 1 | #include "ga/graph/components/paragraph_component.h" 2 | #include "ga/render.h" 3 | #include 4 | 5 | namespace ga { 6 | 7 | Paragraph::Paragraph() 8 | : m_offset( 0 ) 9 | , m_size( -1 ) 10 | , m_textColor( 1 ) 11 | , m_hAlignment( HorzAlignment::LEFT ) 12 | , m_vAlignment( VertAlignment::TOP ) 13 | , m_leading( -1 ) 14 | , m_spacing( -1 ) 15 | , m_isMarkdownFormatted( true ) 16 | , m_isLayoutDirty( true ) 17 | , m_cacheToFbo( false ) 18 | { 19 | } 20 | 21 | Paragraph& Paragraph::setFont( std::shared_ptr font, bool resetLeadingAndSpacing ) 22 | { 23 | m_font = font; 24 | if ( font ) { 25 | if ( resetLeadingAndSpacing || getLeading() == -1 ) 26 | setLeading( font->getSize() * 1.25 ); 27 | if ( resetLeadingAndSpacing || getWordSpacing() == -1 ) 28 | setWordSpacing( font->getStringBoundingBox( "-", 0, 0 ).width ); 29 | } 30 | m_isLayoutDirty = true; 31 | return *this; 32 | } 33 | 34 | Paragraph& Paragraph::setBoldFont( std::shared_ptr font ) 35 | { 36 | m_boldFont = font; 37 | m_isLayoutDirty = true; 38 | return *this; 39 | } 40 | 41 | Paragraph& Paragraph::setItalicFont( std::shared_ptr font ) 42 | { 43 | m_italicFont = font; 44 | m_isLayoutDirty = true; 45 | return *this; 46 | } 47 | 48 | Paragraph& Paragraph::setBoldItalicFont( std::shared_ptr font ) 49 | { 50 | m_boldItalicFont = font; 51 | m_isLayoutDirty = true; 52 | return *this; 53 | } 54 | 55 | Paragraph& Paragraph::setText( std::string text ) 56 | { 57 | if ( m_text != text ) { 58 | m_text = text; 59 | m_isLayoutDirty = true; 60 | } 61 | return *this; 62 | } 63 | 64 | Paragraph& Paragraph::setHorizontalAlignment( HorzAlignment alignment ) 65 | { 66 | if ( m_hAlignment != alignment ) { 67 | m_hAlignment = alignment; 68 | m_isLayoutDirty = true; 69 | } 70 | return *this; 71 | } 72 | 73 | Paragraph& Paragraph::setVerticalAlignment( VertAlignment alignment ) 74 | { 75 | if ( m_vAlignment != alignment ) { 76 | m_vAlignment = alignment; 77 | m_isLayoutDirty = true; 78 | } 79 | return *this; 80 | } 81 | 82 | // position offset from 0,0,0 83 | Paragraph& Paragraph::setTextOffset( ga::vec3 textOffset ) 84 | { 85 | m_offset = textOffset; 86 | return *this; 87 | } 88 | 89 | // text box size 90 | 91 | Paragraph& Paragraph::setSize( ga::vec2 size ) 92 | { 93 | if ( size != m_size ) { 94 | m_size = size; 95 | m_isLayoutDirty = true; 96 | } 97 | return *this; 98 | } 99 | 100 | Paragraph& Paragraph::setTextColor( const ga::Color textColor ) 101 | { 102 | m_textColor = textColor; 103 | return *this; 104 | } 105 | 106 | // spacing between lines 107 | 108 | Paragraph& Paragraph::setLeading( float px ) 109 | { 110 | if ( m_leading != px ) { 111 | m_leading = px; 112 | m_isLayoutDirty = true; 113 | } 114 | return *this; 115 | } 116 | 117 | // spacing between words 118 | 119 | Paragraph& Paragraph::setWordSpacing( float px ) 120 | { 121 | if ( m_spacing != px ) { 122 | m_spacing = px; 123 | m_isLayoutDirty = true; 124 | } 125 | return *this; 126 | } 127 | 128 | Paragraph& Paragraph::setIsMarkdownText( bool isMarkdown ) 129 | { 130 | m_isMarkdownFormatted = isMarkdown; 131 | return *this; 132 | } 133 | 134 | Paragraph& Paragraph::setFboCacheEnabled( bool enable ) 135 | { 136 | m_cacheToFbo = enable; 137 | return *this; 138 | } 139 | 140 | void Paragraph::draw() 141 | { 142 | cleanLayout(); 143 | getRenderer().pushMatrix(); 144 | getRenderer().translate( m_offset ); 145 | auto pColor = getRenderer().getGlobalColor(); 146 | getRenderer().setGlobalColor( pColor * m_textColor ); 147 | 148 | drawParagraph(); 149 | 150 | getRenderer().setGlobalColor( pColor ); 151 | getRenderer().popMatrix(); 152 | } 153 | 154 | std::vector Paragraph::getWordBounds() 155 | { 156 | cleanLayout(); 157 | std::vector bounds; 158 | bounds.reserve( m_words.size() ); 159 | float wordBoundaryPadding = 0; 160 | float lineHeight = m_font->getSize(); 161 | for ( auto& word : m_words ) { 162 | bounds.emplace_back( 163 | word.x - wordBoundaryPadding, 164 | word.y - lineHeight - wordBoundaryPadding, 165 | word.w + ( wordBoundaryPadding * 2 ), 166 | lineHeight + ( wordBoundaryPadding * 2 ) ); 167 | } 168 | return bounds; 169 | } 170 | 171 | ga::Rect Paragraph::getParagraphBounds() 172 | { 173 | cleanLayout(); 174 | auto wordBounds = getWordBounds(); 175 | auto minPt = ga::vec2( std::numeric_limits::max() ); 176 | auto maxPt = ga::vec2( std::numeric_limits::min() ); 177 | for ( auto& wordBox : wordBounds ) { 178 | 179 | minPt.x = std::min( minPt.x, wordBox.x ); 180 | minPt.y = std::min( minPt.y, wordBox.y ); 181 | maxPt.x = std::max( maxPt.x, wordBox.x + wordBox.w ); 182 | maxPt.y = std::max( maxPt.y, wordBox.y + wordBox.h ); 183 | } 184 | return ga::Rect( minPt, maxPt ); 185 | } 186 | 187 | int Paragraph::getNumberOfLines() 188 | { 189 | cleanLayout(); 190 | return m_lines.size(); 191 | } 192 | 193 | void Paragraph::cleanLayout() 194 | { 195 | // recalc line breaks 196 | if ( m_isLayoutDirty ) { 197 | if ( m_isMarkdownFormatted ) { 198 | m_styledText = parseMarkdownStyles( m_text ); 199 | } else { 200 | m_styledText.text = m_text; 201 | m_styledText.styles = std::vector( m_text.length(), FontStyle::REGULAR ); 202 | } 203 | calculateTextFlow(); 204 | m_isLayoutDirty = false; 205 | 206 | if ( m_cacheToFbo && m_font ) { 207 | m_fboRect = getParagraphBounds(); 208 | m_fboRect.h += m_font->getSize(); // add extra to account for descenders... todo: fix the word/paragraph bounds? 209 | if ( m_fbo.getWidth() < m_fboRect.w || m_fbo.getHeight() < m_fboRect.h ) { 210 | m_fbo.allocate( m_fboRect.w, m_fboRect.h, GL_RGBA ); 211 | } 212 | 213 | m_fbo.begin(); 214 | auto bg = m_textColor; 215 | bg.a = 0.f; 216 | getRenderer().clear( bg ); 217 | getRenderer().translate( -1.f * m_fboRect.position() ); 218 | drawParagraph( true ); 219 | m_fbo.end(); 220 | } 221 | } 222 | } 223 | 224 | void Paragraph::calcWordRect( Word& word ) 225 | { 226 | if ( !m_font || word.text.empty() ) 227 | return; 228 | if ( word.style.size() < word.text.length() ) { 229 | // append REGULAR font style to style vector to match word length 230 | word.style.insert( word.style.end(), word.text.length() - word.style.size(), FontStyle::REGULAR ); 231 | } 232 | word.x = 0; 233 | word.y = 0; 234 | word.w = 0; 235 | word.h = 0; 236 | 237 | auto font = m_font; 238 | auto boldFont = m_boldFont ? m_boldFont : m_font; 239 | auto italicFont = m_italicFont ? m_italicFont : m_font; 240 | 241 | // split word text into tokens by style vector 242 | 243 | auto findNextStyle = []( const std::vector& styles, size_t currPos, FontStyle currStyle ) -> size_t { 244 | for ( size_t pos = currPos + 1; pos < styles.size(); ++pos ) { 245 | if ( styles[pos] != currStyle ) { 246 | return pos; 247 | } 248 | } 249 | return styles.size(); 250 | }; 251 | 252 | FontStyle currStyle = word.style[0]; 253 | size_t tokenBegin = 0; 254 | size_t tokenEnd = findNextStyle( word.style, 0, currStyle ); 255 | 256 | while ( tokenEnd < word.style.size() ) { 257 | StyledToken token; 258 | token.text = word.text.substr( tokenBegin, tokenEnd ); 259 | token.style = currStyle; 260 | token.bounds = fontForStyle( currStyle )->getBounds( token.text ); 261 | if ( word.styledTokens.size() ) { 262 | token.bounds.x = word.styledTokens.back().bounds.x + word.styledTokens.back().bounds.w; 263 | } 264 | word.styledTokens.push_back( token ); 265 | tokenBegin = tokenEnd; 266 | currStyle = word.style[tokenBegin]; 267 | tokenEnd = findNextStyle( word.style, tokenBegin, currStyle ); 268 | 269 | if ( word.h < token.bounds.h ) 270 | word.h = token.bounds.h; // grow word bounds 271 | } 272 | StyledToken token; 273 | token.text = word.text.substr( tokenBegin, tokenEnd ); 274 | token.style = currStyle; 275 | token.bounds = fontForStyle( currStyle )->getBounds( token.text ); 276 | if ( word.styledTokens.size() ) { 277 | token.bounds.x = word.styledTokens.back().bounds.x + word.styledTokens.back().bounds.w; 278 | } 279 | word.styledTokens.push_back( token ); 280 | // grow word bounds 281 | if ( word.h < token.bounds.h ) 282 | word.h = token.bounds.h; 283 | word.w = word.styledTokens.back().bounds.x + word.styledTokens.back().bounds.w; 284 | } 285 | 286 | Paragraph::StyledText Paragraph::parseMarkdownStyles( const std::string& text ) 287 | { 288 | StyledText styledTextBold, styledText; 289 | 290 | const auto bReg = std::regex { "(\\*\\*|__)(.*?)\\1" }; // **bold** 291 | const auto iReg = std::regex { "(\\*|_)(.+?)\\1" }; // *italics* 292 | 293 | // parse **bold** 294 | std::string str = text; 295 | std::smatch m; 296 | while ( std::regex_search( str, m, bReg ) ) { // find next **bold** block 297 | 298 | styledTextBold.text += m.prefix(); // add text before **bold** block 299 | styledTextBold.styles.insert( styledTextBold.styles.end(), m.prefix().length(), FontStyle::REGULAR ); // mark text before **bold** block as REGULAR 300 | 301 | styledTextBold.text += m[2]; // add **bold** text 302 | styledTextBold.styles.insert( styledTextBold.styles.end(), m[2].length(), FontStyle::BOLD ); // mark **bold** text as BOLD 303 | 304 | str = m.suffix(); // the text after the **bold** block is parsed next 305 | } 306 | if ( !str.empty() ) { // done with parsing the string (no more **bold** blocks) 307 | styledTextBold.text += str; // append the rest of the string 308 | styledTextBold.styles.insert( styledTextBold.styles.end(), str.length(), FontStyle::REGULAR ); // mark as REGULAR 309 | } 310 | 311 | // parse *italics* 312 | str = styledTextBold.text; 313 | auto boldStylesIt = styledTextBold.styles.begin(); 314 | while ( std::regex_search( str, m, iReg ) ) { 315 | 316 | styledText.text += m.prefix(); 317 | styledText.styles.insert( styledText.styles.end(), boldStylesIt, boldStylesIt + m.prefix().length() ); 318 | 319 | styledText.text += m[2]; 320 | // check each char of m[2] for previously parsed BOLD 321 | boldStylesIt += m.prefix().length(); 322 | auto matchBoldStylesIt = boldStylesIt + m[1].length(); // move bold styles iterator to start of m[2] 323 | for ( int i = 0; i < m[2].length(); ++i ) { 324 | auto boldStyle = *( matchBoldStylesIt + i ); 325 | if ( boldStyle == FontStyle::BOLD ) { 326 | styledText.styles.push_back( FontStyle::BOLD_ITALIC ); 327 | } else { 328 | styledText.styles.push_back( FontStyle::ITALIC ); 329 | } 330 | } 331 | 332 | str = m.suffix(); 333 | boldStylesIt += m[0].length(); 334 | } 335 | if ( !str.empty() ) { 336 | styledText.text += str; 337 | styledText.styles.insert( styledText.styles.end(), boldStylesIt, boldStylesIt + str.length() ); 338 | } 339 | return styledText; 340 | } 341 | 342 | } // namespace ga 343 | -------------------------------------------------------------------------------- /src/ga/graph/components/paragraph_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/color.h" 3 | #include "ga/fbo.h" 4 | #include "ga/font.h" 5 | #include "ga/graph/component.h" 6 | #include "ga/math.h" 7 | 8 | namespace ga { 9 | 10 | class Paragraph : public ga::Component 11 | { 12 | public: 13 | enum class HorzAlignment 14 | { 15 | LEFT, 16 | CENTER, 17 | RIGHT 18 | }; 19 | enum class VertAlignment 20 | { 21 | TOP, 22 | MIDDLE, 23 | BOTTOM 24 | }; 25 | 26 | Paragraph(); 27 | 28 | Paragraph& setFont( std::shared_ptr font, bool resetLeadingAndSpacing = true ); 29 | std::shared_ptr getFont() { return m_font; } 30 | 31 | Paragraph& setBoldFont( std::shared_ptr font ); 32 | std::shared_ptr getBoldFont() { return m_boldFont; } 33 | 34 | Paragraph& setItalicFont( std::shared_ptr font ); 35 | std::shared_ptr getItalicFont() { return m_italicFont; } 36 | 37 | Paragraph& setBoldItalicFont( std::shared_ptr font ); 38 | std::shared_ptr getBoldItalicFont() { return m_boldItalicFont; } 39 | 40 | Paragraph& setText( std::string text ); 41 | std::string getText() { return m_text; } 42 | 43 | Paragraph& setHorizontalAlignment( HorzAlignment alignment ); 44 | Paragraph& setVerticalAlignment( VertAlignment alignment ); 45 | HorzAlignment getHorizontalAlignment() { return m_hAlignment; } 46 | VertAlignment getVerticalAlignment() { return m_vAlignment; } 47 | 48 | // position offset from 0,0,0 49 | Paragraph& setTextOffset( ga::vec3 textOffset ); 50 | Paragraph& setTextOffset( ga::vec2 textOffset ) { return setTextOffset( ga::vec3( textOffset, 0 ) ); } 51 | 52 | ga::vec3 getTextOffset() { return m_offset; } 53 | 54 | // text box size 55 | Paragraph& setSize( ga::vec2 size ); 56 | ga::vec2 getSize() { return m_size; } 57 | 58 | Paragraph& setTextColor( const ga::Color textColor ); 59 | ga::Color getTextColor() { return m_textColor; } 60 | 61 | // spacing between lines 62 | Paragraph& setLeading( float px ); 63 | float getLeading() { return m_leading; } 64 | 65 | // spacing between words 66 | Paragraph& setWordSpacing( float px ); 67 | float getWordSpacing() { return m_spacing; } 68 | 69 | // toggle markdown (bold and italic) parsing 70 | Paragraph& setIsMarkdownText( bool isMarkdown ); 71 | bool getIsMarkdownText() const { return m_isMarkdownFormatted; } 72 | 73 | // enable/disable fbo cache 74 | Paragraph& setFboCacheEnabled( bool enable ); 75 | bool getIsFboCacheEnabled() { return m_cacheToFbo; } 76 | 77 | void draw(); 78 | 79 | std::vector getWordBounds(); 80 | ga::Rect getParagraphBounds(); 81 | int getNumberOfLines(); 82 | 83 | void cleanLayout(); 84 | 85 | protected: 86 | enum class FontStyle 87 | { 88 | REGULAR, 89 | BOLD, 90 | ITALIC, 91 | BOLD_ITALIC 92 | }; 93 | 94 | struct StyledText 95 | { 96 | std::string text; 97 | std::vector styles; // char-by-char styles 98 | }; 99 | 100 | struct StyledToken 101 | { 102 | std::string text; 103 | FontStyle style; 104 | ga::Rect bounds; 105 | }; 106 | 107 | struct Word 108 | { 109 | std::string text; 110 | float x, y, w, h; 111 | std::vector style; // char-by-char style 112 | std::vector styledTokens; // bounds per styled part of text 113 | }; 114 | 115 | std::vector m_words; 116 | std::vector> m_lines; 117 | 118 | std::shared_ptr fontForStyle( FontStyle style ) 119 | { 120 | switch ( style ) { 121 | case FontStyle::BOLD: 122 | return m_boldFont ? m_boldFont : m_font; 123 | case FontStyle::ITALIC: 124 | return m_italicFont ? m_italicFont : m_font; 125 | case FontStyle::BOLD_ITALIC: { 126 | if ( m_boldItalicFont ) 127 | return m_boldItalicFont; 128 | if ( m_italicFont ) 129 | return m_italicFont; 130 | if ( m_boldFont ) 131 | return m_boldFont; 132 | return m_font; 133 | } 134 | default: 135 | return m_font; 136 | } 137 | } 138 | 139 | void calcWordRect( Word& word ); 140 | 141 | void drawParagraph( bool forceRedraw = false ) 142 | { 143 | if ( m_font == nullptr || m_text.empty() ) { 144 | return; 145 | } 146 | if ( !m_cacheToFbo || forceRedraw ) { 147 | for ( auto& word : m_words ) { 148 | // m_font->draw( word.text, word.x, word.y ); 149 | for ( auto& token : word.styledTokens ) { 150 | fontForStyle( token.style )->drawString( token.text, word.x + token.bounds.x, word.y ); 151 | } 152 | } 153 | } else { 154 | if ( m_fbo.isAllocated() ) { 155 | m_fbo.draw( m_fboRect.x, m_fboRect.y, m_fbo.getWidth(), m_fbo.getHeight() ); 156 | } 157 | } 158 | } 159 | 160 | StyledText parseMarkdownStyles( const std::string& text ); 161 | 162 | std::string m_text; // raw text, could contain **bold** and *italics* 163 | StyledText m_styledText; // styled text, parsed for **bold** and *italics* 164 | std::shared_ptr m_font, m_boldFont, m_italicFont, m_boldItalicFont; 165 | 166 | glm::vec3 m_offset; 167 | glm::vec2 m_size; 168 | 169 | ga::Color m_textColor; 170 | HorzAlignment m_hAlignment; 171 | VertAlignment m_vAlignment; 172 | float m_leading; 173 | float m_spacing; 174 | bool m_isMarkdownFormatted; // bold and italic formatting 175 | 176 | ga::Fbo m_fbo; 177 | ga::Rect m_fboRect; 178 | 179 | bool m_isLayoutDirty = true; 180 | bool m_cacheToFbo = false; 181 | 182 | // --- below methods are adapted from ofxParagraph --- 183 | /* 184 | Copyright (C) 2014 Stephen Braitsch [http://braitsch.io] 185 | 186 | Permission is hereby granted, free of charge, to any person obtaining a copy 187 | of this software and associated documentation files (the "Software"), to deal 188 | in the Software without restriction, including without limitation the rights 189 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 190 | copies of the Software, and to permit persons to whom the Software is 191 | furnished to do so, subject to the following conditions: 192 | 193 | The above copyright notice and this permission notice shall be included in all 194 | copies or substantial portions of the Software. 195 | 196 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 197 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 198 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 199 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 200 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 201 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 202 | SOFTWARE. 203 | */ 204 | 205 | void calculateTextFlow() 206 | { 207 | if ( !m_font ) 208 | return; 209 | m_words.clear(); 210 | float lineHeight = m_font->getSize(); 211 | std::string str = m_styledText.text; 212 | 213 | // break paragraph into words // 214 | auto spPosition = str.find( " " ); 215 | auto nlPosition = str.find( "\n" ); 216 | auto position = spPosition < nlPosition ? spPosition : nlPosition; 217 | size_t textPos = 0; 218 | 219 | while ( position != std::string::npos ) { 220 | std::string s = str.substr( 0, position ); 221 | //auto rect = m_font->rect( s ); 222 | Word word = { s }; //, rect.getLeft(), rect.getTop(), rect.getWidth(), rect.getHeight() }; 223 | word.style.insert( word.style.end(), m_styledText.styles.begin() + textPos, m_styledText.styles.begin() + textPos + position ); 224 | calcWordRect( word ); 225 | m_words.push_back( word ); 226 | str.erase( 0, position + 1 ); 227 | textPos += position + 1; 228 | 229 | if ( position == nlPosition ) { 230 | word = { "\n", 0.f, 0.f, 0.f, 0.f, { FontStyle::REGULAR } }; 231 | m_words.push_back( word ); // add new line "word" 232 | } 233 | 234 | spPosition = str.find( " " ); 235 | nlPosition = str.find( "\n" ); 236 | position = spPosition < nlPosition ? spPosition : nlPosition; 237 | } 238 | // append the last word // 239 | //auto rect = m_font->rect( str ); 240 | Word word = { str }; //, rect.getLeft(), rect.getTop(), rect.getWidth(), rect.getHeight() }; 241 | word.style.insert( word.style.end(), m_styledText.styles.begin() + textPos, m_styledText.styles.end() ); 242 | calcWordRect( word ); 243 | m_words.push_back( word ); 244 | 245 | // assign words to lines // 246 | float x = 0; 247 | float y = lineHeight * .85; // origin is top of the text, but ofTTF draws from baseline 248 | 249 | m_lines.clear(); 250 | std::vector line; 251 | for ( auto& word : m_words ) { 252 | 253 | if ( word.text == "\n" ) { 254 | // new line character forces new line 255 | 256 | x = 0; 257 | y += m_leading; 258 | word.x = x; 259 | word.y = y; 260 | if ( line.size() > 0 ) { 261 | m_lines.push_back( line ); 262 | } 263 | line.clear(); 264 | 265 | } else if ( x + word.w < m_size.x ) { 266 | // append word to current line 267 | 268 | word.x = x; 269 | word.y = y; 270 | x += word.w + m_spacing; 271 | line.push_back( &word ); 272 | 273 | } else { 274 | // create new line starting with this word 275 | 276 | if ( line.size() > 0 ) 277 | y += m_leading; 278 | word.x = 0; 279 | word.y = y; // account for descender - todo: fix 280 | x = word.w + m_spacing; 281 | if ( line.size() > 0 ) 282 | m_lines.push_back( line ); 283 | line.clear(); 284 | line.push_back( &word ); 285 | } 286 | } 287 | // append the last line // 288 | m_lines.push_back( line ); 289 | // mHeight = mLines.size() * ( lineHeight + leading ); 290 | 291 | // reposition words for right & center aligned paragraphs // 292 | if ( m_hAlignment == HorzAlignment::CENTER ) { 293 | for ( auto& line : m_lines ) { 294 | int lineWidth = 0; 295 | for ( auto* word : line ) { 296 | lineWidth += word->w; 297 | } 298 | lineWidth += m_spacing * ( line.size() - 1 ); 299 | // calculate the amount each word should move over // 300 | float offset = ( m_size.x - lineWidth ) / 2; 301 | for ( auto* word : line ) { 302 | word->x += offset; 303 | } 304 | } 305 | } else if ( m_hAlignment == HorzAlignment::RIGHT ) { 306 | for ( auto& line : m_lines ) { 307 | auto* lword = line.back(); 308 | // calculate the distance the last word in each line is from the right boundary // 309 | int offset = m_size.x - ( lword->x + lword->w ); 310 | for ( auto* word : line ) { 311 | word->x += offset; 312 | } 313 | } 314 | } 315 | 316 | if ( m_vAlignment == VertAlignment::MIDDLE ) { 317 | float pHeight = lineHeight + ( m_lines.size() - 1 ) * m_leading; 318 | float yOffset = ( m_size.y - pHeight ) * .5; 319 | for ( auto& word : m_words ) { 320 | word.y += yOffset; 321 | } 322 | } else if ( m_vAlignment == VertAlignment::BOTTOM ) { 323 | float pHeight = lineHeight + ( m_lines.size() - 1 ) * m_leading; 324 | float yOffset = ( m_size.y - pHeight ); 325 | for ( auto& word : m_words ) { 326 | word.y += yOffset; 327 | } 328 | } 329 | } 330 | }; 331 | } // namespace ga 332 | -------------------------------------------------------------------------------- /src/ga/graph/components/timeline_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/graph/component.h" 3 | #include "ga/signal.h" 4 | #include "ga/tween.h" 5 | #include 6 | 7 | namespace ga { 8 | 9 | // Tween Timeline 10 | class Timeline : public virtual Component 11 | { 12 | public: 13 | void update() override 14 | { 15 | std::vector> deleteKeys; 16 | for ( auto& el : m_tweenRefMap ) { 17 | auto& tween = el.first; 18 | auto& updateFn = el.second; 19 | bool keep = false; 20 | if ( tween ) { 21 | keep = tween->update_(); 22 | if ( updateFn && tween->isStarted_() ) { 23 | updateFn( tween ); 24 | } 25 | if ( tween->isDone_() ) { 26 | tween->end( true ); 27 | } 28 | } 29 | if ( !keep ) { 30 | deleteKeys.push_back( tween ); 31 | } 32 | } 33 | bool wasEmpty = m_tweenRefMap.empty(); 34 | for ( auto& key : deleteKeys ) { 35 | m_tweenRefMap.erase( key ); 36 | } 37 | bool isEmpty = m_tweenRefMap.empty(); 38 | if ( !wasEmpty && isEmpty ) { 39 | onTimelineDone( this ); 40 | } else if ( wasEmpty && !isEmpty ) { 41 | onTimelineStart( this ); 42 | } 43 | } 44 | 45 | template 46 | void add( std::shared_ptr> tween, Fn updateFn = nullptr ) 47 | { 48 | if ( updateFn ) { 49 | m_tweenRefMap[tween] = [updateFn]( std::shared_ptr t ) { updateFn( std::static_pointer_cast>( t )->getValue() ); }; 50 | } else { 51 | m_tweenRefMap[tween] = nullptr; 52 | } 53 | } 54 | 55 | template 56 | void add( std::shared_ptr> tween, std::function updateFn = nullptr ) 57 | { 58 | if ( updateFn ) { 59 | m_tweenRefMap[tween] = [updateFn]( std::shared_ptr t ) { updateFn( std::static_pointer_cast>( t )->getValue() ); }; 60 | } else { 61 | m_tweenRefMap[tween] = nullptr; 62 | } 63 | } 64 | 65 | template 66 | std::shared_ptr> add( const T& startVal, const T& endVal, std::function easeFn, Fn updateFn = nullptr, std::function onDone = nullptr ) 67 | { 68 | auto tween = std::make_shared>( startVal, endVal, easeFn, onDone ); 69 | this->add( tween, std::function( updateFn ) ); 70 | return tween; 71 | } 72 | 73 | template 74 | std::shared_ptr> add( const T& startVal, const T& endVal, std::function easeFn, std::function updateFn = nullptr, std::function onDone = nullptr ) 75 | { 76 | auto tween = std::make_shared>( startVal, endVal, easeFn, onDone ); 77 | this->add( tween, updateFn ); 78 | return tween; 79 | } 80 | 81 | template 82 | bool setTweenUpdate( std::shared_ptr> tween, std::function updateFn ) 83 | { 84 | try { 85 | m_tweenRefMap.at( tween ) = [updateFn]( std::shared_ptr t ) { updateFn( std::static_pointer_cast>( t )->getValue() ); }; 86 | return true; 87 | } catch ( ... ) { 88 | return false; 89 | } 90 | } 91 | 92 | void clear() 93 | { 94 | m_tweenRefMap.clear(); 95 | } 96 | 97 | std::vector> getTweens() 98 | { 99 | std::vector> refs; 100 | refs.reserve( m_tweenRefMap.size() ); 101 | for ( auto& el : m_tweenRefMap ) { 102 | refs.push_back( el.first ); 103 | } 104 | return refs; 105 | } 106 | 107 | bool isActive() 108 | { 109 | return m_tweenRefMap.size(); 110 | } 111 | 112 | ga::Signal onTimelineStart, onTimelineDone; 113 | 114 | protected: 115 | std::map, std::function )>> m_tweenRefMap; 116 | }; 117 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/components/tint_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/color.h" 3 | #include "ga/render.h" 4 | #include "ga/graph/component.h" 5 | 6 | namespace ga { 7 | 8 | class Tint : public Component 9 | { 10 | public: 11 | Color color { 1.f }; 12 | 13 | protected: 14 | friend class Node; 15 | void setNode( std::shared_ptr node ) override 16 | { 17 | Component::setNode( node ); 18 | // hook into node's draw cycle 19 | if ( node ) { 20 | m_connWillDraw = node->onWillDraw.connect( [this]() { 21 | m_pGlobalColor = getRenderer().getGlobalColor(); 22 | getRenderer().setGlobalColor( m_pGlobalColor * color ); 23 | } ); 24 | 25 | m_connDidDraw = node->onDidDraw.connect( [this]() { 26 | getRenderer().setGlobalColor( m_pGlobalColor ); 27 | } ); 28 | } else { 29 | m_connWillDraw.disconnect(); 30 | m_connDidDraw.disconnect(); 31 | } 32 | } 33 | ScopedConnection m_connWillDraw, m_connDidDraw; 34 | Color m_pGlobalColor; 35 | }; 36 | 37 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/components/touchzone_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/graph/component.h" 3 | #include "ga/graph/components/bounds_component.h" 4 | #include "ga/graph/node.h" 5 | #include "ga/graph/scene.h" 6 | #include "ga/signal.h" 7 | namespace ga { 8 | 9 | class TouchZone : public ga::Component 10 | { 11 | public: 12 | enum class State 13 | { 14 | DISABLED = -1, // disabled 15 | INACTIVE = 0, // enabled, no touch 16 | ACTIVE = 1 // enabled, touch 17 | }; 18 | 19 | struct Event 20 | { 21 | enum class Type 22 | { 23 | PRESS, 24 | RELEASE, 25 | DRAG_INTO, 26 | DRAG_INSIDE, 27 | DRAG_OFF 28 | } type; 29 | 30 | TouchZone* touchZone; // source zone (this) 31 | TouchEvent touchEvent; // raw event 32 | vec3 localPosition; // local touch position 33 | vec3 scenePosition; 34 | }; 35 | 36 | void handleTouchEvent( ga::TouchEvent& touchEvt ) 37 | { 38 | if ( m_state == State::DISABLED ) 39 | return; 40 | 41 | if ( touchEvt.captured && m_isCapturingTouch ) { 42 | return; 43 | } 44 | 45 | auto node = getNode(); 46 | if ( !node ) 47 | return; 48 | 49 | // transform touchEvt position into local node position 50 | TouchZone::Event event; 51 | event.touchZone = this; 52 | event.touchEvent = touchEvt; 53 | event.scenePosition = ga::vec3( touchEvt.position.x, touchEvt.position.y, 0 ); 54 | event.localPosition = node->scenePosToLocal( vec3( touchEvt.position, 0 ) ); 55 | bool isInBounds = m_customBoundsTest 56 | ? m_customBoundsTest( event ) 57 | : node->component().contains( event.localPosition ); // todo: 3D bounds / ray cast... 58 | if ( m_boundsInverted ) 59 | isInBounds = !isInBounds; // e.g. for click-off 60 | 61 | switch ( touchEvt.type ) { 62 | case TouchEvent::Type::PRESS: { 63 | if ( isInBounds ) { 64 | if ( m_isCapturingTouch ) { 65 | touchEvt.captured = true; 66 | } 67 | // tap on 68 | setState( State::ACTIVE ); 69 | event.type = TouchZone::Event::Type::PRESS; 70 | onTouchEvent( event ); 71 | } 72 | break; 73 | } 74 | 75 | case ga::TouchEvent::Type::DRAG: { 76 | if ( isInBounds ) { 77 | if ( m_isCapturingTouch ) { 78 | touchEvt.captured = true; 79 | } 80 | if ( getState() == State::ACTIVE ) { 81 | // dragged within bounds 82 | event.type = TouchZone::Event::Type::DRAG_INSIDE; 83 | } else { 84 | // drag into zone 85 | // setState( State::ACTIVE ); 86 | event.type = TouchZone::Event::Type::DRAG_INTO; 87 | } 88 | onTouchEvent( event ); 89 | } else if ( getState() == State::ACTIVE ) { 90 | // drag off of zone 91 | if ( m_allowLosingFocus ) { 92 | setState( State::INACTIVE ); 93 | } 94 | event.type = TouchZone::Event::Type::DRAG_OFF; 95 | onTouchEvent( event ); 96 | } 97 | break; 98 | } 99 | 100 | case ga::TouchEvent::Type::RELEASE: { 101 | if ( getState() == State::ACTIVE ) { 102 | // tap release 103 | if ( m_isCapturingTouch ) { 104 | touchEvt.captured = true; 105 | } 106 | setState( State::INACTIVE ); 107 | event.type = TouchZone::Event::Type::RELEASE; 108 | onTouchEvent( event ); 109 | } else { 110 | setState( State::INACTIVE ); 111 | } 112 | break; 113 | } 114 | 115 | case ga::TouchEvent::Type::CANCEL: { 116 | // not sure how to handle this... 117 | if ( m_isCapturingTouch ) { 118 | touchEvt.captured = true; 119 | } 120 | setState( State::INACTIVE ); 121 | break; 122 | } 123 | } 124 | } 125 | 126 | void setState( State state ) 127 | { 128 | m_state = state; 129 | } 130 | State getState() const 131 | { 132 | return m_state; 133 | } 134 | 135 | inline void enable() 136 | { 137 | if ( m_state == State::DISABLED ) { 138 | m_state = State::INACTIVE; 139 | } 140 | } 141 | 142 | inline void disable() 143 | { 144 | m_state = State::DISABLED; 145 | } 146 | 147 | inline void setAllowLosingFocus( bool isAllowed = true ) 148 | { 149 | m_allowLosingFocus = isAllowed; 150 | } 151 | 152 | inline void setEnableCapturingTouch( bool isEnabled = true ) 153 | { 154 | m_isCapturingTouch = isEnabled; 155 | } 156 | 157 | inline bool getIsCapturingTouch() 158 | { 159 | return m_isCapturingTouch; 160 | } 161 | 162 | inline void invertBounds( bool invert = true ) 163 | { 164 | m_boundsInverted = invert; 165 | } 166 | 167 | inline bool getAreBoundsInverted() const 168 | { 169 | return m_boundsInverted; 170 | } 171 | 172 | inline void setCustomBoundsTest( std::function testFn ) 173 | { 174 | m_customBoundsTest = testFn; 175 | } 176 | 177 | // will test against ga::Bounds component attached to owner node 178 | inline void setUseDefaultBoundsTest() 179 | { 180 | m_customBoundsTest = nullptr; 181 | } 182 | 183 | inline bool getIsUsingCustomBoundsTest() const 184 | { 185 | return m_customBoundsTest != nullptr; 186 | } 187 | 188 | void connectTouch( std::shared_ptr scene, int groupId ) 189 | { 190 | if ( !scene ) 191 | return; 192 | m_touchConnection = scene->onTouchEvent.connect_scoped( [this]( TouchEvent& te ) { handleTouchEvent( te ); }, groupId ); 193 | } 194 | 195 | void disconnectTouch() 196 | { 197 | m_touchConnection.disconnect(); 198 | } 199 | 200 | // signals 201 | ga::Signal onTouchEvent; 202 | 203 | protected: 204 | virtual void setScene( std::shared_ptr scene ) 205 | { 206 | disconnectTouch(); 207 | connectTouch( scene, 0 ); 208 | } 209 | 210 | State m_state; 211 | ga::Connection m_touchConnection; 212 | bool m_boundsInverted = false; 213 | bool m_isCapturingTouch = true; 214 | bool m_allowLosingFocus = false; 215 | std::function m_customBoundsTest = nullptr; 216 | }; 217 | } // namespace ga 218 | -------------------------------------------------------------------------------- /src/ga/graph/node.cpp: -------------------------------------------------------------------------------- 1 | #include "ga/graph/node.h" 2 | #include "ga/graph/scene.h" 3 | #include "ga/render.h" 4 | 5 | namespace ga { 6 | 7 | std::shared_ptr Node::addChild() 8 | { 9 | return addChild(); 10 | } 11 | 12 | std::shared_ptr Node::insertChild( size_t index ) 13 | { 14 | return insertChild( index ); 15 | } 16 | 17 | void Node::detach() 18 | { 19 | if ( auto p = m_parent.lock() ) { 20 | p->removeChild( shared_from_this() ); 21 | } 22 | } 23 | 24 | bool Node::removeChild( std::shared_ptr child ) 25 | { 26 | bool removed = false; 27 | auto fn = [&removed, &child]( std::shared_ptr ref ) { 28 | if ( ref == child ) { 29 | removed = true; 30 | child->setParent( nullptr ); 31 | child->setScene( nullptr ); // resets scene for child and all descendants 32 | return true; 33 | } 34 | return false; 35 | }; 36 | m_children.erase( std::remove_if( m_children.begin(), m_children.end(), fn ), 37 | m_children.end() ); 38 | return removed; 39 | } 40 | 41 | bool Node::removeDescendant( std::shared_ptr node ) 42 | { 43 | if ( removeChild( node ) ) 44 | return true; // check immediate children 45 | for ( auto& child : m_children ) { // check hierarchy 46 | if ( child && child->removeDescendant( node ) ) 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | void Node::clearChildren() 53 | { 54 | for ( auto& child : m_children ) { 55 | child->setParent( nullptr ); 56 | child->setScene( nullptr ); 57 | } 58 | m_children.clear(); 59 | } 60 | 61 | void Node::sortChildren( std::function& a, const std::shared_ptr& b )> comparisonFn ) 62 | { 63 | std::sort( m_children.begin(), m_children.end(), comparisonFn ); 64 | } 65 | 66 | bool Node::isParentOf( const std::shared_ptr& child ) const 67 | { 68 | return std::find( m_children.begin(), m_children.end(), child ) != m_children.end(); 69 | } 70 | 71 | bool Node::isChildOf( const std::shared_ptr& parent ) const 72 | { 73 | return m_parent.lock() == parent; 74 | } 75 | 76 | bool Node::hasDescendant( const std::shared_ptr& node ) const 77 | { 78 | if ( isParentOf( node ) ) 79 | return true; // check immediate children 80 | for ( auto& child : m_children ) { // check hierarchy 81 | if ( child && child->hasDescendant( node ) ) 82 | return true; 83 | } 84 | return false; 85 | } 86 | 87 | std::shared_ptr Node::getScene() const 88 | { 89 | return m_scene.lock(); 90 | } 91 | 92 | size_t Node::getSceneHierarchyLevel() const 93 | { 94 | size_t level = 0; 95 | auto parent = getParent(); 96 | while ( parent != nullptr ) { 97 | ++level; 98 | parent = parent->getParent(); 99 | } 100 | return level; 101 | } 102 | 103 | size_t Node::getSceneDrawIndex() const 104 | { 105 | if ( m_drawIndex == 0 && getScene() && !isRoot() ) { 106 | // Draw index is re-assigned every Scene::draw() call. 107 | // Only Root nodes and Nodes that have not been drawn yet should have drawIndex == 0. 108 | // Here we assume the Node has not yet been drawn, so we force the scene to re-calc the draw indices. 109 | getScene()->forceAssignDrawIndices(); 110 | } 111 | return m_drawIndex; 112 | } 113 | 114 | ga::Transform Node::getSceneTransform() 115 | { 116 | if ( auto parent = m_parent.lock() ) { 117 | return parent->getSceneTransform() * getTransform(); //getLocalTransform(); 118 | } else { 119 | return getTransform(); //getLocalTransform(); 120 | } 121 | } 122 | 123 | void Node::disableDraw() 124 | { 125 | m_isDrawEnabled = false; 126 | } 127 | 128 | void Node::enableDraw() 129 | { 130 | m_isDrawEnabled = true; 131 | } 132 | 133 | void Node::disableUpdate() 134 | { 135 | m_isUpdateEnabled = false; 136 | } 137 | 138 | void Node::enableUpdate() 139 | { 140 | m_isUpdateEnabled = true; 141 | } 142 | 143 | std::shared_ptr Node::getParent() const 144 | { 145 | return m_parent.lock(); 146 | } 147 | 148 | const std::vector>& Node::getChildren() const 149 | { 150 | return m_children; 151 | } 152 | 153 | // --- internal methods 154 | 155 | Node::Node() 156 | : m_uuid( ga::newUuid() ) 157 | , m_updateFn( [this]() { update(); } ) 158 | , m_drawFn( [this]() { draw(); } ) 159 | , m_drawIndex( 0 ) 160 | { 161 | } 162 | 163 | void Node::setParent( std::shared_ptr parent ) 164 | { 165 | m_parent = parent; 166 | } 167 | 168 | // virtual methods 169 | void Node::setup() 170 | { 171 | } 172 | 173 | void Node::update() 174 | { 175 | } 176 | 177 | void Node::draw() 178 | { 179 | } 180 | 181 | void Node::onDrawIndexChange() 182 | { 183 | } 184 | 185 | void Node::setDrawIndex( size_t index ) 186 | { 187 | if ( m_drawIndex != index ) { 188 | m_drawIndex = index; 189 | onDrawIndexChange(); 190 | } 191 | } 192 | 193 | void Node::updateTree() 194 | { 195 | if ( !m_isUpdateEnabled ) 196 | return; 197 | 198 | onWillUpdate(); 199 | 200 | // update components 201 | for ( auto& c : m_components ) { 202 | if ( c.second ) 203 | c.second->update(); 204 | } 205 | 206 | if ( m_updateFn ) 207 | m_updateFn(); 208 | 209 | onWillUpdateChildren(); 210 | 211 | for ( auto& child : m_children ) { 212 | child->updateTree(); 213 | } 214 | 215 | onDidUpdateChildren(); 216 | onDidUpdate(); 217 | } 218 | 219 | void Node::drawTree() 220 | { 221 | if ( auto scene = m_scene.lock() ) { 222 | // store draw index for later comparison, etc. 223 | setDrawIndex( scene->nextDrawIndex( shared_from_this() ) ); 224 | } 225 | 226 | if ( !m_isDrawEnabled ) 227 | return; 228 | 229 | // transform to local space 230 | getRenderer().pushMatrix(); 231 | getRenderer().multMatrix( Transform::getMatrix() ); 232 | 233 | onWillDraw(); 234 | 235 | // draw components 236 | for ( auto& c : m_components ) { 237 | if ( c.second ) 238 | c.second->draw(); 239 | } 240 | 241 | if ( m_drawFn ) 242 | m_drawFn(); 243 | 244 | onWillDrawChildren(); 245 | 246 | for ( auto& child : m_children ) { 247 | child->drawTree(); 248 | } 249 | 250 | onDidDrawChildren(); 251 | onDidDraw(); 252 | 253 | getRenderer().popMatrix(); 254 | } 255 | 256 | void Node::walkTree( std::function )> fn ) 257 | { 258 | if ( fn ) fn( shared_from_this() ); 259 | 260 | for ( auto& child : m_children ) { 261 | child->walkTree( fn ); 262 | } 263 | } 264 | 265 | void Node::setScene( std::shared_ptr scene ) 266 | { 267 | m_scene = scene; 268 | // notify components 269 | for ( auto& c : m_components ) { 270 | if ( c.second ) 271 | c.second->setScene( scene ); 272 | } 273 | // set scene for all children and descendants 274 | for ( auto& child : m_children ) { 275 | if ( child ) 276 | child->setScene( scene ); 277 | } 278 | } 279 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/node.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file node.h 3 | * @author Tyler Henry, Gallagher & Associates 4 | * @brief 5 | * @version 0.1 6 | * @date 2021-12-09 7 | * 8 | * @copyright Copyright (c) 2021 9 | * 10 | */ 11 | 12 | #pragma once 13 | #include "ga/defines.h" 14 | #include "ga/graph/component.h" 15 | #include "ga/transform.h" 16 | #include "ga/uuid.h" 17 | #include "ga/signal.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace ga { 29 | 30 | class Scene; 31 | 32 | /** 33 | * @brief Node represents a basic "node" (or view) in the scenegraph. 34 | * 35 | * Nodes keep: 36 | * - name (std::string) 37 | * - unique id 38 | * - local Transform 39 | * - Component list 40 | * - reference to a Scene owner 41 | * - reference to a parent Node 42 | * - vector of references to child Nodes 43 | * 44 | */ 45 | class Node : public std::enable_shared_from_this, public Transform 46 | { 47 | public: 48 | // Static Node factory 49 | // in place of public constructor 50 | template 51 | static std::shared_ptr create(); 52 | static std::shared_ptr create() { return Node::create(); } 53 | 54 | // parent / child hierarchy 55 | // ------------------------ 56 | 57 | template 58 | std::shared_ptr addChild( std::shared_ptr child = nullptr ); 59 | std::shared_ptr addChild(); 60 | 61 | template 62 | std::shared_ptr insertChild( size_t index, std::shared_ptr child = nullptr ); 63 | std::shared_ptr insertChild( size_t index ); 64 | 65 | void detach(); // remove this node and its children from its parent and scene 66 | 67 | bool removeChild( std::shared_ptr child ); // returns true if found / removed 68 | bool removeDescendant( std::shared_ptr node ); // searches entire hierarchy for Node and removes 69 | void clearChildren(); // remove all children 70 | 71 | void sortChildren( std::function& a, const std::shared_ptr& b )> comparisonFn ); 72 | 73 | bool isParentOf( const std::shared_ptr& child ) const; 74 | bool isChildOf( const std::shared_ptr& parent ) const; 75 | inline bool hasChild( const std::shared_ptr& child ) const { return isParentOf( child ); } 76 | bool hasDescendant( const std::shared_ptr& node ) const; 77 | 78 | std::shared_ptr getParent() const; 79 | const std::vector>& getChildren() const; 80 | 81 | inline bool hasChildren() const { return !m_children.empty(); } 82 | inline bool hasParent() const { return !m_parent.expired(); } 83 | inline bool isRoot() const { return !hasParent(); } // root == no parent 84 | 85 | inline bool hasScene() const { return !m_scene.expired(); } 86 | std::shared_ptr getScene() const; 87 | 88 | size_t getSceneHierarchyLevel() const; // 0 == root, 1 == child of root, 2 == grandchild, etc. 89 | size_t getSceneDrawIndex() const; 90 | 91 | // setters / getters 92 | 93 | void setName( const std::string& name ) { m_name = name; } 94 | const std::string& getName() const { return m_name; } 95 | 96 | void setUuid( const ga::Uuid& uuid ) { m_uuid = uuid; } 97 | const ga::Uuid& getUuid() { return m_uuid; } 98 | 99 | ga::Transform& getTransform() { return *this; } 100 | 101 | // custom draw / update functions 102 | 103 | void setDrawFn( std::function drawFn ) { m_drawFn = drawFn; } 104 | void resetDrawFn() 105 | { 106 | m_drawFn = [this]() { draw(); }; 107 | } 108 | 109 | void setUpdateFn( std::function updateFn ) { m_updateFn = updateFn; } 110 | void resetUpdateFn() 111 | { 112 | m_updateFn = [this]() { update(); }; 113 | } 114 | 115 | // scene space transformations 116 | 117 | // the global / scene space transformation of this node 118 | ga::Transform getSceneTransform(); 119 | 120 | // helper to convert a local position to scene space 121 | vec3 localPosToScene( const vec3& pos ) { return getSceneTransform().getMatrix() * vec4( pos, 1. ); } 122 | // helper to convert a scene position to local space 123 | vec3 scenePosToLocal( const vec3& pos ) { return glm::inverse( getSceneTransform().getMatrix() ) * vec4( pos, 1. ); } 124 | 125 | // enable / disable node 126 | 127 | void disableUpdate(); // scene WON'T update() this Node (or its children) 128 | void enableUpdate(); // scene WILL update() this Node (and its children) 129 | 130 | bool isUpdateEnabled() const { return m_isUpdateEnabled; } 131 | 132 | void disableDraw(); // scene WON'T draw() this Node (or its children) 133 | void enableDraw(); // scene WILL draw() this Node (and its children) 134 | 135 | bool isDrawEnabled() const { return m_isDrawEnabled; } 136 | 137 | // /// TODO - these are temporary methods for testing. should be replaced with iostream overloads 138 | // void printChildren(); 139 | // void printParent(); 140 | // void printHierarchy(); 141 | // void printScene(); 142 | 143 | // components 144 | 145 | template 146 | std::shared_ptr createComponent(); 147 | 148 | template 149 | std::shared_ptr addComponent( std::shared_ptr component ); 150 | 151 | template 152 | std::shared_ptr getComponent(); 153 | 154 | template 155 | bool removeComponent(); 156 | 157 | template 158 | ComponentT& component(); 159 | 160 | // signals 161 | 162 | ga::Signal<> 163 | onWillUpdate, onDidUpdate, 164 | onWillUpdateChildren, onDidUpdateChildren, 165 | onWillDraw, onDidDraw, 166 | onWillDrawChildren, onDidDrawChildren; 167 | 168 | virtual ~Node() {} 169 | 170 | protected: 171 | Node(); // Nodes should only be created by Node::create() 172 | virtual void setup(); 173 | virtual void update(); 174 | virtual void draw(); 175 | 176 | // useful for things that deal with draw order, i.e. events 177 | virtual void onDrawIndexChange(); 178 | void setDrawIndex( size_t index ); 179 | 180 | friend class Scene; 181 | 182 | void setScene( std::shared_ptr scene ); 183 | void setParent( std::shared_ptr parent ); 184 | 185 | // update and draw hierarchy 186 | void updateTree(); 187 | void drawTree(); 188 | 189 | void walkTree( std::function )> fn ); // run arbitrary function on self and children 190 | 191 | std::weak_ptr m_scene; 192 | std::weak_ptr m_parent; 193 | std::vector> m_children; 194 | 195 | // components 196 | std::unordered_map> m_components; 197 | 198 | // built-in properties 199 | std::string m_name; 200 | ga::Uuid m_uuid; 201 | size_t m_drawIndex; 202 | std::function m_updateFn; 203 | std::function m_drawFn; 204 | // std::shared_ptr m_mesh; 205 | // std::shared_ptr m_material; 206 | 207 | bool m_isDrawEnabled = true; 208 | bool m_isUpdateEnabled = true; 209 | }; 210 | 211 | // template implementations 212 | // --------------------------------------------- 213 | 214 | // --------------------------------------------- 215 | template 216 | std::shared_ptr Node::create() 217 | { 218 | auto node = std::shared_ptr( new NodeT() ); 219 | node->setup(); 220 | return node; 221 | } 222 | 223 | // --------------------------------------------- 224 | template 225 | std::shared_ptr Node::addChild( std::shared_ptr child ) 226 | { 227 | if ( !child ) { 228 | child = create(); 229 | } 230 | if ( !isParentOf( child ) ) { 231 | child->detach(); 232 | m_children.push_back( child ); 233 | child->setParent( shared_from_this() ); 234 | child->setScene( m_scene.lock() ); 235 | } 236 | return child; 237 | } 238 | 239 | // --------------------------------------------- 240 | template 241 | std::shared_ptr Node::insertChild( size_t index, std::shared_ptr child ) 242 | { 243 | if ( !child ) { 244 | child = create(); 245 | } 246 | if ( !isParentOf( child ) ) { 247 | child->detach(); 248 | index = std::min( index, m_children.size() ); 249 | m_children.emplace( m_children.begin() + index, child ); 250 | child->setParent( shared_from_this() ); 251 | child->setScene( m_scene.lock() ); 252 | } 253 | return child; 254 | } 255 | 256 | // --------------------------------------------- 257 | template 258 | std::shared_ptr Node::createComponent() 259 | { 260 | return addComponent( std::shared_ptr( new ComponentT() ) ); 261 | } 262 | 263 | // --------------------------------------------- 264 | template 265 | std::shared_ptr Node::addComponent( std::shared_ptr componentPtr ) 266 | { 267 | if ( !componentPtr ) 268 | componentPtr = std::shared_ptr( new ComponentT() ); 269 | auto p = std::make_pair( std::type_index( typeid( ComponentT ) ), componentPtr ); 270 | auto r = m_components.insert( p ); // returns 271 | if ( r.second ) { 272 | componentPtr->setNode( shared_from_this() ); 273 | } 274 | return r.second ? componentPtr : nullptr; 275 | } 276 | 277 | // -------------------------------------------- 278 | template 279 | std::shared_ptr Node::getComponent() 280 | { 281 | auto it = m_components.find( std::type_index( typeid( ComponentT ) ) ); 282 | if ( it != m_components.end() ) { 283 | return std::dynamic_pointer_cast( it->second ); 284 | } else { 285 | return nullptr; 286 | } 287 | } 288 | 289 | // -------------------------------------------- 290 | // auto creates component, if not found or null 291 | template 292 | ComponentT& Node::component() 293 | { 294 | auto it = m_components.find( std::type_index( typeid( ComponentT ) ) ); 295 | std::shared_ptr component; 296 | if ( it != m_components.end() ) { 297 | if ( it->second ) { 298 | component = std::dynamic_pointer_cast( it->second ); 299 | } else { 300 | // component null, remove before re-create 301 | removeComponent(); 302 | } 303 | } 304 | if ( !component ) { 305 | component = createComponent(); 306 | if ( !component ) { 307 | throw std::runtime_error( "Error creating Component of type: " + std::string( typeid( ComponentT ).name() ) ); 308 | } 309 | } 310 | return *component; 311 | } 312 | 313 | // --------------------------------------------- 314 | template 315 | bool Node::removeComponent() 316 | { 317 | auto it = m_components.find( typeid( ComponentT ) ); 318 | if ( it != m_components.end() ) { 319 | //if (it->second) { 320 | // it->second->m_components = nullptr; 321 | //} 322 | m_components.erase( it ); 323 | return true; 324 | } else { 325 | return false; 326 | } 327 | } 328 | 329 | } // namespace ga 330 | -------------------------------------------------------------------------------- /src/ga/graph/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "ga/graph/scene.h" 2 | 3 | namespace ga { 4 | 5 | Scene::Scene() 6 | : m_rootNode( Node::create() ) 7 | { 8 | } 9 | 10 | Scene::~Scene() 11 | { 12 | } 13 | 14 | // protected method, called by Scene:create() 15 | void Scene::setup() 16 | { 17 | m_rootNode->setName( "root" ); 18 | m_rootNode->setScene( shared_from_this() ); 19 | } 20 | 21 | void Scene::update() 22 | { 23 | m_timeoutManager.updateTimeouts(); 24 | updateNodes(); 25 | } 26 | 27 | void Scene::draw() 28 | { 29 | drawNodes(); 30 | } 31 | 32 | std::shared_ptr Scene::addNode() 33 | { 34 | return addNode(); 35 | } 36 | 37 | bool Scene::removeNode( std::shared_ptr node ) 38 | { 39 | return m_rootNode->removeChild( node ); 40 | } 41 | 42 | void Scene::clearNodes() 43 | { 44 | m_rootNode->clearChildren(); 45 | } 46 | 47 | bool Scene::hasNode( std::shared_ptr node ) 48 | { 49 | return m_rootNode->hasDescendant( node ); 50 | } 51 | 52 | std::shared_ptr Scene::getRootNode() 53 | { 54 | return m_rootNode; 55 | } 56 | 57 | std::vector> Scene::getNodes() 58 | { 59 | return m_rootNode->getChildren(); 60 | } 61 | 62 | void Scene::forEachNode( std::function )> fn ) 63 | { 64 | if ( m_rootNode ) { 65 | m_rootNode->walkTree( fn ); 66 | } 67 | } 68 | 69 | void Scene::setName( const std::string& name ) 70 | { 71 | m_name = name; 72 | } 73 | 74 | const std::string& Scene::getName() 75 | { 76 | return m_name; 77 | } 78 | 79 | void Scene::handleMouseEvent( MouseEvent& mouseEvent ) 80 | { 81 | // trigger signal 82 | onMouseEvent( mouseEvent ); 83 | } 84 | 85 | void Scene::handleTouchEvent( TouchEvent& touchEvent ) 86 | { 87 | // trigger signal 88 | onTouchEvent( touchEvent ); 89 | } 90 | 91 | void Scene::handleKeyEvent( KeyEvent& keyEvent ) 92 | { 93 | // trigger signal 94 | onKeyEvent( keyEvent ); 95 | } 96 | 97 | // --- protected - internal 98 | 99 | void Scene::updateNodes() 100 | { 101 | if ( m_rootNode ) 102 | m_rootNode->updateTree(); 103 | } 104 | 105 | void Scene::drawNodes() 106 | { 107 | m_drawnNodes.clear(); 108 | if ( m_rootNode ) 109 | m_rootNode->drawTree(); 110 | } 111 | 112 | size_t Scene::nextDrawIndex( std::shared_ptr node ) 113 | { 114 | if ( !node ) 115 | return 0; 116 | m_drawnNodes.push_back( node ); 117 | return m_drawnNodes.size() - 1; 118 | } 119 | 120 | void Scene::forceAssignDrawIndices() 121 | { 122 | m_drawnNodes.clear(); 123 | auto fn = [this]( std::shared_ptr node ) { 124 | if ( node ) { 125 | auto index = nextDrawIndex( node ); 126 | node->setDrawIndex( index ); 127 | } 128 | }; 129 | forEachNode( fn ); 130 | } 131 | 132 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/graph/scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include "ga/events.h" 4 | #include "ga/graph/node.h" 5 | #include "ga/signal.h" 6 | #include "ga/timeout.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace ga { 13 | 14 | /** 15 | * @brief Scene is a view controller. 16 | * - contains a single root node, which holds the scene's node / view hierarchy. 17 | * - handles UI events (mouse, touch, etc.) 18 | */ 19 | class Scene : public std::enable_shared_from_this 20 | { 21 | public: 22 | // Static Scene factory 23 | // in place of public constructor 24 | template 25 | static std::shared_ptr create(); 26 | static std::shared_ptr create() { return Scene::create(); } 27 | 28 | virtual ~Scene(); 29 | 30 | virtual void setup(); 31 | virtual void update(); 32 | virtual void draw(); 33 | 34 | template 35 | std::shared_ptr addNode( std::shared_ptr child = nullptr ); 36 | std::shared_ptr addNode(); 37 | 38 | bool removeNode( std::shared_ptr node ); 39 | void clearNodes(); 40 | 41 | bool hasNode( std::shared_ptr node ); 42 | 43 | std::shared_ptr getRootNode(); 44 | std::vector> getNodes(); 45 | void forEachNode( std::function )> fn ); 46 | 47 | void setName( const std::string& name ); 48 | const std::string& getName(); 49 | 50 | virtual void handleMouseEvent( MouseEvent& mouseEvent ); 51 | virtual void handleTouchEvent( TouchEvent& touchEvent ); 52 | virtual void handleKeyEvent( KeyEvent& keyEvent ); 53 | 54 | // signals 55 | Signal onKeyEvent; 56 | Signal onMouseEvent; 57 | Signal onTouchEvent; 58 | 59 | protected: 60 | Scene(); 61 | 62 | void updateNodes(); // update root node and all children 63 | void drawNodes(); // draw root node and all children 64 | 65 | friend void Node::setScene( std::shared_ptr scene ); 66 | //void addToHierarchy( std::shared_ptr node ); 67 | //void removeFromHierarchy( std::shared_ptr node ); 68 | 69 | friend void Node::drawTree(); 70 | size_t nextDrawIndex( std::shared_ptr node ); 71 | 72 | friend size_t Node::getSceneDrawIndex() const; 73 | void forceAssignDrawIndices(); 74 | 75 | std::string m_name; 76 | std::shared_ptr m_rootNode; 77 | ga::TimeoutManager m_timeoutManager; 78 | 79 | std::vector> m_drawnNodes; // sorted by draw order 80 | }; 81 | 82 | // template implementations 83 | // ------------------------ 84 | 85 | template 86 | std::shared_ptr Scene::create() 87 | { 88 | auto scene = std::shared_ptr( new SceneT() ); 89 | scene->setup(); 90 | return scene; 91 | } 92 | 93 | template 94 | std::shared_ptr Scene::addNode( std::shared_ptr node ) 95 | { 96 | return m_rootNode->addChild( node ); 97 | } 98 | } // namespace ga 99 | -------------------------------------------------------------------------------- /src/ga/json.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "json.hpp" // nlohmann::json 3 | 4 | namespace ga { 5 | using Json = nlohmann::json; 6 | 7 | namespace json { 8 | // json parser utility functions 9 | 10 | template 11 | inline T getValue( const ga::Json& json, const std::string& keyOrPointer, const T& defaultValue ) 12 | { 13 | T ret = defaultValue; 14 | try { 15 | if ( keyOrPointer.empty() || keyOrPointer.at( 0 ) == '/' ) { 16 | // allow for json pointer paths - these are either empty or begin with '/' 17 | ret = json.at(ga::Json::json_pointer( keyOrPointer )).get(); 18 | } else { 19 | // assume key is a normal key for a json object 20 | ret = json.at(keyOrPointer).get(); 21 | } 22 | } catch ( ... ) { 23 | // todo: log warning? accept an ostream& for logging? 24 | } 25 | return ret; 26 | } 27 | 28 | inline std::string getValue( const ga::Json& json, const std::string& keyOrPointer, const char* defaultValue ) 29 | { 30 | return getValue( json, keyOrPointer, std::string( defaultValue ) ); 31 | } 32 | 33 | } // namespace json 34 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/json_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/json.h" 3 | 4 | // ---------------- json conversions ---------------- 5 | 6 | namespace ga { 7 | 8 | // ga::vec4 9 | inline void from_json( const ga::Json& j, ga::vec4& v ) 10 | { 11 | try { 12 | v = { j.at( 0 ), j.at( 1 ), j.at( 2 ), j.at( 3 ) }; 13 | } catch ( std::exception& e ) { 14 | std::stringstream ss; 15 | ss << "Error, vec4 from Json: " << j.dump() << " - " << e.what(); 16 | throw std::domain_error( ss.str() ); 17 | } 18 | } 19 | inline void to_json( ga::Json& j, const ga::vec4& v ) 20 | { 21 | j = { v[0], v[1], v[2], v[3] }; 22 | } 23 | 24 | // ga::quat 25 | inline void from_json( const ga::Json& j, ga::quat& q ) 26 | { 27 | try { 28 | q.x = j.at( 0 ); 29 | q.y = j.at( 1 ); 30 | q.z = j.at( 2 ); 31 | q.w = j.at( 3 ); 32 | } catch ( std::exception& e ) { 33 | std::stringstream ss; 34 | ss << "Error, quat from Json: " << j.dump() << " - " << e.what(); 35 | throw std::domain_error( ss.str() ); 36 | } 37 | } 38 | inline void to_json( ga::Json& j, const ga::quat& q ) 39 | { 40 | j = { q.x, q.y, q.z, q.w }; 41 | } 42 | 43 | // ga::vec3 44 | inline void from_json( const ga::Json& j, ga::vec3& v ) 45 | { 46 | try { 47 | v = { j.at( 0 ), j.at( 1 ), j.at( 2 ) }; 48 | } catch ( std::exception& e ) { 49 | std::stringstream ss; 50 | ss << "Error, vec3 from Json: " << j.dump() << " - " << e.what(); 51 | throw std::domain_error( ss.str() ); 52 | } 53 | } 54 | inline void to_json( ga::Json& j, const ga::vec3& v ) 55 | { 56 | j = { v[0], v[1], v[2] }; 57 | } 58 | 59 | // ga::vec2 60 | inline void from_json( const ga::Json& j, ga::vec2& v ) 61 | { 62 | try { 63 | v = { j.at( 0 ), j.at( 1 ) }; 64 | } catch ( std::exception& e ) { 65 | std::stringstream ss; 66 | ss << "Error, vec2 from Json: " << j.dump() << " - " << e.what(); 67 | throw std::domain_error( ss.str() ); 68 | } 69 | } 70 | inline void to_json( ga::Json& j, const ga::vec2& v ) 71 | { 72 | j = { v[0], v[1] }; 73 | } 74 | 75 | // ga::mat4 76 | inline void from_json( const ga::Json& j, ga::mat4& m ) 77 | { 78 | ga::mat4 _m = m; 79 | try { 80 | // glm is column major - convert from row major 81 | for ( int r = 0; r < 4; ++r ) { 82 | for ( int c = 0; c < 4; ++c ) { 83 | _m[c][r] = j.at(r).at(c); 84 | } 85 | } 86 | } catch ( std::exception& e ) { 87 | std::stringstream ss; 88 | ss << "Error, mat4 from Json: " << j.dump() << " - " << e.what(); 89 | throw std::domain_error( ss.str() ); 90 | } 91 | m = _m; 92 | } 93 | 94 | inline void to_json( ga::Json& j, const ga::mat4& m ) 95 | { 96 | j.clear(); 97 | // store row major - convert from column major 98 | for ( int r = 0; r < 4; ++r ) { 99 | ga::Json row; 100 | for ( int c = 0; c < 4; ++c ) { 101 | row.push_back( m[c][r] ); 102 | } 103 | j.push_back( row ); 104 | } 105 | } 106 | 107 | // ga::Transform 108 | //inline void from_json(const ga::Json& j, ga::Transform& t) 109 | //{ 110 | // ga::Transform _t = t; 111 | // try { 112 | // _t.setTranslation(j.at("translation").get()); 113 | // _t.setRotation(j.at("rotation").get()); 114 | // _t.setScale(j.at("scale").get()); 115 | // } catch (std::exception& e) { 116 | // std::stringstream ss; 117 | // ss << "Error, ga::Transform from Json: " << j.dump() << " - " << e.what(); 118 | // throw std::domain_error(ss.str()); 119 | // } 120 | // t = _t; 121 | //} 122 | // 123 | //inline void to_json(ga::Json& j, const ga::Transform& t) 124 | //{ 125 | // j.clear(); 126 | // j["translation"] = t.getTranslation(); 127 | // j["rotation"] = t.getRotation(); 128 | // j["scale"] = t.getScale(); 129 | //} 130 | } // namespace ga 131 | 132 | // todo: overload glm namespace? 133 | -------------------------------------------------------------------------------- /src/ga/layout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/math.h" 3 | 4 | namespace ga { 5 | enum class HorzAlign 6 | { 7 | LEFT, 8 | CENTER, 9 | RIGHT 10 | }; 11 | enum class VertAlign 12 | { 13 | TOP, 14 | MIDDLE, 15 | BOTTOM 16 | }; 17 | 18 | // helper for alignment, returns anchor in percent 19 | inline vec2 anchor( HorzAlign h, VertAlign v ) 20 | { 21 | ga::vec2 p { 0, 0 }; 22 | switch ( h ) { 23 | case HorzAlign::CENTER: 24 | p.x = .5; 25 | break; 26 | case HorzAlign::RIGHT: 27 | p.x = 1.; 28 | break; 29 | } 30 | switch ( v ) { 31 | case VertAlign::MIDDLE: 32 | p.y = .5; 33 | break; 34 | case VertAlign::BOTTOM: 35 | p.y = 1.; 36 | break; 37 | } 38 | return p; 39 | } 40 | 41 | /* 42 | FitMode - describes how to scale an object to fit a container object 43 | */ 44 | enum class FitMode 45 | { 46 | NONE, // no auto scaling 47 | FIT, // scale proportionally to fit entirely within container 48 | STRETCH, // match width and height to container 49 | COVER, // scale to cover container 50 | FIT_WIDTH, // fit width, scale height accordingly 51 | FIT_HEIGHT, 52 | FIT_DEPTH // for 3D 53 | }; 54 | 55 | inline vec3 calcScaleToFit( const vec3& sourceSize, const vec3& containerSize, const FitMode& fitMode ) 56 | { 57 | vec3 scale = vec3( 1.f ); 58 | 59 | // calculate sizing based on fit mode 60 | switch ( fitMode ) { 61 | 62 | case FitMode::FIT: { 63 | vec3 s = containerSize / sourceSize; 64 | float s0 = std::min( std::min( s.x, s.y ), s.z ); 65 | scale = vec3( s0 ); 66 | break; 67 | } 68 | 69 | case FitMode::STRETCH: { 70 | scale = containerSize / sourceSize; 71 | break; 72 | } 73 | 74 | case FitMode::COVER: { 75 | vec3 s = containerSize / sourceSize; 76 | float s1 = std::max( std::max( s.x, s.y ), s.z ); 77 | scale = vec3( s1 ); 78 | break; 79 | } 80 | 81 | case FitMode::FIT_WIDTH: { 82 | float sx = containerSize.x / sourceSize.x; 83 | scale = vec3( sx ); 84 | break; 85 | } 86 | 87 | case FitMode::FIT_HEIGHT: { 88 | float sy = containerSize.y / sourceSize.y; 89 | scale = vec3( sy ); 90 | break; 91 | } 92 | 93 | case FitMode::FIT_DEPTH: { 94 | float sz = containerSize.z / sourceSize.z; 95 | scale = vec3( sz ); 96 | break; 97 | } 98 | 99 | case FitMode::NONE: 100 | default: 101 | break; 102 | } 103 | 104 | return scale; 105 | } 106 | 107 | inline vec2 calcScaleToFit( const vec2& sourceSize, const vec2& containerSize, const FitMode& fitMode ) 108 | { 109 | vec2 scale = vec2( 1.f ); 110 | 111 | // calculate sizing based on fit mode 112 | switch ( fitMode ) { 113 | 114 | case FitMode::FIT: { 115 | vec2 s = containerSize / sourceSize; 116 | float s0 = std::min( s.x, s.y ); 117 | scale = vec2( s0 ); 118 | break; 119 | } 120 | 121 | case FitMode::STRETCH: { 122 | scale = containerSize / sourceSize; 123 | break; 124 | } 125 | 126 | case FitMode::COVER: { 127 | vec2 s = containerSize / sourceSize; 128 | float s1 = std::max( s.x, s.y ); 129 | scale = vec2( s1 ); 130 | break; 131 | } 132 | 133 | case FitMode::FIT_WIDTH: { 134 | float sx = containerSize.x / sourceSize.x; 135 | scale = vec2( sx ); 136 | break; 137 | } 138 | 139 | case FitMode::FIT_HEIGHT: { 140 | float sy = containerSize.y / sourceSize.y; 141 | scale = vec2( sy ); 142 | break; 143 | } 144 | 145 | case FitMode::FIT_DEPTH: 146 | case FitMode::NONE: 147 | default: 148 | break; 149 | } 150 | 151 | return scale; 152 | } 153 | 154 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include "glm/glm.hpp" 4 | #include "glm/gtx/matrix_decompose.hpp" // needed for matrix decomposition 5 | #include "glm/gtx/quaternion.hpp" 6 | #include 7 | #include 8 | 9 | #ifdef GA_OPENFRAMEWORKS 10 | #include "ofRectangle.h" 11 | #endif 12 | 13 | namespace ga { 14 | 15 | namespace math { 16 | 17 | const double pi = 4. * std::atan( 1. ); // tan(1/4 PI) = 1 18 | const double halfPi = pi * .5; 19 | 20 | const double radToDegFactor = 180. / math::pi; // degrees = 180/pi * radians 21 | const double degToRadFactor = math::pi / 180.; // radians = pi/180 * degrees 22 | 23 | } // namespace math 24 | 25 | // pull into ga namespace: 26 | using glm::mat2; 27 | using glm::mat2x3; 28 | using glm::mat3; 29 | using glm::mat4; 30 | using glm::quat; 31 | using glm::vec2; 32 | using glm::vec3; 33 | using glm::vec4; 34 | 35 | template 36 | inline T toDegrees( const T& radians ) 37 | { 38 | return math::radToDegFactor * radians; 39 | } 40 | 41 | inline double toDegrees( const double& radians ) 42 | { 43 | return math::radToDegFactor * radians; 44 | } 45 | 46 | template 47 | inline T toRadians( const T& degrees ) 48 | { 49 | return math::degToRadFactor * degrees; 50 | } 51 | 52 | inline double toRadians( const double& degrees ) 53 | { 54 | return math::degToRadFactor * degrees; 55 | } 56 | 57 | /** 58 | * @brief A 2D axis-aligned rectangle, with x,y (float) position and w,h (float) size components. 59 | * 60 | * The x,y position components are the "principle point" of the rectangle. 61 | * The w,h components are *added* to the x,y point to find the 3 other corners. 62 | */ 63 | struct Rect 64 | { 65 | Rect() {} 66 | Rect( float xPos, float yPos, float width, float height ) 67 | : x( xPos ) 68 | , y( yPos ) 69 | , w( width ) 70 | , h( height ) 71 | { 72 | } 73 | Rect( const vec2& ptA, const vec2& ptB ) 74 | { 75 | auto sz = ptB - ptA; 76 | x = ptA.x; 77 | y = ptA.y; 78 | w = sz.x; 79 | h = sz.y; 80 | } 81 | #ifdef GA_OPENFRAMEWORKS 82 | Rect( const ofRectangle& rect ) 83 | : x( rect.x ) 84 | , y( rect.y ) 85 | , w( rect.width ) 86 | , h( rect.height ) 87 | { 88 | } 89 | #endif 90 | 91 | float x, y, w, h; 92 | 93 | vec2 position() const { return vec2 { x, y }; } 94 | vec2 size() const { return vec2 { w, h }; } 95 | float area() const { return std::abs( w * h ); } 96 | vec2 min() const { return vec2( std::min( x, x + w ), std::min( y, y + h ) ); } // same as position(), unless width or height < 0 97 | vec2 max() const { return vec2( std::max( x, x + w ), std::max( y, y + h ) ); } 98 | bool contains( const vec2& pt ) const 99 | { 100 | vec2 a = min(); 101 | vec2 b = max(); 102 | return pt.x >= a.x && pt.y >= a.y && pt.x <= b.x && pt.y <= b.y; 103 | } 104 | }; 105 | 106 | // /** 107 | // * @brief A 2D axis-aligned bounding box. 108 | // * 109 | // */ 110 | // struct Bounds2D 111 | // { 112 | // vec2 min, max; 113 | // bool isInside( vec2 114 | // vec2 center() const { return ( min + max ) * .5f; } 115 | // float width() const { return max.x - min.x; } 116 | // float height() const { return max.y - min.y; } 117 | // }; 118 | 119 | /** 120 | * @brief A 3D axis-aligned bounding box. 121 | * 122 | */ 123 | struct Bounds3D 124 | { 125 | vec3 min, max; 126 | vec3 center() const { return ( min + max ) * .5f; } 127 | vec3 size() const { return max - min; } 128 | float width() const { return max.x - min.x; } 129 | float height() const { return max.y - min.y; } 130 | float depth() const { return max.z - min.z; } 131 | }; 132 | 133 | /** 134 | * @brief Clamp a value between a minimum and a maximum 135 | * 136 | * @tparam T type must support < and > comparison 137 | */ 138 | template 139 | inline const T& clamp( const T& val, const T& min, const T& max ) 140 | { 141 | return ( val < min ) ? min 142 | : ( val > max ) ? max 143 | : val; 144 | } 145 | 146 | /** 147 | * @brief Map a value from one range to another. 148 | * 149 | * Optionally, clamp the result to stay within the output range. 150 | * @tparam T type must support +, -, /, * operators (and <,> for clamp) 151 | * 152 | */ 153 | template 154 | inline T map( const T& val, const T& inMin, const T& inMax, const T& outMin, const T& outMax, bool bClamp = false ) 155 | { 156 | T r = val; 157 | try { // check for 0 divide 158 | r = ( val - inMin ) / ( inMax - inMin ) * ( outMax - outMin ) + outMin; 159 | } catch ( ... ) { 160 | } 161 | return bClamp ? clamp( r, outMin, outMax ) : r; 162 | } 163 | 164 | inline float map(const float& val, const float& inMin, const float& inMax, const float& outMin, const float& outMax, bool bClamp = false) 165 | { 166 | float r = val; 167 | try { // check for 0 divide 168 | r = (val - inMin) / (inMax - inMin) * (outMax - outMin) + outMin; 169 | } 170 | catch (...) { 171 | } 172 | return bClamp ? clamp(r, outMin, outMax) : r; 173 | } 174 | 175 | 176 | // linear interpolations 177 | // ---------------------- 178 | 179 | template 180 | inline T lerp( const T& a, const T& b, const T& pct ) 181 | { 182 | return ( b - a ) * pct + a; 183 | } 184 | 185 | template 186 | inline T lerp( const T& a, const T& b, float pct ) 187 | { 188 | return ( b - a ) * pct + a; 189 | } 190 | 191 | inline float lerp( float a, float b, float pct ) 192 | { 193 | return ( b - a ) * pct + a; 194 | } 195 | 196 | inline quat lerp( const quat& a, const quat& b, float pct ) 197 | { 198 | return slerp( a, b, pct ); 199 | } 200 | 201 | // quaternion rotation 202 | inline quat slerp( const quat& a, const quat& b, float pct ) 203 | { 204 | return glm::slerp( a, b, pct ); // spherical lerp 205 | } 206 | 207 | // cubic bezier curve - https://cubic-bezier.com/ 208 | inline float cubicBezier( float x, float x1, float y1, float x2, float y2 ) 209 | { 210 | if ( x == 0. ) 211 | return 0.; 212 | if ( x == 1. ) 213 | return 1.; 214 | 215 | // from http://www.flong.com/texts/code/shapers_bez/ 216 | 217 | float y0a = 0.; // initial y 218 | float x0a = 0.; // initial x 219 | float y1a = y1; // 1st influence y 220 | float x1a = x1; // 1st influence x 221 | float y2a = y2; // 2nd influence y 222 | float x2a = x2; // 2nd influence x 223 | float y3a = 1.; // final y 224 | float x3a = 1.; // final x 225 | 226 | float A = x3a - 3.f * x2a + 3.f * x1a - x0a; 227 | float B = 3.f * x2a - 6.f * x1a + 3.f * x0a; 228 | float C = 3.f * x1a - 3.f * x0a; 229 | float D = x0a; 230 | 231 | float E = y3a - 3.f * y2a + 3.f * y1a - y0a; 232 | float F = 3.f * y2a - 6.f * y1a + 3.f * y0a; 233 | float G = 3.f * y1a - 3.f * y0a; 234 | float H = y0a; 235 | 236 | auto slopeFromT = []( float t, float A, float B, float C ) -> float { 237 | return 1.f / ( 3.f * A * t * t + 2.f * B * t + C ); // dtdx 238 | }; 239 | auto xFromT = []( float t, float A, float B, float C, float D ) -> float { 240 | return A * ( t * t * t ) + B * ( t * t ) + C * t + D; 241 | }; 242 | auto yFromT = []( float t, float E, float F, float G, float H ) -> float { 243 | return E * ( t * t * t ) + F * ( t * t ) + G * t + H; 244 | }; 245 | 246 | // Solve for t given x (using Newton-Raphelson), then solve for y given t. 247 | // Assume for the first guess that t = x. 248 | float currentt = x; 249 | int nRefinementIterations = 5; 250 | for ( int i = 0; i < nRefinementIterations; i++ ) { 251 | float currentx = xFromT( currentt, A, B, C, D ); 252 | float currentslope = slopeFromT( currentt, A, B, C ); 253 | currentt -= ( currentx - x ) * ( currentslope ); 254 | currentt = clamp( currentt, 0.f, 1.f ); 255 | } 256 | 257 | return yFromT( currentt, E, F, G, H ); 258 | } 259 | 260 | // templated interpolation 261 | // use an ease function to interpolate from one value to another 262 | template 263 | inline T interpolate( const T& a, const T& b, float pct, std::function easeFn, bool bClamp = false ) 264 | { 265 | pct = bClamp ? ga::clamp( easeFn( pct ), 0.f, 1.f ) : easeFn( pct ); // use easing on the pct 266 | return ga::lerp( a, b, pct ); // then lerp based on eased pct 267 | } 268 | 269 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/render.cpp: -------------------------------------------------------------------------------- 1 | #include "ga/render.h" 2 | #include "ga/defines.h" 3 | #include "ga/gl.h" 4 | 5 | #ifdef GA_OPENFRAMEWORKS 6 | #include "ofGraphics.h" 7 | #endif 8 | 9 | namespace ga { 10 | void Renderer::pushMatrix( MatrixType type ) 11 | { 12 | m_matrixStack[type].push_back( m_matrices[type] ); 13 | 14 | #ifdef GA_OPENFRAMEWORKS 15 | switch ( type ) { 16 | case MatrixType::MODEL: 17 | case MatrixType::VIEW: 18 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_MODELVIEW ); 19 | break; 20 | case MatrixType::PROJECTION: 21 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_PROJECTION ); 22 | break; 23 | } 24 | ofPushMatrix(); 25 | #endif 26 | } 27 | 28 | void Renderer::popMatrix( MatrixType type ) 29 | { 30 | if ( !m_matrixStack[type].empty() ) { 31 | m_matrices[type] = m_matrixStack[type].back(); 32 | m_matrixStack[type].pop_back(); 33 | 34 | #ifdef GA_OPENFRAMEWORKS 35 | switch ( type ) { 36 | case MatrixType::MODEL: 37 | case MatrixType::VIEW: 38 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_MODELVIEW ); 39 | break; 40 | case MatrixType::PROJECTION: 41 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_PROJECTION ); 42 | break; 43 | } 44 | ofPopMatrix(); 45 | #endif 46 | 47 | } else { 48 | // todo: log error 49 | } 50 | } 51 | 52 | void Renderer::setMatrix( const ga::mat4& matrix, MatrixType type ) 53 | { 54 | m_matrices[type] = matrix; 55 | 56 | #ifdef GA_OPENFRAMEWORKS 57 | switch ( type ) { 58 | case MatrixType::MODEL: 59 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_MODELVIEW ); 60 | ofLoadMatrix( matrix ); 61 | break; 62 | case MatrixType::VIEW: 63 | ofLoadViewMatrix( matrix ); 64 | break; 65 | case MatrixType::PROJECTION: 66 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_PROJECTION ); 67 | ofLoadMatrix( matrix ); 68 | break; 69 | } 70 | #endif 71 | } 72 | 73 | const ga::mat4& Renderer::getMatrix( MatrixType type ) 74 | { 75 | return m_matrices[type]; 76 | } 77 | 78 | void Renderer::multMatrix( const ga::mat4& matrix, MatrixType type ) 79 | { 80 | m_matrices[type] = m_matrices[type] * matrix; 81 | 82 | #ifdef GA_OPENFRAMEWORKS 83 | switch ( type ) { 84 | case MatrixType::MODEL: 85 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_MODELVIEW ); 86 | ofMultMatrix( matrix ); 87 | break; 88 | case MatrixType::VIEW: 89 | ofMultViewMatrix( matrix ); 90 | break; 91 | case MatrixType::PROJECTION: 92 | ofSetMatrixMode( ofMatrixMode::OF_MATRIX_PROJECTION ); 93 | ofMultMatrix( matrix ); 94 | break; 95 | } 96 | #endif 97 | } 98 | 99 | void Renderer::translate( const vec3& translation ) 100 | { 101 | multMatrix( glm::translate( mat4( 1. ), translation ) ); 102 | } 103 | 104 | void Renderer::translate( const vec2& translation ) 105 | { 106 | multMatrix( glm::translate( mat4( 1. ), vec3( translation, 0.f ) ) ); 107 | } 108 | 109 | void Renderer::rotate( const quat& rotation ) 110 | { 111 | multMatrix( glm::toMat4( rotation ), MatrixType::MODEL ); 112 | } 113 | 114 | void Renderer::scale( const vec3& scale ) 115 | { 116 | multMatrix( glm::scale( mat4( 1. ), scale ) ); 117 | } 118 | 119 | void Renderer::setGlobalColor( const Color& color ) 120 | { 121 | m_globalColor = color; 122 | #ifdef GA_OPENFRAMEWORKS 123 | ofSetColor( toOf( color ) ); 124 | #endif 125 | } 126 | 127 | const Color& Renderer::getGlobalColor() 128 | { 129 | return m_globalColor; 130 | } 131 | 132 | void Renderer::clear( const Color& color ) 133 | { 134 | glClearColor( color.r, color.g, color.b, color.a ); 135 | glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 136 | } 137 | 138 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/render.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/color.h" 3 | #include "ga/math.h" 4 | #include 5 | #include 6 | 7 | namespace ga { 8 | 9 | enum class MatrixType 10 | { 11 | MODEL, 12 | VIEW, 13 | PROJECTION 14 | }; 15 | 16 | class Renderer 17 | { 18 | public: 19 | void pushMatrix( MatrixType type = MatrixType::MODEL ); 20 | void popMatrix( MatrixType type = MatrixType::MODEL ); 21 | 22 | void setMatrix( const ga::mat4& matrix, MatrixType type = MatrixType::MODEL ); 23 | const ga::mat4& getMatrix( MatrixType type = MatrixType::MODEL ); 24 | 25 | void multMatrix( const ga::mat4& matrix, MatrixType type = MatrixType::MODEL ); 26 | 27 | // space helpers 28 | void translate( const vec3& translation ); 29 | void translate( const vec2& translation ); 30 | void rotate( const quat& rotation ); 31 | void scale( const vec3& scale ); 32 | 33 | // color / style management 34 | void setGlobalColor( const ga::Color& color ); 35 | const ga::Color& getGlobalColor(); 36 | 37 | // gl convenience functions 38 | void clear( const ga::Color& color ); // clear fbo 39 | 40 | protected: 41 | std::map m_matrices { 42 | { MatrixType::MODEL, ga::mat4( 1.f ) }, 43 | { MatrixType::VIEW, ga::mat4( 1.f ) }, 44 | { MatrixType::PROJECTION, ga::mat4( 1.f ) } }; 45 | std::map> m_matrixStack; 46 | Color m_globalColor { 1.f, 1.f, 1.f, 1.f }; 47 | }; 48 | 49 | // singleton 50 | inline Renderer& getRenderer() 51 | { 52 | static Renderer r; 53 | return r; 54 | } 55 | 56 | struct MatrixScope 57 | { 58 | MatrixScope( MatrixType type = MatrixType::MODEL, Renderer& renderer = getRenderer() ) 59 | : m_type( type ) 60 | , m_renderer( &renderer ) 61 | { 62 | m_renderer->pushMatrix( type ); 63 | } 64 | ~MatrixScope() 65 | { 66 | m_renderer->popMatrix(); 67 | } 68 | 69 | protected: 70 | MatrixType m_type; 71 | Renderer* m_renderer; 72 | }; 73 | 74 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/resource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ga { 10 | 11 | template 12 | bool loadResource( ResourceT& r, Args... args ) 13 | { 14 | return load( r, args... ); 15 | } 16 | 17 | /** 18 | * @brief resource cache class, represents a container for a resource type 19 | * organized by name (string) 20 | * 21 | * @tparam ResourceT - any class where the following function 22 | * is defined within the class's namespace: 23 | * 24 | * template 25 | * bool load( ResourceT& resource, Args... args ); 26 | * 27 | */ 28 | template 29 | class ResourceCache 30 | { 31 | public: 32 | // retrieve a Resource from the Cache by name, return nullptr if not found 33 | std::shared_ptr get( const std::string& name ) 34 | { 35 | auto it = cache.find( name ); 36 | return it != cache.end() ? it->second : nullptr; 37 | } 38 | 39 | // load a Resource into the Cache from disk, return nullptr on failure 40 | template 41 | std::shared_ptr load( const std::string& name, Args... args ) 42 | { 43 | auto rPtr = std::make_shared(); 44 | if ( ga::loadResource( *rPtr.get(), args... ) ) { 45 | cache[name] = std::move( rPtr ); 46 | return cache[name]; 47 | } 48 | return nullptr; 49 | } 50 | 51 | //// add a loaded Resource to the cache (using move semantics) 52 | //bool add( ResourceT&& resource, bool overwrite = true ) 53 | //{ 54 | // if ( !overwrite && cache.find( resource.name ) != cache.end() ) { 55 | // return false; 56 | // } 57 | // cache[name] = std::move( resource ); 58 | // return true; 59 | //} 60 | 61 | // get the names / keys of the cache 62 | std::vector getNames() 63 | { 64 | std::vector names; 65 | names.reserve( cache.size() ); 66 | for ( auto& el : cache ) { 67 | names.push_back( el.first ); 68 | } 69 | return names; 70 | } 71 | 72 | std::unordered_map> cache; 73 | }; 74 | 75 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/signal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sigslot/sigslot.hpp" 3 | 4 | namespace ga { 5 | 6 | template 7 | using Signal = sigslot::signal; 8 | 9 | using Connection = sigslot::connection; 10 | using ScopedConnection = sigslot::scoped_connection; 11 | 12 | using Observer = sigslot::observer; 13 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/defines.h" 3 | #include "ga/resource.h" 4 | 5 | #ifdef GA_OPENFRAMEWORKS 6 | #include "ofImage.h" 7 | 8 | namespace ga { 9 | class Texture : public ofTexture 10 | { 11 | }; 12 | 13 | inline bool load( Texture& texture, const std::string& path ) 14 | { 15 | return ofLoadImage( texture, path ); 16 | } 17 | #endif 18 | // todo: extend Texture for Cinder, or more bare-metal implementation 19 | 20 | // --------------------------- 21 | // Global Texture Cache 22 | // --------------------------- 23 | using TextureCache = ResourceCache; 24 | 25 | inline TextureCache& textureCache() 26 | { 27 | static TextureCache texCache; 28 | return texCache; 29 | } 30 | } -------------------------------------------------------------------------------- /src/ga/timeout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "ga/timer.h" 10 | 11 | namespace ga { 12 | 13 | // 14 | // Timeout represents a function callback 15 | // with an attached timer, for scheduling execution 16 | // 17 | 18 | struct Timeout 19 | { 20 | 21 | Timer timer; 22 | std::function callback; 23 | std::string name; 24 | void clear() 25 | { 26 | timer.clear(); 27 | callback = nullptr; 28 | name = ""; 29 | } 30 | }; 31 | 32 | // 33 | // TimeoutManager 34 | // owns a set of function callbacks that fire after a specified wait 35 | // 36 | 37 | class TimeoutManager 38 | { 39 | 40 | public: 41 | // ----------------------------------------------------------------------------------- 42 | // register a Timeout callback to be called 'delayMs' milliseconds from now 43 | // returns the name of the Timeout (generated from the function pointer if left blank) 44 | // ----------------------------------------------------------------------------------- 45 | inline std::string setTimeout( long long delayMs, std::function callback, std::string name = "" ) 46 | { 47 | if ( !callback ) return ""; 48 | 49 | m_timeouts.push_back( Timeout() ); 50 | m_timeouts.back().callback = callback; 51 | m_timeouts.back().timer.startNow( delayMs ); 52 | 53 | if ( name.empty() ) { 54 | // use the callback pointer address as the name, if name is empty 55 | // get underlying address of function ptr - https://stackoverflow.com/a/44236212/5195277 56 | std::stringstream ss; 57 | ss << *( size_t* )( char* )&m_timeouts.back().callback; 58 | name = ss.str(); 59 | } 60 | 61 | m_timeouts.back().name = name; 62 | 63 | // sort the timeouts chronologically (by end time) 64 | std::sort( m_timeouts.begin(), m_timeouts.end(), 65 | []( Timeout& a, Timeout& b ) { 66 | return a.timer.getEnd() < b.timer.getEnd(); 67 | } ); 68 | return name; 69 | } 70 | 71 | // ------------------------ 72 | // fire and remove timeouts 73 | // ------------------------ 74 | inline void updateTimeouts() 75 | { 76 | 77 | m_timeouts.erase( 78 | std::remove_if( m_timeouts.begin(), m_timeouts.end(), []( ga::Timeout& evt ) -> bool { 79 | if ( evt.timer.isDone() ) { 80 | if ( evt.callback ) { 81 | try { 82 | evt.callback(); 83 | // TODO: ga::log 84 | // ga::log::verbose << "[" << evt.name << "] Timeout fired"; 85 | } catch ( std::exception& e ) { 86 | // TODO: ga::log 87 | std::cout << "ERROR [" << evt.name << "] Timeout callback exception:\n\t" << e.what() << std::endl; 88 | } 89 | } 90 | return true; 91 | } 92 | return !evt.timer.isSet(); // prune unset events 93 | } ), 94 | m_timeouts.end() ); 95 | } 96 | 97 | // ------------------------ 98 | // cancel a timeout by name 99 | // ------------------------ 100 | inline bool cancelTimeout( std::string name ) 101 | { 102 | bool did = false; 103 | 104 | m_timeouts.erase( 105 | std::remove_if( m_timeouts.begin(), m_timeouts.end(), [&]( Timeout& evt ) -> bool { 106 | if ( evt.name == name ) { 107 | evt.clear(); 108 | return did = true; 109 | } 110 | return false; 111 | } ), 112 | m_timeouts.end() ); 113 | return did; 114 | } 115 | 116 | // --------------------------------------- 117 | // return the registered timeout callbacks 118 | // --------------------------------------- 119 | inline std::vector& getTimeouts() 120 | { 121 | return m_timeouts; 122 | } 123 | 124 | protected: 125 | std::vector m_timeouts; 126 | }; 127 | 128 | } // namespace ga 129 | -------------------------------------------------------------------------------- /src/ga/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | namespace ga { 4 | 5 | Timer::Timer() 6 | { 7 | } 8 | 9 | // -------- setters 10 | 11 | void Timer::setStart( const ga::TimePoint &timePoint ) 12 | { 13 | mBegin_t = timePoint; 14 | } 15 | 16 | void Timer::setEnd( const ga::TimePoint &timePoint ) 17 | { 18 | mEnd_t = timePoint; 19 | } 20 | 21 | void Timer::set( const ga::TimePoint &start, const ga::TimePoint &end ) 22 | { 23 | setStart( start ); 24 | setEnd( end ); 25 | } 26 | 27 | void Timer::clear() 28 | { 29 | mBegin_t = TimePoint(); 30 | mEnd_t = TimePoint(); 31 | } 32 | 33 | void Timer::startNow() 34 | { 35 | mBegin_t = Clock::now(); 36 | } 37 | 38 | void Timer::startNow( long durationMillis ) 39 | { 40 | startNow(); 41 | setEnd( mBegin_t + Millis( durationMillis ) ); 42 | } 43 | 44 | // -------- getters 45 | 46 | double Timer::duration() 47 | { 48 | return static_cast( durMillis( mBegin_t, mEnd_t ) ); 49 | } 50 | 51 | double Timer::elapsedMillis() 52 | { 53 | return static_cast( durMillis( mBegin_t, Clock::now() ) ); 54 | } 55 | 56 | double Timer::elapsedSeconds() 57 | { 58 | return elapsedMillis() * .001; 59 | } 60 | 61 | double Timer::elapsedPercent() 62 | { 63 | auto dur = duration(); 64 | if ( dur == 0. ) 65 | return 0.; 66 | else 67 | return elapsedMillis() / dur; 68 | } 69 | 70 | } // namespace ga 71 | -------------------------------------------------------------------------------- /src/ga/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ga { 8 | 9 | // --------------------- 10 | // Time typedefs and utils 11 | // --------------------- 12 | 13 | using Clock = std::chrono::steady_clock; 14 | using TimePoint = std::chrono::time_point; 15 | using Millis = std::chrono::milliseconds; 16 | 17 | // get millis since epoch for a point in time 18 | inline long timeMillis( const TimePoint& timePoint ) 19 | { 20 | return ( long )std::chrono::duration_cast( timePoint.time_since_epoch() ).count(); 21 | } 22 | 23 | // get current millis since epoch 24 | inline long timeMillis() 25 | { 26 | return ( long )std::chrono::duration_cast( Clock::now().time_since_epoch() ).count(); 27 | } 28 | 29 | // get duration in millis between two points in time 30 | inline long durMillis( const TimePoint& begin, const TimePoint& end ) 31 | { 32 | return ( long )std::chrono::duration_cast( end - begin ).count(); 33 | } 34 | 35 | // ostream operator for a point in time, as millis since epoch 36 | inline std::ostream& operator<<( std::ostream& stream, const TimePoint& timePoint ) 37 | { 38 | return stream << timeMillis( timePoint ); 39 | } 40 | 41 | // --------------------- 42 | // Timer class 43 | // --------------------- 44 | 45 | class Timer 46 | { 47 | public: 48 | Timer(); 49 | 50 | // -------- setters 51 | 52 | virtual void setStart( const TimePoint& time ); 53 | virtual void setEnd( const TimePoint& time ); 54 | 55 | virtual void set( const TimePoint& start, const TimePoint& end ); 56 | virtual void clear(); 57 | 58 | virtual void startNow(); // ends at set end time point 59 | virtual void startNow( long durationMillis ); // ends at calc'd end point from now 60 | 61 | // -------- getters 62 | 63 | inline virtual const TimePoint& getStart() const { return mBegin_t; } 64 | inline virtual const TimePoint& getEnd() const { return mEnd_t; } 65 | 66 | inline virtual bool hasStart() { return mBegin_t != TimePoint(); } 67 | inline virtual bool hasEnd() { return mEnd_t != TimePoint(); } 68 | inline virtual bool isSet() { return hasStart() && hasEnd(); } 69 | inline virtual bool isStarted() { return hasStart() && Clock::now() >= mBegin_t; } 70 | inline virtual bool isDone() { return hasEnd() && Clock::now() >= mEnd_t; } 71 | inline virtual bool isActive() { return hasStart() && Clock::now() >= mBegin_t && ( hasEnd() ? Clock::now() < mEnd_t : true ); } 72 | 73 | double duration(); 74 | double elapsedMillis(); 75 | double elapsedSeconds(); 76 | double elapsedPercent(); 77 | 78 | protected: 79 | TimePoint mBegin_t, mEnd_t; 80 | }; 81 | 82 | } // namespace ga 83 | -------------------------------------------------------------------------------- /src/ga/transform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/math.h" 3 | 4 | namespace ga { 5 | 6 | /** 7 | * @brief Transform represents a basic model matrix: translate * rotate * scale 8 | * Stored internally as ga::mat4 (4x4 matrix) as well as: 9 | * - ga::vec3 translation 10 | * - ga::quat rotation 11 | * - ga::vec3 scale 12 | * 13 | */ 14 | class Transform 15 | { 16 | 17 | public: 18 | Transform() 19 | : m_transform( 1.f ), m_translation( 0.f ), m_rotation(), m_scale( 1.f ) 20 | { 21 | flagDirty(); 22 | } 23 | 24 | Transform( const mat4& transform ) 25 | { 26 | setMatrix( transform ); 27 | } 28 | 29 | Transform( const vec3& translation, const quat& rotation, const vec3& scale ) 30 | : m_transform( 1.f ), m_translation( translation ), m_scale( scale ), m_rotation( rotation ) 31 | { 32 | flagDirty(); 33 | } 34 | 35 | // setters 36 | 37 | inline Transform& setMatrix( const mat4& matrix ) 38 | { 39 | m_transform = matrix; 40 | decompose(); 41 | return *this; 42 | } 43 | 44 | inline Transform& set( const mat4& matrix ) 45 | { 46 | return setMatrix( matrix ); 47 | } 48 | 49 | inline Transform& setTranslation( const vec3& translation ) 50 | { 51 | m_translation = translation; 52 | flagDirty(); 53 | return *this; 54 | } 55 | 56 | inline Transform& setTranslation( const vec2& translation ) 57 | { 58 | return setTranslation( vec3 { translation.x, translation.y, m_translation.z } ); 59 | } 60 | 61 | inline Transform& setRotation( const quat& rotation ) 62 | { 63 | m_rotation = rotation; 64 | flagDirty(); 65 | return *this; 66 | } 67 | 68 | inline Transform& setScale( const vec3& scale ) 69 | { 70 | m_scale = scale; 71 | flagDirty(); 72 | return *this; 73 | } 74 | 75 | inline Transform& setEulerRotation( const vec3& radians ) 76 | { 77 | return setRotation( quat( radians ) ); 78 | } 79 | 80 | // getters 81 | 82 | inline const vec3& getTranslation() const { return m_translation; } 83 | inline const quat& getRotation() const { return m_rotation; } 84 | inline const vec3& getScale() const { return m_scale; } 85 | inline vec3 getEulerRotation() const { return glm::eulerAngles( m_rotation ); } 86 | 87 | inline const mat4& getMatrix() const 88 | { 89 | clean(); 90 | return m_transform; 91 | } 92 | 93 | // helpers 94 | 95 | inline Transform& translate( const vec3& vec ) { return setTranslation( m_translation + vec ); } 96 | inline Transform& translate( const vec2& vec ) { return setTranslation( { m_translation.x + vec.x, m_translation.y + vec.y, m_translation.z } ); } 97 | inline Transform& translateX( float x ) { return setTranslation( { m_translation.x + x, m_translation.y, m_translation.z } ); } 98 | inline Transform& translateY( float y ) { return setTranslation( { m_translation.x, m_translation.y + y, m_translation.z } ); } 99 | inline Transform& translateZ( float z ) { return setTranslation( { m_translation.x, m_translation.y, m_translation.z + z } ); } 100 | inline Transform& scale( const vec3& pct ) { return setScale( m_scale * pct ); } 101 | inline Transform& scale( float pct ) { return setScale( m_scale * pct ); } 102 | inline Transform& rotate( const vec3& eulerRadians ) { return setEulerRotation( eulerRadians + glm::eulerAngles( m_rotation ) ); } 103 | inline Transform& rotateAround( const vec3& axis, float radians ) { return rotate( axis * radians ); } 104 | inline Transform& rotateX( float radians ) { return rotateAround( { 1, 0, 0 }, radians ); } 105 | inline Transform& rotateY( float radians ) { return rotateAround( { 0, 1, 0 }, radians ); } 106 | inline Transform& rotateZ( float radians ) { return rotateAround( { 0, 0, 1 }, radians ); } 107 | 108 | // implicit conversion to mat4 109 | 110 | inline operator const mat4&() const { return getMatrix(); } 111 | inline Transform& operator=( const mat4& m ) { return setMatrix( m ); } 112 | inline Transform operator*( const mat4& m ) { return Transform( getMatrix() * m ); } 113 | inline Transform operator*( const Transform& t ) { return Transform( getMatrix() * t.getMatrix() ); } 114 | 115 | protected: 116 | vec3 m_translation; 117 | vec3 m_scale; 118 | quat m_rotation; 119 | mat4 m_transform; 120 | bool m_dirty; // matrix needs rebuilding 121 | 122 | inline void flagDirty() { m_dirty = true; } 123 | 124 | inline void clean() const 125 | { 126 | if ( m_dirty ) { 127 | // hack to keep internal matrix synced while keeping const-ness 128 | auto* self = const_cast( this ); 129 | // rebuild model matrix - translate, rotate, scale 130 | self->m_transform = glm::translate( mat4( 1.0 ), m_translation ); 131 | self->m_transform = m_transform * glm::toMat4( m_rotation ); 132 | self->m_transform = glm::scale( m_transform, m_scale ); 133 | self->m_dirty = false; 134 | } 135 | } 136 | 137 | inline void decompose() 138 | { 139 | // decomposes the internal transform matrix into translation, rotation, scale components 140 | vec3 skew; 141 | vec4 perspective; // unused here but needed by the method signature 142 | glm::decompose( m_transform, m_scale, m_rotation, m_translation, skew, perspective ); 143 | m_dirty = false; 144 | } 145 | }; 146 | 147 | } // namespace ga -------------------------------------------------------------------------------- /src/ga/tween.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ga/easing.h" 3 | #include "ga/math.h" 4 | #include "ga/signal.h" 5 | #include "ga/timer.h" 6 | 7 | namespace ga { 8 | 9 | class TweenBase 10 | { 11 | public: 12 | friend class Timeline; 13 | virtual ~TweenBase() = default; 14 | void setName( const std::string& name ) { m_name = name; } 15 | const std::string& getName() { return m_name; } 16 | 17 | protected: 18 | std::string m_name = ""; 19 | virtual bool update_() = 0; 20 | virtual bool isDone_() = 0; 21 | virtual bool isStarted_() = 0; 22 | virtual void end( bool fireCallback ) = 0; 23 | }; 24 | 25 | // Tween class 26 | template 27 | class Tween : public TweenBase, public Timer 28 | { 29 | public: 30 | Tween( const T& startVal, const T& endVal, std::function easeFn ) 31 | : Timer() 32 | , m_startVal( startVal ) 33 | , m_endVal( endVal ) 34 | , m_easeFn( easeFn ) 35 | , m_onDone( nullptr ) 36 | , m_boundPtr( nullptr ) 37 | { 38 | } 39 | Tween( const T& startVal, const T& endVal, std::function easeFn, std::function onDone ) 40 | : Timer() 41 | , m_startVal( startVal ) 42 | , m_endVal( endVal ) 43 | , m_easeFn( easeFn ) 44 | , m_onDone( onDone ) 45 | , m_boundPtr( nullptr ) 46 | { 47 | } 48 | Tween() {}; 49 | 50 | friend class Timeline; 51 | 52 | Tween& set( const T& startVal, const T& endVal, std::function easeFn, std::function onDone = nullptr ) 53 | { 54 | return setStartVal( startVal ).setEndVal( endVal ).setEaseFn( easeFn ).setOnDone( onDone ); 55 | } 56 | 57 | Tween& setStartVal( const T& startVal ) 58 | { 59 | m_startVal = startVal; 60 | return *this; 61 | } 62 | Tween& setEndVal( const T& endVal ) 63 | { 64 | m_endVal = endVal; 65 | return *this; 66 | } 67 | Tween& setEaseFn( ga::EaseType easeFnType ) 68 | { 69 | m_easeFnType = easeFnType; 70 | m_easeFn = ga::easeFn( easeFnType ); 71 | return *this; 72 | } 73 | Tween& setEaseFn( std::function easeFn ) 74 | { 75 | m_easeFnType = ga::EaseType::CUSTOM; 76 | m_easeFn = easeFn; 77 | return *this; 78 | } 79 | // assign callback on done 80 | Tween& setOnDone( std::function onDone ) 81 | { 82 | m_onDone = onDone; 83 | return *this; 84 | } 85 | 86 | // bind a ptr to update with the animation value 87 | Tween& bind( T* ptr ) 88 | { 89 | m_boundPtr = ptr; 90 | return *this; 91 | } 92 | Tween& unbind() 93 | { 94 | m_boundPtr = nullptr; 95 | return *this; 96 | } 97 | 98 | const T& getStartVal() const { return m_startVal; } 99 | const T& getEndVal() const { return m_endVal; } 100 | 101 | const T& getValue() const { return m_val; } 102 | 103 | // ends at preset end time 104 | virtual void startNow() override 105 | { 106 | m_val = m_startVal; 107 | Timer::startNow(); 108 | } 109 | 110 | // recalculates end time from now 111 | virtual void startNow( long durationMillis ) override 112 | { 113 | m_val = m_startVal; 114 | Timer::startNow( durationMillis ); 115 | } 116 | 117 | void startAfterDelay( long delayMs, long durationMs ) 118 | { 119 | auto start = ga::Clock::now() + ga::Millis( delayMs ); 120 | Timer::setStart( start ); 121 | Timer::setEnd( start + ga::Millis( durationMs ) ); 122 | } 123 | 124 | // update and return current value (call at frame rate when using bound ptr) 125 | const T& update() 126 | { 127 | if ( Timer::isSet() && Timer::isActive() ) { // current time is between begin and end 128 | m_val = ga::interpolate( m_startVal, m_endVal, Timer::elapsedPercent(), m_easeFn, true ); 129 | updateBoundPtr(); 130 | } 131 | if ( Timer::isDone() ) { 132 | end(); 133 | } 134 | return m_val; 135 | } 136 | 137 | // end animation now (optionally firing callback), return end value 138 | const T& endNow( bool fireCallback = true ) 139 | { 140 | end( fireCallback ); 141 | return m_val; 142 | } 143 | 144 | protected: 145 | T m_val, m_startVal, m_endVal; // val tweens from startVal to endVal 146 | std::function m_easeFn; // easing function: p = f(t) - see Ease.h 147 | ga::EaseType m_easeFnType; 148 | std::function m_onDone; // callback 149 | T* m_boundPtr; 150 | bool updateBoundPtr() 151 | { 152 | auto ptr = m_boundPtr; 153 | if ( ptr ) { 154 | try { 155 | *ptr = m_val; 156 | } catch ( std::exception& e ) { 157 | // todo: log exception 158 | m_boundPtr = nullptr; // clear pointer since it's broken 159 | } 160 | } 161 | return ptr != nullptr; 162 | } 163 | 164 | void end( bool fireCallback = true ) override 165 | { 166 | m_val = m_endVal; 167 | updateBoundPtr(); 168 | Timer::clear(); 169 | // fire on-done signal 170 | if ( fireCallback && m_onDone ) { 171 | // callback 172 | try { 173 | m_onDone(); 174 | } catch ( std::exception& e ) { 175 | // todo: log exception 176 | m_onDone = nullptr; // clear callback since it's broken 177 | } 178 | } 179 | } 180 | 181 | bool update_() override 182 | { 183 | // Timer::update() 184 | if ( Timer::isSet() ) { 185 | if ( Timer::isActive() ) { // current time is between begin and end 186 | m_val = ga::interpolate( m_startVal, m_endVal, Timer::elapsedPercent(), m_easeFn, true ); 187 | } else if ( Timer::isDone() ) { 188 | m_val = m_endVal; 189 | } 190 | updateBoundPtr(); 191 | } 192 | return isSet() && !isDone(); 193 | } 194 | 195 | bool isDone_() override 196 | { 197 | return Timer::isDone(); 198 | } 199 | 200 | bool isStarted_() override 201 | { 202 | return Timer::isStarted(); 203 | } 204 | }; 205 | 206 | } // namespace ga 207 | -------------------------------------------------------------------------------- /src/ga/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ga { 11 | 12 | // string utilities 13 | // ---------------------- 14 | 15 | inline std::vector splitString( const std::string& str, char delim = ' ' ) 16 | { 17 | std::vector result; 18 | std::stringstream ss( str ); 19 | std::string token; 20 | while ( std::getline( ss, token, delim ) ) { 21 | result.push_back( token ); 22 | } 23 | return result; 24 | } 25 | 26 | inline std::string toLower( std::string str ) 27 | { 28 | std::transform( str.begin(), str.end(), str.begin(), []( char c ) { return std::tolower( c, std::locale() ); } ); 29 | return str; 30 | } 31 | 32 | inline std::string toUpper( std::string str ) 33 | { 34 | std::transform( str.begin(), str.end(), str.begin(), []( char c ) { return std::toupper( c, std::locale() ); } ); 35 | 36 | return str; 37 | } 38 | 39 | // Helper RAII class to auto cleanup / do something on early return 40 | struct scope_guard 41 | { 42 | scope_guard( const std::function& f ) 43 | : _f( f ) 44 | { 45 | } 46 | ~scope_guard() 47 | { 48 | if ( _f ) 49 | _f(); 50 | } 51 | 52 | protected: 53 | std::function _f; 54 | }; 55 | 56 | } // namespace ga 57 | -------------------------------------------------------------------------------- /src/ga/uuid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "crossguid/crossguid.hpp" 3 | 4 | // universally unique identifiers (aka guid - globally unique id) 5 | // https://en.wikipedia.org/wiki/Universally_unique_identifier 6 | // 128-bit ids, in string hex format: "7bcd757f-5b10-4f9b-af69-1a1f226f3b3e" 7 | 8 | namespace ga { 9 | 10 | using Uuid = xg::Guid; // cross-platform uuid class 11 | inline Uuid newUuid() 12 | { 13 | return xg::newGuid(); 14 | } 15 | } // namespace ga --------------------------------------------------------------------------------