diff --git a/.gitignore b/.gitignore index f3aff03..d9136a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea cmake-build-* build +src/sandbox.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e61884..e4830ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,73 +25,96 @@ # 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.0) +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_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/tweeny + FILES + include/tweeny/event.h + include/tweeny/tweeny.h + include/tweeny/tween.h + include/tweeny/tween.tcc + include/tweeny/easing.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() - -# 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) + add_subdirectory(src/doc) +endif () + +if (TWEENY_BUILD_TESTS) + enable_testing() + add_subdirectory(src/tests) +endif () if (TWEENY_BUILD_SINGLE_HEADER) - include(cmake/GenerateSingleHeader.cmake) -endif() + include(cmake/GenerateSingleHeader.cmake) +endif () + +# Check for sandbox file existence and build if found +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/sandbox.cpp") + add_executable(sandbox src/sandbox.cpp) + target_link_libraries(sandbox tweeny) -if (TWEENY_BUILD_SANDBOX) - add_executable(sandbox src/sandbox.cc) - target_link_libraries(sandbox tweeny) -endif() + # Enable very strict warnings and treat warnings as errors for the sandbox target + target_compile_options(sandbox PRIVATE + $<$:-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wold-style-cast -Wcast-align -Woverloaded-virtual -Wnull-dereference -Wdouble-promotion -Wformat=2 -Wimplicit-fallthrough -Wswitch-enum -Wzero-as-null-pointer-constant -Wuseless-cast -Werror> + $<$:-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wold-style-cast -Wcast-align -Woverloaded-virtual -Wnull-dereference -Wformat=2 -Wimplicit-fallthrough -Wswitch-enum -Wzero-as-null-pointer-constant -Wuseless-cast -Werror> + $<$:/W4 /WX /permissive-> + ) +endif () diff --git a/cmake/GenerateSingleHeader.cmake b/cmake/GenerateSingleHeader.cmake index 08ff5e1..64c6b6d 100644 --- a/cmake/GenerateSingleHeader.cmake +++ b/cmake/GenerateSingleHeader.cmake @@ -1,14 +1,14 @@ # This cmake script is used to generate a single header file with all of tweeny -find_package(Python 3.6 QUIET) +find_package(Python3 3.6 COMPONENTS Interpreter QUIET) -if (NOT PYTHON_FOUND) - message(STATUS "Python 3.6 not found. Single-header include file will NOT be created") +if (NOT Python3_Interpreter_FOUND) + message(STATUS "Python 3.6+ interpreter not found. Single-header include file will NOT be created") return() endif() find_program(QUOM_EXECUTABLE NAMES quom) -if (QUOM_EXECUTABLE-NOTFOUND) - message(STATUS "quom program not found. Install it with pip or easy_install") +if (NOT QUOM_EXECUTABLE) + message(STATUS "quom program not found. Install it with: pip3 install quom") return() endif() diff --git a/cmake/SetupExports.cmake b/cmake/SetupExports.cmake index 692cc93..95686c4 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 17f9a75..0000000 --- a/include/dispatcher.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 48f2835..0000000 --- a/include/easing.h +++ /dev/null @@ -1,662 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 f7d266e..0000000 --- a/include/easingresolve.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 a0d8f17..0000000 --- a/include/int2type.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 ad64dfd..0000000 --- a/include/tween.h +++ /dev/null @@ -1,657 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 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 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 - float 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(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const; - void interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const; - void render(float p); - void dispatch(std::vector & cbVector); - uint16_t pointAt(float 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 - float progress() const; ///< @sa tween::progress - 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 - float 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(float prog, unsigned point, T & value) const; - void render(float p); - void dispatch(std::vector & cbVector); - uint16_t pointAt(float 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 dd0e77f..0000000 --- a/include/tween.tcc +++ /dev/null @@ -1,347 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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) { - return step(static_cast(dt)/static_cast(total), suppress); - } - - 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) { - dp *= currentDirection; - seek(currentProgress + dp, true); - if (!suppress) dispatch(onStepCallbacks); - return current; - } - - template - inline const typename detail::tweentraits::valuesType & tween::seek(float p, bool suppress) { - p = detail::clip(p, 0.0f, 1.0f); - 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(t) / static_cast(total), suppress); - } - - template - inline uint32_t tween::duration() const { - return total; - } - - template - template - inline void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { - auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); - 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(float 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 * static_cast(total)))); - 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(float 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; - interpolate(progress, pointAt(progress), values, detail::int2type{ }); - return values; - } - - template - const typename detail::tweentraits::valuesType tween::peek(uint32_t time) const { - typename detail::tweentraits::valuesType values; - float progress = static_cast(time) / static_cast(total); - interpolate(progress, pointAt(progress), values, detail::int2type{ }); - return values; - } - - template - float tween::progress() const { - return currentProgress; - } - - 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(float progress) const { - progress = detail::clip(progress, 0.0f, 1.0f); - uint32_t t = static_cast(progress * total); - 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_TWEEN_TCC diff --git a/include/tweenone.tcc b/include/tweenone.tcc deleted file mode 100644 index c2a52ad..0000000 --- a/include/tweenone.tcc +++ /dev/null @@ -1,335 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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) { - return step(static_cast(dt)/static_cast(total), suppress); - } - - 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) { - dp *= currentDirection; - seek(currentProgress + dp, true); - if (!suppress) dispatch(onStepCallbacks); - return current; - } - - template - inline const T & tween::seek(float p, bool suppress) { - p = detail::clip(p, 0.0f, 1.0f); - currentProgress = p; - render(p); - if (!suppress) dispatch(onSeekCallbacks); - return current; - } - - template - inline const T & tween::seek(int32_t t, bool suppress) { - return seek(static_cast(t) / static_cast(total), suppress); - } - - template - inline const T & tween::seek(uint32_t t, bool suppress) { - return seek(static_cast(t) / static_cast(total), suppress); - } - - template - inline uint32_t tween::duration() const { - return total; - } - - template - inline void tween::interpolate(float prog, unsigned point, T & value) const { - auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); - 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(float 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, pointAt(progress), value); - return value; - } - - template - T tween::peek(uint32_t time) const { - T value; - float progress = static_cast(time) / static_cast(total); - interpolate(progress, pointAt(progress), value); - return value; - } - - - template - float tween::progress() const { - return currentProgress; - } - - 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(float progress) const { - progress = detail::clip(progress, 0.0f, 1.0f); - auto t = static_cast(progress * total); - 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 732446c..0000000 --- a/include/tweenpoint.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 c5cda0e..0000000 --- a/include/tweenpoint.tcc +++ /dev/null @@ -1,115 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 ff22938..0000000 --- a/include/tweentraits.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 3c7ba44..0000000 --- a/include/tweeny.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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 e2defac..0000000 --- a/include/tweeny.tcc +++ /dev/null @@ -1,40 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 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..57058ec --- /dev/null +++ b/include/tweeny/detail/easing/back.h @@ -0,0 +1,78 @@ +/* +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. +*/ + +#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..3aab301 --- /dev/null +++ b/include/tweeny/detail/easing/bounce.h @@ -0,0 +1,81 @@ +/* +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. +*/ + +#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..fe63713 --- /dev/null +++ b/include/tweeny/detail/easing/circular.h @@ -0,0 +1,77 @@ +/* +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. +*/ + +#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..40bff53 --- /dev/null +++ b/include/tweeny/detail/easing/cubic.h @@ -0,0 +1,72 @@ +/* +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. +*/ + +#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..9c8a44a --- /dev/null +++ b/include/tweeny/detail/easing/def.h @@ -0,0 +1,75 @@ +/* +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. +*/ + +#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..3fb3c9a --- /dev/null +++ b/include/tweeny/detail/easing/elastic.h @@ -0,0 +1,98 @@ +/* +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. +*/ + +#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..75a9cb0 --- /dev/null +++ b/include/tweeny/detail/easing/exponential.h @@ -0,0 +1,73 @@ +/* +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. +*/ + +#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) { + 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) { + 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) { + 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..2446598 --- /dev/null +++ b/include/tweeny/detail/easing/linear.h @@ -0,0 +1,50 @@ +/* +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. +*/ + +#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..76b655c --- /dev/null +++ b/include/tweeny/detail/easing/quadratic.h @@ -0,0 +1,72 @@ +/* +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. +*/ + +#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..b0b79ba --- /dev/null +++ b/include/tweeny/detail/easing/quartic.h @@ -0,0 +1,72 @@ +/* +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. +*/ + +#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..feb777f --- /dev/null +++ b/include/tweeny/detail/easing/quintic.h @@ -0,0 +1,72 @@ +/* +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. +*/ + +#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..156ddc2 --- /dev/null +++ b/include/tweeny/detail/easing/sinusoidal.h @@ -0,0 +1,72 @@ +/* +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. +*/ + +#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..e00fb61 --- /dev/null +++ b/include/tweeny/detail/easing/stepped.h @@ -0,0 +1,42 @@ +/* +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. +*/ + +#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..44dd29f --- /dev/null +++ b/include/tweeny/detail/event.h @@ -0,0 +1,120 @@ +/* +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. +*/ + +#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..ec29ba6 --- /dev/null +++ b/include/tweeny/detail/interpolate.h @@ -0,0 +1,62 @@ +/* +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. +*/ + +#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..9b0a1b8 --- /dev/null +++ b/include/tweeny/detail/key-frame.h @@ -0,0 +1,131 @@ +/* +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. +*/ + +#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..52f06a0 --- /dev/null +++ b/include/tweeny/detail/tuple-utilities.h @@ -0,0 +1,61 @@ +/* +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. +*/ + +#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..57af111 --- /dev/null +++ b/include/tweeny/detail/tween-value.h @@ -0,0 +1,65 @@ +/* +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. +*/ + +#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..d244a7f --- /dev/null +++ b/include/tweeny/detail/value-container.h @@ -0,0 +1,95 @@ +/* +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. +*/ + +#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..474ca4d --- /dev/null +++ b/include/tweeny/easing.h @@ -0,0 +1,1468 @@ +/* + 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. 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..2342f2c --- /dev/null +++ b/include/tweeny/event.h @@ -0,0 +1,283 @@ +/* +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 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..a0f4d46 --- /dev/null +++ b/include/tweeny/tween.h @@ -0,0 +1,354 @@ +/* +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 + * @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..4aa94c7 --- /dev/null +++ b/include/tweeny/tween.tcc @@ -0,0 +1,326 @@ +/* +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. +*/ + +#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..aa77f38 --- /dev/null +++ b/include/tweeny/tweeny.h @@ -0,0 +1,352 @@ +/* +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 + * @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 firstValue, RemainingValues... remainingValues) { + key_frames.emplace_back(firstValue, remainingValues...); + } + + 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 firstValue Target value for the first component + * @param remainingValues 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 & firstValue, const RemainingValues &... remainingValues) & { + key_frames.emplace_back(firstValue, remainingValues...); + return tweeny_builder(key_frames); + } + + /// @overload + tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) && { + key_frames.emplace_back(firstValue, remainingValues...); + 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 firstValue, RemainingValues... remainingValues) { + key_frames.emplace_back(firstValue, remainingValues...); + } + + 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 firstValue Target value for the first component + * @param remainingValues 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 & firstValue, const RemainingValues &... remainingValues) & { + key_frames.emplace_back(firstValue, remainingValues...); + return tweeny_builder(key_frames); + } + + /// @overload + tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) && { + key_frames.emplace_back(firstValue, remainingValues...); + 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 first_value Initial value for the first component + * @param remaining_values 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 first_value, RemainingValues... remaining_values) { + return tweeny_builder(first_value, remaining_values...); + } +} + +#endif //TWEENY_TWEENY_H diff --git a/doc/CMakeLists.txt b/src/doc/CMakeLists.txt similarity index 65% rename from doc/CMakeLists.txt rename to src/doc/CMakeLists.txt index 68b7b74..dd24aba 100644 --- a/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -24,18 +24,24 @@ 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}/../../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 ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + 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..36cbdda 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 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/mainpage.dox b/src/doc/mainpage.dox new file mode 100644 index 0000000..291416c --- /dev/null +++ b/src/doc/mainpage.dox @@ -0,0 +1,144 @@ +/** +@mainpage Tweeny + +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 numeric 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 (start, step, end, keyframes) +- **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(); + // 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(); + +// 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..4280f8a --- /dev/null +++ b/src/doc/manual.dox @@ -0,0 +1,381 @@ +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`). + + @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::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 range `[0, total_duration]`. + + @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 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 + + @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::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 + + @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; + }); + + // 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/v3_to_v4.dox b/src/doc/v3_to_v4.dox new file mode 100644 index 0000000..9079e99 --- /dev/null +++ b/src/doc/v3_to_v4.dox @@ -0,0 +1,389 @@ +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 + tween.seek(500U); // Seek to frame 500 + + // For percentage, calculate manually: + tween.seek(static_cast(tween.total_duration() * 0.5f)); + @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 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::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 "tweeny.h" + + 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..667e2e2 --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +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 + events/step.cpp + events/seek.cpp + events/jump.cpp + events/complete.cpp + events/keyframe_enter.cpp + events/keyframe_leave.cpp + events/update.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/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..cdd23c2 --- /dev/null +++ b/src/tests/events/keyframe_enter.cpp @@ -0,0 +1,147 @@ +/* +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. +*/ + +#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..39f997e --- /dev/null +++ b/src/tests/events/keyframe_leave.cpp @@ -0,0 +1,168 @@ +/* +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. +*/ + +#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/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); + } +}