Tweening made simple

December 28, 2015 Leave a comment Go to comments

Smart Mobile Studio has a pretty good RTL. It’s small yet covers some of the more advanced topics available under HTML5. It’s compact yet delivers more features than every other framework on the market. But what it doesnt do is make your life easier when it comes to smooth transitions and movements.

Well, thats both true and false actually. We shipped classes for executing CSS animations and transitions all the way back in version 1.0, and we recently followed up with even more animations and easier access (ridicules easy) via the SmartCL.Effects.pas unit. Just include that in your project and all your TW3CustomControl based widgets gain X number of “fx” methods.So adding effects to your custom controls is now so easy – you will be considered a slob if you dont use them.

But all that aside, there is still a lack of the fine-grain control you expect to find. I mean, the JVL has depth and substance, no doubt there — but you still cant stop a GPU based animation. Silly as it may sound.

tweening_demo

Tweening is about moving from A to B during a timeline

This is actually not a shortcoming of our RTL or code. We have implemented both start and stop mechanisms according to HTML5/CSS RFC specification. It’s just that no modern browser has bothered to implement the stop() method, because ordinary JS developers dont use these effects like we do. We have classes, inheritance and all the other goodies which means we approach problems differently.

Tween your heart out

Like mentioned, CSS animations has the benefit of being GPU powered. Meaning that it uses the custom-graphics-chipset of your phone or computer to move things around. Which is really neat and powerful. The downside is that these GPU animations cant really be stopped once you start them. They have to play out before you regain control of the animated HTML element. And to make matters worse, you can only execute one effect at a time on any visual element.

But why do we use them? Well, they are pretty and makes tweening easier. To “tween” simply means to move between two values over a stretch of time, applying some effect to start and end-points. Like all things in programming you provide meaning to the value by using it for something. All tweening does is to move between A and B using a special formula.

tween-js-javascript-tweening-engine

To make stuff move around, like say, navigating between two forms in typical iOS fashion, you would manipulate the “left” property of the two forms being moved between. One should move out of view and the other into view. It’s actually quite simple once you get the hang of it.

The other kids are doing it

For a while now I have watched other libraries and frameworks use more and more CPU based tweens. It feels like cheating since I take great pride in having sculpted so much of the RTL by hand, solving problems step by step and thinking it all through. But the sad fact is that fiddling with hardware and GPU control is getting old. The CPU in the latest generation of mobile phones is en-par with PC’s only a couple of years back. More than capable of smoothly moving forms and buttons around.

framework7

It looks super slick, but it’s just fancy Tweens + CSS3

Needless to say, I have ported over a famous tweening library to Smart Pascal. This gives us access to a whole host of interesting stuff. Not just movement and being able to stop effects and so on, but also the ability to move between colors, objects, a timeline and much, much more. So I will be replacing as many of the GPU effects in the RTL with Tweens.

Example, moving a button

Right. A quick example but an important one. We are going to move a button smoothly from A to B, applying a quad-filter that eases the animation in and out. This just means that it moves slower to begin with, then accellerates, and then slows down towards the end. Just like iPhone controls do. So let’s look at the code.

procedure TForm1.UpdateAnim;
begin
  var value := FTween.GetValue;
  writeln(Value);
  w3_RequestAnimationFrame( procedure ()
  begin
    w3button3.left := Round(value);
  end);
  if not FTween.Expired then
  w3_callback( UpdateAnim, 10);
end;

procedure TForm1.W3Button4Click(Sender: TObject);
begin
  FTween := TW3Tween.Create(
    w3button3.left, // startvalue
    200,            // advance
    400,          // duration
    ttQuadInOut,'');
  w3_callback( UpdateAnim, 10);
end;

Now, start by looking at the second procedure (w3Button4Click). Here we create a tweening object, and then we start an update loop which will continue in the background until the tween has expired (finish).

The first parameter is the starting value. Since we will be moving a button smoothly from A to B, I just use the X position of the control here.

Second parameter is the distance or “how much should the original number advance”. If you want to move the button 200 pixels to the right, then 200 is the value you use here.

The third parameter is the duration of the tween, or how long it should take to finish the animation. This is the number of milliseconds it should take to transform the original value, into the target value. The larger the number, the slower the animation executes.

The fourth parameter is the ease-filter. We have several filters available. You will have to try them all out to find one you like. So far I have added support for the following ease filters (more will be added soon):

  • ttLinear
  • ttQuadIn
  • ttQuadOut
  • ttQuadInOut
  • ttCubeIn
  • ttCubeOut
  • ttCubeInOut
  • ttQuartIn
  • ttQuartOut
  • ttQuartInOut

The fifth parameter controls looping and execution mode. This is presently defined as a string because I will add more options to it, but havent gotten around to it yet. So far the options are:

  • [empty]     – No loop
  • “repeat”    – Keep on repeating the animation
  • “reverse”  – Execute in a toggle between start and stop value

Note: The final option, reverse, is special. It will execute your animation from A to B, but then execute the animation back, from B to A. This option loops.

Updating the tween object

With the tween object created all you have to do is to call the GetValue() method. It’s really nothing more to it than that. It will calculate the position according to where you are on the time-line and return the present position.

If you want to stop the tween, then simply stop calling GetValue().

So what are you waiting for! Go out there and write a super nice animated toolbar!

unit system.animation.tween;

interface

uses
  System.Types,
  SmartCL.System;

type

  TW3TweenAnimationType = (
    ttlinear,
    ttQuadIn,
    ttQuadOut,
    ttQuadInOut,
    ttCubeIn,
    ttCubeOut,
    ttCubeInOut,
    ttQuartIn,
    ttQuartOut,
    ttQuartInOut
    );

  TW3TweenEase = class
  public
    class function  Linear(t,b,c,d:float):float;
    class function  QuadIn(t, b, c, d:float):float;
    class function  QuadOut(t, b, c, d:float):float;
    class function  QuadInOut(t, b, c, d:float):float;
    class function  CubeIn(t, b, c, d:float):float;
    class function  CubeOut(t, b, c, d:float):float;
    class function  CubeInOut(t, b, c, d:float):float;
    class function  QuartIn(t, b, c, d:float):float;
    class function  QuartOut(t, b, c, d:float):float;
    class function  QuartInOut(t, b, c, d:float):float;
  end;

  TW3Tween = class
  private
    FStartTime:     TDateTime;
    FStartValue:    float;
    FDistance:      float;
    FDuration:      float;
    FAnimationtype: TW3TweenAnimationType;
    FLoop:          string;
  protected
    class function  RoundTk(num:float):float;
    class function  TimeCode:float;
    function    DoTween(t, b, c, d:float):float;
  public
    function    Expired:Boolean;
    function    GetValue:float;
    function    GetStartTime:TDateTime;
    constructor Create(aStartValue,aDistance,aDuration:float;
                aAnimationType:TW3TweenAnimationType;aLoop:String);virtual;
  end;

implementation

//############################################################################
// TW3Tween
//############################################################################

constructor TW3Tween.Create(aStartValue,aDistance,aDuration:float;
            aAnimationType:TW3TweenAnimationType;aLoop:string);
begin
  inherited Create;
  FStartTime := TimeCode;
  FStartValue := aStartValue;
  FDistance:= aDistance;
  FDuration := aDuration;
  FAnimationtype := aAnimationtype;
  FLoop := aLoop;
end;

class function TW3Tween.TimeCode:float;
begin
  asm
    @result = Date.now();
  end;
end;

function TW3Tween.DoTween(t, b, c, d:float):float;
begin
  result := 0.0;
  case FAnimationtype of
  ttlinear:     result := TW3TweenEase.Linear(t,b,c,d);
  ttQuadIn:     result := TW3TweenEase.QuadIn(t,b,c,d);
  ttQuadOut:    result := TW3TweenEase.QuadOut(t,b,c,d);
  ttQuadInOut:  result := TW3TweenEase.QuadInOut(t,b,c,d);
  ttCubeIn:     result := TW3TweenEase.CubeIn(t,b,c,d);
  ttCubeOut:    result := TW3TweenEase.CubeOut(t,b,c,d);
  ttCubeInOut:  result := TW3TweenEase.CubeInOut(t,b,c,d);
  ttQuartIn:    result := TW3TweenEase.QuartIn(t,b,c,d);
  ttQuartOut:   result := TW3TweenEase.QuartOut(t,b,c,d);
  ttQuartInOut: result := TW3TweenEase.QuartInOut(t,b,c,d);
  end;
end;

function TW3Tween.GetValue:float;
var
  LTotal: float;
begin

  if not Expired then
  begin
    LTotal := DoTween(TimeCode-FStartTime,FStartValue,FDistance,FDuration);
  end else
  if FLoop='' then
  begin
    LTotal := FStartValue + FDistance;
  end else
  if FLoop = 'repeat' then
  begin
    FStartTime := TimeCode;
    LTotal := DoTween(TimeCode-FStartTime,FStartValue,FDistance,FDuration);
  end else
  if FLoop = 'reverse' then
  begin
    FStartValue := FStartValue + FDistance;
    FDistance := -FDistance;
    FStartTime := TimeCode;
    LTotal := DoTween(TimeCode-FStartTime,FStartValue,FDistance,FDuration)
  end;

  result := RoundTk(LTotal);
end;

function TW3Tween.Expired:Boolean;
begin
  result := FStartTime + FDuration < TimeCode;
end;

function TW3Tween.GetStartTime:TDateTime;
begin
  result := Now - FStartTime - FDuration + TimeCode;
end;

class function TW3Tween.RoundTk(num:float):float;
begin
  result := Round( Num * 100) / 100;
end;

//#############################################################################
//  TW3TweenEase
//#############################################################################

class function TW3TweenEase.QuartIn(t, b, c, d:float):float;
begin
  asm
    @t /= @d;
    return @c * @t * @t * @t * @t + @b;
  end;
end;

class function TW3TweenEase.QuartOut(t, b, c, d:float):float;
begin
  asm
    @t /= @d;
    @t--;
    return -@c * (@t * @t * @t * @t - 1) + @b;
  end;
end;

class function TW3TweenEase.QuartInOut(t, b, c, d:float):float;
begin
  asm
    @t /= @d/2;
    if (@t < 1) {
        return @c / 2 * @t * @t * @t * @t + @b;
    }
    @t -= 2;
    return -@c / 2 * (@t * @t * @t * @t - 2) + @b;
  end;
end;

class function TW3TweenEase.CubeInOut(t, b, c, d:float):float;
begin
  asm
    @t /= @d/2;
    if (@t < 1) {
      return @c / 2 * @t * @t * @t + @b;
    }
    @t -= 2;
    return @c/2 * (@t * @t * @t + 2) + @b;
  end;
end;

class function TW3TweenEase.CubeOut(t, b, c, d:float):float;
begin
  asm
    @t /= @d;
    @t--;
    return @c * (@t * @t * @t + 1) + @b;
  end;
end;

class function TW3TweenEase.CubeIn(t, b, c, d:float):float;
begin
  asm
    @t /= @d;
    return @c * @t * @t * @t + @b;
  end;
end;

class function TW3TweenEase.QuadInOut(t, b, c, d:float):float;
begin
  asm
    @t /= @d/2;
    if (@t < 1) {
      return @c / 2 * @t * @t * @t + @b;
    }
    @t -= 2;
    @result = @c/2*(@t * @t * @t + 2) + @b;
  end;
end;

class function TW3TweenEase.QuadOut(t, b, c, d:float):float;
begin
  asm
    @t /= @d;
    return -@c * @t * (@t - 2) + @b;
  end;
end;

class function TW3TweenEase.QuadIn(t, b, c, d:float):float;
begin
  asm
    @t /= @d;
    return @c * @t * @t + @b;
  end;
end;

class function TW3TweenEase.Linear(t,b,c,d:float):float;
begin
  asm
   @result = @c * @t / @d + @b;
  end;
end;

end.
Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: