#ifndef Magnum_Animation_Player_h #define Magnum_Animation_Player_h /* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Vladimír Vondruš 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 * @brief Class @ref Magnum::Animation::Player, enum @ref Magnum::Animation::State */ #include #include #include "Magnum/Animation/Track.h" #include "Magnum/Math/Range.h" namespace Magnum { namespace Animation { /** @brief Player state @see @ref Player @experimental */ enum class State: UnsignedByte { /** * The animation clip is currently playing. Setting the state to * @ref State::Playing does nothing. */ Playing, /** * The animation clip is currently paused. Setting the state to * @ref State::Playing starts playing from where it left, setting the state * to @ref State::Stopped stops the animation, setting the state to * @ref State::Paused does nothing. */ Paused, /** * The animation clip is currently stopped. Setting the state to * @ref State::Playing starts playing from the beginning, attempting to set * the state to @ref State::Paused will retain the @ref State::Stopped * state, setting the state to @ref State::Stopped does nothing. */ Stopped }; /** @debugoperatorenum{State} */ MAGNUM_EXPORT Debug& operator<<(Debug& debug, State value); namespace Implementation { template struct DefaultScaler; } /** @brief Animation player @tparam T Time type @tparam K Key type Provides a generic way for querying interpolated results from multiple animation tracks of distinct types from a single place, together with managing the animation running state. Similarly to @ref Track / @ref TrackView, the player is also partially statless --- in particular, it neither accesses any global timer or keeps any notion of "current time". Instead, all time-dependent functions take absolute time as a parameter. This both simplifies the internal state management and adds additional flexibility on user side. @section Animation-Player-setup Setting up The @ref Player class is used by adding tracks to it and specifying what should be done with interpolation result values. The simplest option is specifying a destination location when adding the track using @ref add() --- that'll mean you get a fresh set of animated values at your disposal after every iteration: @snippet MagnumAnimation.cpp Player-usage The Player stores just @ref TrackView instances, for every @ref Track instance you have to ensure that it stays alive for the whole lifetime of the player instance. In case you need to apply the animated values using a setter, it's possible to fire a callback every iteration. Note that the @ref addWithCallback() function has also a typeless version taking just @cpp void* @ce user pointer instead of a reference to a concrete type. Below is an example of animating @ref SceneGraph object transformations using the @ref SceneGraph::TranslationRotationScalingTransformation3D transformation implementation: @snippet MagnumAnimation.cpp Player-usage-callback The @ref addWithCallbackOnChange() variant will fire the callback only if the interpolated value changes, which is useful for triggering other events. See @ref Animation-Player-higher-order "below" for an example. Lastly, there is @ref addRawCallback() that allows for greater control and further performance optimizations. See its documentation for a usage example code snippet. The animation is implicitly played only once, use @ref setPlayCount() to set a number of repeats or make it repeat indefinitely. By default, the @ref duration() of an animation is calculated implicitly from all added tracks. You can use @ref setDuration() to specify a custom duration: - If it extends beyond the keyframe values, values of begin/end keyframes will be extrapolated according to @ref Extrapolation specified for every track. - If it will be shorter, only a slice of the animation will be played. - If duration size is empty (min and and max se to the same value) and @ref setPlayCount() is set to inifite, then the animator will indefinitely give out value from a key that's at the start of the duration. If play count is finite, the animation will get stopped right away. @section Animation-Player-playback Animation playback By default, the player is in a @ref State::Stopped state. Call @ref play() with a time value denoting the moment at which the animation should start. After that, the @ref advance() function is meant to be called every frame with a current time value. As long as the animation is playing, the @ref advance() function will update track result destination locations with interpolated values and/or fire user-defined callbacks described above. Once the animation playback is finished (exhausing the whole @ref duration() of all @ref playCount() iterations), the @ref advance() will update the destination locations and/or fire user-defined callbacks with values that correspond to @ref duration() end time. This is guaranteed to be always the case in order to correctly "park" the animations --- even if your app would freeze for a while and @ref advance() would get called later, the result values will never be calculated from a key value that's outside @ref duration(). Calling @ref stop() immediately transfers @ref state() to @ref State::Stopped and the next @ref advance() iteration will give out interpolated values corresponding to the begin time of @ref duration(), again to "park" the animation back to its initial state. After that, no more updates are done until the animation is started again. Compared to when the animation stops by itself, this will park it at the beginning, not at the end. Calling @ref pause() while the animation is running immediately transfers the animation state to @ref State::Paused and the next @ref advance() iteration will give out interpolated values corresponding to a time that was passed to the @ref pause() function. After that, no more updates are done until the animation is resumed again with @ref play(), stopped with @ref stop() or seeked using @ref seekBy() / @ref seekTo(). Calling @ref seekBy() / @ref seekTo() while the animation is either playing or pause will cause it to jump to specified time -- the next call to @ref advance() will update the destination locations and/or fire user-defined callbacks with new values, behaving as if the animation was played / paused with the seek time. The callbacks are only ever fired from within the @ref advance() function, never from @ref pause(), @ref stop() or any other API. For managing global application you can use @ref Timeline, @ref std::chrono APIs or any other type that supports basic arithmetic. The time doesn't have to be monotonic or have constant speed, but note that non-continuous and backward time jumps may have worse performance than going monotonically forward. With the @ref Timeline in particular, it's recommended to never call @ref Timeline::stop() but control the player start/pause/stop state instead. See @ref Animation-Player-time-type "below" for more information about using different time types. @snippet MagnumAnimation.cpp Player-usage-playback @section Animation-Player-time-type Using custom time/key types In long-running apps it's not desirable to use @ref Magnum::Float "Float" for global application time, since its precision will deteriorate over time. Even after one hour the precision loss might start to get noticeable. To overcome this problem, it's possible to specify a type for time values that's different from type used for animation track keys. In contrast, using @ref Magnum::Float "Float" for animation track key values is usually good enough, as the tracks are never too long for this to become a problem --- and if the tracks *are* long, you can always use a different key type for them as well. A good choice is @ref std::chrono::nanoseconds as a time type and keeping track key values as @ref Magnum::Float "Float" seconds: @snippet MagnumAnimation.cpp Player-usage-chrono While there's a builtin support for the above, you are free to use any other type combination --- for that you need to provide a *scaler* function that will take care of converting a time difference to play iteration index and key value inside given iteration. The types should be implicitly constructible, and have basic arithmetic and comparison operators. In order to reduce header size, the @ref Player implementation is in a separate @ref Player.hpp file that you need to include to get all needed template function definitions. See also @ref compilation-speedup-hpp for more information. @snippet MagnumAnimation-custom.cpp Player-usage-custom @section Animation-Player-higher-order Higher-order players, animating time Sometimes you might want to control multiple players at the same time or animate player state. That's doable by creating specialized tracks that control given player via a state change callback. By adding more tracks you can control multiple players from a central location. @snippet MagnumAnimation.cpp Player-higher-order Besides state, you can also animate @ref setDuration() and @ref setPlayCount(), but be aware that setting those while the animation is playing might cause unwanted jumps and abrupt stops. Time is also completely in your control and you can employ another @ref Player instance to speed it up or slow it down for a particular animation: @snippet MagnumAnimation.cpp Player-higher-order-animated-time @section Animation-Player-explicit-specializations Explicit template specializations The following specializations are explicitly compiled into the @ref Animation library. For other specializations (e.g. using an integer key type) you have to use the @ref Player.hpp implementation file to avoid linker errors. See also @ref compilation-speedup-hpp for more information. - @ref Player "Player" - @ref Player "Player" @experimental */ template class Player { public: /** @brief Time type */ typedef T TimeType; /** @brief Key type */ typedef K KeyType; /** * @brief Scaler function type * * The function gets time from when the animation started and combined * duration of all tracks; returns play iteration index and key value * inside given iteration. The combined duration is guaranteed to be * always non-zero, zero durations are handled by the player itself. */ typedef std::pair(*Scaler)(T, K); /** * @brief Advance multiple players at the same time * * Equivalent to calling @ref advance(T) for each item in @p players. */ static void advance(T time, std::initializer_list>> players); /** @brief Constructor */ explicit Player(); /** * @brief Construct with a custom scaler function * @param scaler Scaler function */ explicit Player(Scaler scaler); /** @brief Copying is not allowed */ Player(const Player&) = delete; /** @brief Move constructor */ Player(Player&&) noexcept; ~Player(); /** @brief Copying is not allowed */ Player& operator=(const Player&) = delete; /** @brief Move assignment */ Player& operator=(Player&&) noexcept; /** @brief Time-to-key scaler */ Scaler scaler() const { return _scaler; } /** * @brief Duration * * If the duration was not set explicitly using @ref setDuration(), * returns value calculated implicitly from all added tracks. If no * tracks are added, returns default-constructed value. * @see @ref elapsed() */ Math::Range1D duration() const { return _duration; } /** * @brief Set duration * * The duration is initially a default-constructed value, then * calculated implicitly from added tracks. Setting it explicitly will * overwrite the implicitly calculated value. Adding a track after the * duration was set explicitly will extend the duration to span all * track durations. * * Setting a duration that extends beyond the keyframe values will * cause values of begin/end keyframes to be extrapolated according to * @ref Extrapolation specified for given track. Setting a shorter * duration will cause only a slice of all tracks to be played. * * Modifying this value while @ref state() is @ref State::Playing may * cause the animation to jump or abruptly stop after next call to * @ref advance(). * @see @ref TrackView::duration() */ Player& setDuration(const Math::Range1D& duration) { _duration = duration; return *this; } /** @brief Play count */ UnsignedInt playCount() const { return _playCount; } /** * @brief Set play count * * By default, play count is set to @cpp 1 @ce, meaning the animation * @ref duration() is played once. Value of @cpp 0 @ce means the * animation is repeated indefinitely. * * Modifying this value while @ref state() is @ref State::Playing may * cause the animation to jump or abruptly stop after next call to * @ref advance(). */ Player& setPlayCount(UnsignedInt count) { _playCount = count; return *this; } /** * @brief Whether the player is empty * * @see @ref size(), @ref add(), @ref addWithCallback(), * @ref addWithCallbackOnChange(), @ref addRawCallback() */ bool isEmpty() const; /** * @brief Count of tracks managed by this player * * @see @ref isEmpty(), @ref add(), @ref addWithCallback(), * @ref addWithCallbackOnChange(), @ref addRawCallback() */ std::size_t size() const; /** * @brief Track at given position * * Due to the type-erased nature of the player implementation, it's not * possible to know the exact track type. */ const TrackViewStorage& track(std::size_t i) const; /** * @brief Add a track with a result destination * * The @p destination is updated with new value after each call to * @ref advance() as long as the animation is playing. */ template Player& add(const TrackView& track, R& destination); /** @overload */ template Player& add(const TrackView& track, R& destination) { return add(reinterpret_cast&>(track), destination); } /** @overload * * Note that track ownership is *not* transferred to the @ref Player * and you have to ensure that it's kept in scope for the whole * lifetime of the @ref Player instance. */ #ifndef CORRADE_MSVC_COMPATIBILITY template Player& add(const Track& track, R& destination) { return add(TrackView{track}, destination); } #else /* MSVC 2015 and 2017 is clueless when it comes to trying to deduce the template parameters (C2893: Failed to specialize function template). MSVC 2019+ works with /permissive-. It works when calling add explicitly, but that makes the API hard to use and inconsistent between platforms. The only possible workaround is to make add() take *anything*, casting it to proper TrackView type and then calling add() with explicit template parameters. This also neatly resolves the Track/TrackView overload, as the static_cast is either a no-op or it invokes the conversion operator on Track. The original code also reportedly makes the Intellisense freezing like hell and adding this overload fixes the freezes. Three birds with one stone. */ template Player& add(const Track& track, R& destination) { return add(static_cast&>(track), destination); } #endif /** * @brief Add a track with a result callback * * The @p callback is called with current key value, interpolated * result value and the @p userData pointer after each call to * @ref advance() as long as the animation is playing. The key value is * guaranteed to never be outside of the @ref duration() ranage, with * the interpolated result always corresponding to that key value. * * See the overload below for a more convenient type-safe way to pass * user data. * @see @ref addRawCallback() */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallback(const TrackView& track, void(*callback)(K, const R&, void*), void* userData = nullptr); /** @overload */ template Player& addWithCallback(const TrackView& track, void(*callback)(K, const R&, void*), void* userData = nullptr); #else /* Otherwise the user would be forced to use the + operator to convert a lambda to a function pointer and (besides being weird and annoying) it's also not portable because it doesn't work on MSVC 2015 and older versions of MSVC 2017. OTOH, putting this in the docs would say nothing about how the callback signature should look. */ template Player& addWithCallback(const TrackView& track, Callback callback, void* userData = nullptr); template Player& addWithCallback(const TrackView& track, Callback callback, void* userData = nullptr) { return addWithCallback(reinterpret_cast&>(track), callback, userData); } #endif /** @overload * * Note that the track ownership is *not* transferred to the * @ref Player and you have to ensure that it's kept in scope for the * whole lifetime of the @ref Player instance. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallback(const Track& track, void(*callback)(K, const R&, void*), void* userData = nullptr); #elif !defined(CORRADE_MSVC_COMPATIBILITY) /* See above why */ template Player& addWithCallback(const Track& track, Callback callback, void* userData = nullptr) { return addWithCallback(TrackView{track}, callback, userData); } #else /* see the add() function for explanation */ template Player& addWithCallback(const Track& track, Callback callback, void* userData = nullptr) { return addWithCallback(static_cast&>(track), callback, userData); } #endif /** * @brief Add a track with a result callback * * Equivalent to calling the above with a lambda wrapper that casts * @cpp void* @ce back to @cpp U* @ce and dereferences it in order to * pass it to @p callback. There is no additional overhead compared to * the overload taking the @cpp void* @ce pointer, however see * @ref addRawCallback() for optimization possibilities. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallback(const TrackView& track, void(*callback)(K, const R&, U&), U& userData); /** @overload */ template Player& addWithCallback(const TrackView& track, void(*callback)(K, const R&, U&), U& userData); #else /* See above why */ template Player& addWithCallback(const TrackView& track, Callback callback, U& userData); template Player& addWithCallback(const TrackView& track, Callback callback, U& userData) { return addWithCallback(reinterpret_cast&>(track), callback, userData); } #endif /** @overload * * Note that the track ownership is *not* transferred to the * @ref Player and you have to ensure that it's kept in scope for the * whole lifetime of the @ref Player instance. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallback(const Track& track, void(*callback)(K, const R&, U&), U& userData); #elif !defined(CORRADE_MSVC_COMPATIBILITY) /* See above why */ template Player& addWithCallback(const Track& track, Callback callback, U& userData) { return addWithCallback(TrackView{track}, callback, userData); } #else /* see the add() function for explanation */ template Player& addWithCallback(const Track& track, Callback callback, U& userData) { return addWithCallback(static_cast&>(track), callback, userData); } #endif /** * @brief Add a track with a result callback that's called on change * * A combination of @ref add() and @ref addWithCallback() --- during * each call to @ref advance(), as long as the animation is playing, * the new value is compared to @p destination. If the new value is * different from the stored one, @p callback is called and * @p destination is updated. Note that in order to keep the memory * management inside the player class simple, the value can't be cached * inside and you are required to provide the @p destination location. * * See the overload below for a more convenient type-safe way to pass * user data. * @see @ref addRawCallback() */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallbackOnChange(const TrackView& track, void(*callback)(K, const R&, void*), R& destination, void* userData = nullptr); /** @overload */ template Player& addWithCallbackOnChange(const TrackView& track, void(*callback)(K, const R&, void*), R& destination, void* userData = nullptr); #else /* See above why */ template Player& addWithCallbackOnChange(const TrackView& track, Callback callback, R& destination, void* userData = nullptr); template Player& addWithCallbackOnChange(const TrackView& track, Callback callback, R& destination, void* userData = nullptr) { return addWithCallbackOnChange(reinterpret_cast&>(track), callback, destination, userData); } #endif /** @overload * * Note that the track ownership is *not* transferred to the * @ref Player and you have to ensure that it's kept in scope for the * whole lifetime of the @ref Player instance. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallbackOnChange(const Track& track, void(*callback)(K, const R&, void*), R& destination, void* userData = nullptr); #elif !defined(CORRADE_MSVC_COMPATIBILITY) /* See above why */ template Player& addWithCallbackOnChange(const Track& track, Callback callback, R& destination, void* userData = nullptr) { return addWithCallbackOnChange(TrackView{track}, callback, destination, userData); } #else /* see the add() function for explanation */ template Player& addWithCallbackOnChange(const Track& track, Callback callback, R& destination, void* userData = nullptr) { return addWithCallbackOnChange(static_cast&>(track), callback, destination, userData); } #endif /** * @brief Add a track with a result callback that's called on change * * Equivalent to calling the above with a lambda wrapper that casts * @cpp void* @ce back to @cpp U* @ce and dereferences it in order to * pass it to @p callback. There is no additional overhead compared to * the overload taking the @cpp void* @ce pointer, however see * @ref addRawCallback() for optimization possibilities. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallbackOnChange(const TrackView& track, void(*callback)(K, const R&, U&), R& destination, U& userData); /** @overload */ template Player& addWithCallbackOnChange(const TrackView& track, void(*callback)(K, const R&, U&), R& destination, U& userData); #else /* See above why */ template Player& addWithCallbackOnChange(const TrackView& track, Callback callback, R& destination, U& userData); template Player& addWithCallbackOnChange(const TrackView& track, Callback callback, R& destination, U& userData) { return addWithCallbackOnChange(reinterpret_cast&>(track), callback, destination, userData); } #endif /** @overload * * Note that the track ownership is *not* transferred to the * @ref Player and you have to ensure that it's kept in scope for the * whole lifetime of the @ref Player instance. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addWithCallbackOnChange(const Track& track, void(*callback)(K, const R&, void*), R& destination, U& userData); #elif !defined(CORRADE_MSVC_COMPATIBILITY) /* See above why */ template Player& addWithCallbackOnChange(const Track& track, Callback callback, R& destination, U& userData) { return addWithCallbackOnChange(TrackView{track}, callback, destination, userData); } #else /* see the add() function for explanation */ template Player& addWithCallbackOnChange(const Track& track, Callback callback, R& destination, U& userData) { return addWithCallbackOnChange(static_cast&>(track), callback, destination, userData); } #endif /** * @brief Add a track with a raw callback * * This is a low-level function meant to be used if you want to avoid * the extra overhead of an additional callback in @ref addWithCallback() * or @ref addWithCallbackOnChange(), want more flexibility in the user * callback or want to control the track interpolation directly --- for * example taking advantage of @ref TrackView::atStrict() or passing an * inlineable interpolator function instead of using the saved * interpolator function pointer. * * The callback takes the raw @ref TrackViewStorage reference (which * you need to cast to a correct type), the interpolated key and hint * that's meant to be passed to @ref TrackView::at(), the destination * pointer (equivalent to the one passed to @ref add()), user callback * pointer (which again needs to be cast to a correct type) and user * data pointer. The following code snippet shows implementation of the * @ref addWithCallbackOnChange() API using this function, using a * custom callback to add a value to a vector if it changes: * * @snippet MagnumAnimation.cpp Player-addRawCallback */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addRawCallback(const TrackView& track, void(*callback)(const TrackViewStorage&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userData); /** @overload */ template Player& addRawCallback(const TrackView& track, void(*callback)(const TrackViewStorage&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userData); #else template Player& addRawCallback(const TrackView& track, Callback callback, void* destination, void(*userCallback)(), void* userData); template Player& addRawCallback(const TrackView& track, Callback callback, void* destination, void(*userCallback)(), void* userData) { return addRawCallback(reinterpret_cast&>(track), callback, destination, userCallback, userData); } #endif /** @overload * * Note that the track ownership is *not* transferred to the * @ref Player and you have to ensure that it's kept in scope for the * whole lifetime of the @ref Player instance. */ #ifdef DOXYGEN_GENERATING_OUTPUT template Player& addRawCallback(const Track& track, void(*callback)(const TrackViewStorage&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userData); #elif !defined(CORRADE_MSVC_COMPATIBILITY) /* See above why */ template Player& addRawCallback(const Track& track, Callback callback, void* destination, void(*userCallback)(), void* userData) { return addRawCallback(TrackView{track}, callback, destination, userCallback, userData); } #else /* see the add() function for explanation */ template Player& addRawCallback(const Track& track, Callback callback, void* destination, void(*userCallback)(), void* userData) { return addRawCallback(static_cast&>(track), callback, destination, userCallback, userData); } #endif /** * @brief State * * The player is @ref State::Stopped by default. * @see @ref play(), @ref pause(), @ref stop(), @ref setState() */ State state() const { return _state; } /** * @brief Elapsed animation iteration and keyframe * * Returns repeat iteration index and elapsed animation keyframe in * given iteration corresponding to @p time. If @ref state() is * @ref State::Stopped and the player was stopped explicitly, the * function returns a default-constructed value (usually * @cpp {0, 0.0f} @ce). If @ref state() is @ref State::Stopped due to * the animation running out, the function returns the iteration count * and duration end keyframe. If @ref state() is @ref State::Paused, * the function returns a time at which the animation was paused. * * Unlike @ref advance(), this function doesn't modify the animation * state in any way, it's merely a query. * @see @ref duration() */ std::pair elapsed(T time) const; /** * @brief Play * * Starts playing all tracks added to the player at given @p startTime. * If @ref state() is already @ref State::Playing, the animation is * restarted from the beginning at @p startTime. If @ref state() is * @ref State::Paused, the animation continues from the time that was * passed to @ref pause(). * * If @p startTime is in the future (that is, time passed to the next * @ref advance() iteration will be less than @p startTime), * @ref advance() will do nothing until given point in the future. * Setting time to such a particular value can be used to synchronize * playback of multiple independent animation clips. * @see @ref setState() */ Player& play(T startTime); /** * @brief Resume * * Behaves similarly to @ref play(), but doesn't restart the animation * from the beginning when @ref state() is already @ref State::Playing. */ Player& resume(T startTime); /** * @brief Pause * * Pauses the currently playing animation at given @p pauseTime. If * @ref state() is not @ref State::Playing, the function does nothing. * If @p pauseTime is too far in the future, the animation will get * paused at the end (i.e., not stopped). See @ref advance() for a * detailed description of behavior when the animation gets paused. * @see @ref setState() */ Player& pause(T pauseTime); /** * @brief Seek by given time delta * * Causes the animation to jump forward (if @p timeDelta is positive) * or backward (if @p timeDelta is negative). If @ref state() is * @ref State::Paused, seeking too far backward will make the animation * paused at the beginning, while seeking too far forward will cause * it paused at the end (i.e., not stopped). If @ref state() is already * @ref State::Stopped, the function does nothing. See @ref advance() * for a detailed description of seeking behavior. * * @note This function doesn't clamp the seek in any way --- so for * example seeking too far back will make the animation wait for * being played from the beginning in the future. */ Player& seekBy(T timeDelta); /** * @brief Seek to given absolute animation time * * Causes the animation to jump to @p animationTime at given * @p seekTime. If @ref state() is @ref State::Playing, seeking too far * backward will make the animation start from the beginning, while * seeking too far forward will cause the animation to be stopped. If * @ref state() is @ref State::Paused, seeking too far backward will * make the animation paused at the beginning, while seeking too far * forward will cause it paused at the end (i.e., not stopped). If * @ref state() is @ref State::Stopped, the function does nothing. See * @ref advance() for a detailed description of seeking behavior. * * @note This function doesn't clamp the seek in any way --- so for * example seeking too far back will make the animation wait for * being played from the beginning in the future. */ Player& seekTo(T seekTime, T animationTime); /** * @brief Stop * * Stops the currently playing animation. If @ref state() is * @ref State::Paused, discards the pause information. If @ref state() * is already @ref State::Stopped, the function does nothing. See * @ref advance() for a detailed description of behavior when the * animation gets stopped. * @see @ref setState() */ Player& stop(); /** * @brief Set state * * Convenience function that calls @ref play(), @ref pause() or * @ref stop() based on @p state. See documentation of these functions * for detailed description. The @p time parameter is used only when * @p state is @ref State::Playing or @ref State::Paused, it's ignored * for @ref State::Stopped. */ Player& setState(State state, T time); /** * @brief Advance the animation * * As long as @ref state() is @ref State::Playing, goes through all * tracks added with @ref add(), @ref addWithCallback() or * @ref addWithCallbackOnChange() in order they were added and updates * the destination locations and/or fires the callbacks with * interpolation results. * * If @ref state() is @ref State::Paused or @ref State::Stopped, the * function does nothing. If @p time is less than time that was passed * to @ref play(), the function does nothing. If @p time is large * enough that @ref duration() times @ref playCount() got exhausted, * the function will update destination locations and/or fire * user-defined callback with key and result values corresponding to * the end time of @ref duration() in order to correctly "park" the * animation. The state then becomes @ref State::Stopped and no more * updates are done until the animation is started again. * * If @ref pause() was called right before a particular @ref advance() * iteration, the function will update destination locations and/or * fire user-defined callbacks with key and result values corresponding * to the time passed to the @ref pause() call before in order to * correctly "park" the animation. After that, no more updates are done * until the animation is started again or @ref seekBy() / @ref seekTo() * is called. * * If @ref stop() was called right before a particular @ref advance() * iteration, the function will update destination locations and/or * fire user-defined callbacks with key and result values corresponding * to the begin time of @ref duration() in order to correctly "park" * the animation back to its initial state. After that, no more updates * are done until the animation is started again. * * If @ref seekBy() or @ref seekTo() was called right before a * particular @ref advance() iteration and @ref state() is * @ref State::Paused, the function will update destination locations * and/or fire user-defined callbacks with key and result values * corresponding to the new pause time in order to correctly "park" the * animation. After that, no more updates are done until the animation * is started again or @ref seekBy() / @ref seekTo() is called. * @see @ref elapsed() */ Player& advance(T time); private: struct Track; Player& addInternal(const TrackViewStorage& track, void (*advancer)(const TrackViewStorage&, K, std::size_t&, void*, void(*)(), void*), void* destination, void(*userCallback)(), void* userCallbackData); Containers::Optional> elapsedInternal(T time, T& updatedStartTime, T& updatedPauseTime, State& updatedState) const; Containers::Array _tracks; Math::Range1D _duration; UnsignedInt _playCount{1}; State _state{State::Stopped}; T _startTime{}, _stopPauseTime{}; Scaler _scaler; }; template template Player& Player::add(const TrackView& track, R& destination) { return addInternal(track, [](const TrackViewStorage& track, K key, std::size_t& hint, void* destination, void(*)(), void*) { *static_cast(destination) = static_cast&>(track).at(key, hint); }, &destination, nullptr, nullptr); } #ifndef DOXYGEN_GENERATING_OUTPUT template template Player& Player::addWithCallback(const TrackView& track, Callback callback, void* userData) { auto callbackPtr = static_cast(callback); return addInternal(track, [](const TrackViewStorage& track, K key, std::size_t& hint, void*, void(*callback)(), void* userData) { /** @todo try to use atStrict() if possible */ reinterpret_cast(callback)(key, static_cast&>(track).at(key, hint), userData); }, nullptr, reinterpret_cast(callbackPtr), userData); } template template Player& Player::addWithCallback(const TrackView& track, Callback callback, U& userData) { auto callbackPtr = static_cast(callback); return addInternal(track, [](const TrackViewStorage& track, K key, std::size_t& hint, void*, void(*callback)(), void* userData) { /** @todo try to use atStrict() if possible */ reinterpret_cast(callback)(key, static_cast&>(track).at(key, hint), *static_cast(userData)); }, nullptr, reinterpret_cast(callbackPtr), &userData); } template template Player& Player::addWithCallbackOnChange(const TrackView& track, Callback callback, R& destination, void* userData) { auto callbackPtr = static_cast(callback); return addInternal(track, [](const TrackViewStorage& track, K key, std::size_t& hint, void* destination, void(*callback)(), void* userData) { /** @todo try to use atStrict() if possible */ R result = static_cast&>(track).at(key, hint); if(result == *static_cast(destination)) return; reinterpret_cast(callback)(key, result, userData); *static_cast(destination) = result; }, &destination, reinterpret_cast(callbackPtr), userData); } template template Player& Player::addWithCallbackOnChange(const TrackView& track, Callback callback, R& destination, U& userData) { auto callbackPtr = static_cast(callback); return addInternal(track, [](const TrackViewStorage& track, K key, std::size_t& hint, void* destination, void(*callback)(), void* userData) { /** @todo try to use atStrict() if possible */ R result = static_cast&>(track).at(key, hint); if(result == *static_cast(destination)) return; reinterpret_cast(callback)(key, result, *static_cast(userData)); *static_cast(destination) = result; }, &destination, reinterpret_cast(callbackPtr), &userData); } template template Player& Player::addRawCallback(const TrackView& track, Callback callback, void* destination, void(*userCallback)(), void* userData) { auto callbackPtr = static_cast&, K, std::size_t&, void*, void(*)(), void*)>(callback); return addInternal(track, callbackPtr, destination, userCallback, userData); } #endif #if defined(CORRADE_TARGET_WINDOWS) && !(defined(CORRADE_TARGET_MINGW) && !defined(CORRADE_TARGET_CLANG)) extern template class MAGNUM_EXPORT Player; extern template class MAGNUM_EXPORT Player; #endif }} #endif