diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f9c8ee5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build catch2 + + - name: Configure + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DTWEENY_BUILD_TESTS=ON + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4ae9c..babe564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,28 @@ # Tweeny Changelog +- Version 4.0.0 + - **Breaking:** Requires C++17 + - **Breaking:** `tweeny::from()` returns a builder; call `.build()` to create a tween + - **Breaking:** Tweens are immutable after `build()` (keyframes, durations, and easings cannot be changed) + - **Breaking:** Durations must be `uint32_t` (`during(60U)`); `step()` accepts `int32_t` frame deltas only (no percentage mode) + - **Breaking:** `seek()` accepts `uint32_t` absolute frame positions only (no percentage mode) + - **Breaking:** Multi-value tweens return `std::tuple` instead of `std::array` + - **Breaking:** Callbacks use `on(event::…)` with `event::response` return values instead of `onStep()` / `onSeek()` + - **Breaking:** `forward()` / `backward()` removed; use negative `step()` values to move backward + - **Breaking:** Headers moved to `include/tweeny/`; include as `#include ` + - **Breaking:** Removed `easing::enumerated` and string-based `via("linear")` easing selection + - New event types: `complete`, `keyframeEnter`, `keyframeLeave`, `update` + - New methods: `peek()`, `peek(frame)`, `progress()`, `jump(keyframe)` + - Restructured internals under `tweeny::detail`; easing split into per-function headers + - Added Catch2 test suite and Doxygen manual with v3-to-v4 migration guide + - CMake: C++17 enforcement, `FILE_SET HEADERS`, optional tests and single-header target (via `uvx` + quom) + - Fixed exponential easing endpoints (`position == 0` / `== 1`) to match Penner behavior + - Expanded tests: Penner reference samples for bundled easings (direct + `via`); `def`/`stepped` (including multi-keyframe stepped); seek/step/jump vs `peek` consistency + - GitHub Actions CI (Ubuntu, Catch2, build + ctest) + - `scripts/create-release.sh` — release from an existing tag (docs sync to `gh-pages`, single-header asset, GitHub Release) + - README: CMake install + `find_package(Tweeny)`; fixed installed include interface so `#include ` works after install + - Docs: `event::update`, `progress()`, honest zero-`during()` behavior; Doxygen styling aligned with the site + - Removed sandbox CMake option/target + - Version 3.2.1 - Adds `` as dependency diff --git a/CMakeLists.txt b/CMakeLists.txt index 07058ec..062f0ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,73 +25,83 @@ # target that uses tweeny, a simple `target_link_libraries(target tweeny)` is sufficient to set up include and link # instructions. -cmake_minimum_required(VERSION 3.0...3.28) +cmake_minimum_required(VERSION 3.23...3.28) cmake_policy(SET CMP0063 NEW) -project(Tweeny LANGUAGES CXX VERSION 3.2.1) +project(Tweeny LANGUAGES CXX VERSION 4.0.0) + +# Enforce C++17 for targets built in this project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) # Setup variables and options option(TWEENY_BUILD_DOCUMENTATION "Attempts to build the documentation. You'll need doxygen and graphviz installed" OFF) -option(TWEENY_BUILD_SINGLE_HEADER "Joins together all header files in a single one. Needs Python 3.6 and quom installed" OFF) -option(TWEENY_BUILD_SANDBOX "Adds a 'sandbox' target that links to tweeny. Useful when exploring tweeny" OFF) +option(TWEENY_BUILD_SINGLE_HEADER "Joins together all header files in a single one. Needs uv (https://docs.astral.sh/uv/) to run quom via uvx" OFF) +option(TWEENY_BUILD_TESTS "Build Tweeny tests (requires Catch2 v3 to be findable via CMake)" OFF) # The library target add_library(tweeny INTERFACE) -# Specify the C++ features a compiler should have to use this library. -if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") - target_compile_features(tweeny - INTERFACE - cxx_auto_type - cxx_variadic_templates - cxx_lambdas - cxx_nullptr - cxx_right_angle_brackets - cxx_static_assert - cxx_template_template_parameters - ) -else() - list(APPEND CMAKE_CXX_FLAGS -std=c++11) -endif() +# Require C++17 for consumers of this interface library. +target_compile_features(tweeny INTERFACE cxx_std_17) + +# Provide namespaced alias for consumers. +add_library(tweeny::tweeny ALIAS tweeny) # Set up include directories target_include_directories(tweeny INTERFACE - $ - $ + $ + $ +) + +# Attach headers to the interface target for IDEs and installation +target_sources(tweeny INTERFACE + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/include + FILES + include/tweeny/event.h + include/tweeny/tweeny.h + include/tweeny/tween.h + include/tweeny/tween.tcc + include/tweeny/easing.h + include/tweeny/detail/event.h + include/tweeny/detail/interpolate.h + include/tweeny/detail/key-frame.h + include/tweeny/detail/tuple-utilities.h + include/tweeny/detail/tween-value.h + include/tweeny/detail/value-container.h + include/tweeny/detail/easing/back.h + include/tweeny/detail/easing/bounce.h + include/tweeny/detail/easing/circular.h + include/tweeny/detail/easing/cubic.h + include/tweeny/detail/easing/def.h + include/tweeny/detail/easing/elastic.h + include/tweeny/detail/easing/exponential.h + include/tweeny/detail/easing/linear.h + include/tweeny/detail/easing/quadratic.h + include/tweeny/detail/easing/quartic.h + include/tweeny/detail/easing/quintic.h + include/tweeny/detail/easing/sinusoidal.h + include/tweeny/detail/easing/stepped.h ) # Set up install include(GNUInstallDirs) -install(TARGETS tweeny EXPORT TweenyTargets) -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tweeny) +install(TARGETS tweeny EXPORT TweenyTargets FILE_SET HEADERS) # Set up export and config include(cmake/SetupExports.cmake) if (TWEENY_BUILD_DOCUMENTATION) - add_subdirectory(doc) -endif() + add_subdirectory(src/doc) +endif () -# This library is a convenience library to force files appear in the IDE properly. -add_library(tweeny-dummy - include/tweeny.h - include/tweeny.tcc - include/tween.h - include/tween.tcc - include/tweenone.tcc - include/tweenpoint.h - include/tweenpoint.tcc - include/tweentraits.h - include/easing.h - include/easingresolve.h - include/int2type.h - include/dispatcher.h) -set_target_properties(tweeny-dummy PROPERTIES LINKER_LANGUAGE CXX EXCLUDE_FROM_ALL TRUE) +if (TWEENY_BUILD_TESTS) + enable_testing() + add_subdirectory(src/tests) +endif () if (TWEENY_BUILD_SINGLE_HEADER) - include(cmake/GenerateSingleHeader.cmake) -endif() - -if (TWEENY_BUILD_SANDBOX) - add_executable(sandbox src/sandbox.cc) - target_link_libraries(sandbox tweeny) -endif() + include(cmake/GenerateSingleHeader.cmake) +endif () diff --git a/README-3.md b/README-3.md new file mode 100644 index 0000000..6988778 --- /dev/null +++ b/README-3.md @@ -0,0 +1,86 @@ +> **Note:** This is the README for Tweeny 3.x, kept for reference. For Tweeny 4.x, see [README.md](README.md). + +# Tweeny + + Packaging status + + +Tweeny is an inbetweening library designed for the creation of complex animations for games and other beautiful interactive software. It leverages features of modern C++ to empower developers with an intuitive API for declaring tweenings of any type of value, as long as they support arithmetic operations. + +The goal of Tweeny is to provide means to create fluid interpolations when animating position, scale, rotation, frames or other values of screen objects, by setting their values as the tween starting point and then, after each tween step, plugging back the result. + +**It features**: + +- A descriptive and (hopefully) intuitive API, +- 30+ easing functions, +- Allows custom easing functions, +- Multi-point tweening, +- Simultaneous tween of heterogeneous value sets, +- Timeline-like usage (allows seeking to any point), +- Header-only +- Zero external dependencies +- Steps forwards or backwards :) +- Accepts lambdas, functors and functions as step and seek callbacks + +**Obligatory hello world example**: + +Linearly interpolate character by character from the word *hello* to *world* in `50` steps: + +```cpp +auto helloworld = tweeny::from('h','e','l','l','o').to('w','o','r','l','d').during(50); +for (int i = 0; i < 50; i++) { + for (char c : helloworld.step(1)) { printf("%c", c); } + printf("\n"); +} +``` + +Relevant code: + +- **1**: create the tween instance starting with characters of the `hello` word, adds a tween target with the chars of the `world` word and specify it should reach it in `50` steps. +- **3**: move the tween forward by one step. Use the return value of it (which ill be a `std::array` in this case) to set up a for loop iterating in each char, printing it. + +## Installation methods: + +**Using your package manager** + +There are some packages for tweeny made by some great people. Repology has a list of them and their versions [here](https://repology.org/metapackage/tweeny/versions). Thanks, great people! + +**Not installing it** + +You just need to adjust your include path to point to the `include/` folder after you've cloned this repository. + +**Copying the `include` folder:** + +Tweeny itself is a header only library. You can copy the `include/` folder into your project folder and then include from it: `#include "tweeny/tweeny.h"` + +**Copying the `tweeny-.h` header** + +Since version 3.1.1 tweeny releases include a single-header file with all the necessary code glued together. Simply drop it on your project and/or adjust the include path and then `#include "tweeny-3.1.1.h"`. + +**CMake subproject** + +This is useful if you are using CMake already. Copy the whole tweeny project and include it in a top-level `CMakeLists.txt` file and then use `target_link_libraries` to add it to your target: + +``` +add_subdirectory(tweeny) +target_link_libraries(yourtarget tweeny) +``` +This will add the `include/` folder to your search path, and you can `#include "tweeny.h"`. + +## Doxygen documentation + +This library is documented using Doxygen. If you intend to generate docs, specify the flag `TWEENY_BUILD_DOCUMENTATION` when generating CMake build files (e.g: `cmake .. -DTWEENY_BUILD_DOCUMENTATION=1`). You will need doxygen installed. + +## Contributing + +Tweeny is open-source, meaning that it is open to modifications and contrubutions from the community (you are very much encouraged to do so!). However, we'd appreciate if you follow these guidelines: + +- Don't use `PascalCase` nor `snake_case` in names +- Use `camelCase`, but try to avoid multi word names as hard as possible +- Document code using Doxygen +- Implementation details should go inside `tweeny::detail` namespace. +- Template implementations should go into a `.tcc` file + +## Examples: + +Demo code showcasing some of Tweeny features can be seen in the [tweeny-demo](https://github.com/mobius3/tweeny-demos) repository. This repository also has instructions on how to build them. \ No newline at end of file diff --git a/README.md b/README.md index 1a4c110..15e1cab 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,153 @@ # Tweeny - -**Request for comments**: Tweeny is going through a major rewrite. If you're interested in contributing, please see [this PR](https://github.com/mobius3/tweeny/pull/49). - Packaging status -Tweeny is an inbetweening library designed for the creation of complex animations for games and other beautiful interactive software. It leverages features of modern C++ to empower developers with an intuitive API for declaring tweenings of any type of value, as long as they support arithmetic operations. +Tweeny is a modern C++ inbetweening library for creating complex animations in games and other interactive software. It provides a type-safe, fluent API for declaring interpolations of any value that supports arithmetic operations. -The goal of Tweeny is to provide means to create fluid interpolations when animating position, scale, rotation, frames or other values of screen objects, by setting their values as the tween starting point and then, after each tween step, plugging back the result. +The goal of Tweeny is to make it easy to animate position, scale, rotation, color, or any other property: set the starting values, step the tween each frame, and plug the result back into your object. **It features**: -- A descriptive and (hopefully) intuitive API, -- 30+ easing functions, -- Allows custom easing functions, -- Multi-point tweening, -- Simultaneous tween of heterogeneous value sets, -- Timeline-like usage (allows seeking to any point), -- Header-only -- Zero external dependencies -- Steps forwards or backwards :) -- Accepts lambdas, functors and functions as step and seek callbacks +- A fluent builder API with compile-time safety +- 30+ easing functions, plus support for custom easings +- Multi-point keyframe animations +- Simultaneous tweening of heterogeneous value sets +- Timeline-like control (`seek`, `jump`, backward stepping) +- An event system for step, seek, jump, update, completion, and keyframe enter/leave +- Header-only, zero external dependencies +- C++17 **Obligatory hello world example**: -Linearly interpolate character by character from the word *hello* to *world* in `50` steps: +Linearly interpolate character by character from *hello* to *world* in `50` frames: ```cpp -auto helloworld = tweeny::from('h','e','l','l','o').to('w','o','r','l','d').during(50); +#include + +auto helloworld = tweeny::from('h', 'e', 'l', 'l', 'o') + .to('w', 'o', 'r', 'l', 'd') + .during(50U) + .build(); + for (int i = 0; i < 50; i++) { - for (char c : helloworld.step(1)) { printf("%c", c); } - printf("\n"); + auto [w, o, r, l, d] = helloworld.step(1); + printf("%c%c%c%c%c\n", w, o, r, l, d); } ``` -Relevant code: +A few more patterns: + +```cpp +using tweeny::easing; + +// Easing +auto smooth = tweeny::from(0.0f) + .to(100.0f) + .via(easing::quadraticInOut) + .during(60U) + .build(); + +// Multi-segment keyframe animation +auto path = tweeny::from(0) + .to(50).via(easing::linear).during(30U) + .to(100).via(easing::bounceOut).during(30U) + .build(); + +// Events +auto tween = tweeny::from(0).to(100).during(60U).build(); +tween.on(tweeny::event::update, [](auto& t) { + printf("now at %d\n", t.peek()); + return tweeny::event::response::ok; +}); +tween.on(tweeny::event::complete, [](auto& t) { + printf("done at %d\n", t.peek()); + return tweeny::event::response::ok; +}); +``` + +## Migrating from 3.x + +Tweeny 4.x introduces breaking API changes. The previous README for version 3.x is kept in [README-3.md](README-3.md). + +Key differences: + +- `tweeny::from(...)` returns a **builder** — call `.build()` to get a tween +- Durations use `uint32_t` (`60U`), steps use `int32_t` (negative values step backward) +- Callbacks use `on(event::step, ...)` instead of `onStep()` / `onSeek()` +- Multi-value tweens return `std::tuple` (use structured bindings) -- **1**: create the tween instance starting with characters of the `hello` word, adds a tween target with the chars of the `world` word and specify it should reach it in `50` steps. -- **3**: move the tween forward by one step. Use the return value of it (which ill be a `std::array` in this case) to set up a for loop iterating in each char, printing it. +Build the Doxygen documentation (`-DTWEENY_BUILD_DOCUMENTATION=ON`) for the full migration guide. -## Installation methods: +## Installation **Using your package manager** There are some packages for tweeny made by some great people. Repology has a list of them and their versions [here](https://repology.org/metapackage/tweeny/versions). Thanks, great people! -**Not installing it** +**Copying the `include/` folder** -You just need to adjust your include path to point to the `include/` folder after you've cloned this repository. +Tweeny is header-only. Copy `include/` into your project and include: -**Copying the `include` folder:** - -Tweeny itself is a header only library. You can copy the `include/` folder into your project folder and then include from it: `#include "tweeny/tweeny.h"` +```cpp +#include +``` -**Copying the `tweeny-.h` header** +**Single-header file** -Since version 3.1.1 tweeny releases include a single-header file with all the necessary code glued together. Simply drop it on your project and/or adjust the include path and then `#include "tweeny-3.1.1.h"`. +Tweeny releases include a single-header file with all the necessary code glued together. Simply drop it on your project and/or adjust the include path and then `#include "tweeny-.h"` (eg `#include "tweeny-4.0.0.h"`). **CMake subproject** -This is useful if you are using CMake already. Copy the whole tweeny project and include it in a top-level `CMakeLists.txt` file and then use `target_link_libraries` to add it to your target: +```cmake +add_subdirectory(tweeny) +target_link_libraries(yourtarget PRIVATE tweeny::tweeny) +``` + +This adds the `include/` directory to your target and requires C++17. + +**CMake install + `find_package`** +Install the headers and CMake package config, then consume Tweeny from another project: + +```sh +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build +cmake --install build --prefix /path/to/prefix ``` -add_subdirectory(tweeny) -target_link_libraries(yourtarget tweeny) + +```cmake +find_package(Tweeny 4 CONFIG REQUIRED) +target_link_libraries(yourtarget PRIVATE tweeny::tweeny) ``` -This will add the `include/` folder to your search path, and you can `#include "tweeny.h"`. -## Doxygen documentation +Point CMake at the install prefix if needed (`CMAKE_PREFIX_PATH`). After linking `tweeny::tweeny`, include as usual: + +```cpp +#include +``` + +## Documentation + +The library is documented with Doxygen. Build it with: + +```sh +cmake -B build -DTWEENY_BUILD_DOCUMENTATION=ON +cmake --build build --target doc +``` -This library is documented using Doxygen. If you intend to generate docs, specify the flag `TWEENY_BUILD_DOCUMENTATION` when generating CMake build files (e.g: `cmake .. -DTWEENY_BUILD_DOCUMENTATION=1`). You will need doxygen installed. +Easing function visualizations: [easings.net](http://easings.net/) ## Contributing -Tweeny is open-source, meaning that it is open to modifications and contrubutions from the community (you are very much encouraged to do so!). However, we'd appreciate if you follow these guidelines: +Tweeny is open-source and welcomes contributions. Please follow these guidelines: - Don't use `PascalCase` nor `snake_case` in names -- Use `camelCase`, but try to avoid multi word names as hard as possible +- Use `camelCase`, but try to avoid multi-word names as hard as possible - Document code using Doxygen -- Implementation details should go inside `tweeny::detail` namespace. +- Implementation details should go inside the `tweeny::detail` namespace - Template implementations should go into a `.tcc` file -## Examples: +## License -Demo code showcasing some of Tweeny features can be seen in the [tweeny-demo](https://github.com/mobius3/tweeny-demos) repository. This repository also has instructions on how to build them. \ No newline at end of file +Tweeny is licensed under the MIT License. See [LICENSE](LICENSE). diff --git a/cmake/DedupeLicense.cmake b/cmake/DedupeLicense.cmake new file mode 100644 index 0000000..bc088b2 --- /dev/null +++ b/cmake/DedupeLicense.cmake @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.23) + +if (NOT INPUT OR NOT OUTPUT) + message(FATAL_ERROR "DedupeLicense.cmake requires -DINPUT= and -DOUTPUT=") +endif() + +file(READ "${CMAKE_CURRENT_LIST_DIR}/LICENSE_HEADER" TWEENY_LICENSE_HEADER) +file(READ "${INPUT}" CONTENT) + +string(REPLACE "${TWEENY_LICENSE_HEADER}" "" CONTENT "${CONTENT}") +string(PREPEND CONTENT "${TWEENY_LICENSE_HEADER}") +file(WRITE "${OUTPUT}" "${CONTENT}") diff --git a/cmake/GenerateSingleHeader.cmake b/cmake/GenerateSingleHeader.cmake index 08ff5e1..ff407c6 100644 --- a/cmake/GenerateSingleHeader.cmake +++ b/cmake/GenerateSingleHeader.cmake @@ -1,22 +1,21 @@ # This cmake script is used to generate a single header file with all of tweeny -find_package(Python 3.6 QUIET) +find_program(UVX_EXECUTABLE NAMES uvx REQUIRED) -if (NOT PYTHON_FOUND) - message(STATUS "Python 3.6 not found. Single-header include file will NOT be created") - return() -endif() +set(_single_header_dir "${CMAKE_CURRENT_BINARY_DIR}/single-header") +set(_single_header_file "${_single_header_dir}/tweeny-${Tweeny_VERSION}.h") +set(_single_header_tmp "${_single_header_file}.tmp") -find_program(QUOM_EXECUTABLE NAMES quom) -if (QUOM_EXECUTABLE-NOTFOUND) - message(STATUS "quom program not found. Install it with pip or easy_install") - return() -endif() - -file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/single-header) +file(MAKE_DIRECTORY "${_single_header_dir}") add_custom_target(single-header - COMMAND - ${QUOM_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/include/tweeny.h ${CMAKE_CURRENT_BINARY_DIR}/single-header/tweeny-${Tweeny_VERSION}.h + COMMAND ${UVX_EXECUTABLE} quom + ${CMAKE_CURRENT_SOURCE_DIR}/include/tweeny/tweeny.h + ${_single_header_tmp} + COMMAND ${CMAKE_COMMAND} + -DINPUT=${_single_header_tmp} + -DOUTPUT=${_single_header_file} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DedupeLicense.cmake + COMMAND ${CMAKE_COMMAND} -E remove ${_single_header_tmp} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating single header file" -) \ No newline at end of file +) diff --git a/cmake/LICENSE_HEADER b/cmake/LICENSE_HEADER new file mode 100644 index 0000000..67a0ee1 --- /dev/null +++ b/cmake/LICENSE_HEADER @@ -0,0 +1,23 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/cmake/SetupExports.cmake b/cmake/SetupExports.cmake index 692cc93..8592e28 100644 --- a/cmake/SetupExports.cmake +++ b/cmake/SetupExports.cmake @@ -27,13 +27,17 @@ include(CMakePackageConfigHelpers) include(GNUInstallDirs) # Setup install of exported targets -install(EXPORT TweenyTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tweeny) +install( + EXPORT TweenyTargets + NAMESPACE tweeny:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tweeny +) # Macro to write config write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/TweenyConfigVersion.cmake" VERSION ${Tweeny_VERSION} - COMPATIBILITY AnyNewerVersion + COMPATIBILITY SameMajorVersion ) # Setup install of version config diff --git a/doc/MANUAL.dox b/doc/MANUAL.dox deleted file mode 100644 index 62ff779..0000000 --- a/doc/MANUAL.dox +++ /dev/null @@ -1,238 +0,0 @@ -namespace tweeny { -/** - @page manual Tweeny Manual - - This document is the manual for Tweeny. It walks you through all the important steps when creating and controlling tweens. - - @section creating Creating a tween - - Tweeny can interpolate a single value, a set of values and a set of values with different types. For each of these cases, you create a tween using the same method: tweeny::from. The argument types you pass to it defines the tween itself which in turn defines argument number and types for tween::to, tween::during and tween::via, as well as the callback argument types. - - Here is how you create a tween: - - @code - // Creates a single-valued tween - auto tween = tweeny::from(0); - - // Creates a multi-valued tween - auto tween2 = tweeny::from(0, 1, 2); - - // Creates a multi-value heterogeneous tween - auto tween3 = tweeny::from(0, 'a', 1.0f); - @endcode - - @section points Adding tween points - - A tween without a target value does nothing. To add a point to a tween, use the method tween::to. Since tweeny::from returns the tween instance itself, you can chain both calls, forming the following: - - @code - // For a single value - auto tween = tweeny::from(0).to(100); - - // For multiple values - auto tween = tweeny::from(0, 'a').to(10, 'z'); - @endcode - - Notice how arguments to tween::to were of the same type of the arguments to tweeny::from. - - @section durations Specifying durations - - A tween needs a duration between each point. This duration unit is a unsigned integer that can represent anything you want. Usually I use it as milliseconds (this is relevant when stepping a tween). To specify the duration, call tween::during **after** tween::to: - - @code - auto tween = tweeny::from(0).to(100).during(100); - @endcode - - When you have a multivalued tween, you can either pass one single duration for all the values or specify a value for each point: - - @code - auto tween = tweeny::from(0, 1, 2).to(3, 4, 5).during(50, 100, 200); - @endcode - - The total duration from the starting point `(0, 1, 2)` to the next point `(3, 4, 5)` is 200, but each value will reach their target at different times. - - To specify the same time for each value, pass a single value for tween::during: - - @code - auto tween = tweeny::from(0, 1, 2).to(3, 4, 5).during(200); - @endcode - - @section easings Changing easing functions - - Easing functions control the interpolation values between each point. Given a percentage `p`, initial value `a` and final value `b`, a easing function will return a value between `a` and `b` corresponding to that `p`. For instance, the common implementation of a linear easing function is the following: - - @code - int linear(float p, int a, int b) { - return (b-a)*p + a; - } - @endcode - - By default, tweens use a linear easing function to interpolate their values. You can change that, though: Tweeny has 30 easing functions that you can specify using tween::via function. - - @code - auto tween = tweeny::from(0).to(10).during(100).via(tweeny::easing::circularInOut); - @endcode - - The same rules of tween::during applies here: if you have multi-valued tweens, you can specify a different easing for each one or use the same for all of them: - - @code - using tweeny::easing; - auto tween = tweeny::from(0, 1, 2).to(3, 4, 5).during(200).via(easing::exponentialIn, easing::exponentialInOut, easing::backOut); - @endcode - - For a list of all available easings, consult the modules page. - http://easings.net has a nice visualization of those easing curves. - - You can specify custom easing functions if a different behavior is needed, by passing any callable type to tween::via conforming to the T ease(float p, T begin, T end) - prototype and returning the corresponding value. - - @code - auto tween = tweeny::from(0).to(100).during(100).via([](float p, int a, int b) { return (b-a)*p + a; } ); - @endcode - - To a multi type tween, you need to make sure that easing arguments conform to their type: - - @code - auto tween = tweeny::from(0, 1.0f).to(100, 200.0f).during(100) - .via([](float p, int a, int b) { return (b-a)*p + a; }, [](float p, float a, float b) { return (b-a)*p + a; }); - @endcode - - Beware that when using integral types most easing functions will not round but truncate their results. This leads to strange behaviors such as - the tween reaching its final value only when it its 100% but staying in its initial value for a long percentage portion. You can use floating point values - and round them to obtain smoother results. easing::linear does this by default for integral types, but other easings don't. - - @section multipoint Multi point tweens - - Tweens can have multiple points: a sequence of values that will be reached in order. For instance, you might want to start from 0, reach - 100 during 500ms through a easing::linear easing, then reach 200 during 100ms through a easing::circularOut easing. - - To allow for that, each call to tween::to adds a new tweening point. Calls to tween::during and tween::via always refer to the last added - point: - - @code - auto tween = tweeny::from(0) - .to(100).during(500) // 0 to 100 during 500 - .to(200).during(100).via(easing::circularOut); // 100 to 200 during 100 via circularOut - @endcode - - Stepping and seeking works transparently for the user, regardless of how many tween points there are. This means that Tweeny will - automatically manage switching from one point to another when using tween::step and tween::seek. - - @section interpolating Stepping, seeking and jumping. - - After setting up points, durations and easings, a tween is ready to interpolate. There are three main ways to do that and we are going - to cover them in this section. - - The first one is **stepping**. It is used to move the tween forward by a delta amount, which can be either specified in duration units - or percentage. Stepping is particularly useful when you are in a event/rendering loop and has access to the delta time between frames: - - @code - auto tween = tweeny::from(0).to(100).during(1000); - while (!done) { - tween.step(dt); - } - @endcode - - Passing a integral quantity (integers) to tween::step will step it in duration units. Passing a float value will step it by - a percentage (ranging from 0.0f to 1.0f). - - You can set a tween to go backwards, so that it steps in reverse. To to that, use tween::backward: each tween::step call will decrease - a tween time until it reaches 0. To make it go forward again, use tween::forward. Tween direction makes no difference when seeking or jumping. - - The second one is **seeking**. Seeking is useful if you need the tween to move to a specific point in time or percentage. - - @code - auto tween = tweeny::from(0).to(100).during(1000); - tween.seek(0.5f); - @endcode - - The same value type rules of tween::step applies: a float value means a percentage and an integral value means duration. - - The third one is **jumping**. Jumping is useful to seek to a specific tween point, when you have @ref multipoint . - - @code - auto tween = tweeny::from(0).to(100).during(100).to(200).during(100); - tween.jump(1); - @endcode - - tween::step, tween::seek and tween::jump both returns the result of their action. The return type varies according to the - tween type according to these rules: - - - If the tween has a single value, it will yield that value directly: - @code - auto tween = tweeny::from(0).to(100).during(100); - int value = tween.step(10); - @endcode - - - If the tween has multiple values of the same type, it will yield an array with those values: - @code - auto tween = tweeny::from(0, 1).to(2, 3).during(100); - std::array v = tween.step(10); - @endcode - - - If the tween has multiple types, it will return a tuple: - @code - auto tween = tweeny::from(0, 1.0f).to(2, 3.0f).during(100); - std::tuple v = tween.step(10); - @endcode - - @section callbacks Callbacks - - Tweeny lets you specify seeking and stepping callbacks so that actions can executed in specific points - (e.g, playing a sound when a tween reaches a frame). tween::onStep add a function that will be called whenever a tween - steps whereas tween::onSeek will add a function to be called when it seeks. Although stepping is resolved - in terms of seeking, tween::step it will not trigger seek callbacks. - - Callbacks can be of three different types: - - - Accept the tween and its current values. Useful if you want access to tween values and need to control the tween instance: - @code - bool stepped(tween & t, int x, int y); - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode - - - Accept only a tween, useful if the values itself are not interesting but you need to control tween behavior: - @code - bool stepped(tween & t); - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode - - - Accept only tween values, if you just want tween values: - @code - bool stepped(int x, int y); - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode - - The return type of a callback is always boolean. If it returns true, it will be *dismissed* and - removed from the callback list. Returning false keeps the callback in the queue: - - @code - bool stepped(int x, int y) { - printf("x: %d, y: %d\n", x, y); - if (x == y) return true; - return false; - } - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode - - All callable types can be used as a callback, as long as they conform to the interface. - - @code - struct ftor { - bool operator()(int x, int y) { return false; } - }; - auto tween = tweeny::from(0, 0).to(100, 200).during(100); - tween.onStep([](int, int) { return false; }); // lambdas - tween.onStep(ftor()); // functors - @endcode - - The @ref loop has some nice ways of using callbacks. - -
- - This covers all the basics steps of using Tweeny. There is more to learn though, take a look at the demo repository to see - more. Consult the API of the @ref tween class to see all its methods and more examples within. - - I hope you have fun using Tweeny. -*/ -} diff --git a/include/dispatcher.h b/include/dispatcher.h deleted file mode 100644 index cc9c669..0000000 --- a/include/dispatcher.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* This file contains code to help call a function applying a tuple as its arguments. - * This code is private and not documented. */ - -#ifndef TWEENY_DISPATCHER_H -#define TWEENY_DISPATCHER_H - -#include - -namespace tweeny { - namespace detail { - template struct seq { }; - template struct gens : gens { }; - template struct gens<0, S...> { - typedef seq type; - }; - - template - R dispatch(Func && f, TupleType && args, seq) { - return f(std::get(args) ...); - } - - template - R call(Func && f, const std::tuple & args) { - return dispatch(f, args, typename gens::type()); - } - } -} - -#endif //TWEENY_DISPATCHER_H diff --git a/include/easing.h b/include/easing.h deleted file mode 100644 index a504664..0000000 --- a/include/easing.h +++ /dev/null @@ -1,662 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @file easing.h - * The purpose of this file is to list all bundled easings. All easings are based on Robert Penner's easing - * functions: http://robertpenner.com/easing/ - */ - -#ifndef TWEENY_EASING_H -#define TWEENY_EASING_H - -#include -#include - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -/** - * @defgroup easings Easings - * @brief Bundled easing functions based on - * Robert Penner's Easing Functions - * @details You should plug these functions into @ref tweeny::tween::via function to specify the easing used in a tween. - * @sa tweeny::easing - * @{ - *//** - * @defgroup stepped Stepped - * @{ - * @brief The value does not change. No interpolation is used. - * @} - *//** - * @defgroup default Default - * @{ - * @brief A default mode for arithmetic values it will change in constant speed, for non-arithmetic value will be constant. - * @} - *//** - * @defgroup linear Linear - * @{ - * @brief The most boring ever easing function. It has no acceleration and change values in constant speed. - * @} - *//** - * @defgroup quadratic Quadratic - * @{ - * @brief The most commonly used easing functions. - * @} - *//** - * @defgroup cubic Cubic - * @{ - * @brief A bit curvier than the quadratic easing. - * @} - *//** - * @defgroup quartic Quartic - * @{ - * @brief A steeper curve. Acceleration changes faster than Cubic. - * @} - *//** - * @defgroup quintic Quintic - * @{ - * @brief An even steeper curve. Acceleration changes really fast. - * @} - *//** - * @defgroup sinuisodal Sinuisodal - * @{ - * @brief A very gentle curve, gentlier than quadratic. - * @} - *//** - * @defgroup exponential Exponential - * @{ - * @brief A very steep curve, based on the `p(t) = 2^(10*(t-1))` equation. - * @} - *//** - * @defgroup circular Circular - * @{ - * @brief A smooth, circular slope that resembles the arc of an circle. - * @} - *//** - * @defgroup back Back - * @{ - * @brief An easing function that has a "cute" natural coming back effect. - * @} - *//** - * @defgroup elastic Elastic - * @{ - * @brief An elastic easing function. Values go a little past the maximum/minimum in an elastic effect. - * @} - *//** - * @defgroup bounce Bounce - * @{ - * @brief A bouncing easing function. Values "bounce" around the maximum/minumum. - * @} - *//** - * @} - */ - -namespace tweeny { - /** - * @brief The easing class holds all the bundled easings. - * - * You should pass the easing function to the @p tweeny::tween::via method, to set the easing function that will - * be used to interpolate values in a tween point. - * - * **Example**: - * - * @code - * auto tween = tweeny::from(0).to(100).via(tweeny::easing::linear); - * @endcode - */ - class easing { - public: - /** - * @brief Enumerates all easings to aid in runtime when adding easins to a tween using tween::via - * - * The aim of this enum is to help in situations where the easing doesn't come straight from the C++ - * code but rather from a configuration file or some sort of external paramenter. - */ - enum class enumerated { - def, - linear, - stepped, - quadraticIn, - quadraticOut, - quadraticInOut, - cubicIn, - cubicOut, - cubicInOut, - quarticIn, - quarticOut, - quarticInOut, - quinticIn, - quinticOut, - quinticInOut, - sinusoidalIn, - sinusoidalOut, - sinusoidalInOut, - exponentialIn, - exponentialOut, - exponentialInOut, - circularIn, - circularOut, - circularInOut, - bounceIn, - bounceOut, - bounceInOut, - elasticIn, - elasticOut, - elasticInOut, - backIn, - backOut, - backInOut - }; - - /** - * @ingroup stepped - * @brief Value is constant. - */ - static constexpr struct steppedEasing { - template - static T run(float position, T start, T end) { - return start; - } - } stepped = steppedEasing{}; - - /** - * @ingroup default - * @brief Values change with constant speed for arithmetic type only. The non-arithmetic it will be constant. - */ - static constexpr struct defaultEasing { - template struct voidify { using type = void; }; - template using void_t = typename voidify::type; - - template - struct supports_arithmetic_operations : std::false_type {}; - - template - struct supports_arithmetic_operations() + std::declval()), - decltype(std::declval() - std::declval()), - decltype(std::declval() * std::declval()), - decltype(std::declval() * std::declval()), - decltype(std::declval() * std::declval()) - >> : std::true_type{}; - - - template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { - return static_cast(roundf((end - start) * position + start)); - } - - template - static typename std::enable_if::value && !std::is_integral::value, T>::type run(float position, T start, T end) { - return static_cast((end - start) * position + start); - } - - template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { - return start; - } - } def = defaultEasing{}; - - /** - * @ingroup linear - * @brief Values change with constant speed. - */ - static constexpr struct linearEasing { - template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { - return static_cast(roundf((end - start) * position + start)); - } - - template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { - return static_cast((end - start) * position + start); - } - } linear = linearEasing{}; - - /** - * @ingroup quadratic - * @brief Accelerate initial values with a quadratic equation. - */ - static constexpr struct quadraticInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position + start); - } - } quadraticIn = quadraticInEasing{}; - - /** - * @ingroup quadratic - * @brief Deaccelerate ending values with a quadratic equation. - */ - static constexpr struct quadraticOutEasing { - template - static T run(float position, T start, T end) { - return static_cast((-(end - start)) * position * (position - 2) + start); - } - } quadraticOut = quadraticOutEasing{}; - - /** - * @ingroup quadratic - * @brief Acceelerate initial and deaccelerate ending values with a quadratic equation. - */ - static constexpr struct quadraticInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast(((end - start) / 2) * position * position + start); - } - - --position; - return static_cast((-(end - start) / 2) * (position * (position - 2) - 1) + start); - } - } quadraticInOut = quadraticInOutEasing{}; - - /** - * @ingroup cubic - * @brief Aaccelerate initial values with a cubic equation. - */ - static constexpr struct cubicInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position * position + start); - } - } cubicIn = cubicInEasing{}; - - /** - * @ingroup cubic - * @brief Deaccelerate ending values with a cubic equation. - */ - static constexpr struct cubicOutEasing { - template - static T run(float position, T start, T end) { - --position; - return static_cast((end - start) * (position * position * position + 1) + start); - } - } cubicOut = cubicOutEasing{}; - - /** - * @ingroup cubic - * @brief Acceelerate initial and deaccelerate ending values with a cubic equation. - */ - static constexpr struct cubicInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast(((end - start) / 2) * position * position * position + start); - } - position -= 2; - return static_cast(((end - start) / 2) * (position * position * position + 2) + start); - } - } cubicInOut = cubicInOutEasing{}; - - /** - * @ingroup quartic - * @brief Acceelerate initial values with a quartic equation. - */ - static constexpr struct quarticInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position * position * position + start); - } - } quarticIn = quarticInEasing{}; - - /** - * @ingroup quartic - * @brief Deaccelerate ending values with a quartic equation. - */ - static constexpr struct quarticOutEasing { - template - static T run(float position, T start, T end) { - --position; - return static_cast( -(end - start) * (position * position * position * position - 1) + start); - } - } quarticOut = quarticOutEasing{}; - - /** - * @ingroup quartic - * @brief Acceelerate initial and deaccelerate ending values with a quartic equation. - */ - static constexpr struct quarticInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast(((end - start) / 2) * (position * position * position * position) + - start); - } - position -= 2; - return static_cast((-(end - start) / 2) * (position * position * position * position - 2) + - start); - } - } quarticInOut = quarticInOutEasing{}; - - /** - * @ingroup quintic - * @brief Acceelerate initial values with a quintic equation. - */ - static constexpr struct quinticInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position * position * position * position + start); - } - } quinticIn = quinticInEasing{}; - - /** - * @ingroup quintic - * @brief Deaccelerate ending values with a quintic equation. - */ - static constexpr struct quinticOutEasing { - template - static T run(float position, T start, T end) { - position--; - return static_cast((end - start) * (position * position * position * position * position + 1) + - start); - } - } quinticOut = quinticOutEasing{}; - - /** - * @ingroup quintic - * @brief Acceelerate initial and deaccelerate ending values with a quintic equation. - */ - static constexpr struct quinticInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast( - ((end - start) / 2) * (position * position * position * position * position) + - start); - } - position -= 2; - return static_cast( - ((end - start) / 2) * (position * position * position * position * position + 2) + - start); - } - } quinticInOut = quinticInOutEasing{}; - - /** - * @ingroup sinusoidal - * @brief Acceelerate initial values with a sinusoidal equation. - */ - static constexpr struct sinusoidalInEasing { - template - static T run(float position, T start, T end) { - return static_cast(-(end - start) * cosf(position * static_cast(M_PI) / 2) + (end - start) + start); - } - } sinusoidalIn = sinusoidalInEasing{}; - - /** - * @ingroup sinusoidal - * @brief Deaccelerate ending values with a sinusoidal equation. - */ - static constexpr struct sinusoidalOutEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * sinf(position * static_cast(M_PI) / 2) + start); - } - } sinusoidalOut = sinusoidalOutEasing{}; - - /** - * @ingroup sinusoidal - * @brief Acceelerate initial and deaccelerate ending values with a sinusoidal equation. - */ - static constexpr struct sinusoidalInOutEasing { - template - static T run(float position, T start, T end) { - return static_cast((-(end - start) / 2) * (cosf(position * static_cast(M_PI)) - 1) + start); - } - } sinusoidalInOut = sinusoidalInOutEasing{}; - - /** - * @ingroup exponential - * @brief Acceelerate initial values with an exponential equation. - */ - static constexpr struct exponentialInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * powf(2, 10 * (position - 1)) + start); - } - } exponentialIn = exponentialInEasing{}; - - /** - * @ingroup exponential - * @brief Deaccelerate ending values with an exponential equation. - */ - static constexpr struct exponentialOutEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * (-powf(2, -10 * position) + 1) + start); - } - } exponentialOut = exponentialOutEasing{}; - - /** - * @ingroup exponential - * @brief Acceelerate initial and deaccelerate ending values with an exponential equation. - */ - static constexpr struct exponentialInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast(((end - start) / 2) * powf(2, 10 * (position - 1)) + start); - } - --position; - return static_cast(((end - start) / 2) * (-powf(2, -10 * position) + 2) + start); - } - } exponentialInOut = exponentialInOutEasing{}; - - /** - * @ingroup circular - * @brief Acceelerate initial values with a circular equation. - */ - static constexpr struct circularInEasing { - template - static T run(float position, T start, T end) { - return static_cast( -(end - start) * (sqrtf(1 - position * position) - 1) + start ); - } - } circularIn = circularInEasing{}; - - /** - * @ingroup circular - * @brief Deaccelerate ending values with a circular equation. - */ - static constexpr struct circularOutEasing { - template - static T run(float position, T start, T end) { - --position; - return static_cast((end - start) * (sqrtf(1 - position * position)) + start); - } - } circularOut = circularOutEasing{}; - - /** - * @ingroup circular - * @brief Acceelerate initial and deaccelerate ending values with a circular equation. - */ - static constexpr struct circularInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast((-(end - start) / 2) * (sqrtf(1 - position * position) - 1) + start); - } - - position -= 2; - return static_cast(((end - start) / 2) * (sqrtf(1 - position * position) + 1) + start); - } - } circularInOut = circularInOutEasing{}; - - /** - * @ingroup bounce - * @brief Acceelerate initial values with a "bounce" equation. - */ - static constexpr struct bounceInEasing { - template - static T run(float position, T start, T end) { - return (end - start) - bounceOut.run((1 - position), T(), (end - start)) + start; - } - } bounceIn = bounceInEasing{}; - - /** - * @ingroup bounce - * @brief Deaccelerate ending values with a "bounce" equation. - */ - static constexpr struct bounceOutEasing { - template - static T run(float position, T start, T end) { - T c = end - start; - if (position < (1 / 2.75f)) { - return static_cast(c * (7.5625f * position * position) + start); - } else if (position < (2.0f / 2.75f)) { - float postFix = position -= (1.5f / 2.75f); - return static_cast(c * (7.5625f * (postFix) * position + .75f) + start); - } else if (position < (2.5f / 2.75f)) { - float postFix = position -= (2.25f / 2.75f); - return static_cast(c * (7.5625f * (postFix) * position + .9375f) + start); - } else { - float postFix = position -= (2.625f / 2.75f); - return static_cast(c * (7.5625f * (postFix) * position + .984375f) + start); - } - } - } bounceOut = bounceOutEasing{}; - - /** - * @ingroup bounce - * @brief Acceelerate initial and deaccelerate ending values with a "bounce" equation. - */ - static constexpr struct bounceInOutEasing { - template - static T run(float position, T start, T end) { - if (position < 0.5f) return static_cast(bounceIn.run(position * 2, T(), (end - start)) * .5f + start); - else return static_cast(bounceOut.run((position * 2 - 1), T(), (end - start)) * .5f + (end - start) * .5f + start); - } - } bounceInOut = bounceInOutEasing{}; - - /** - * @ingroup elastic - * @brief Acceelerate initial values with an "elastic" equation. - */ - static constexpr struct elasticInEasing { - template - static T run(float position, T start, T end) { - if (position <= 0.00001f) return start; - if (position >= 0.999f) return end; - float p = .3f; - auto a = end - start; - float s = p / 4; - float postFix = - a * powf(2, 10 * (position -= 1)); // this is a fix, again, with post-increment operators - return static_cast(-(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); - } - } elasticIn = elasticInEasing{}; - - /** - * @ingroup elastic - * @brief Deaccelerate ending values with an "elastic" equation. - */ - static constexpr struct elasticOutEasing { - template - static T run(float position, T start, T end) { - if (position <= 0.00001f) return start; - if (position >= 0.999f) return end; - float p = .3f; - auto a = end - start; - float s = p / 4; - return static_cast(a * powf(2, -10 * position) * sinf((position - s) * (2 * static_cast(M_PI)) / p) + end); - } - } elasticOut = elasticOutEasing{}; - - /** - * @ingroup elastic - * @brief Acceelerate initial and deaccelerate ending values with an "elastic" equation. - */ - static constexpr struct elasticInOutEasing { - template - static T run(float position, T start, T end) { - if (position <= 0.00001f) return start; - if (position >= 0.999f) return end; - position *= 2; - float p = (.3f * 1.5f); - auto a = end - start; - float s = p / 4; - float postFix; - - if (position < 1) { - postFix = a * powf(2, 10 * (position -= 1)); // postIncrement is evil - return static_cast(-0.5f * (postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); - } - postFix = a * powf(2, -10 * (position -= 1)); // postIncrement is evil - return static_cast(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p) * .5f + end); - } - } elasticInOut = elasticInOutEasing{}; - - /** - * @ingroup back - * @brief Acceelerate initial values with a "back" equation. - */ - static constexpr struct backInEasing { - template - static T run(float position, T start, T end) { - float s = 1.70158f; - float postFix = position; - return static_cast((end - start) * (postFix) * position * ((s + 1) * position - s) + start); - } - } backIn = backInEasing{}; - - /** - * @ingroup back - * @brief Deaccelerate ending values with a "back" equation. - */ - static constexpr struct backOutEasing { - template - static T run(float position, T start, T end) { - float s = 1.70158f; - position -= 1; - return static_cast((end - start) * ((position) * position * ((s + 1) * position + s) + 1) + start); - } - } backOut = backOutEasing{}; - - /** - * @ingroup back - * @brief Acceelerate initial and deaccelerate ending values with a "back" equation. - */ - static constexpr struct backInOutEasing { - template - static T run(float position, T start, T end) { - float s = 1.70158f; - float t = position; - auto b = start; - auto c = end - start; - float d = 1; - s *= (1.525f); - if ((t /= d / 2) < 1) return static_cast(c / 2 * (t * t * (((s) + 1) * t - s)) + b); - float postFix = t -= 2; - return static_cast(c / 2 * ((postFix) * t * (((s) + 1) * t + s) + 2) + b); - } - } backInOut = backInOutEasing{}; - }; -} -#endif //TWEENY_EASING_H diff --git a/include/easingresolve.h b/include/easingresolve.h deleted file mode 100644 index 89911ec..0000000 --- a/include/easingresolve.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides the easing resolution mechanism so that the library user can mix lambdas and the bundled - * pre-defined easing functions. It shall not be used directly. - * This file is private. - */ - -#ifndef TWEENY_EASINGRESOLVE_H -#define TWEENY_EASINGRESOLVE_H - -#include -#include "easing.h" - -namespace tweeny { - namespace detail { - using std::get; - - template - struct easingresolve { - static void impl(FunctionTuple &b, Fs... fs) { - if (sizeof...(Fs) == 0) return; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - static void impl(FunctionTuple &b, F1 f1, Fs... fs) { - get(b) = f1; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - typedef typename std::tuple_element::type ArgType; - - static void impl(FunctionTuple &b, easing::steppedEasing, Fs... fs) { - get(b) = easing::stepped.run; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - typedef typename std::tuple_element::type ArgType; - - static void impl(FunctionTuple &b, easing::linearEasing, Fs... fs) { - get(b) = easing::linear.run; - easingresolve::impl(b, fs...); - } - }; - template - struct easingresolve { - typedef typename std::tuple_element::type ArgType; - - static void impl(FunctionTuple &b, easing::defaultEasing, Fs... fs) { - get(b) = easing::def.run; - easingresolve::impl(b, fs...); - } - }; - - #define DECLARE_EASING_RESOLVE(__EASING_TYPE__) \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## In), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## In.run; \ - easingresolve::impl(b, fs...); \ - } \ - }; \ - \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## Out), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## Out.run; \ - easingresolve::impl(b, fs...); \ - } \ - }; \ - \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## InOut), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## InOut.run; \ - easingresolve::impl(b, fs...); \ - } \ - } - - DECLARE_EASING_RESOLVE(quadratic); - DECLARE_EASING_RESOLVE(cubic); - DECLARE_EASING_RESOLVE(quartic); - DECLARE_EASING_RESOLVE(quintic); - DECLARE_EASING_RESOLVE(sinusoidal); - DECLARE_EASING_RESOLVE(exponential); - DECLARE_EASING_RESOLVE(circular); - DECLARE_EASING_RESOLVE(bounce); - DECLARE_EASING_RESOLVE(elastic); - DECLARE_EASING_RESOLVE(back); - } -} - -#endif //TWEENY_EASINGRESOLVE_H diff --git a/include/int2type.h b/include/int2type.h deleted file mode 100644 index 24e8961..0000000 --- a/include/int2type.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file declares a helper struct to create a type from a integer value, to aid in template tricks. - * This file is private. - */ -#ifndef TWEENY_INT2TYPE_H -#define TWEENY_INT2TYPE_H - -namespace tweeny { - namespace detail { - template struct int2type { }; - } -} -#endif //TWEENY_INT2TYPE_H diff --git a/include/tween.h b/include/tween.h deleted file mode 100644 index 2fd62bd..0000000 --- a/include/tween.h +++ /dev/null @@ -1,673 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @file tween.h - * This file contains the core of tweeny: the main tween class. - */ - -#ifndef TWEENY_TWEEN_H -#define TWEENY_TWEEN_H - -#include -#include -#include -#include - -#include "tweentraits.h" -#include "tweenpoint.h" - -namespace tweeny { - /** - * @brief The tween class is the core class of tweeny. It controls the interpolation steps, easings and durations. - * - * It should not be constructed manually but rather from @p tweeny::from, to facilitate template argument - * deduction (and also to keep your code clean). - */ - template - class tween { - public: - /** - * @brief Instantiates a tween from a starting point. - * - * This is a static factory helper function to be used by @p tweeny::from. You should not use this directly. - * @p t The first value in the point - * @p vs The remaining values - */ - static tween from(T t, Ts... vs); - - public: - /** - * @brief Default constructor for a tween - * - * This constructor is provided to facilitate the usage of containers of tweens (e.g, std::vector). It - * should not be used manually as the tweening created by it is invalid. - */ - tween(); - - /** - * @brief Adds a new point in this tweening. - * - * This will add a new tweening point with the specified values. Next calls to @p via and @p during - * will refer to this point. - * - * **Example** - * - * @code - * auto t = tweeny::from(0).to(100).to(200); - * @endcode - * - * @param t, vs Point values - * @returns *this - */ - tween & to(T t, Ts... vs); - - /** - * @brief Specifies the easing function for the last added point. - * - * This will specify the easing between the last tween point added by @p to and its previous step. You can - * use any callable object. Additionally, you can use the easing objects specified in the class @p easing. - * - * If it is a multi-value point, you can either specify a single easing function that will be used for - * every value or you can specify an easing function for each value. You can mix and match callable objects, - * lambdas and bundled easing objects. - * - * **Example**: - * - * @code - * // use bundled linear easing - * auto tween1 = tweeny::from(0).to(100).via(tweeny::easing::linear); - * - * // use custom lambda easing - * auto tween2 = tweeny::from(0).to(100).via([](float p, int a, int b) { return (b-a) * p + a; }); - * @endcode - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(Fs... fs); - - - /** - * @brief Specifies the easing function for the last added point, accepting an enumeration. - * - * This will specify the easing between the last tween point added by @p to and its previous step. You can - * use a value from the @p tweeny::easing::enumerated enum. You can then have an enumeration of your own - * poiting to this enumerated enums, or use it directly. You can mix-and-match enumerated easings, functions - * and easing names. - * - * **Example**: - * - * @code - * auto tween1 = tweeny::from(0).to(100).via(tweeny::easing::enumerated::linear); - * auto tween2 = tweeny::from(0.0f, 100.0f).to(100.0f, 0.0f).via(tweeny::easing::linear, "backOut"); - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(easing::enumerated enumerated, Fs... fs); - - /** - * @brief Specifies the easing function for the last added point, accepting an easing name as a `std::string` value. - * - * This will specify the easing between the last tween point added by @p to and its previous step. - * You can mix-and-match enumerated easings, functions and easing names. - * - * **Example**: - * - * @code - * auto tween = tweeny::from(0.0f, 100.0f).to(100.0f, 0.0f).via(tweeny::easing::linear, "backOut"); - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(const std::string & easing, Fs... fs); - - /** - * @brief Specifies the easing function for the last added point, accepting an easing name as a `const char *` value. - * - * This will specify the easing between the last tween point added by @p to and its previous step. - * You can mix-and-match enumerated easings, functions and easing names. - * - * **Example**: - * - * @code - * auto tween = tweeny::from(0.0f, 100.0f).to(100.0f, 0.0f).via(tweeny::easing::linear, "backOut"); - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(const char * easing, Fs... fs); - - /** - * @brief Specifies the easing function for a specific point. - * - * Points starts at index 0. The index 0 refers to the first @p to call. - * Using this function without adding a point with @p to leads to undefined - * behaviour. - * - * @param index The tween point index - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(int index, Fs... fs); - - /** - * @brief Specifies the duration, typically in milliseconds, for the tweening of values in last point. - * - * You can either specify a single duration for all values or give every value its own duration. Value types - * must be convertible to the uint16_t type. - * - * **Example**: - * - * @code - * // Specify that the first point will be reached in 100 milliseconds and the first value in the second - * // point in 100, whereas the second value will be reached in 500. - * auto tween = tweeny::from(0, 0).to(100, 200).during(100).to(200, 300).during(100, 500); - * @endcode - * - * @param ds Duration values - * @returns *this - */ - template tween & during(Ds... ds); - - /** - * @brief Steps the animation by the designated delta amount. - * - * You should call this every frame of your application, passing in the amount of delta time that - * you want to animate. - * - * **Example**: - * - * @code - * // tween duration is 100ms - * auto tween = tweeny::from(0).to(100).during(100); - * - * // steps for 16ms - * tween.step(16); - * @endcode - * - * @param dt Delta duration - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onStep() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & step(int32_t dt, bool suppressCallbacks = false); - - /** - * @brief Steps the animation by the designated delta amount. - * - * You should call this every frame of your application, passing in the amount of delta time that - * you want to animate. This overload exists to match unsigned int arguments. - * - * @param dt Delta duration - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onStep() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & step(uint32_t dt, bool suppressCallbacks = false); - - /** - * @brief Steps the animation by the designated percentage amount. - * - * You can use this function to step the tweening by a specified percentage delta. - - * **Example**: - * - * @code - * // tween duration is 100ms - * auto tween = tweeny::from(0).to(100).during(100); - * - * // steps for 16ms - * tween.step(0.001f); - * @endcode - * - * @param dp Delta percentage, between `0.0f` and `1.0f` - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onStep() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & step(float dp, bool suppressCallbacks = false); - - /** - * @brief Seeks to a specified point in time based on the currentProgress. - * - * This function sets the current animation time and currentProgress. Callbacks set by @p call will be triggered. - * - * @param p The percentage to seek to, between 0.0f and 1.0f, inclusive. - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onSeek() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & seek(float p, bool suppressCallbacks = false); - - /** - * @brief Seeks to a specified point in time. - * - * This function sets the current animation time and currentProgress. Callbacks set by @p call will be triggered. - * - * @param d The duration to seek to, between 0 and the total duration. - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onSeek() - * @returns std::tuple with the current tween values. - * @see duration - */ - const typename detail::tweentraits::valuesType & seek(int32_t d, bool suppressCallbacks = false); - - /** - * @brief Seeks to a specified point in time. - * - * This function sets the current animation time and currentProgress. Callbacks set by @p call will be triggered. - * - * @param d The duration to seek to, between 0 and the total duration. - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onSeek() - * @returns std::tuple with the current tween values. - * @see duration - */ - const typename detail::tweentraits::valuesType & seek(uint32_t d, bool suppressCallbacks = false); - - /** - * @brief Adds a callback that will be called when stepping occurs, accepting both the tween and - * its values. - * - * You can add as many callbacks as you want. Its arguments types must be equal to the argument types - * of a tween instance, preceded by a variable of the tween type. Callbacks can be of any callable type. It will only be called - * via tween::step() functions. For seek callbacks, see tween::onSeek(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny:from(0).to(100).during(100); - * - * // pass a lambda - * t.onStep([](tweeny::tween & t, int v) { printf("%d ", v); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d ", v); return false; } }; - * t.onStep(ftor()); - * @endcode - * @sa step - * @sa seek - * @sa onSeek - * @param callback A callback in with the prototype `bool callback(tween & t, Ts...)` - */ - tween & onStep(typename detail::tweentraits::callbackType callback); - - /** - * @brief Adds a callback that will be called when stepping occurs, accepting only the tween. - * - * You can add as many callbacks as you want. It must receive the tween as an argument. - * Callbacks can be of any callable type. It will only be called - * via tween::step() functions. For seek callbacks, see tween::onSeek(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny:from(0).to(100).during(100); - * - * // pass a lambda - * t.onStep([](tweeny::tween & t) { printf("%d ", t.value()); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t) { printf("%d ", t.values()); return false; } }; - * t.onStep(ftor()); - * @endcode - * @sa step - * @sa seek - * @sa onSeek - * @param callback A callback in the form `bool f(tween & t)` - */ - tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); - - /** - * @brief Adds a callback that will be called when stepping occurs, accepting only the tween values. - * - * You can add as many callbacks as you want. It must receive the tween values as an argument. - * Callbacks can be of any callable type. It will only be called - * via tween::step() functions. For seek callbacks, see tween::onSeek(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny:from(0).to(100).during(100); - * - * // pass a lambda - * t.onStep([](int v) { printf("%d ", v); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(int x) { printf("%d ", x); return false; } }; - * t.onStep(ftor()); - * @endcode - * @sa step - * @sa seek - * @sa onSeek - * @param callback A callback in the form `bool f(Ts...)` - */ - tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); - - /** - * @brief Adds a callback for that will be called when seeking occurs - * - * You can add as many callbacks as you want. Its arguments types must be equal to the argument types - * of a tween instance, preceded by a variable of the tween typve. Callbacks can be of any callable type. It will be called - * via tween::seek() functions. For step callbacks, see tween::onStep(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = t:from(0).to(100).during(100); - * - * // pass a lambda - * t.onSeek([](tweeny::tween & t, int v) { printf("%d ", v); }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d ", v); } }; - * t.onSeek(ftor()); - * @endcode - * @param callback A callback in with the prototype `bool callback(tween & t, Ts...)` - */ - tween & onSeek(typename detail::tweentraits::callbackType callback); - - /** - * @brief Adds a callback for that will be called when seeking occurs, accepting only the tween values. - * - * You can add as many callbacks as you want. It must receive the tween as an argument. - * Callbacks can be of any callable type. It will be called - * via tween::seek() functions. For step callbacks, see tween::onStep(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied again. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = t:from(0).to(100).during(100); - * - * // pass a lambda - * t.onSeek([](int v) { printf("%d ", v); }); - * - * // pass a functor instance - * struct ftor { void operator()(int v) { printf("%d ", v); return false; } }; - * t.onSeek(ftor()); - * @endcode - * @param callback A callback in the form `bool f(Ts...)` - */ - tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); - - /** - * @brief Adds a callback for that will be called when seeking occurs, accepting only the tween. - * - * You can add as many callbacks as you want. It must receive the tween as an argument. - * Callbacks can be of any callable type. It will be called - * via tween::seek() functions. For step callbacks, see tween::onStep(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied again. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = t:from(0).to(100).during(100); - * - * // pass a lambda - * t.onSeek([](tweeny::tween & t) { printf("%d ", t.value()); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t) { printf("%d ", t.value()); return false; } }; - * t.onSeek(ftor()); - * @endcode - * @param callback A callback in the form `bool f(tween & t)` - */ - tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); - - /** - * @brief Returns the total duration of this tween - * - * @returns The duration of all the tween points. - */ - uint32_t duration() const; - - /** - * @brief Returns the current tween values - * - * This returns the current tween value as returned by the - * tween::step() function, except that it does not perform a step. - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & peek() const; - - /** - * @brief Calculates and returns the tween values at a given progress - * - * This returns the tween value at the requested progress, without stepping - * or seeking. - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType peek(float progress) const; - - - /** - * @brief Calculates and return the tween values at a given time - * - * This returns the tween values at the requested time, without stepping - * or seeking. - * @returns std::tuple with the calculated tween values. - */ - const typename detail::tweentraits::valuesType peek(uint32_t time) const; - - /** - * @brief Returns the current time point of the interpolation. - * - * @returns the current timr point between 0 and total time point (inclusive) - */ - uint32_t currentTimePoint() const; ///< @sa tween::currenttimepoint - - /** - * @brief Returns the current currentProgress of the interpolation. - * - * 0 means its at the values passed in the construction, 1 means the last step. - * @returns the current currentProgress between 0 and 1 (inclusive) - */ - float progress() const; - - /** - * @brief Returns true if tween reach to end of interpolation progress. - * - * @returns True means its finished, false means its in progress. - */ - bool isFinished() const; - - /** - * @brief Sets the direction of this tween forward. - * - * Note that this only affects tween::step() function. - * @returns *this - * @sa backward - */ - tween & forward(); - - /** - * @brief Sets the direction of this tween backward. - * - * Note that this only affects tween::step() function. - * @returns *this - * @sa forward - */ - tween & backward(); - - /** - * @brief Returns the current direction of this tween - * - * @returns -1 If it is mobin backwards in time, 1 if it is moving forward in time - */ - int direction() const; - - /** - * @brief Jumps to a specific tween point - * - * This will seek the tween to a percentage matching the beginning of that step. - * - * @param point The point to seek to. 0 means the point passed in tweeny::from - * @param suppressCallbacks (optional) set to true to suppress seek() callbacks - * @returns current values - * @sa seek - */ - const typename detail::tweentraits::valuesType & jump(size_t point, bool suppressCallbacks = false); - - /** - * @brief Returns the current tween point - * - * @returns Current tween point - */ - uint16_t point() const; - - private /* member types */: - using traits = detail::tweentraits; - - private /* member variables */: - uint32_t total = 0; // total runtime - uint16_t currentPoint = 0; // current point - uint32_t currentProgress = 0; // current progress - std::vector> points; - typename traits::valuesType current; - std::vector onStepCallbacks; - std::vector onSeekCallbacks; - int8_t currentDirection = 1; - - private: - /* member functions */ - tween(T t, Ts... vs); - template void interpolate(uint32_t prog, unsigned point, typename traits::valuesType & values, detail::int2type) const; - void interpolate(uint32_t prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const; - void render(uint32_t p); - void dispatch(std::vector & cbVector); - uint16_t pointAt(uint32_t progress) const; - }; - - /** - * @brief Class specialization when a tween has a single value - * - * This class is preferred automatically by your compiler when your tween has only one value. It exists mainly - * so that you dont need to use std::get<0> to obtain a single value when using tween::step, tween::seek or any other - * value returning function. Other than that, you should look at the - * tweeny::tween documentation. - * - * Except for this little detail, this class methods and behaviours are exactly the same. - */ - template - class tween { - public: - static tween from(T t); - - public: - tween(); ///< @sa tween::tween - tween & to(T t); ///< @sa tween::to - template tween & via(Fs... fs); ///< @sa tween::via - template tween & via(int index, Fs... fs); ///< @sa tween::via - template tween & via(tweeny::easing::enumerated enumerated, Fs... fs); ///< @sa tween::via - template tween & via(const std::string & easing, Fs... fs); ///< @sa tween::via - template tween & via(const char * easing, Fs... fs); ///< @sa tween::via - template tween & during(Ds... ds); ///< @sa tween::during - const T & step(int32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(int32_t dt, bool suppressCallbacks) - const T & step(uint32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(uint32_t dt, bool suppressCallbacks) - const T & step(float dp, bool suppressCallbacks = false); ///< @sa tween::step(float dp, bool suppressCallbacks) - const T & seek(float p, bool suppressCallbacks = false); ///< @sa tween::seek(float p, bool suppressCallbacks) - const T & seek(int32_t d, bool suppressCallbacks = false); ///< @sa tween::seek(int32_t d, bool suppressCallbacks) - const T & seek(uint32_t d, bool suppressCallbacks = false); ///< @sa tween::seek(uint32_t d, bool suppressCallbacks) - tween & onStep(typename detail::tweentraits::callbackType callback); ///< @sa tween::onStep - tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onStep - tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onStep - tween & onSeek(typename detail::tweentraits::callbackType callback); ///< @sa tween::onSeek - tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onSeek - tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onSeek - const T & peek() const; ///< @sa tween::peek - T peek(float progress) const; ///< @sa tween::peek - T peek(uint32_t time) const; ///< @sa tween::peek - uint32_t duration() const; ///< @sa tween::duration - uint32_t currentTimePoint() const; ///< @sa tween::currenttimepoint - float progress() const; ///< @sa tween::progress - bool isFinished() const; ///< @sa tween::isFinished - tween & forward(); ///< @sa tween::forward - tween & backward(); ///< @sa tween::backward - int direction() const; ///< @sa tween::direction - const T & jump(size_t point, bool suppressCallbacks = false); ///< @sa tween::jump - uint16_t point() const; ///< @sa tween::point - - private /* member types */: - using traits = detail::tweentraits; - - private /* member variables */: - uint32_t total = 0; // total runtime - uint16_t currentPoint = 0; // current point - uint32_t currentProgress = 0; // current progress - std::vector> points; - T current; - std::vector onStepCallbacks; - std::vector onSeekCallbacks; - int8_t currentDirection = 1; - - private: - /* member functions */ - tween(T t); - void interpolate(uint32_t prog, unsigned point, T & value) const; - void render(uint32_t p); - void dispatch(std::vector & cbVector); - uint16_t pointAt(uint32_t progress) const; - }; -} - -#include "tween.tcc" -#include "tweenone.tcc" - -#endif //TWEENY_TWEEN_H diff --git a/include/tween.tcc b/include/tween.tcc deleted file mode 100644 index e5fc48a..0000000 --- a/include/tween.tcc +++ /dev/null @@ -1,362 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * The purpose of this file is to hold implementations for the tween.h file. - */ - -#ifndef TWEENY_TWEEN_TCC -#define TWEENY_TWEEN_TCC - -#include "tween.h" -#include "dispatcher.h" - -namespace tweeny { - - namespace detail { - template - T clip(const T & n, const T & lower, const T & upper) { - return std::max(lower, std::min(n, upper)); - } - } - - template inline tween tween::from(T t, Ts... vs) { return tween(t, vs...); } - template inline tween::tween() { } - template inline tween::tween(T t, Ts... vs) { - points.emplace_back(t, vs...); - } - - template inline tween & tween::to(T t, Ts... vs) { - points.emplace_back(t, vs...); - return *this; - } - - template - template - inline tween & tween::via(Fs... vs) { - points.at(points.size() - 2).via(vs...); - return *this; - } - - template - template - inline tween & tween::via(int index, Fs... vs) { - points.at(static_cast(index)).via(vs...); - return *this; - } - - template - template - tween & tween::via(easing::enumerated enumerated, Fs... vs) { - switch (enumerated) { - case easing::enumerated::def: return via(easing::def, vs...); - case easing::enumerated::linear: return via(easing::linear, vs...); - case easing::enumerated::stepped: return via(easing::stepped, vs...); - case easing::enumerated::quadraticIn: return via(easing::quadraticIn, vs...); - case easing::enumerated::quadraticOut: return via(easing::quadraticOut, vs...); - case easing::enumerated::quadraticInOut: return via(easing::quadraticInOut, vs...); - case easing::enumerated::cubicIn: return via(easing::cubicIn, vs...); - case easing::enumerated::cubicOut: return via(easing::cubicOut, vs...); - case easing::enumerated::cubicInOut: return via(easing::cubicInOut, vs...); - case easing::enumerated::quarticIn: return via(easing::quarticIn, vs...); - case easing::enumerated::quarticOut: return via(easing::quarticOut, vs...); - case easing::enumerated::quarticInOut: return via(easing::quarticInOut, vs...); - case easing::enumerated::quinticIn: return via(easing::quinticIn, vs...); - case easing::enumerated::quinticOut: return via(easing::quinticOut, vs...); - case easing::enumerated::quinticInOut: return via(easing::quinticInOut, vs...); - case easing::enumerated::sinusoidalIn: return via(easing::sinusoidalIn, vs...); - case easing::enumerated::sinusoidalOut: return via(easing::sinusoidalOut, vs...); - case easing::enumerated::sinusoidalInOut: return via(easing::sinusoidalInOut, vs...); - case easing::enumerated::exponentialIn: return via(easing::exponentialIn, vs...); - case easing::enumerated::exponentialOut: return via(easing::exponentialOut, vs...); - case easing::enumerated::exponentialInOut: return via(easing::exponentialInOut, vs...); - case easing::enumerated::circularIn: return via(easing::circularIn, vs...); - case easing::enumerated::circularOut: return via(easing::circularOut, vs...); - case easing::enumerated::circularInOut: return via(easing::circularInOut, vs...); - case easing::enumerated::bounceIn: return via(easing::bounceIn, vs...); - case easing::enumerated::bounceOut: return via(easing::bounceOut, vs...); - case easing::enumerated::bounceInOut: return via(easing::bounceInOut, vs...); - case easing::enumerated::elasticIn: return via(easing::elasticIn, vs...); - case easing::enumerated::elasticOut: return via(easing::elasticOut, vs...); - case easing::enumerated::elasticInOut: return via(easing::elasticInOut, vs...); - case easing::enumerated::backIn: return via(easing::backIn, vs...); - case easing::enumerated::backOut: return via(easing::backOut, vs...); - case easing::enumerated::backInOut: return via(easing::backInOut, vs...); - default: return via(easing::def, vs...); - } - } - - template - template - tween & tween::via(const std::string & easing, Fs... vs) { - if (easing == "stepped") return via(easing::stepped, vs...); - if (easing == "linear") return via(easing::linear, vs...); - if (easing == "quadraticIn") return via(easing::quadraticIn, vs...); - if (easing == "quadraticOut") return via(easing::quadraticOut, vs...); - if (easing == "quadraticInOut") return via(easing::quadraticInOut, vs...); - if (easing == "cubicIn") return via(easing::cubicIn, vs...); - if (easing == "cubicOut") return via(easing::cubicOut, vs...); - if (easing == "cubicInOut") return via(easing::cubicInOut, vs...); - if (easing == "quarticIn") return via(easing::quarticIn, vs...); - if (easing == "quarticOut") return via(easing::quarticOut, vs...); - if (easing == "quarticInOut") return via(easing::quarticInOut, vs...); - if (easing == "quinticIn") return via(easing::quinticIn, vs...); - if (easing == "quinticOut") return via(easing::quinticOut, vs...); - if (easing == "quinticInOut") return via(easing::quinticInOut, vs...); - if (easing == "sinusoidalIn") return via(easing::sinusoidalIn, vs...); - if (easing == "sinusoidalOut") return via(easing::sinusoidalOut, vs...); - if (easing == "sinusoidalInOut") return via(easing::sinusoidalInOut, vs...); - if (easing == "exponentialIn") return via(easing::exponentialIn, vs...); - if (easing == "exponentialOut") return via(easing::exponentialOut, vs...); - if (easing == "exponentialInOut") return via(easing::exponentialInOut, vs...); - if (easing == "circularIn") return via(easing::circularIn, vs...); - if (easing == "circularOut") return via(easing::circularOut, vs...); - if (easing == "circularInOut") return via(easing::circularInOut, vs...); - if (easing == "bounceIn") return via(easing::bounceIn, vs...); - if (easing == "bounceOut") return via(easing::bounceOut, vs...); - if (easing == "bounceInOut") return via(easing::bounceInOut, vs...); - if (easing == "elasticIn") return via(easing::elasticIn, vs...); - if (easing == "elasticOut") return via(easing::elasticOut, vs...); - if (easing == "elasticInOut") return via(easing::elasticInOut, vs...); - if (easing == "backIn") return via(easing::backIn, vs...); - if (easing == "backOut") return via(easing::backOut, vs...); - if (easing == "backInOut") return via(easing::backInOut, vs...); - return via(easing::def, vs...); - } - - template - template - tween & tween::via(const char * easing, Fs... vs) { - return via(std::string(easing)); - } - - template - template - inline tween & tween::during(Ds... ds) { - total = 0; - points.at(points.size() - 2).during(ds...); - for (detail::tweenpoint & p : points) { - total += p.duration(); - p.stacked = total; - } - return *this; - } - - template - inline const typename detail::tweentraits::valuesType & tween::step(int32_t dt, bool suppress) { - dt *= currentDirection; - seek(currentProgress + dt, true); - if (!suppress) - dispatch(onStepCallbacks); - return current; - } - - template - inline const typename detail::tweentraits::valuesType & tween::step(uint32_t dt, bool suppress) { - return step(static_cast(dt), suppress); - } - - template - inline const typename detail::tweentraits::valuesType & tween::step(float dp, bool suppress) { - return step(static_cast(dp * total), suppress); - } - - template - inline const typename detail::tweentraits::valuesType & tween::seek(uint32_t p, bool suppress) { - p = detail::clip(p, 0u, total); - currentProgress = p; - render(p); - if (!suppress) dispatch(onSeekCallbacks); - return current; - } - - template - inline const typename detail::tweentraits::valuesType & tween::seek(int32_t t, bool suppress) { - return seek(static_cast(std::abs(t)), suppress); - } - - template - inline const typename detail::tweentraits::valuesType &tween::seek(float p, bool suppress) { - return seek(static_cast(p * total), suppress); - } - - template - inline uint32_t tween::duration() const { - return total; - } - - template - template - inline void tween::interpolate(uint32_t prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { - auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - prog)); - float pointTotal = static_cast(pointDuration) / static_cast(p.duration(I)); - if (pointTotal > 1.0f) pointTotal = 1.0f; - auto easing = std::get(p.easings); - std::get(values) = easing(pointTotal, std::get(p.values), std::get(points.at(point+1).values)); - interpolate(prog, point, values, detail::int2type{ }); - } - - template - inline void tween::interpolate(uint32_t prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const { - auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - prog)); - float pointTotal = static_cast(pointDuration) / static_cast(p.duration(0)); - if (pointTotal > 1.0f) pointTotal = 1.0f; - auto easing = std::get<0>(p.easings); - std::get<0>(values) = easing(pointTotal, std::get<0>(p.values), std::get<0>(points.at(point+1).values)); - } - - template - inline void tween::render(uint32_t p) { - currentPoint = pointAt(p); - interpolate(p, currentPoint, current, detail::int2type{ }); - } - - template - tween & tween::onStep(typename detail::tweentraits::callbackType callback) { - onStepCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noValuesCallbackType callback) { - onStepCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noTweenCallbackType callback) { - onStepCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::callbackType callback) { - onSeekCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noValuesCallbackType callback) { - onSeekCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noTweenCallbackType callback) { - onSeekCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); - return *this; - } - - template - void tween::dispatch(std::vector & cbVector) { - std::vector dismissed; - for (size_t i = 0; i < cbVector.size(); ++i) { - auto && cb = cbVector[i]; - bool dismiss = detail::call(cb, std::tuple_cat(std::make_tuple(std::ref(*this)), current)); - if (dismiss) dismissed.push_back(i); - } - - if (dismissed.size() > 0) { - for (size_t i = 0; i < dismissed.size(); ++i) { - size_t index = dismissed[i]; - cbVector[index] = cbVector.at(cbVector.size() - 1 - i); - } - cbVector.resize(cbVector.size() - dismissed.size()); - } - } - - template - const typename detail::tweentraits::valuesType & tween::peek() const { - return current; - } - - template - const typename detail::tweentraits::valuesType tween::peek(float progress) const { - typename detail::tweentraits::valuesType values; - uint32_t time = progress * total; - interpolate(time, pointAt(time), values, detail::int2type{ }); - return values; - } - - template - const typename detail::tweentraits::valuesType tween::peek(uint32_t time) const { - typename detail::tweentraits::valuesType values; - interpolate(time, pointAt(time), values, detail::int2type{ }); - return values; - } - - template - uint32_t tween::currentTimePoint() const { - return currentProgress; - } - - template - float tween::progress() const { - return static_cast(currentProgress) / static_cast(total); - } - - template - bool tween::isFinished() const { - return currentProgress == total; - } - - template - tween & tween::forward() { - currentDirection = 1; - return *this; - } - - template - tween & tween::backward() { - currentDirection = -1; - return *this; - } - - template - int tween::direction() const { - return currentDirection; - } - - template - inline const typename detail::tweentraits::valuesType & tween::jump(std::size_t p, bool suppress) { - p = detail::clip(p, static_cast(0), points.size() -1); - return seek(static_cast(points.at(p).stacked), suppress); - } - - template inline uint16_t tween::point() const { - return currentPoint; - } - - template inline uint16_t tween::pointAt(uint32_t progress) const { - progress = detail::clip(progress, 0u, total); - uint16_t point = 0; - while (progress > points.at(point).stacked) point++; - if (point > 0 && progress <= points.at(point - 1u).stacked) point--; - return point; - } -} - -#endif //TWEENY_TWEEN_TCC diff --git a/include/tweenone.tcc b/include/tweenone.tcc deleted file mode 100644 index 6b82fdd..0000000 --- a/include/tweenone.tcc +++ /dev/null @@ -1,341 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * The purpose of this file is to hold implementations for the tween.h file, s - * pecializing on the single value case. - */ -#ifndef TWEENY_TWEENONE_TCC -#define TWEENY_TWEENONE_TCC - -#include "tween.h" -#include "dispatcher.h" - -namespace tweeny { - template inline tween tween::from(T t) { return tween(t); } - template inline tween::tween() { } - template inline tween::tween(T t) { - points.emplace_back(t); - } - - template inline tween & tween::to(T t) { - points.emplace_back(t); - return *this; - } - - template - template - inline tween & tween::via(Fs... vs) { - points.at(points.size() - 2).via(vs...); - return *this; - } - - template - template - inline tween & tween::via(int index, Fs... vs) { - points.at(static_cast(index)).via(vs...); - return *this; - } - - template - template - tween & tween::via(easing::enumerated enumerated, Fs... vs) { - switch (enumerated) { - case easing::enumerated::def: return via(easing::def, vs...); - case easing::enumerated::linear: return via(easing::linear, vs...); - case easing::enumerated::stepped: return via(easing::stepped, vs...); - case easing::enumerated::quadraticIn: return via(easing::quadraticIn, vs...); - case easing::enumerated::quadraticOut: return via(easing::quadraticOut, vs...); - case easing::enumerated::quadraticInOut: return via(easing::quadraticInOut, vs...); - case easing::enumerated::cubicIn: return via(easing::cubicIn, vs...); - case easing::enumerated::cubicOut: return via(easing::cubicOut, vs...); - case easing::enumerated::cubicInOut: return via(easing::cubicInOut, vs...); - case easing::enumerated::quarticIn: return via(easing::quarticIn, vs...); - case easing::enumerated::quarticOut: return via(easing::quarticOut, vs...); - case easing::enumerated::quarticInOut: return via(easing::quarticInOut, vs...); - case easing::enumerated::quinticIn: return via(easing::quinticIn, vs...); - case easing::enumerated::quinticOut: return via(easing::quinticOut, vs...); - case easing::enumerated::quinticInOut: return via(easing::quinticInOut, vs...); - case easing::enumerated::sinusoidalIn: return via(easing::sinusoidalIn, vs...); - case easing::enumerated::sinusoidalOut: return via(easing::sinusoidalOut, vs...); - case easing::enumerated::sinusoidalInOut: return via(easing::sinusoidalInOut, vs...); - case easing::enumerated::exponentialIn: return via(easing::exponentialIn, vs...); - case easing::enumerated::exponentialOut: return via(easing::exponentialOut, vs...); - case easing::enumerated::exponentialInOut: return via(easing::exponentialInOut, vs...); - case easing::enumerated::circularIn: return via(easing::circularIn, vs...); - case easing::enumerated::circularOut: return via(easing::circularOut, vs...); - case easing::enumerated::circularInOut: return via(easing::circularInOut, vs...); - case easing::enumerated::bounceIn: return via(easing::bounceIn, vs...); - case easing::enumerated::bounceOut: return via(easing::bounceOut, vs...); - case easing::enumerated::bounceInOut: return via(easing::bounceInOut, vs...); - case easing::enumerated::elasticIn: return via(easing::elasticIn, vs...); - case easing::enumerated::elasticOut: return via(easing::elasticOut, vs...); - case easing::enumerated::elasticInOut: return via(easing::elasticInOut, vs...); - case easing::enumerated::backIn: return via(easing::backIn, vs...); - case easing::enumerated::backOut: return via(easing::backOut, vs...); - case easing::enumerated::backInOut: return via(easing::backInOut, vs...); - default: return via(easing::def, vs...); - } - } - - template - template - tween & tween::via(const std::string & easing, Fs... vs) { - if (easing == "stepped") return via(easing::stepped, vs...); - if (easing == "linear") return via(easing::linear, vs...); - if (easing == "quadraticIn") return via(easing::quadraticIn, vs...); - if (easing == "quadraticOut") return via(easing::quadraticOut, vs...); - if (easing == "quadraticInOut") return via(easing::quadraticInOut, vs...); - if (easing == "cubicIn") return via(easing::cubicIn, vs...); - if (easing == "cubicOut") return via(easing::cubicOut, vs...); - if (easing == "cubicInOut") return via(easing::cubicInOut, vs...); - if (easing == "quarticIn") return via(easing::quarticIn, vs...); - if (easing == "quarticOut") return via(easing::quarticOut, vs...); - if (easing == "quarticInOut") return via(easing::quarticInOut, vs...); - if (easing == "quinticIn") return via(easing::quinticIn, vs...); - if (easing == "quinticOut") return via(easing::quinticOut, vs...); - if (easing == "quinticInOut") return via(easing::quinticInOut, vs...); - if (easing == "sinusoidalIn") return via(easing::sinusoidalIn, vs...); - if (easing == "sinusoidalOut") return via(easing::sinusoidalOut, vs...); - if (easing == "sinusoidalInOut") return via(easing::sinusoidalInOut, vs...); - if (easing == "exponentialIn") return via(easing::exponentialIn, vs...); - if (easing == "exponentialOut") return via(easing::exponentialOut, vs...); - if (easing == "exponentialInOut") return via(easing::exponentialInOut, vs...); - if (easing == "circularIn") return via(easing::circularIn, vs...); - if (easing == "circularOut") return via(easing::circularOut, vs...); - if (easing == "circularInOut") return via(easing::circularInOut, vs...); - if (easing == "bounceIn") return via(easing::bounceIn, vs...); - if (easing == "bounceOut") return via(easing::bounceOut, vs...); - if (easing == "bounceInOut") return via(easing::bounceInOut, vs...); - if (easing == "elasticIn") return via(easing::elasticIn, vs...); - if (easing == "elasticOut") return via(easing::elasticOut, vs...); - if (easing == "elasticInOut") return via(easing::elasticInOut, vs...); - if (easing == "backIn") return via(easing::backIn, vs...); - if (easing == "backOut") return via(easing::backOut, vs...); - if (easing == "backInOut") return via(easing::backInOut, vs...); - return via(easing::def, vs...); - } - - template - template - tween & tween::via(const char * easing, Fs... vs) { - return via(std::string(easing)); - } - - template - template - inline tween & tween::during(Ds... ds) { - total = 0; - points.at(points.size() - 2).during(ds...); - for (detail::tweenpoint & p : points) { - total += p.duration(); - p.stacked = total; - } - return *this; - } - - template - inline const T & tween::step(int32_t dt, bool suppress) { - dt *= currentDirection; - seek(currentProgress + dt, true); - if (!suppress) dispatch(onStepCallbacks); - return current; - } - - template - inline const T & tween::step(uint32_t dt, bool suppress) { - return step(static_cast(dt), suppress); - } - - template - inline const T & tween::step(float dp, bool suppress) { - return step(static_cast(dp * total), suppress); - } - - template - inline const T & tween::seek(float p, bool suppress) { - return seek(static_cast(p * total), suppress); - } - - template - inline const T & tween::seek(int32_t t, bool suppress) { - t = detail::clip(t, 0, (int32_t) total); - currentProgress = t; - render(t); - if (!suppress) dispatch(onSeekCallbacks); - return current; - } - - template - inline const T & tween::seek(uint32_t t, bool suppress) { - return seek(static_cast(t), suppress); - } - - template - inline uint32_t tween::duration() const { - return total; - } - - template - inline void tween::interpolate(uint32_t prog, unsigned point, T & value) const { - auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - prog)); - float pointTotal = static_cast(pointDuration) / static_cast(p.duration()); - if (pointTotal > 1.0f) pointTotal = 1.0f; - auto easing = std::get<0>(p.easings); - value = easing(pointTotal, std::get<0>(p.values), std::get<0>(points.at(point+1).values)); - } - - template - inline void tween::render(uint32_t p) { - currentPoint = pointAt(p); - interpolate(p, currentPoint, current); - } - - template - tween & tween::onStep(typename detail::tweentraits::callbackType callback) { - onStepCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noValuesCallbackType callback) { - onStepCallbacks.push_back([callback](tween & tween, T) { return callback(tween); }); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noTweenCallbackType callback) { - onStepCallbacks.push_back([callback](tween &, T v) { return callback(v); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::callbackType callback) { - onSeekCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noValuesCallbackType callback) { - onSeekCallbacks.push_back([callback](tween & t, T) { return callback(t); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noTweenCallbackType callback) { - onSeekCallbacks.push_back([callback](tween &, T v) { return callback(v); }); - return *this; - } - - template - void tween::dispatch(std::vector & cbVector) { - std::vector dismissed; - for (size_t i = 0; i < cbVector.size(); ++i) { - auto && cb = cbVector[i]; - bool dismiss = cb(*this, current); - if (dismiss) dismissed.push_back(i); - } - - if (dismissed.size() > 0) { - for (size_t i = 0; i < dismissed.size(); ++i) { - size_t index = dismissed[i]; - cbVector[index] = cbVector.at(cbVector.size() - 1 - i); - } - cbVector.resize(cbVector.size() - dismissed.size()); - } - } - - template - const T & tween::peek() const { - return current; - } - - - template - T tween::peek(float progress) const { - T value; - interpolate(progress * total, pointAt(progress * total), value); - return value; - } - - template - T tween::peek(uint32_t time) const { - T value; - interpolate(time, pointAt(time), value); - return value; - } - - template - uint32_t tween::currentTimePoint() const { - return currentProgress; - } - - template - float tween::progress() const { - return static_cast(currentProgress) / static_cast(total); - } - - template - bool tween::isFinished() const { - return currentProgress == total; - } - - template - tween & tween::forward() { - currentDirection = 1; - return *this; - } - - template - tween & tween::backward() { - currentDirection = -1; - return *this; - } - - template - int tween::direction() const { - return currentDirection; - } - - template - inline const T & tween::jump(size_t p, bool suppress) { - p = detail::clip(p, static_cast(0), points.size() -1); - return seek(points.at(p).stacked, suppress); - } - - template inline uint16_t tween::point() const { - return currentPoint; - } - - template inline uint16_t tween::pointAt(uint32_t timePoint) const { - timePoint = detail::clip(timePoint, 0u, total); - auto t = static_cast(timePoint); - uint16_t point = 0; - while (t > points.at(point).stacked) point++; - if (point > 0 && t <= points.at(point - 1u).stacked) point--; - return point; - } -} -#endif //TWEENY_TWEENONE_TCC diff --git a/include/tweenpoint.h b/include/tweenpoint.h deleted file mode 100644 index 6e4a344..0000000 --- a/include/tweenpoint.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides the declarations for a tween point utility class. A tweenpoint holds the tween values, - * easings and durations. - */ - - -#ifndef TWEENY_TWEENPOINT_H -#define TWEENY_TWEENPOINT_H - - -#include -#include - -#include "tweentraits.h" - -namespace tweeny { - namespace detail { - /* - * The tweenpoint class aids in the management of a tweening point by the tween class. - * This class is private. - */ - template - struct tweenpoint { - typedef detail::tweentraits traits; - - typename traits::valuesType values; - typename traits::durationsArrayType durations; - typename traits::easingCollection easings; - typename traits::callbackType onEnterCallbacks; - uint32_t stacked; - - /* Constructs a tweenpoint from a set of values, filling their durations and easings */ - tweenpoint(Ts... vs); - - /* Set the duration for all the values in this point */ - template void during(D milis); - - /* Sets the duration for each value in this point */ - template void during(Ds... vs); - - /* Sets the easing functions of each value */ - template void via(Fs... fs); - - /* Sets the same easing function for all values */ - template void via(F f); - - /* Returns the highest value in duration array */ - uint16_t duration() const; - - /* Returns the value of that specific value */ - uint16_t duration(size_t i) const; - }; - } -} - -#include "tweenpoint.tcc" - -#endif //TWEENY_TWEENPOINT_H diff --git a/include/tweenpoint.tcc b/include/tweenpoint.tcc deleted file mode 100644 index 1844e7a..0000000 --- a/include/tweenpoint.tcc +++ /dev/null @@ -1,115 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file implements the tweenpoint class - */ - -#ifndef TWEENY_TWEENPOINT_TCC -#define TWEENY_TWEENPOINT_TCC - -#include -#include - -#include "tweenpoint.h" -#include "tweentraits.h" -#include "easing.h" -#include "easingresolve.h" -#include "int2type.h" - -namespace tweeny { - namespace detail { - template void easingfill(EasingCollectionT & f, EasingT easing, int2type) { - easingresolve::impl(f, easing); - easingfill(f, easing, int2type{ }); - } - - template void easingfill(EasingCollectionT & f, EasingT easing, int2type<0>) { - easingresolve<0, TypeTupleT, EasingCollectionT, EasingT>::impl(f, easing); - } - - - template - struct are_same; - - template - struct are_same - { - static const bool value = std::is_same::value && are_same::value; - }; - - template - struct are_same - { - static const bool value = true; - }; - - - template - inline tweenpoint::tweenpoint(Ts... vs) : values{vs...} { - during(static_cast(0)); - via(easing::def); - } - - template - template - inline void tweenpoint::during(D milis) { - for (uint16_t & t : durations) { t = static_cast(milis); } - } - - template - template - inline void tweenpoint::during(Ds... milis) { - static_assert(sizeof...(Ds) == sizeof...(Ts), - "Amount of durations should be equal to the amount of values in a point"); - std::array list = {{ milis... }}; - std::copy(list.begin(), list.end(), durations.begin()); - } - - template - template - inline void tweenpoint::via(Fs... fs) { - static_assert(sizeof...(Fs) == sizeof...(Ts), - "Number of functions passed to via() must be equal the number of values."); - detail::easingresolve<0, std::tuple, typename traits::easingCollection, Fs...>::impl(easings, fs...); - } - - template - template - inline void tweenpoint::via(F f) { - easingfill(easings, f, int2type{ }); - } - - template - inline uint16_t tweenpoint::duration() const { - return *std::max_element(durations.begin(), durations.end()); - } - - template - inline uint16_t tweenpoint::duration(size_t i) const { - return durations.at(i); - } - } -} -#endif //TWEENY_TWEENPOINT_TCC diff --git a/include/tweentraits.h b/include/tweentraits.h deleted file mode 100644 index 2c7bbf8..0000000 --- a/include/tweentraits.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides useful typedefs and traits for a tween. - */ - -#ifndef TWEENY_TWEENTRAITS_H -#define TWEENY_TWEENTRAITS_H - -#include -#include -#include -#include - -namespace tweeny { - template class tween; - - namespace detail { - - template struct equal {}; - template struct equal { enum { value = true }; }; - template struct equal { - enum { value = std::is_same::value && equal::value && equal::value }; - }; - - template struct first { typedef T type; }; - - template - struct valuetype { }; - - template - struct valuetype { - typedef std::tuple type; - }; - - template - struct valuetype { - typedef std::array::type, sizeof...(Ts)> type; - }; - - template - struct tweentraits { - typedef std::tuple...> easingCollection; - typedef std::function &, Ts...)> callbackType; - typedef std::function &)> noValuesCallbackType; - typedef std::function noTweenCallbackType; - typedef typename valuetype::value, Ts...>::type valuesType; - typedef std::array durationsArrayType; - typedef tween type; - }; - } -} - -#endif //TWEENY_TWEENTRAITS_H diff --git a/include/tweeny.h b/include/tweeny.h deleted file mode 100644 index aceae1f..0000000 --- a/include/tweeny.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @file tweeny.h - * This file is the main header file for Tweeny. You should not need to include anything else. - */ - -/** - * @mainpage Tweeny - * - * Tweeny is an inbetweening library designed for the creation of complex animations for games and other beautiful - * interactive software. It leverages features of modern C++ to empower developers with an intuitive API for - * declaring tweenings of any type of value, as long as they support arithmetic operations. - * - * This document contains Tweeny's API reference. The most interesting parts are: - * - * * The Fine @ref manual - * * The tweeny::from global function, to start a new tween. - * * The tweeny::tween class itself, that has all the interesting methods for a tween. - * * The modules page has a list of type of easings. - * - * This is how the API looks like: - * - * @code - * - * #include "tweeny.h" - * - * using tweeny::easing; - * - * int main() { - * // steps 1% each iteration - * auto tween = tweeny::from(0).to(100).during(100).via(easing::linear); - * while (tween.progress() < 1.0f) tween.step(0.01f); - * - * // a tween with multiple values - * auto tween2 = tweeny::from(0, 1.0f).to(1200, 7.0f).during(1000).via(easing::backInOut, easing::linear); - * - * // a tween with multiple points, different easings and durations - * auto tween3 = tweeny::from(0, 0) - * .to(100, 100).during(100).via(easing::backOut, easing::backOut) - * .to(200, 200).during(500).via(easing::linear); - * return 0; - * } - * - * @endcode - * - * **Examples** - * - * * Check tweeny-demos repository to see demonstration code - * - * **Useful links and references** - * * Tim Groleau's easing function generator (requires flash) - * * Easing cheat sheet (contains graphics!) - */ - -#ifndef TWEENY_H -#define TWEENY_H - -#include "tween.h" -#include "easing.h" - -/** - * @brief The tweeny namespace contains all symbols and names for the Tweeny library. - */ -namespace tweeny { - /** - * @brief Creates a tween starting from the values defined in the arguments. - * - * Starting values can have heterogeneous types, even user-defined types, provided they implement the - * four arithmetic operators (+, -, * and /). The types used will also define the type of each next step, the type - * of the callback and the type of arguments the passed easing functions must have. - * - * @sa tweeny::tween - */ - template tween from(Ts... vs); -} - -#include "tweeny.tcc" - -#endif //TWEENY_TWEENY_H diff --git a/include/tweeny.tcc b/include/tweeny.tcc deleted file mode 100644 index 9b16d3d..0000000 --- a/include/tweeny.tcc +++ /dev/null @@ -1,40 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides the implementation for tweeny.h - */ - -#ifndef TWEENY_TWEENY_TCC -#define TWEENY_TWEENY_TCC - -#include "tween.h" - -namespace tweeny { - template inline tween from(Ts... vs) { - return tween::from(vs...); - } -} - -#endif //TWEENY_TWEENY_TCC diff --git a/include/tweeny/detail/easing/back.h b/include/tweeny/detail/easing/back.h new file mode 100644 index 0000000..e4b8d6f --- /dev/null +++ b/include/tweeny/detail/easing/back.h @@ -0,0 +1,78 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_BACK_H +#define TWEENY_DETAIL_EASING_BACK_H + +namespace tweeny::detail { + struct backInEasing { + template + static T run(float position, T start, T end) { + constexpr float s = 1.70158f; + float postFix = position; + return static_cast((end - start) * postFix * position * ((s + 1) * position - s) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct backOutEasing { + template + static T run(float position, T start, T end) { + constexpr float s = 1.70158f; + position -= 1; + return static_cast((end - start) * ((position) * position * ((s + 1) * position + s) + 1) + start); + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct backInOutEasing { + template + static T run(float position, T start, T end) { + float s = 1.70158f; + float t = position; + auto b = start; + auto c = end - start; + constexpr float d = 1; + s *= 1.525f; + if ((t /= d / 2) < 1) return static_cast(c / 2 * (t * t * ((s + 1) * t - s)) + b); + const float postFix = t -= 2; + return static_cast(c / 2 * (postFix * t * ((s + 1) * t + s) + 2) + b); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_BACK_H diff --git a/include/tweeny/detail/easing/bounce.h b/include/tweeny/detail/easing/bounce.h new file mode 100644 index 0000000..20e882a --- /dev/null +++ b/include/tweeny/detail/easing/bounce.h @@ -0,0 +1,81 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_BOUNCE_H +#define TWEENY_DETAIL_EASING_BOUNCE_H + +namespace tweeny::detail { + struct bounceOutEasing { + template + static T run(float position, T start, T end) { + T c = end - start; + if (position < 1 / 2.75f) { + return static_cast(c * (7.5625f * position * position) + start); + } + if (position < 2.0f / 2.75f) { + const float postFix = position -= 1.5f / 2.75f; + return static_cast(c * (7.5625f * (postFix) * position + .75f) + start); + } + if (position < 2.5f / 2.75f) { + const float postFix = position -= 2.25f / 2.75f; + return static_cast(c * (7.5625f * postFix * position + .9375f) + start); + } + const float postFix = position -= (2.625f / 2.75f); + return static_cast(c * (7.5625f * postFix * position + .984375f) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct bounceInEasing { + template + static T run(const float position, T start, T end) { + return end - start - bounceOutEasing::run(1 - position, T(), (end - start)) + start; + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct bounceInOutEasing { + template + static T run(const float position, T start, T end) { + if (position < 0.5f) return static_cast(bounceInEasing::run(position * 2, T(), end - start) * .5f + start); + return static_cast(bounceOutEasing::run(position * 2 - 1, T(), end - start) * .5f + (end - start) * .5f + + start); + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_BOUNCE_H diff --git a/include/tweeny/detail/easing/circular.h b/include/tweeny/detail/easing/circular.h new file mode 100644 index 0000000..707c647 --- /dev/null +++ b/include/tweeny/detail/easing/circular.h @@ -0,0 +1,77 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_CIRCULAR_H +#define TWEENY_DETAIL_EASING_CIRCULAR_H + +#include + +namespace tweeny::detail { + struct circularInEasing { + template + static T run(const float position, T start, T end) { + return static_cast(-(end - start) * (sqrtf(1 - position * position) - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct circularOutEasing { + template + static T run(float position, T start, T end) { + --position; + return static_cast((end - start) * sqrtf(1 - position * position) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct circularInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast(-(end - start) / 2 * (sqrtf(1 - position * position) - 1) + start); + } + + position -= 2; + return static_cast((end - start) / 2 * (sqrtf(1 - position * position) + 1) + start); + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; +} + + + +#endif // TWEENY_DETAIL_EASING_CIRCULAR_H diff --git a/include/tweeny/detail/easing/cubic.h b/include/tweeny/detail/easing/cubic.h new file mode 100644 index 0000000..40ad1a2 --- /dev/null +++ b/include/tweeny/detail/easing/cubic.h @@ -0,0 +1,72 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_CUBIC_H +#define TWEENY_DETAIL_EASING_CUBIC_H + +namespace tweeny::detail { + struct cubicInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct cubicOutEasing { + template + static T run(float position, T start, T end) { + --position; + return static_cast((end - start) * (position * position * position + 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct cubicInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * position * position * position + start); + } + position -= 2; + return static_cast((end - start) / 2 * (position * position * position + 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_CUBIC_H diff --git a/include/tweeny/detail/easing/def.h b/include/tweeny/detail/easing/def.h new file mode 100644 index 0000000..cf032ca --- /dev/null +++ b/include/tweeny/detail/easing/def.h @@ -0,0 +1,75 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_DEF_H +#define TWEENY_DETAIL_EASING_DEF_H + +#include +#include +#include + +namespace tweeny::detail { + template struct voidify { + using type = void; + }; + + template using void_t = typename voidify::type; + + template + struct supports_arithmetic_operations : std::false_type {}; + + template + struct supports_arithmetic_operations() + std::declval()), + decltype(std::declval() - std::declval()), + decltype(std::declval() * std::declval()), + decltype(std::declval() * std::declval()), + decltype(std::declval() * std::declval()) + >> : std::true_type {}; + + struct defaultEasing { + template + static std::enable_if_t, T> run(float position, T start, T end) { + return static_cast(roundf((end - start) * position + start)); + } + + template + static typename std::enable_if::value && !std::is_integral::value, T>::type + run(float position, T start, T end) { + return static_cast((end - start) * position + start); + } + + template + static std::enable_if_t::value, T> run(float /*position*/, T start, T /*end*/) { + return start; + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_DEF_H diff --git a/include/tweeny/detail/easing/elastic.h b/include/tweeny/detail/easing/elastic.h new file mode 100644 index 0000000..1bc97a6 --- /dev/null +++ b/include/tweeny/detail/easing/elastic.h @@ -0,0 +1,98 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_ELASTIC_H +#define TWEENY_DETAIL_EASING_ELASTIC_H + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace tweeny::detail { + struct elasticInEasing { + template + static T run(float position, T start, T end) { + if (position <= 0.00001f) return start; + if (position >= 0.999f) return end; + const float p = .3f; + auto a = end - start; + const float s = p / 4; + const float postFix = a * powf(2, 10 * (position -= 1)); + return static_cast(-(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct elasticOutEasing { + template + static T run(const float position, T start, T end) { + if (position <= 0.00001f) return start; + if (position >= 0.999f) return end; + float p = .3f; + auto a = end - start; + float s = p / 4; + return static_cast(a * powf(2, -10 * position) * sinf( + (position - s) * (2 * static_cast(M_PI)) / p + ) + end); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct elasticInOutEasing { + template + static T run(float position, T start, T end) { + if (position <= 0.00001f) return start; + if (position >= 0.999f) return end; + position *= 2; + const float p = .3f * 1.5f; + auto a = end - start; + const float s = p / 4; + float postFix; + + if (position < 1) { + postFix = a * powf(2, 10 * (position -= 1)); + return static_cast(-0.5f * (postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); + } + postFix = a * powf(2, -10 * (position -= 1)); + return static_cast(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p) * .5f + end); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_ELASTIC_H diff --git a/include/tweeny/detail/easing/exponential.h b/include/tweeny/detail/easing/exponential.h new file mode 100644 index 0000000..5522d00 --- /dev/null +++ b/include/tweeny/detail/easing/exponential.h @@ -0,0 +1,77 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_EXPONENTIAL_H +#define TWEENY_DETAIL_EASING_EXPONENTIAL_H + +#include + +namespace tweeny::detail { + struct exponentialInEasing { + template + static T run(const float position, T start, T end) { + if (position == 0) return start; + return static_cast((end - start) * powf(2, 10 * (position - 1)) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct exponentialOutEasing { + template + static T run(const float position, T start, T end) { + if (position == 1) return end; + return static_cast((end - start) * (-powf(2, -10 * position) + 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct exponentialInOutEasing { + template + static T run(float position, T start, T end) { + if (position == 0) return start; + if (position == 1) return end; + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * powf(2, 10 * (position - 1)) + start); + } + --position; + return static_cast((end - start) / 2 * (-powf(2, -10 * position) + 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_EXPONENTIAL_H diff --git a/include/tweeny/detail/easing/linear.h b/include/tweeny/detail/easing/linear.h new file mode 100644 index 0000000..0a72e5b --- /dev/null +++ b/include/tweeny/detail/easing/linear.h @@ -0,0 +1,50 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_LINEAR_H +#define TWEENY_DETAIL_EASING_LINEAR_H + +#include +#include + +namespace tweeny::detail { + struct linearEasing { + template + static std::enable_if_t, T> run(const float position, T start, T end) { + return static_cast(roundf((end - start) * position + start)); + } + + template + static std::enable_if_t, T> run(const float position, T start, T end) { + return static_cast((end - start) * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_LINEAR_H diff --git a/include/tweeny/detail/easing/quadratic.h b/include/tweeny/detail/easing/quadratic.h new file mode 100644 index 0000000..f4b513f --- /dev/null +++ b/include/tweeny/detail/easing/quadratic.h @@ -0,0 +1,72 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_QUADRATIC_H +#define TWEENY_DETAIL_EASING_QUADRATIC_H + +namespace tweeny::detail { + struct quadraticInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quadraticOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast((-(end - start)) * position * (position - 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quadraticInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * position * position + start); + } + + --position; + return static_cast(-(end - start) / 2 * (position * (position - 2) - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_QUADRATIC_H diff --git a/include/tweeny/detail/easing/quartic.h b/include/tweeny/detail/easing/quartic.h new file mode 100644 index 0000000..d78fe72 --- /dev/null +++ b/include/tweeny/detail/easing/quartic.h @@ -0,0 +1,72 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_QUARTIC_H +#define TWEENY_DETAIL_EASING_QUARTIC_H + +namespace tweeny::detail { + struct quarticInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quarticOutEasing { + template + static T run(float position, T start, T end) { + --position; + return static_cast(-(end - start) * (position * position * position * position - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quarticInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * (position * position * position * position) + start); + } + position -= 2; + return static_cast(-(end - start) / 2 * (position * position * position * position - 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_QUARTIC_H diff --git a/include/tweeny/detail/easing/quintic.h b/include/tweeny/detail/easing/quintic.h new file mode 100644 index 0000000..971cda0 --- /dev/null +++ b/include/tweeny/detail/easing/quintic.h @@ -0,0 +1,72 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_QUINTIC_H +#define TWEENY_DETAIL_EASING_QUINTIC_H + +namespace tweeny::detail { + struct quinticInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position * position * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quinticOutEasing { + template + static T run(float position, T start, T end) { + position--; + return static_cast((end - start) * (position * position * position * position * position + 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quinticInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * (position * position * position * position * position) + start); + } + position -= 2; + return static_cast((end - start) / 2 * (position * position * position * position * position + 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_QUINTIC_H diff --git a/include/tweeny/detail/easing/sinusoidal.h b/include/tweeny/detail/easing/sinusoidal.h new file mode 100644 index 0000000..a581cdf --- /dev/null +++ b/include/tweeny/detail/easing/sinusoidal.h @@ -0,0 +1,72 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_SINUSOIDAL_H +#define TWEENY_DETAIL_EASING_SINUSOIDAL_H + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace tweeny::detail { + struct sinusoidalInEasing { + template + static T run(const float position, T start, T end) { + return static_cast(-(end - start) * cosf(position * static_cast(M_PI) / 2) + (end - start) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct sinusoidalOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * sinf(position * static_cast(M_PI) / 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct sinusoidalInOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast(-(end - start) / 2 * (cosf(position * static_cast(M_PI)) - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_SINUSOIDAL_H diff --git a/include/tweeny/detail/easing/stepped.h b/include/tweeny/detail/easing/stepped.h new file mode 100644 index 0000000..91f82f6 --- /dev/null +++ b/include/tweeny/detail/easing/stepped.h @@ -0,0 +1,42 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EASING_STEPPED_H +#define TWEENY_DETAIL_EASING_STEPPED_H + +namespace tweeny::detail { + struct steppedEasing { + template + static T run(float /*position*/, T start, T /*end*/) { + return start; + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +#endif // TWEENY_DETAIL_EASING_STEPPED_H diff --git a/include/tweeny/detail/event.h b/include/tweeny/detail/event.h new file mode 100644 index 0000000..d6477d2 --- /dev/null +++ b/include/tweeny/detail/event.h @@ -0,0 +1,120 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EVENT_H +#define TWEENY_DETAIL_EVENT_H + +#include + +namespace tweeny::detail::event { + /** + * @brief Tag type for step events. + * @internal Implementation detail - users should use tweeny::event::step + */ + struct step_t {}; + + /** + * @brief Tag type for seek events. + * @internal Implementation detail - users should use tweeny::event::seek + */ + struct seek_t {}; + + /** + * @brief Tag type for jump events. + * @internal Implementation detail - users should use tweeny::event::jump + */ + struct jump_t {}; + + /** + * @brief Tag type for completion events. + * @internal Implementation detail - users should use tweeny::event::complete + */ + struct complete_t {}; + + /** + * @brief Tag type for keyframeEnter events. + * @internal Implementation detail - users should use tweeny::event::keyframeEnter + */ + struct keyframeEnter_t {}; + + /** + * @brief Tag type for keyframeLeave events. + * @internal Implementation detail - users should use tweeny::event::keyframeLeave + */ + struct keyframeLeave_t {}; + + /** + * @brief Tag type for update events. + * @internal Implementation detail - users should use tweeny::event::update + */ + struct update_t {}; +} + +namespace tweeny::event { + /** + * @brief Event data passed when entering a new keyframe. + * + * This event is triggered when the tween transitions into a new keyframe section, + * providing the index of the keyframe being entered. + */ + struct keyframeEnter { + size_t key_frame; + explicit keyframeEnter(const size_t key_frame_input) : key_frame(key_frame_input) {} + }; + + /** + * @brief Event data passed when leaving a keyframe. + * + * This event is triggered when the tween transitions out of a keyframe section, + * providing the index of the keyframe being exited. + */ + struct keyframeLeave { + size_t key_frame; + explicit keyframeLeave(const size_t key_frame_input) : key_frame(key_frame_input) {} + }; + + /** + * @brief Response codes returned by event callbacks. + * + * Controls whether a callback continues receiving events or is automatically removed. + */ + enum class response { + /** + * @brief Continue receiving events. + * + * The callback remains registered and will be invoked on future events. + */ + ok = 0, + + /** + * @brief Unsubscribe after this callback. + * + * The callback is automatically removed after returning and will not + * receive future events. Useful for one-shot callbacks. + */ + unsubscribe = 1, + }; +} + +#endif // TWEENY_DETAIL_EVENT_H diff --git a/include/tweeny/detail/interpolate.h b/include/tweeny/detail/interpolate.h new file mode 100644 index 0000000..2679aab --- /dev/null +++ b/include/tweeny/detail/interpolate.h @@ -0,0 +1,62 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_INTERPOLATE_H +#define TWEENY_INTERPOLATE_H +#include +#include +#include + +#include "key-frame.h" +#include "../easing.h" + +namespace tweeny::detail { + template + static auto interpolate_one( + float t, + const key_frame & base, + const key_frame & next + ) -> std::remove_reference_t(base.values))> { + const auto & start = std::get(base.values); + const auto & end = std::get(next.values); + const auto & func = std::get(base.easing_functions); + if (func) return func(t, start, end); + return easing::def(t, start, end); + } + + template + static auto interpolate_values( + float t, + const key_frame & base, + const key_frame & next, + std::index_sequence + ) -> typename key_frame::values_t { + using values_t = typename key_frame::values_t; + values_t out{}; + ((std::get(out) = interpolate_one(t, base, next)), ...); + return out; + } +} + +#endif //TWEENY_INTERPOLATE_H diff --git a/include/tweeny/detail/key-frame.h b/include/tweeny/detail/key-frame.h new file mode 100644 index 0000000..e5e5478 --- /dev/null +++ b/include/tweeny/detail/key-frame.h @@ -0,0 +1,131 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_KEY_FRAME_H +#define TWEENY_DETAIL_KEY_FRAME_H + + +#include + +#include "value-container.h" + +#include +#include +#include + +namespace tweeny::detail { + template + /** + * @brief Represents a key frame in a tween. + * + * The key_frame class encapsulates the attributes and behaviors + * of a single frame in an animation sequence, allowing for storage + * and manipulation of key values at specific points. + * + * @warning This structure is private and shouldn't be used directly + */ + struct key_frame { + typedef value_container_t values_t; + typedef std::tuple...> value_easing_functions_t; + typedef std::array value_tween_frame_counts_t; + + /** + * @brief Constructs a key_frame object initialized with provided values. + * + * This constructor initializes the key_frame object by setting its position to 0, + * populating the values container with the provided values `vs...`, and initializing + * the easing functions and tween frame counts with default values. + * + * @param vs The values to initialize the key_frame, passed as a parameter pack of type `ValueTypes...`. + * These values are stored in a container, which can either be a tuple or an array depending + * on their types. + * @return A key_frame object initialized with the provided values. + */ + explicit key_frame(ValueTypes... vs) : position(0), values{vs...}, easing_functions(), tween_frame_counts() {} + + + /** + * Holds the 'position' of the key frame within the animation sequence. + * + * This variable represents the position of the current key frame as an unsigned 32-bit integer. + * It refers to the frame index at which this key frame is located + * within the timeline of a tween. + */ + uint32_t position; + + /** + * @typedef values_t + * @brief Represents a container to store values in `key_frame`. + * + * The `values_t` type alias is used to hold the data values associated with + * the `key_frame` structure. The container type is determined at compile-time + * based on the type consistency of the provided value types. If all value types + * are the same, the container is implemented as a `std::array`. Otherwise, it + * is implemented as a `std::tuple`. + */ + values_t values; + + /** + * @typedef value_easing_functions_t easing_functions + * @brief Represents a tuple of easing function objects for each value component in a key frame. + * + * This variable contains easing functions used to interpolate or transform + * individual value components over time in a tweening operation. + * Each easing function is specified as a callable object (e.g., a lambda or function) + * following the signature `(float progress, ValueType start, ValueType end) -> ValueType`. + * + * It is used within the context of the tween system to determine how intermediate + * values between this key frame and the next are computed based on the specified easing functions. + */ + value_easing_functions_t easing_functions; + + /** + * An array that represents the number of frames allocated for tweening + * each value in a key frame. The size of the array corresponds to the + * number of values being tweened, and each entry specifies the frame + * count for the respective value. + * + * This array is populated either by specifying explicit frame counts + * for each value during a tween's construction or by using a uniform + * frame count for all values. The highest count is used to calculate + * the position of key frames and manage the timing of individual + * value transitions within a tween animation. + */ + value_tween_frame_counts_t tween_frame_counts; + + /** + * @brief Retrieves the highest frame count from the tween_frame_counts array. + * + * This function searches through the tween_frame_counts array to find + * and return the maximum frame count value. + * + * @return The highest frame count as an unsigned 32-bit integer. + */ + [[nodiscard]] uint32_t highest_frame_count() const { + return *std::max_element(tween_frame_counts.begin(), tween_frame_counts.end()); + } + }; +} + +#endif //TWEENY_DETAIL_KEY_FRAME_H diff --git a/include/tweeny/detail/tuple-utilities.h b/include/tweeny/detail/tuple-utilities.h new file mode 100644 index 0000000..914f542 --- /dev/null +++ b/include/tweeny/detail/tuple-utilities.h @@ -0,0 +1,61 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_TUPLE_UTILITIES_H +#define TWEENY_DETAIL_TUPLE_UTILITIES_H +#include +#include + +namespace tweeny::detail { + + /** + * Implements the creation of a tuple with repeated elements of the given value. + * + * @param v The value to be repeated in the tuple. + * @param sequence An index sequence used to generate the tuple with the required size. + * @return A tuple where each element is a copy of the input value. + * + * @warning This function is private and shouldn't be used directly + */ + template + static auto make_repeated_tuple_impl(const T & v, std::index_sequence sequence) { + (void) sequence; + return std::make_tuple(((void) I, v)...); + } + + /** + * Creates a tuple of size N where each element is a copy of the given value. + * + * @param v The value to be repeated in the tuple. + * @return A tuple of size N with each element being a copy of the input value. + * + * @warning This function is private and shouldn't be used directly + */ + template + static auto make_repeated_tuple(const T & v) { + return make_repeated_tuple_impl(v, std::make_index_sequence{}); + } +} + +#endif //TWEENY_DETAIL_TUPLE_UTILITIES_H diff --git a/include/tweeny/detail/tween-value.h b/include/tweeny/detail/tween-value.h new file mode 100644 index 0000000..0b4ec2e --- /dev/null +++ b/include/tweeny/detail/tween-value.h @@ -0,0 +1,65 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_TWEEN_VALUE_H +#define TWEENY_DETAIL_TWEEN_VALUE_H + +#include +#include "value-container.h" + +namespace tweeny::detail { + + /** + * @struct tween_value + * @brief Metafunction that resolves to a type based on the provided template arguments. + * + * This struct determines the appropriate type depending on the number and types + * of its template parameters. If there is only one parameter (First), it resolves + * to that type. Otherwise, it resolves to a `value_container_t` type, which is + * constructed using the provided `First` and additional `Rest` types. + * + * @tparam First The first type parameter used for evaluation. + * @tparam Rest Additional type parameters provided for consideration. These are + * used to construct a `value_container_t` if more than one parameter is passed. + * + * @see value_container_t + * @see tween_value_t + * + * @warning This structure is private and shouldn't be used directly + */ + template + struct tween_value { + using type = std::conditional_t< + sizeof...(Rest) == 0, + First, + value_container_t + >; + }; + + template + using tween_value_t = typename tween_value::type; + +} + +#endif // TWEENY_DETAIL_TWEEN_VALUE_H diff --git a/include/tweeny/detail/value-container.h b/include/tweeny/detail/value-container.h new file mode 100644 index 0000000..3ef804c --- /dev/null +++ b/include/tweeny/detail/value-container.h @@ -0,0 +1,95 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_VALUE_CONTAINER_H +#define TWEENY_DETAIL_VALUE_CONTAINER_H + +#include +#include + +namespace tweeny::detail { + /** + * @struct all_same + * @brief A type trait that evaluates to `std::true_type`. + * + * This specialization of the `all_same` struct is used when no template parameters + * are provided. It serves as a base case for type pack evaluation and always evaluates + * to `true_type`, representing that an empty pack is trivially considered to have + * "all the same" types. + * + * This type trait is mainly used as a base case in conjunction with other + * template specializations that check for type consistency within a parameter pack. + * + * @warning This structure is private and shouldn't be used directly + */ + template + struct all_same : std::true_type {}; + + + /** + * @struct all_same + * @brief A type trait to check if all types in a parameter pack are the same. + * + * This struct inherits from `std::conjunction` with the result of comparing all + * types in the parameter pack `Rest...` using `std::is_same`, relative to the first + * type `First`. The resulting value will evaluate to `true_type` if all types are the + * same, otherwise to `false_type`. + * + * @tparam First The first type in the parameter pack to compare against. + * @tparam Rest The remaining types in the parameter pack. + * + * @warning This structure is private and shouldn't be used directly + */ + template + struct all_same : std::conjunction...> {}; + + /** + * @brief A compile-time boolean that is true if all types in the provided parameter pack are the same, false otherwise. + * + * Determines whether all types in the template parameter pack `Ts...` are the same type. This is evaluated at + * compile-time using the `all_same` trait. + * + * Example usage: + * @code + * static_assert(all_same_v == true); + * static_assert(all_same_v == false); + * @endcode + * + * @tparam Ts pack of types to be evaluated + * + * @warning This value is private and shouldn't be used directly + */ + template + inline constexpr bool all_same_v = all_same::value; + + template + using value_container_t = + std::conditional_t< + all_same_v, + std::array>, sizeof...(Ts)>, + std::tuple + >; +} + +#endif //TWEENY_DETAIL_VALUE_CONTAINER_H diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h new file mode 100644 index 0000000..da7c814 --- /dev/null +++ b/include/tweeny/easing.h @@ -0,0 +1,1468 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @file easing.h + * The purpose of this file is to list all bundled easings. Each easing is defined + * in its own header under include/tweeny/detail/easing/. Users may include individual + * easing headers or this header to get all easings. + */ + +#ifndef TWEENY_EASING_H +#define TWEENY_EASING_H + +#include "detail/easing/back.h" +#include "detail/easing/bounce.h" +#include "detail/easing/circular.h" +#include "detail/easing/cubic.h" +#include "detail/easing/def.h" +#include "detail/easing/elastic.h" +#include "detail/easing/exponential.h" +#include "detail/easing/linear.h" +#include "detail/easing/quadratic.h" +#include "detail/easing/quartic.h" +#include "detail/easing/quintic.h" +#include "detail/easing/sinusoidal.h" +#include "detail/easing/stepped.h" + +/** + * @namespace tweeny::easing + * @brief Contains all built-in easing functions for controlling animation curves. + * + * Provides 30+ easing functions in In, Out, and InOut variants for creating natural-looking + * animations. + */ +namespace tweeny::easing { + /** + * @brief Easing function that creates anticipation by pulling back before moving forward. + * + * The `backIn` easing function begins by moving backwards slightly (overshooting in the + * negative direction), then reverses and accelerates toward the target value. This creates + * an anticipation effect similar to pulling back a slingshot or winding up before a throw. + * + * The backward motion at the start makes this easing particularly effective for animations + * that benefit from telegraphing or anticipation, such as: + * - UI elements that "wind up" before sliding in + * - Character movements that show preparation before action + * - Camera movements that pull back before zooming forward + * + * Mathematically, this easing uses the formula: `c * t * t * ((s + 1) * t - s) + b` + * where `s` controls the overshoot amount (typically 1.70158). + * + * @note The animation will briefly have values less than the start value before proceeding + * to the target. Ensure your animation system can handle values outside the expected range. + * + * @code + * // Create a tween with back-in easing for anticipation effect + * auto tween = tweeny::from(0.0f) + * .to(100.0f) + * .via(easing::backIn) + * .during(60U) + * .build(); + * + * // The animation will dip below 0.0f briefly before accelerating to 100.0f + * @endcode + * + * @see backOut For overshoot at the end instead of the beginning + * @see backInOut For anticipation at the start and overshoot at the end + * @see Visualize at https://easings.net/#easeInBack + */ + inline constexpr detail::backInEasing backIn{}; + + /** + * @brief Easing function that overshoots the target before settling back. + * + * The `backOut` easing function accelerates toward and past the target value, then pulls + * back to settle at the final position. This creates an overshoot or "spring-back" effect + * that adds liveliness and energy to animations. + * + * This easing is particularly effective for: + * - UI elements that pop into place with energy (buttons, dialogs, notifications) + * - Object arrivals that should feel bouncy and dynamic + * - Emphasizing the end state of an animation + * - Adding personality to mechanical movements + * + * The overshoot creates a more natural, less rigid feel compared to standard easing + * functions. It's widely used in iOS, Android, and Material Design animation patterns + * to make interactions feel more responsive and playful. + * + * Mathematically, this is the inverse of backIn, applied at the end of the transition. + * + * @note The animation will briefly exceed the target value before settling. Ensure your + * rendering or logic can handle values beyond the specified range. + * + * @code + * // Animate a button with overshoot for a lively effect + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::backOut) + * .during(30U) + * .build(); + * + * // Scale will exceed 1.0f (e.g., 1.1f) before settling back to 1.0f + * @endcode + * + * @see backIn For anticipation at the start instead of overshoot at the end + * @see backInOut For both anticipation and overshoot + * @see elasticOut For a more pronounced oscillating overshoot effect + * @see Visualize at https://easings.net/#easeOutBack + */ + inline constexpr detail::backOutEasing backOut{}; + + /** + * @brief Easing function combining anticipation at the start and overshoot at the end. + * + * The `backInOut` easing function creates a dramatic motion curve by pulling back before + * the start (anticipation), accelerating through the middle, then overshooting past the + * target before settling (spring-back). This combines the effects of both backIn and + * backOut for maximum expressiveness. + * + * This easing creates highly dynamic animations suitable for: + * - Attention-grabbing transitions that need maximum visual interest + * - Character animations requiring wind-up and follow-through + * - Scene transitions with dramatic flair + * - Emphasizing both the start and end states of an animation + * + * The dual overshoot (negative at start, positive at end) makes this one of the most + * expressive easings, but it should be used judiciously as it can feel exaggerated if + * overused. It works best for focal animations rather than ambient motion. + * + * @note The animation will have values outside the start-to-target range at both ends. + * During the first half, values will dip below the start; during the second half, values + * will exceed the target before settling. + * + * @code + * // Create a dramatic page transition + * auto position = tweeny::from(-100.0f) + * .to(100.0f) + * .via(easing::backInOut) + * .during(90U) + * .build(); + * + * // Position will go below -100.0f at start and above 100.0f near end + * @endcode + * + * @see backIn For only anticipation without overshoot + * @see backOut For only overshoot without anticipation + * @see elasticInOut For a more extreme oscillating version + * @see Visualize at https://easings.net/#easeInOutBack + */ + inline constexpr detail::backInOutEasing backInOut{}; + + /** + * @brief Bounce easing simulating a ball bouncing with increasing height at the start. + * + * The `bounceIn` easing function creates a bouncing effect before the animation begins, + * like a ball dropped from above that bounces progressively higher before launching into + * the main motion. This is the reverse of bounceOut's natural physics. + * + * Characteristics: + * - Multiple discrete "bounces" at the start + * - Each bounce is higher than the previous + * - Creates anticipation through impact-based motion + * - Less commonly used than bounceOut + * + * This easing works well for: + * - **Landing preparation**: Elements about to drop into place + * - **Impact anticipation**: Building up to a collision + * - **Reverse playback effects**: Bounced animations played backward + * - **Stylized entrances**: Cartoon-style wind-up effects + * + * BounceIn is less intuitive than bounceOut because it reverses natural physics (balls + * don't normally bounce higher each time). Consider backIn for more natural anticipation. + * + * @code + * // Element that bounces before sliding in + * auto position = tweeny::from(0.0f) + * .to(200.0f) + * .via(easing::bounceIn) + * .during(50U) + * .build(); + * @endcode + * + * @see bounceOut For natural ball-drop bouncing at the end + * @see bounceInOut For bouncing at both start and end + * @see backIn For simpler anticipation without discrete impacts + * @see Visualize at https://easings.net/#easeInBounce + */ + inline constexpr detail::bounceInEasing bounceIn{}; + + /** + * @brief Bounce easing simulating a ball dropping and bouncing to rest. + * + * The `bounceOut` easing function creates the classic ball-drop effect where an object + * bounces several times with decreasing height before coming to rest. This mimics real-world + * physics of an inelastic collision and is one of the most recognizable easing patterns. + * + * Characteristics: + * - Multiple discrete bounces with decreasing amplitude + * - Simulates impact and energy loss + * - Creates playful, physical motion + * - Widely recognized and understood by users + * + * This easing excels at: + * - **Object drops**: Items falling into place + * - **Landing animations**: Characters, UI elements touching down + * - **Playful interactions**: Buttons, icons, notifications + * - **Game elements**: Collectibles, power-ups, score displays + * - **Error/success feedback**: Visual confirmation with personality + * - **Cartoon physics**: Exaggerated, entertaining motion + * + * BounceOut is more popular than elasticOut when you want discrete impacts rather than + * smooth oscillation. It conveys weight and physicality better than spring-based easings. + * + * The bouncing creates natural emphasis on the final position, making it excellent for + * drawing attention to where something lands. + * + * @code + * // Notification dropping in from above + * auto y = tweeny::from(-100.0f) + * .to(0.0f) + * .via(easing::bounceOut) + * .during(45U) + * .build(); + * + * // Button that bounces into place + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::bounceOut) + * .during(40U) + * .build(); + * @endcode + * + * @see bounceIn For inverse bouncing at the start + * @see bounceInOut For bouncing at both ends + * @see elasticOut For smooth spring-like alternative + * @see Visualize at https://easings.net/#easeOutBounce + */ + inline constexpr detail::bounceOutEasing bounceOut{}; + + /** + * @brief Bounce easing with bouncing at both start and end. + * + * The `bounceInOut` easing function combines reverse bouncing at the start with natural + * bouncing at the end. The motion bounces with increasing height initially, accelerates + * through the middle, then bounces to rest at the target. + * + * Motion profile: + * - First half: Bounces with increasing amplitude (bounceIn) + * - Midpoint: Smooth transition + * - Second half: Bounces with decreasing amplitude (bounceOut) + * - Creates playful, impact-based motion at both ends + * + * This easing is appropriate for: + * - **Playful transitions**: Fun, energetic scene changes + * - **Game UI**: High-energy, cartoon-style interfaces + * - **Children's applications**: Whimsical, entertaining motion + * - **Attention-grabbing effects**: Elements that need maximum personality + * + * BounceInOut is quite dramatic and can feel excessive for business applications, + * productivity tools, and enterprise software where subtlety is preferred. The dual + * bouncing works best in contexts where playfulness is a design goal. + * + * @code + * // Playful modal transition + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::bounceInOut) + * .during(60U) + * .build(); + * @endcode + * + * @see bounceIn For bouncing only at the start + * @see bounceOut For bouncing only at the end (more commonly useful) + * @see elasticInOut For spring-like alternative + * @see Visualize at https://easings.net/#easeInOutBounce + */ + inline constexpr detail::bounceInOutEasing bounceInOut{}; + + /** + * @brief Circular easing based on quarter-circle arc for smooth acceleration. + * + * The `circularIn` easing function accelerates following the curve of a quarter circle, + * creating smooth, gradual acceleration. The motion follows the equation `1 - sqrt(1 - t²)`, + * which traces a circular arc. + * + * Characteristics: + * - Smooth, continuous acceleration + * - More gradual than quadratic, less than cubic + * - No sharp transitions in velocity + * - Mathematically elegant curve + * + * This easing is ideal for: + * - **Natural motion**: Movements that need organic feel + * - **Camera movements**: Smooth pans and zooms + * - **Scroll animations**: Gentle acceleration for reading comfort + * - **Subtle UI transitions**: Motion with moderate acceleration + * + * Circular easings strike a balance between the gentle quadratic and the more aggressive + * cubic curves, making them versatile for many contexts. + * + * @code + * // Smooth camera pan + * auto cameraX = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::circularIn) + * .during(90U) + * .build(); + * @endcode + * + * @see circularOut For circular deceleration + * @see circularInOut For circular acceleration and deceleration + * @see quadraticIn For gentler acceleration + * @see cubicIn For stronger acceleration + * @see Visualize at https://easings.net/#easeInCirc + */ + inline constexpr detail::circularInEasing circularIn{}; + + /** + * @brief Circular easing based on quarter-circle arc for smooth deceleration. + * + * The `circularOut` easing function decelerates following a circular curve, creating + * smooth, natural-looking motion that settles gently to rest. Popular for UI animations + * requiring moderate deceleration without dramatic emphasis. + * + * Characteristics: + * - Smooth, continuous deceleration + * - Natural feeling without being too soft or aggressive + * - Balanced between gentle and pronounced + * - Widely used in production interfaces + * + * This easing excels at: + * - **Modern UI animations**: Cards, panels, menus + * - **Material Design patterns**: Following Google's motion guidelines + * - **Content transitions**: Page changes, tab switches + * - **Professional applications**: Business and productivity software + * - **General-purpose motion**: Versatile for many animation types + * + * CircularOut works well in most contexts. It's less dramatic than cubic but with + * smoother deceleration than quadratic. + * + * @code + * // Menu panel sliding in + * auto x = tweeny::from(-300.0f) + * .to(0.0f) + * .via(easing::circularOut) + * .during(35U) + * .build(); + * @endcode + * + * @see circularIn For circular acceleration + * @see circularInOut For circular motion at both ends + * @see quadraticOut For gentler alternative + * @see cubicOut For more pronounced alternative + * @see Visualize at https://easings.net/#easeOutCirc + */ + inline constexpr detail::circularOutEasing circularOut{}; + + /** + * @brief Circular easing with smooth acceleration and deceleration. + * + * The `circularInOut` easing function uses circular curves for both acceleration and + * deceleration, creating balanced motion suitable for general UI work. This is a + * popular all-purpose easing. + * + * Motion profile: + * - First half: Circular acceleration + * - Midpoint: Maximum velocity + * - Second half: Circular deceleration + * - Creates smooth S-curve + * + * This easing is appropriate for: + * - **All-purpose UI animations**: Buttons, dialogs, drawers + * - **Material Design**: Recommended in Google's motion guidelines + * - **Business applications**: Productivity tools, enterprise apps + * - **Default animation choice**: Safe, versatile motion curve + * + * CircularInOut is often recommended as a starting point for animations because it + * provides polish without being too subtle or too dramatic. + * + * @code + * // Modal dialog appearance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::circularInOut) + * .during(25U) + * .build(); + * @endcode + * + * @see circularIn For only acceleration + * @see circularOut For only deceleration + * @see quadraticInOut For gentler motion + * @see cubicInOut For more dramatic motion + * @see Visualize at https://easings.net/#easeInOutCirc + */ + inline constexpr detail::circularInOutEasing circularInOut{}; + + /** + * @brief Cubic polynomial easing (t³) with moderate acceleration. + * + * The `cubicIn` easing function uses a cubic power curve for acceleration, providing + * smooth, noticeable easing that's more pronounced than quadratic but less extreme + * than quartic or exponential. + * + * Characteristics: + * - Polynomial acceleration with power of 3 + * - Balanced between gentle and dramatic + * - Widely used and well-understood + * - Good default for many animation types + * + * This easing is ideal for: + * - **Standard UI animations**: Menus, panels, overlays + * - **Easing beginners**: Easy to understand and predict + * - **General-purpose acceleration**: Versatile for many contexts + * - **Medium-length animations**: 200-500ms durations + * + * CubicIn is a popular choice because it provides clear easing without being subtle + * (like quadratic) or extreme (like quartic/exponential). + * + * Mathematically: `f(t) = t³` + * + * @code + * // Dropdown menu opening + * auto height = tweeny::from(0.0f) + * .to(200.0f) + * .via(easing::cubicIn) + * .during(30U) + * .build(); + * @endcode + * + * @see cubicOut For cubic deceleration + * @see cubicInOut For cubic motion at both ends + * @see quadraticIn For gentler acceleration + * @see quarticIn For stronger acceleration + * @see Visualize at https://easings.net/#easeInCubic + */ + inline constexpr detail::cubicInEasing cubicIn{}; + + /** + * @brief Cubic polynomial easing (t³) with moderate deceleration. + * + * The `cubicOut` easing function provides smooth, balanced deceleration that's popular + * across many design systems. It's pronounced enough to be noticeable but not dramatic. + * + * Characteristics: + * - Smooth, natural-feeling deceleration + * - More pronounced than quadratic, gentler than quartic + * - Industry-standard motion curve + * - Works well for most animation types + * + * This easing excels at: + * - **Web interfaces**: Following CSS animation best practices + * - **Mobile UI**: iOS and Android design patterns + * - **Content animations**: Cards, lists, grids + * - **Default easing choice**: Safe for most situations + * - **User-triggered actions**: Button presses, toggles, switches + * + * CubicOut is one of the most commonly used easings in production interfaces. It + * provides clear polish without drawing excessive attention to the motion itself. + * + * Mathematically: `f(t) = 1 - (1-t)³` + * + * @code + * // Button press feedback + * auto scale = tweeny::from(1.0f) + * .to(0.95f) + * .via(easing::cubicOut) + * .during(10U) + * .build(); + * + * // Card appearing + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::cubicOut) + * .during(25U) + * .build(); + * @endcode + * + * @see cubicIn For cubic acceleration + * @see cubicInOut For cubic motion at both ends + * @see quadraticOut For gentler deceleration + * @see quarticOut For stronger deceleration + * @see Visualize at https://easings.net/#easeOutCubic + */ + inline constexpr detail::cubicOutEasing cubicOut{}; + + /** + * @brief Cubic polynomial easing with balanced acceleration and deceleration. + * + * The `cubicInOut` easing function combines cubic acceleration and deceleration for + * smooth, professional motion. This is one of the most popular general-purpose easings. + * + * Motion profile: + * - First half: Cubic acceleration (t³) + * - Midpoint: Maximum velocity + * - Second half: Cubic deceleration + * - Creates smooth, balanced S-curve + * + * This easing is appropriate for: + * - **All-purpose animations**: When in doubt, use this + * - **Web standards**: CSS ease-in-out equivalent + * - **Design system defaults**: Common in component libraries + * - **Cross-platform consistency**: Works well everywhere + * - **Medium animations**: 200-500ms sweet spot + * + * CubicInOut is frequently the default easing in design systems and animation libraries + * because it provides clear, professional motion that works for most scenarios. + * + * Mathematically: Combines cubicIn for t < 0.5 and cubicOut for t >= 0.5 + * + * @code + * // Page transition + * auto x = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::cubicInOut) + * .during(40U) + * .build(); + * @endcode + * + * @see cubicIn For only acceleration + * @see cubicOut For only deceleration + * @see quadraticInOut For gentler motion + * @see quarticInOut For more dramatic motion + * @see Visualize at https://easings.net/#easeInOutCubic + */ + inline constexpr detail::cubicInOutEasing cubicInOut{}; + /** + * @brief Default easing function, alias for linear easing. + * + * The `def` easing is a convenience alias for `linear`, providing the same constant-velocity + * interpolation with no acceleration or deceleration. It exists as a semantic indicator that + * the default easing behavior is being explicitly chosen. + * + * Using `def` instead of `linear` can make code intent clearer in contexts where you want to + * explicitly state "use the default behavior" rather than specifically requesting linear motion. + * However, both are functionally identical. + * + * This is the easing applied when no via() call is made in the tween builder. + * + * @code + * // These three tweens are functionally identical: + * auto t1 = tweeny::from(0).to(100).during(60U).build(); // Implicit default + * auto t2 = tweeny::from(0).to(100).via(easing::def).during(60U).build(); + * auto t3 = tweeny::from(0).to(100).via(easing::linear).during(60U).build(); + * @endcode + * + * @see linear For the primary documentation of this easing behavior + */ + inline constexpr detail::defaultEasing def{}; + + /** + * @brief Elastic easing with oscillating spring-like motion at the start. + * + * The `elasticIn` easing function creates a spring or elastic band effect that oscillates + * with increasing amplitude before reaching the starting point of the animation. The motion + * resembles pulling back an elastic band that vibrates as tension builds. + * + * The oscillation characteristics: + * - Multiple back-and-forth swings before the main motion begins + * - Amplitude increases as the animation progresses + * - Creates a "winding up" or "charging" effect + * - More pronounced than backIn's single overshoot + * + * This easing is particularly effective for: + * - **Magical or fantasy effects**: Spell charging, energy gathering + * - **Exaggerated cartoon animations**: Extreme anticipation and wind-up + * - **Attention-grabbing entrances**: Elements that need dramatic introduction + * - **Game power-ups**: Visual feedback for charging actions + * - **Playful UI elements**: Whimsical, high-energy interactions + * + * The elastic effect is more dramatic than back easing, making it suitable for contexts + * where strong visual emphasis or entertainment value is desired. It's less appropriate + * for subtle or professional interfaces. + * + * Mathematically, this uses a decaying sine wave with exponential amplitude growth. + * + * @warning This easing creates significant overshoot in both directions. Values will + * oscillate well beyond the start value in both positive and negative directions. Ensure + * your rendering system can handle these extreme values gracefully. + * + * @code + * // Magical charging effect before an action + * auto glow = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::elasticIn) + * .during(45U) + * .build(); + * + * // The glow will oscillate negative before reaching 1.0f + * // producing a "charging" visual effect + * @endcode + * + * @see elasticOut For spring-like oscillation at the end + * @see elasticInOut For oscillation at both start and end + * @see backIn For a simpler single-overshoot alternative + * @see Visualize at https://easings.net/#easeInElastic + */ + inline constexpr detail::elasticInEasing elasticIn{}; + + /** + * @brief Elastic easing with spring-like oscillation at the end. + * + * The `elasticOut` easing function creates a natural spring or rubber band effect that + * overshoots and oscillates around the target value before settling. This is one of the + * most visually distinctive and playful easings, mimicking real-world elastic physics. + * + * The oscillation characteristics: + * - Overshoots the target value multiple times + * - Amplitude decreases with each oscillation (damped motion) + * - Settles naturally at the final value + * - Creates a bouncy, energetic feel without the hard impacts of bounce easing + * + * This easing excels at: + * - **Playful UI animations**: Buttons, toggles, modal appearances + * - **Game elements**: Power-up notifications, achievement popups, score displays + * - **Cartoon-style motion**: Exaggerated, entertaining character movements + * - **Attention direction**: Drawing eyes to important elements + * - **Spring simulation**: Rubber bands, diving boards, springy objects + * - **Joyful interactions**: Adding personality to standard UI patterns + * + * ElasticOut is widely used in modern mobile UI design to add energy and delight to + * interactions. It's more pronounced than backOut but smoother than bounceOut. + * + * The spring feel makes animations memorable and adds perceived responsiveness, making + * interfaces feel "alive" rather than mechanical. + * + * Mathematically, this uses a decaying sine wave with exponentially decreasing amplitude. + * + * @warning Values will oscillate beyond the target in both directions before settling. + * For example, animating from 0 to 100 might temporarily reach 110, then 95, then 102, + * before settling at 100. Ensure clipping or overflow handling is appropriate. + * + * @code + * // Springy button press feedback + * auto scale = tweeny::from(1.0f) + * .to(1.2f) + * .via(easing::elasticOut) + * .during(40U) + * .build(); + * + * // Scale will overshoot 1.2f (maybe 1.3f) then oscillate + * // down and up before settling at exactly 1.2f + * + * // Modal dialog with playful entrance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::elasticOut) + * .during(50U) + * .build(); + * @endcode + * + * @see elasticIn For spring anticipation at the start + * @see elasticInOut For oscillation at both ends + * @see backOut For a subtler single-overshoot alternative + * @see bounceOut For a similar but impact-based bouncing effect + * @see Visualize at https://easings.net/#easeOutElastic + */ + inline constexpr detail::elasticOutEasing elasticOut{}; + + /** + * @brief Elastic easing combining spring oscillation at both start and end. + * + * The `elasticInOut` easing function creates dramatic elastic motion with oscillation + * during both the initial acceleration and final deceleration phases. The animation + * winds up with spring-like vibration, accelerates smoothly through the middle, then + * oscillates around the target before settling. + * + * The motion profile: + * - First half: Oscillates with increasing amplitude (elasticIn behavior) + * - Middle: Smooth transition through the midpoint + * - Second half: Oscillates with decreasing amplitude (elasticOut behavior) + * - Creates the most dramatic elastic effect available + * + * This easing is appropriate for: + * - **Hero animations**: Focal transitions that demand attention + * - **Game events**: Critical moments, boss appearances, dramatic reveals + * - **Cartoon physics**: Exaggerated, entertaining motion for stylized visuals + * - **Transitions with flair**: Scene changes that need maximum personality + * - **Experimental UI**: Interfaces prioritizing delight over convention + * + * ElasticInOut is the most expressive elastic variant but also the most extreme. It works + * best for animations that are: + * - Intentionally playful or whimsical + * - The primary focus of user attention + * - Part of a stylized, high-energy aesthetic + * - Not repeated frequently (can become tiresome) + * + * Use sparingly; the dual oscillation can feel excessive for everyday interactions. + * Consider elasticOut or backInOut for a more balanced alternative. + * + * @warning This easing produces extreme value overshoots in both directions throughout + * the animation. During the first half, values oscillate around the start; during the + * second half, around the target. Plan for values far outside the expected range. + * + * @code + * // Dramatic screen transition + * auto position = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::elasticInOut) + * .during(90U) + * .build(); + * + * // Position will oscillate around 0.0f at start and 1920.0f at end + * + * // Game title appearing with maximum impact + * auto scale = tweeny::from(0.0f) + * .to(2.0f) + * .via(easing::elasticInOut) + * .during(75U) + * .build(); + * @endcode + * + * @see elasticIn For oscillation only at the start + * @see elasticOut For oscillation only at the end (more commonly useful) + * @see backInOut For a less extreme but still expressive alternative + * @see Visualize at https://easings.net/#easeInOutElastic + */ + inline constexpr detail::elasticInOutEasing elasticInOut{}; + + /** + * @brief Exponential easing with very slow start and explosive acceleration. + * + * The `exponentialIn` easing function starts extremely slowly and builds to a very rapid + * acceleration toward the end. Based on exponential growth (2^x), this creates one of the + * most dramatic acceleration curves available, with velocity increasing exponentially over time. + * + * Characteristics: + * - Almost no visible motion for the first portion of the animation + * - Sudden, explosive acceleration in the final phase + * - Change rate doubles repeatedly as time progresses + * - Creates extreme contrast between start and end velocity + * + * This easing is ideal for: + * - **Dramatic reveals**: Elements that burst into view + * - **Explosive effects**: Particle systems, energy blasts, explosions + * - **Fade-ins with impact**: Starting invisible and suddenly appearing + * - **Speed-up effects**: Rockets launching, vehicles accelerating + * - **Tension building**: Slow build-up to sudden release + * + * The extreme acceleration curve makes this feel more dramatic than polynomial easings + * (quadratic, cubic, etc.). The motion appears to "explode" into existence rather than + * gradually accelerate. + * + * Use exponentialIn when you want maximum contrast between the patient start and the + * explosive finish. For most UI work, cubic or quartic easings provide sufficient + * acceleration with less extreme behavior. + * + * Mathematically: `f(t) = 2^(10 * (t - 1))` + * + * @code + * // Fade in that suddenly snaps to full visibility + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::exponentialIn) + * .during(45U) + * .build(); + * + * // Rocket launch with explosive acceleration + * auto velocity = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::exponentialIn) + * .during(120U) + * .build(); + * @endcode + * + * @see exponentialOut For explosive deceleration at the end + * @see exponentialInOut For dramatic acceleration and deceleration + * @see quarticIn For strong but less extreme acceleration + * @see Visualize at https://easings.net/#easeInExpo + */ + inline constexpr detail::exponentialInEasing exponentialIn{}; + + /** + * @brief Exponential easing with explosive start and gradual slow-down. + * + * The `exponentialOut` easing function starts with maximum velocity and decelerates + * exponentially, creating a smooth glide to a stop. The initial burst of speed followed + * by gradual settling creates a powerful, energetic feel. + * + * Characteristics: + * - Immediate, explosive motion at the start + * - Exponentially decreasing velocity + * - Long, smooth deceleration phase + * - Settles gently to final value + * + * This easing excels at: + * - **Quick UI responses**: Instant feedback that settles smoothly + * - **Impact effects**: Objects hitting and settling into place + * - **Momentum-based motion**: Thrown objects, swipe gestures + * - **Energetic entrances**: Elements that burst in with energy + * - **Modern UI patterns**: iOS-style animations with quick start + * + * ExponentialOut is popular in modern mobile design because it provides immediate + * visual feedback (the fast start) while ending smoothly. Users perceive the interface + * as highly responsive due to the instant motion. + * + * The long tail of deceleration gives animations a "quality feel" - nothing stops + * abruptly. This is gentler on the eyes than linear or even cubic deceleration. + * + * Mathematically: `f(t) = 1 - 2^(-10 * t)` + * + * @code + * // Responsive drawer that slides out quickly then settles + * auto position = tweeny::from(-300.0f) + * .to(0.0f) + * .via(easing::exponentialOut) + * .during(35U) + * .build(); + * + * // Notification that pops in with energy + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::exponentialOut) + * .during(25U) + * .build(); + * @endcode + * + * @see exponentialIn For explosive acceleration at the start + * @see exponentialInOut For explosive motion at both ends + * @see quarticOut For strong but less extreme deceleration + * @see Visualize at https://easings.net/#easeOutExpo + */ + inline constexpr detail::exponentialOutEasing exponentialOut{}; + + /** + * @brief Exponential easing with extreme acceleration and deceleration. + * + * The `exponentialInOut` easing function combines explosive acceleration in the first + * half with explosive deceleration in the second half. This creates one of the most + * dramatic, high-energy motion curves available, with rapid changes at both ends and + * smooth motion through the middle. + * + * Motion profile: + * - First half: Exponential acceleration from near-zero velocity + * - Midpoint: Maximum velocity + * - Second half: Exponential deceleration to rest + * - Creates extreme S-curve shape + * + * This easing is appropriate for: + * - **High-impact transitions**: Scene changes, screen swipes + * - **Dramatic animations**: Hero elements, focal content + * - **Fast-paced interfaces**: Games, action-oriented apps + * - **Attention-grabbing motion**: Elements that need maximum visibility + * - **Strong transitions**: For elements moving significant distances or longer durations (500ms+) + * + * ExponentialInOut creates a powerful sense of momentum and energy. The motion feels + * purposeful and confident. However, the extreme acceleration can be disorienting if + * overused or applied to large elements. + * + * Best practices: + * - Use for animations under 1 second duration + * - Apply to elements that move short to medium distances + * - Reserve for important, infrequent transitions + * - Consider quarticInOut or quinticInOut as less extreme alternatives + * + * Mathematically: Combines exponentialIn for t < 0.5 and exponentialOut for t >= 0.5 + * + * @code + * // Page transition with explosive motion + * auto position = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::exponentialInOut) + * .during(40U) + * .build(); + * + * // Modal dialog with dramatic appearance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::exponentialInOut) + * .during(30U) + * .build(); + * @endcode + * + * @see exponentialIn For only explosive acceleration + * @see exponentialOut For only explosive deceleration + * @see quinticInOut For slightly less extreme alternative + * @see Visualize at https://easings.net/#easeInOutExpo + */ + inline constexpr detail::exponentialInOutEasing exponentialInOut{}; + /** + * @brief Linear easing function with constant velocity throughout the animation. + * + * The `linear` easing function produces uniform motion with no acceleration or deceleration. + * The interpolation progresses at a constant rate from start to finish, creating mechanical, + * predictable movement. + * + * Linear easing is characterized by: + * - Constant velocity: the rate of change never varies + * - No ease-in or ease-out: motion starts and stops abruptly + * - Simple mathematical relationship: output = start + (end - start) * progress + * - Predictable timing: halfway through time means halfway through distance + * + * This easing is appropriate for: + * - **Mechanical objects**: Conveyor belts, pistons, automated systems + * - **Progress indicators**: Loading bars, timers, countdowns + * - **Continuous loops**: Rotating objects, scrolling backgrounds + * - **Data visualization**: Graph animations where consistency is important + * - **Debug and testing**: Predictable behavior for verification + * + * Linear easing is generally **not recommended** for most UI animations because: + * - Lacks the natural feel of acceleration/deceleration + * - Abrupt starts and stops can feel jarring + * - Missing visual polish that easing provides + * - Human perception expects objects to ease into and out of motion + * + * However, it serves as the foundation for all other easing functions and is essential + * when mechanical precision is more important than natural motion feel. + * + * Mathematically, this implements: `f(t) = t` where t is normalized progress [0, 1]. + * + * @code + * // Constant velocity scrolling background + * auto scroll = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::linear) + * .during(600U) + * .build(); + * + * // Progress indicator that matches time exactly + * auto progress = tweeny::from(0) + * .to(100) + * .via(easing::linear) + * .during(100U) + * .build(); + * + * // At frame 50, progress will be exactly 50 + * @endcode + * + * @note Linear is the default easing when no via() is specified, though using def is + * more explicit in code. + * + * @see def Alias for linear easing + * @see quadraticInOut For a gentle alternative with ease-in and ease-out + * @see sinusoidalInOut For smooth, natural-feeling motion + */ + inline constexpr detail::linearEasing linear{}; + + /** + * @brief Quadratic polynomial easing (t²) with gentle acceleration. + * + * The `quadraticIn` easing function uses a squared power curve for acceleration, + * providing the gentlest polynomial easing. It's more subtle than cubic but still + * provides noticeable easing. + * + * Characteristics: + * - Gentle, smooth acceleration + * - Polynomial with power of 2 + * - Subtle but perceptible easing + * - Good for beginners and subtle animations + * + * This easing is ideal for: + * - **Subtle UI motion**: When easing should be felt but not seen + * - **Quick animations**: Short durations where gentle curves work best + * - **Minimal designs**: Interfaces prioritizing restraint + * - **Learning easings**: Easy to understand and predict + * + * Mathematically: `f(t) = t²` + * + * @code + * // Gentle fade in + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quadraticIn) + * .during(20U) + * .build(); + * @endcode + * + * @see quadraticOut For gentle deceleration + * @see quadraticInOut For gentle motion at both ends + * @see cubicIn For more pronounced acceleration + * @see Visualize at https://easings.net/#easeInQuad + */ + inline constexpr detail::quadraticInEasing quadraticIn{}; + + /** + * @brief Quadratic polynomial easing (t²) with gentle deceleration. + * + * The `quadraticOut` easing function provides subtle, smooth deceleration. It's the + * gentlest polynomial easing, perfect when you want polish without drama. + * + * Characteristics: + * - Gentle, smooth deceleration + * - Subtle but professional feel + * - Never feels too slow or too fast + * - Safe choice for any context + * + * This easing excels at: + * - **Subtle UI polish**: Adding refinement without drawing attention + * - **Fast animations**: 100-200ms durations + * - **Minimal interfaces**: Clean, understated design systems + * - **Accessibility-friendly**: Gentle motion reduces disorientation + * - **Background animations**: Motion that shouldn't distract + * + * QuadraticOut is great when you want animations to stay out of the way. It's less + * dramatic than cubic but smoother than linear due to gradual deceleration. + * + * Mathematically: `f(t) = 1 - (1-t)²` + * + * @code + * // Subtle tooltip appearance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quadraticOut) + * .during(15U) + * .build(); + * @endcode + * + * @see quadraticIn For gentle acceleration + * @see quadraticInOut For gentle motion at both ends + * @see cubicOut For more pronounced deceleration + * @see Visualize at https://easings.net/#easeOutQuad + */ + inline constexpr detail::quadraticOutEasing quadraticOut{}; + + /** + * @brief Quadratic polynomial easing with gentle acceleration and deceleration. + * + * The `quadraticInOut` easing function provides the most subtle polynomial S-curve. + * Perfect for animations that need easing but should remain understated. + * + * Motion profile: + * - First half: Gentle acceleration (t²) + * - Midpoint: Maximum velocity + * - Second half: Gentle deceleration + * - Creates subtle S-curve + * + * This easing is appropriate for: + * - **Minimal design systems**: Understated, refined motion + * - **Fast animations**: Under 200ms where gentle curves shine + * - **Accessibility**: Motion-sensitive users + * - **Background motion**: Animations that shouldn't dominate + * + * Mathematically: Combines quadraticIn for t < 0.5 and quadraticOut for t >= 0.5 + * + * @code + * // Subtle menu transition + * auto x = tweeny::from(0.0f) + * .to(100.0f) + * .via(easing::quadraticInOut) + * .during(20U) + * .build(); + * @endcode + * + * @see quadraticIn For only gentle acceleration + * @see quadraticOut For only gentle deceleration + * @see cubicInOut For more pronounced motion + * @see Visualize at https://easings.net/#easeInOutQuad + */ + inline constexpr detail::quadraticInOutEasing quadraticInOut{}; + + /** + * @brief Quartic polynomial easing (t⁴) with strong acceleration. + * + * The `quarticIn` easing function uses a power-4 curve for acceleration, creating + * pronounced easing that's stronger than cubic but less extreme than exponential. + * + * Characteristics: + * - Strong acceleration curve + * - Polynomial with power of 4 + * - Very slow start, rapid finish + * - More dramatic than cubic + * + * This easing is ideal for: + * - **Dramatic entrances**: Elements that burst into view + * - **Emphasis**: Drawing attention to motion start + * - **Game animations**: Action-oriented interfaces + * - **Long animations**: Over 500ms where strong curves work + * + * Mathematically: `f(t) = t⁴` + * + * @code + * // Panel sliding in with emphasis + * auto x = tweeny::from(-400.0f) + * .to(0.0f) + * .via(easing::quarticIn) + * .during(50U) + * .build(); + * @endcode + * + * @see quarticOut For strong deceleration + * @see quarticInOut For strong motion at both ends + * @see cubicIn For gentler acceleration + * @see quinticIn For even stronger acceleration + * @see Visualize at https://easings.net/#easeInQuart + */ + inline constexpr detail::quarticInEasing quarticIn{}; + + /** + * @brief Quartic polynomial easing (t⁴) with strong deceleration. + * + * The `quarticOut` easing function provides pronounced deceleration that's popular + * for animations needing clear emphasis without being as extreme as exponential. + * + * Characteristics: + * - Strong deceleration curve + * - Smooth, extended slowdown + * - Emphasizes final position + * - Balanced between cubic and exponential + * + * This easing excels at: + * - **Emphasized arrivals**: Elements settling with impact + * - **Current design patterns**: Strong deceleration suitable for emphasized arrivals + * - **Medium-length animations**: 300-600ms sweet spot + * - **Purposeful motion**: Clear acceleration and deceleration + * + * QuarticOut provides more "oomph" than cubic while remaining smooth and + * professional. It's a good choice when cubic feels too subtle. + * + * Mathematically: `f(t) = 1 - (1-t)⁴` + * + * @code + * // Card sliding into place with emphasis + * auto y = tweeny::from(200.0f) + * .to(0.0f) + * .via(easing::quarticOut) + * .during(40U) + * .build(); + * @endcode + * + * @see quarticIn For strong acceleration + * @see quarticInOut For strong motion at both ends + * @see cubicOut For gentler deceleration + * @see quinticOut For even stronger deceleration + * @see Visualize at https://easings.net/#easeOutQuart + */ + inline constexpr detail::quarticOutEasing quarticOut{}; + + /** + * @brief Quartic polynomial easing with strong acceleration and deceleration. + * + * The `quarticInOut` easing function combines strong acceleration and deceleration + * for confident, purposeful motion. More dramatic than cubic, less extreme than quintic. + * + * Motion profile: + * - First half: Strong acceleration (t⁴) + * - Midpoint: Maximum velocity + * - Second half: Strong deceleration + * - Creates pronounced S-curve + * + * This easing is appropriate for: + * - **Emphasized transitions**: Strong acceleration and deceleration + * - **Current design patterns**: Strong deceleration curves + * - **Important animations**: Focal transitions + * - **Medium durations**: 300-600ms range + * + * Mathematically: Combines quarticIn for t < 0.5 and quarticOut for t >= 0.5 + * + * @code + * // Page transition with impact + * auto x = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::quarticInOut) + * .during(45U) + * .build(); + * @endcode + * + * @see quarticIn For only strong acceleration + * @see quarticOut For only strong deceleration + * @see cubicInOut For gentler motion + * @see quinticInOut For more dramatic motion + * @see Visualize at https://easings.net/#easeInOutQuart + */ + inline constexpr detail::quarticInOutEasing quarticInOut{}; + + /** + * @brief Quintic polynomial easing (t⁵) with very strong acceleration. + * + * The `quinticIn` easing function uses a power-5 curve, creating the strongest + * polynomial acceleration. It's more extreme than quartic but less than exponential. + * + * Characteristics: + * - Very strong acceleration + * - Polynomial with power of 5 + * - Extremely slow start + * - Dramatic finish + * + * This easing is ideal for: + * - **Maximum polynomial emphasis**: Strongest polynomial option + * - **Dramatic effects**: Hero animations, focal transitions + * - **Long animations**: Over 600ms durations + * - **Alternative to exponential**: Slightly less extreme + * + * Mathematically: `f(t) = t⁵` + * + * @code + * // Dramatic hero section reveal + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quinticIn) + * .during(60U) + * .build(); + * @endcode + * + * @see quinticOut For very strong deceleration + * @see quinticInOut For very strong motion at both ends + * @see quarticIn For slightly gentler acceleration + * @see exponentialIn For even more extreme acceleration + * @see Visualize at https://easings.net/#easeInQuint + */ + inline constexpr detail::quinticInEasing quinticIn{}; + + /** + * @brief Quintic polynomial easing (t⁵) with very strong deceleration. + * + * The `quinticOut` easing function provides the strongest polynomial deceleration, + * creating smooth but dramatic slowdown. Popular for animations with long durations + * (500ms+) requiring strong deceleration. + * + * Characteristics: + * - Very strong deceleration + * - Long, smooth tail + * - Very strong deceleration with extended slowdown phase + * - Strongest polynomial option + * + * This easing excels at: + * - **Applications prioritizing strong visual emphasis**: Galleries, portfolios, marketing sites + * - **Animations with extended durations**: Requiring maximum polynomial deceleration + * - **Longer animations**: 500ms+ durations + * - **Emphasized arrivals**: Strong focus on end state + * + * QuinticOut provides the strongest polynomial deceleration while maintaining a + * smooth, continuous curve. + * + * Mathematically: `f(t) = 1 - (1-t)⁵` + * + * @code + * // Premium modal entrance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quinticOut) + * .during(45U) + * .build(); + * @endcode + * + * @see quinticIn For very strong acceleration + * @see quinticInOut For very strong motion at both ends + * @see quarticOut For slightly gentler deceleration + * @see exponentialOut For even more extreme deceleration + * @see Visualize at https://easings.net/#easeOutQuint + */ + inline constexpr detail::quinticOutEasing quinticOut{}; + + /** + * @brief Quintic polynomial easing with very strong acceleration and deceleration. + * + * The `quinticInOut` easing function provides the strongest polynomial S-curve, + * creating powerful motion with maximum polynomial emphasis. + * + * Motion profile: + * - First half: Very strong acceleration (t⁵) + * - Midpoint: Maximum velocity + * - Second half: Very strong deceleration + * - Creates dramatic S-curve + * + * This easing is appropriate for: + * - **Applications prioritizing strong visual emphasis**: Galleries, portfolios, marketing sites + * - **Animations requiring maximum polynomial emphasis**: At both acceleration and deceleration phases + * - **Important transitions**: Focal, memorable moments + * - **Longer animations**: 500ms+ durations + * + * Mathematically: Combines quinticIn for t < 0.5 and quinticOut for t >= 0.5 + * + * @code + * // Premium screen transition + * auto x = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::quinticInOut) + * .during(50U) + * .build(); + * @endcode + * + * @see quinticIn For only very strong acceleration + * @see quinticOut For only very strong deceleration + * @see quarticInOut For gentler motion + * @see exponentialInOut For more extreme motion + * @see Visualize at https://easings.net/#easeInOutQuint + */ + inline constexpr detail::quinticInOutEasing quinticInOut{}; + + /** + * @brief Sinusoidal easing based on sine wave for smooth acceleration. + * + * The `sinusoidalIn` easing function uses a sine curve for acceleration, creating + * extremely smooth, natural motion. Based on trigonometric functions rather than + * polynomials. + * + * Characteristics: + * - Very smooth acceleration + * - Natural, organic feel + * - No abrupt velocity changes + * - Gentle but noticeable + * + * This easing is ideal for: + * - **Natural motion**: Organic, flowing animations + * - **Smooth camera moves**: Pans, zooms, orbits + * - **Elegant transitions**: Refined, sophisticated feel + * - **Accessible animations**: Gentle on motion sensitivity + * + * Mathematically: `f(t) = 1 - cos(t * π/2)` + * + * @code + * // Smooth camera pan + * auto x = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::sinusoidalIn) + * .during(60U) + * .build(); + * @endcode + * + * @see sinusoidalOut For smooth deceleration + * @see sinusoidalInOut For smooth motion at both ends + * @see quadraticIn For similar gentleness + * @see Visualize at https://easings.net/#easeInSine + */ + inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; + + /** + * @brief Sinusoidal easing based on sine wave for smooth deceleration. + * + * The `sinusoidalOut` easing function uses a sine curve for deceleration, creating + * extremely smooth, natural motion that settles gently. + * + * Characteristics: + * - Very smooth deceleration + * - Natural, flowing motion + * - Gentle slowdown + * - Mathematically elegant + * + * This easing excels at: + * - **Natural UI motion**: Smooth, organic feel + * - **Continuous animations**: Loops, cycles, repeated motion + * - **Gentle transitions**: Calm, relaxed interfaces + * - **Accessible design**: Motion-sensitivity friendly + * + * SinusoidalOut is excellent when you want smoothness above all else. It's + * gentler than quadratic while still providing clear easing. + * + * Mathematically: `f(t) = sin(t * π/2)` + * + * @code + * // Smooth fade in + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::sinusoidalOut) + * .during(30U) + * .build(); + * @endcode + * + * @see sinusoidalIn For smooth acceleration + * @see sinusoidalInOut For smooth motion at both ends + * @see quadraticOut For similar gentleness + * @see Visualize at https://easings.net/#easeOutSine + */ + inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; + + /** + * @brief Sinusoidal easing with smooth acceleration and deceleration. + * + * The `sinusoidalInOut` easing function uses sine curves for both acceleration + * and deceleration, creating the smoothest possible S-curve motion. + * + * Motion profile: + * - First half: Smooth sine-based acceleration + * - Midpoint: Maximum velocity + * - Second half: Smooth sine-based deceleration + * - Creates extremely smooth S-curve + * + * This easing is appropriate for: + * - **Natural, organic motion**: Smoothest easing available + * - **Continuous loops**: Seamless repeated animations + * - **Calm interfaces**: Relaxed, gentle design systems + * - **Accessible animations**: Minimal motion stress + * + * SinusoidalInOut is the smoothest InOut easing, making it perfect when + * fluid, natural motion is the priority. + * + * Mathematically: `f(t) = (1 - cos(t * π)) / 2` + * + * @code + * // Ultra-smooth transition + * auto x = tweeny::from(0.0f) + * .to(100.0f) + * .via(easing::sinusoidalInOut) + * .during(40U) + * .build(); + * @endcode + * + * @see sinusoidalIn For only smooth acceleration + * @see sinusoidalOut For only smooth deceleration + * @see quadraticInOut For similar gentleness + * @see Visualize at https://easings.net/#easeInOutSine + */ + inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; + + /** + * @brief Stepped easing that holds the start value until the keyframe completes. + * + * The `stepped` easing function returns the starting value throughout the entire + * duration of the keyframe segment, only jumping to the target value when moving + * to the next keyframe. This creates instant transitions between keyframes without + * any interpolation within each segment. + * + * Characteristics: + * - No interpolation within keyframe segments + * - Holds start value until keyframe ends + * - Instant jumps between keyframes + * - Creates discrete, stair-step motion + * + * This easing is ideal for: + * - **Discrete state transitions**: Values that shouldn't interpolate smoothly + * - **Keyframe-based animations**: Step through distinct poses or states + * - **Frame-by-frame effects**: Hold each frame without blending + * - **Boolean-like values**: Properties that need instant changes + * - **Sprite switching**: Change sprites at keyframe boundaries + * - **Cut transitions**: Instant changes without fading + * + * Stepped is fundamentally different from other easings because it eliminates + * interpolation entirely within each keyframe segment, creating hard cuts between + * animation states. + * + * @code + * // Hold value at each keyframe, jump instantly between them + * auto value = tweeny::from(0) + * .to(10).via(easing::stepped).during(100U) + * .to(20).via(easing::stepped).during(100U) + * .to(30).via(easing::stepped).during(100U) + * .build(); + * + * // During frames 0-99: returns 0 + * // During frames 100-199: returns 10 + * // During frames 200-299: returns 20 + * // At frame 300: returns 30 + * + * // Useful for sprite animation indices + * auto spriteIndex = tweeny::from(0) + * .to(1).via(easing::stepped).during(10U) + * .to(2).via(easing::stepped).during(10U) + * .to(3).via(easing::stepped).during(10U) + * .build(); + * // Holds each sprite index for 10 frames, then instantly switches + * @endcode + * + * @see linear For smooth constant-velocity interpolation + */ + inline constexpr detail::steppedEasing stepped{}; +} + +#endif //TWEENY_EASING_H diff --git a/include/tweeny/event.h b/include/tweeny/event.h new file mode 100644 index 0000000..89f8264 --- /dev/null +++ b/include/tweeny/event.h @@ -0,0 +1,283 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @file event.h + * @brief Event system for tween animation callbacks. + * + * This file defines event types and response codes for the tween event system. + * Events are triggered during tween operations (step, seek, jump, complete) and + * allow callbacks to react to animation state changes. Tag types are defined in + * detail/event.h and instantiated here with documentation. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(tweeny::event::step, [](auto& tween) { + * printf("Value: %d\n", tween.peek()); + * return tweeny::event::response::ok; + * }); + * @endcode + */ + +#ifndef TWEENY_EVENT_H +#define TWEENY_EVENT_H + +#include "detail/event.h" + +/** + * @namespace tweeny::event + * @brief Event types and response codes for tween callbacks. + * + * This namespace contains tag types for registering event listeners and + * response codes for controlling callback behavior. Use these tags with + * tween::on() to register callbacks for animation lifecycle events. + */ +namespace tweeny::event { + /** + * @brief Event triggered after each step() call. + * + * Use this event to react to frame-by-frame progression of an animation. + * The callback receives a reference to the tween and can query its current + * state using peek() or progress(). + * + * Common use cases: + * - Updating visual properties every frame + * - Logging animation progress + * - Synchronizing with other animations + * - Implementing custom timing logic + * + * @code + * auto tween = tweeny::from(0).to(100).during(60U).build(); + * tween.on(tweeny::event::step, [](auto& t) { + * printf("Current value: %d\n", t.peek()); + * return tweeny::event::response::ok; + * }); + * + * tween.step(1); // Triggers the callback + * @endcode + * + * @see seek For events when jumping to specific frames + * @see complete For events when animation finishes + */ + inline constexpr detail::event::step_t step{}; + + /** + * @brief Event triggered after each seek() call. + * + * Use this event to react when the tween jumps to a specific frame position. + * Unlike step events, seek events fire regardless of the direction or distance + * of the movement. + * + * Common use cases: + * - Scrubbing through animations + * - Reacting to timeline jumps + * - Updating state after non-linear navigation + * - Synchronizing with external timeline controls + * + * @code + * auto tween = tweeny::from(0).to(100).during(100U).build(); + * tween.on(tweeny::event::seek, [](auto& t) { + * printf("Seeked to frame with value: %d\n", t.peek()); + * return tweeny::event::response::ok; + * }); + * + * tween.seek(50U); // Triggers the callback + * @endcode + * + * @see step For frame-by-frame progression events + * @see jump For keyframe-specific navigation + */ + inline constexpr detail::event::seek_t seek{}; + + /** + * @brief Event triggered after each jump() call. + * + * Use this event to react when the tween jumps to a specific keyframe by index. + * This is particularly useful for multi-point animations where keyframes represent + * distinct animation phases. + * + * Common use cases: + * - Transitioning between animation states + * - Triggering phase-specific logic + * - Resetting to specific animation checkpoints + * - Implementing state machines + * + * @code + * auto tween = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * tween.on(tweeny::event::jump, [](auto& t) { + * printf("Jumped to keyframe\n"); + * return tweeny::event::response::ok; + * }); + * + * tween.jump(1); // Jump to keyframe 1, triggers callback + * @endcode + * + * @see keyframeEnter For detecting keyframe transitions during playback + * @see seek For arbitrary frame jumps + */ + inline constexpr detail::event::jump_t jump{}; + + /** + * @brief Event triggered when the animation is at completion. + * + * Fires whenever progress() is >= 1.0 (100%) after a step(), seek(), or jump() call. + * This means the callback will fire **every time** the tween is navigated to a completed + * state, not just the first time. + * + * Common use cases: + * - Cleaning up resources after animation + * - Chaining animations sequentially + * - Triggering completion callbacks + * - Transitioning to next state + * - Playing sound effects or particle effects at end + * + * @note The callback fires every time the tween is at completion (progress >= 1.0). + * If you step to completion, then back, then forward to completion again, it will + * fire both times. Use response::unsubscribe if you want one-shot behavior. + * + * @code + * auto tween = tweeny::from(0).to(100).during(60U).build(); + * tween.on(tweeny::event::complete, [](auto& t) { + * printf("Animation at completion: %d\n", t.peek()); + * return tweeny::event::response::unsubscribe; // One-shot callback + * }); + * + * tween.step(60); // Fires complete callback + * tween.step(-10); // Now progress < 1.0 + * tween.step(10); // Would fire again, but we unsubscribed + * @endcode + * + * @see step For frame-by-frame events during animation + * @see response::unsubscribe For one-shot completion handlers + */ + inline constexpr detail::event::complete_t complete{}; + + /** + * @brief Event triggered when entering a new keyframe segment. + * + * Fires when the tween transitions into a new keyframe section during playback. + * The callback receives both the tween reference and a keyframeEnter struct + * containing the keyframe index. + * + * Common use cases: + * - Triggering phase-specific animations + * - Playing transition sounds + * - Updating UI to reflect animation phase + * - Synchronizing multi-part animations + * - Implementing animation state machines + * + * @note Keyframe indices are 0-based. The first keyframe is index 0, second is 1, etc. + * + * @code + * auto tween = tweeny::from(0) + * .to(50).during(30U) + * .to(100).during(30U) + * .build(); + * + * tween.on(tweeny::event::keyframeEnter, [](auto& t, tweeny::event::keyframeEnter evt) { + * printf("Entering keyframe %zu\n", evt.key_frame); + * return tweeny::event::response::ok; + * }); + * + * tween.step(31); // Triggers: "Entering keyframe 1" + * @endcode + * + * @see keyframeLeave For detecting when leaving a keyframe + * @see keyframeEnter For the event data struct + * @see jump For manually jumping to keyframes + */ + inline constexpr detail::event::keyframeEnter_t keyframeEnter{}; + + /** + * @brief Event triggered when leaving a keyframe segment. + * + * Fires when the tween transitions out of a keyframe section during playback. + * The callback receives both the tween reference and a keyframeLeave struct + * containing the keyframe index being exited. + * + * Common use cases: + * - Cleaning up phase-specific resources + * - Stopping phase-specific effects + * - Logging animation progression + * - Implementing phase exit handlers + * - Coordinating complex multi-segment animations + * + * @note Keyframe indices are 0-based. Leaving keyframe 0 means transitioning + * from the first to the second segment. + * + * @code + * auto tween = tweeny::from(0) + * .to(50).during(30U) + * .to(100).during(30U) + * .build(); + * + * tween.on(tweeny::event::keyframeLeave, [](auto& t, tweeny::event::keyframeLeave evt) { + * printf("Leaving keyframe %zu\n", evt.key_frame); + * return tweeny::event::response::ok; + * }); + * + * tween.step(31); // Triggers: "Leaving keyframe 0" + * @endcode + * + * @see keyframeEnter For detecting when entering a keyframe + * @see keyframeLeave For the event data struct + */ + inline constexpr detail::event::keyframeLeave_t keyframeLeave{}; + + /** + * @brief Event triggered whenever the tween position changes. + * + * Fires after any step(), seek(), or jump() call. This is a convenience event + * that consolidates all position update events into a single callback point. + * + * Common use cases: + * - Updating visuals regardless of how the tween changed + * - Monitoring all tween changes from one place + * - Logging every position update + * - Synchronizing with external systems + * - Implementing universal change handlers + * + * @note This event fires **after** the specific event (step, seek, or jump) and + * **before** the complete event if applicable. + * + * @code + * auto tween = tweeny::from(0).to(100).during(60U).build(); + * tween.on(tweeny::event::update, [](auto& t) { + * printf("Tween updated to: %d\n", t.peek()); + * return tweeny::event::response::ok; + * }); + * + * tween.step(10); // Triggers update + * tween.seek(50U); // Triggers update + * tween.jump(1); // Triggers update + * @endcode + * + * @see step For frame-by-frame updates + * @see seek For arbitrary frame jumps + * @see jump For keyframe jumps + */ + inline constexpr detail::event::update_t update{}; +} + +#endif //TWEENY_EVENT_H diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h new file mode 100644 index 0000000..1603e44 --- /dev/null +++ b/include/tweeny/tween.h @@ -0,0 +1,354 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @file tween.h + * @brief Core tween class for frame-based animation. + * + * This file defines the tween class, which represents an animation between keyframes. + * Tweens interpolate values over time using configurable easing functions and support + * event listeners for animation lifecycle events. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.step(1); // Advance one frame + * auto val = t.peek(); // Get current value + * float p = t.progress(); // Get completion (0.0-1.0) + * @endcode + */ + +#ifndef TWEENY_TWEEN_H +#define TWEENY_TWEEN_H + +#include +#include +#include +#include + +#include "detail/key-frame.h" +#include "detail/tween-value.h" +#include "detail/event.h" + +namespace tweeny { + /** + * @brief A tween represents an animation between keyframes. + * + * Tweens interpolate values over discrete frames using configurable easing functions. + * They maintain internal state (current frame and value) and support event listeners + * for animation events. + * + * @tparam FirstValueType Type of the first animated value + * @tparam RemainingValueTypes Types of additional animated values (for multi-value tweens) + * + * @note For single values, tween_value_t is the value type directly. + * For multiple values, tween_value_t is std::tuple + */ + template + class tween { + typedef detail::key_frame key_frame_t; + typedef std::vector key_frames_t; + + public: + /** + * @brief The type returned by navigation and query methods. + * + * For single-value tweens: the value type itself. + * For multi-value tweens: std::tuple of all value types. + */ + using tween_value_t = detail::tween_value_t; + + /** + * @brief Constructs a tween from keyframes (use builder API instead). + * @internal Users should use tweeny::from() to create tweens. + */ + explicit tween(const key_frames_t & key_frames_input); + + /// @overload + explicit tween(key_frames_t && key_frames_input); + + /** + * @brief Seeks to a specific frame in the animation. + * + * Jumps directly to the target frame, updating the tween's current position and value. + * Clamped to [first_frame, last_frame]. Triggers event::seek listeners. + * + * @param target_frame Absolute frame number to seek to + * @return Interpolated value at the target frame + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.seek(50U); // Jump to frame 50 (value: 50) + * t.seek(0U); // Jump back to start + * @endcode + */ + auto seek(uint32_t target_frame) -> tween_value_t; + + /** + * @brief Jumps to a specific keyframe index. + * + * Moves the tween to the exact position of a keyframe. Useful for resetting to + * known points or implementing discrete state animations. Triggers event::jump listeners. + * + * @param target_key_frame Zero-based keyframe index (clamped to valid range) + * @return Value at the target keyframe + * + * @code + * auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * t.jump(0); // Jump to first keyframe (value: 0) + * t.jump(1); // Jump to second keyframe (value: 50) + * t.jump(2); // Jump to third keyframe (value: 100) + * @endcode + */ + auto jump(std::size_t target_key_frame) -> tween_value_t; + + /** + * @brief Advances or rewinds the animation by a frame delta. + * + * Moves the tween forward (positive) or backward (negative) by the specified number + * of frames. Clamped to valid frame range. Triggers event::step listeners. + * + * @param frames Number of frames to move (negative values step backward) + * @return Interpolated value at the new frame + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.step(10); // Advance 10 frames (value: 10) + * t.step(5); // Advance 5 more frames (value: 15) + * t.step(-3); // Rewind 3 frames (value: 12) + * @endcode + */ + auto step(int32_t frames) -> tween_value_t; + + /** + * @brief Returns the current interpolated value without changing state. + * + * Non-mutating query of the tween's current value. Does not trigger events + * or modify the tween's position. + * + * @return Current interpolated value + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.step(25); + * auto val = t.peek(); // Returns 25, doesn't change state + * @endcode + */ + [[nodiscard]] auto peek() const -> tween_value_t; + + /** + * @brief Queries the interpolated value at any frame without changing state. + * + * Previews what the value would be at the target frame without moving the tween. + * Useful for scrubbing, previewing, or inspecting the animation curve. + * Does not trigger events. + * + * @param target_frame Frame to query (clamped to valid range) + * @return Interpolated value at the target frame + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * auto midpoint = t.peek(50U); // Preview value at frame 50 (returns 50) + * auto start = t.peek(0U); // Preview start value (returns 0) + * // Tween still at frame 0, not moved + * @endcode + */ + [[nodiscard]] auto peek(uint32_t target_frame) const -> tween_value_t; + + /** + * @brief Returns the animation completion percentage. + * + * Calculates progress as (current_frame - first_frame) / total_frames. + * Returns a value in the range [0.0, 1.0]. + * + * @return Progress percentage (0.0 = start, 1.0 = complete) + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.step(25); + * printf("%.0f%% complete\n", t.progress() * 100.0f); // "25% complete" + * + * // Check if animation is done + * if (t.progress() >= 1.0f) { + * printf("Animation complete!\n"); + * } + * @endcode + */ + [[nodiscard]] auto progress() const -> float; + + /** + * @brief Registers a callback for step() events. + * + * The callback is invoked after each step() call. It receives a reference to + * the tween and must return event::response::ok to continue receiving events, + * or event::response::unsubscribe to auto-remove. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.on(event::step, [](auto& tween) { + * printf("Stepped to: %d\n", tween.peek()); + * return event::response::ok; + * }); + * t.step(10); // Prints: "Stepped to: 10" + * @endcode + */ + template auto on(detail::event::step_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for seek() events. + * + * The callback is invoked after each seek() call. Same signature and behavior as step events. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * t.on(event::seek, [](auto& tween) { + * printf("Seeked to: %d\n", tween.peek()); + * return event::response::ok; + * }); + * @endcode + */ + template auto on(detail::event::seek_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for jump() events. + * + * The callback is invoked after each jump() call. Same signature and behavior as step events. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * t.on(event::jump, [](auto& tween) { + * printf("Jumped to keyframe\n"); + * return event::response::ok; + * }); + * @endcode + */ + template auto on(detail::event::jump_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for animation completion. + * + * The callback is invoked when the tween reaches the last frame (progress >= 1.0). + * Triggered by step(), seek(), or jump() when they result in completion. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.on(event::complete, [](auto& tween) { + * printf("Animation complete!\n"); + * return event::response::ok; + * }); + * t.seek(100U); // Triggers complete event + * @endcode + */ + template auto on(detail::event::complete_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for entering a keyframe. + * + * The callback is invoked when the tween transitions into a new keyframe section. + * Receives the tween reference and event data containing the keyframe index. + * + * @param cb Callback with signature: event::response(tween&, event::keyframeEnter) + * + * @code + * auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * t.on(event::keyframeEnter, [](auto& tween, auto evt) { + * printf("Entered keyframe %zu\n", evt.key_frame); + * return event::response::ok; + * }); + * t.step(31); // Triggers: "Entered keyframe 1" + * @endcode + */ + template auto on(detail::event::keyframeEnter_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for leaving a keyframe. + * + * The callback is invoked when the tween transitions out of a keyframe section. + * Receives the tween reference and event data containing the keyframe index. + * + * @param cb Callback with signature: event::response(tween&, event::keyframeLeave) + * + * @code + * auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * t.on(event::keyframeLeave, [](auto& tween, auto evt) { + * printf("Left keyframe %zu\n", evt.key_frame); + * return event::response::ok; + * }); + * t.step(31); // Triggers: "Left keyframe 0" + * @endcode + */ + template auto on(detail::event::keyframeLeave_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for any position update (step, seek, or jump). + * + * The callback is invoked after step(), seek(), or jump() calls. This is a convenience + * event for monitoring all position changes from a single callback. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(event::update, [](auto& tween) { + * printf("Tween updated to: %d\n", tween.peek()); + * return event::response::ok; + * }); + * t.step(10); // Triggers update + * t.seek(50U); // Triggers update + * @endcode + */ + template auto on(detail::event::update_t, Callback&& cb) -> void ; + + private: + using callback_t = std::function; + using keyframe_enter_callback_t = std::function; + using keyframe_leave_callback_t = std::function; + + key_frames_t key_frames; + uint32_t current_frame = 0; + tween_value_t current_value; + std::size_t current_keyframe_index = 0; + std::vector step_listeners; + std::vector seek_listeners; + std::vector jump_listeners; + std::vector complete_listeners; + std::vector update_listeners; + std::vector keyframe_enter_listeners; + std::vector keyframe_leave_listeners; + + auto invoke_listeners(std::vector& listeners) -> void; + auto invoke_keyframe_listeners(std::size_t old_keyframe_index, std::size_t new_keyframe_index) -> void; + auto render(uint32_t target_frame) const -> tween_value_t; + [[nodiscard]] auto find_key_frame_index(uint32_t frame) const -> std::size_t; + }; +} + +#include "tween.tcc" + +#endif //TWEENY_TWEEN_H diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc new file mode 100644 index 0000000..b685cef --- /dev/null +++ b/include/tweeny/tween.tcc @@ -0,0 +1,326 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_TWEEN_TCC +#define TWEENY_TWEEN_TCC + +#include +#include +#include +#include + +#include "detail/interpolate.h" +#include "easing.h" + +template +tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input), current_value(render(0)) { } + +template +tweeny::tween::tween(key_frames_t && key_frames_input) : key_frames(std::move(key_frames_input)), current_value(render(0)) { } + +template +auto tweeny::tween::find_key_frame_index(uint32_t frame) const -> size_t { + std::size_t i = 0; + while (i + 1 < key_frames.size() && frame >= key_frames[i + 1].position) { ++i; } + return i; +} + + +template +auto tweeny::tween::seek(const uint32_t target_frame) -> tween_value_t { + const std::size_t old_keyframe_index = current_keyframe_index; + const std::size_t new_keyframe_index = find_key_frame_index(target_frame); + + current_value = render(target_frame); + current_frame = target_frame; + current_keyframe_index = new_keyframe_index; + + invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index); + invoke_listeners(seek_listeners); + invoke_listeners(update_listeners); + + if (progress() >= 1.0f) { + invoke_listeners(complete_listeners); + } + + return current_value; +} + +template +auto tweeny::tween::jump(std::size_t target_key_frame) -> tween_value_t { + const std::size_t old_keyframe_index = current_keyframe_index; + target_key_frame = std::clamp(target_key_frame, static_cast(0), key_frames.size() - 1); + + const auto target_frame = static_cast(key_frames[target_key_frame].position); + current_value = render(target_frame); + current_frame = target_frame; + current_keyframe_index = target_key_frame; + + invoke_keyframe_listeners(old_keyframe_index, target_key_frame); + invoke_listeners(jump_listeners); + invoke_listeners(update_listeners); + + if (progress() >= 1.0f) { + invoke_listeners(complete_listeners); + } + + return current_value; +} + +template +auto tweeny::tween::step(const int32_t frames) -> tween_value_t { + const std::size_t old_keyframe_index = current_keyframe_index; + + uint32_t target_frame = current_frame; + if (frames < 0) { + const auto dec = static_cast(-frames); + if (dec > target_frame) target_frame = 0; + else target_frame -= dec; + } else { + target_frame += static_cast(frames); + } + + const std::size_t new_keyframe_index = find_key_frame_index(target_frame); + + current_value = render(target_frame); + current_frame = target_frame; + current_keyframe_index = new_keyframe_index; + + invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index); + invoke_listeners(step_listeners); + invoke_listeners(update_listeners); + + if (progress() >= 1.0f) { + invoke_listeners(complete_listeners); + } + + return current_value; +} + +template +template +auto tweeny::tween::on(detail::event::step_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "step callback must return tweeny::event::response"); + step_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(detail::event::seek_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "seek callback must return tweeny::event::response"); + seek_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(detail::event::jump_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "jump callback must return tweeny::event::response"); + jump_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(detail::event::complete_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "complete callback must return tweeny::event::response"); + complete_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(detail::event::keyframeEnter_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "keyframeEnter callback must return tweeny::event::response"); + keyframe_enter_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(detail::event::keyframeLeave_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "keyframeLeave callback must return tweeny::event::response"); + keyframe_leave_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(detail::event::update_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "update callback must return tweeny::event::response"); + update_listeners.emplace_back(std::forward(cb)); +} + +template +auto tweeny::tween::invoke_listeners(std::vector& listeners) -> void { + std::vector to_remove; + to_remove.reserve(listeners.size()); + + for (std::size_t i = 0; i < listeners.size(); ++i) { + const auto resp = listeners[i](*this); + if (resp == event::response::unsubscribe) { + to_remove.push_back(i); + } + } + + if (!to_remove.empty()) { + using diff_t = typename std::vector::difference_type; + for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) { + listeners.erase(listeners.begin() + static_cast(*it)); + } + } +} + +template +auto tweeny::tween::invoke_keyframe_listeners(const std::size_t old_keyframe_index, const std::size_t new_keyframe_index) -> void { + if (old_keyframe_index == new_keyframe_index) return; + + // Leave listeners + { + std::vector to_remove; + const struct event::keyframeLeave evt{old_keyframe_index}; + for (std::size_t i = 0; i < keyframe_leave_listeners.size(); ++i) { + const auto resp = keyframe_leave_listeners[i](*this, evt); + if (resp == event::response::unsubscribe) { + to_remove.push_back(i); + } + } + for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) { + keyframe_leave_listeners.erase(keyframe_leave_listeners.begin() + static_cast::difference_type>(*it)); + } + } + + // Enter listeners + { + std::vector to_remove; + const struct event::keyframeEnter evt{new_keyframe_index}; + for (std::size_t i = 0; i < keyframe_enter_listeners.size(); ++i) { + const auto resp = keyframe_enter_listeners[i](*this, evt); + if (resp == event::response::unsubscribe) { + to_remove.push_back(i); + } + } + for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) { + keyframe_enter_listeners.erase(keyframe_enter_listeners.begin() + static_cast::difference_type>(*it)); + } + } +} + +template +auto tweeny::tween::render(uint32_t target_frame) const -> tween_value_t { + constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; + + const auto as_return_value = [](const auto & val) -> tween_value_t { + if constexpr (ValuesCount == 1) { + return std::get<0>(val); + } else { + return val; + } + }; + + if (key_frames.empty()) { + if constexpr (ValuesCount == 1) { + return FirstValueType{}; + } else { + return typename key_frame_t::values_t{}; + } + } + + const auto & first_key_frame = key_frames.front(); + const auto & last_key_frame = key_frames.back(); + + target_frame = std::clamp( + target_frame, + first_key_frame.position, + last_key_frame.position + ); + + if (target_frame <= first_key_frame.position) return as_return_value(first_key_frame.values); + if (target_frame >= last_key_frame.position) return as_return_value(last_key_frame.values); + + std::size_t base_key_key_frame_idx = find_key_frame_index(target_frame); + + if (base_key_key_frame_idx + 1 >= key_frames.size()) { + return as_return_value(last_key_frame.values); + } + + const key_frame_t & base_key_frame = key_frames[base_key_key_frame_idx]; + const key_frame_t & next_key_frame = key_frames[base_key_key_frame_idx + 1]; + + const int64_t base_kf_position = base_key_frame.position; + const uint32_t target_kf_position = next_key_frame.position; + const int64_t target_frame_i64 = target_frame; + + float inbetween_progress = 1.0f; + if (target_kf_position > base_kf_position) { + const auto numerator = static_cast(target_frame_i64 - base_kf_position); + const auto denominator = static_cast(target_kf_position - base_kf_position); + inbetween_progress = numerator / denominator; + } + inbetween_progress = std::clamp(inbetween_progress, 0.0f, 1.0f); + + auto values = detail::interpolate_values( + inbetween_progress, + base_key_frame, + next_key_frame, + std::make_index_sequence{} + ); + + return as_return_value(values); +} + +template +auto tweeny::tween::peek() const -> tween_value_t { + return current_value; +} + +template +auto tweeny::tween::peek(uint32_t target_frame) const -> tween_value_t { + return render(target_frame); +} + +template +auto tweeny::tween::progress() const -> float { + if (key_frames.empty()) return 0.0f; + + const auto & first_key_frame = key_frames.front(); + const auto & last_key_frame = key_frames.back(); + + const uint32_t total_frames = last_key_frame.position - first_key_frame.position; + if (total_frames == 0) return 1.0f; + + const uint32_t current_offset = current_frame - first_key_frame.position; + return std::clamp(static_cast(current_offset) / static_cast(total_frames), 0.0f, 1.0f); +} + +#endif //TWEENY_TWEEN_TCC diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h new file mode 100644 index 0000000..0d4bb79 --- /dev/null +++ b/include/tweeny/tweeny.h @@ -0,0 +1,352 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @file tweeny.h + * @brief Builder API for creating tweens. + * + * This file provides the fluent builder interface for constructing tween animations. + * The typical workflow is: from() → to() → via() → during() → build() + * + * @code + * auto tween = tweeny::from(0.0f).to(100.0f).via(easing::linear).during(60U).build(); + * @endcode + */ + +#ifndef TWEENY_TWEENY_H +#define TWEENY_TWEENY_H + +#include "tween.h" +#include +#include + +#include "detail/tuple-utilities.h" +#include "event.h" +#include "easing.h" + +/** + * @namespace tweeny + * @brief Contains all public API types and functions for creating and managing tweens. + * + * This namespace provides the builder pattern API (from(), tweeny_builder), the main + * tween class template, easing functions, and event types. All user-facing functionality + * is contained within this namespace to avoid naming conflicts. + */ +namespace tweeny { + /** + * @brief Primary template for the tween builder type. + * + * Models a fluent builder used to construct tween instances for one or more parts. + * The boolean template parameter encodes the builder state: when false, no to() call + * has been made yet; when true, easing functions and durations can be configured. + * + * @tparam WasToCalled Compile-time flag indicating whether at least one to() call has been made. + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + template + class tweeny_builder; + + template + /** + * @brief Builder specialization for the stage before the first to() call. + * + * Holds the initial key-frame values and allows adding the next key-frame via to(). + * Calling `to()` transitions the builder to the WasToCalled=true specialization; build() + * can be used to create a tween with the currently collected key-frames. + * + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + class tweeny_builder { + typedef std::vector> key_frames_t; + typedef tween tween_t; + static size_t constexpr value_count = 1 + sizeof...(RemainingValues); + + public: + explicit tweeny_builder(FirstValue firstComponent, RemainingValues... remainingComponents) { + key_frames.emplace_back(firstComponent, remainingComponents...); + } + + explicit tweeny_builder(key_frames_t && frames) : key_frames(std::move(frames)) {} + explicit tweeny_builder(const key_frames_t & frames) : key_frames(frames) {} + + /** + * @brief Adds a target keyframe to the tween. + * + * Specifies the destination value(s) for the animation. After calling this, + * you can configure easing and duration with via() and during(). + * + * @param firstComponent Target value for the first component + * @param remainingComponents Target values for remaining components (if multi-value tween) + * @return Builder in a configurable state (can call via(), during(), to(), or build()) + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * @endcode + */ + tweeny_builder to(const FirstValue & firstComponent, const RemainingValues &... remainingComponents) & { + key_frames.emplace_back(firstComponent, remainingComponents...); + return tweeny_builder(key_frames); + } + + /// @overload + tweeny_builder to(const FirstValue & firstComponent, const RemainingValues &... remainingComponents) && { + key_frames.emplace_back(firstComponent, remainingComponents...); + return tweeny_builder(std::move(key_frames)); + } + + private: + key_frames_t key_frames; + }; + + /** + * @brief Builder specialization for the stage after at least one to() call. + * + * In this stage, additional key-frames can be appended with to(), easing functions can be + * specified using via(), and per-component or uniform frame counts can be set with during(). + * Finally, build() materializes the configured tween. + * + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + template + class tweeny_builder { + typedef std::vector> key_frames_t; + typedef tween tween_t; + static size_t constexpr value_count = 1 + sizeof...(RemainingValues); + + public: + explicit tweeny_builder(FirstValue firstComponent, RemainingValues... remainingComponents) { + key_frames.emplace_back(firstComponent, remainingComponents...); + } + + explicit tweeny_builder(key_frames_t && frames) : key_frames(std::move(frames)) {} + explicit tweeny_builder(const key_frames_t & frames) : key_frames(frames) {} + + /** + * @brief Adds another keyframe to create multipoint animations. + * + * Call `to()` multiple times to create complex animations with multiple segments, + * each with its own easing and duration. + * + * @param firstComponent Target value for the first component + * @param remainingComponents Target values for remaining components + * @return Reference to this builder for method chaining + * + * @code + * // Three-point animation: 0 → 50 → 100 + * auto t = tweeny::from(0) + * .to(50).via(easing::quadraticOut).during(30U) + * .to(100).via(easing::bounceOut).during(30U) + * .build(); + * @endcode + * + * @anchor builder_to + */ + tweeny_builder to(const FirstValue & firstComponent, const RemainingValues &... remainingComponents) & { + key_frames.emplace_back(firstComponent, remainingComponents...); + return tweeny_builder(key_frames); + } + + /// @overload + tweeny_builder to(const FirstValue & firstComponent, const RemainingValues &... remainingComponents) && { + key_frames.emplace_back(firstComponent, remainingComponents...); + return tweeny_builder(std::move(key_frames)); + } + + /** + * @brief Specifies per-component easing functions for the last keyframe segment. + * + * Each tween component gets its own easing function. The number of easing + * functions must match the number of tween components (compile-time checked). + * + * @param easing_functions One easing function per tween component + * @return Reference to this builder for method chaining + * + * @code + * // Two components with different easings + * auto t = tweeny::from(0, 0.0f) + * .to(100, 50.0f) + * .via(easing::linear, easing::bounceOut) + * .during(60U) + * .build(); + * @endcode + */ + template + tweeny_builder & via(EasingFunctionTypes... easing_functions) { + static_assert(sizeof...(EasingFunctionTypes) == value_count, + "via() must have one easing function per tween component"); + auto & key_frame = key_frames.at(key_frames.size() - 2); + key_frame.easing_functions = std::make_tuple(easing_functions...); + return *this; + } + + /** + * @brief Specifies a single easing function for all components. + * + * Applies the same easing to all tween components. This is the most common usage. + * + * @param easing_function Easing function to apply to all components + * @return Reference to this builder for method chaining + * + * @code + * auto t = tweeny::from(0, 0.0f).to(100, 100.0f).via(easing::quadraticInOut).during(60U).build(); + * @endcode + * + * @anchor builder_via + */ + template + tweeny_builder & via(EasingFunctionType easing_function) { + auto & key_frame = key_frames.at(key_frames.size() - 2); + key_frame.easing_functions = detail::make_repeated_tuple(easing_function); + return *this; + } + + /** + * @brief Specifies per-component frame durations for the last keyframe segment. + * + * Each component can have its own animation duration in frames. The number of + * durations must match the number of components (compile-time checked). + * All parameters must be uint32_t. + * + * @param frame_counts Duration in frames for each component + * @return Reference to this builder for method chaining + * + * @code + * // X animates over 60 frames, Y over 120 frames + * auto t = tweeny::from(0, 0).to(100, 100).during(60U, 120U).build(); + * @endcode + * + * @anchor builder_during + */ + template + tweeny_builder & during(FrameCountsType... frame_counts) { + static_assert(sizeof...(FrameCountsType) == value_count, + "during() must have one frame count per tween component"); + static_assert((std::is_same_v, uint32_t> && ...), + "during() parameters must be of type uint32_t"); + auto & key_frame = key_frames.at(key_frames.size() - 2); + std::array frame_counts_array = { frame_counts... }; + std::copy( + std::begin(frame_counts_array), + std::end(frame_counts_array), + std::begin(key_frame.tween_frame_counts)); + fix_frame_positions(); + return *this; + } + + /** + * @brief Specifies a uniform frame duration for all components. + * + * All tween components will animate over the same number of frames. + * This is the most common usage. + * + * @param frame_count Duration in frames for the animation segment + * @return Reference to this builder for method chaining + * + * @code + * // Animate from 0 to 100 over 60 frames + * auto t = tweeny::from(0, 0).to(100, 100).during(60U).build(); + * @endcode + */ + tweeny_builder & during(uint32_t frame_count) { + auto & key_frame = key_frames.at(key_frames.size() - 2); + std::fill( + std::begin(key_frame.tween_frame_counts), + std::end(key_frame.tween_frame_counts), + frame_count + ); + fix_frame_positions(); + return *this; + } + + /** + * @brief Constructs a tween object from the configured keyframes. + * + * Creates a tween with all configured keyframes, easings, and durations. + * The builder can be reused to create multiple tween instances with the same + * configuration or modified further to create variations. + * + * @return A tween object ready for animation + * + * @code + * // Direct use (builder discarded after build) + * auto t1 = tweeny::from(0).to(100).via(easing::linear).during(60U).build(); + * + * // Reusable builder + * auto builder = tweeny::from(0).to(100).via(easing::linear).during(60U); + * auto t2 = builder.build(); // First tween + * auto t3 = builder.build(); // Second tween with same config + * + * // Create variations + * auto t4 = builder.to(200).during(120U).build(); // Extended animation + * @endcode + * + * @anchor builder_build + */ + tween_t build() const & { return tween(key_frames); } + + /// @overload + tween_t build() && { return tween(std::move(key_frames)); } + + private: + key_frames_t key_frames; + void fix_frame_positions() { + uint32_t key_frame_position = 0; + for (auto & key_frame : key_frames) { + key_frame.position = key_frame_position; + key_frame_position += key_frame.highest_frame_count(); + } + } + }; + + /** + * @brief Creates a new tween builder starting from the specified value(s). + * + * This is the entry point for creating all tweens. It deduces types automatically + * and supports single values, multiple values, and heterogeneous types. + * + * @param firstComponent Initial value for the first component + * @param remainingComponents Initial values for additional components (optional) + * @return A builder in the initial state (must call to() next) + * + * @code + * // Single value + * auto t1 = tweeny::from(0).to(100).during(60U).build(); + * + * // Multiple homogeneous values + * auto t2 = tweeny::from(0, 0).to(100, 100).during(60U).build(); + * + * // Heterogeneous types + * auto t3 = tweeny::from(0, 0.0f, 0u).to(10, 5.0f, 100u).during(60U).build(); + * @endcode + */ + template + tweeny_builder from(FirstValue firstComponent, RemainingValues... remainingComponents) { + return tweeny_builder(firstComponent, remainingComponents...); + } +} + +#endif //TWEENY_TWEENY_H diff --git a/scripts/create-release.sh b/scripts/create-release.sh new file mode 100755 index 0000000..f057d7c --- /dev/null +++ b/scripts/create-release.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# create-release.sh — publish a Tweeny GitHub Release from an existing tag. +# +# Usage: +# ./scripts/create-release.sh X.Y.Z +# +# Prerequisites (must already exist on origin): +# - Git tag vX.Y.Z +# - CMakeLists.txt project VERSION equals X.Y.Z +# - CHANGELOG.md section "- Version X.Y.Z" +# +# Required tools: git, cmake, doxygen, uvx, gh (authenticated) +# +# What it does: +# 1. Clones origin into /tmp, checks out vX.Y.Z +# 2. Builds Doxygen docs + single-header +# 3. Syncs docs into a fresh gh-pages clone and pushes if changed +# 4. Creates GitHub Release vX.Y.Z with changelog notes + tweeny-X.Y.Z.h +# 5. Removes both /tmp clones (also on failure via trap) + +set -euo pipefail + +usage() { + echo "Usage: $0 X.Y.Z" >&2 + exit 1 +} + +if [[ $# -ne 1 ]]; then + usage +fi + +VERSION="$1" +if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "error: version must be bare semver X.Y.Z (got: $VERSION)" >&2 + exit 1 +fi + +TAG="v${VERSION}" +PID="$$" +SRC_DIR="/tmp/tweeny-release-${PID}-src" +PAGES_DIR="/tmp/tweeny-release-${PID}-pages" + +cleanup() { + rm -rf "${SRC_DIR}" "${PAGES_DIR}" +} +trap cleanup EXIT + +for cmd in git cmake doxygen uvx gh; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "error: required tool not found: $cmd" >&2 + exit 1 + fi +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel)" +ORIGIN="$(git -C "${REPO_ROOT}" remote get-url origin)" + +echo "==> Cloning ${ORIGIN} → ${SRC_DIR}" +git clone "${ORIGIN}" "${SRC_DIR}" + +echo "==> Checking out ${TAG}" +git -C "${SRC_DIR}" checkout "${TAG}" + +CMAKE_VERSION="$( + sed -nE 's/^project\([^)]*[[:space:]]VERSION[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' \ + "${SRC_DIR}/CMakeLists.txt" | head -n1 +)" +if [[ -z "${CMAKE_VERSION}" ]]; then + echo "error: could not parse project VERSION from CMakeLists.txt" >&2 + exit 1 +fi +if [[ "${CMAKE_VERSION}" != "${VERSION}" ]]; then + echo "error: CMakeLists.txt VERSION is ${CMAKE_VERSION}, expected ${VERSION}" >&2 + exit 1 +fi +echo "==> CMake VERSION matches ${VERSION}" + +CHANGELOG="${SRC_DIR}/CHANGELOG.md" +if ! grep -qE "^- Version ${VERSION}(:|[[:space:]]|$)" "${CHANGELOG}"; then + echo "error: CHANGELOG.md has no '- Version ${VERSION}' section" >&2 + exit 1 +fi + +NOTES_FILE="${SRC_DIR}/.release-notes.md" +# Extract from "- Version $VERSION" through the line before the next "- Version " +awk -v ver="${VERSION}" ' + BEGIN { printing = 0 } + $0 ~ ("^- Version " ver "(:|[[:space:]]|$)") { printing = 1 } + printing && $0 ~ /^- Version / && $0 !~ ("^- Version " ver "(:|[[:space:]]|$)") { exit } + printing { print } +' "${CHANGELOG}" > "${NOTES_FILE}" + +if [[ ! -s "${NOTES_FILE}" ]]; then + echo "error: failed to extract changelog section for ${VERSION}" >&2 + exit 1 +fi +echo "==> Extracted CHANGELOG section for ${VERSION}" + +echo "==> Configuring (docs + single-header)" +cmake -S "${SRC_DIR}" -B "${SRC_DIR}/build" \ + -DTWEENY_BUILD_DOCUMENTATION=ON \ + -DTWEENY_BUILD_SINGLE_HEADER=ON + +echo "==> Building doc + single-header" +cmake --build "${SRC_DIR}/build" --target doc single-header + +HTML_DIR="${SRC_DIR}/build/src/doc/html" +HEADER_ASSET="${SRC_DIR}/build/single-header/tweeny-${VERSION}.h" + +if [[ ! -d "${HTML_DIR}" ]]; then + echo "error: Doxygen output missing: ${HTML_DIR}" >&2 + exit 1 +fi +if [[ ! -f "${HEADER_ASSET}" ]]; then + echo "error: single-header asset missing: ${HEADER_ASSET}" >&2 + exit 1 +fi + +echo "==> Cloning gh-pages → ${PAGES_DIR}" +git clone --branch gh-pages --single-branch "${ORIGIN}" "${PAGES_DIR}" + +echo "==> Syncing Doxygen html/ → doc/" +rm -rf "${PAGES_DIR}/doc" +mkdir -p "${PAGES_DIR}/doc" +cp -a "${HTML_DIR}/." "${PAGES_DIR}/doc/" + +git -C "${PAGES_DIR}" add doc/ +if git -C "${PAGES_DIR}" diff --cached --quiet; then + echo "==> No doc/ changes; skipping gh-pages push" +else + echo "==> Committing and pushing docs for ${TAG}" + git -C "${PAGES_DIR}" commit -m "docs: publish API docs for ${TAG}" + git -C "${PAGES_DIR}" push origin gh-pages +fi + +if (cd "${SRC_DIR}" && gh release view "${TAG}" >/dev/null 2>&1); then + echo "error: GitHub release ${TAG} already exists" >&2 + exit 1 +fi + +echo "==> Creating GitHub release ${TAG}" +# Run from the src clone so gh resolves owner/repo from origin. +( + cd "${SRC_DIR}" + gh release create "${TAG}" \ + --title "${TAG}" \ + --notes-file "${NOTES_FILE}" \ + "${HEADER_ASSET}" +) + +echo "==> Done: ${TAG} released; cleaning up clones" +# trap cleanup removes SRC_DIR and PAGES_DIR on EXIT diff --git a/doc/CMakeLists.txt b/src/doc/CMakeLists.txt similarity index 58% rename from doc/CMakeLists.txt rename to src/doc/CMakeLists.txt index 68b7b74..68e98e7 100644 --- a/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -24,18 +24,27 @@ find_package(Doxygen REQUIRED) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../README.md DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -file(COPY - ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml - ${CMAKE_CURRENT_SOURCE_DIR}/MANUAL.dox - DESTINATION - ${CMAKE_CURRENT_BINARY_DIR} +configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) + +set(DOC_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/manual.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/v3_to_v4.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/tweeny-styling.css" + "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md" ) + add_custom_target(doc ALL - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" VERBATIM) + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/../../README.md ${CMAKE_CURRENT_BINARY_DIR}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DOC_FILES} ${CMAKE_CURRENT_BINARY_DIR}/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/fonts ${CMAKE_CURRENT_BINARY_DIR}/fonts + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/fonts ${CMAKE_CURRENT_BINARY_DIR}/html/fonts + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${DOC_FILES} Doxyfile.in + COMMENT "Generating API documentation with Doxygen" VERBATIM +) include(GNUInstallDirs) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/doc/Doxyfile.in b/src/doc/Doxyfile.in similarity index 99% rename from doc/Doxyfile.in rename to src/doc/Doxyfile.in index d6cea5d..a3ebafa 100644 --- a/doc/Doxyfile.in +++ b/src/doc/Doxyfile.in @@ -765,7 +765,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = MANUAL.dox @CMAKE_CURRENT_SOURCE_DIR@/../include +INPUT = mainpage.dox manual.dox v3_to_v4.dox @CMAKE_CURRENT_SOURCE_DIR@/../../include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -790,13 +790,13 @@ INPUT_ENCODING = UTF-8 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, # *.vhdl, *.ucf, *.qsf, *.as and *.js. -FILE_PATTERNS = +FILE_PATTERNS = *.h *.hpp *.tcc *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a @@ -838,7 +838,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/../examples +# EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/../examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -1116,7 +1116,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -# HTML_EXTRA_STYLESHEET = customdoxygen.css +HTML_EXTRA_STYLESHEET = tweeny-styling.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note diff --git a/doc/DoxygenLayout.xml b/src/doc/DoxygenLayout.xml similarity index 98% rename from doc/DoxygenLayout.xml rename to src/doc/DoxygenLayout.xml index 193ca63..d0630fb 100644 --- a/doc/DoxygenLayout.xml +++ b/src/doc/DoxygenLayout.xml @@ -3,9 +3,9 @@ - + - + diff --git a/src/doc/fonts/IosevkaAile-SemiBold.woff2 b/src/doc/fonts/IosevkaAile-SemiBold.woff2 new file mode 100644 index 0000000..4b7ecd8 Binary files /dev/null and b/src/doc/fonts/IosevkaAile-SemiBold.woff2 differ diff --git a/src/doc/fonts/IosevkaTermCurlySlab-ExtendedSemiBold.woff2 b/src/doc/fonts/IosevkaTermCurlySlab-ExtendedSemiBold.woff2 new file mode 100644 index 0000000..1cc46b4 Binary files /dev/null and b/src/doc/fonts/IosevkaTermCurlySlab-ExtendedSemiBold.woff2 differ diff --git a/src/doc/fonts/OFL.txt b/src/doc/fonts/OFL.txt new file mode 100644 index 0000000..cc9e0da --- /dev/null +++ b/src/doc/fonts/OFL.txt @@ -0,0 +1,6 @@ +Iosevka Aile and Iosevka Term Curly Slab are licensed under the SIL Open +Font License 1.1. + +Copyright (c) 2015-2026, Renzhi Li (aka. Belleve Invis, belleve@typeof.net) + +See https://github.com/be5invis/Iosevka/blob/master/LICENSE.md diff --git a/src/doc/mainpage.dox b/src/doc/mainpage.dox new file mode 100644 index 0000000..d6cb18c --- /dev/null +++ b/src/doc/mainpage.dox @@ -0,0 +1,144 @@ +/** +@mainpage Introduction + +Tweeny is a modern C++ inbetweening library designed for creating complex animations +for games and other interactive software. It provides a type-safe, fluent API for +declaring interpolations (tweens) of any type that supports arithmetic operations. + +@section features Key Features + +- **Type-safe and modern**: Leverages C++17 features for compile-time type checking +- **Fluent builder API**: Intuitive method chaining for tween creation +- **Multi-value tweens**: Animate multiple values simultaneously (e.g., RGB colors, 3D positions) +- **Heterogeneous types**: Mix different numeric types in a single tween +- **Rich easing library**: Includes 30+ built-in easing functions +- **Keyframe animations**: Create complex multi-segment animations +- **Event system**: React to tween lifecycle events (step, seek, jump, update, complete, keyframeEnter, keyframeLeave) +- **Header-only**: Simple integration with no linking required +- **Zero dependencies**: Only requires a C++17 compiler + +@section quickstart Quick Start + +@note Coming from Tweeny 3.x? Check out the @ref v3_to_v4 "migration guide"! + +@code +#include +using tweeny::easing; + +int main() { + // Simple tween from 0 to 100 over 60 frames + auto tween = tweeny::from(0).to(100).during(60U).build(); + + // Step through the animation + for (int i = 0; i < 60; i++) { + int value = tween.step(1); + // Use value... + } + + // Multi-value tween (e.g., for RGB color) + auto color = tweeny::from(255, 0, 0) + .to(0, 255, 0) + .during(120U) + .build(); + + // Tween with easing + auto smooth = tweeny::from(0.0f) + .to(100.0f) + .via(easing::quadraticInOut) + .during(60U) + .build(); + + // Multi-segment animation with keyframes + auto complex = tweeny::from(0) + .to(50).via(easing::linear).during(30U) + .to(100).via(easing::bounceOut).during(30U) + .build(); + + return 0; +} +@endcode + +Visit the manual for a more in-depth explanation of the library. + +@section api API Reference + +The most important parts of the API are: + +- tweeny::from() - Entry point for creating new tweens +- tweeny::tween - Main tween class with animation control methods +- tweeny::tweeny_builder - Fluent builder for constructing tweens +- tweeny::easing namespace - Collection of easing functions +- tweeny::event - Event types for tween lifecycle callbacks + + +@section examples Common Patterns + +@subsection ex_basic Basic Animation +@code +auto tween = tweeny::from(0.0f).to(1.0f).during(60U).build(); +while (tween.progress() < 1.0f) { + float alpha = tween.step(1); + // Render with alpha... +} +@endcode + +@subsection ex_seek Seeking and Jumping +@code +auto tween = tweeny::from(0).to(100).during(100U).build(); + +// Jump to specific frame +tween.seek(50U); + +// Step backward (negative delta) +tween.step(-10); + +// Jump to a keyframe by index +tween.jump(0); // Jump back to first keyframe +@endcode + +@subsection ex_events Event Listeners +@code +auto tween = tweeny::from(0).to(100).during(60U).build(); + +// Listen for step events +tween.on(tweeny::event::step, [](auto& tween) { + std::cout << "Value: " << tween.peek() << ", Progress: " << tween.progress() << std::endl; + return tweeny::event::response::ok; +}); + +// Listen for keyframe transitions +tween.on(tweeny::event::keyframeEnter, [](auto& tween, auto evt) { + std::cout << "Entered keyframe " << evt.key_frame << std::endl; + return tweeny::event::response::ok; +}); +@endcode + +@subsection ex_multivalue Multi-Value Tweens +@code +// Animate RGB color +auto color = tweeny::from(255, 0, 0).to(0, 255, 0).during(120U).build(); +auto [r, g, b] = color.step(1); + +// Different easing per component +auto position = tweeny::from(0.0f, 0.0f) + .to(100.0f, 50.0f) + .via(easing::linear, easing::bounceOut) + .during(60U) + .build(); + +// Different duration per component +auto mixed = tweeny::from(0, 0) + .to(100, 200) + .during(60U, 120U) // X completes in 60 frames, Y in 120 + .build(); +@endcode + +@section resources Resources + +- GitHub Repository +- Easing visualizations, a very useful tool + +@section license License + +Tweeny is licensed under the MIT License. See the LICENSE file in the repository for details. +*/ diff --git a/src/doc/manual.dox b/src/doc/manual.dox new file mode 100644 index 0000000..869a762 --- /dev/null +++ b/src/doc/manual.dox @@ -0,0 +1,406 @@ +namespace tweeny { +/** + @page manual Tweeny Manual + + This document is the manual for Tweeny. It walks you through all the important steps when creating and controlling tweens. + +@note Coming from Tweeny 3.x? Check out the @ref v3_to_v4 "migration guide"! + + @section creating Creating Tweens + + @subsection builder_intro The Builder Pattern + + Tweeny uses a fluent builder API to create tweens. The tweeny::from function returns a **builder object**, not a tween directly. + You configure the interpolation using the builder's methods (\ref builder_to "to()", \ref builder_via "via()", + \ref builder_during "during()") and then call \ref builder_build "build()" to create + the actual tween object. + + @code + // tweeny::from returns a builder + auto builder = tweeny::from(0); + + // Configure the builder (methods modify the builder and return a reference) + builder.to(100).during(60U); + + // Build the tween + auto tween = builder.build(); + @endcode + + Most commonly, you'll chain all calls together in a single expression: + + @code + auto tween = tweeny::from(0).to(100).during(60U).build(); + @endcode + + @note **Important:** Unlike Tweeny 3.x, you must explicitly call `build()` to create a tween. The builder + can be reused and modified to create variations. + @code + auto builder = tweeny::from(0).to(100).during(60U); + auto tween1 = builder.build(); // First tween (0→100 in 60 frames) + builder.to(200).during(120U); // Add another keyframe + auto tween2 = builder.build(); // New tween (0→100→200) + @endcode + + Once built, a tween's **keyframes, durations, and easing functions are immutable**. However, the tween's + current state (frame position and value) changes as you navigate it with `step()`, `seek()`, or `jump()`. + + @subsection value_types Value Types + + Tweeny can interpolate single values, multiple values, or values of different types. The types you pass to tweeny::from + determine the tween's type signature, which affects all subsequent builder methods and the tween's return values: + + @code + // Single value tween + auto t1 = tweeny + ::from(0) + .to(100) + .during(60U) + .build(); + + // Multi-value tween (homogeneous) + auto t2 = tweeny + ::from(0, 0, 0) + .to(255, 128, 64) + .during(60U) + .build(); + + // Multi-value heterogeneous tween + auto t3 = tweeny + ::from(0, 'a', 1.0f) + .to(10, 'z', 5.0f) + .during(60U) + .build(); + @endcode + + @subsection from_to From and To + + Every \ref tween needs at least a starting point and an ending point. tweeny::from specifies the starting values, + and you must call `to()` at least once to specify target values. **This requirement is enforced at compile time** - if you + try to build a tween without calling `to()`, you'll get a compilation error. + + @code + // This won't compile - no to() called + // auto tween = tweeny::from(0).during(60U).build(); // ERROR! + + // This is correct + auto tween = tweeny::from(0).to(100).during(60U).build(); + @endcode + + The number and types of arguments to `to()` must match those passed to `from()`: + + @code + // Single value: one argument + auto t1 = tweeny::from(0).to(100).during(60U).build(); + + // Two values: two arguments of matching types + auto t2 = tweeny::from(0, 'a').to(100, 'z').during(60U).build(); + + // Wrong number of arguments - won't compile + // auto t3 = tweeny::from(0, 0).to(100).build(); // ERROR! + @endcode + + @subsection during Duration + + Every interpolation segment needs a duration. The `during()` method specifies how many units (typically frames or milliseconds) + the interpolation should take to reach the target values. The duration is always an unsigned 32-bit integer (`uint32_t`). + + Unlike a missing `to()`, omitting `during()` is **not** a compile-time error. The segment’s duration defaults to `0`, so both + keyframes sit at the same frame: `progress()` is always `1.0`, and `peek()` / `step()` / `seek()` / `jump()` keep returning the + **starting** values (never the target). Calling `during(0U)` is the same. Always call `during()` with a positive duration for + each segment you intend to animate. + @code + auto tween = tweeny::from(0).to(100).during(60U).build(); + @endcode + + For multi-value tweens, you can specify either: + - A single duration that applies to all values + - Individual durations for each value (must match the number of values) + + @code + // Same duration for all values (60 frames) + auto t1 = tweeny::from(0, 0, 0).to(100, 200, 300).during(60U).build(); + + // Different durations per value + auto t2 = tweeny::from(0, 0, 0).to(100, 200, 300).during(30U, 60U, 90U).build(); + @endcode + + When using per-value durations, the total interpolation length is determined by the **longest** duration. In the example above, + the first value reaches its target at frame 30, the second at frame 60, and the third at frame 90. The interpolation is + complete when all values have reached their targets (at frame 90). + + @subsection via Easing Functions + + Easing functions control **how** values interpolate between keyframes. They take a progress value (0.0 to 1.0), a start value, + and an end value, then return the interpolated value at that progress. For example, a linear easing is simply: + + @code + int linear(float p, int a, int b) { + return static_cast((b - a) * p + a); + } + @endcode + + By default, tweens use `easing::def` (an alias of `easing::linear`). You can change this with the `via()` method, which must be called **after** `to()`. + Tweeny includes \ref tweeny::easing "30+ built-in easing functions": + + @code + auto tween = tweeny::from(0).to(100).during(60U).via(tweeny::easing::quadraticInOut).build(); + @endcode + + Like `during()`, you can specify easings per-value or use the same for all values: + + @code + using tweeny::easing; + + // Same easing for all values + auto t1 = tweeny::from(0, 0, 0).to(100, 200, 300).during(60U) + .via(easing::bounceOut) + .build(); + + // Different easing per value + auto t2 = tweeny::from(0, 0, 0).to(100, 200, 300).during(60U) + .via( + easing::linear, + easing::quadraticOut, + easing::bounceOut + ) + .build(); + @endcode + + See tweeny::easing namespace documentation for all available easings, or visit http://easings.net for visualizations. + + @subsubsection custom_easing Custom Easing Functions + + You can provide custom easing functions as any callable matching the signature `T(float, T, T)`: + + @code + auto tween = tweeny::from(0).to(100).during(60U) + .via([](float p, int a, int b) { + return static_cast((b - a) * p * p + a); // Quadratic + }) + .build(); + @endcode + + For heterogeneous tweens, each easing must match its corresponding value type: + + @code + auto tween = tweeny::from(0, 1.0f).to(100, 200.0f).during(60U) + .via([](float p, int a, int b) { return (b - a) * p + a; }, + [](float p, float a, float b) { return (b - a) * p + a; }) + .build(); + @endcode + + @note Most easing functions truncate (not round) when returning integral types. This can cause interpolations to appear + "stuck" at the start value for a while. Use floating-point types and round manually for smoother results, or use + `easing::linear` which handles this correctly for integers. + + @subsection multipoint Multi-Point Animations + + You can create complex interpolations by chaining multiple keyframes together. Each call to `to()` adds a new keyframe, and subsequent + calls to `during()` and `via()` configure that specific segment: + + @code + auto tween = tweeny::from(0) + .to(100).during(500U) // 0 → 100 (linear, 500 frames) + .to(200).during(100U).via(easing::bounceOut) // 100 → 200 (bounce, 100 frames) + .to(50).during(200U).via(easing::backInOut) // 200 → 50 (back, 200 frames) + .build(); + @endcode + + The resulting tween seamlessly transitions through all keyframes. Navigation methods like `step()` and `seek()` work transparently + across keyframe boundaries. + + @section navigation Navigating Tweens + + Once built, a tween can be navigated in three ways: stepping, seeking, and jumping. + + @subsection step Stepping + + @b Stepping moves the tween by a relative amount (delta). This is the primary method for frame-by-frame interpolation in game loops: + + @code + auto tween = tweeny::from(0).to(100).during(1000U).build(); + while (tween.progress() < 1.0f) { + int value = tween.step(1); // Advance by 1 frame + // Use value... + } + @endcode + + step() accepts a signed 32-bit integer (`int32_t`). Positive values move forward, negative values move backward: + + @code + tween.step(10); // Move forward 10 frames + tween.step(-5); // Move backward 5 frames + @endcode + + @subsection seek Seeking + + @b Seeking jumps to an absolute frame position. Useful for scrubbing or jumping to specific points: + + @code + auto tween = tweeny::from(0).to(100).during(1000U).build(); + tween.seek(500U); // Jump to frame 500 (50% complete) + tween.seek(0U); // Jump back to start + @endcode + + seek() accepts an unsigned 32-bit integer (`uint32_t`) representing the absolute frame number. Values are clamped to the + valid frame range, from the first keyframe's position to the last keyframe's position. + + @subsection jump Jumping to Keyframes + + @b Jumping moves directly to a keyframe by its index (0-based). This is useful for multi-point interpolations: + + @code + auto tween = tweeny::from(0).to(100).during(100U).to(200).during(100U).build(); + tween.jump(0); // Jump to keyframe 0 (value: 0, frame: 0) + tween.jump(1); // Jump to keyframe 1 (value: 100, frame: 100) + tween.jump(2); // Jump to keyframe 2 (value: 200, frame: 200) + @endcode + + @subsection nav_return_values Return Values + + All navigation methods (`step()`, `seek()`, `jump()`) return the current interpolated value(s): + + - **Single-value tweens** return the value directly: + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + int value = tween.step(10); + @endcode + + - **Multi-value tweens** return a tuple (use structured bindings): + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + auto [x, y] = tween.step(10); + @endcode + + - **Heterogeneous tweens** also return a tuple: + @code + auto tween = tweeny::from(0, 1.0f).to(100, 5.0f).during(100U).build(); + auto [i, f] = tween.step(10); + @endcode + + @subsection peek Peeking Values + + Use `peek()` to query the current value without modifying the tween's state: + + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + tween.step(50); + int current = tween.peek(); // Returns 50, doesn't change state + int preview = tween.peek(75U); // Preview value at frame 75, doesn't move tween + @endcode + + @subsection progress Progress + + Use `progress()` to query how far the tween has advanced as a normalized `float` in `[0, 1]`. + Like `peek()`, it does not mutate the tween: `peek()` answers “what value?”, `progress()` answers “how far in time?”. + + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + tween.step(50); + float p = tween.progress(); // 0.5f + @endcode + + @section events Event System + + Tweeny provides an event system for reacting to interpolation lifecycle events. Register callbacks using the tween::on() method with + an event type tag. + + @subsection event_types Event Types + + Available event types: + - `event::step` - Triggered after each `step()` call + - `event::seek` - Triggered after each `seek()` call + - `event::jump` - Triggered after each `jump()` call + - `event::update` - Triggered after any position change (`step`, `seek`, or `jump`) + - `event::complete` - Triggered when the interpolation reaches the end (progress >= 1.0) + - `event::keyframeEnter` - Triggered when transitioning into a new keyframe segment + - `event::keyframeLeave` - Triggered when transitioning out of a keyframe segment + + When several apply to the same call, they fire in this order: + `specific (step|seek|jump)` → `update` → `complete` (when complete applies). + + @subsection basic_callbacks Basic Callbacks + + Most callbacks receive a reference to the tween and return an `event::response`: + + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + + tween.on(tweeny::event::step, [](auto& t) { + auto [x, y] = t.peek(); + printf("Position: (%d, %d), Progress: %.2f\n", x, y, t.progress()); + return tweeny::event::response::ok; + }); + + // Fires after step/seek/jump, before complete when applicable + tween.on(tweeny::event::update, [](auto& t) { + printf("Tween updated to: %d\n", t.peek()); + return tweeny::event::response::ok; + }); + + // Completion callback + tween.on(tweeny::event::complete, [](auto& t) { + printf("Animation finished!\n"); + return tweeny::event::response::ok; + }); + @endcode + + @subsection keyframe_callbacks Keyframe Callbacks + + Keyframe events receive additional data through an event struct: + + @code + auto tween = tweeny::from(0).to(50).during(50U).to(100).during(50U).build(); + + tween.on(tweeny::event::keyframeEnter, [](auto& t, auto evt) { + printf("Entering keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + + tween.on(tweeny::event::keyframeLeave, [](auto& t, auto evt) { + printf("Leaving keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + @endcode + + @subsection callback_lifetime Callback Lifetime + + The return value controls whether a callback stays registered: + + - `event::response::ok` - Keep receiving events (default behavior) + - `event::response::unsubscribe` - Remove callback after this invocation (one-shot) + + @code + int step_count = 0; + tween.on(tweeny::event::step, [&](auto& t) { + step_count++; + if (step_count >= 10) { + printf("Unsubscribing after 10 steps\n"); + return tweeny::event::response::unsubscribe; + } + return tweeny::event::response::ok; + }); + @endcode + + @subsection callable_types Callable Types + + Any callable matching the required signature can be used - lambdas, function pointers, functors, etc: + + @code + // Lambda (most common) + tween.on(tweeny::event::step, [](auto& t) { + return tweeny::event::response::ok; + }); + + // Function + auto my_callback = [](tweeny::tween& t) { + return tweeny::event::response::ok; + }; + tween.on(tweeny::event::seek, my_callback); + @endcode + + @section done Done! + + Enjoy using Tweeny! +*/ +} diff --git a/src/doc/tweeny-styling.css b/src/doc/tweeny-styling.css new file mode 100644 index 0000000..f7dc9f3 --- /dev/null +++ b/src/doc/tweeny-styling.css @@ -0,0 +1,541 @@ +/* Tweeny Doxygen theme — matches tweeny-gh-pages/index.css */ + +@font-face { + font-family: "Iosevka Aile"; + font-display: swap; + font-weight: 600; + font-style: normal; + src: url("fonts/IosevkaAile-SemiBold.woff2") format("woff2"); +} + +@font-face { + font-family: "Iosevka Term Curly Slab Extended"; + font-display: swap; + font-weight: 600; + font-style: normal; + src: url("fonts/IosevkaTermCurlySlab-ExtendedSemiBold.woff2") format("woff2"); +} + +html { + color-scheme: light dark; + + --tweeny-surface: #ffffff; + --tweeny-border: rgba(137, 89, 173, 0.14); + --tweeny-shadow: 0 18px 50px rgba(72, 42, 104, 0.08); + --tweeny-header-pad-x: 2rem; + --tweeny-text-muted: #5f5968; + + --font-family-normal: "Iosevka Aile", system-ui, sans-serif; + --font-family-title: "Iosevka Aile", system-ui, sans-serif; + --font-family-nav: "Iosevka Aile", system-ui, sans-serif; + --font-family-monospace: "Iosevka Term Curly Slab Extended", ui-monospace, monospace; + --font-family-toc: "Iosevka Aile", system-ui, sans-serif; + --font-family-search: "Iosevka Aile", system-ui, sans-serif; + --font-family-tooltip: "Iosevka Aile", system-ui, sans-serif; + + --page-background-color: #f7f5fb; + --page-foreground-color: #2d2a32; + --page-link-color: #8959ad; + --page-visited-link-color: #8959ad; + --page-external-link-color: #6f4591; + + --index-odd-item-bg-color: #ffffff; + --index-even-item-bg-color: #f7f5fb; + --index-header-color: #6f4591; + --index-separator-color: rgba(137, 89, 173, 0.2); + + --header-background-color: #f7f5fb; + --header-separator-color: rgba(137, 89, 173, 0.14); + --group-header-separator-color: rgba(137, 89, 173, 0.14); + --group-header-color: #6f4591; + + --footer-foreground-color: #5f5968; + --citation-label-color: #6f4591; + + --title-background-color: #f7f5fb; + --title-separator-color: rgba(137, 89, 173, 0.14); + + --blockquote-background-color: #ffffff; + --blockquote-border-color: rgba(137, 89, 173, 0.25); + + --scrollbar-thumb-color: rgba(137, 89, 173, 0.35); + --scrollbar-background-color: #f7f5fb; + + --icon-background-color: #8959ad; + --icon-foreground-color: #ffffff; + --icon-folder-open-fill-color: rgba(137, 89, 173, 0.2); + --icon-folder-fill-color: rgba(137, 89, 173, 0.12); + --icon-folder-border-color: #8959ad; + --icon-doc-fill-color: rgba(137, 89, 173, 0.12); + --icon-doc-border-color: #8959ad; + + --memdecl-background-color: #ffffff; + --memdecl-foreground-color: #5f5968; + --memdecl-template-color: #8959ad; + --memdecl-border-color: rgba(137, 89, 173, 0.14); + + --memdef-border-color: rgba(137, 89, 173, 0.2); + --memdef-title-background-color: #ffffff; + --memdef-proto-background-color: #faf8fc; + --memdef-proto-text-color: #2d2a32; + --memdef-param-name-color: #c82829; + --memdef-template-color: #8959ad; + + --table-cell-border-color: rgba(137, 89, 173, 0.2); + --table-header-background-color: #6f4591; + --table-header-foreground-color: #ffffff; + + --label-background-color: #8959ad; + --label-left-top-border-color: #6f4591; + --label-right-bottom-border-color: rgba(137, 89, 173, 0.2); + --label-foreground-color: #ffffff; + + --nav-background-color: #f7f5fb; + --nav-foreground-color: #6f4591; + --nav-border-color: rgba(137, 89, 173, 0.14); + --nav-breadcrumb-separator-color: rgba(137, 89, 173, 0.14); + --nav-breadcrumb-active-bg: rgba(137, 89, 173, 0.08); + --nav-breadcrumb-color: #6f4591; + --nav-splitbar-bg-color: rgba(137, 89, 173, 0.1); + --nav-splitbar-handle-color: rgba(137, 89, 173, 0.35); + --nav-text-normal-color: #6f4591; + --nav-menu-button-color: #6f4591; + --nav-menu-background-color: #f7f5fb; + --nav-menu-foreground-color: #5f5968; + --nav-menu-active-bg: rgba(137, 89, 173, 0.1); + --nav-menu-active-color: #8959ad; + --nav-arrow-color: rgba(137, 89, 173, 0.45); + --nav-arrow-selected-color: #8959ad; + + --sync-icon-border-color: rgba(137, 89, 173, 0.14); + --sync-icon-background-color: #f7f5fb; + --sync-icon-selected-background-color: rgba(137, 89, 173, 0.08); + --sync-icon-color: rgba(137, 89, 173, 0.35); + --sync-icon-selected-color: #8959ad; + + --toc-background-color: #ffffff; + --toc-border-color: rgba(137, 89, 173, 0.14); + --toc-header-color: #6f4591; + + --search-background-color: #ffffff; + --search-foreground-color: #5f5968; + --search-active-color: #2d2a32; + --search-filter-background-color: rgba(255, 255, 255, 0.92); + --search-filter-foreground-color: #2d2a32; + --search-filter-border-color: rgba(137, 89, 173, 0.2); + --search-filter-highlight-text-color: #ffffff; + --search-filter-highlight-bg-color: #8959ad; + --search-results-foreground-color: #6f4591; + --search-results-background-color: rgba(255, 255, 255, 0.95); + --search-results-border-color: rgba(137, 89, 173, 0.2); + --search-box-border-color: rgba(137, 89, 173, 0.25); + + --code-keyword-color: #8959a8; + --code-type-keyword-color: #4271ae; + --code-flow-keyword-color: #f5871f; + --code-comment-color: #8e908c; + --code-preprocessor-color: #f5871f; + --code-string-literal-color: #718c00; + --code-char-literal-color: #008080; + --code-xml-cdata-color: #4d4d4c; + --fragment-foreground-color: #4d4d4c; + --fragment-background-color: #faf8fc; + --fragment-border-color: rgba(137, 89, 173, 0.14); + --fragment-lineno-background-color: #f0edf5; + --fragment-lineno-foreground-color: #5f5968; + --fragment-lineno-link-fg-color: #8959ad; + --fragment-lineno-link-bg-color: #e8e4ef; + --fragment-lineno-link-hover-fg-color: #f9a12d; + --fragment-lineno-link-hover-bg-color: #e8e4ef; + + --tooltip-foreground-color: #2d2a32; + --tooltip-background-color: #ffffff; + --tooltip-arrow-background-color: #ffffff; + --tooltip-border-color: rgba(137, 89, 173, 0.2); + --tooltip-doc-color: #5f5968; + --tooltip-declaration-color: #718c00; + --tooltip-link-color: #8959ad; +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + --tweeny-surface: #211c28; + --tweeny-border: rgba(184, 143, 212, 0.2); + --tweeny-shadow: 0 18px 50px rgba(0, 0, 0, 0.35); + --tweeny-text-muted: #a89fb8; + + --font-family-normal: "Iosevka Aile", system-ui, sans-serif; + --font-family-title: "Iosevka Aile", system-ui, sans-serif; + --font-family-nav: "Iosevka Aile", system-ui, sans-serif; + --font-family-monospace: "Iosevka Term Curly Slab Extended", ui-monospace, monospace; + --font-family-toc: "Iosevka Aile", system-ui, sans-serif; + --font-family-search: "Iosevka Aile", system-ui, sans-serif; + --font-family-tooltip: "Iosevka Aile", system-ui, sans-serif; + + --page-background-color: #141118; + --page-foreground-color: #e8e4ef; + --page-link-color: #b88fd4; + --page-visited-link-color: #b88fd4; + --page-external-link-color: #d4b8e8; + + --index-odd-item-bg-color: #211c28; + --index-even-item-bg-color: #141118; + --index-header-color: #d4b8e8; + --index-separator-color: rgba(184, 143, 212, 0.2); + + --header-background-color: #141118; + --header-separator-color: rgba(184, 143, 212, 0.2); + --group-header-separator-color: rgba(184, 143, 212, 0.2); + --group-header-color: #d4b8e8; + + --footer-foreground-color: #a89fb8; + --citation-label-color: #d4b8e8; + + --title-background-color: #141118; + --title-separator-color: rgba(184, 143, 212, 0.2); + + --blockquote-background-color: #211c28; + --blockquote-border-color: rgba(184, 143, 212, 0.25); + + --scrollbar-thumb-color: rgba(184, 143, 212, 0.35); + --scrollbar-background-color: #141118; + + --icon-background-color: #b88fd4; + --icon-foreground-color: #141118; + --icon-folder-open-fill-color: rgba(184, 143, 212, 0.2); + --icon-folder-fill-color: rgba(184, 143, 212, 0.12); + --icon-folder-border-color: #b88fd4; + --icon-doc-fill-color: rgba(184, 143, 212, 0.12); + --icon-doc-border-color: #b88fd4; + + --memdecl-background-color: #211c28; + --memdecl-foreground-color: #a89fb8; + --memdecl-template-color: #b88fd4; + --memdecl-border-color: rgba(184, 143, 212, 0.2); + + --memdef-border-color: rgba(184, 143, 212, 0.2); + --memdef-title-background-color: #211c28; + --memdef-proto-background-color: #1a1620; + --memdef-proto-text-color: #e8e4ef; + --memdef-param-name-color: #cc6666; + --memdef-template-color: #b88fd4; + + --table-cell-border-color: rgba(184, 143, 212, 0.2); + --table-header-background-color: #6f4591; + --table-header-foreground-color: #e8e4ef; + + --label-background-color: #8959ad; + --label-left-top-border-color: #b88fd4; + --label-right-bottom-border-color: rgba(184, 143, 212, 0.2); + --label-foreground-color: #e8e4ef; + + --nav-background-color: #141118; + --nav-foreground-color: #d4b8e8; + --nav-border-color: rgba(184, 143, 212, 0.2); + --nav-breadcrumb-separator-color: rgba(184, 143, 212, 0.2); + --nav-breadcrumb-active-bg: rgba(184, 143, 212, 0.1); + --nav-breadcrumb-color: #d4b8e8; + --nav-splitbar-bg-color: rgba(184, 143, 212, 0.12); + --nav-splitbar-handle-color: rgba(184, 143, 212, 0.35); + --nav-text-normal-color: #d4b8e8; + --nav-menu-button-color: #d4b8e8; + --nav-menu-background-color: #141118; + --nav-menu-foreground-color: #a89fb8; + --nav-menu-active-bg: rgba(184, 143, 212, 0.12); + --nav-menu-active-color: #b88fd4; + --nav-arrow-color: rgba(184, 143, 212, 0.45); + --nav-arrow-selected-color: #b88fd4; + + --sync-icon-border-color: rgba(184, 143, 212, 0.2); + --sync-icon-background-color: #141118; + --sync-icon-selected-background-color: rgba(184, 143, 212, 0.1); + --sync-icon-color: rgba(184, 143, 212, 0.35); + --sync-icon-selected-color: #b88fd4; + + --toc-background-color: #211c28; + --toc-border-color: rgba(184, 143, 212, 0.2); + --toc-header-color: #d4b8e8; + + --search-background-color: #211c28; + --search-foreground-color: #a89fb8; + --search-active-color: #e8e4ef; + --search-filter-background-color: #211c28; + --search-filter-foreground-color: #e8e4ef; + --search-filter-border-color: rgba(184, 143, 212, 0.2); + --search-filter-highlight-text-color: #141118; + --search-filter-highlight-bg-color: #b88fd4; + --search-results-background-color: #211c28; + --search-results-foreground-color: #d4b8e8; + --search-results-border-color: rgba(184, 143, 212, 0.2); + --search-box-border-color: rgba(184, 143, 212, 0.25); + + --code-keyword-color: #b294bb; + --code-type-keyword-color: #81a2be; + --code-flow-keyword-color: #de935f; + --code-comment-color: #969896; + --code-preprocessor-color: #de935f; + --code-string-literal-color: #b5bd68; + --code-char-literal-color: #00e0f0; + --code-xml-cdata-color: #c5c8c6; + --fragment-foreground-color: #c5c8c6; + --fragment-background-color: #1a1620; + --fragment-border-color: rgba(184, 143, 212, 0.2); + --fragment-lineno-background-color: #141118; + --fragment-lineno-foreground-color: #a89fb8; + --fragment-lineno-link-fg-color: #b88fd4; + --fragment-lineno-link-bg-color: #211c28; + --fragment-lineno-link-hover-fg-color: #f9a12d; + --fragment-lineno-link-hover-bg-color: #211c28; + + --tooltip-foreground-color: #e8e4ef; + --tooltip-background-color: #211c28; + --tooltip-arrow-background-color: #211c28; + --tooltip-border-color: rgba(184, 143, 212, 0.2); + --tooltip-doc-color: #a89fb8; + --tooltip-declaration-color: #b5bd68; + --tooltip-link-color: #b88fd4; + } +} + +body, +table, +div, +p, +dl, +div.contents, +div.textblock { + font-weight: 600; + font-size: 1.05rem; + line-height: 1.65; +} + +body { + color: var(--tweeny-text-muted); +} + +div.contents li { + line-height: 1.65; +} + +#top { + margin: 0; + width: 100%; + border: none; + border-bottom: 1px solid var(--tweeny-border); + background: var(--tweeny-surface); + box-shadow: none; + overflow: visible; + position: relative; + z-index: 200; +} + +#titlearea { + padding: 1rem var(--tweeny-header-pad-x) 0.85rem; + margin: 0; + border-bottom: 1px solid var(--tweeny-border); + background: var(--tweeny-surface); +} + +#projectalign { + padding-left: 0 !important; +} + +#main-nav { + border-bottom: none; + background: var(--tweeny-surface); + overflow: visible; + position: relative; + z-index: 201; +} + +#main-menu.sm-dox { + background: var(--tweeny-surface) !important; + padding: 0 var(--tweeny-header-pad-x) !important; + line-height: 36px; + overflow: visible; +} + +@media (min-width: 768px) { + #main-menu.sm-dox > li { + padding: 0; + } + + #main-menu.sm-dox > li > a { + padding: 0 0.75rem 0 0; + } + + /* Keep room for the absolutely positioned .sub-arrow (tabs.css: right:10px) */ + #main-menu.sm-dox > li > a.has-submenu { + padding-right: 1.5rem; + } + + #main-menu.sm-dox > li:first-child > a { + padding-left: 0; + } +} + +#main-menu.sm-dox ul { + z-index: 202; +} + +.sm-dox { + background-color: var(--tweeny-surface); +} + +#projectname { + color: #6f4591; + font-weight: 600; + letter-spacing: -0.02em; +} + +#projectbrief { + color: #5f5968; + font-weight: 600; +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) #projectname { + color: #d4b8e8; + } + + html:not(.dark-mode) #projectbrief { + color: #a89fb8; + } +} + +#doc-content { + padding: 0 2rem 3rem; +} + +div.contents { + margin-top: 1.5rem; + margin-left: 0; + margin-right: 0; +} + +div.header { + background: transparent; + border-bottom: none; +} + +div.headertitle { + padding: 1rem 0 0.75rem; +} + +div.headertitle > .title { + font-family: var(--font-family-normal); + font-size: 2em; + font-weight: 600; + line-height: normal; + margin: 0 15px 0 0; + color: #6f4591; +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) div.headertitle > .title { + color: #d4b8e8; + } +} + +a.el, +a.el:visited, +a.code, +a.code:visited, +a.line, +a.line:visited, +.contents a:not([class]), +#nav-tree a, +.sm-dox a { + transition: color 160ms ease; +} + +a.el:hover, +a.elRef:hover, +a.code:hover, +a.line:hover, +.contents a:not([class]):hover, +#nav-tree a:hover, +.sm-dox a:hover { + color: #f9a12d !important; +} + +.sm-dox a.current { + color: #f9a12d; +} + +.sm-dox a, +.sm-dox a:hover, +.sm-dox ul a, +.sm-dox ul a:hover, +.sm-dox > li:first-child > a, +.sm-dox > li:last-child > a, +.sm-dox ul, +#main-menu.sm-dox { + border-radius: 0 !important; +} + +.sm-dox a:hover { + background-color: transparent !important; + text-decoration: underline; + text-decoration-thickness: 2px; + text-decoration-color: #f9a12d; +} + +pre, +pre.fragment, +.fragment, +div.line, +div.line *, +span.tt, +.memname, +.memproto, +.memtitle, +.memtemplate, +.permalink, +code, +code *, +tt, +tt * { + font-family: "Iosevka Term Curly Slab Extended", ui-monospace, monospace; + font-weight: 600; +} + +pre, +pre.fragment, +div.line, +.fragment { + font-size: 0.92rem; + line-height: 1.7; +} + +.memname, +.memproto, +.memtitle, +.memtemplate, +.permalink { + line-height: 1.5; +} + +.fragment, +pre.fragment, +div.fragment { + border-radius: 0; + box-shadow: 0 18px 50px rgba(72, 42, 104, 0.08); +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) .fragment, + html:not(.dark-mode) pre.fragment, + html:not(.dark-mode) div.fragment { + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35); + } +} + +.memitem, +.memproto, +.memtitle, +.fieldtable, +.directory tr.even, +.directory tr.odd { + border-radius: 0; +} diff --git a/src/doc/v3_to_v4.dox b/src/doc/v3_to_v4.dox new file mode 100644 index 0000000..7c42882 --- /dev/null +++ b/src/doc/v3_to_v4.dox @@ -0,0 +1,394 @@ +namespace tweeny { +/** + @page v3_to_v4 Migrating from Tweeny 3.x to 4.x + + This guide helps you migrate code from Tweeny 3.x to 4.x. While the core concepts remain the same, + version 4 introduces significant API changes centered around a builder pattern and a new event system. + + @section overview_changes Overview of Changes + + The main changes in Tweeny 4.x are: + - **Builder Pattern:** tweeny::from now returns a builder; you must call `build()` to create a tween + - **Immutable Tweens:** Once built, keyframes, durations, and easings cannot be changed + - **New Event System:** Callbacks are now registered via `on()` with event types instead of `onStep()`/`onSeek()` + - **Type System Changes:** Duration and step parameters are now strongly typed (`uint32_t` and `int32_t`) + - **Return Value Changes:** Multi-value tweens now return tuples instead of arrays + - **New peek() Method:** Query values without changing tween state + - **Direction API Removed:** `forward()`/`backward()` removed; use negative steps instead + + @section builder_pattern The Builder Pattern + + @subsection builder_basic Basic Usage + + @b Tweeny 3.x: + @code + auto tween = tweeny::from(0).to(100).during(100); + @endcode + + @b Tweeny 4.x: + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + @endcode + + The key difference: you must explicitly call `build()` to create the tween. The builder is reusable: + + @code + auto builder = tweeny::from(0).to(100).during(60U); + auto tween1 = builder.build(); // First tween + builder.to(200).during(120U); // Add keyframe + auto tween2 = builder.build(); // Different tween (0→100→200) + @endcode + + @subsection builder_compile_time Compile-Time Safety + + Tweeny 4.x enforces that you call `to()` at least once before building: + + @b Tweeny 3.x: (would create invalid tween) + @code + auto tween = tweeny::from(0).during(100); // Creates tween with no target + @endcode + + @b Tweeny 4.x: (compilation error) + @code + // auto tween = tweeny::from(0).during(100U).build(); // ERROR: no to() called + auto tween = tweeny::from(0).to(100).during(100U).build(); // OK + @endcode + + @section type_changes Type System Changes + + @subsection duration_types Duration Types + + @b Tweeny 3.x: Durations could be any unsigned integer type + @code + auto tween = tweeny::from(0).to(100).during(100); // int literal + @endcode + + @b Tweeny 4.x: Durations must be `uint32_t` (use the `U` suffix) + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); // uint32_t + @endcode + + @subsection step_types Step Types + + @b Tweeny 3.x: step() accepted floats (percentage) or integers (duration) + @code + tween.step(0.5f); // Step by 50% + tween.step(10); // Step by 10 units + @endcode + + @b Tweeny 4.x: step() only accepts `int32_t` (duration), no percentage mode + @code + tween.step(10); // Step forward by 10 frames + tween.step(-5); // Step backward by 5 frames + @endcode + + @subsection seek_types Seek Types + + @b Tweeny 3.x: seek() accepted floats (percentage) or integers (absolute position) + @code + tween.seek(0.5f); // Seek to 50% + tween.seek(500); // Seek to frame 500 + @endcode + + @b Tweeny 4.x: seek() only accepts `uint32_t` (absolute frame position) + @code + auto tween = tweeny::from(0).to(100).during(1000U).build(); + tween.seek(500U); // Seek to frame 500 (50% of a 1000-frame tween) + @endcode + + @subsection return_values Return Values + + @b Tweeny 3.x: Multi-value tweens returned `std::array` + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100); + std::array values = tween.step(10); + int x = values[0]; + int y = values[1]; + @endcode + + @b Tweeny 4.x: Multi-value tweens return tuples (use structured bindings) + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + auto [x, y] = tween.step(10); + @endcode + + @section direction_changes Direction Changes + + @b Tweeny 3.x: Used `forward()` and `backward()` to control direction + @code + tween.backward(); + tween.step(10); // Steps backward + tween.forward(); + tween.step(10); // Steps forward + @endcode + + @b Tweeny 4.x: Use signed integers with step() + @code + tween.step(-10); // Steps backward by 10 + tween.step(10); // Steps forward by 10 + @endcode + + @section peek_method The peek() Method + + Tweeny 4.x introduces `peek()` to query values without mutating state. + + @b Tweeny 3.x: No direct equivalent; you had to step and track state manually + @code + auto tween = tweeny::from(0).to(100).during(100); + tween.seek(50); + // Get current value by stepping 0 (awkward) + int value = tween.step(0); + @endcode + + @b Tweeny 4.x: Use peek() for non-mutating queries + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + tween.seek(50U); + + int current = tween.peek(); // Get current value without changing state + int preview = tween.peek(75U); // Preview value at frame 75 without seeking + @endcode + + @section event_system Event System Changes + + The callback system has been completely redesigned around an event-based architecture. + + @subsection callback_registration Registration + + @b Tweeny 3.x: Used `onStep()` and `onSeek()` methods + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100); + + // Step callback accepting values + tween.onStep([](int x, int y) { + printf("Position: (%d, %d)\n", x, y); + return false; // false = keep callback + }); + + // Step callback accepting tween reference + tween.onStep([](auto& t) { + return false; + }); + + // Step callback accepting both + tween.onStep([](auto& t, int x, int y) { + return false; + }); + + // Seek callback + tween.onSeek([](int x, int y) { + return false; + }); + @endcode + + @b Tweeny 4.x: Use `on()` with event type tags + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + + // Step callback (receives tween reference) + tween.on(tweeny::event::step, [](auto& t) { + auto [x, y] = t.peek(); + printf("Position: (%d, %d)\n", x, y); + return tweeny::event::response::ok; // Keep callback + }); + + // Seek callback + tween.on(tweeny::event::seek, [](auto& t) { + return tweeny::event::response::ok; + }); + + // Jump callback (new in v4) + tween.on(tweeny::event::jump, [](auto& t) { + return tweeny::event::response::ok; + }); + @endcode + + @subsection callback_return_values Callback Return Values + + @b Tweeny 3.x: Returned `bool` + @code + return true; // Remove callback (one-shot) + return false; // Keep callback + @endcode + + @b Tweeny 4.x: Returns `event::response` enum + @code + return tweeny::event::response::unsubscribe; // Remove callback + return tweeny::event::response::ok; // Keep callback + @endcode + + @subsection new_events New Event Types + + Tweeny 4.x introduces several new event types: + + @code + // Triggered when interpolation completes (reaches 100%) + tween.on(tweeny::event::complete, [](auto& t) { + printf("Animation finished!\n"); + return tweeny::event::response::ok; + }); + + // Triggered after any step(), seek(), or jump() (after the specific event, before complete) + tween.on(tweeny::event::update, [](auto& t) { + printf("Tween updated to: %d\n", t.peek()); + return tweeny::event::response::ok; + }); + + // Triggered when entering a new keyframe segment + tween.on(tweeny::event::keyframeEnter, [](auto& t, auto evt) { + printf("Entering keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + + // Triggered when leaving a keyframe segment + tween.on(tweeny::event::keyframeLeave, [](auto& t, auto evt) { + printf("Leaving keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + @endcode + + @subsection callback_signatures Callback Signature Changes + + Tweeny 3.x callbacks could receive interpolated values directly. Tweeny 4.x callbacks always receive + a tween reference; use `peek()` to get values: + + @b Tweeny 3.x: + @code + tween.onStep([](int x, int y) { // Values passed directly + printf("x=%d, y=%d\n", x, y); + return false; + }); + @endcode + + @b Tweeny 4.x: + @code + tween.on(tweeny::event::step, [](auto& t) { // Tween reference only + auto [x, y] = t.peek(); // Explicitly peek values + printf("x=%d, y=%d\n", x, y); + return tweeny::event::response::ok; + }); + @endcode + + @section migration_checklist Migration Checklist + + Use this checklist to migrate your code: + + 1. **Add `.build()` calls** + - Find all `tweeny::from(...)` chains + - Add `.build()` at the end to create the tween + + 2. **Update duration literals** + - Change `during(100)` to `during(100U)` + - Ensure all duration values use `uint32_t` + + 3. **Update step() calls** + - Remove percentage-based stepping (convert to frame counts) + - Use negative values for backward stepping instead of `backward()` + + 4. **Update seek() calls** + - Remove percentage-based seeking (calculate frame from percentage manually) + - Add `U` suffix to all seek values: `seek(500)` → `seek(500U)` + + 5. **Replace array destructuring with tuple destructuring** + - Change `std::array v = tween.step(...)` to `auto [v1, v2, ...] = tween.step(...)` + + 6. **Update callbacks** + - Replace `onStep(callback)` with `on(event::step, callback)` + - Replace `onSeek(callback)` with `on(event::seek, callback)` + - Change callback signatures to accept `auto& t` parameter + - Use `t.peek()` to get values inside callbacks + - Change `return true/false` to `return event::response::unsubscribe/ok` + + 7. **Remove forward()/backward() calls** + - Replace with signed step values + + 8. **Use peek() for non-mutating queries** + - Replace `step(0)` patterns with `peek()` + - Use `peek(frame)` to preview values at different positions + + 9. **Consider new event types** + - Add `event::complete` callbacks for animation completion + - Add `event::update` for a single handler on any position change (`step`/`seek`/`jump`) + - Add `event::keyframeEnter`/`event::keyframeLeave` for multi-point tweens + + @section example_migration Complete Migration Example + + @b Tweeny 3.x: + @code + #include "tweeny.h" + + auto tween = tweeny::from(0, 0, 255) + .to(640, 480, 0) + .during(2000) + .via(tweeny::easing::backOut); + + tween.onStep([](int x, int y, int alpha) { + draw_sprite(x, y, alpha); + return false; + }); + + tween.onSeek([](int x, int y, int alpha) { + printf("Seeked to: %d, %d, %d\n", x, y, alpha); + return false; + }); + + // Animation loop + while (tween.progress() < 1.0f) { + tween.step(delta_time); + render(); + } + + // Reverse + tween.backward(); + while (tween.progress() > 0.0f) { + tween.step(delta_time); + render(); + } + @endcode + + @b Tweeny 4.x: + @code + #include + + auto tween = tweeny::from(0, 0, 255) + .to(640, 480, 0) + .during(2000U) + .via(tweeny::easing::backOut) + .build(); // <-- Must call build() + + tween.on(tweeny::event::step, [](auto& t) { + auto [x, y, alpha] = t.peek(); // <-- Use peek() to get values + draw_sprite(x, y, alpha); + return tweeny::event::response::ok; // <-- New return type + }); + + tween.on(tweeny::event::seek, [](auto& t) { + auto [x, y, alpha] = t.peek(); + printf("Seeked to: %d, %d, %d\n", x, y, alpha); + return tweeny::event::response::ok; + }); + + // Completion event (new in v4) + tween.on(tweeny::event::complete, [](auto& t) { + printf("Animation complete!\n"); + return tweeny::event::response::ok; + }); + + // Animation loop + while (tween.progress() < 1.0f) { + tween.step(delta_time); // delta_time is int32_t + render(); + } + + // Reverse (use negative steps instead of backward()) + while (tween.progress() > 0.0f) { + tween.step(-delta_time); // <-- Negative value steps backward + render(); + } + @endcode + + @section migration_done Done! + + For detailed information on the new API, see the @ref manual. +*/ +} diff --git a/src/sandbox.cc b/src/sandbox.cc deleted file mode 100644 index b1bea48..0000000 --- a/src/sandbox.cc +++ /dev/null @@ -1,6 +0,0 @@ -#include "tweeny.h" - -int main() { - auto tween1 = tweeny::from(0.0, 1.0f).to(1.0f, 0.0f).via("stepped", "linear"); - return 0; -} \ No newline at end of file diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 0000000..55e8c5d --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.15) +find_package(Catch2 3 REQUIRED CONFIG) + +add_executable(tweeny_tests + sanity.cpp + tween/peek.cpp + tween/progress.cpp + tween/step.cpp + tween/seek.cpp + tween/jump.cpp + tween/navigation_consistency.cpp + events/step.cpp + events/seek.cpp + events/jump.cpp + events/complete.cpp + events/keyframe_enter.cpp + events/keyframe_leave.cpp + events/update.cpp + easings/linear.cpp + easings/quadratic.cpp + easings/cubic.cpp + easings/quartic.cpp + easings/quintic.cpp + easings/sinusoidal.cpp + easings/exponential.cpp + easings/circular.cpp + easings/elastic.cpp + easings/back.cpp + easings/bounce.cpp + easings/def.cpp + easings/stepped.cpp +) + +target_compile_features(tweeny_tests PRIVATE cxx_std_17) +target_link_libraries(tweeny_tests PRIVATE Catch2::Catch2WithMain tweeny::tweeny) + +include(Catch) +catch_discover_tests(tweeny_tests) diff --git a/src/tests/easings/back.cpp b/src/tests/easings/back.cpp new file mode 100644 index 0000000..3803267 --- /dev/null +++ b/src/tests/easings/back.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::backIn matches Penner easeInBack samples", "[easing][back]") { + test_easing(tweeny::easing::backIn, easeInBack); +} + +TEST_CASE("easing::backOut matches Penner easeOutBack samples", "[easing][back]") { + test_easing(tweeny::easing::backOut, easeOutBack); +} + +TEST_CASE("easing::backInOut matches Penner easeInOutBack samples", "[easing][back]") { + test_easing(tweeny::easing::backInOut, easeInOutBack); +} + +TEST_CASE("tween via(backIn) matches Penner easeInBack samples", "[easing][back][tween]") { + test_tween_easing(tweeny::easing::backIn, easeInBack); +} + +TEST_CASE("tween via(backOut) matches Penner easeOutBack samples", "[easing][back][tween]") { + test_tween_easing(tweeny::easing::backOut, easeOutBack); +} + +TEST_CASE("tween via(backInOut) matches Penner easeInOutBack samples", "[easing][back][tween]") { + test_tween_easing(tweeny::easing::backInOut, easeInOutBack); +} diff --git a/src/tests/easings/bounce.cpp b/src/tests/easings/bounce.cpp new file mode 100644 index 0000000..04e2aa4 --- /dev/null +++ b/src/tests/easings/bounce.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::bounceIn matches Penner easeInBounce samples", "[easing][bounce]") { + test_easing(tweeny::easing::bounceIn, easeInBounce); +} + +TEST_CASE("easing::bounceOut matches Penner easeOutBounce samples", "[easing][bounce]") { + test_easing(tweeny::easing::bounceOut, easeOutBounce); +} + +TEST_CASE("easing::bounceInOut matches Penner easeInOutBounce samples", "[easing][bounce]") { + test_easing(tweeny::easing::bounceInOut, easeInOutBounce); +} + +TEST_CASE("tween via(bounceIn) matches Penner easeInBounce samples", "[easing][bounce][tween]") { + test_tween_easing(tweeny::easing::bounceIn, easeInBounce); +} + +TEST_CASE("tween via(bounceOut) matches Penner easeOutBounce samples", "[easing][bounce][tween]") { + test_tween_easing(tweeny::easing::bounceOut, easeOutBounce); +} + +TEST_CASE("tween via(bounceInOut) matches Penner easeInOutBounce samples", "[easing][bounce][tween]") { + test_tween_easing(tweeny::easing::bounceInOut, easeInOutBounce); +} diff --git a/src/tests/easings/circular.cpp b/src/tests/easings/circular.cpp new file mode 100644 index 0000000..d4fcdc0 --- /dev/null +++ b/src/tests/easings/circular.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::circularIn matches Penner easeInCirc samples", "[easing][circular]") { + test_easing(tweeny::easing::circularIn, easeInCirc); +} + +TEST_CASE("easing::circularOut matches Penner easeOutCirc samples", "[easing][circular]") { + test_easing(tweeny::easing::circularOut, easeOutCirc); +} + +TEST_CASE("easing::circularInOut matches Penner easeInOutCirc samples", "[easing][circular]") { + test_easing(tweeny::easing::circularInOut, easeInOutCirc); +} + +TEST_CASE("tween via(circularIn) matches Penner easeInCirc samples", "[easing][circular][tween]") { + test_tween_easing(tweeny::easing::circularIn, easeInCirc); +} + +TEST_CASE("tween via(circularOut) matches Penner easeOutCirc samples", "[easing][circular][tween]") { + test_tween_easing(tweeny::easing::circularOut, easeOutCirc); +} + +TEST_CASE("tween via(circularInOut) matches Penner easeInOutCirc samples", "[easing][circular][tween]") { + test_tween_easing(tweeny::easing::circularInOut, easeInOutCirc); +} diff --git a/src/tests/easings/cubic.cpp b/src/tests/easings/cubic.cpp new file mode 100644 index 0000000..ba155d5 --- /dev/null +++ b/src/tests/easings/cubic.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::cubicIn matches Penner easeInCubic samples", "[easing][cubic]") { + test_easing(tweeny::easing::cubicIn, easeInCubic); +} + +TEST_CASE("easing::cubicOut matches Penner easeOutCubic samples", "[easing][cubic]") { + test_easing(tweeny::easing::cubicOut, easeOutCubic); +} + +TEST_CASE("easing::cubicInOut matches Penner easeInOutCubic samples", "[easing][cubic]") { + test_easing(tweeny::easing::cubicInOut, easeInOutCubic); +} + +TEST_CASE("tween via(cubicIn) matches Penner easeInCubic samples", "[easing][cubic][tween]") { + test_tween_easing(tweeny::easing::cubicIn, easeInCubic); +} + +TEST_CASE("tween via(cubicOut) matches Penner easeOutCubic samples", "[easing][cubic][tween]") { + test_tween_easing(tweeny::easing::cubicOut, easeOutCubic); +} + +TEST_CASE("tween via(cubicInOut) matches Penner easeInOutCubic samples", "[easing][cubic][tween]") { + test_tween_easing(tweeny::easing::cubicInOut, easeInOutCubic); +} diff --git a/src/tests/easings/def.cpp b/src/tests/easings/def.cpp new file mode 100644 index 0000000..ef7b780 --- /dev/null +++ b/src/tests/easings/def.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::def matches Penner linear samples", "[easing][def]") { + test_easing(tweeny::easing::def, linear); +} + +TEST_CASE("tween via(def) matches Penner linear samples", "[easing][def][tween]") { + test_tween_easing(tweeny::easing::def, linear); +} + +TEST_CASE("tween without via() uses def (linear) behavior", "[easing][def][tween]") { + auto with_def = tweeny::from(0.f).to(1.f).via(tweeny::easing::def).during(100U).build(); + auto implicit = tweeny::from(0.f).to(1.f).during(100U).build(); + for (std::size_t t = 0; t < sample_count; ++t) { + const auto frame = static_cast(t); + REQUIRE(implicit.peek(frame) == Catch::Approx(with_def.peek(frame)).margin(1e-6f)); + } +} diff --git a/src/tests/easings/elastic.cpp b/src/tests/easings/elastic.cpp new file mode 100644 index 0000000..724b3a2 --- /dev/null +++ b/src/tests/easings/elastic.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::elasticIn matches Penner easeInElastic samples", "[easing][elastic]") { + test_easing(tweeny::easing::elasticIn, easeInElastic); +} + +TEST_CASE("easing::elasticOut matches Penner easeOutElastic samples", "[easing][elastic]") { + test_easing(tweeny::easing::elasticOut, easeOutElastic); +} + +TEST_CASE("easing::elasticInOut matches Penner easeInOutElastic samples", "[easing][elastic]") { + test_easing(tweeny::easing::elasticInOut, easeInOutElastic); +} + +TEST_CASE("tween via(elasticIn) matches Penner easeInElastic samples", "[easing][elastic][tween]") { + test_tween_easing(tweeny::easing::elasticIn, easeInElastic); +} + +TEST_CASE("tween via(elasticOut) matches Penner easeOutElastic samples", "[easing][elastic][tween]") { + test_tween_easing(tweeny::easing::elasticOut, easeOutElastic); +} + +TEST_CASE("tween via(elasticInOut) matches Penner easeInOutElastic samples", "[easing][elastic][tween]") { + test_tween_easing(tweeny::easing::elasticInOut, easeInOutElastic); +} diff --git a/src/tests/easings/exponential.cpp b/src/tests/easings/exponential.cpp new file mode 100644 index 0000000..9e45e48 --- /dev/null +++ b/src/tests/easings/exponential.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::exponentialIn matches Penner easeInExpo samples", "[easing][exponential]") { + test_easing(tweeny::easing::exponentialIn, easeInExpo); +} + +TEST_CASE("easing::exponentialOut matches Penner easeOutExpo samples", "[easing][exponential]") { + test_easing(tweeny::easing::exponentialOut, easeOutExpo); +} + +TEST_CASE("easing::exponentialInOut matches Penner easeInOutExpo samples", "[easing][exponential]") { + test_easing(tweeny::easing::exponentialInOut, easeInOutExpo); +} + +TEST_CASE("tween via(exponentialIn) matches Penner easeInExpo samples", "[easing][exponential][tween]") { + test_tween_easing(tweeny::easing::exponentialIn, easeInExpo); +} + +TEST_CASE("tween via(exponentialOut) matches Penner easeOutExpo samples", "[easing][exponential][tween]") { + test_tween_easing(tweeny::easing::exponentialOut, easeOutExpo); +} + +TEST_CASE("tween via(exponentialInOut) matches Penner easeInOutExpo samples", "[easing][exponential][tween]") { + test_tween_easing(tweeny::easing::exponentialInOut, easeInOutExpo); +} diff --git a/src/tests/easings/linear.cpp b/src/tests/easings/linear.cpp new file mode 100644 index 0000000..ca7ec76 --- /dev/null +++ b/src/tests/easings/linear.cpp @@ -0,0 +1,12 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::linear matches Penner reference samples", "[easing][linear]") { + test_easing(tweeny::easing::linear, linear); +} + +TEST_CASE("tween via(linear) matches Penner reference samples", "[easing][linear][tween]") { + test_tween_easing(tweeny::easing::linear, linear); +} diff --git a/src/tests/easings/quadratic.cpp b/src/tests/easings/quadratic.cpp new file mode 100644 index 0000000..0a1be7b --- /dev/null +++ b/src/tests/easings/quadratic.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::quadraticIn matches Penner easeInQuad samples", "[easing][quadratic]") { + test_easing(tweeny::easing::quadraticIn, easeInQuad); +} + +TEST_CASE("easing::quadraticOut matches Penner easeOutQuad samples", "[easing][quadratic]") { + test_easing(tweeny::easing::quadraticOut, easeOutQuad); +} + +TEST_CASE("easing::quadraticInOut matches Penner easeInOutQuad samples", "[easing][quadratic]") { + test_easing(tweeny::easing::quadraticInOut, easeInOutQuad); +} + +TEST_CASE("tween via(quadraticIn) matches Penner easeInQuad samples", "[easing][quadratic][tween]") { + test_tween_easing(tweeny::easing::quadraticIn, easeInQuad); +} + +TEST_CASE("tween via(quadraticOut) matches Penner easeOutQuad samples", "[easing][quadratic][tween]") { + test_tween_easing(tweeny::easing::quadraticOut, easeOutQuad); +} + +TEST_CASE("tween via(quadraticInOut) matches Penner easeInOutQuad samples", "[easing][quadratic][tween]") { + test_tween_easing(tweeny::easing::quadraticInOut, easeInOutQuad); +} diff --git a/src/tests/easings/quartic.cpp b/src/tests/easings/quartic.cpp new file mode 100644 index 0000000..a3e36d7 --- /dev/null +++ b/src/tests/easings/quartic.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::quarticIn matches Penner easeInQuart samples", "[easing][quartic]") { + test_easing(tweeny::easing::quarticIn, easeInQuart); +} + +TEST_CASE("easing::quarticOut matches Penner easeOutQuart samples", "[easing][quartic]") { + test_easing(tweeny::easing::quarticOut, easeOutQuart); +} + +TEST_CASE("easing::quarticInOut matches Penner easeInOutQuart samples", "[easing][quartic]") { + test_easing(tweeny::easing::quarticInOut, easeInOutQuart); +} + +TEST_CASE("tween via(quarticIn) matches Penner easeInQuart samples", "[easing][quartic][tween]") { + test_tween_easing(tweeny::easing::quarticIn, easeInQuart); +} + +TEST_CASE("tween via(quarticOut) matches Penner easeOutQuart samples", "[easing][quartic][tween]") { + test_tween_easing(tweeny::easing::quarticOut, easeOutQuart); +} + +TEST_CASE("tween via(quarticInOut) matches Penner easeInOutQuart samples", "[easing][quartic][tween]") { + test_tween_easing(tweeny::easing::quarticInOut, easeInOutQuart); +} diff --git a/src/tests/easings/quintic.cpp b/src/tests/easings/quintic.cpp new file mode 100644 index 0000000..ecc97dc --- /dev/null +++ b/src/tests/easings/quintic.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::quinticIn matches Penner easeInQuint samples", "[easing][quintic]") { + test_easing(tweeny::easing::quinticIn, easeInQuint); +} + +TEST_CASE("easing::quinticOut matches Penner easeOutQuint samples", "[easing][quintic]") { + test_easing(tweeny::easing::quinticOut, easeOutQuint); +} + +TEST_CASE("easing::quinticInOut matches Penner easeInOutQuint samples", "[easing][quintic]") { + test_easing(tweeny::easing::quinticInOut, easeInOutQuint); +} + +TEST_CASE("tween via(quinticIn) matches Penner easeInQuint samples", "[easing][quintic][tween]") { + test_tween_easing(tweeny::easing::quinticIn, easeInQuint); +} + +TEST_CASE("tween via(quinticOut) matches Penner easeOutQuint samples", "[easing][quintic][tween]") { + test_tween_easing(tweeny::easing::quinticOut, easeOutQuint); +} + +TEST_CASE("tween via(quinticInOut) matches Penner easeInOutQuint samples", "[easing][quintic][tween]") { + test_tween_easing(tweeny::easing::quinticInOut, easeInOutQuint); +} diff --git a/src/tests/easings/reference-values.h b/src/tests/easings/reference-values.h new file mode 100644 index 0000000..4481b6c --- /dev/null +++ b/src/tests/easings/reference-values.h @@ -0,0 +1,504 @@ +#ifndef TWEENY_TESTS_EASINGS_REFERENCE_VALUES_H +#define TWEENY_TESTS_EASINGS_REFERENCE_VALUES_H + +#include + +constexpr std::size_t sample_count = 101; + +constexpr float linear[sample_count] = { + 0.0f, 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, + 0.08f, 0.09f, 0.1f, 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, + 0.16f, 0.17f, 0.18f, 0.19f, 0.2f, 0.21f, 0.22f, 0.23f, + 0.24f, 0.25f, 0.26f, 0.27f, 0.28f, 0.29f, 0.3f, 0.31f, + 0.32f, 0.33f, 0.34f, 0.35f, 0.36f, 0.37f, 0.38f, 0.39f, + 0.4f, 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, + 0.48f, 0.49f, 0.5f, 0.51f, 0.52f, 0.53f, 0.54f, 0.55f, + 0.56f, 0.57f, 0.58f, 0.59f, 0.6f, 0.61f, 0.62f, 0.63f, + 0.64f, 0.65f, 0.66f, 0.67f, 0.68f, 0.69f, 0.7f, 0.71f, + 0.72f, 0.73f, 0.74f, 0.75f, 0.76f, 0.77f, 0.78f, 0.79f, + 0.8f, 0.81f, 0.82f, 0.83f, 0.84f, 0.85f, 0.86f, 0.87f, + 0.88f, 0.89f, 0.9f, 0.91f, 0.92f, 0.93f, 0.94f, 0.95f, + 0.96f, 0.97f, 0.98f, 0.99f, 1.0f +}; + +constexpr float easeInQuad[sample_count] = { + 0.0f, 0.0001f, 0.0004f, 0.0009f, 0.0016f, 0.0025000000000000005f, 0.0036f, 0.004900000000000001f, + 0.0064f, 0.0081f, 0.010000000000000002f, 0.0121f, 0.0144f, 0.016900000000000002f, 0.019600000000000003f, 0.0225f, + 0.0256f, 0.028900000000000006f, 0.0324f, 0.0361f, 0.04000000000000001f, 0.04409999999999999f, 0.0484f, 0.0529f, + 0.0576f, 0.0625f, 0.06760000000000001f, 0.0729f, 0.07840000000000001f, 0.0841f, 0.09f, 0.0961f, + 0.1024f, 0.10890000000000001f, 0.11560000000000002f, 0.12249999999999998f, 0.1296f, 0.1369f, 0.1444f, 0.1521f, + 0.16000000000000003f, 0.16809999999999997f, 0.17639999999999997f, 0.18489999999999998f, 0.1936f, 0.2025f, 0.2116f, 0.22089999999999999f, + 0.2304f, 0.24009999999999998f, 0.25f, 0.2601f, 0.27040000000000003f, 0.28090000000000004f, 0.2916f, 0.30250000000000005f, + 0.31360000000000005f, 0.32489999999999997f, 0.3364f, 0.34809999999999997f, 0.36f, 0.3721f, 0.3844f, 0.39690000000000003f, + 0.4096f, 0.42250000000000004f, 0.43560000000000004f, 0.4489000000000001f, 0.4624000000000001f, 0.4760999999999999f, 0.48999999999999994f, 0.5041f, + 0.5184f, 0.5328999999999999f, 0.5476f, 0.5625f, 0.5776f, 0.5929f, 0.6084f, 0.6241000000000001f, + 0.6400000000000001f, 0.6561000000000001f, 0.6723999999999999f, 0.6889f, 0.7055999999999999f, 0.7224999999999999f, 0.7395999999999999f, 0.7569f, + 0.7744f, 0.7921f, 0.81f, 0.8281000000000001f, 0.8464f, 0.8649000000000001f, 0.8835999999999999f, 0.9025f, + 0.9216f, 0.9409f, 0.9603999999999999f, 0.9801f, 1.0f +}; + +constexpr float easeOutQuad[sample_count] = { + 0.0f, 0.0199f, 0.0396f, 0.0591f, 0.0784f, 0.0975f, 0.11639999999999999f, 0.1351f, + 0.1536f, 0.1719f, 0.19f, 0.2079f, 0.22559999999999997f, 0.2431f, 0.2604f, 0.2775f, + 0.2944f, 0.31110000000000004f, 0.3276f, 0.34390000000000004f, 0.36000000000000004f, 0.3759f, 0.3916f, 0.4071f, + 0.4224f, 0.4375f, 0.4524f, 0.4671f, 0.48160000000000003f, 0.49589999999999995f, 0.51f, 0.5239f, + 0.5376f, 0.5511f, 0.5644f, 0.5774999999999999f, 0.5904f, 0.6031f, 0.6156f, 0.6279f, + 0.6400000000000001f, 0.6519f, 0.6636f, 0.6751f, 0.6864f, 0.6975f, 0.7084f, 0.7191f, + 0.7296f, 0.7399f, 0.75f, 0.7599f, 0.7696000000000001f, 0.7791f, 0.7884f, 0.7975f, + 0.8064f, 0.8151f, 0.8235999999999999f, 0.8319000000000001f, 0.84f, 0.8479000000000001f, 0.8555999999999999f, 0.8631000000000001f, + 0.8704f, 0.8775000000000001f, 0.8844f, 0.8911000000000001f, 0.8976f, 0.9038999999999999f, 0.9099999999999999f, 0.9158999999999999f, + 0.9216f, 0.9271f, 0.9324f, 0.9375f, 0.9424f, 0.9471f, 0.9516f, 0.9559f, + 0.96f, 0.9639f, 0.9676000000000001f, 0.9710999999999999f, 0.9744f, 0.9774999999999999f, 0.9804f, 0.9830999999999999f, + 0.9856000000000001f, 0.9878999999999999f, 0.9900000000000001f, 0.9918999999999999f, 0.9936000000000001f, 0.9950999999999999f, 0.9964f, 0.9974999999999999f, + 0.9984f, 0.9991f, 0.9996f, 0.9999f, 1.0f +}; + +constexpr float easeInOutQuad[sample_count] = { + 0.0f, 0.0002f, 0.0008f, 0.0018f, 0.0032f, 0.005000000000000001f, 0.0072f, 0.009800000000000001f, + 0.0128f, 0.0162f, 0.020000000000000004f, 0.0242f, 0.0288f, 0.033800000000000004f, 0.039200000000000006f, 0.045f, + 0.0512f, 0.05780000000000001f, 0.0648f, 0.0722f, 0.08000000000000002f, 0.08819999999999999f, 0.0968f, 0.1058f, + 0.1152f, 0.125f, 0.13520000000000001f, 0.1458f, 0.15680000000000002f, 0.1682f, 0.18f, 0.1922f, + 0.2048f, 0.21780000000000002f, 0.23120000000000004f, 0.24499999999999997f, 0.2592f, 0.2738f, 0.2888f, 0.3042f, + 0.32000000000000006f, 0.33619999999999994f, 0.35279999999999995f, 0.36979999999999996f, 0.3872f, 0.405f, 0.4232f, 0.44179999999999997f, + 0.4608f, 0.48019999999999996f, 0.5f, 0.5198f, 0.5392f, 0.5582f, 0.5768000000000001f, 0.5950000000000001f, + 0.6128000000000001f, 0.6301999999999999f, 0.6472f, 0.6638f, 0.6799999999999999f, 0.6958f, 0.7112f, 0.7262f, + 0.7408f, 0.755f, 0.7688f, 0.7822f, 0.7952000000000001f, 0.8077999999999999f, 0.82f, 0.8318f, + 0.8432f, 0.8542f, 0.8648f, 0.875f, 0.8848f, 0.8942f, 0.9032f, 0.9118f, + 0.92f, 0.9278f, 0.9352f, 0.9421999999999999f, 0.9488f, 0.955f, 0.9608f, 0.9662f, + 0.9712000000000001f, 0.9758f, 0.98f, 0.9838f, 0.9872000000000001f, 0.9902f, 0.9927999999999999f, 0.995f, + 0.9968f, 0.9982f, 0.9992f, 0.9998f, 1.0f +}; + +constexpr float easeInCubic[sample_count] = { + 0.0f, 0.0000010000000000000002f, 0.000008000000000000001f, 0.000027f, 0.00006400000000000001f, 0.00012500000000000003f, 0.000216f, 0.0003430000000000001f, + 0.0005120000000000001f, 0.0007289999999999999f, 0.0010000000000000002f, 0.001331f, 0.001728f, 0.002197f, 0.0027440000000000008f, 0.003375f, + 0.004096000000000001f, 0.004913000000000002f, 0.0058319999999999995f, 0.0068590000000000005f, 0.008000000000000002f, 0.009260999999999998f, 0.010648f, 0.012167f, + 0.013824f, 0.015625f, 0.017576f, 0.019683000000000003f, 0.021952000000000006f, 0.024388999999999997f, 0.027f, 0.029791f, + 0.032768000000000005f, 0.035937000000000004f, 0.03930400000000001f, 0.04287499999999999f, 0.046655999999999996f, 0.050653f, 0.054872000000000004f, 0.059319000000000004f, + 0.06400000000000002f, 0.06892099999999998f, 0.07408799999999999f, 0.079507f, 0.085184f, 0.09112500000000001f, 0.097336f, 0.10382299999999998f, + 0.110592f, 0.11764899999999999f, 0.125f, 0.132651f, 0.140608f, 0.14887700000000004f, 0.15746400000000002f, 0.16637500000000005f, + 0.17561600000000005f, 0.18519299999999997f, 0.19511199999999998f, 0.20537899999999998f, 0.216f, 0.226981f, 0.238328f, 0.250047f, + 0.26214400000000004f, 0.27462500000000006f, 0.28749600000000003f, 0.30076300000000006f, 0.3144320000000001f, 0.32850899999999994f, 0.3429999999999999f, 0.357911f, + 0.37324799999999997f, 0.38901699999999995f, 0.405224f, 0.421875f, 0.43897600000000003f, 0.456533f, 0.47455200000000003f, 0.4930390000000001f, + 0.5120000000000001f, 0.5314410000000002f, 0.5513679999999999f, 0.5717869999999999f, 0.5927039999999999f, 0.6141249999999999f, 0.636056f, 0.6585030000000001f, + 0.681472f, 0.7049690000000001f, 0.7290000000000001f, 0.7535710000000001f, 0.778688f, 0.8043570000000001f, 0.8305839999999999f, 0.8573749999999999f, + 0.884736f, 0.912673f, 0.9411919999999999f, 0.9702989999999999f, 1.0f +}; + +constexpr float easeOutCubic[sample_count] = { + 0.0f, 0.02970100000000009f, 0.05880800000000008f, 0.08732700000000004f, 0.11526400000000003f, 0.1426250000000001f, 0.16941600000000012f, 0.19564300000000012f, + 0.22131199999999995f, 0.2464289999999999f, 0.2709999999999999f, 0.29503099999999993f, 0.31852800000000003f, 0.34149699999999994f, 0.36394400000000005f, 0.3858750000000001f, + 0.4072960000000001f, 0.42821300000000007f, 0.4486319999999999f, 0.46855899999999984f, 0.4879999999999999f, 0.5069609999999999f, 0.5254479999999999f, 0.5434669999999999f, + 0.561024f, 0.578125f, 0.594776f, 0.610983f, 0.626752f, 0.642089f, 0.657f, 0.6714910000000001f, + 0.6855680000000001f, 0.6992370000000001f, 0.7125040000000001f, 0.7253749999999999f, 0.737856f, 0.749953f, 0.761672f, 0.773019f, + 0.784f, 0.7946209999999999f, 0.8048879999999999f, 0.814807f, 0.824384f, 0.833625f, 0.842536f, 0.851123f, + 0.8593919999999999f, 0.867349f, 0.875f, 0.882351f, 0.889408f, 0.896177f, 0.902664f, 0.908875f, + 0.9148160000000001f, 0.920493f, 0.925912f, 0.931079f, 0.9359999999999999f, 0.940681f, 0.945128f, 0.949347f, + 0.953344f, 0.957125f, 0.960696f, 0.964063f, 0.967232f, 0.970209f, 0.973f, 0.975611f, + 0.978048f, 0.980317f, 0.982424f, 0.984375f, 0.986176f, 0.987833f, 0.989352f, 0.990739f, + 0.992f, 0.993141f, 0.9941679999999999f, 0.9950869999999999f, 0.995904f, 0.996625f, 0.997256f, 0.997803f, + 0.998272f, 0.998669f, 0.999f, 0.999271f, 0.999488f, 0.999657f, 0.999784f, 0.999875f, + 0.999936f, 0.999973f, 0.999992f, 0.999999f, 1.0f +}; + +constexpr float easeInOutCubic[sample_count] = { + 0.0f, 0.000004000000000000001f, 0.000032000000000000005f, 0.000108f, 0.00025600000000000004f, 0.0005000000000000001f, 0.000864f, 0.0013720000000000004f, + 0.0020480000000000003f, 0.0029159999999999998f, 0.004000000000000001f, 0.005324f, 0.006912f, 0.008788f, 0.010976000000000003f, 0.0135f, + 0.016384000000000003f, 0.019652000000000006f, 0.023327999999999998f, 0.027436000000000002f, 0.03200000000000001f, 0.037043999999999994f, 0.042592f, 0.048668f, + 0.055296f, 0.0625f, 0.070304f, 0.07873200000000001f, 0.08780800000000002f, 0.09755599999999999f, 0.108f, 0.119164f, + 0.13107200000000002f, 0.14374800000000001f, 0.15721600000000005f, 0.17149999999999996f, 0.18662399999999998f, 0.202612f, 0.21948800000000002f, 0.23727600000000001f, + 0.25600000000000006f, 0.27568399999999993f, 0.29635199999999995f, 0.318028f, 0.340736f, 0.36450000000000005f, 0.389344f, 0.41529199999999994f, + 0.442368f, 0.47059599999999996f, 0.5f, 0.529404f, 0.557632f, 0.584708f, 0.6106560000000001f, 0.6355000000000002f, + 0.6592640000000001f, 0.6819719999999999f, 0.7036479999999999f, 0.724316f, 0.744f, 0.762724f, 0.780512f, 0.797388f, + 0.813376f, 0.8285f, 0.842784f, 0.856252f, 0.868928f, 0.880836f, 0.8919999999999999f, 0.902444f, + 0.912192f, 0.921268f, 0.929696f, 0.9375f, 0.944704f, 0.9513320000000001f, 0.957408f, 0.962956f, + 0.968f, 0.972564f, 0.976672f, 0.980348f, 0.983616f, 0.9865f, 0.989024f, 0.991212f, + 0.993088f, 0.994676f, 0.996f, 0.997084f, 0.997952f, 0.998628f, 0.999136f, 0.9994999999999999f, + 0.999744f, 0.999892f, 0.999968f, 0.999996f, 1.0f +}; + +constexpr float easeInQuart[sample_count] = { + 0.0f, 1.0000000000000002e-8f, 1.6000000000000003e-7f, 8.1e-7f, 0.0000025600000000000005f, 0.000006250000000000002f, 0.00001296f, 0.00002401000000000001f, + 0.00004096000000000001f, 0.00006560999999999999f, 0.00010000000000000003f, 0.00014641f, 0.00020736f, 0.00028561000000000005f, 0.00038416000000000014f, 0.00050625f, + 0.0006553600000000001f, 0.0008352100000000003f, 0.0010497599999999998f, 0.0013032100000000002f, 0.0016000000000000005f, 0.0019448099999999995f, 0.00234256f, 0.0027984100000000003f, + 0.00331776f, 0.00390625f, 0.004569760000000001f, 0.005314410000000001f, 0.006146560000000002f, 0.007072809999999999f, 0.0081f, 0.00923521f, + 0.010485760000000002f, 0.011859210000000002f, 0.013363360000000005f, 0.015006249999999995f, 0.016796159999999997f, 0.01874161f, 0.020851360000000003f, 0.02313441f, + 0.025600000000000008f, 0.028257609999999992f, 0.031116959999999992f, 0.03418801f, 0.03748096f, 0.04100625000000001f, 0.044774560000000005f, 0.04879680999999999f, + 0.05308416f, 0.05764800999999999f, 0.0625f, 0.06765201f, 0.07311616000000001f, 0.07890481000000002f, 0.08503056000000002f, 0.09150625000000004f, + 0.09834496000000004f, 0.10556000999999997f, 0.11316495999999998f, 0.12117360999999999f, 0.1296f, 0.13845840999999998f, 0.14776336f, 0.15752961000000001f, + 0.16777216000000003f, 0.17850625000000006f, 0.18974736000000003f, 0.20151121000000005f, 0.21381376000000007f, 0.22667120999999993f, 0.24009999999999992f, 0.25411680999999997f, + 0.26873855999999996f, 0.28398240999999996f, 0.29986576f, 0.31640625f, 0.33362176000000004f, 0.35153041f, 0.37015056f, 0.3895008100000001f, + 0.40960000000000013f, 0.43046721000000016f, 0.4521217599999999f, 0.4745832099999999f, 0.4978713599999999f, 0.5220062499999999f, 0.54700816f, 0.5728976100000001f, + 0.59969536f, 0.6274224100000001f, 0.6561000000000001f, 0.6857496100000001f, 0.7163929600000001f, 0.7480520100000001f, 0.7807489599999998f, 0.8145062499999999f, + 0.84934656f, 0.8852928099999999f, 0.9223681599999999f, 0.96059601f, 1.0f +}; + +constexpr float easeOutQuart[sample_count] = { + 0.0f, 0.039403990000000055f, 0.07763184000000012f, 0.11470719000000007f, 0.15065344000000003f, 0.18549375000000012f, 0.21925104000000017f, 0.2519479900000001f, + 0.2836070399999999f, 0.3142503899999999f, 0.3438999999999999f, 0.3725775899999999f, 0.40030464f, 0.42710238999999994f, 0.45299184000000003f, 0.4779937500000001f, + 0.5021286400000001f, 0.5254167900000001f, 0.54787824f, 0.5695327899999998f, 0.5903999999999998f, 0.6104991899999999f, 0.62984944f, 0.6484695899999999f, + 0.66637824f, 0.68359375f, 0.70013424f, 0.7160175900000001f, 0.73126144f, 0.74588319f, 0.7599f, 0.7733287900000001f, + 0.78618624f, 0.7984887900000001f, 0.8102526400000001f, 0.8214937499999999f, 0.8322278399999999f, 0.84247039f, 0.85223664f, 0.8615415900000001f, + 0.8704000000000001f, 0.87882639f, 0.8868350399999999f, 0.89443999f, 0.90165504f, 0.90849375f, 0.91496944f, 0.92109519f, + 0.92688384f, 0.93234799f, 0.9375f, 0.94235199f, 0.94691584f, 0.95120319f, 0.95522544f, 0.95899375f, + 0.96251904f, 0.96581199f, 0.96888304f, 0.97174239f, 0.9744f, 0.97686559f, 0.97914864f, 0.98125839f, + 0.98320384f, 0.98499375f, 0.98663664f, 0.98814079f, 0.98951424f, 0.99076479f, 0.9919f, 0.99292719f, + 0.99385344f, 0.99468559f, 0.99543024f, 0.99609375f, 0.99668224f, 0.99720159f, 0.99765744f, 0.99805519f, + 0.9984f, 0.99869679f, 0.99895024f, 0.99916479f, 0.99934464f, 0.99949375f, 0.99961584f, 0.99971439f, + 0.99979264f, 0.99985359f, 0.9999f, 0.99993439f, 0.99995904f, 0.99997599f, 0.99998704f, 0.99999375f, + 0.99999744f, 0.99999919f, 0.99999984f, 0.99999999f, 1.0f +}; + +constexpr float easeInOutQuart[sample_count] = { + 0.0f, 8.000000000000001e-8f, 0.0000012800000000000002f, 0.00000648f, 0.000020480000000000004f, 0.000050000000000000016f, 0.00010368f, 0.00019208000000000007f, + 0.00032768000000000006f, 0.0005248799999999999f, 0.0008000000000000003f, 0.00117128f, 0.00165888f, 0.0022848800000000004f, 0.003073280000000001f, 0.00405f, + 0.005242880000000001f, 0.006681680000000002f, 0.008398079999999999f, 0.010425680000000001f, 0.012800000000000004f, 0.015558479999999996f, 0.01874048f, 0.022387280000000002f, + 0.02654208f, 0.03125f, 0.03655808000000001f, 0.04251528000000001f, 0.04917248000000002f, 0.05658247999999999f, 0.0648f, 0.07388168f, + 0.08388608000000002f, 0.09487368000000002f, 0.10690688000000004f, 0.12004999999999996f, 0.13436927999999998f, 0.14993288f, 0.16681088000000002f, 0.18507528f, + 0.20480000000000007f, 0.22606087999999994f, 0.24893567999999994f, 0.27350408f, 0.29984768f, 0.32805000000000006f, 0.35819648000000004f, 0.3903744799999999f, + 0.42467328f, 0.46118407999999994f, 0.5f, 0.53881592f, 0.5753267200000001f, 0.60962552f, 0.6418035200000001f, 0.6719500000000002f, + 0.7001523200000002f, 0.7264959199999999f, 0.7510643199999999f, 0.77393912f, 0.7951999999999999f, 0.81492472f, 0.83318912f, 0.8500671200000001f, + 0.86563072f, 0.87995f, 0.8930931200000001f, 0.90512632f, 0.91611392f, 0.9261183199999999f, 0.9351999999999999f, 0.94341752f, + 0.95082752f, 0.95748472f, 0.96344192f, 0.96875f, 0.97345792f, 0.97761272f, 0.98125952f, 0.98444152f, + 0.9872f, 0.98957432f, 0.99160192f, 0.99331832f, 0.99475712f, 0.99595f, 0.99692672f, 0.99771512f, + 0.99834112f, 0.99882872f, 0.9992f, 0.99947512f, 0.99967232f, 0.99980792f, 0.99989632f, 0.99995f, + 0.99997952f, 0.99999352f, 0.99999872f, 0.99999992f, 1.0f +}; + +constexpr float easeInQuint[sample_count] = { + 0.0f, 1.0000000000000002e-10f, 3.2000000000000005e-9f, 2.43e-8f, 1.0240000000000002e-7f, 3.1250000000000013e-7f, 7.776e-7f, 0.0000016807000000000007f, + 0.0000032768000000000005f, 0.000005904899999999999f, 0.000010000000000000004f, 0.0000161051f, 0.0000248832f, 0.00003712930000000001f, 0.00005378240000000002f, 0.0000759375f, + 0.00010485760000000002f, 0.00014198570000000007f, 0.00018895679999999997f, 0.0002476099f, 0.00032000000000000013f, 0.00040841009999999987f, 0.0005153632f, 0.0006436343000000001f, + 0.0007962624f, 0.0009765625f, 0.0011881376000000003f, 0.0014348907000000003f, 0.0017210368000000007f, 0.0020511148999999996f, 0.00243f, 0.0028629151f, + 0.0033554432000000006f, 0.003913539300000001f, 0.004543542400000002f, 0.005252187499999998f, 0.006046617599999999f, 0.006934395699999999f, 0.0079235168f, 0.0090224199f, + 0.010240000000000004f, 0.011585620099999996f, 0.013069123199999996f, 0.014700844299999998f, 0.0164916224f, 0.018452812500000006f, 0.020596297600000004f, 0.022934500699999992f, + 0.0254803968f, 0.028247524899999994f, 0.03125f, 0.0345025251f, 0.03802040320000001f, 0.041819549300000015f, 0.04591650240000001f, 0.050328437500000024f, + 0.05507317760000002f, 0.06016920569999998f, 0.06563567679999999f, 0.07149242989999999f, 0.07776f, 0.08445963009999999f, 0.0916132832f, 0.09924365430000001f, + 0.10737418240000002f, 0.11602906250000004f, 0.12523325760000004f, 0.13501251070000003f, 0.14539335680000007f, 0.15640313489999993f, 0.16806999999999994f, 0.18042293509999996f, + 0.19349176319999997f, 0.20730715929999996f, 0.22190066239999998f, 0.2373046875f, 0.2535525376f, 0.2706784157f, 0.2887174368f, 0.3077056399000001f, + 0.32768000000000014f, 0.34867844010000015f, 0.37073984319999986f, 0.3939040642999999f, 0.41821194239999987f, 0.4437053124999999f, 0.47042701759999994f, 0.49842092070000005f, + 0.5277319168f, 0.5584059449000001f, 0.5904900000000002f, 0.6240321451000002f, 0.6590815232000001f, 0.6956883693000001f, 0.7339040223999997f, 0.7737809374999999f, + 0.8153726976f, 0.8587340256999999f, 0.9039207967999998f, 0.9509900498999999f, 1.0f +}; + +constexpr float easeOutQuint[sample_count] = { + 0.0f, 0.04900995010000009f, 0.09607920320000018f, 0.1412659743000001f, 0.18462730240000003f, 0.22621906250000012f, 0.26609597760000026f, 0.3043116307000001f, + 0.34091847679999987f, 0.37596785489999984f, 0.4095099999999998f, 0.44159405509999994f, 0.4722680832f, 0.5015790792999999f, 0.5295729824000001f, 0.5562946875000001f, + 0.5817880576000001f, 0.6060959357000001f, 0.6292601567999999f, 0.6513215598999998f, 0.6723199999999998f, 0.6922943600999999f, 0.7112825631999999f, 0.7293215843f, + 0.7464474623999999f, 0.7626953125f, 0.7780993376f, 0.7926928407f, 0.8065082368000001f, 0.8195770649f, 0.8319300000000001f, 0.8435968651000001f, + 0.8546066432f, 0.8649874893f, 0.8747667424000001f, 0.8839709375f, 0.8926258175999999f, 0.9007563457f, 0.9083867167999999f, 0.9155403699f, + 0.92224f, 0.9285075701f, 0.9343643232f, 0.9398307942999999f, 0.9449268224f, 0.9496715625f, 0.9540834976f, 0.9581804507f, + 0.9619795968f, 0.9654974749f, 0.96875f, 0.9717524751f, 0.9745196032f, 0.9770654993f, 0.9794037024f, 0.9815471875f, + 0.9835083776f, 0.9852991557f, 0.9869308768f, 0.9884143799f, 0.98976f, 0.9909775801f, 0.9920764832f, 0.9930656043f, + 0.9939533824f, 0.9947478125f, 0.9954564576f, 0.9960864607f, 0.9966445568f, 0.9971370849f, 0.99757f, 0.9979488851f, + 0.9982789632f, 0.9985651093f, 0.9988118624f, 0.9990234375f, 0.9992037376f, 0.9993563657f, 0.9994846368f, 0.9995915899f, + 0.99968f, 0.9997523901f, 0.9998110432f, 0.9998580143f, 0.9998951424f, 0.9999240625f, 0.9999462176f, 0.9999628707f, + 0.9999751168f, 0.9999838949f, 0.99999f, 0.9999940951f, 0.9999967232f, 0.9999983193f, 0.9999992224f, 0.9999996875f, + 0.9999998976f, 0.9999999757f, 0.9999999968f, 0.9999999999f, 1.0f +}; + +constexpr float easeInOutQuint[sample_count] = { + 0.0f, 1.6000000000000003e-9f, 5.120000000000001e-8f, 3.888e-7f, 0.0000016384000000000003f, 0.000005000000000000002f, 0.0000124416f, 0.00002689120000000001f, + 0.00005242880000000001f, 0.00009447839999999999f, 0.00016000000000000007f, 0.0002576816f, 0.0003981312f, 0.0005940688000000002f, 0.0008605184000000004f, 0.001215f, + 0.0016777216000000003f, 0.002271771200000001f, 0.0030233087999999996f, 0.0039617584f, 0.005120000000000002f, 0.006534561599999998f, 0.0082458112f, 0.010298148800000002f, + 0.0127401984f, 0.015625f, 0.019010201600000005f, 0.022958251200000005f, 0.02753658880000001f, 0.03281783839999999f, 0.03888f, 0.0458066416f, + 0.05368709120000001f, 0.06261662880000002f, 0.07269667840000003f, 0.08403499999999997f, 0.09674588159999999f, 0.11095033119999999f, 0.1267762688f, 0.1443587184f, + 0.16384000000000007f, 0.18536992159999993f, 0.20910597119999993f, 0.23521350879999997f, 0.2638659584f, 0.2952450000000001f, 0.32954076160000007f, 0.3669520111999999f, + 0.4076863488f, 0.4519603983999999f, 0.5f, 0.5480396016000001f, 0.5923136512f, 0.6330479888000001f, 0.6704592384000001f, 0.7047550000000002f, + 0.7361340416000002f, 0.7647864911999999f, 0.7908940287999999f, 0.8146300784f, 0.8361599999999999f, 0.8556412816f, 0.8732237312f, 0.8890496688f, + 0.9032541184f, 0.915965f, 0.9273033216000001f, 0.9373833712f, 0.9463129088000001f, 0.9541933584f, 0.96112f, 0.9671821616f, + 0.9724634112f, 0.9770417487999999f, 0.9809897984f, 0.984375f, 0.9872598016f, 0.9897018512f, 0.9917541888f, 0.9934654384f, + 0.99488f, 0.9960382416f, 0.9969766912f, 0.9977282288f, 0.9983222784f, 0.998785f, 0.9991394816f, 0.9994059312f, + 0.9996018688f, 0.9997423184f, 0.99984f, 0.9999055216f, 0.9999475712f, 0.9999731088f, 0.9999875584f, 0.999995f, + 0.9999983616f, 0.9999996112f, 0.9999999488f, 0.9999999984f, 1.0f +}; + +constexpr float easeInSine[sample_count] = { + 0.0f, 0.00012336751833941229f, 0.0004934396342684f, 0.0011101250380299854f, 0.001973271571728441f, 0.003082666266872036f, 0.004438035396920004f, 0.006039044544820293f, + 0.007885298685522124f, 0.009976342283442463f, 0.01231165940486223f, 0.014890673845226132f, 0.01771274927131128f, 0.02077718937823425f, 0.024083238061252565f, 0.027630079602323443f, + 0.031416838871368924f, 0.03544258154220192f, 0.039706314323056935f, 0.04420698520166988f, 0.04894348370484647f, 0.0539146411724547f, 0.059119231045774545f, 0.06455596917013273f, + 0.07022351411174854f, 0.07612046748871326f, 0.08224537431601886f, 0.08859672336455471f, 0.09517294753398053f, 0.10197242423938435f, 0.1089934758116321f, 0.11623436991130653f, + 0.12369331995613642f, 0.1313684855618088f, 0.13925797299605636f, 0.1473598356459077f, 0.15567207449798492f, 0.16419263863172973f, 0.17291942572543817f, 0.1818502825749766f, + 0.19098300562505255f, 0.20031534151290942f, 0.2098449876243096f, 0.2195695926616702f, 0.22948675722421075f, 0.23959403439996907f, 0.24988893036954052f, 0.2603689050213902f, + 0.27103137257858845f, 0.2818737022368111f, 0.2928932188134524f, 0.3040872034076857f, 0.3154528940713113f, 0.3269874864902267f, 0.3386881346763482f, 0.35055195166981645f, + 0.36257601025131037f, 0.3747573436642947f, 0.3870929463470234f, 0.3995797746741159f, 0.41221474770752686f, 0.42499474795672143f, 0.4379166221478694f, 0.4509771820018682f, + 0.46417320502100345f, 0.4775014352840511f, 0.4909585842496288f, 0.5045413315675924f, 0.5182463258982848f, 0.5320701857394265f, 0.5460095002604531f, 0.5600608301440848f, + 0.5742207084349273f, 0.5884856413948911f, 0.6028521093652195f, 0.6173165676349102f, 0.6318754473153219f, 0.6465251562207428f, 0.6612620797547085f, 0.6760825818018505f, + 0.6909830056250525f, 0.7059596747676962f, 0.7210088939607705f, 0.736126950034627f, 0.751310112835145f, 0.7665546361440945f, 0.7818567586034573f, 0.7972127046434875f, + 0.8126186854142753f, 0.8280708997205904f, 0.843565534959769f, 0.8590987680624174f, 0.8746667664356957f, 0.8902656889089549f, 0.9058916866814855f, 0.921540904272155f, + 0.9372094804706865f, 0.9528935492903573f, 0.9685892409218716f, 0.9842926826881794f, 0.9999999999999999f +}; + +constexpr float easeOutSine[sample_count] = { + 0.0f, 0.015707317311820675f, 0.03141075907812829f, 0.04710645070964266f, 0.06279051952931337f, 0.07845909572784494f, 0.09410831331851431f, 0.10973431109104528f, + 0.12533323356430426f, 0.14090123193758267f, 0.15643446504023087f, 0.17192910027940952f, 0.1873813145857246f, 0.2027872953565125f, 0.21814324139654256f, 0.2334453638559054f, + 0.2486898871648548f, 0.2638730499653729f, 0.2789911060392293f, 0.29404032523230395f, 0.3090169943749474f, 0.32391741819814934f, 0.33873792024529137f, 0.35347484377925714f, + 0.3681245526846779f, 0.3826834323650898f, 0.3971478906347806f, 0.4115143586051088f, 0.4257792915650727f, 0.4399391698559151f, 0.45399049973954675f, 0.4679298142605734f, + 0.4817536741017153f, 0.4954586684324076f, 0.5090414157503713f, 0.5224985647159488f, 0.5358267949789967f, 0.5490228179981318f, 0.5620833778521306f, 0.5750052520432786f, + 0.5877852522924731f, 0.6004202253258839f, 0.6129070536529764f, 0.6252426563357051f, 0.6374239897486896f, 0.6494480483301837f, 0.6613118653236518f, 0.6730125135097733f, + 0.6845471059286886f, 0.6959127965923143f, 0.7071067811865475f, 0.7181262977631888f, 0.7289686274214116f, 0.7396310949786097f, 0.7501110696304596f, 0.760405965600031f, + 0.7705132427757893f, 0.7804304073383297f, 0.7901550123756903f, 0.7996846584870905f, 0.8090169943749475f, 0.8181497174250234f, 0.8270805742745618f, 0.8358073613682702f, + 0.8443279255020151f, 0.8526401643540922f, 0.8607420270039436f, 0.8686315144381913f, 0.8763066800438637f, 0.8837656300886934f, 0.8910065241883678f, 0.8980275757606155f, + 0.9048270524660196f, 0.9114032766354452f, 0.9177546256839811f, 0.9238795325112867f, 0.9297764858882513f, 0.9354440308298673f, 0.9408807689542255f, 0.9460853588275453f, + 0.9510565162951535f, 0.9557930147983301f, 0.960293685676943f, 0.9645574184577981f, 0.9685831611286311f, 0.9723699203976766f, 0.9759167619387473f, 0.9792228106217657f, + 0.9822872507286886f, 0.9851093261547739f, 0.9876883405951378f, 0.9900236577165575f, 0.9921147013144779f, 0.9939609554551797f, 0.99556196460308f, 0.996917333733128f, + 0.9980267284282716f, 0.99888987496197f, 0.9995065603657316f, 0.9998766324816606f, 1.0f +}; + +constexpr float easeInOutSine[sample_count] = { + 0.0f, 0.0002467198171342f, 0.0009866357858642205f, 0.002219017698460002f, 0.003942649342761062f, 0.006155829702431115f, 0.00885637463565564f, 0.012041619030626283f, + 0.015708419435684462f, 0.019853157161528467f, 0.024471741852423234f, 0.029559615522887273f, 0.03511175705587427f, 0.04112268715800943f, 0.04758647376699021f, 0.05449673790581605f, + 0.06184665997806821f, 0.06962898649802818f, 0.07783603724899246f, 0.08645971286271908f, 0.09549150281252627f, 0.1049224938121548f, 0.11474337861210537f, 0.1249444651847702f, + 0.13551568628929422f, 0.1464466094067262f, 0.15772644703565564f, 0.16934406733817403f, 0.18128800512565513f, 0.19354647317351176f, 0.20610737385376338f, 0.21895831107393465f, + 0.23208660251050173f, 0.2454792921248144f, 0.2591231629491424f, 0.27300475013022657f, 0.28711035421746367f, 0.30142605468260975f, 0.31593772365766093f, 0.33063103987735426f, + 0.3454915028125263f, 0.36050444698038525f, 0.37565505641757263f, 0.39092837930172863f, 0.40630934270713764f, 0.4217827674798845f, 0.43733338321784776f, 0.45294584334074284f, + 0.46860474023534326f, 0.4842946204609358f, 0.49999999999999994f, 0.5157053795390641f, 0.5313952597646567f, 0.5470541566592572f, 0.5626666167821521f, 0.5782172325201154f, + 0.5936906572928623f, 0.6090716206982711f, 0.6243449435824274f, 0.6394955530196146f, 0.6545084971874735f, 0.6693689601226457f, 0.6840622763423388f, 0.6985739453173904f, + 0.7128896457825363f, 0.7269952498697734f, 0.7408768370508577f, 0.7545207078751857f, 0.7679133974894985f, 0.7810416889260654f, 0.7938926261462365f, 0.8064535268264883f, + 0.8187119948743449f, 0.8306559326618259f, 0.8422735529643444f, 0.8535533905932737f, 0.8644843137107057f, 0.8750555348152298f, 0.8852566213878945f, 0.8950775061878452f, + 0.9045084971874737f, 0.9135402871372809f, 0.9221639627510074f, 0.9303710135019718f, 0.9381533400219317f, 0.9455032620941839f, 0.9524135262330097f, 0.9588773128419905f, + 0.9648882429441257f, 0.9704403844771128f, 0.9755282581475768f, 0.9801468428384714f, 0.9842915805643155f, 0.9879583809693737f, 0.9911436253643444f, 0.9938441702975689f, + 0.9960573506572389f, 0.99778098230154f, 0.9990133642141358f, 0.9997532801828658f, 1.0f +}; + +constexpr float easeInExpo[sample_count] = { + 0.0f, 0.0010466537720080985f, 0.0011217757373017914f, 0.0012022894661571455f, 0.001288581944114155f, 0.0013810679320049757f, 0.001480191959482812f, 0.0015864304616332737f, + 0.0017002940689377411f, 0.0018223300615953274f, 0.001953125f, 0.002093307544016197f, 0.002243551474603583f, 0.002404578932314291f, 0.00257716388822831f, 0.0027621358640099515f, + 0.0029603839189656206f, 0.0031728609232665474f, 0.0034005881378754823f, 0.0036446601231906505f, 0.00390625f, 0.004186615088032394f, 0.004487102949207166f, 0.0048091578646285785f, + 0.00515432777645662f, 0.005524271728019903f, 0.005920767837931241f, 0.0063457218465330905f, 0.006801176275750973f, 0.00728932024638131f, 0.0078125f, 0.008373230176064794f, + 0.008974205898414342f, 0.009618315729257164f, 0.01030865555291324f, 0.011048543456039806f, 0.011841535675862483f, 0.012691443693066181f, 0.013602352551501938f, 0.01457864049276262f, + 0.015625f, 0.016746460352129577f, 0.017948411796828663f, 0.019236631458514303f, 0.020617311105826465f, 0.02209708691207961f, 0.023683071351724965f, 0.025382887386132348f, + 0.027204705103003875f, 0.02915728098552524f, 0.03125f, 0.03349292070425915f, 0.03589682359365735f, 0.038473262917028656f, 0.04123462221165296f, 0.04419417382415922f, + 0.04736614270344996f, 0.050765774772264696f, 0.05440941020600775f, 0.05831456197105044f, 0.0625f, 0.0669858414085183f, 0.0717936471873147f, 0.07694652583405726f, + 0.08246924442330592f, 0.08838834764831845f, 0.09473228540689992f, 0.10153154954452945f, 0.10881882041201557f, 0.11662912394210088f, 0.12499999999999996f, 0.1339716828170366f, + 0.14358729437462936f, 0.1538930516681145f, 0.16493848884661177f, 0.1767766952966369f, 0.18946457081379978f, 0.2030630990890589f, 0.2176376408240311f, 0.2332582478842019f, + 0.25000000000000006f, 0.2679433656340734f, 0.28717458874925866f, 0.30778610333622897f, 0.3298769776932235f, 0.35355339059327373f, 0.3789291416275995f, 0.40612619817811774f, + 0.43527528164806206f, 0.46651649576840376f, 0.5000000000000001f, 0.5358867312681467f, 0.5743491774985177f, 0.6155722066724584f, 0.6597539553864469f, 0.7071067811865474f, + 0.7578582832551989f, 0.8122523963562354f, 0.870550563296124f, 0.9330329915368074f, 1.0f +}; + +constexpr float easeOutExpo[sample_count] = { + 0.0f, 0.06696700846319259f, 0.12944943670387588f, 0.18774760364376442f, 0.242141716744801f, 0.2928932188134524f, 0.3402460446135529f, 0.38442779332754184f, + 0.42565082250148256f, 0.46411326873185343f, 0.5f, 0.5334835042315963f, 0.5647247183519379f, 0.5938738018218823f, 0.6210708583724005f, 0.6464466094067263f, + 0.6701230223067765f, 0.692213896663771f, 0.7128254112507413f, 0.7320566343659267f, 0.75f, 0.7667417521157982f, 0.782362359175969f, 0.796936900910941f, + 0.8105354291862003f, 0.8232233047033631f, 0.8350615111533882f, 0.8461069483318855f, 0.8564127056253706f, 0.8660283171829634f, 0.875f, 0.8833708760578991f, + 0.8911811795879845f, 0.8984684504554705f, 0.9052677145931001f, 0.9116116523516815f, 0.9175307555766941f, 0.9230534741659427f, 0.9282063528126853f, 0.9330141585914817f, + 0.9375f, 0.9416854380289496f, 0.9455905897939922f, 0.9492342252277353f, 0.95263385729655f, 0.9558058261758408f, 0.958765377788347f, 0.9615267370829714f, + 0.9641031764063427f, 0.9665070792957409f, 0.96875f, 0.9708427190144747f, 0.9727952948969961f, 0.9746171126138676f, 0.976316928648275f, 0.9779029130879204f, + 0.9793826888941736f, 0.9807633685414857f, 0.9820515882031713f, 0.9832535396478704f, 0.984375f, 0.9854213595072374f, 0.9863976474484981f, 0.9873085563069338f, + 0.9881584643241376f, 0.9889514565439602f, 0.9896913444470867f, 0.9903816842707428f, 0.9910257941015856f, 0.9916267698239352f, 0.9921875f, 0.9927106797536187f, + 0.9931988237242491f, 0.993654278153467f, 0.9940792321620687f, 0.99447572827198f, 0.9948456722235434f, 0.9951908421353715f, 0.9955128970507928f, 0.9958133849119676f, + 0.99609375f, 0.9963553398768094f, 0.9965994118621245f, 0.9968271390767335f, 0.9970396160810344f, 0.99723786413599f, 0.9974228361117717f, 0.9975954210676857f, + 0.9977564485253965f, 0.9979066924559838f, 0.998046875f, 0.9981776699384046f, 0.9982997059310622f, 0.9984135695383667f, 0.9985198080405172f, 0.998618932067995f, + 0.9987114180558858f, 0.9987977105338428f, 0.9988782242626982f, 0.9989533462279919f, 1.0f +}; + +constexpr float easeInOutExpo[sample_count] = { + 0.0f, 0.0005608878686508957f, 0.0006442909720570775f, 0.000740095979741406f, 0.0008501470344688706f, 0.0009765625f, 0.0011217757373017914f, 0.001288581944114155f, + 0.0014801919594828103f, 0.0017002940689377411f, 0.001953125f, 0.002243551474603583f, 0.00257716388822831f, 0.0029603839189656206f, 0.0034005881378754866f, 0.00390625f, + 0.004487102949207171f, 0.00515432777645662f, 0.005920767837931241f, 0.006801176275750969f, 0.0078125f, 0.008974205898414332f, 0.010308655552913232f, 0.011841535675862483f, + 0.013602352551501938f, 0.015625f, 0.017948411796828673f, 0.02061731110582648f, 0.02368307135172498f, 0.027204705103003875f, 0.03125f, 0.03589682359365735f, + 0.04123462221165296f, 0.04736614270344996f, 0.054409410206007786f, 0.06249999999999998f, 0.07179364718731468f, 0.08246924442330589f, 0.09473228540689989f, 0.10881882041201554f, + 0.12500000000000003f, 0.14358729437462933f, 0.16493848884661175f, 0.18946457081379975f, 0.21763764082403103f, 0.25000000000000006f, 0.28717458874925883f, 0.32987697769322344f, + 0.37892914162759944f, 0.435275281648062f, 0.5f, 0.5647247183519379f, 0.6210708583724005f, 0.6701230223067766f, 0.7128254112507414f, 0.7500000000000002f, + 0.7823623591759692f, 0.8105354291862001f, 0.8350615111533881f, 0.8564127056253705f, 0.875f, 0.8911811795879845f, 0.9052677145931001f, 0.9175307555766941f, + 0.9282063528126854f, 0.9375f, 0.9455905897939922f, 0.95263385729655f, 0.9587653777883471f, 0.9641031764063426f, 0.96875f, 0.9727952948969961f, + 0.976316928648275f, 0.9793826888941736f, 0.9820515882031713f, 0.984375f, 0.9863976474484981f, 0.9881584643241376f, 0.9896913444470867f, 0.9910257941015856f, + 0.9921875f, 0.9931988237242491f, 0.9940792321620687f, 0.9948456722235434f, 0.9955128970507928f, 0.99609375f, 0.9965994118621245f, 0.9970396160810344f, + 0.9974228361117717f, 0.9977564485253965f, 0.998046875f, 0.9982997059310622f, 0.9985198080405172f, 0.9987114180558858f, 0.9988782242626982f, 0.9990234375f, + 0.9991498529655312f, 0.9992599040202585f, 0.9993557090279429f, 0.9994391121313491f, 1.0f +}; + +constexpr float easeInCirc[sample_count] = { + 0.0f, 0.00005000125006249245f, 0.00020002000400098918f, 0.0004501012955882011f, 0.000800320256256315f, 0.0012507822280910519f, 0.0018016229225775726f, 0.0024530086256587813f, + 0.003205136449831003f, 0.004058234634172986f, 0.005012562893380035f, 0.006068412817059166f, 0.00722610832073145f, 0.008486006150190573f, 0.009848496441074883f, 0.011314003335740508f, + 0.012882985659754653f, 0.01455593766058949f, 0.016333389811365007f, 0.018215909682785747f, 0.020204102886728803f, 0.02229861409528522f, 0.02450012813942415f, 0.026809371191851228f, + 0.029227112039072245f, 0.031754163448145745f, 0.034391383634135075f, 0.03713967783483774f, 0.040000000000000036f, 0.04297335460291385f, 0.04606079858305434f, 0.049263443429253595f, + 0.052582457413839157f, 0.05601906798918865f, 0.0595745643593002f, 0.06325030024024025f, 0.06704769682475187f, 0.07096824596787865f, 0.07501351361222575f, 0.07918514347345595f, + 0.08348486100883201f, 0.08791447769411442f, 0.09247589563692582f, 0.09717111255786681f, 0.10200222717425411f, 0.10697144502541245f, 0.11208108478307544f, 0.11733358509570557f, + 0.12273151202154764f, 0.1282775671120996f, 0.1339745962155614f, 0.13982559907888448f, 0.14583373983749504f, 0.1520023584938458f, 0.15833498349996755f, 0.16483534557549673f, + 0.17150739291168093f, 0.17835530793414112f, 0.18538352582334794f, 0.19259675502262186f, 0.19999999999999996f, 0.20759858657369878f, 0.2153981901626788f, 0.2234048673858431f, + 0.2316250915080581f, 0.24006579232146685f, 0.248734401160282f, 0.25763890188130145f, 0.2667878888070657f, 0.2761906328320971f, 0.285857157145715f, 0.29579832434166986f, + 0.3060259370841011f, 0.3165528550063289f, 0.3273931311679905f, 0.3385621722338523f, 0.3500769276291231f, 0.3619561143620291f, 0.37422048611351943f, 0.3868931577612268f, + 0.40000000000000013f, 0.4135701235441701f, 0.4276364791498325f, 0.4422366093046264f, 0.45741360134997844f, 0.473217312357363f, 0.489705967113077f, 0.506948278575158f, + 0.5250263165184833f, 0.5440394753928801f, 0.5641101056459328f, 0.5853917511674425f, 0.6080816411546915f, 0.632440481010218f, 0.6588255578153603f, 0.6877501000800801f, + 0.72f, 0.7568950843771356f, 0.8010025125786758f, 0.858932640203341f, 1.0f +}; + +constexpr float easeOutCirc[sample_count] = { + 0.0f, 0.14106735979665894f, 0.1989974874213242f, 0.24310491562286443f, 0.28f, 0.31224989991991997f, 0.3411744421846397f, 0.3675595189897823f, + 0.39191835884530846f, 0.41460824883255754f, 0.4358898943540673f, 0.4559605246071199f, 0.4749736834815167f, 0.493051721424842f, 0.510294032886923f, 0.526782687642637f, + 0.5425863986500216f, 0.5577633906953736f, 0.5723635208501673f, 0.5864298764558299f, 0.5999999999999999f, 0.6131068422387732f, 0.6257795138864806f, 0.6380438856379709f, + 0.6499230723708769f, 0.6614378277661477f, 0.6726068688320095f, 0.6834471449936711f, 0.6939740629158989f, 0.7042016756583301f, 0.714142842854285f, 0.7238093671679029f, + 0.7332121111929345f, 0.7423610981186987f, 0.751265598839718f, 0.7599342076785331f, 0.7683749084919419f, 0.7765951326141569f, 0.7846018098373212f, 0.7924014134263012f, + 0.8f, 0.807403244977378f, 0.8146164741766521f, 0.8216446920658588f, 0.8284926070883191f, 0.8351646544245033f, 0.8416650165000324f, 0.8479976415061542f, + 0.854166260162505f, 0.8601744009211155f, 0.8660254037844386f, 0.8717224328879004f, 0.8772684879784524f, 0.8826664149042944f, 0.8879189152169246f, 0.8930285549745877f, + 0.8979977728257459f, 0.9028288874421332f, 0.9075241043630742f, 0.9120855223058855f, 0.916515138991168f, 0.920814856526544f, 0.9249864863877743f, 0.9290317540321213f, + 0.9329523031752481f, 0.9367496997597597f, 0.9404254356406998f, 0.9439809320108113f, 0.947417542586161f, 0.9507365565707463f, 0.9539392014169457f, 0.957026645397086f, + 0.96f, 0.9628603221651623f, 0.9656086163658649f, 0.9682458365518543f, 0.9707728879609278f, 0.9731906288081488f, 0.9754998718605759f, 0.9777013859047148f, + 0.9797958971132712f, 0.9817840903172143f, 0.983666610188635f, 0.9854440623394105f, 0.9871170143402452f, 0.9886859966642595f, 0.9901515035589251f, 0.9915139938498094f, + 0.9927738916792685f, 0.9939315871829408f, 0.99498743710662f, 0.995941765365827f, 0.996794863550169f, 0.9975469913743412f, 0.9981983770774224f, 0.998749217771909f, + 0.9991996797437437f, 0.9995498987044118f, 0.999799979995999f, 0.9999499987499375f, 1.0f +}; + +constexpr float easeInOutCirc[sample_count] = { + 0.0f, 0.00010001000200049459f, 0.0004001601281281575f, 0.0009008114612887863f, 0.0016025682249155015f, 0.0025062814466900174f, 0.003613054160365725f, 0.004924248220537442f, + 0.006441492829877327f, 0.008166694905682503f, 0.010102051443364402f, 0.012250064069712074f, 0.014613556019536122f, 0.017195691817067538f, 0.020000000000000018f, 0.02303039929152717f, + 0.026291228706919578f, 0.0297872821796501f, 0.033523848412375934f, 0.03750675680611287f, 0.041742430504416006f, 0.04623794781846291f, 0.051001113587127056f, 0.05604054239153772f, + 0.06136575601077382f, 0.0669872981077807f, 0.07291686991874752f, 0.07916749174998378f, 0.08575369645584047f, 0.09269176291167397f, 0.09999999999999998f, 0.1076990950813394f, + 0.11581254575402905f, 0.124367200580141f, 0.13339394440353286f, 0.1429285785728575f, 0.15301296854205054f, 0.16369656558399526f, 0.17503846381456156f, 0.18711024305675972f, + 0.20000000000000007f, 0.21381823957491625f, 0.22870680067498922f, 0.2448529835565385f, 0.26251315825924165f, 0.2820550528229664f, 0.30404082057734577f, 0.32941277890768017f, + 0.36f, 0.4005012562893379f, 0.5f, 0.5994987437106621f, 0.64f, 0.6705872210923198f, 0.6959591794226544f, 0.7179449471770338f, + 0.7374868417407584f, 0.7551470164434614f, 0.7712931993250107f, 0.7861817604250836f, 0.7999999999999999f, 0.8128897569432403f, 0.8249615361854384f, 0.8363034344160047f, + 0.8469870314579495f, 0.8570714214271424f, 0.8666060555964672f, 0.8756327994198589f, 0.8841874542459709f, 0.8923009049186605f, 0.8999999999999999f, 0.9073082370883261f, + 0.9142463035441595f, 0.9208325082500162f, 0.9270831300812525f, 0.9330127018922193f, 0.9386342439892261f, 0.9439594576084622f, 0.9489988864128729f, 0.9537620521815371f, + 0.958257569495584f, 0.9624932431938871f, 0.966476151587624f, 0.9702127178203499f, 0.9737087712930804f, 0.9769696007084728f, 0.98f, 0.9828043081829325f, + 0.9853864439804638f, 0.9877499359302879f, 0.9898979485566356f, 0.9918333050943176f, 0.9935585071701227f, 0.9950757517794626f, 0.9963869458396342f, 0.9974937185533099f, + 0.9983974317750846f, 0.9990991885387113f, 0.9995998398718718f, 0.9998999899979994f, 1.0f +}; + +constexpr float easeInElastic[sample_count] = { + 0.0f, -0.000323433802777145f, -0.0001172574939510949f, 0.00012567347030029136f, 0.00039819371937598584f, 0.0006905339660024882f, 0.000990441743376242f, 0.0012834492038554105f, + 0.0015532959233347289f, 0.0017825077774945583f, 0.001953125f, 0.002047563751777418f, 0.0020495862586114607f, 0.0019453452205582292f, 0.0017244592352163612f, 0.0013810679320049725f, + 0.0009148089408346769f, 0.00033165427647103993f, -0.00035545825225833993f, -0.0011262619167865967f, -0.0019531250000000126f, -0.0028013922924462645f, -0.00363014254141855f, -0.0043933843223176285f, + -0.005041693347936652f, -0.005524271728019903f, -0.005791384855174325f, -0.005797105368363719f, -0.0055022671888222555f, -0.004877507276405027f, -0.0039062499999999918f, -0.0025874704222171633f, + -0.0009380599516087465f, 0.0010053877624023625f, 0.003185549755007902f, 0.00552427172801992f, 0.00792353394700991f, 0.01026759363084329f, 0.012426367386677838f, 0.01426006221995646f, + 0.015625f, 0.01638051001421935f, 0.016396690068891703f, 0.015562761764465798f, 0.013795673881730912f, 0.011048543456039818f, 0.007318471526677461f, 0.002653234211768322f, + -0.0028436660180667147f, -0.009010095334292827f, -0.015624999999999997f, -0.022411138339570112f, -0.029041140331348376f, -0.035147074578541077f, -0.04033354678349324f, -0.04419417382415922f, + -0.04633107884139462f, -0.04637684294690977f, -0.04401813751057797f, -0.039020058211240126f, -0.03125000000000004f, -0.0206997633777372f, -0.007504479612870113f, 0.008043102099218739f, + 0.025484398040062917f, 0.0441941738241592f, 0.06338827157607929f, 0.0821407490467464f, 0.09941093909342275f, 0.11408049775965161f, 0.12499999999999996f, 0.13104408011375476f, + 0.13117352055113363f, 0.12450209411572646f, 0.11036539105384705f, 0.08838834764831832f, 0.058547772213419745f, 0.02122587369414646f, -0.022749328144533863f, -0.07208076267434256f, + -0.12500000000000014f, -0.17928910671656112f, -0.23232912265078673f, -0.2811765966283281f, -0.32266837426794565f, -0.35355339059327373f, -0.37064863073115684f, -0.371014743575278f, + -0.35214510008462385f, -0.31216046568992173f, -0.24999999999999992f, -0.1655981070218973f, -0.06003583690296029f, 0.06434481679375144f, 0.20387518432050222f, 0.35355339059327334f, + 0.5071061726086341f, 0.6571259923739698f, 0.7952875127473815f, 0.9126439820772134f, 1.0f +}; + +constexpr float easeOutElastic[sample_count] = { + 0.0f, 0.08735601792278658f, 0.20471248725261837f, 0.3428740076260298f, 0.49289382739136534f, 0.6464466094067263f, 0.796124815679497f, 0.9356551832062496f, + 1.0600358369029608f, 1.1655981070218977f, 1.25f, 1.3121604656899217f, 1.3521451000846239f, 1.3710147435752782f, 1.370648630731157f, 1.3535533905932737f, + 1.3226683742679457f, 1.2811765966283282f, 1.2323291226507869f, 1.1792891067165607f, 1.125f, 1.0720807626743425f, 1.0227493281445335f, 0.9787741263058535f, + 0.9414522277865802f, 0.9116116523516816f, 0.8896346089461529f, 0.8754979058842736f, 0.8688264794488664f, 0.8689559198862452f, 0.875f, 0.8859195022403483f, + 0.9005890609065773f, 0.9178592509532537f, 0.9366117284239207f, 0.9558058261758408f, 0.9745156019599371f, 0.9919568979007812f, 1.00750447961287f, 1.0206997633777373f, + 1.03125f, 1.03902005821124f, 1.044018137510578f, 1.0463768429469098f, 1.0463310788413946f, 1.0441941738241591f, 1.040333546783493f, 1.035147074578541f, + 1.0290411403313484f, 1.02241113833957f, 1.015625f, 1.0090100953342929f, 1.0028436660180666f, 0.9973467657882317f, 0.9926815284733226f, 0.9889514565439601f, + 0.9862043261182691f, 0.9844372382355342f, 0.9836033099311083f, 0.9836194899857806f, 0.984375f, 0.9857399377800435f, 0.9875736326133222f, 0.9897324063691567f, + 0.99207646605299f, 0.99447572827198f, 0.9968144502449922f, 0.9989946122375977f, 1.0009380599516087f, 1.002587470422217f, 1.00390625f, 1.0048775072764051f, + 1.0055022671888223f, 1.0057971053683636f, 1.0057913848551743f, 1.00552427172802f, 1.0050416933479367f, 1.0043933843223176f, 1.0036301425414185f, 1.0028013922924464f, + 1.001953125f, 1.0011262619167867f, 1.0003554582522582f, 0.9996683457235289f, 0.9990851910591653f, 0.998618932067995f, 0.9982755407647836f, 0.9980546547794418f, + 0.9979504137413885f, 0.9979524362482226f, 0.998046875f, 0.9982174922225054f, 0.9984467040766652f, 0.9987165507961446f, 0.9990095582566237f, 0.9993094660339975f, + 0.999601806280624f, 0.9998743265296997f, 1.0001172574939512f, 1.000323433802777f, 1.0f +}; + +constexpr float easeInOutElastic[sample_count] = { + 0.0f, 0.00024587705820057406f, 0.00043111480880408926f, 0.0006276369865927234f, 0.0008172137801504786f, 0.0009765625f, 0.0010783200477011783f, 0.0010927794644475567f, + 0.0009904417433762394f, 0.0007453598608789065f, 0.00033915659700572883f, -0.00023451498790218883f, -0.0009654225848437084f, -0.001822594331080728f, -0.002751133594411123f, -0.0036706742999449535f, + -0.004476172591943908f, -0.0050416933479366524f, -0.005227727710377947f, -0.004892356784266678f, -0.003906250000000005f, -0.0021710569024461176f, 0.0003597668904761134f, 0.00365923576333874f, + 0.007606339017307079f, 0.011969444423734025f, 0.016396690068891703f, 0.020416664851410915f, 0.02345258932934151f, 0.024852734773355697f, 0.023938888847468073f, 0.020073249010714462f, + 0.012742199020031505f, 0.0016530545410892961f, -0.013162827655497365f, -0.03124999999999997f, -0.0516440278333065f, -0.0728160208484524f, -0.09266215768278922f, -0.10855374323470665f, + -0.11746157759823855f, -0.11616456132539349f, -0.10154627338128966f, -0.07097467744556543f, -0.022749328144533492f, 0.04341204441673269f, 0.12588905379869447f, 0.22073078210769398f, + 0.3213501371354743f, 0.41841345543704545f, 0.5f, 0.5815865445629546f, 0.6786498628645257f, 0.7792692178923061f, 0.8741109462013061f, 0.9565879555832678f, + 1.022749328144534f, 1.0709746774455653f, 1.1015462733812895f, 1.1161645613253934f, 1.1174615775982386f, 1.1085537432347066f, 1.0926621576827893f, 1.0728160208484523f, + 1.0516440278333063f, 1.03125f, 1.0131628276554971f, 0.9983469454589107f, 0.9872578009799685f, 0.9799267509892856f, 0.9760611111525319f, 0.9751472652266443f, + 0.9765474106706585f, 0.9795833351485891f, 0.9836033099311083f, 0.988030555576266f, 0.9923936609826929f, 0.9963407642366613f, 0.9996402331095239f, 1.0021710569024462f, + 1.00390625f, 1.0048923567842667f, 1.005227727710378f, 1.0050416933479367f, 1.004476172591944f, 1.003670674299945f, 1.002751133594411f, 1.0018225943310808f, + 1.0009654225848437f, 1.0002345149879022f, 0.9996608434029943f, 0.9992546401391211f, 0.9990095582566237f, 0.9989072205355525f, 0.9989216799522989f, 0.9990234375f, + 0.9991827862198496f, 0.9993723630134073f, 0.9995688851911959f, 0.9997541229417994f, 1.0f +}; + +constexpr float easeInBack[sample_count] = { + 0.0f, -0.00016745642000000002f, -0.00065901936f, -0.00145847934f, -0.0025496268800000005f, -0.003916252500000001f, -0.00554214672f, -0.007411100060000002f, + -0.00950690304f, -0.01181334618f, -0.014314220000000002f, -0.01699331502f, -0.019834421760000002f, -0.022821330740000003f, -0.025937832480000006f, -0.029167717500000006f, + -0.03249477632000001f, -0.03590279946000001f, -0.039375577440000004f, -0.042896900780000004f, -0.04645056000000001f, -0.05002034562f, -0.05359004816f, -0.05714345814f, + -0.06066436608000001f, -0.06413656250000001f, -0.06754383792000002f, -0.07086998286000001f, -0.07409878784000001f, -0.07721404338000001f, -0.08019954000000001f, -0.08303906822000001f, + -0.08571641856000002f, -0.08821538154000001f, -0.09051974768000003f, -0.09261330750000002f, -0.09447985152000002f, -0.09610317026000002f, -0.09746705424000002f, -0.09855529398000003f, + -0.09935168000000003f, -0.09984000282000002f, -0.10000405296000002f, -0.09982762094000003f, -0.09929449728000002f, -0.09838847250000003f, -0.09709333712000003f, -0.09539288166000005f, + -0.09327089664000004f, -0.09071117258000004f, -0.08769750000000004f, -0.08421366942000003f, -0.08024347136000003f, -0.07577069634000003f, -0.07077913488000001f, -0.0652525775f, + -0.059174814719999996f, -0.05252963706000011f, -0.045300835040000105f, -0.037472199180000096f, -0.02902752000000009f, -0.019950588020000083f, -0.010225193760000074f, 0.00016487225999993796f, + 0.01123581951999995f, 0.023003857499999964f, 0.03548519567999998f, 0.04869604354f, 0.06265261056000002f, 0.07737110621999979f, 0.09286773999999981f, 0.10915872137999984f, + 0.12626025983999983f, 0.14418856485999984f, 0.16295984591999987f, 0.1825903124999999f, 0.2030961740799999f, 0.22449364013999992f, 0.24679892015999996f, 0.27002822362f, + 0.29419776000000003f, 0.3193237387800001f, 0.34542236943999965f, 0.3725098614599997f, 0.4006024243199997f, 0.42971626749999975f, 0.45986760047999975f, 0.49107263273999985f, + 0.5233475737599999f, 0.5567086330199998f, 0.5911720199999999f, 0.62675394418f, 0.6634706150399999f, 0.7013382420600001f, 0.7403730347199996f, 0.7805912024999996f, + 0.8220089548799997f, 0.8646425013399996f, 0.9085080513599997f, 0.9536218144199997f, 0.9999999999999998f +}; + +constexpr float easeOutBack[sample_count] = { + 2.220446049250313e-16f, 0.046378185580000286f, 0.09149194864000032f, 0.13535749866000035f, 0.1779910451200003f, 0.21940879750000042f, 0.2596269652800004f, 0.2986617579400005f, + 0.33652938496000007f, 0.37324605582000003f, 0.4088279800000001f, 0.44329136698000016f, 0.47665242624000015f, 0.5089273672600001f, 0.5401323995200003f, 0.5702837325000003f, + 0.5993975756800003f, 0.6274901385400002f, 0.65457763056f, 0.6806762612199999f, 0.7058022399999999f, 0.72997177638f, 0.7532010798400001f, 0.77550635986f, + 0.7969038259200001f, 0.8174096875000001f, 0.8370401540800001f, 0.8558114351400001f, 0.8737397401600002f, 0.8908412786200002f, 0.9071322600000002f, 0.9226288937800002f, + 0.9373473894400002f, 0.9513039564600002f, 0.9645148043200003f, 0.9769961425f, 0.98876418048f, 0.99983512774f, 1.01022519376f, 1.0199505880200002f, + 1.02902752f, 1.03747219918f, 1.04530083504f, 1.05252963706f, 1.05917481472f, 1.0652525775f, 1.07077913488f, 1.07577069634f, + 1.08024347136f, 1.08421366942f, 1.0876975f, 1.09071117258f, 1.09327089664f, 1.09539288166f, 1.09709333712f, 1.0983884725f, + 1.09929449728f, 1.09982762094f, 1.10000405296f, 1.09984000282f, 1.09935168f, 1.09855529398f, 1.09746705424f, 1.09610317026f, + 1.09447985152f, 1.0926133075f, 1.09051974768f, 1.08821538154f, 1.08571641856f, 1.08303906822f, 1.08019954f, 1.07721404338f, + 1.0740987878400001f, 1.0708699828600001f, 1.06754383792f, 1.0641365625f, 1.06066436608f, 1.05714345814f, 1.05359004816f, 1.05002034562f, + 1.04645056f, 1.04289690078f, 1.03937557744f, 1.03590279946f, 1.03249477632f, 1.0291677175f, 1.02593783248f, 1.02282133074f, + 1.01983442176f, 1.01699331502f, 1.01431422f, 1.01181334618f, 1.00950690304f, 1.00741110006f, 1.00554214672f, 1.0039162525f, + 1.00254962688f, 1.00145847934f, 1.00065901936f, 1.00016745642f, 1.0f +}; + +constexpr float easeInOutBack[sample_count] = { + 0.0f, -0.000504602262f, -0.001960890496f, -0.004282586874f, -0.0073834135680000005f, -0.011177092750000001f, -0.015577346592f, -0.020497897266f, + -0.025852466944000004f, -0.031554777798f, -0.037518552000000004f, -0.043657511722f, -0.049885379135999997f, -0.05611587641400001f, -0.062262725728000005f, -0.06823964924999999f, + -0.07396036915200001f, -0.07933860760600002f, -0.084288086784f, -0.088722528858f, -0.092555656f, -0.09570119038199999f, -0.098072854176f, -0.099584369554f, + -0.10014945868799999f, -0.09968184375f, -0.09809524691200001f, -0.09530339034599998f, -0.09121999622399998f, -0.08575878671800001f, -0.07883348399999998f, -0.07035781024200004f, + -0.060245487615999994f, -0.04841023829399995f, -0.034765784448000006f, -0.019225848250000063f, -0.0017041518720000168f, 0.017885582514000034f, 0.03962963273599997f, 0.06361427662200003f, + 0.0899257920000001f, 0.11865045669799999f, 0.1498745485439999f, 0.18368434536599998f, 0.22016612499200006f, 0.25940616525f, 0.3014907439680001f, 0.3465061389739999f, + 0.394538628096f, 0.44567448916199986f, 0.5f, 0.5543255108380001f, 0.6054613719040001f, 0.6534938610260002f, 0.6985092560320002f, 0.7405938347500003f, + 0.7798338750080002f, 0.8163156546339998f, 0.8501254514559999f, 0.881349543302f, 0.9100742079999999f, 0.936385723378f, 0.960370367264f, 0.9821144174859999f, + 1.001704151872f, 1.01922584825f, 1.034765784448f, 1.048410238294f, 1.0602454876160001f, 1.070357810242f, 1.078833484f, 1.0857587867179999f, + 1.091219996224f, 1.0953033903459999f, 1.098095246912f, 1.09968184375f, 1.100149458688f, 1.099584369554f, 1.098072854176f, 1.095701190382f, + 1.092555656f, 1.088722528858f, 1.084288086784f, 1.079338607606f, 1.073960369152f, 1.06823964925f, 1.062262725728f, 1.056115876414f, + 1.049885379136f, 1.043657511722f, 1.0375185519999999f, 1.031554777798f, 1.025852466944f, 1.020497897266f, 1.015577346592f, 1.0111770927500001f, + 1.007383413568f, 1.004282586874f, 1.001960890496f, 1.000504602262f, 1.0f +}; + +constexpr float easeInBounce[sample_count] = { + 0.0f, 0.006118750000000062f, 0.010724999999999985f, 0.01381874999999999f, 0.01539999999999997f, 0.015468750000000031f, 0.014024999999999954f, 0.01106874999999996f, + 0.00660000000000005f, 0.0006187500000000012f, 0.01187500000000008f, 0.02349375000000009f, 0.033600000000000074f, 0.04219375000000003f, 0.04927500000000007f, 0.05484375000000008f, + 0.05890000000000006f, 0.06144375000000002f, 0.06247499999999995f, 0.06199374999999996f, 0.06000000000000005f, 0.05649375000000001f, 0.05147499999999994f, 0.04494374999999995f, + 0.03689999999999993f, 0.02734375f, 0.01627499999999993f, 0.00369374999999994f, 0.01959999999999995f, 0.04524375000000003f, 0.06937499999999996f, 0.0919937500000001f, + 0.11309999999999976f, 0.13269374999999983f, 0.15077499999999988f, 0.1673437499999999f, 0.1823999999999999f, 0.19594374999999997f, 0.2079749999999999f, 0.21849374999999993f, + 0.22750000000000004f, 0.23499375f, 0.24097500000000005f, 0.24544374999999996f, 0.24839999999999995f, 0.24984375000000003f, 0.24977499999999997f, 0.24819375f, + 0.24509999999999998f, 0.24049375000000006f, 0.234375f, 0.22674375000000002f, 0.21760000000000002f, 0.20694374999999998f, 0.19477500000000003f, 0.18109375000000005f, + 0.16590000000000005f, 0.14919375f, 0.13097500000000006f, 0.11124375000000009f, 0.09000000000000019f, 0.06724375000000005f, 0.042975000000000096f, 0.01719375000000012f, + 0.01990000000000014f, 0.07359375000000024f, 0.12577499999999997f, 0.17644375f, 0.22560000000000002f, 0.27324375f, 0.3193750000000001f, 0.3639937500000001f, + 0.4070999999999999f, 0.4486937499999999f, 0.48877499999999996f, 0.52734375f, 0.5644f, 0.59994375f, 0.633975f, 0.66649375f, + 0.6975f, 0.72699375f, 0.7549750000000001f, 0.78144375f, 0.8064f, 0.82984375f, 0.851775f, 0.87219375f, + 0.8911f, 0.90849375f, 0.924375f, 0.93874375f, 0.9516f, 0.96294375f, 0.972775f, 0.98109375f, + 0.9879f, 0.99319375f, 0.996975f, 0.99924375f, 1.0f +}; + +constexpr float easeOutBounce[sample_count] = { + 0.0f, 0.00075625f, 0.003025f, 0.00680625f, 0.0121f, 0.018906250000000003f, 0.027225f, 0.037056250000000006f, + 0.0484f, 0.06125624999999999f, 0.07562500000000001f, 0.09150625f, 0.1089f, 0.12780625f, 0.14822500000000002f, 0.17015624999999998f, + 0.1936f, 0.21855625f, 0.24502499999999997f, 0.27300625f, 0.30250000000000005f, 0.33350625f, 0.366025f, 0.40005625000000006f, + 0.4356f, 0.47265625f, 0.511225f, 0.5513062500000001f, 0.5929000000000001f, 0.6360062499999999f, 0.6806249999999999f, 0.72675625f, + 0.7744f, 0.82355625f, 0.874225f, 0.9264062499999998f, 0.9800999999999999f, 0.9828062499999999f, 0.9570249999999999f, 0.93275625f, + 0.9099999999999998f, 0.8887562499999999f, 0.8690249999999999f, 0.85080625f, 0.8341f, 0.81890625f, 0.805225f, 0.79305625f, + 0.7824f, 0.77325625f, 0.765625f, 0.7595062499999999f, 0.7549f, 0.75180625f, 0.750225f, 0.75015625f, + 0.7516f, 0.75455625f, 0.759025f, 0.76500625f, 0.7725f, 0.7815062500000001f, 0.7920250000000001f, 0.80405625f, + 0.8176000000000001f, 0.8326562500000001f, 0.8492250000000001f, 0.8673062500000002f, 0.8869000000000002f, 0.9080062499999999f, 0.930625f, 0.95475625f, + 0.9804f, 0.9963062500000001f, 0.9837250000000001f, 0.97265625f, 0.9631000000000001f, 0.95505625f, 0.9485250000000001f, 0.94350625f, + 0.94f, 0.93800625f, 0.937525f, 0.93855625f, 0.9410999999999999f, 0.9451562499999999f, 0.9507249999999999f, 0.95780625f, + 0.9663999999999999f, 0.9765062499999999f, 0.9881249999999999f, 0.99938125f, 0.9934f, 0.98893125f, 0.985975f, 0.98453125f, + 0.9846f, 0.98618125f, 0.989275f, 0.9938812499999999f, 1.0f +}; + +constexpr float easeInOutBounce[sample_count] = { + 0.0f, 0.005362499999999992f, 0.007699999999999985f, 0.007012499999999977f, 0.003300000000000025f, 0.00593750000000004f, 0.016800000000000037f, 0.024637500000000034f, + 0.02945000000000003f, 0.031237499999999974f, 0.030000000000000027f, 0.02573749999999997f, 0.018449999999999966f, 0.008137499999999964f, 0.009799999999999975f, 0.03468749999999998f, + 0.05654999999999988f, 0.07538749999999994f, 0.09119999999999995f, 0.10398749999999995f, 0.11375000000000002f, 0.12048750000000003f, 0.12419999999999998f, 0.12488749999999998f, + 0.12254999999999999f, 0.1171875f, 0.10880000000000001f, 0.09738750000000002f, 0.08295000000000002f, 0.06548750000000003f, 0.045000000000000095f, 0.021487500000000048f, + 0.00995000000000007f, 0.06288749999999999f, 0.11280000000000001f, 0.15968750000000004f, 0.20354999999999995f, 0.24438749999999998f, 0.2822f, 0.3169875f, + 0.34875f, 0.37748750000000003f, 0.4032f, 0.4258875f, 0.44555f, 0.4621875f, 0.4758f, 0.4863875f, + 0.49395f, 0.4984875f, 0.5f, 0.5015125f, 0.50605f, 0.5136125f, 0.5242f, 0.5378125f, + 0.55445f, 0.5741125f, 0.5968f, 0.6225125f, 0.65125f, 0.6830125f, 0.7178f, 0.7556125f, + 0.7964500000000001f, 0.8403125f, 0.8872f, 0.9371125f, 0.9900499999999999f, 0.9785124999999999f, 0.9549999999999998f, 0.9345125f, + 0.9170499999999999f, 0.9026125f, 0.8912f, 0.8828125f, 0.8774500000000001f, 0.8751125f, 0.8758f, 0.8795124999999999f, + 0.88625f, 0.8960125000000001f, 0.9088f, 0.9246125000000001f, 0.9434500000000001f, 0.9653125f, 0.9902f, 0.9918625000000001f, + 0.98155f, 0.9742625f, 0.97f, 0.9687625f, 0.97055f, 0.9753624999999999f, 0.9832f, 0.9940625f, + 0.9966999999999999f, 0.9929875f, 0.9923f, 0.9946375000000001f, 1.0f +}; + +#endif // TWEENY_TESTS_EASINGS_REFERENCE_VALUES_H diff --git a/src/tests/easings/sinusoidal.cpp b/src/tests/easings/sinusoidal.cpp new file mode 100644 index 0000000..9afa843 --- /dev/null +++ b/src/tests/easings/sinusoidal.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "test-easing.h" + +TEST_CASE("easing::sinusoidalIn matches Penner easeInSine samples", "[easing][sinusoidal]") { + test_easing(tweeny::easing::sinusoidalIn, easeInSine); +} + +TEST_CASE("easing::sinusoidalOut matches Penner easeOutSine samples", "[easing][sinusoidal]") { + test_easing(tweeny::easing::sinusoidalOut, easeOutSine); +} + +TEST_CASE("easing::sinusoidalInOut matches Penner easeInOutSine samples", "[easing][sinusoidal]") { + test_easing(tweeny::easing::sinusoidalInOut, easeInOutSine); +} + +TEST_CASE("tween via(sinusoidalIn) matches Penner easeInSine samples", "[easing][sinusoidal][tween]") { + test_tween_easing(tweeny::easing::sinusoidalIn, easeInSine); +} + +TEST_CASE("tween via(sinusoidalOut) matches Penner easeOutSine samples", "[easing][sinusoidal][tween]") { + test_tween_easing(tweeny::easing::sinusoidalOut, easeOutSine); +} + +TEST_CASE("tween via(sinusoidalInOut) matches Penner easeInOutSine samples", "[easing][sinusoidal][tween]") { + test_tween_easing(tweeny::easing::sinusoidalInOut, easeInOutSine); +} diff --git a/src/tests/easings/stepped.cpp b/src/tests/easings/stepped.cpp new file mode 100644 index 0000000..1f66356 --- /dev/null +++ b/src/tests/easings/stepped.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +TEST_CASE("easing::stepped always returns start", "[easing][stepped]") { + for (int i = 0; i <= 100; ++i) { + const float position = static_cast(i) / 100.f; + REQUIRE(tweeny::easing::stepped.run(position, 0.f, 1.f) == Catch::Approx(0.f).margin(1e-6f)); + REQUIRE(tweeny::easing::stepped.run(position, 10, 20) == 10); + } +} + +TEST_CASE("tween via(stepped) holds start until the end keyframe", "[easing][stepped][tween]") { + auto tween = tweeny::from(0.f).to(1.f).via(tweeny::easing::stepped).during(100U).build(); + + for (uint32_t t = 0; t < 100U; ++t) { + REQUIRE(tween.peek(t) == Catch::Approx(0.f).margin(1e-6f)); + } + + // At the final keyframe position, render returns the keyframe value (not via easing). + REQUIRE(tween.peek(100U) == Catch::Approx(1.f).margin(1e-6f)); +} + +TEST_CASE("tween via(stepped) snaps at each keyframe with no in-between easing", "[easing][stepped][tween]") { + auto tween = tweeny::from(0) + .to(10).via(tweeny::easing::stepped).during(100U) + .to(20).via(tweeny::easing::stepped).during(100U) + .to(30).via(tweeny::easing::stepped).during(100U) + .build(); + + for (uint32_t t = 0; t < 100U; ++t) { + REQUIRE(tween.peek(t) == 0); + } + for (uint32_t t = 100; t < 200U; ++t) { + REQUIRE(tween.peek(t) == 10); + } + for (uint32_t t = 200; t < 300U; ++t) { + REQUIRE(tween.peek(t) == 20); + } + + REQUIRE(tween.peek(100U) == 10); + REQUIRE(tween.peek(200U) == 20); + REQUIRE(tween.peek(300U) == 30); +} diff --git a/src/tests/easings/test-easing.h b/src/tests/easings/test-easing.h new file mode 100644 index 0000000..dd07b65 --- /dev/null +++ b/src/tests/easings/test-easing.h @@ -0,0 +1,28 @@ +#ifndef TWEENY_TESTS_EASINGS_TEST_EASING_H +#define TWEENY_TESTS_EASINGS_TEST_EASING_H + +#include +#include +#include + +#include "reference-values.h" + +template +void test_easing(const Easing & easing, const float (&reference)[sample_count]) { + for (std::size_t t = 0; t < sample_count; ++t) { + const float position = static_cast(t) / 100.f; + const float got = easing(position, 0.f, 1.f); + REQUIRE(got == Catch::Approx(reference[t]).margin(1e-6f)); + } +} + +template +void test_tween_easing(const Easing & easing, const float (&reference)[sample_count]) { + auto tween = tweeny::from(0.f).to(1.f).via(easing).during(100U).build(); + for (std::size_t t = 0; t < sample_count; ++t) { + const float got = tween.peek(static_cast(t)); + REQUIRE(got == Catch::Approx(reference[t]).margin(1e-6f)); + } +} + +#endif // TWEENY_TESTS_EASINGS_TEST_EASING_H diff --git a/src/tests/events/complete.cpp b/src/tests/events/complete.cpp new file mode 100644 index 0000000..b26a7a9 --- /dev/null +++ b/src/tests/events/complete.cpp @@ -0,0 +1,116 @@ +#include +#include + +TEST_CASE("event::complete - triggered when reaching end via step", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + // Not complete yet + t.step(50); + REQUIRE_FALSE(completed); + + // Complete now + t.step(50); + REQUIRE(completed); +} + +TEST_CASE("event::complete - triggered when reaching end via seek", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + t.seek(100U); + REQUIRE(completed); +} + +TEST_CASE("event::complete - triggered when jumping to last keyframe", "[event][complete]") { + auto t = tweeny::from(0).to(50).during(50U).to(100).during(50U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + t.jump(2); // Jump to last keyframe + REQUIRE(completed); +} + +TEST_CASE("event::complete - not triggered when not at end", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + t.step(50); + REQUIRE_FALSE(completed); + + t.seek(75U); + REQUIRE_FALSE(completed); +} + +TEST_CASE("event::complete - can unsubscribe", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + int call_count = 0; + t.on(tweeny::event::complete, [&](auto&) { + call_count++; + return tweeny::event::response::unsubscribe; + }); + + t.seek(100U); + REQUIRE(call_count == 1); + + // Reset and complete again - listener should be gone + t.seek(0U); + t.seek(100U); + REQUIRE(call_count == 1); // Should still be 1 +} + +TEST_CASE("event::complete - multiple listeners", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + int listener1_count = 0; + int listener2_count = 0; + + t.on(tweeny::event::complete, [&](auto&) { + listener1_count++; + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::complete, [&](auto&) { + listener2_count++; + return tweeny::event::response::ok; + }); + + t.step(100); + REQUIRE(listener1_count == 1); + REQUIRE(listener2_count == 1); +} + +TEST_CASE("event::complete - triggered on exact completion", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto& tween) { + REQUIRE(tween.progress() >= 1.0f); + REQUIRE(tween.peek() == 100); + completed = true; + return tweeny::event::response::ok; + }); + + t.step(100); + REQUIRE(completed); +} diff --git a/src/tests/events/jump.cpp b/src/tests/events/jump.cpp new file mode 100644 index 0000000..88e402d --- /dev/null +++ b/src/tests/events/jump.cpp @@ -0,0 +1,19 @@ +#include +#include + +TEST_CASE("event::jump - callback is invoked on jump()", "[event][jump]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::jump, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.jump(1); + REQUIRE(called == 1); +} diff --git a/src/tests/events/keyframe_enter.cpp b/src/tests/events/keyframe_enter.cpp new file mode 100644 index 0000000..971e481 --- /dev/null +++ b/src/tests/events/keyframe_enter.cpp @@ -0,0 +1,147 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include "tweeny/tweeny.h" + +TEST_CASE("event::keyframeEnter - triggers when entering a new keyframe via step", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(31); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 1); +} + +TEST_CASE("event::keyframeEnter - triggers when entering a new keyframe via seek", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.seek(35U); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 1); +} + +TEST_CASE("event::keyframeEnter - triggers when entering a new keyframe via jump", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.jump(1); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 1); +} + +TEST_CASE("event::keyframeEnter - does not trigger when staying in the same keyframe", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter) { + call_count++; + return tweeny::event::response::ok; + }); + + t.step(10); + t.step(5); + t.step(10); + + REQUIRE(call_count == 0); +} + +TEST_CASE("event::keyframeEnter - triggers for the correct keyframe in multi-keyframe tween", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(25).during(10U).to(50).during(10U).to(75).during(10U).to(100).during(10U).build(); + std::vector entered_keyframes; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframes.push_back(evt.key_frame); + return tweeny::event::response::ok; + }); + + t.step(11); + t.step(10); + t.step(10); + + REQUIRE(entered_keyframes.size() == 3); + REQUIRE(entered_keyframes[0] == 1); + REQUIRE(entered_keyframes[1] == 2); + REQUIRE(entered_keyframes[2] == 3); +} + +TEST_CASE("event::keyframeEnter - can unsubscribe", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter) { + call_count++; + return tweeny::event::response::unsubscribe; + }); + + t.step(31); + t.seek(0U); + t.step(31); + + REQUIRE(call_count == 1); +} + +TEST_CASE("event::keyframeEnter - triggers when stepping backward into a different keyframe", "[event][keyframeEnter]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(35); + call_count = 0; + t.step(-10); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 0); +} diff --git a/src/tests/events/keyframe_leave.cpp b/src/tests/events/keyframe_leave.cpp new file mode 100644 index 0000000..d2e5830 --- /dev/null +++ b/src/tests/events/keyframe_leave.cpp @@ -0,0 +1,168 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2026 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include "tweeny/tweeny.h" + +TEST_CASE("event::keyframeLeave - triggers when leaving a keyframe via step", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(31); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 0); +} + +TEST_CASE("event::keyframeLeave - triggers when leaving a keyframe via seek", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.seek(35U); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 0); +} + +TEST_CASE("event::keyframeLeave - triggers when leaving a keyframe via jump", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.jump(1); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 0); +} + +TEST_CASE("event::keyframeLeave - does not trigger when staying in the same keyframe", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave) { + call_count++; + return tweeny::event::response::ok; + }); + + t.step(10); + t.step(5); + t.step(10); + + REQUIRE(call_count == 0); +} + +TEST_CASE("event::keyframeLeave - triggers for the correct keyframe in multi-keyframe tween", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(25).during(10U).to(50).during(10U).to(75).during(10U).to(100).during(10U).build(); + std::vector left_keyframes; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframes.push_back(evt.key_frame); + return tweeny::event::response::ok; + }); + + t.step(11); + t.step(10); + t.step(10); + + REQUIRE(left_keyframes.size() == 3); + REQUIRE(left_keyframes[0] == 0); + REQUIRE(left_keyframes[1] == 1); + REQUIRE(left_keyframes[2] == 2); +} + +TEST_CASE("event::keyframeLeave - can unsubscribe", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave) { + call_count++; + return tweeny::event::response::unsubscribe; + }); + + t.step(31); + t.seek(0U); + t.step(31); + + REQUIRE(call_count == 1); +} + +TEST_CASE("event::keyframeLeave - triggers when stepping backward into a different keyframe", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(35); + call_count = 0; + t.step(-10); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 1); +} + +TEST_CASE("event::keyframeLeave - leave and enter events trigger in correct order", "[event][keyframeLeave]") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::vector event_order; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + event_order.push_back("leave:" + std::to_string(evt.key_frame)); + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + event_order.push_back("enter:" + std::to_string(evt.key_frame)); + return tweeny::event::response::ok; + }); + + t.step(31); + + REQUIRE(event_order.size() == 2); + REQUIRE(event_order[0] == "leave:0"); + REQUIRE(event_order[1] == "enter:1"); +} diff --git a/src/tests/events/seek.cpp b/src/tests/events/seek.cpp new file mode 100644 index 0000000..0914096 --- /dev/null +++ b/src/tests/events/seek.cpp @@ -0,0 +1,19 @@ +#include +#include + +TEST_CASE("event::seek - callback is invoked on seek()", "[event][seek]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::seek, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.seek(5U); + REQUIRE(called == 1); +} diff --git a/src/tests/events/step.cpp b/src/tests/events/step.cpp new file mode 100644 index 0000000..0d59d7f --- /dev/null +++ b/src/tests/events/step.cpp @@ -0,0 +1,44 @@ +#include +#include + +TEST_CASE("event::step - callback is invoked on step()", "[event][step]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::step, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); +} + +TEST_CASE("event::step - unsubscribe removes listener", "[event][step][unsubscribe]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::step, [&](const auto &) { + ++called; + return tweeny::event::response::unsubscribe; + }); + + t.on(tweeny::event::step, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 2); + + (void)t.step(1); + REQUIRE(called == 3); +} diff --git a/src/tests/events/update.cpp b/src/tests/events/update.cpp new file mode 100644 index 0000000..b416bbb --- /dev/null +++ b/src/tests/events/update.cpp @@ -0,0 +1,153 @@ +#include +#include + +TEST_CASE("event::update - callback is invoked on step()", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); +} + +TEST_CASE("event::update - callback is invoked on seek()", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.seek(5U); + REQUIRE(called == 1); +} + +TEST_CASE("event::update - callback is invoked on jump()", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.jump(1); + REQUIRE(called == 1); +} + +TEST_CASE("event::update - callback fires after specific event", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int step_order = 0; + int update_order = 0; + int counter = 0; + + t.on(tweeny::event::step, [&](const auto &) { + step_order = ++counter; + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::update, [&](const auto &) { + update_order = ++counter; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(step_order == 1); + REQUIRE(update_order == 2); +} + +TEST_CASE("event::update - callback fires before complete event", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int update_order = 0; + int complete_order = 0; + int counter = 0; + + t.on(tweeny::event::update, [&](const auto &) { + update_order = ++counter; + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::complete, [&](const auto &) { + complete_order = ++counter; + return tweeny::event::response::ok; + }); + + (void)t.step(10); + REQUIRE(update_order == 1); + REQUIRE(complete_order == 2); +} + +TEST_CASE("event::update - unsubscribe removes listener", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::unsubscribe; + }); + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 2); + + (void)t.step(1); + REQUIRE(called == 3); +} + +TEST_CASE("event::update - fires on all update types in sequence", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .to(20) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); + + (void)t.seek(5U); + REQUIRE(called == 2); + + (void)t.jump(1); + REQUIRE(called == 3); +} diff --git a/src/tests/sanity.cpp b/src/tests/sanity.cpp new file mode 100644 index 0000000..a9856ca --- /dev/null +++ b/src/tests/sanity.cpp @@ -0,0 +1,5 @@ +#include + +TEST_CASE("sanity - the test framework runs", "[sanity]") { + REQUIRE(1 + 1 == 2); +} diff --git a/src/tests/tween/jump.cpp b/src/tests/tween/jump.cpp new file mode 100644 index 0000000..fee5c81 --- /dev/null +++ b/src/tests/tween/jump.cpp @@ -0,0 +1,72 @@ +#include +#include + +TEST_CASE("jump() - jumps to keyframe by index", "[tween][jump]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + auto val = t.jump(0); + REQUIRE(val == 0); + + val = t.jump(1); + REQUIRE(val == 50); + + val = t.jump(2); + REQUIRE(val == 100); +} + +TEST_CASE("jump() - clamped to valid keyframe range", "[tween][jump]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + t.jump(10); // Beyond last keyframe + REQUIRE(t.peek() == 100); +} + +TEST_CASE("jump() - works with two-point tween", "[tween][jump]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.jump(0); + REQUIRE(t.peek() == 0); + + t.jump(1); + REQUIRE(t.peek() == 100); +} + +TEST_CASE("jump() - multi-value tween", "[tween][jump]") { + auto t = tweeny::from(0, 0.0f) + .to(50, 25.0f).during(50U) + .to(100, 100.0f).during(50U) + .build(); + + auto result = t.jump(1); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == 25.0f); + + result = t.jump(2); + REQUIRE(std::get<0>(result) == 100); + REQUIRE(std::get<1>(result) == 100.0f); +} + +TEST_CASE("jump() - can jump backward", "[tween][jump]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + t.jump(2); + REQUIRE(t.peek() == 100); + + t.jump(0); + REQUIRE(t.peek() == 0); + + t.jump(1); + REQUIRE(t.peek() == 50); +} diff --git a/src/tests/tween/navigation_consistency.cpp b/src/tests/tween/navigation_consistency.cpp new file mode 100644 index 0000000..46ba675 --- /dev/null +++ b/src/tests/tween/navigation_consistency.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +namespace { + +template +void require_seek_step_match_peek(Easing easing) { + auto probe = tweeny::from(0.f).to(1.f).via(easing).during(100U).build(); + auto seeker = tweeny::from(0.f).to(1.f).via(easing).during(100U).build(); + auto stepper = tweeny::from(0.f).to(1.f).via(easing).during(100U).build(); + + for (uint32_t frame = 0; frame <= 100U; ++frame) { + const auto expected = probe.peek(frame); + + REQUIRE(seeker.seek(frame) == Catch::Approx(expected).margin(1e-6f)); + REQUIRE(seeker.peek() == Catch::Approx(expected).margin(1e-6f)); + + stepper.seek(0U); + REQUIRE(stepper.step(frame) == Catch::Approx(expected).margin(1e-6f)); + REQUIRE(stepper.peek() == Catch::Approx(expected).margin(1e-6f)); + } +} + +template +void require_jump_seek_match_peek(Easing easing) { + auto t = tweeny::from(0.f) + .to(0.5f).via(easing).during(50U) + .to(1.f).via(easing).during(50U) + .build(); + auto seeker = tweeny::from(0.f) + .to(0.5f).via(easing).during(50U) + .to(1.f).via(easing).during(50U) + .build(); + + constexpr uint32_t positions[] = {0U, 50U, 100U}; + for (int i = 0; i < 3; ++i) { + const auto pos = positions[i]; + const auto expected = t.peek(pos); + + REQUIRE(t.jump(i) == Catch::Approx(expected).margin(1e-6f)); + REQUIRE(t.peek() == Catch::Approx(expected).margin(1e-6f)); + REQUIRE(seeker.seek(pos) == Catch::Approx(expected).margin(1e-6f)); + REQUIRE(seeker.peek() == Catch::Approx(expected).margin(1e-6f)); + } +} + +} // namespace + +TEST_CASE("navigation_consistency - seek/step agree with peek across easings", "[tween][navigation][navigation_consistency]") { + SECTION("linear") { require_seek_step_match_peek(tweeny::easing::linear); } + SECTION("def") { require_seek_step_match_peek(tweeny::easing::def); } + SECTION("stepped") { require_seek_step_match_peek(tweeny::easing::stepped); } + SECTION("quadraticIn") { require_seek_step_match_peek(tweeny::easing::quadraticIn); } + SECTION("cubicOut") { require_seek_step_match_peek(tweeny::easing::cubicOut); } + SECTION("bounceOut") { require_seek_step_match_peek(tweeny::easing::bounceOut); } + SECTION("elasticIn") { require_seek_step_match_peek(tweeny::easing::elasticIn); } + SECTION("backIn") { require_seek_step_match_peek(tweeny::easing::backIn); } +} + +TEST_CASE("navigation_consistency - jump/seek agree with peek at keyframes across easings", "[tween][navigation][navigation_consistency]") { + SECTION("linear") { require_jump_seek_match_peek(tweeny::easing::linear); } + SECTION("def") { require_jump_seek_match_peek(tweeny::easing::def); } + SECTION("stepped") { require_jump_seek_match_peek(tweeny::easing::stepped); } + SECTION("quadraticIn") { require_jump_seek_match_peek(tweeny::easing::quadraticIn); } + SECTION("cubicOut") { require_jump_seek_match_peek(tweeny::easing::cubicOut); } + SECTION("bounceOut") { require_jump_seek_match_peek(tweeny::easing::bounceOut); } + SECTION("elasticIn") { require_jump_seek_match_peek(tweeny::easing::elasticIn); } + SECTION("backIn") { require_jump_seek_match_peek(tweeny::easing::backIn); } +} diff --git a/src/tests/tween/peek.cpp b/src/tests/tween/peek.cpp new file mode 100644 index 0000000..d3e765f --- /dev/null +++ b/src/tests/tween/peek.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +TEST_CASE("peek() - returns current value without mutation", "[tween][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + REQUIRE(t.peek() == 0); + t.step(50); + REQUIRE(t.peek() == 50); +} + +TEST_CASE("peek(frame) - queries value at arbitrary frame without mutation", "[tween][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + // Should not change current position + REQUIRE(t.peek(50U) == 50); + REQUIRE(t.peek() == 0); // Still at start + REQUIRE(t.progress() == Catch::Approx(0.0f)); +} + +TEST_CASE("peek(frame) - multi-value tween", "[tween][peek]") { + auto t = tweeny::from(0, 0.0f) + .to(100, 100.0f) + .during(100U) + .build(); + + auto result = t.peek(50U); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == Catch::Approx(50.0f)); +} + +TEST_CASE("peek() - does not trigger events", "[tween][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + int call_count = 0; + t.on(tweeny::event::step, [&](auto&) { + call_count++; + return tweeny::event::response::ok; + }); + + (void)t.peek(); + (void)t.peek(50U); + REQUIRE(call_count == 0); +} diff --git a/src/tests/tween/progress.cpp b/src/tests/tween/progress.cpp new file mode 100644 index 0000000..f739728 --- /dev/null +++ b/src/tests/tween/progress.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +TEST_CASE("progress() - returns 0.0 at start", "[tween][progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + REQUIRE(t.progress() == Catch::Approx(0.0f)); +} + +TEST_CASE("progress() - returns 1.0 at end", "[tween][progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.seek(100U); + REQUIRE(t.progress() == Catch::Approx(1.0f)); +} + +TEST_CASE("progress() - returns 0.5 at midpoint", "[tween][progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(50); + REQUIRE(t.progress() == Catch::Approx(0.5f)); +} + +TEST_CASE("progress() - multi-point tween", "[tween][progress]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + REQUIRE(t.progress() == Catch::Approx(0.0f)); + + t.seek(25U); + REQUIRE(t.progress() == Catch::Approx(0.25f)); + + t.seek(50U); + REQUIRE(t.progress() == Catch::Approx(0.5f)); + + t.seek(75U); + REQUIRE(t.progress() == Catch::Approx(0.75f)); + + t.seek(100U); + REQUIRE(t.progress() == Catch::Approx(1.0f)); +} + +TEST_CASE("progress() - handles zero duration", "[tween][progress]") { + auto t = tweeny::from(0) + .to(100) + .during(0U) + .build(); + + REQUIRE(t.progress() == Catch::Approx(1.0f)); +} + +TEST_CASE("progress() - consistent with peek", "[tween][progress][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + for (int i = 0; i <= 100; i += 10) { + t.seek(static_cast(i)); + REQUIRE(t.peek() == i); + REQUIRE(t.progress() == Catch::Approx(static_cast(i) / 100.0f)); + } +} diff --git a/src/tests/tween/seek.cpp b/src/tests/tween/seek.cpp new file mode 100644 index 0000000..cfd0ad2 --- /dev/null +++ b/src/tests/tween/seek.cpp @@ -0,0 +1,81 @@ +#include +#include + +TEST_CASE("seek() - jumps to target frame", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + auto val = t.seek(50U); + REQUIRE(val == 50); + REQUIRE(t.peek() == 50); +} + +TEST_CASE("seek() - can jump forward and backward", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.seek(75U); + REQUIRE(t.peek() == 75); + + t.seek(25U); + REQUIRE(t.peek() == 25); + + t.seek(100U); + REQUIRE(t.peek() == 100); + + t.seek(0U); + REQUIRE(t.peek() == 0); +} + +TEST_CASE("seek() - clamped to valid range", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.seek(200U); // Beyond end + REQUIRE(t.peek() == 100); +} + +TEST_CASE("seek() - multi-point tween", "[tween][seek]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + t.seek(25U); + REQUIRE(t.peek() == 25); + + t.seek(50U); + REQUIRE(t.peek() == 50); + + t.seek(75U); + REQUIRE(t.peek() == 75); +} + +TEST_CASE("seek() - multi-value tween", "[tween][seek]") { + auto t = tweeny::from(0, 100.0f) + .to(100, 0.0f) + .during(100U) + .build(); + + auto result = t.seek(50U); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == 50.0f); +} + +TEST_CASE("seek() - returns interpolated value", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + for (uint32_t i = 0; i <= 100; i += 10) { + auto val = t.seek(i); + REQUIRE(val == static_cast(i)); + } +} diff --git a/src/tests/tween/step.cpp b/src/tests/tween/step.cpp new file mode 100644 index 0000000..768d8e2 --- /dev/null +++ b/src/tests/tween/step.cpp @@ -0,0 +1,77 @@ +#include +#include + +TEST_CASE("step() - advances by positive delta", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + auto val = t.step(10); + REQUIRE(val == 10); + REQUIRE(t.peek() == 10); + + val = t.step(20); + REQUIRE(val == 30); + REQUIRE(t.peek() == 30); +} + +TEST_CASE("step() - rewinds by negative delta", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(50); + REQUIRE(t.peek() == 50); + + t.step(-20); + REQUIRE(t.peek() == 30); + + t.step(-10); + REQUIRE(t.peek() == 20); +} + +TEST_CASE("step() - clamped at start", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(10); + t.step(-100); // Try to go negative + REQUIRE(t.peek() == 0); +} + +TEST_CASE("step() - clamped at end", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(200); // Overshoot + REQUIRE(t.peek() == 100); +} + +TEST_CASE("step() - multi-value tween", "[tween][step]") { + auto t = tweeny::from(0, 0.0f) + .to(100, 50.0f) + .during(100U) + .build(); + + auto result = t.step(50); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == 25.0f); +} + +TEST_CASE("step() - returns interpolated value", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + for (int i = 1; i <= 10; i++) { + auto val = t.step(10); + REQUIRE(val == i * 10); + } +}