Git Product home page Git Product logo

animator's Introduction

Friz

A generic UI animation controller for JUCE projects.

For a fuller discussion of these classes, see my blog post at https://artandlogic.com/friz-and-the-illusion-of-life/.

API Docs

API documentation is available here.

Overview

The friz project is a set of C++ classes that can be used in projects built with the JUCE application framework to add animation effects to user interface elements. Using this library lets you easily specify a stream of timed values that can be used for any purpose in an application, but most frequently for moving or otherwise changing the visual appearance of UI elements in the application. It comes with a rich set of "easing curves" that control how the values are generated over time, and adding your own control curves is simple and straightforward.

It's named after Friz Freling, animator and director of Looney Tunes/Merrie Melodies shorts in the golden age of the Warner Bros. animation studio.

Crash Course

Let's imagine a simple use of this: A JUCE applicaton with a main component that has nothing in it. Every time you click inside the window, the background color will smoothly animate to a new, random color. This component needs two member variables, a juce::Colour and a friz::Animator.

class MyContentComponent : public juce::Component 
{
public:
    MyContentComponent (); 

    // called whenever the mouse is clicked in the component
    void mouseDown (const juce::MouseEvent& e) override;

    // flood-fill the component with the active color
    void paint (juce::Graphics& g) override { g.fillAll (bgColor); }
private:
    juce::Colour bgColor { juce::Colours::white };
    friz::Animator animator;   
};

We'll hand-wave past all of the "getting a JUCE application that has a main window that can host this as a content component" business -- you should already know how to do that part.

Without doing anything more, we have a component that is ready to generate and handle animation events. Instantiating a friz::Animator with no arguments creates an animation controller that's driven by a juce::Timer and will execute at a rate of 30 frames per second when an animation is running.

To get the color change animation running, we'll implement the mouseDown handler that JUCE provides for us:

void MyContentComponent::mouseDown (const juce::MouseEvent& e)
{
    // arbitrary ID value; we use to prevent multiple animations at once. 
    const int animationId { 1 };

    // check to see if our animation is currently running -- if so, just exit:
    if (animator->getAnimation (animationId) != nullptr)
        return;
    // we'll animate the color through HSB color space, so we need some random numbers to go toward: 
    auto& random { Random::getSystemRandom () };
    const auto nextHue { random.nextFloat () };
    const auto nextSat { random.nextFloat () };
    const auto nextBright { random.nextFloat () };
    // a random number of milliseconds for the transition, between 300 and 2000
    const auto duration { random.nextInt { 300, 2000 } };

    // create the animation, using 3 parametric curves with the same settings:
    auto effect { friz::makeAnimation <friz::Parametric, 3> (
        // ID of the animation
        animationId, 
        //  The values to start from, Hue/Saturation/Brightness
        { bgColor.getHue (), bgColor.getSaturation (), bgColor.getBrightness ()}, 
        // The values to animate toward
        { nextHue, nextSat, nextBright}, 
        // # of millseconds for the transition
        duration, 
        // additional args -- here, we specify the shape of the curve. 
        friz::Parametric::kCubic)
        };

    // The animator will execute this callback lambda on every frame:
    effect->updateFn = [this] (int id, const auto& vals)
    {
        // vals is an std::array<float, 3> containing the current in-between state of the 
        // animation. Update the bg color variable and repaint. 
        bgColor = juce::Colour (vals[0], vals[1], vals[2], 1.f);
        repaint ();
    }

    // pass the animation object to the animator, which will start running it immediately. 
    animator.addAnimation (std::move (effect));
}

...and that's it. Clicking inside the component will start the animation, run it to completion, and clean up all the objects that it needed when it completes.

This animation at 30 frames/second is pretty nice, but JUCE timers aren't rock-solid (by design; they really weren't designed for this level of precision). If your app targets versions of JUCE after 7.0.0, you can add a single line of code to make your animations sync exactly to the refresh rate of the monitor, which will make them execute as smoothly as they possibly can:

MyContentComponent::MyContentComponent ()
:   animator { std::make_unique<friz::DisplaySyncController> (this) }
{

}

This instantiates the animator so that it uses the refresh interval of whatever display the component is being displayed on as its timing source; if you drag your app window from a monitor with a 60Hz refresh rate to a different monitor that updates at 120 Hz, everything adapts automatically for the best performance.

Design Goals

  • Lightweight If no effects are being run, there's no runtime overhead.
  • Flexible Adding new types of animation curves is simple, typically only requiring the creation of a new C++ class derived from an existing effect type and the overriding of a single method. Client code provides a pair of lambda objects to receive updates on frame updates and effect completion. Using the Parametric class, you can implement an effect by providing a single function that maps a floating point value in the range [0, 1] to a floating point output variable mostly in the range [0, 1] (small excursions outside that range are permitted and can be useful!)
  • Decoupled Friz doesn't need or want to know anything about your application; it just sends your code back a stream of values at regular intervals.
  • Modern Written using current (C++11 and later) capabilities and techniques.

Classes

friz::Animator

Animator docs

Top level animator object, typically owned by a JUCE Component object that needs to animate some aspect of one or more child components.

friz::Animation

Animation docs

An individual instance of a set of animation data. Each animation can provide one or more sets of animation curve data that will be sent back to your code on each frame. A derived class friz::Sequence is used to chain multiple animations together as a single logical unit.

friz::AnimatedValue

AnimatedValue docs

Base class for a set of animation curve types that can be instantiated with a start/end value and the definition of when the end value has been reached, either a time duration, or a floating point tolerance to the ending value.

Currently Defined Curves

  • Constant—emits a stream consisting of the same constant value
  • Linear—interpolates linearly between start and end
  • Parametric—provides a set of commonly used easing curves as seen e.g. at https://easings.net
  • Sinusoid—generates sin/cos values between any two phase values
  • EaseIn—accelerates quickly away from startVal, decelerates as it approaches endVal
  • EaseOut—accelerates slowly away from startVal, accelerates into endVal
  • Spring—accelerates away from startVal. If it overshoots the endVal, will simulate the oscillation of a dampened spring around the endVal until within tolerance.

Demo application

The demonstration application (located in the bgporter/frizDemo repository performs simple animations that

  • apply each of the curve types to animate randomly sized & colored squares away from a mouse click.
  • after a delay from the movement animation, fade the square to invisiblity and delete it from the screen.

The control panel on the right can be hidden and shown by clicking on its gray border (and is itself animated). All of the animation parameters can be tweaked and played with using the sliders in the control panel.

Additionally, a 'show breadcrumbs' checkbox controls the display of a point on the screen indicating the position of the last square on each frame of the animation—very helpful for visualizing how each of the curves actually behaves.

Other Examples

(Courtesy of Sudara): This tween animates 2 values at once, the opacity and the position of the component. It uses an AffineTransform for smooth float positioning accuracy. You could also use juce::Component's setBounds methods with integers.

auto fadeIn = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseOutSine, 0.0f, 1.0f, 0.3f * 60);
auto dropIn = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseOutSine, -10.f, 0.f, 0.5f * 60);
auto animation = std::make_unique<friz::Animation<2>> (friz::Animation<2>::SourceList { std::move (fadeIn), std::move (dropIn) }, 0);
animation->onUpdate ([&] (int id, const auto& val) {
    juce::AffineTransform t;
    this->setTransform (t.translated (0, val[1]));
    this->setAlpha (val[0]);
});
animator.addAnimation (std::move (animation));

Release History

2.1.1 Feb 12, 2023

Non-breaking Changes

  • added new FRIZ_VBLANK_ENABLED macro, which will be set to 1 if the version of JUCE being targeted is 7.0.0 or higher.

2.1.0 Feb 11, 2023

Non-breaking Changes

Chain animation type

Add a new Chain animation type that lets you bundle together a series of individual animations that will be triggered sequentially. Animations added to a Chain each retain their own update and completion callback functions, and do not need to have the same number of Values.

The existing Sequence animation type has been updated so that it's a specialization of the Chain type, where all the contained animations must contain the same number of Values, and a single UpdateFn callback is used for the entire series of effects.

Comceptually, a Chain lets you say "I want this to happen, and when that's done, I want this other thing to happen." A Sequence is used when you want to build a single complex animation out of several simpler effects that happen in order.

In the demo app, we use a Sequence for the 'pop out' of the sidebar—we first click the sidebar to the right, then run another animation to pop it out and make it visible. A single callback updates the position of the sidebar component.

We use a Chain when handling the demo boxes; the first animation moves a box from its creation point to some other (x,y) position on screen, and when that's done we start a second animation that performs a linear (1-dimensional) fade of the box's fill color.

makeAnimation Factory Function

Add a new free function makeAnimation to make the creation of many effects much simpler. In practice, most of the effects that I use are either 1-dimensional, or multi-dimensional, but all of the values being generated are using the same curve type and parameters.

Code that before might have been written like:

auto fade = std::make_unique<friz::Animation<1>> (
    friz::Animation<1>::SourceList {
        std::make_unique<friz::Linear> (startValue, endValue, duration) },
                effectId);

can be cleaned up into:

auto fade = friz::makeAnimation<friz::Linear>(effectId, startValue, endValue, duration);

The benefits become apparent when making multi-dimensional effects, as you can pass all the keyframe values at once:

auto moveEffect { friz::makeAnimation<friz::Parametric, 2> (
    effectId, {0.f, 0.f}, {100.f, 100.f}, 200, friz::Parametric::kCubic)};

-- creating an animation object that contains two AnimatedValue obiects, ready to run by passing to an Animator object.

2.0.0 February 5, 2023

Breaking Changes

This is a major version bump, and as such, there are breaking changes that will require updates in existing code:

  • All functions/method names have been converted from StudlyCaps to mixedCase to follow JUCE conventions.
  • All durations are now specified in milliseconds instead of frames. It should always have been this way, but without doing this, synching to vertical blanking would have been problematic.
  • new argument added to the completion callback; a boolean wasCanceled will be passed to indicate whether the animation is ending normally, or because it was cancelled.
  • New DisplaySyncController class uses the juce::VBlankAttachment class to synchronize animation updates with the display.
  • The Easing family of AnimatedValue (EaseIn, EaseOut, Spring) objects will need much attention with regards to their control values (slew, acceleration, etc.) To support variable frame rates sensibly, all of these curves are now updated internally at a rate of 1000 frames per second, so they should have the same behavior regardless of the actual frame update rate that's in use.

Non-breaking Changes

  • Doxygen comments corrected, cleaned up, added as needed
  • General cleanup throughout
  • MIT license text added at top of all source files.

1.6.0 January 2023

  • bug fixes, compiler warnings

1.5.0 March 2022

  • Corrected bug in the Sinusoid animated value; start value returned when initial phase angle of the object was anything but zero was wrong.
  • Removed demo app code & build files into a separate repo (look for the bgporter/frizDemo repo). This repository now contains just the bare JUCE module for inclusion in a JCUE project.

1.4.0 November 3, 2021

  • Add mutex protection around animator functions that handle the list of running animations. Working on a project where animations are launched, modified from MIDI callback data, and exposed data races.
  • Add AnimatedValue::UpdateTarget() virtual method to allow changing an animated value's end state while the animation is in progress. Default implementation does nothing.
  • Add SmoothedValue class that accepts new end values while the animation is running.

1.3.0 November 6, 2020

Restructured the Animation::Update() logic.

Originally, on each frame, we got the next value from each of the value sources in the animation, and if all of them are in a state of completion, after call the OnUpdate() function, we'd immediately call the OnCompletion() function.

I've rewritten this so that the OnCompletion function is not called until the next frame's Update() call -- I encountered code where calling the completion function too soon after the update function created weird behavior because model code was being updated in the completion callback before the message loop had a chance to process the value set in the preceding Update call.

1.2.0: November 4, 2020

Corrected bug where the EaseIn, EaseOut, and Spring curve classes all used integer parameters in their constructors for the startValue and endValue.

1.1.0: October 2, 2020: Added the new Parametric animated value, which supports the following common easing curves:

  • Linear
  • Sine (ease in)
  • Sine (ease out)
  • Sine (in/out)
  • Quad (ease in)
  • Quad (ease out)
  • Quad (in/out)
  • Cubic (ease in)
  • Cubic (ease out)
  • Cubic (in/out)
  • Quartic (ease in)
  • Quartic (ease out)
  • Quartic (in/out)
  • Quintic (ease in)
  • Quintic (ease out)
  • Quintic (in/out)
  • Exponential (ease in)
  • Exponential (ease out)
  • Exponential (in/out)
  • Circular (ease in)
  • Circular (ease out)
  • Circular (in/out)
  • Back (ease in)
  • Back (ease out)
  • Back (in/out)
  • Elastic (ease in)
  • Elastic (ease out)
  • Elastic (in/out)
  • Bounce (ease in)
  • Bounce (ease out)
  • Bounce (in/out)

animator's People

Contributors

bgporter avatar cpr2323 avatar sudara avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

animator's Issues

Can't add animations to a sequence unless they have matching 'valueCount' template parameters

Ok last issue from today for reals!

I might be missing something here, but it looks like I can't chain two animations together sequentially (for example a fade in and a fade out) unless they have the exact sample `valueCount' template parameter.

Here's a minimal way to reproduce:

auto fadeInParams = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseOutSine, 0.0f, 1.0f, 0.3f * 60);
auto dropInParams = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseOutSine, -10.f, 0.f, 0.5f * 60);
auto fadeInTween = std::make_unique<friz::Animation<2>> (friz::Animation<2>::SourceList { std::move (fadeInParams), std::move (dropInParams) }, 0);

auto fadeOutParams = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseInQuartic, 1.0f, 0.0f, 2.0f * 60);
auto fadeOutTween = std::make_unique<friz::Animation<1>> (friz::Animation<1>::SourceList { std::move (fadeOutParams) }, 0);

auto fadeInThenOutSequence = std::make_unique<friz::Sequence<2>> (0);
fadeInThenOutSequence->AddAnimation (std::move (fadeInTween));
fadeInThenOutSequence->AddAnimation (std::move (fadeOutTween));

The error is on the 2nd AddAnimation call, it doesn't like that I'm adding a friz::Animation<1> type.

No viable conversion from 'unique_ptr<Animation<1>, default_delete<Animation<1>>>' to 'unique_ptr<Animation<2 aka 2>, default_delete<Animation<2>>>'

My current workaround is to add a dummy extra AnimatedValue that isn't used.

auto fadeOutParams = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseInQuartic, 1.0f, 0.0f, 2.0f * 60);
auto doNothingParams = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseInQuartic, 0.0f, 0.0f, 2.0f * 60);
auto fadeOutTween = std::make_unique<friz::Animation<2>> (friz::Animation<2>::SourceList { std::move (fadeOutParams), std::move (doNothingParams) }, 0);

I don't know the code well enough to suggest a fix. I'm guessing ideally a sequence shouldn't really care about how many values the animation tween has and should just ask the animations to iterate through them?...

[Feature] Allow duration to be specified in seconds

Hi Brett!

Back to working with animator and enjoying it! I might add a few issues as a way of communicating my usage. No pressure though, they are mainly that "first time user's friction" as well as some excited ideas. I've worked with a few other animation libraries. If any of the things I say can be helpful, then woo!

The most useful change to me would be able to specify duration in seconds, especially to Parametric. This is common in other animation libraries and a nice way to think of the actual animation itself, discuss with my designer, etc. (And I don't have to pepper my code with some math).

Abstracting frame would also allow for some nice features like throttling the frame-rate on some platforms without having to re-write code (I'm defaulting to 60fps).

I understand a concern might be that there's not a "time guarantee" — for example if there's some heavy duty painting which slows rendering calls. This is greensock's thinking about it and I also think that maybe just a bit of documentation (and maybe an assert?) would be enough, as in most use cases the benefit to working in seconds would outweigh the potential risk.

Thanks!

README bugs:

(reported by JUCE forum user aamf)

  • The example uses a non-existent curve kCubic
  • Remove uses of 'effect' to refer to an animation for clarity/consistency.

friz::Linear documentation error

In linear.h:

    /**
     * A value that changes linearly.
     * @param startVal  initial value
     * @param endVal    ending value
     * @param tolerance tolerance for completion.
     * @param duration  # of frames the effect should take.
     */
    Linear (float startVal, float endVal, int duration);

The comment is incorrect; duration is in units of frames.

Warning: Capture of this with = is deprecated

Also seeing this warning on a line like

fadeInThenOut->AddAnimation (std::move (fadeInTween));
In instantiation of member function 'friz::Sequence<2>::AddAnimation' modules/friz/Source/friz/curves/sequence.h:80:21: warning: implicit capture of 'this' with a capture default of '=' is deprecated

Sorry for the flurry of activity!

[Feature] Abstract away object ownership for improved clarity/readability

Just to make this a formal feature request:

I think it would be really sweet for clarity/readability if we added constructors that take ownership of incoming class objects. I'm usually playing with animations on the stack, so I'd love be able to "trust" the animator to just take some parameters and handle ownership on its own.

Right now, a simple fade out looks like so (with animator being a class member).

auto fadeOut = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseInQuartic, 0.0f, 1.0f, 60);
auto animation = std::make_unique<friz::Animation<1>> (friz::Animation<1>::SourceList { std::move (fadeOut) }, 0);
animation->OnUpdate ([&] (int id, const friz::Animation<1>::ValueList& val) {
    this->setAlpha (val[0]);
});
animator.AddAnimation (std::move (animation));

If there was an owning constructor, it could look like this

auto fadeOut = friz::Parametric (friz::Parametric::CurveType::kEaseInQuartic, 0.0f, 1.0f, 60);
auto animation = friz::Animation<1>::SourceList { fadeOut }, 0);
animation->OnUpdate ([&] (int id, const auto& val) {
    this->setAlpha (val[0]);
});
animator.AddAnimation (animation);

Thinking of other ways to cut down on verbosity, especially for a single tween like a fade out.... It would be nice in these cases to abstract away SourceList and the template parameter.... maybe Animation could have a constructor that can take a Parametric and automatically wrap it in a friz::Animation<1>::SourceList?...

auto animation = friz::Animation({friz::Parametric::CurveType::kEaseInQuartic, 0.0f, 1.0f, 60}); // constructs an Animation<1>
animation->OnUpdate ([&] (int id, const auto& val) {
    this->setAlpha (val[0]);
});
animator.AddAnimation (animation);

In general I wonder if a SourceList can be abstracted away behind the scenes, maybe with variadic templates (yikes?) vs. needing the user to be aware of them? Or with maybe with an ability to call .add(anotherParametric) on an animation (tween) which then updates its internal state....Hmm...

Thanks for this great lib!

Compile error on gcc 11

In file included from /modules/friz/Source/friz/friz.cpp:13:
2552
/modules/friz/Source/friz/curves/parametric.cpp: In lambda function:
2553
/modules/friz/Source/friz/curves/parametric.cpp:254:34:error: ‘powf’ is not a member of ‘std’; did you mean ‘pow’?
2554
  254 |                     return -std::powf (2, 10 * x - 10) *
2555
      |                                  ^~~~
2556
      |                                  pow
2557
/modules/friz/Source/friz/curves/parametric.cpp: In lambda function:
2558
/modules/friz/Source/friz/curves/parametric.cpp:274:33:error: ‘powf’ is not a member of ‘std’; did you mean ‘pow’?
2559
  274 |                     return std::powf (2, -10 * x) * std::sin ((x * 10 - 0.75f) * kC4) + 1;
2560
      |                                 ^~~~
2561
      |                                 pow
2562
/modules/friz/Source/friz/curves/parametric.cpp: In lambda function:
2563
/modules/friz/Source/friz/curves/parametric.cpp:294:42:error: ‘powf’ is not a member of ‘std’; did you mean ‘pow’?
2564
  294 |                     return 0.5f * -(std::powf (2, 20 * x - 10) *
2565
      |                                          ^~~~
2566
      |                                          pow
2567
/modules/friz/Source/friz/curves/parametric.cpp:299:41:error: ‘powf’ is not a member of ‘std’; did you mean ‘pow’?
2568
  299 |                     return 0.5f * (std::powf (2, -20 * x + 10) *
2569
      |                                         ^~~~
2570
      |                                         pow

Change VBlank macro

The FRIZ_VBLANK_ENABLED macro tests for JUCE releases of 7.0.0 or later (based on the JUCE release notes). The capability wasn't actually added until 7.0.3; update the macro.

Basic usage examples

I think a few very basic usage examples in the README (or a USAGE.md) would help people confidently get started using this amazing library.

I'll collect some here as I write them, and will submit a PR if desired or feel free to modify them and pop them somewhere.

Warning: Implicit conversion from int

I get a warning with this usage:

auto fadeOut = std::make_unique<friz::Parametric> (friz::Parametric::CurveType::kEaseInQuartic, 0.0f, 1.0f, 0.5f * 60);
auto animation = std::make_unique<friz::Animation<1>> (friz::Animation<1>::SourceList { std::move (fadeOut) }, 0);
implicit conversion changes signedness: 'int' to 'std::array::size_type' (aka 'unsigned long')
In instantiation of function template specialization 'std::make_unique<friz::Animation<1>, std::array<std::unique_ptr<friz::AnimatedValue>, 1>, int>'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.