From bc3600cde8337526100cc0bf995659e37e4bc754 Mon Sep 17 00:00:00 2001 From: attila Date: Tue, 1 Nov 2022 11:15:39 +0100 Subject: [PATCH] Animation: Add juce_animation module --- .../Utility/Helpers/jucer_MiscUtilities.cpp | 1 + modules/CMakeLists.txt | 1 + .../animation/juce_Animator.cpp | 234 ++++++++++++++ .../juce_animation/animation/juce_Animator.h | 210 +++++++++++++ .../animation/juce_AnimatorSetBuilder.cpp | 291 ++++++++++++++++++ .../animation/juce_AnimatorSetBuilder.h | 143 +++++++++ .../animation/juce_AnimatorUpdater.cpp | 100 ++++++ .../animation/juce_AnimatorUpdater.h | 107 +++++++ .../juce_animation/animation/juce_Easings.cpp | 156 ++++++++++ .../juce_animation/animation/juce_Easings.h | 232 ++++++++++++++ .../animation/juce_StaticAnimationLimits.h | 131 ++++++++ .../animation/juce_VBlankAnimatorUpdater.h | 59 ++++ .../animation/juce_ValueAnimatorBuilder.cpp | 112 +++++++ .../animation/juce_ValueAnimatorBuilder.h | 276 +++++++++++++++++ .../detail/chromium/cubic_bezier.cc | 272 ++++++++++++++++ .../detail/chromium/cubic_bezier.h | 109 +++++++ .../detail/juce_ArrayAndTupleOps.h | 141 +++++++++ modules/juce_animation/juce_animation.cpp | 70 +++++ modules/juce_animation/juce_animation.h | 76 +++++ .../juce_gui_basics/desktop/juce_Desktop.h | 7 +- .../layout/juce_ComponentAnimator.h | 5 +- .../windows/juce_VBlankAttachment.h | 2 +- 22 files changed, 2731 insertions(+), 4 deletions(-) create mode 100644 modules/juce_animation/animation/juce_Animator.cpp create mode 100644 modules/juce_animation/animation/juce_Animator.h create mode 100644 modules/juce_animation/animation/juce_AnimatorSetBuilder.cpp create mode 100644 modules/juce_animation/animation/juce_AnimatorSetBuilder.h create mode 100644 modules/juce_animation/animation/juce_AnimatorUpdater.cpp create mode 100644 modules/juce_animation/animation/juce_AnimatorUpdater.h create mode 100644 modules/juce_animation/animation/juce_Easings.cpp create mode 100644 modules/juce_animation/animation/juce_Easings.h create mode 100644 modules/juce_animation/animation/juce_StaticAnimationLimits.h create mode 100644 modules/juce_animation/animation/juce_VBlankAnimatorUpdater.h create mode 100644 modules/juce_animation/animation/juce_ValueAnimatorBuilder.cpp create mode 100644 modules/juce_animation/animation/juce_ValueAnimatorBuilder.h create mode 100644 modules/juce_animation/detail/chromium/cubic_bezier.cc create mode 100644 modules/juce_animation/detail/chromium/cubic_bezier.h create mode 100644 modules/juce_animation/detail/juce_ArrayAndTupleOps.h create mode 100644 modules/juce_animation/juce_animation.cpp create mode 100644 modules/juce_animation/juce_animation.h diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_MiscUtilities.cpp b/extras/Projucer/Source/Utility/Helpers/jucer_MiscUtilities.cpp index c750703181..25cefcc2b9 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_MiscUtilities.cpp +++ b/extras/Projucer/Source/Utility/Helpers/jucer_MiscUtilities.cpp @@ -273,6 +273,7 @@ StringArray getJUCEModules() noexcept static StringArray juceModuleIds = { "juce_analytics", + "juce_animation", "juce_audio_basics", "juce_audio_devices", "juce_audio_formats", diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 8c60c7814e..9119dc7933 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -34,6 +34,7 @@ juce_add_modules( INSTALL_PATH "include/JUCE-${JUCE_VERSION}/modules" ALIAS_NAMESPACE juce juce_analytics + juce_animation juce_audio_basics juce_audio_devices juce_audio_formats diff --git a/modules/juce_animation/animation/juce_Animator.cpp b/modules/juce_animation/animation/juce_Animator.cpp new file mode 100644 index 0000000000..28e4511704 --- /dev/null +++ b/modules/juce_animation/animation/juce_Animator.cpp @@ -0,0 +1,234 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +class Animator::Impl +{ +public: + virtual ~Impl() = default; + + virtual double getDurationMs() const + { + return 0.0; + } + + void start() + { + shouldStart = true; + shouldComplete = false; + } + + void complete() + { + shouldComplete = true; + } + + Animator::Status update (double timestampMs) + { + if (std::exchange (shouldStart, false)) + { + onStart (timestampMs); + running = true; + } + + if (! running) + return Animator::Status::idle; + + const auto status = internalUpdate (timestampMs); + + if (status != Animator::Status::finished && ! shouldComplete) + return status; + + shouldComplete = false; + running = false; + onComplete(); + return Animator::Status::finished; + } + + virtual bool isComplete() const { return shouldComplete; } + + virtual void onStart (double timeStampMs) = 0; + virtual void onComplete() = 0; + virtual Animator::Status internalUpdate (double timestampMs) = 0; + + bool shouldStart = false, shouldComplete = false, running = false; +}; + +//============================================================================== +Animator::Animator (std::shared_ptr ai) + : ptr (std::move (ai)) +{ + jassert (ptr != nullptr); +} + +double Animator::getDurationMs() const +{ + return ptr->getDurationMs(); +} + +void Animator::start() const +{ + ptr->start(); +} + +void Animator::complete() const +{ + ptr->complete(); +} + +Animator::Status Animator::update (double timestampMs) const +{ + return ptr->update (timestampMs); +} + +bool Animator::isComplete() const +{ + return ptr->isComplete(); +} + +//============================================================================== +#if JUCE_UNIT_TESTS + +struct AnimatorTests : public UnitTest +{ + AnimatorTests() + : UnitTest ("Animator", UnitTestCategories::gui) + { + } + + struct TestEvents + { + enum class TestEventType + { + animatorStarted, + animatorEnded + }; + + struct TestEvent + { + TestEvent (TestEventType e, int a) : eventType (e), animatorId (a) {} + + TestEventType eventType; + int animatorId; + }; + + void started (int animatorId) + { + events.emplace_back (TestEventType::animatorStarted, animatorId); + } + + void ended (int animatorId) + { + events.emplace_back (TestEventType::animatorEnded, animatorId); + } + + bool animatorStartedBeforeAnotherStarted (int animator, int before) + { + for (const auto& event : events) + { + if (event.animatorId == before && event.eventType == TestEventType::animatorStarted) + return false; + + if (event.animatorId == animator && event.eventType == TestEventType::animatorStarted) + return true; + } + + return false; + } + + bool animatorStartedBeforeAnotherEnded (int animator, int before) + { + for (const auto& event : events) + { + if (event.animatorId == before && event.eventType == TestEventType::animatorEnded) + return false; + + if (event.animatorId == animator && event.eventType == TestEventType::animatorStarted) + return true; + } + + return false; + } + + private: + std::vector events; + }; + + void runTest() override + { + beginTest ("AnimatorSet starts end ends Animators in the right order"); + { + TestEvents events; + + auto createTestAnimator = [&events] (int animatorId) + { + return ValueAnimatorBuilder {} + .withOnStartCallback ([&events, animatorId] + { + events.started (animatorId); + return [] (auto) {}; + }) + .withOnCompleteCallback ([&events, animatorId] + { + events.ended (animatorId); + }) + .build(); + }; + + auto stage1 = AnimatorSetBuilder { createTestAnimator (1) }; + stage1.followedBy (createTestAnimator (2)); + stage1.togetherWith (createTestAnimator (3)); + + Animator animator = stage1.build(); + animator.start(); + + for (double timeMs = 0.0; animator.update (timeMs) != Animator::Status::finished; timeMs += 16.667) + ; + + expect (events.animatorStartedBeforeAnotherEnded (1, 2)); + expect (! events.animatorStartedBeforeAnotherEnded (2, 1)); + expect (events.animatorStartedBeforeAnotherEnded (3, 1)); + expect (events.animatorStartedBeforeAnotherStarted (3, 2)); + } + } +}; + +static AnimatorTests animatorTests; + +#endif + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_Animator.h b/modules/juce_animation/animation/juce_Animator.h new file mode 100644 index 0000000000..70adfbb2c3 --- /dev/null +++ b/modules/juce_animation/animation/juce_Animator.h @@ -0,0 +1,210 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Wrapper class for managing the lifetime of all the different animator kinds created through the + builder classes. + + It uses reference counting. If you copy an Animator the resulting object will refer to the same + underlying instance, and the underlying instance is guaranteed to remain valid for as long as + you have an Animator object referencing it. + + An Animator object can be registered with the AnimatorUpdater, which only stores a weak + reference to the underlying instance. If an AnimatorUpdater references the underlying instance + and it becomes deleted due to all Animator objects being deleted, the updater will automatically + remove it from its queue, so manually removing it is not required. + + @see ValueAnimatorBuilder, AnimatorSetBuilder, AnimatorUpdater, VBlankAnimatorUpdater + + @tags{Animations} +*/ +class JUCE_API Animator +{ +public: + class Impl; + + /** The state of an Animator that determines how AnimatorUpdaters and other Animators will + interact with it. + */ + enum class Status + { + /** The Animator is idle and its state is not progressing even if it is attached to an + AnimatorUpdater. + */ + idle, + + /** The Animator is active and its state is progressing whenever its update function is + called. + */ + inProgress, + + /** The Animator finished its run and its onCompletion callback may be called. It requires + no further calls to its update function. + */ + finished + }; + + /** @internal + + Constructor. Used by the builder classes. + + @see ValueAnimatorBuilder, AnimatorSetBuilder + */ + explicit Animator (std::shared_ptr); + + /** Returns the total animation duration in milliseconds. */ + double getDurationMs() const; + + /** Marks the Animator ready for starting. You must call this function to allow the Animator to + move out of the idle state. + + After calling this function the Animator's on start callback will be executed at the next + update immediately followed by the first call to it's update function. + + You can call this function before or after adding the Animator to an AnimatorUpdater. Until + start() is called the Animator will just sit idly in the updater's queue. + */ + void start() const; + + /** Marks the Animator ready to be completed. ValueAnimators will be completed automatically + when they reach a progress >= 1.0 unless they are infinitely running. AnimatorSets will also + complete on their own when all of their constituent Animators complete. + + Using this function you can fast track the completion of an Animator. After calling this + function isComplete will return true, and it's guaranteed that you will receive an update + callback with a progress value of 1.0. After this the onComplete callback will be executed. + */ + void complete() const; + + /** Called periodically for active Animators by the AnimatorUpdater classes. The passed in + timestamp must be monotonically increasing. This allows the underlying Animator + to follow its progression towards completion. + + While you can call this function in special circumstances, you will generally want an + AnimatorUpdater to do it. Using the VBlankAnimatorUpdater ensures that update is called in + sync with the monitor's vertical refresh resulting in smooth animations. + + @see AnimatorUpdater, VBlankAnimatorUpdater + */ + Status update (double timestampMs) const; + + /** Returns true if the Animator has reached the point of completion either because complete() + has been called on it, or in case of the ValueAnimator, if it reached a progress of >= 1.0. + + You typically don't need to call this function, because in any case a completed Animator + will receive an update callback with a progress value of 1.0 and following that the + on complete callback will be called. + */ + bool isComplete() const; + + /** Comparison function used by the implementation to store Animators in ordered collections. + It can also be used to determine equality of Animator objects based on whether they + reference the same underlying implementation. + */ + struct JUCE_API Compare + { + /** Comparison function. */ + bool operator() (const Animator& a, const Animator& b) const { return a.ptr < b.ptr; } + }; + + /** @internal + + Stores a weak reference to the Animator's underlying implementation. Animator objects store + a strong reference to the implementation, so it won't be deleted as long as an Animator + object references it. Instead of copying the Animator, you can use makeWeak() to create a + weak reference, which will not prevent deletion of the underlying implementation, but allows + you to create a strong reference using the lock function for as long as the underlying + object is alive. + + This class is used by the AnimatorUpdater, and it's unlikely you will need to use it + directly. + + @see AnimatorUpdater, VBlankAnimatorUpdater + */ + class JUCE_API Weak + { + public: + /** Constructor used by the Animator implementation. To obtain a weak reference use + Animator::makeWeak(). + */ + Weak() = default; + + /** Constructor used by the Animator implementation. To obtain a weak reference use + Animator::makeWeak(). + */ + explicit Weak (std::shared_ptr p) : ptr (p), originalPtr (p.get()) {} + + /** If the referenced Animator implementation object still exists it returns an Animator + object storing a strong reference to it. + + If the implementation object was deleted it returns a nullopt. + */ + std::optional lock() const + { + if (const auto l = ptr.lock()) + return Animator { l }; + + return {}; + } + + /** @internal + + Used internally for storing the reference in a std::map. + */ + void* getKey() const + { + return originalPtr; + } + + private: + std::weak_ptr ptr; + Impl* originalPtr{}; + }; + + /** @internal + + Returns a weak reference to the underlying implementation. You can use Weak::lock() to + obtain a strong reference as long as the underlying object has not been deleted. + */ + Weak makeWeak() const { return Weak { ptr }; } + +private: + std::shared_ptr ptr; +}; + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_AnimatorSetBuilder.cpp b/modules/juce_animation/animation/juce_AnimatorSetBuilder.cpp new file mode 100644 index 0000000000..dfec8a9182 --- /dev/null +++ b/modules/juce_animation/animation/juce_AnimatorSetBuilder.cpp @@ -0,0 +1,291 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +class DelayAnimator : public Animator::Impl +{ +public: + double getDurationMs() const override { return delayMs; } + + static Animator build (double delayMsIn, std::function callbackIn = nullptr) + { + return Animator { rawToUniquePtr (new DelayAnimator (delayMsIn, std::move (callbackIn))) }; + } + +private: + DelayAnimator (double delayMsIn, std::function callbackIn) + : onCompletion (std::move (callbackIn)), + delayMs (delayMsIn) + { + } + + Animator::Status internalUpdate (double timestampMs) override + { + if (timestampMs - startedAtMs >= delayMs) + return Animator::Status::finished; + + return Animator::Status::inProgress; + } + + void onStart (double timeMs) override { startedAtMs = timeMs; } + + void onComplete() override { NullCheckedInvocation::invoke (onCompletion); } + + std::function onCompletion; + double startedAtMs = 0.0, delayMs = 0.0; +}; + +//============================================================================== +struct AnimatorSetData +{ + explicit AnimatorSetData (Animator root) + : roots { root }, entries { { root, {} } } {} + + struct Entry + { + std::optional parent; // If no parent, this is the root node + std::set children; + }; + + auto getRoots() const { return roots; } + + auto getChildren (const Animator& a) const + { + if (const auto iter = entries.find (a); iter != entries.end()) + return iter->second.children; + + return std::set(); + } + + std::set roots; + std::map entries; + std::function timeTransform; +}; + +class AnimatorSet : public Animator::Impl +{ +public: + explicit AnimatorSet (AnimatorSetData dataIn) : data (std::move (dataIn)) {} + + double getDurationMs() const override + { + const auto roots = data.getRoots(); + return getMaxDuration (roots.begin(), roots.end()); + } + +private: + void onStart (double timestampMs) override + { + startedAtMs = timestampMs; + active = data.getRoots(); + + for (const auto& i : active) + i.start(); + } + + void onComplete() override {} + + Animator::Status internalUpdate (double timestampMs) override + { + const auto internalTimestampMs = [&] + { + if (data.timeTransform == nullptr) + return timestampMs; + + return data.timeTransform (timestampMs - startedAtMs); + }(); + + if (isComplete()) + { + for (auto i : active) + i.complete(); + + while (updateAnimatorSet (internalTimestampMs) != Animator::Status::finished) + { + } + + return Animator::Status::finished; + } + + return updateAnimatorSet (internalTimestampMs); + } + + Animator::Status updateAnimatorSet (double timestampMs) + { + std::set animatorsToRemove; + const auto currentlyActive = active; + + for (const auto& animator : currentlyActive) + { + const auto status = animator.update (timestampMs); + + if (status == Animator::Status::finished) + { + animatorsToRemove.insert (animator); + + for (const auto& j : data.getChildren (animator)) + { + j.start(); + + if (isComplete()) + j.complete(); + + active.insert (j); + } + } + } + + for (auto i : animatorsToRemove) + active.erase (i); + + return active.empty() ? Animator::Status::finished : Animator::Status::inProgress; + } + + template + double getMaxDuration (It begin, It end) const + { + return std::accumulate (begin, end, 0.0, [this] (const auto acc, const auto& anim) + { + const auto children = data.getChildren (anim); + return std::max (acc, anim.getDurationMs() + getMaxDuration (children.begin(), children.end())); + }); + } + + const AnimatorSetData data; + std::set active; + double startedAtMs = 0.0; +}; + +//============================================================================== +struct AnimatorSetBuilder::AnimatorSetBuilderState +{ + explicit AnimatorSetBuilderState (Animator animator) + : data (std::move (animator)) {} + + bool valid = true; + AnimatorSetData data; +}; + +AnimatorSetBuilder::AnimatorSetBuilder (Animator startingAnimator) + : AnimatorSetBuilder (startingAnimator, + std::make_shared (startingAnimator)) +{} + +AnimatorSetBuilder::AnimatorSetBuilder (double delayMs) + : AnimatorSetBuilder (DelayAnimator::build (delayMs, nullptr)) +{} + +AnimatorSetBuilder::AnimatorSetBuilder (std::function cb) + : AnimatorSetBuilder (DelayAnimator::build (0.0, std::move (cb))) +{} + +AnimatorSetBuilder::AnimatorSetBuilder (std::shared_ptr dataIn) + : cursor (*dataIn->data.getRoots().begin()), state (std::move (dataIn)) +{} + +AnimatorSetBuilder::AnimatorSetBuilder (Animator cursorIn, std::shared_ptr dataIn) + : cursor (cursorIn), state (std::move (dataIn)) +{} + +AnimatorSetBuilder AnimatorSetBuilder::togetherWith (Animator animator) +{ + add (state->data.entries.at (cursor).parent, animator); + return { animator, state }; +} + +AnimatorSetBuilder AnimatorSetBuilder::followedBy (Animator animator) +{ + add (cursor, animator); + return { animator, state }; +} + +void AnimatorSetBuilder::add (std::optional parent, Animator child) +{ + state->data.entries.emplace (child, AnimatorSetData::Entry { parent, {} }); + + if (parent.has_value()) + state->data.entries.at (*parent).children.insert (child); + else + state->data.roots.insert (child); +} + +Animator AnimatorSetBuilder::build() +{ + if (state == nullptr) + { + /* If you're hitting this assertion, you've already used this AnimatorSetBuilder to build an + AnimatorSet. + + To create another AnimatorSet you need to use another AnimatorSetBuilder independently + created with the AnimatorSetBuilder (Animator) constructor. + */ + jassertfalse; + return ValueAnimatorBuilder{}.build(); + } + + auto animator = Animator { std::make_unique (std::move (state->data)) }; + state = nullptr; + return animator; +} + +AnimatorSetBuilder AnimatorSetBuilder::followedBy (double delayMs) +{ + return followedBy (DelayAnimator::build (delayMs, nullptr)); +} + +AnimatorSetBuilder AnimatorSetBuilder::followedBy (std::function cb) +{ + return followedBy (DelayAnimator::build (0.0, std::move (cb))); +} + +AnimatorSetBuilder AnimatorSetBuilder::togetherWith (double delayMs) +{ + return togetherWith (DelayAnimator::build (delayMs, nullptr)); +} + +AnimatorSetBuilder AnimatorSetBuilder::togetherWith (std::function cb) +{ + return togetherWith (DelayAnimator::build (0.0, std::move (cb))); +} + +AnimatorSetBuilder AnimatorSetBuilder::withTimeTransform (std::function timeTransformIn) +{ + state->data.timeTransform = std::move (timeTransformIn); + return *this; +} + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_AnimatorSetBuilder.h b/modules/juce_animation/animation/juce_AnimatorSetBuilder.h new file mode 100644 index 0000000000..1ff8b42b4a --- /dev/null +++ b/modules/juce_animation/animation/juce_AnimatorSetBuilder.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A builder class that can be used to construct an Animator wrapping an AnimatorSet + implementation. It allows you to compose larger, complex animations by executing multiple + constituent Animator instances in a coordinated manner. It essentially builds an Animator + with an execution graph referencing other Animators. + + Unlike ValueAnimatorBuilder, objects of AnimatorSetBuilder returned by its member functions + reference the same underlying, modifiable builder instance. For this reason build() can be + called only once on an underlying builder instance. This is to allow you to attach Animators to + different points of the execution graph. + + E.g. to have two functions followed by different amounts of delay, each followed by another + function you would write the following. + + @code + // Both objects reference the same execution graph, but also refer to different Animators in it + auto builderReferencingFirst = AnimatorSetBuilder { firstFunction }; + auto builderReferencingSecond = builderReferencingFirst.togetherWith (secondFunction); + + builderReferencingFirst.followedBy (200).followedBy (thirdFunction); + builderReferencingSecond.followedBy (500).followedBy (fourthFunction); + + // You could use any one of the builder objects that refer to the same execution graph + auto animator = builderReferencingFirst.build(); + @endcode + + @tags{Animations} +*/ +class JUCE_API AnimatorSetBuilder +{ +public: + /** Creates a new builder instance specifying the startingAnimator as the first Animator that is + started. + */ + explicit AnimatorSetBuilder (Animator startingAnimator); + + /** Creates a builder with an empty starting animation that completes after delayMs. + */ + explicit AnimatorSetBuilder (double delayMs); + + /** Creates a builder with a starting animation that completes at the first update and executes + the provided callback function. + */ + explicit AnimatorSetBuilder (std::function cb); + + /** Adds an Animator to the execution graph that will start executing at the same time as the + Animator provided last to this builder object. + */ + AnimatorSetBuilder togetherWith (Animator animator); + + /** Adds an empty Animator to the execution graph that will start executing at the same time as + the Animator provided last to this builder object, and completes in delayMs. + */ + AnimatorSetBuilder togetherWith (double delayMs); + + /** Adds an empty Animator to the execution graph that will start executing at the same time as + the Animator provided last to this builder object, completes upon its first update, and + executes the provided callback. + */ + AnimatorSetBuilder togetherWith (std::function cb); + + /** Adds an Animator to the execution graph that will start executing after the Animator + provided last to this builder object completes. + */ + AnimatorSetBuilder followedBy (Animator animator); + + /** Adds an empty Animator to the execution graph that will start executing after the Animator + provided last to this builder object + */ + AnimatorSetBuilder followedBy (double delayMs); + + /** Adds an empty Animator to the execution graph that will start executing after the Animator + provided last to this builder object, completes upon its first update, and executes the + provided callback. + */ + AnimatorSetBuilder followedBy (std::function cb); + + /** Specifies a time transformation function that the built Animator should utilise, allowing + accelerating and decelerating the entire set of Animators. + + The provided function should be monotonically increasing. + */ + AnimatorSetBuilder withTimeTransform (std::function transform); + + /** Builds an Animator that executes the previously described and parameterised execution graph. + + This function should only be called once for every AnimatorSetBuilder created by its public + constructor. + */ + Animator build(); + +private: + struct AnimatorSetBuilderState; + + explicit AnimatorSetBuilder (std::shared_ptr dataIn); + + AnimatorSetBuilder (Animator cursorIn, std::shared_ptr dataIn); + + void add (std::optional parent, Animator child); + + Animator cursor; + std::shared_ptr state; +}; + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_AnimatorUpdater.cpp b/modules/juce_animation/animation/juce_AnimatorUpdater.cpp new file mode 100644 index 0000000000..80318bebb7 --- /dev/null +++ b/modules/juce_animation/animation/juce_AnimatorUpdater.cpp @@ -0,0 +1,100 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +void AnimatorUpdater::addAnimator (const Animator& animator) +{ + addAnimator (animator, nullptr); +} + +void AnimatorUpdater::addAnimator (const Animator& animator, std::function onComplete) +{ + Entry entry { animator.makeWeak(), std::move (onComplete) }; + animators[entry.animator.getKey()] = std::move (entry); +} + +void AnimatorUpdater::removeAnimator (const Animator& animator) +{ + if (auto it = animators.find (animator.makeWeak().getKey()); it != animators.end()) + { + if (it == currentIterator) + { + ++currentIterator; + iteratorServiced = false; + } + + animators.erase (it); + } +} + +void AnimatorUpdater::update() +{ + if (reentrancyGuard) + { + // If this is hit, one of the animators is trying to update itself + // recursively. This is a bad idea! Inspect the callstack to find the + // cause of the problem. + jassertfalse; + return; + } + + const ScopedValueSetter setter { reentrancyGuard, true }; + + const auto timestampMs = Time::getMillisecondCounterHiRes(); + + for (currentIterator = animators.begin(); currentIterator != animators.end();) + { + auto& current = *currentIterator; + + if (const auto locked = current.second.animator.lock()) + { + iteratorServiced = true; + + if (locked->update (timestampMs) == Animator::Status::finished) + NullCheckedInvocation::invoke (current.second.onComplete); + + if (iteratorServiced && currentIterator != animators.end()) + ++currentIterator; + } + else + { + currentIterator = animators.erase (currentIterator); + } + } +} + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_AnimatorUpdater.h b/modules/juce_animation/animation/juce_AnimatorUpdater.h new file mode 100644 index 0000000000..a2cdc461d0 --- /dev/null +++ b/modules/juce_animation/animation/juce_AnimatorUpdater.h @@ -0,0 +1,107 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Helper class to update several animators at once, without owning or otherwise extending the + lifetimes of those animators. + + The intended use case is to register Animators with an updater as opposed to separately calling + Animator::update() on each of them. Calling update() then will update all registered Animators. + In case an Animator's underlying implementation is deleted (all Animator objects that were + strongly referencing it were deleted) it is automatically removed by the AnimatorUpdater. + + If you want to update all your Animators in sync with the display refresh you will probably want + to use the VBlankAnimatorUpdater. + + The order in which Animator::update() functions are called for registered Animators is not + specified, as Animators should be implemented in a way where it doesn't matter. + + @see VBlankAnimatorUpdater + + @tags{Animations} +*/ +class JUCE_API AnimatorUpdater +{ +public: + /** Registers an Animator with the updater. + */ + void addAnimator (const Animator& animator); + + /** Registers an Animator with the updater and specifies a callback to be called upon the + completion of the Animator. + + This callback can be used for cleanup purposes e.g. + + @code + animatorUpdater.addAnimator (someComponentPtr->getAnimator(), + [&someComponentPtr] { someComponentPtr.reset(); }); + @endcode + */ + void addAnimator (const Animator& animator, std::function onComplete); + + /** Removes an Animator + */ + void removeAnimator (const Animator& animator); + + /** Calls Animator::update() for all registered Animators that are still alive. References to + deleted Animators are removed. + */ + void update(); + +private: + struct JUCE_API Entry + { + Entry() = default; + + Entry(Animator::Weak animatorIn, std::function onCompleteIn) + : animator (animatorIn), + onComplete (std::move (onCompleteIn)) + {} + + Animator::Weak animator; + std::function onComplete; + }; + + std::map animators; + std::map::iterator currentIterator; + + bool iteratorServiced = false; + bool reentrancyGuard = false; +}; + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_Easings.cpp b/modules/juce_animation/animation/juce_Easings.cpp new file mode 100644 index 0000000000..c2ff4a494a --- /dev/null +++ b/modules/juce_animation/animation/juce_Easings.cpp @@ -0,0 +1,156 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +std::function Easings::createCubicBezier (float x1, float y1, float x2, float y2) +{ + // The x axis represents time, it's important this always stays in the range 0 - 1 + jassert (isPositiveAndNotGreaterThan (x1, 1.0f)); + jassert (isPositiveAndNotGreaterThan (x2, 1.0f)); + + chromium::gfx::CubicBezier cubicBezier { (double) x1, (double) y1, (double) x2, (double) y2 }; + return [bezier = std::move (cubicBezier)] (float v) { return (float) bezier.Solve (v); }; +} + +std::function Easings::createCubicBezier (Point controlPoint1, + Point controlPoint2) +{ + return createCubicBezier (controlPoint1.getX(), + controlPoint1.getY(), + controlPoint2.getX(), + controlPoint2.getY()); +} + +std::function Easings::createEase() +{ + const static auto f = createCubicBezier (0.25f, 0.1f, 0.25f, 1.0f); + return f; +} + +std::function Easings::createEaseIn() +{ + const static auto f = createCubicBezier (0.42f, 0.0f, 1.0f, 1.0f); + return f; +} + +std::function Easings::createEaseOut() +{ + const static auto f = createCubicBezier (0.0f, 0.0f, 0.58f, 1.0f);; + return f; +} + +std::function Easings::createEaseInOut() +{ + const static auto f = createCubicBezier (0.42f, 0.0f, 0.58f, 1.0f); + return f; +} + +std::function Easings::createLinear() +{ + return [] (auto x){ return x; }; +} + +std::function Easings::createEaseOutBack() +{ + const static auto f = createCubicBezier (0.34f, 1.56f, 0.64f, 1.0f); + return f; +} + +std::function Easings::createEaseInOutCubic() +{ + const static auto f = createCubicBezier (0.65f, 0.0f, 0.35f, 1.0f); + return f; +} + +std::function Easings::createSpring (const SpringEasingOptions& options) +{ + return [=] (float v) + { + const auto t = std::clamp (v, 0.0f, 1.0f); + const auto omega = 2.0f * MathConstants::pi * options.getFrequency(); + const auto physicalValue = 1.0f - std::exp (-options.getAttenuation() * t) * std::cos (omega * t); + const auto squish = 1.0f / options.getExtraAttenuationRange(); + const auto shift = 1.0f - options.getExtraAttenuationRange(); + const auto weight = std::clamp (std::pow (squish * (std::max (t - shift, 0.0f)), 2.0f), 0.0f, 1.0f); + return weight + (1.0f - weight) * physicalValue; + }; +} + +std::function Easings::createBounce (int numBounces) +{ + jassert (numBounces >= 0); + numBounces = std::max (0, numBounces); + + const auto alpha = std::pow (0.05f, 1.0f / (float) numBounces); + + const auto fallTime = [] (float h) + { + return std::sqrt (2.0f * h); + }; + + std::vector bounceTimes; + bounceTimes.reserve ((size_t) (numBounces + 1)); + bounceTimes.push_back (fallTime (1.0f)); + + for (int i = 1; i < numBounces + 1; ++i) + bounceTimes.push_back (bounceTimes.back() + 2.0f * fallTime (std::pow (alpha, (float) i))); + + for (auto& bounce : bounceTimes) + bounce /= bounceTimes.back(); + + return [alpha, times = std::move (bounceTimes)] (float v) + { + v = std::clamp (v, 0.0f, 1.0f); + + const auto boundIt = std::lower_bound (times.begin(), times.end(), v); + + if (boundIt == times.end()) + return 1.0f; + + const auto i = (size_t) std::distance (times.begin(), boundIt); + const auto height = i == 0 ? 1.0f : std::pow (alpha, (float) i); + const auto center = i == 0 ? 0.0f : (times[i] + times[i - 1]) / 2.0f; + const auto distToZero = i == 0 ? times[i] : (times[i] - times[i - 1]) / 2.0f; + return 1.0f - height * (1.0f - std::pow (1.0f / distToZero * (v - center), 2.0f)); + }; +} + +std::function Easings::createOnOffRamp() +{ + return [] (float x) { return 1.0f - std::abs (2.0f * (x - 0.5f)); }; +} + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_Easings.h b/modules/juce_animation/animation/juce_Easings.h new file mode 100644 index 0000000000..c3d25fe668 --- /dev/null +++ b/modules/juce_animation/animation/juce_Easings.h @@ -0,0 +1,232 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** A selection of options available for customising a spring style easing function. */ +class SpringEasingOptions +{ +public: + /** Specifies the number of oscillations the easing would undergo. + + This also affects the speed of the movement. + + @see getFrequency, withAttenuation, withExtraAttenuationRange + */ + [[nodiscard]] auto withFrequency (float newFrequency) const + { + return withMember (*this, &SpringEasingOptions::frequency, newFrequency); + } + + /** Affects how quickly the oscillations die down. + + @see getAttenuation, withFrequency, withExtraAttenuationRange + */ + [[nodiscard]] auto withAttenuation (float newAttenuation) const + { + return withMember (*this, &SpringEasingOptions::attenuation, newAttenuation); + } + + /** Specifies the input value at which an extra non-physical attenuation begins to be applied. + The value must be in the range [0.05f, 0.98f]. + + This ensures that the easing always reaches an output value of 1.0f when the input value is + 1.0f. If the attenuation is set sufficiently high this won't have a visible effect. + + @see getExtraAttenuationRange, withFrequency, withAttenuation + */ + [[nodiscard]] auto withExtraAttenuationRange (float newExtraAttenuationRange) const + { + return withMember (*this, &SpringEasingOptions::extraAttenuationRange, std::clamp (newExtraAttenuationRange, 0.05f, 0.98f)); + } + + /** Returns the value specified by withFrequency. + + If no value was specified the default value is 3.0f. + + @see withFrequency + */ + [[nodiscard]] auto getFrequency() const { return frequency; } + + /** Returns the value specified by withAttenuation. + + If no value was specified the default value is 3.0f. + + @see withAttenuation + */ + [[nodiscard]] auto getAttenuation() const { return attenuation; } + + /** Returns the value specified by withExtraAttenuationRange + + If no value was specified the default value is 0.25f. + + @see withExtraAttenuationRange + */ + [[nodiscard]] auto getExtraAttenuationRange() const { return extraAttenuationRange; } + +private: + float frequency = 3.0f; + float attenuation = 3.0f; + float extraAttenuationRange = 0.25f; +}; + +//============================================================================== +/** + Holds a number of easing functions that you can pass into ValueAnimatorBuilder::withEasing to + transform the linear progression of animations. + + Using createSpring() for example would transform a rigid movement into one that is reminiscent + of a weight attached to a spring. + + For examples of all the easing functions see the AnimationEasingDemo in the DemoRunner. + + @tags{Animations} +*/ +struct Easings +{ + /** Returns a cubic Bezier function with the control points (x1, y1), (x2, y2). These points are + the two middle points of a cubic Bezier function's four control points, the first and last + being (0, 0), and (1, 1). + */ + static std::function createCubicBezier (float x1, float y1, float x2, float y2); + + /** Returns a cubic Bezier function with two control points. These points are the two middle + points of a cubic Bezier function's four control points, the first and last being (0, 0), + and (1, 1). + */ + static std::function createCubicBezier (Point controlPoint1, + Point controlPoint2); + + /** Returns the easing function createCubicBezier (0.25f, 0.1f, 0.25f, 1.0f). This indicates + that the interpolation starts slowly, accelerates sharply, and then slows gradually towards + the end. It is similar to createEaseInOut(), though it accelerates more sharply at the + beginning. + + This is equivalent to using the "ease" keyword when specifying a timing-function in CSS. + + This is the default easing used by the ValueAnimatorBuilder class if no other easing + function is specified. + + @see createCubicBezier, createEaseIn, createEaseOut, createEaseInOut + */ + static std::function createEase(); + + /** Returns the easing function createCubicBezier (0.42f, 0.0f, 1.0f, 1.0f). This indicates that + the interpolation starts slowly, then progressively speeds up until the end, at which point + it stops abruptly. + + This is equivalent to using the "ease-in" keyword when specifying a timing-function in CSS. + + @see createCubicBezier, createEase, createEaseOut, createEaseInOut + */ + static std::function createEaseIn(); + + /** Returns the easing function createCubicBezier (0.0f, 0.0f, 0.58f, 1.0f). This indicates that + the interpolation starts abruptly and then progressively slows down towards the end. + + This is equivalent to using the "ease-out" keyword when specifying a timing-function in CSS. + + @see createCubicBezier, createEase, createEaseIn, createEaseInOut, createEaseOutBack + */ + static std::function createEaseOut(); + + /** Returns the easing function createCubicBezier (0.34f, 1.56f, 0.64f, 1.0f). This indicates + that the interpolation starts abruptly, quickly decelerating before overshooting the target + value by approximately 10% and changing direction to slowly head back towards the target + value. + + Like createSpring() this will overshoot causing it to return float values exceeding 1.0f. + + This is equivalent to easeOutBack as specified on https://easings.net/#easeOutBack. + + @see createCubicBezier, createEaseOutBack, createSpring + */ + static std::function createEaseOutBack(); + + /** Returns the easing function createCubicBezier (0.42f, 0.0f, 0.58f, 1.0f). This indicates + that the interpolation starts slowly, speeds up, and then slows down towards the end. At the + beginning, it behaves like createEaseIn(); at the end, it behaves like createEaseOut(). + + This is equivalent to using the "ease-in-out" keyword when specifying a timing-function in + CSS. + + @see createCubicBezier, createEase, createEaseIn, createEaseInOut + */ + static std::function createEaseInOut(); + + /** Returns the easing function createCubicBezier (0.65f, 0.0f, 0.35f, 1.0f). This indicates + that the interpolation starts slowly, speeds up, and then slows down towards the end. It + behaves similar to createEaseInOut() but is more exaggerated and has a more symmetrical + curve. + + This is equivalent to easeInOutCubic as specified on https://easings.net/#easeInOutCubic. + + @see createCubicBezier, createEaseInOut + */ + static std::function createEaseInOutCubic(); + + /** Returns an easing function with a constant rate of interpolation, with no change in the rate + of progress throughout the duration (that is, no acceleration or deceleration). + */ + static std::function createLinear(); + + /** Returns an easing function that behaves like a spring with a weight attached. + + Like createEaseOutBack() this might overshoot causing it to return float values exceeding + 1.0f. + + @see createEaseOutBack + */ + static std::function createSpring (const SpringEasingOptions& options = {}); + + /** Returns an easing function that behaves like a bouncy ball dropped on the ground. + + The function will bounce numBounces times on the input range of [0, 1] before coming to + stop, each bounce is less pronounced than the previous. + + This is equivalent to easeInOutCubic as specified on https://easings.net/#easeOutBounce. + */ + static std::function createBounce (int numBounces = 3); + + /** Returns an easing function that reaches 1.0f when the input value is 0.5f, before returning + to 0.0f when the input values reaches 1.0f. + + This is useful for making a repeating pulsation. + */ + static std::function createOnOffRamp(); +}; + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_StaticAnimationLimits.h b/modules/juce_animation/animation/juce_StaticAnimationLimits.h new file mode 100644 index 0000000000..2d4d6060b0 --- /dev/null +++ b/modules/juce_animation/animation/juce_StaticAnimationLimits.h @@ -0,0 +1,131 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Helper class for using linear interpolation between a begin and an end value. + + The ValueType could be any numerical type, or a std::tuple containing numerical types. This + class is mainly intended to be used with the latter. + + This way you can interpolate multiple values by supplying a single float value, which you can + access in an Animator's value change callback. + + E.g. + @code + const auto boundsToTuple = [] (auto b) + { + return std::make_tuple (b.getX(), b.getY(), b.getWidth(), b.getHeight()); + }; + + const auto begin = boundsToTuple (component.getBoundsInParent()); + const auto end = boundsToTuple (targetBounds); + const auto limits = makeAnimationLimits (begin, end); + + // This is the value change callback of an Animator, where you will transition a Component from + // one bounds to the next. See the AnimatorsDemo for a more detailed example. + const auto valueChanged = [&component, limits] (auto v) + { + const auto [x, y, w, h] = limits.lerp (v); + component.setBounds (x, y, w, h); + }; + @endcode + + @see ValueAnimatorBuilder::ValueChangedCallback + + @tags{Animations} +*/ +template +class JUCE_API StaticAnimationLimits +{ +public: + /** Constructor. You can use it to interpolate between a 0 initialised numerical value or tuple + and the provided end state. + */ + explicit StaticAnimationLimits (const ValueType& endIn) + : StaticAnimationLimits ({}, endIn) {} + + /** Constructor. Creates an object that will interpolate between the two provided beginning and + end states. The ValueType can be a numerical type or a std::tuple containing numerical + types. + */ + StaticAnimationLimits (const ValueType& beginIn, const ValueType& endIn) + : begin (beginIn), end (endIn) {} + + /** Evaluation operator. Returns a value that is a linear interpolation of the beginning and end + state. It's a shorthand for the lerp() function. + */ + ValueType operator() (float value) const + { + return lerp (value); + } + + /** Returns a value that is a linear interpolation of the beginning and end state. + */ + ValueType lerp (float value) const + { + using namespace detail::ArrayAndTupleOps; + + if constexpr (std::is_integral_v) + return (ValueType) std::round ((float) begin + ((float) (end - begin) * value)); + else + return (ValueType) (begin + ((end - begin) * value)); + } + +private: + ValueType begin{}, end{}; +}; + +/** Creates an instance of StaticAnimationLimits, deducing ValueType from + the function argument. +*/ +template +StaticAnimationLimits makeAnimationLimits (const ValueType& end) +{ + return StaticAnimationLimits (end); +} + +/** Creates an instance of StaticAnimationLimits, deducing ValueType from + the function arguments. +*/ +template +StaticAnimationLimits makeAnimationLimits (const ValueType& begin, const ValueType& end) +{ + return StaticAnimationLimits (begin, end); +} + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_VBlankAnimatorUpdater.h b/modules/juce_animation/animation/juce_VBlankAnimatorUpdater.h new file mode 100644 index 0000000000..aa6d7fd050 --- /dev/null +++ b/modules/juce_animation/animation/juce_VBlankAnimatorUpdater.h @@ -0,0 +1,59 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Similar to AnimatorUpdater, but automatically calls update() whenever the screen refreshes. + + @tags{Animations} +*/ +class JUCE_API VBlankAnimatorUpdater : private AnimatorUpdater +{ +public: + /** Constructs a VBlankAnimatorUpdater that is synchronised to the refresh rate of the monitor + that the provided Component is being displayed on. + */ + explicit VBlankAnimatorUpdater (Component* c) : vBlankAttachment (c, [this] { update(); }) + { + } + + using AnimatorUpdater::addAnimator, AnimatorUpdater::removeAnimator; + +private: + VBlankAttachment vBlankAttachment; +}; + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_ValueAnimatorBuilder.cpp b/modules/juce_animation/animation/juce_ValueAnimatorBuilder.cpp new file mode 100644 index 0000000000..528249012a --- /dev/null +++ b/modules/juce_animation/animation/juce_ValueAnimatorBuilder.cpp @@ -0,0 +1,112 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +class ValueAnimator : public Animator::Impl +{ +public: + explicit ValueAnimator (ValueAnimatorBuilder optionsIn) : options (std::move (optionsIn)) {} + + auto getValue() const + { + using namespace detail::ArrayAndTupleOps; + + return options.getEasing() == nullptr ? getProgress() : options.getEasing() (getProgress()); + } + + float getProgress() const + { + if (isComplete()) + return 1.0f; + + return timeBasedProgress; + } + + /** Returns the time in milliseconds that it takes for the progress to go from 0.0 to 1.0. + + This is the value returned even if the Animator is infinitely running. + */ + double getDurationMs() const override + { + return options.getDurationMs(); + } + + bool isComplete() const override + { + return Animator::Impl::isComplete() + || (! options.isInfinitelyRunning() && timeBasedProgress >= 1.0f); + } + +private: + Animator::Status internalUpdate (double timestampMs) override + { + timeBasedProgress = (float) ((timestampMs - startedAtMs) / options.getDurationMs()); + + NullCheckedInvocation::invoke (onValueChanged, getValue()); + + if (! options.isInfinitelyRunning()) + return getProgress() >= 1.0 ? Animator::Status::finished : Animator::Status::inProgress; + + return Animator::Status::inProgress; + } + + void onStart (double timeMs) override + { + startedAtMs = timeMs; + timeBasedProgress = 0.0f; + + if (auto fn = options.getOnStartWithValueChanged()) + onValueChanged = fn(); + } + + void onComplete() override + { + NullCheckedInvocation::invoke (options.getOnComplete()); + } + + double startedAtMs = 0.0; + float timeBasedProgress = 0.0f; + + const ValueAnimatorBuilder options; + ValueAnimatorBuilder::ValueChangedCallback onValueChanged; +}; + +//============================================================================== +Animator ValueAnimatorBuilder::build() const& { return Animator { std::make_unique (*this) }; } +Animator ValueAnimatorBuilder::build() && { return Animator { std::make_unique (std::move (*this)) }; } + +} // namespace juce diff --git a/modules/juce_animation/animation/juce_ValueAnimatorBuilder.h b/modules/juce_animation/animation/juce_ValueAnimatorBuilder.h new file mode 100644 index 0000000000..503f88ef4f --- /dev/null +++ b/modules/juce_animation/animation/juce_ValueAnimatorBuilder.h @@ -0,0 +1,276 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A builder class that can be used to construct an Animator wrapping a ValueAnimator + implementation. + + Every ValueAnimatorBuilder object is immutable, and every "with..." function returns a new + object. Each object can be used independently and as many times as required to build an Animator + object. + + Calling build() multiple times will return an independent Animator object referencing a new + instance of the underlying implementation. Such Animator objects don't affect each other's + lifetime. The copy of an Animator object however shares ownership with the object that it was + copied from. + + You can treat ValueAnimatorBuilder instances as disposable objects that are only needed until + you call build() on them. You can then store only the returned Animator object, which can be + started and completed multiple times as needed. + + All functions beginning with "with..." are optional and can be used to affect the created + Animator's behaviour by overriding defaults. + + @tags{Animations} +*/ +class JUCE_API ValueAnimatorBuilder +{ +public: + /** The type of the value change callback. The float parameter is related to the time parameter + passed to Animator::update(). The update function is typically called by an AnimatorUpdater. + The parameter will have a value of 0.0 during the first call of the value change callback, + and it will reach 1.0 when the time passed equals the duration of the Animator. This however + can be changed if an EasingFn is also specified. Correctly written easing functions should + preserve the 0.0 and 1.0 start and end values, but intermittent values can fall outside the + range of [0.0, 1.0]. + + After a call with a progress value of 1.0, the on complete callback will be called and the + updates will stop, unless the Animator is infinitely running, in which case the progress + will go beyond 1.0 and on complete will not be called until after Animator::complete() has + been called. + + A value change callback is optional. If you want to use one, you need to create it inside + your on start callback that you must pass to withOnStartCallback(). + + @see withOnStartCallback, withDurationMs, withEasing, runningInfinitely + */ + using ValueChangedCallback = std::function; + + /** The type of the on start callback. It can be used to do any initialisation necessary at the + start of an animation, then it must return a ValueChangedCallback. + + The ValueChangedCallback is called during every update of the Animator after the on start + callback and before the on complete callback. + + The on start callback is called during the first update that the Animator receives after + Animator::start() has been called. Immediately after the on start callback the first call to + the value changed callback is made. + + @see ValueChangedCallback + */ + using OnStartReturningValueChangedCallback = std::function; + + /** The type of an optional easing function that can be passed to the withEasing() builder + function. + + If this function is specified it will be called with the progress value going from 0.0 to + 1.0 (and beyond in case of an infinitely running Animator), that would otherwise be passed + directly to the value change callback. The return value is then passed to the on value + change callback instead. + + This function can be used to change the linear progression of the animation and create + effects like rubber band like motion. + + @see ValueChangedCallback + */ + using EasingFn = std::function; + + /** Use this function to specify an optional on start callback. + + Alternatively you can use the withOnStartReturningValueChangedCallback function that allows + you to return the ValueChangedCallback from inside your on start callback. + + You can only use either withOnStartCallback() or withOnStartReturningValueChangedCallback(). + */ + [[nodiscard]] ValueAnimatorBuilder withOnStartCallback (std::function onStartCallback) const + { + return with (&ValueAnimatorBuilder::onStartReturningValueChanged, + [start = std::move (onStartCallback), previous = onStartReturningValueChanged] + { + NullCheckedInvocation::invoke (start); + return previous != nullptr ? previous() : nullptr; + }); + } + + /** Use this function to specify an optional on change callback. + + Alternatively you can use the withOnStartReturningValueChangedCallback function that allows + you to return the ValueChangedCallback from inside your on start callback. + + You can only use either withValueChangedCallback or withOnStartReturningValueChangedCallback. + + @see OnStartReturningValueChangedCallback + */ + [[nodiscard]] ValueAnimatorBuilder withValueChangedCallback (ValueChangedCallback valueChangedCallback) const + { + return with (&ValueAnimatorBuilder::onStartReturningValueChanged, + [changed = std::move (valueChangedCallback), previous = onStartReturningValueChanged] + { + return [changed, previousChanged = previous != nullptr ? previous() : nullptr] (float x) + { + NullCheckedInvocation::invoke (previousChanged, x); + NullCheckedInvocation::invoke (changed, x); + }; + }); + } + + /** Use this function to specify an optional on start callback. + + The return value of the provided function is a ValueChangedCallback. This allows you to + construct a new ValueChangedCallback on every on start event, capturing state that is also + constructed at the time of starting. + + If you don't need to return a new ValueChangedCallback on every animation start, you can use + the simpler variants withOnStartCallback and withValueChangedCallback. However you cannot + use those functions together with this one. + + @see OnStartReturningValueChangedCallback, withOnStartCallback, withValueChangedCallback + */ + [[nodiscard]] ValueAnimatorBuilder withOnStartReturningValueChangedCallback (OnStartReturningValueChangedCallback value) const + { + return with (&ValueAnimatorBuilder::onStartReturningValueChanged, std::move (value)); + } + + /** Use this function to optionally specify an on complete callback. This function will be + called after the Animator reached a progress value >= 1.0, or in the case of an + infinitely running animation, if Animator::complete() has been called. + */ + [[nodiscard]] ValueAnimatorBuilder withOnCompleteCallback (std::function value) const + { + return with (&ValueAnimatorBuilder::onComplete, std::move (value)); + } + + /** Use this function to specify the time it takes for the Animator to reach a progress of 1.0. + + The default value is 300 ms. + + A progress of 1.0 will be reached after this time elapses even if the Animator is infinitely + running. + */ + [[nodiscard]] ValueAnimatorBuilder withDurationMs (double durationMsIn) const + { + return with (&ValueAnimatorBuilder::durationMs, durationMsIn); + } + + /** Supply a function that transforms the linear progression of time. + + @see EasingFn + */ + [[nodiscard]] ValueAnimatorBuilder withEasing (EasingFn fn) const + { + return with (&ValueAnimatorBuilder::easing, std::move (fn)); + } + + /** This function specifies that the Animator will keep running even after its progress > 1.0 + and its on complete function will not be called until Animator::complete() is called. + */ + [[nodiscard]] ValueAnimatorBuilder runningInfinitely() const + { + return with (&ValueAnimatorBuilder::infinitelyRunning, true); + } + + //============================================================================== + /** Getter function used by the corresponding Animator implementation. + */ + auto& getOnComplete() const + { + return onComplete; + } + + /** Getter function used by the corresponding Animator implementation. + */ + auto& getOnStartWithValueChanged() const + { + return onStartReturningValueChanged; + } + + /** Getter function used by the corresponding Animator implementation. + */ + auto getDurationMs() const + { + return durationMs; + } + + /** Getter function used by the corresponding Animator implementation. + */ + auto isInfinitelyRunning() const + { + return infinitelyRunning; + } + + /** Getter function used by the corresponding Animator implementation. + */ + auto& getEasing() const + { + return easing; + } + + /** The build() function will instantiate a new underlying implementation with the specified + parameters and return an Animator object referencing it. Calling build() multiple times + will return unrelated Animator objects, that reference separate underlying implementation + instances. + */ + Animator build() const&; + + /** The build() function will instantiate a new underlying implementation with the specified + parameters and return an Animator object referencing it. Calling build() multiple times + will return unrelated Animator objects, that reference separate underlying implementation + instances. + + This overload will be called on rvalue handles. + */ + Animator build() &&; + +private: + //============================================================================== + template + ValueAnimatorBuilder with (Member&& member, Value&& value) const noexcept + { + auto copy = *this; + copy.*member = std::forward (value); + return copy; + } + + OnStartReturningValueChangedCallback onStartReturningValueChanged; + std::function onComplete; + double durationMs = 300.0; + bool infinitelyRunning = false; + EasingFn easing = Easings::createEase(); +}; + +} // namespace juce diff --git a/modules/juce_animation/detail/chromium/cubic_bezier.cc b/modules/juce_animation/detail/chromium/cubic_bezier.cc new file mode 100644 index 0000000000..8ca6af4a08 --- /dev/null +++ b/modules/juce_animation/detail/chromium/cubic_bezier.cc @@ -0,0 +1,272 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cubic_bezier.h" + +#include +#include +#include + +namespace gfx { + +namespace { + +const int kMaxNewtonIterations = 4; + +} // namespace + +static const double kBezierEpsilon = 1e-7; + +double CubicBezier::ToFinite(double value) { + // TODO(crbug.com/1275541): We can clamp this in numeric operation helper + // function like ClampedNumeric. + if (std::isinf(value)) { + if (value > 0) + return std::numeric_limits::max(); + return std::numeric_limits::lowest(); + } + return value; +} + +CubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) { + InitCoefficients(p1x, p1y, p2x, p2y); + InitGradients(p1x, p1y, p2x, p2y); + InitRange(p1y, p2y); + InitSpline(); +} + +CubicBezier::CubicBezier(const CubicBezier& other) = default; + +void CubicBezier::InitCoefficients(double p1x, + double p1y, + double p2x, + double p2y) { + // Calculate the polynomial coefficients, implicit first and last control + // points are (0,0) and (1,1). + cx_ = 3.0 * p1x; + bx_ = 3.0 * (p2x - p1x) - cx_; + ax_ = 1.0 - cx_ - bx_; + + cy_ = ToFinite(3.0 * p1y); + by_ = ToFinite(3.0 * (p2y - p1y) - cy_); + ay_ = ToFinite(1.0 - cy_ - by_); + +#ifndef NDEBUG + // Bezier curves with x-coordinates outside the range [0,1] for internal + // control points may have multiple values for t for a given value of x. + // In this case, calls to SolveCurveX may produce ambiguous results. + monotonically_increasing_ = p1x >= 0 && p1x <= 1 && p2x >= 0 && p2x <= 1; +#endif +} + +void CubicBezier::InitGradients(double p1x, + double p1y, + double p2x, + double p2y) { + // End-point gradients are used to calculate timing function results + // outside the range [0, 1]. + // + // There are four possibilities for the gradient at each end: + // (1) the closest control point is not horizontally coincident with regard to + // (0, 0) or (1, 1). In this case the line between the end point and + // the control point is tangent to the bezier at the end point. + // (2) the closest control point is coincident with the end point. In + // this case the line between the end point and the far control + // point is tangent to the bezier at the end point. + // (3) both internal control points are coincident with an endpoint. There + // are two special case that fall into this category: + // CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are + // equivalent to linear. + // (4) the closest control point is horizontally coincident with the end + // point, but vertically distinct. In this case the gradient at the + // end point is Infinite. However, this causes issues when + // interpolating. As a result, we break down to a simple case of + // 0 gradient under these conditions. + + if (p1x > 0) + start_gradient_ = p1y / p1x; + else if (!p1y && p2x > 0) + start_gradient_ = p2y / p2x; + else if (!p1y && !p2y) + start_gradient_ = 1; + else + start_gradient_ = 0; + + if (p2x < 1) + end_gradient_ = (p2y - 1) / (p2x - 1); + else if (p2y == 1 && p1x < 1) + end_gradient_ = (p1y - 1) / (p1x - 1); + else if (p2y == 1 && p1y == 1) + end_gradient_ = 1; + else + end_gradient_ = 0; +} + +// This works by taking taking the derivative of the cubic bezier, on the y +// axis. We can then solve for where the derivative is zero to find the min +// and max distance along the line. We the have to solve those in terms of time +// rather than distance on the x-axis +void CubicBezier::InitRange(double p1y, double p2y) { + range_min_ = 0; + range_max_ = 1; + if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1) + return; + + const double epsilon = kBezierEpsilon; + + // Represent the function's derivative in the form at^2 + bt + c + // as in sampleCurveDerivativeY. + // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros + // but does not actually give the slope of the curve.) + const double a = 3.0 * ay_; + const double b = 2.0 * by_; + const double c = cy_; + + // Check if the derivative is constant. + if (std::abs(a) < epsilon && std::abs(b) < epsilon) + return; + + // Zeros of the function's derivative. + double t1 = 0; + double t2 = 0; + + if (std::abs(a) < epsilon) { + // The function's derivative is linear. + t1 = -c / b; + } else { + // The function's derivative is a quadratic. We find the zeros of this + // quadratic using the quadratic formula. + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) + return; + double discriminant_sqrt = sqrt(discriminant); + t1 = (-b + discriminant_sqrt) / (2 * a); + t2 = (-b - discriminant_sqrt) / (2 * a); + } + + double sol1 = 0; + double sol2 = 0; + + // If the solution is in the range [0,1] then we include it, otherwise we + // ignore it. + + // An interesting fact about these beziers is that they are only + // actually evaluated in [0,1]. After that we take the tangent at that point + // and linearly project it out. + if (0 < t1 && t1 < 1) + sol1 = SampleCurveY(t1); + + if (0 < t2 && t2 < 1) + sol2 = SampleCurveY(t2); + + range_min_ = std::min({range_min_, sol1, sol2}); + range_max_ = std::max({range_max_, sol1, sol2}); +} + +void CubicBezier::InitSpline() { + double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1); + for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) { + spline_samples_[i] = SampleCurveX(i * delta_t); + } +} + +double CubicBezier::GetDefaultEpsilon() { + return kBezierEpsilon; +} + +double CubicBezier::SolveCurveX(double x, double epsilon) const { + jassert (x >= 0.0); + jassert (x <= 1.0); + + double t0; + double t1; + double t2 = x; + double x2; + double d2; + int i; + +#ifndef NDEBUG + jassert (monotonically_increasing_); +#endif + + // Linear interpolation of spline curve for initial guess. + double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1); + for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) { + if (x <= spline_samples_[i]) { + t1 = delta_t * i; + t0 = t1 - delta_t; + t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) / + (spline_samples_[i] - spline_samples_[i - 1]); + break; + } + } + + // Perform a few iterations of Newton's method -- normally very fast. + // See https://en.wikipedia.org/wiki/Newton%27s_method. + double newton_epsilon = std::min(kBezierEpsilon, epsilon); + for (i = 0; i < kMaxNewtonIterations; i++) { + x2 = SampleCurveX(t2) - x; + if (fabs(x2) < newton_epsilon) + return t2; + d2 = SampleCurveDerivativeX(t2); + if (fabs(d2) < kBezierEpsilon) + break; + t2 = t2 - x2 / d2; + } + if (fabs(x2) < epsilon) + return t2; + + // Fall back to the bisection method for reliability. + while (t0 < t1) { + x2 = SampleCurveX(t2); + if (fabs(x2 - x) < epsilon) + return t2; + if (x > x2) + t0 = t2; + else + t1 = t2; + t2 = (t1 + t0) * .5; + } + + // Failure. + return t2; +} + +double CubicBezier::Solve(double x) const { + return SolveWithEpsilon(x, kBezierEpsilon); +} + +double CubicBezier::SlopeWithEpsilon(double x, double epsilon) const { + x = std::clamp(x, 0.0, 1.0); + double t = SolveCurveX(x, epsilon); + double dx = SampleCurveDerivativeX(t); + double dy = SampleCurveDerivativeY(t); + // TODO(crbug.com/1275534): We should clamp NaN to a proper value. + // Please see the issue for detail. + if (!dx && !dy) + return 0; + return ToFinite(dy / dx); +} + +double CubicBezier::Slope(double x) const { + return SlopeWithEpsilon(x, kBezierEpsilon); +} + +double CubicBezier::GetX1() const { + return cx_ / 3.0; +} + +double CubicBezier::GetY1() const { + return cy_ / 3.0; +} + +double CubicBezier::GetX2() const { + return (bx_ + cx_) / 3.0 + GetX1(); +} + +double CubicBezier::GetY2() const { + return (by_ + cy_) / 3.0 + GetY1(); +} + +} // namespace gfx diff --git a/modules/juce_animation/detail/chromium/cubic_bezier.h b/modules/juce_animation/detail/chromium/cubic_bezier.h new file mode 100644 index 0000000000..1738f6b0b3 --- /dev/null +++ b/modules/juce_animation/detail/chromium/cubic_bezier.h @@ -0,0 +1,109 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_CUBIC_BEZIER_H_ +#define UI_GFX_GEOMETRY_CUBIC_BEZIER_H_ + +namespace gfx { + +#define CUBIC_BEZIER_SPLINE_SAMPLES 11 + +class CubicBezier { + public: + CubicBezier(double p1x, double p1y, double p2x, double p2y); + CubicBezier(const CubicBezier& other); + + CubicBezier& operator=(const CubicBezier&) = delete; + + double SampleCurveX(double t) const { + // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + // The x values are in the range [0, 1]. So it isn't needed toFinite + // clamping. + // https://drafts.csswg.org/css-easing-1/#funcdef-cubic-bezier-easing-function-cubic-bezier + return ((ax_ * t + bx_) * t + cx_) * t; + } + + double SampleCurveY(double t) const { + return ToFinite(((ay_ * t + by_) * t + cy_) * t); + } + + double SampleCurveDerivativeX(double t) const { + return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_; + } + + double SampleCurveDerivativeY(double t) const { + return ToFinite( + ToFinite(ToFinite(3.0 * ay_) * t + ToFinite(2.0 * by_)) * t + cy_); + } + + static double GetDefaultEpsilon(); + + // Given an x value, find a parametric value it came from. + // x must be in [0, 1] range. Doesn't use gradients. + double SolveCurveX(double x, double epsilon) const; + + // Evaluates y at the given x with default epsilon. + double Solve(double x) const; + // Evaluates y at the given x. The epsilon parameter provides a hint as to the + // required accuracy and is not guaranteed. Uses gradients if x is + // out of [0, 1] range. + double SolveWithEpsilon(double x, double epsilon) const { + if (x < 0.0) + return ToFinite(0.0 + start_gradient_ * x); + if (x > 1.0) + return ToFinite(1.0 + end_gradient_ * (x - 1.0)); + return SampleCurveY(SolveCurveX(x, epsilon)); + } + + // Returns an approximation of dy/dx at the given x with default epsilon. + double Slope(double x) const; + // Returns an approximation of dy/dx at the given x. + // Clamps x to range [0, 1]. + double SlopeWithEpsilon(double x, double epsilon) const; + + // These getters are used rarely. We reverse compute them from coefficients. + // See CubicBezier::InitCoefficients. The speed has been traded for memory. + double GetX1() const; + double GetY1() const; + double GetX2() const; + double GetY2() const; + + // Gets the bezier's minimum y value in the interval [0, 1]. + double range_min() const { return range_min_; } + // Gets the bezier's maximum y value in the interval [0, 1]. + double range_max() const { return range_max_; } + + private: + void InitCoefficients(double p1x, double p1y, double p2x, double p2y); + void InitGradients(double p1x, double p1y, double p2x, double p2y); + void InitRange(double p1y, double p2y); + void InitSpline(); + static double ToFinite(double value); + + double ax_; + double bx_; + double cx_; + + double ay_; + double by_; + double cy_; + + double start_gradient_; + double end_gradient_; + + double range_min_; + double range_max_; + + double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES]; + +#ifndef NDEBUG + // Guard against attempted to solve for t given x in the event that the curve + // may have multiple values for t for some values of x in [0, 1]. + bool monotonically_increasing_; +#endif +}; + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_CUBIC_BEZIER_H_ diff --git a/modules/juce_animation/detail/juce_ArrayAndTupleOps.h b/modules/juce_animation/detail/juce_ArrayAndTupleOps.h new file mode 100644 index 0000000000..c08f903b28 --- /dev/null +++ b/modules/juce_animation/detail/juce_ArrayAndTupleOps.h @@ -0,0 +1,141 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +#ifndef DOXYGEN +//============================================================================== +/** The contents of this namespace are used to implement Animator and should not + be used elsewhere. Their interfaces (and existence) are liable to change! +*/ +namespace juce::detail::ArrayAndTupleOps +{ + template + constexpr auto hasTupleSize = false; + + template + constexpr auto hasTupleSize::value)>> = true; + + static_assert (! hasTupleSize); + static_assert (hasTupleSize>); + static_assert (hasTupleSize>); + + template , int> = 0> + constexpr auto& assignOpImpl (A& a, const B& b, Op&& op, std::index_sequence) + { + (op (std::get (a), std::get (b)), ...); + return a; + } + + template , int> = 0> + constexpr auto& assignOpImpl (A& a, const B& b, Op&& op, std::index_sequence) + { + (op (std::get (a), b), ...); + return a; + } + + template , int> = 0> + constexpr auto& assignOpImpl (A& a, const B& b, Op&& op) + { + return assignOpImpl (a, b, std::forward (op), std::make_index_sequence>()); + } + + template , int> = 0> + constexpr auto& operator+= (A& a, const B& b) + { + return assignOpImpl (a, b, [] (auto& x, auto y) + { + using Tx = std::remove_reference_t; + using Ty = std::remove_reference_t; + + if constexpr (std::is_integral_v && std::is_floating_point_v) + x = (Tx) std::round ((Ty) x + y); + else + x += y; + }); + } + + template , int> = 0> + constexpr auto& operator-= (A& a, const B& b) + { + return assignOpImpl (a, b, [] (auto& x, auto y) + { + using Tx = std::remove_reference_t; + using Ty = std::remove_reference_t; + + if constexpr (std::is_integral_v && std::is_floating_point_v) + x = (Tx) std::round ((Ty) x - y); + else + x -= y; + }); + } + + template , int> = 0> + constexpr auto& operator*= (A& a, const B& b) + { + return assignOpImpl (a, b, [] (auto& x, auto y) + { + using Tx = std::remove_reference_t; + using Ty = std::remove_reference_t; + + if constexpr (std::is_integral_v && std::is_floating_point_v) + x = (Tx) std::round ((Ty) x * y); + else + x *= y; + }); + } + + template , int> = 0> + constexpr auto& operator/= (A& a, const B& b) + { + return assignOpImpl (a, b, [] (auto& x, auto y) + { + using Tx = std::remove_reference_t; + using Ty = std::remove_reference_t; + + if constexpr (std::is_integral_v && std::is_floating_point_v) + x = (Tx) std::round ((Ty) x / y); + else + x /= y; + }); + } + + template , int> = 0> constexpr auto operator+ (const A& a, const B& b) { A copy { a }; return copy += b; } + template , int> = 0> constexpr auto operator- (const A& a, const B& b) { A copy { a }; return copy -= b; } + template , int> = 0> constexpr auto operator* (const A& a, const B& b) { A copy { a }; return copy *= b; } + template , int> = 0> constexpr auto operator/ (const A& a, const B& b) { A copy { a }; return copy /= b; } + + static_assert (std::tuple (1.0f, 5.0) + 3.0f == std::tuple (4.0f, 8.0)); + static_assert (std::tuple (1.0f, 5.0) - 1.0f == std::tuple (0.0f, 4.0)); + static_assert (std::tuple (1, 2, 3) * std::tuple (4, 5, 6) == std::tuple (4, 10, 18)); +} // namespace juce::detail::ArrayAndTupleOps +#endif diff --git a/modules/juce_animation/juce_animation.cpp b/modules/juce_animation/juce_animation.cpp new file mode 100644 index 0000000000..2dcb30a987 --- /dev/null +++ b/modules/juce_animation/juce_animation.cpp @@ -0,0 +1,70 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +#ifdef JUCE_ANIMATION_H_INCLUDED + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +//============================================================================== +#include "juce_animation.h" + +//============================================================================== +namespace chromium +{ + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4701 6001) +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfloat-conversion", + "-Wfloat-equal", + "-Wconditional-uninitialized") + + +#include "detail/chromium/cubic_bezier.h" +#include "detail/chromium/cubic_bezier.cc" + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +} // namespace chromium + +//============================================================================== +#include "animation/juce_Animator.cpp" +#include "animation/juce_AnimatorSetBuilder.cpp" +#include "animation/juce_AnimatorUpdater.cpp" +#include "animation/juce_Easings.cpp" +#include "animation/juce_ValueAnimatorBuilder.cpp" diff --git a/modules/juce_animation/juce_animation.h b/modules/juce_animation/juce_animation.h new file mode 100644 index 0000000000..34d586397d --- /dev/null +++ b/modules/juce_animation/juce_animation.h @@ -0,0 +1,76 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + + +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.md file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_animation + vendor: juce + version: 7.0.9 + name: JUCE Animation classes + description: Classes for defining and handling animations. + website: http://www.juce.com/juce + license: GPL/Commercial + minimumCppStandard: 17 + + dependencies: juce_gui_basics + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + +#pragma once +#define JUCE_ANIMATION_H_INCLUDED + +#include + +//============================================================================== +#include "detail/juce_ArrayAndTupleOps.h" + +//============================================================================== +#include "animation/juce_Animator.h" +#include "animation/juce_AnimatorSetBuilder.h" +#include "animation/juce_AnimatorUpdater.h" +#include "animation/juce_Easings.h" +#include "animation/juce_StaticAnimationLimits.h" +#include "animation/juce_ValueAnimatorBuilder.h" +#include "animation/juce_VBlankAnimatorUpdater.h" diff --git a/modules/juce_gui_basics/desktop/juce_Desktop.h b/modules/juce_gui_basics/desktop/juce_Desktop.h index 0f3352f188..7c57feb2ae 100644 --- a/modules/juce_gui_basics/desktop/juce_Desktop.h +++ b/modules/juce_gui_basics/desktop/juce_Desktop.h @@ -259,14 +259,17 @@ public: */ Component* findComponentAt (Point screenPosition) const; - /** The Desktop object has a ComponentAnimator instance which can be used for performing + /** The ComponentAnimator has been superseded, it is now recommended you use the Animator + class in the juce_animation module. + + The Desktop object has a ComponentAnimator instance which can be used for performing your animations. Having a single shared ComponentAnimator object makes it more efficient when multiple components are being moved around simultaneously. It's also more convenient than having to manage your own instance of one. - @see ComponentAnimator + @see Animator, ComponentAnimator */ ComponentAnimator& getAnimator() noexcept { return animator; } diff --git a/modules/juce_gui_basics/layout/juce_ComponentAnimator.h b/modules/juce_gui_basics/layout/juce_ComponentAnimator.h index 1ce8853291..e8a0f04b14 100644 --- a/modules/juce_gui_basics/layout/juce_ComponentAnimator.h +++ b/modules/juce_gui_basics/layout/juce_ComponentAnimator.h @@ -37,6 +37,9 @@ namespace juce //============================================================================== /** + This class has been superseded, it is now recommended you use the Animator + class in the juce_animation module. + Animates a set of components, moving them to a new position and/or fading their alpha levels. @@ -54,7 +57,7 @@ namespace juce The class is a ChangeBroadcaster and sends a notification when any components start or finish being animated. - @see Desktop::getAnimator + @see Animator, Desktop::getAnimator @tags{GUI} */ diff --git a/modules/juce_gui_basics/windows/juce_VBlankAttachment.h b/modules/juce_gui_basics/windows/juce_VBlankAttachment.h index f9181fe871..a0e6a13a52 100644 --- a/modules/juce_gui_basics/windows/juce_VBlankAttachment.h +++ b/modules/juce_gui_basics/windows/juce_VBlankAttachment.h @@ -39,7 +39,7 @@ namespace juce Helper class to synchronise Component updates to the vertical blank event of the display that the Component is presented on. This is useful when animating the Component's contents. - @tags{GUI} + @tags{GUI, Animations} */ class JUCE_API VBlankAttachment final : public ComponentPeer::VBlankListener, public ComponentListener