IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Les interpolations avec Lazarus (1/3)

Interpolations et fonctions d'easing

Vous utilisez Windows 10 et les transitions entre les vignettes de l'accueil vous intriguent ? Vous connaissez les propriétés transition et animation de CSS et vous aimeriez en reproduire certains des effets en Pascal ? Vous êtes un adepte de JavaScript et vous pensez que seul jQuery Easing Plugin est capable de produire des animations dignes de ce nom ? Vous êtes un inconditionnel de FireMonkey et vous aimeriez étendre des fonctionnalités de type animation à des composants de la VCL de Delphi ? Tout simplement, vous souhaitez rendre plus vivantes vos présentations ? Avec la nouvelle série de tutoriels introduite ici, Lazarus sera pourvu de classes qui vous aideront à répondre à ces besoins ou interrogations.

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction aux interpolations

Les programmes de test sont présents dans le répertoire exemples accompagnant le tutoriel.

Free Pascal et Lazarus ne connaissent pas par défaut les fonctions d'interpolation pourtant utiles dès qu'un processus n'est pas linéaire. Le tutoriel qui suit propose une étude des fonctions d'interpolation et une implémentation des courbes d'easing dont disposent d'autres langages. Après la construction de classes dédiées dans un deuxième épisode, une solution alternative à base de courbes de Bézier cubiques viendra clore la série.

I-A. Les interpolations

Une interpolation est « une opération mathématique permettant de construire une courbe à partir de la donnée d'un nombre fini de points, ou une fonction à partir de la donnée d'un nombre fini de valeurs ». En ce qui concerne notre sujet d'étude, il faudra prévoir l'évolution d'une fonction pour une durée voulue, depuis un point d'origine et jusqu'à un point d'arrivée. Autrement dit, il s'agira d'appliquer des formules mathématiques personnalisées à des animations : toute valeur évoluant dans le temps pourra être soumise à nos fonctions et classes.

À partir de cet objectif, nous pouvons déjà dégager les données nécessaires à sa réalisation :

  • un pas (ou fraction de temps) ;
  • une durée ;
  • une valeur initiale ;
  • une valeur finale.

Dans sa forme la plus élémentaire, notre travail produira une interpolation linéaire simple : nous utiliserons une suite arithmétique de raison égale à 1 divisé par le nombre total de pas. Ainsi, pour un début fixé à 0 et une fin fixée à 5, nous aurons une raison de 1/5 (= 0,2) et par conséquent la progression suivante :

Valeur de la variable de boucle

Progression

0

0/5 = 0

1

1/5 = 0,2

2

2/5 = 0,4

3

3/5 = 0,6

4

4/5 = 0,8

5

5/5 = 1

Si nous prenons comme abscisses les valeurs de la boucle et comme ordonnées les valeurs de progression, nous obtenons un segment de la droite d'équation y = 1/5 x. Un intervalle qui commencerait à la valeur s créerait une droite d'équation y = 1/5 x + s. On ne peut pas imaginer plus simple, plus ordinaire... et plus monotone !

L'idée essentielle du travail à réaliser consiste à soumettre ce déroulement à des fonctions qui ne produisent pas cette suite arithmétique, mais qui aboutissent à la même valeur finale (1 ou 100%).

Par exemple, si nous considérons une interpolation qui calcule des valeurs à partir du carré de la valeur de de la variable de boucle, nous obtenons le tableau suivant :

Valeur de la variable de boucle

Progression

0

(0/5)² = 0

1

(1/5)² = 0,04

2

(2/5)² = 0,16

3

(3/5)² = 0,36

4

(4/5)² = 0,64

5

(5/5)² = 1

Nous avons bien les mêmes valeurs aux extrémités : 0 et 1. Ces valeurs signifient qu'au bout du compte, que ce soit avec la suite arithmétique ou avec cette formule qui utilise un carré, nous partons du même point d'origine pour arriver au même point final pendant la même durée : seules varient les valeurs intermédiaires.

Qu'apporte ce changement ? À examiner les tableaux, nous nous rendons compte que la progression avec le carré prend du retard sur celle qui est linéaire, mais qu'elle l'a totalement rattrapée à la fin de la boucle.

Voici les courbes représentatives des deux fonctions sur l'intervalle [0 , 1] :

Image non disponible

D'un point de vue mathématique, ces résultats s'expliquent facilement : multiplier par lui-même, un nombre positif plus petit que 1 donne un nombre plus petit que le nombre initial ! C'est l'explication du retard. En revanche, ce retard est de moins en moins vrai et disparaît dès que 1 est atteint. L'effet obtenu sera donc celui d'une accélération.

Plus le degré de l'expression sera élevé, plus l'effet sera perceptible : avec des cubes, l'inertie de départ sera encore plus forte, mais le résultat sera au final le même, à savoir 1.

Nous verrons que les degrés ne sont pas les seuls outils à notre disposition : en fait, toute fonction ou combinaison de fonctions qui retourne des valeurs qui partent de 0 pour aboutir à 1 (ou 100%) est une candidate sérieuse à la réalisation d'une interpolation.

Nous pouvons évidemment viser d'autres effets qu'une accélération positive : une décélération est elle aussi tout à fait envisageable.

I-B. Une approche empirique des interpolations

[Exemple Interpolations 01]

Pour les besoins du composant TGVTransition qui traite les transitions d'image à image, nous avons déjà donné des éléments pour créer des interpolations. Nous reprenons ci-après les formules empiriques qui ont accompagné ce projet dans sa forme initiale :

 
Sélectionnez
function TMainForm.ComputeInterpolation(AStart, AEnd: Integer; AStep: Integer;
  AInter: TInterpolation; ABack: Boolean): Integer;
// *** calcul des interpolations ***

  function Exponant(AExp: Byte): Integer;
  begin
    Result := Round((AEnd - AStart) * Power(AStep, AExp) / Power(100, AExp));
  end;

  function DownExponant(AExp: Byte): Integer;
  begin
    Result := AEnd - AStart - Round((AEnd - AStart) *
      Power(100 - AStep, AExp) / Power(100, AExp));
  end;

begin
  case AInter of
    intLinear: Result := Exponant(1);
    intQuadratic: Result := Exponant(2);
    intCubic: Result := Exponant(3);
    intQuartic: Result := Exponant(4);
    intQuintic: Result := Exponant(5);
    intSinus: Result := Round(Exponant(1) * sin(pi * AStep / 200));
    intSpring: Result := Round(Exponant(1) * (Power(cos(pi * AStep / 100), 2)));
    intExpo: Result := Round(Exponant(1) * (Power(2, AStep / 100) - 1));
    intSqrt: Result := Round(Exponant(1) * (Sqrt(AStep) / 10));
    intSlowDownQuadratic: Result := DownExponant(2);
    intSlowDownCubic: Result := DownExponant(3);
    intSlowDownQuartic: Result := DownExponant(4);
    intSlowDownQuintic: Result := DownExponant(5);
    intBounceCos: Result := Exponant(1) + Round((cos(AStep * pi / 100) + 1) * Exponant(1));
    intStepsCos: Result := Exponant(1) + Round(Power((cos(AStep * pi / 100) + 1), 2) * 100);
    intCos: Result := Round(Exponant(1) * (1 - cos(AStep / 100 * pi)) / 2);
    intHalfCos: Result := Round(Exponant(1) * ((1 - cos(AStep /100 * Pi)) / 4 + AStep / 200));
  end;
  if ABack then
    Result := AEnd - Result;
end;

L'objectif de la fonction Exponant est de renvoyer une valeur entre AStart et AEnd qui soit modifiée par la puissance utilisée. Par exemple, si la puissance vaut 1, nous obtenons une progression linéaire : en effet, la différence entre AStart et AEnd sera multipliée par le pas à la puissance 1 (elle sera donc inchangée) puis divisée par 100 à la même puissance (donc par 100 inchangé encore une fois). Nous aurons reconnu la formule qui permet de calculer le pourcentage d'une valeur. Pour des puissance supérieures à 1, nous nous retrouvons dans les cas étudiés lors du chapitre précédent.

Comme nous pouvions nous y attendre, la racine carrée ou les puissances de 2 produisent des effets proches des puissances déjà vues. Elles les modulent seulement avec leur propre progression.

Les ralentissements sont obtenus grâce à la fonction imbriquée DownExponant qui inverse les calculs de l'accélération positive par deux soustractions.

Si nous reconnaissons les formules avec des degrés de polynômes, nous découvrons aussi des formules avec des fonctions trigonométriques. Leurs cycles permettent en particulier d'envisager des effets de rebonds.

Pour le moment, contentons-nous d'avoir un aperçu de ce que produisent ces interpolations en action :

Par la suite, il s'agira de formaliser cette approche en la rendant compatible avec celle qu'offrent CSS ou JavaScript par exemple, souvent via des bibliothèques complémentaires.

I-C. Les courbes d'easing

Les courbes d'easing qui seront au cœur de ce tutoriel sont des interpolations plus ou moins standardisées que nous retrouvons un peu partout sur Internet dès qu'il est question d'animations.

Nous pouvons distinguer des courbes d'easing de quatre types :

  • In : l'interpolation suit une formule mathématique d'accélération positive (inertie) ;
  • Out : l'interpolation suit une formule d'accélération négative (décélération) ;
  • InOut : la première partie de l'interpolation est de type In, la seconde relevant du type Out (inertie, accélération puis décélération);
  • OutIn : la première partie est de type Out, la seconde relevant du type In (effet de pause en milieu de parcours avec un premier freinage puis une accélération positive).

De nombreux frameworks ignorent le type OutIn : par exemple, FireMonkey (Delphi) ou .Net. Il peut sembler peu naturel par rapport aux autres, mais nous l'implémenterons malgré tout.

Voici une représentation graphique générale de ces quatre types d'interpolation :

Image non disponible

À partir de ces types fondamentaux, les courbes réelles plus ou moins prononcées sont obtenues selon les fonctions, formules ou degrés appliqués.

II. Une application pour le test des courbes d'easing

II-A. L'interface de l'application de test

[Exemple Interpolations 02]

Pour un aperçu efficace des courbes d'easing, nous allons créer une application sans prétention dont l'interface ressemblera à ceci :

Image non disponible

Nous reconnaissons :

  • trois zones délimitées par des panneaux TPanel (un pour les contrôles de types Out ou In, un pour les contrôles de types InOut ou OutIn et un pour un composant TTrackbar afin d'ajuster la vitesse de l'animation ;
  • une séparation ajustable TSplitter entre les deux panneaux supérieurs afin de moduler les distances à parcourir par les contrôles animés (elle est à deviner ;-) ) ;
  • des boutons TButton qui serviront d'objets animés (21 dans chaque panneau : 1 pour un repère linéaire et 10 pour chaque type).

Voici le fichier LFM correspondant :

 
Sélectionnez
object MainForm: TMainForm
  Left = 304
  Height = 623
  Top = 197
  Width = 953
  Caption = 'Test des interpolations - © G. Vasseur 2018'
  ClientHeight = 623
  ClientWidth = 953
  OnCreate = FormCreate
  LCLVersion = '1.8.2.0'
  object pnlMain: TPanel
    Left = 10
    Height = 553
    Top = 10
    Width = 933
    Align = alClient
    BorderSpacing.Around = 10
    ClientHeight = 553
    ClientWidth = 933
    TabOrder = 0
    OnClick = btnLinear2Click
    object pnlLeft: TPanel
      Left = 1
      Height = 551
      Top = 1
      Width = 474
      Align = alLeft
      ClientHeight = 551
      ClientWidth = 474
      TabOrder = 0
      OnClick = btnLinear2Click
      object btnEaseOutBounce: TButton
        Left = 0
        Height = 25
        Top = 256
        Width = 120
        Caption = 'EaseOutBounce'
        OnClick = btnEaseOutQuadClick
        TabOrder = 0
      end
      object btnEaseOutBack: TButton
        Left = 0
        Height = 25
        Top = 232
        Width = 120
        Caption = 'EaseOutBack'
        OnClick = btnEaseOutQuadClick
        TabOrder = 1
      end
      object btnEaseOutElastic: TButton
        Left = 0
        Height = 25
        Top = 208
        Width = 120
        Caption = 'EaseOutElastic'
        OnClick = btnEaseOutQuadClick
        TabOrder = 2
      end
      object btnEaseOutCirc: TButton
        Left = 0
        Height = 25
        Top = 184
        Width = 120
        Caption = 'EaseOutCirc'
        OnClick = btnEaseOutQuadClick
        TabOrder = 3
      end
      object btnEaseOutSine: TButton
        Left = 0
        Height = 25
        Top = 136
        Width = 120
        Caption = 'EaseOutSine'
        OnClick = btnEaseOutQuadClick
        TabOrder = 4
      end
      object btnEaseOutQuint: TButton
        Left = 0
        Height = 25
        Top = 112
        Width = 120
        Caption = 'EaseOutQuint'
        OnClick = btnEaseOutQuadClick
        TabOrder = 5
      end
      object btnEaseOutQuad: TButton
        Left = 0
        Height = 25
        Top = 40
        Width = 120
        Caption = 'EaseOutQuad'
        OnClick = btnEaseOutQuadClick
        TabOrder = 6
      end
      object btnEaseOutCubic: TButton
        Left = 0
        Height = 25
        Top = 64
        Width = 120
        Caption = 'EaseOutCubic'
        OnClick = btnEaseOutQuadClick
        TabOrder = 7
      end
      object btnEaseOutQuart: TButton
        Left = 0
        Height = 25
        Top = 88
        Width = 120
        Caption = 'EaseOutQuart'
        OnClick = btnEaseOutQuadClick
        TabOrder = 8
      end
      object btnEaseOutExpo: TButton
        Left = 0
        Height = 25
        Top = 160
        Width = 120
        Caption = 'EaseOutExpo'
        OnClick = btnEaseOutQuadClick
        TabOrder = 9
      end
      object btnEaseInQuad: TButton
        Left = 1
        Height = 25
        Top = 296
        Width = 120
        Caption = 'EaseInQuad'
        OnClick = btnEaseInQuadClick
        TabOrder = 10
      end
      object btnEaseInCubic: TButton
        Left = 1
        Height = 25
        Top = 320
        Width = 120
        Caption = 'EaseInCubic'
        OnClick = btnEaseInQuadClick
        TabOrder = 11
      end
      object btnEaseInQuart: TButton
        Left = 1
        Height = 25
        Top = 344
        Width = 120
        Caption = 'EaseInQuart'
        OnClick = btnEaseInQuadClick
        TabOrder = 12
      end
      object btnEaseInQuint: TButton
        Left = 1
        Height = 25
        Top = 368
        Width = 120
        Caption = 'EaseInQuint'
        OnClick = btnEaseInQuadClick
        TabOrder = 13
      end
      object btnEaseInSine: TButton
        Left = 1
        Height = 25
        Top = 392
        Width = 120
        Caption = 'EaseInSine'
        OnClick = btnEaseInQuadClick
        TabOrder = 14
      end
      object btnEaseInExpo: TButton
        Left = 1
        Height = 25
        Top = 416
        Width = 120
        Caption = 'EaseInExpo'
        OnClick = btnEaseInQuadClick
        TabOrder = 15
      end
      object btnEaseInCirc: TButton
        Left = 1
        Height = 25
        Top = 440
        Width = 120
        Caption = 'EaseInCirc'
        OnClick = btnEaseInQuadClick
        TabOrder = 16
      end
      object btnEaseInElastic: TButton
        Left = 1
        Height = 25
        Top = 464
        Width = 120
        Caption = 'EaseInElastic'
        OnClick = btnEaseInQuadClick
        TabOrder = 17
      end
      object btnEaseInBack: TButton
        Left = 1
        Height = 25
        Top = 488
        Width = 120
        Caption = 'EaseInBack'
        OnClick = btnEaseInQuadClick
        TabOrder = 18
      end
      object btnEaseInBounce: TButton
        Left = 1
        Height = 25
        Top = 512
        Width = 120
        Caption = 'EaseInBounce'
        OnClick = btnEaseInQuadClick
        TabOrder = 19
      end
      object btnLinear: TButton
        Left = 0
        Height = 25
        Top = 8
        Width = 120
        Caption = 'Linear'
        Font.Style = [fsBold]
        OnClick = btnLinear2Click
        ParentFont = False
        TabOrder = 20
      end
    end
    object pnlRight: TPanel
      Left = 480
      Height = 551
      Top = 1
      Width = 452
      Align = alClient
      ClientHeight = 551
      ClientWidth = 452
      TabOrder = 1
      OnClick = btnLinear2Click
      object btnLinear2: TButton
        Left = 0
        Height = 25
        Top = 8
        Width = 120
        Caption = 'Linear'
        Font.Style = [fsBold]
        OnClick = btnLinear2Click
        ParentFont = False
        TabOrder = 0
      end
      object btnEaseInOutCubic: TButton
        Left = 0
        Height = 25
        Top = 320
        Width = 120
        Caption = 'EaseInOutCubic'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 1
      end
      object btnEaseInOutQuart: TButton
        Left = 0
        Height = 25
        Top = 344
        Width = 120
        Caption = 'EaseInOutQuart'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 2
      end
      object btnEaseOutInQuart: TButton
        Left = 0
        Height = 25
        Top = 88
        Width = 120
        Caption = 'EaseOutInQuart'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 3
      end
      object btnEaseOutInCubic: TButton
        Left = 0
        Height = 25
        Top = 64
        Width = 120
        Caption = 'EaseOutInCubic'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 4
      end
      object btnEaseOutInQuint: TButton
        Left = 0
        Height = 25
        Top = 112
        Width = 120
        Caption = 'EaseOutInQuint'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 5
      end
      object btnEaseInOutQuint: TButton
        Left = 0
        Height = 25
        Top = 368
        Width = 120
        Caption = 'EaseInOutQuint'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 6
      end
      object btnEaseOutInSine: TButton
        Left = 0
        Height = 25
        Top = 136
        Width = 120
        Caption = 'EaseOutInSine'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 7
      end
      object btnEaseInOutQuad: TButton
        Left = 0
        Height = 25
        Top = 296
        Width = 120
        Caption = 'EaseInOutQuad'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 8
      end
      object btnEaseOutInQuad: TButton
        Left = 0
        Height = 25
        Top = 40
        Width = 120
        Caption = 'EaseOutInQuad'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 9
      end
      object btnEaseInOutSine: TButton
        Left = 0
        Height = 25
        Top = 392
        Width = 120
        Caption = 'EaseInOutSine'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 10
      end
      object btnEaseOutInCirc: TButton
        Left = 0
        Height = 25
        Top = 184
        Width = 120
        Caption = 'EaseOutInCirc'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 11
      end
      object btnEaseOutInElastic: TButton
        Left = 0
        Height = 25
        Top = 208
        Width = 120
        Caption = 'EaseOutInElastic'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 12
      end
      object btnEaseOutInBack: TButton
        Left = 0
        Height = 25
        Top = 232
        Width = 120
        Caption = 'EaseOutInBack'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 13
      end
      object btnEaseOutInBounce: TButton
        Left = 0
        Height = 25
        Top = 256
        Width = 120
        Caption = 'EaseOutInBounce'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 14
      end
      object btnEaseOutInExpo: TButton
        Left = 0
        Height = 25
        Top = 160
        Width = 120
        Caption = 'EaseOutInExpo'
        OnClick = btnEaseOutInQuadClick
        TabOrder = 15
      end
      object btnEaseInOutExpo: TButton
        Left = 0
        Height = 25
        Top = 416
        Width = 120
        Caption = 'EaseInOutExpo'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 16
      end
      object btnEaseInOutCirc: TButton
        Left = 0
        Height = 25
        Top = 440
        Width = 120
        Caption = 'EaseInOutCirc'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 17
      end
      object btnEaseInOutElastic: TButton
        Left = 0
        Height = 25
        Top = 464
        Width = 120
        Caption = 'EaseInOutElastic'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 18
      end
      object btnEaseInOutBack: TButton
        Left = 0
        Height = 25
        Top = 488
        Width = 120
        Caption = 'EaseInOutBack'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 19
      end
      object btnEaseInOutBounce: TButton
        Left = 0
        Height = 25
        Top = 512
        Width = 120
        Caption = 'EaseInOutBounce'
        OnClick = btnEaseInOutQuadClick
        TabOrder = 20
      end
    end
    object Splitter1: TSplitter
      Left = 475
      Height = 551
      Top = 1
      Width = 5
    end
  end
  object pnlBottom: TPanel
    Left = 0
    Height = 50
    Top = 573
    Width = 953
    Align = alBottom
    BorderSpacing.InnerBorder = 10
    ClientHeight = 50
    ClientWidth = 953
    TabOrder = 1
    object tbarWait: TTrackBar
      Left = 11
      Height = 28
      Top = 11
      Width = 301
      Max = 100
      OnChange = tbarWaitChange
      Position = 0
      Align = alLeft
      BorderSpacing.Around = 10
      TabOrder = 0
    end
  end
end

En dehors de gestionnaires d'événements sur lesquelles nous reviendrons plus tard, ce fichier révèle les noms des catégories auxquelles seront appliqués les types déjà décrits :

  • Quad : application d'une formule du second degré ;
  • Cubic : application d'une formule de degré 3 ;
  • Quart : application d'une formule de degré 4 ;
  • Quint : application d'une formule de degré 5 ;
  • Sine : application d'une formule comportant une fonction sinusoïdale ;
  • Expo : application d'une formule à base d'exponentielle (puissances de 2) ;
  • Circ : application d'une formule à base de cercle ;
  • Elastic : formule créant un effet d'élastique à base de fonctions sinusoïdales ;
  • Back : formule créant un aller-retour aux extrémités à partir de puissances de 2 et de coefficients particuliers ;
  • Bounce : formule créant un effet de rebonds à partir d'une série de sous-formules à base de puissances de 2.

Les courbes dessinées ressembleront à celles-ci :

Image non disponible

La première ligne correspond à la catégorie Quad : les autres catégories utilisant des degrés accentuent simplement les effets obtenus.

Toutes ces catégories prendront des paramètres similaires déduits du fonctionnement d'une interpolation. La base est évidemment la formule toute simple de l'interpolation linéaire habituelle :

 
Sélectionnez
function TMainForm.EaseNone(AStart, AChange, AStep: Single): Single;
// *** pas de fonction ease ***
begin
  Result := AChange * AStep / fDuration + AStart;
end;

Nous retrouvons :

  • le point de départ : AStart ;
  • le point d'arrivée : AChange ;
  • le pas (ou intervalle de temps) : AStep ;
  • la durée totale de l'interpolation : fDuration.

La formule, encore une fois, ne fait qu'exprimer un pourcentage de réalisation de la valeur finale auquel on aura ajouté la valeur de départ.

Nous avons choisi de traiter à part la durée (d'où son préfixe particulier), car elle est sous contrôle de la valeur du composant de type TTrackbar. Dans cet exemple, nos fonctions n'auront donc que trois paramètres en entrée sur les quatre attendus.

II-B. L'implémentation des fonctions

Plusieurs sites proposent les algorithmes permettant de réaliser des fonctions d'easing. Le plus clair à mon avis est celui-ci : ici.

En Pascal, notre ami BeanzMaster propose sa propre implémentation, forcément très proche, mais qui se limite à des fonctions simples, sans les classes que nous développerons plus loin. Vous trouverez ici son travail.

Les paragraphes qui suivent proposent une implémentation originale de ces algorithmes bien connus. Les classes seront construites dans un second temps.

Certains algorithmes font appel à des calculs complexes souvent réduits dans les formules à des valeurs numériques énigmatiques. Ces calculs ne sont pas nécessaires à la compréhension de la suite. Je n'ai parfois trouvé qu'une explication en japonais que les traducteurs automatiques avaient bien du mal à traduire...

II-B-1. Les fonctions utilisant des degrés de polynômes

Les fonctions comprenant les termes Quad, Cubic, Quart et Quint s'appuient respectivement sur des formules de degrés 2, 3, 4 et 5.

Elles utilisent les trois méthodes générales suivantes :

 
Sélectionnez
function TMainForm.InPower(AStart, AChange, AStep: Single; APower: Integer
  ): Single;
// *** calcul d'une interpolation de type IN pour les puissances ***
begin
  Result := AChange * Power(AStep / fDuration, APower) + AStart;
end;

function TMainForm.OutPower(AStart, AChange, AStep: Single; APower: Integer
  ): Single;
// *** calcul d'une interpolation de type OUT pour les puissances ***
var
  LSign: Integer;
begin
  LSign := ifthen(Odd(APower), 1, -1);
  Result := LSign * AChange * (Power(AStep / fDuration - 1, APower) + LSign) + AStart;
end;

function TMainForm.InOutPower(AStart, AChange, AStep: Single; APower: Integer
  ): Single;
// *** calcul d'une interpolation de type INOUT pour les puissances ***
var
  LSign: Integer;
begin
  LSign := ifthen(Odd(APower), 1, -1);
  AStep := AStep / fDuration * 2;
  if AStep < 1 then
    Result := AChange / 2 * Power(AStep, APower) + AStart
  else
    Result :=  LSign * AChange / 2 * (Power(AStep - 2, APower) + LSign * 2) + AStart;
end;

Vous pouvez utiliser la fonction ifthen pour simplifier le test des conditions. Cette fonction est présente dans l'unité math qu'il faut donc référencer dans la clause uses de la section implementation de l'unité.

La fonction ifthen prend trois paramètres en entrée :

  • une valeur booléenne ;
  • une expression renvoyée si la valeur booléenne est vraie ;
  • une expression renvoyée si la valeur booléenne est fausse.

Ces fonctions reposent sur l'utilisation de la fonction Power qui renvoie une puissance d'un nombre flottant fourni en paramètre.

Une fois ces bases définies, l'implémentation des fonctions est plutôt simple, voire répétitive !

Voici les fonctions associées à la catégorie Quad (degré 2) :

 
Sélectionnez
function TMainForm.EaseInQuad(AStart, AChange, AStep: Single): Single;
// *** INQUAD ***
begin
  Result := InPower(AStart, AChange, AStep, 2);
end;

function TMainForm.EaseOutQuad(AStart, AChange, AStep: Single): Single;
// *** OUTQUAD ***
begin
  Result := OutPower(AStart, AChange, AStep, 2);
end;

function TMainForm.EaseInOutQuad(AStart, AChange, AStep: Single): Single;
// *** INOUTQUAD ***
begin
  Result := InOutPower(AStart, AChange, AStep, 2);
end;

function TMainForm.EaseOutInQuad(AStart, AChange, AStep: Single): Single;
// *** OUTINQUAD ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutQuad(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInQuad(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

Nous n'avons fait qu'appeler les fonctions de base avec le degré voulu. Il en sera de même pour les fonctions associées à la catégorie Cubic (degré 3) :

 
Sélectionnez
function TMainForm.EaseInCubic(AStart, AChange, AStep: Single): Single;
// *** INCUBIC ***
begin
  Result := InPower(AStart, AChange, AStep, 3);
end;

function TMainForm.EaseOutCubic(AStart, AChange, AStep: Single): Single;
// *** OUTCUBIC ***
begin
  Result := OutPower(AStart, AChange, AStep, 3);
end;

function TMainForm.EaseInOutCubic(AStart, AChange, AStep: Single): Single;
// *** INOUTCUBIC ***
begin
  Result := InOutPower(AStart, AChange, AStep, 3);
end;

function TMainForm.EaseOutInCubic(AStart, AChange, AStep: Single): Single;
// *** OUTINCUBIC ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutCubic(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInCubic(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

Les fonctions relatives à la catégorie Quart (degré 4) ne font pas exception :

 
Sélectionnez
function TMainForm.EaseInQuart(AStart, AChange, AStep: Single): Single;
// *** INQUART ***
begin
  Result := InPower(AStart, AChange, AStep, 4);
end;

function TMainForm.EaseOutQuart(AStart, AChange, AStep: Single): Single;
// *** OUTQUART ***
begin
  Result := OutPower(AStart, AChange, AStep, 4);
end;

function TMainForm.EaseInOutQuart(AStart, AChange, AStep: Single): Single;
// *** INOUTQUART ***
begin
  Result := InOutPower(AStart, AChange, AStep, 4);
end;

function TMainForm.EaseOutInQuart(AStart, AChange, AStep: Single): Single;
// *** OUTINQUART ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutQuart(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInQuart(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

Est-il besoin de préciser qu'il en est de même pour la catégorie Quint (degré 5) ?

 
Sélectionnez
function TMainForm.EaseInQuint(AStart, AChange, AStep: Single): Single;
// *** INQUINT ***
begin
  Result := InPower(AStart, AChange, AStep, 5);
end;

function TMainForm.EaseOutQuint(AStart, AChange, AStep: Single): Single;
// *** OUTQUINT ***
begin
  Result := OutPower(AStart, AChange, AStep, 5);
end;

function TMainForm.EaseInOutQuint(AStart, AChange, AStep: Single): Single;
// *** INOUTQUINT ***
begin
  Result := InOutPower(AStart, AChange, AStep, 5);
end;

function TMainForm.EaseOutInQuint(AStart, AChange, AStep: Single): Single;
// *** OUTINQUINT ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutQuint(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInQuint(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

II-B-2. Les fonctions à base de fonctions sinusoïdales

Les fonctions à base de fonctions sinusoïdales font appel, selon le besoin, au sinus ou au cosinus de la valeur en cours à appliquer au point d'arrivée. Le cosinus sera utilisé pour l'accélération positive alors que le sinus conviendra pour la décélération.

Voici les fonctions telles que nous les définirons :

 
Sélectionnez
function TMainForm.EaseInSine(AStart, AChange, AStep: Single): Single;
// *** INSINE ***
begin
  Result := - AChange * cos(AStep / fDuration * Pi / 2) + AChange + AStart;
end;

function TMainForm.EaseOutSine(AStart, AChange, AStep: Single): Single;
// *** OUTSINE ***
begin
  Result := AChange * sin(AStep / fDuration * Pi / 2) + AStart;
end;

function TMainForm.EaseInOutSine(AStart, AChange, AStep: Single): Single;
// *** INOUTSINE ***
begin
  Result := - AChange / 2 * (cos(AStep / fDuration * Pi) - 1) + AStart;
end;

function TMainForm.EaseOutInSine(AStart, AChange, AStep: Single): Single;
// *** OUTINSINE ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutSine(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInSine(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

Finalement, elles ne diffèrent guère des fonctions déjà implémentées : les puissances ont cédé la place aux fonctions trigonométriques, voilà tout ! Nous verrons, à quelques détails près qui tiennent à des valeurs exclues pour certaines fonctions ou à certaines singularités des effets désirés, que notre schéma de départ est applicable la plupart du temps.

II-B-3. Les fonctions exponentielles

Sans surprise, la catégorie des fonctions Expo sera implémentée ainsi :

 
Sélectionnez
function TMainForm.EaseInExpo(AStart, AChange, AStep: Single): Single;
// *** INEXPO ***
begin
  Result := ifthen(AStep = 0, AStart,
    AChange * Power(2, 10 * (AStep / fDuration - 1)) + AStart);
end;

function TMainForm.EaseOutExpo(AStart, AChange, AStep: Single): Single;
// *** OUTEXPO ***
begin
  Result := ifthen(AStep = fDuration, AChange + AStart,
    AChange * (- Power(2, -10 * AStep / fDuration) + 1) + AStart);
end;

function TMainForm.EaseInOutExpo(AStart, AChange, AStep: Single): Single;
// *** INOUTEXPO ***
begin
  if AStep = 0 then
    Result := AStart
  else
  if AStep = fDuration then
    Result := AChange + AStart
  else
  begin
    AStep := AStep / fDuration * 2;
    if AStep < 1 then
      Result := AChange / 2 * Power(2, 10 * (AStep - 1)) + AStart
    else
      Result := AChange / 2 * (- Power(2, - 10 * (AStep - 1)) + 2) + AStart;
  end;
end;

function TMainForm.EaseOutInExpo(AStart, AChange, AStep: Single): Single;
// *** OUTINEXPO ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutExpo(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInExpo(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

II-B-4. Les fonctions fondées sur un cercle

Celles relatives au cercle de la catégorie Circ auront l'implémentation suivante :

 
Sélectionnez
function TMainForm.EaseInCirc(AStart, AChange, AStep: Single): Single;
// *** INCIRC ***
begin
  Result := - AChange * (Sqrt(1 - Power(AStep / fDuration, 2)) - 1) + AStart;
end;

function TMainForm.EaseOutCirc(AStart, AChange, AStep: Single): Single;
// *** OUTCIRC ***
begin
  Result := AChange * Sqrt(1 - Power(AStep / fDuration - 1, 2)) + AStart;
end;

function TMainForm.EaseInOutCirc(AStart, AChange, AStep: Single): Single;
// *** INOUTCIRC ***
begin
  AStep := AStep / fDuration * 2;
  if AStep < 1 then
    Result := - AChange / 2 * (Sqrt(1 - Power(AStep, 2)) - 1) + AStart
  else
    Result := AChange / 2 * (Sqrt(1 - Power(AStep - 2, 2)) + 1) + AStart;
end;

function TMainForm.EaseOutInCirc(AStart, AChange, AStep: Single): Single;
// *** OUTINCIRC ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutCirc(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInCirc(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

II-B-5. Les fonctions à effet d'élastique

Bien sûr, les fonctions de la catégorie Elastic sont plus complexes. Elles s'appuient à la fois sur le caractère amplificateur des puissances de 2 et sur les cycles créés par la fonction sinus :

 
Sélectionnez
function TMainForm.EaseInElastic(AStart, AChange, AStep: Single): Single;
// *** INELASTIC ***
begin
  if AStep = 0 then
    Result := AStart
  else
  begin
    AStep := AStep / fDuration;
    if AStep = 1 then
      Result := AChange + AStart
    else
    begin
      AStep := AStep - 1;
      Result := - (AChange * Power(2, 10 * AStep) * sin((AStep * fDuration
        - (fDuration * 0.3 / 4)) * 2 * Pi / (fDuration * 0.3))) + AStart;
    end;
  end;
end;

function TMainForm.EaseOutElastic(AStart, AChange, AStep: Single): Single;
// *** OUTELASTIC ***
begin
  if AStep = 0 then
    Result := AStart
  else
  begin
    AStep := AStep / fDuration;
    if AStep = 1 then
      Result := AChange + AStart
    else
      Result := AChange * Power(2, - 10 * AStep) * sin((AStep * fDuration -
        (fDuration * 0.3 / 4)) * 2 * Pi / (fDuration * 0.3)) + AChange + AStart;
   end;
end;

function TMainForm.EaseInOutElastic(AStart, AChange, AStep: Single): Single;
// *** INOUTELASTIC ***
begin
  if AStep = 0 then
    Result := AStart
  else
  begin
    AStep := AStep / fDuration * 2;
    if AStep = 2 then
      Result := AChange + AStart
    else
    begin
      // 0.45 = 0.3*1.5  0.1125 = 0.45 / 4
      if AStep < 1 then
      begin
        AStep := AStep - 1;
        Result := - (AChange * Power(2, 10 * AStep) * sin((AStep * fDuration -
          fDuration * 0.1125) * 2 * Pi / (fDuration * 0.45))) / 2 + AStart;
      end
      else
      begin
        AStep := AStep - 1;
        Result :=  AChange * Power(2, - 10 * AStep) * sin((AStep * fDuration -
          fDuration * 0.1125) * 2 * Pi / (fDuration * 0.45)) / 2 + AChange + AStart;
      end;
    end;
  end;
end;

function TMainForm.EaseOutInElastic(AStart, AChange, AStep: Single): Single;
// *** OUTINELASTIC ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutElastic(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInElastic(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

L'effet produit n'en est que plus spectaculaire !

II-B-6. Les fonctions avec aller-retour

Les fonctions relatives à la catégorie Back complexifient encore les calculs. Nous les implémenterons en prenant une valeur correspondant à un rebond de 10%, mais d'autres valeurs indiquées dans le code sont utilisables : ces nombres mystérieux cachent en fait des calculs plutôt fastidieux qui permettent de calculer l'amplitude des allers-retours.

Voici l'implémentation adoptée :

 
Sélectionnez
function TMainForm.EaseInBack(AStart, AChange, AStep: Single): Single;
// *** INBACK ***
begin
  // 1.70158 = 10% 2.592389 = 20% 3.394051 = 30% 4.15574465 = 40%
  Result := AChange * Power(AStep / fDuration, 2) * (2.70158 *
    AStep / fDuration - 1.70158) + AStart;
end;

function TMainForm.EaseOutBack(AStart, AChange, AStep: Single): Single;
// *** OUTBACK ***
begin
  Result := AChange * (Power(AStep / fDuration - 1, 2) *
    (2.70158 * (AStep / fDuration - 1) + 1.70158) + 1) + AStart;
end;

function TMainForm.EaseInOutBack(AStart, AChange, AStep: Single): Single;
// *** INOUTBACK ***
begin
  // 2.5949095 = 1.70158 * 1.525
  AStep := AStep / fDuration * 2;
  if AStep < 1 then
    Result := AChange / 2 * Power(AStep, 2) * (3.594905 * AStep - 2.594905)
      + AStart
  else
    Result := AChange / 2 * (Power(AStep - 2, 2) * (3.594905 * (AStep - 2)
      + 2.594905) + 2) + AStart;
end;

function TMainForm.EaseOutInBack(AStart, AChange, AStep: Single): Single;
// *** OUTINBACK ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutBack(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInBack(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

II-B-7. Les fonctions avec rebonds

Enfin, les fonctions relatives à la catégorie Bounce sont les plus longs. Il faut en effet calculer chaque étape afin d'obtenir les résultats les plus proches possibles de rebonds qui paraissent naturels.

Voici l'implémentation adoptée :

 
Sélectionnez
function TMainForm.EaseInOutBounce(AStart, AChange, AStep: Single): Single;
// *** INOUTBOUNCE ***
begin
  if AStep < fDuration / 2 then
    Result := EaseInBounce(0, AChange, AStep * 2) / 2 + AStart
  else
    Result := EaseOutBounce(0, AChange, AStep * 2 - fDuration) / 2 + AChange / 2
      + AStart;
end;

function TMainForm.EaseOutInBounce(AStart, AChange, AStep: Single): Single;
// *** OUTINBOUNCE ***
begin
  if AStep < fDuration / 2 then
    Result := EaseOutBounce(AStart, AChange / 2, AStep * 2)
  else
    Result := EaseInBounce(AStart + AChange / 2, AChange / 2, AStep * 2 - fDuration);
end;

function TMainForm.EaseInBounce(AStart, AChange, AStep: Single): Single;
// *** INBOUNCE ***
begin
  Result := AChange - EaseOutBounce(0, AChange, fDuration - AStep) + AStart;
end;

function TMainForm.EaseOutBounce(AStart, AChange, AStep: Single): Single;
// *** OUTBOUNCE ***
begin
  AStep := AStep / fDuration;
  if AStep < 1 / 2.75 then
    Result := AChange * 7.5625 * Power(AStep, 2) + AStart
  else
  if AStep < 2 / 2.75 then
  begin
    AStep := AStep - 1.5 / 2.75;
    Result := AChange * (7.5625 * Power(AStep, 2) + 0.75) + AStart;
  end
  else
  if AStep < 2.5 / 2.75 then
  begin
    AStep := AStep - 2.25 / 2.75;
    Result := AChange * (7.5625 * Power(AStep, 2) + 0.9375) + AStart;
  end
  else
  begin
    AStep := AStep - 2.625 / 2.75;
    Result := AChange * (7.5625 * Power(AStep, 2) + 0.984375) + AStart;
  end;
end;

II-C. L'application de test complète

Une fois toutes ces fonctions solidement définies, le reste de l'application ne met en œuvre que des techniques habituelles.

Nous déclarons des constantes pour la valeur de fin à atteindre et la durée par défaut ainsi qu'une énumération afin de différencier les interpolations :

 
Sélectionnez
const
 C_DefaultDuration = 100;
 C_End = 200;

type

 TInterpolation = (intLinear,
    intEaseInQuad, intEaseOutQuad, intEaseInOutQuad, intEaseOutInQuad,
    intEaseInCubic, intEaseOutCubic, intEaseInOutCubic, intEaseOutInCubic,
    intEaseInQuart, intEaseOutQuart, intEaseInOutQuart, intEaseOutInQuart,
    intEaseInQuint, intEaseOutQuint, intEaseInOutQuint, intEaseOutInQuint,
    intEaseInSine, intEaseOutSine, intEaseInOutSine, intEaseOutInSine,
    intEaseInExpo, intEaseOutExpo, intEaseInOutExpo, intEaseOutInExpo,
    intEaseInCirc, intEaseOutCirc, intEaseInOutCirc, intEaseOutInCirc,
    intEaseInElastic, intEaseOutElastic, intEaseInOutElastic, intEaseOutInElastic,
    intEaseInBack, intEaseOutBack, intEaseInOutBack, intEaseOutInBack,
    intEaseInBounce, intEaseOutBounce, intEaseInOutBounce, intEaseOutInBounce
    );

Il nous reste à déclarer et définir deux fonctions pour des appels aux fonctions d’interpolation appropriées, l'une renvoyant un nombre flottant, l'autre un entier :

 
Sélectionnez
function TMainForm.ComputeInterpolation(AStart, AEnd, AStep: Single;
  AInter: TInterpolation; ABack: Boolean): Single;
// *** calcul des interpolations ***
begin
  case AInter of
    // linéaire
    intLinear: Result := EaseNone(AStart, AEnd, AStep);
    // quadratique
    intEaseInQuad: Result := EaseInQuad(AStart, AEnd, AStep);
    intEaseOutQuad: Result := EaseOutQuad(AStart, AEnd, AStep);
    intEaseInOutQuad: Result := EaseInOutQuad(AStart, AEnd, AStep);
    intEaseOutInQuad: Result := EaseOutInQuad(AStart, AEnd, AStep);
    // cubique
    intEaseInCubic: Result := EaseInCubic(AStart, AEnd, AStep);
    intEaseOutCubic: Result := EaseOutCubic(AStart, AEnd, AStep);
    intEaseInOutCubic: Result := EaseInOutCubic(AStart, AEnd, AStep);
    intEaseOutInCubic: Result := EaseOutInCubic(AStart, AEnd, AStep);
    // quartique
    intEaseInQuart: Result := EaseInQuart(AStart, AEnd, AStep);
    intEaseOutQuart: Result := EaseOutQuart(AStart, AEnd, AStep);
    intEaseInOutQuart:  Result := EaseInOutQuart(AStart, AEnd, AStep);
    intEaseOutInQuart: Result := EaseOutInQuart(AStart, AEnd, AStep);
    // quintique
    intEaseInQuint: Result := EaseInQuint(AStart, AEnd, AStep);
    intEaseOutQuint: Result := EaseOutQuint(AStart, AEnd, AStep);
    intEaseInOutQuint:  Result := EaseInOutQuint(AStart, AEnd, AStep);
    intEaseOutInQuint: Result := EaseOutInQuint(AStart, AEnd, AStep);
    // sinus
    intEaseInSine: Result := EaseInSine(AStart, AEnd, AStep);
    intEaseOutSine: Result := EaseOutSine(AStart, AEnd, AStep);
    intEaseInOutSine: Result := EaseInOutSine(AStart, AEnd, AStep);
    intEaseOutInSine: Result := EaseOutInSine(AStart, AEnd, AStep);
    // exponentielle
    intEaseInExpo: Result := EaseInExpo(AStart, AEnd, AStep);
    intEaseOutExpo: Result := EaseOutExpo(AStart, AEnd, AStep);
    intEaseInOutExpo: Result := EaseInOutExpo(AStart, AEnd, AStep);
    intEaseOutInExpo: Result := EaseOutInExpo(AStart, AEnd, AStep);
    // cercle
    intEaseInCirc: Result := EaseInCirc(AStart, AEnd, AStep);
    intEaseOutCirc: Result := EaseOutCirc(AStart, AEnd, AStep);
    intEaseInOutCirc: Result := EaseInOutCirc(AStart, AEnd, AStep);
    intEaseOutInCirc: Result := EaseOutInCirc(AStart, AEnd, AStep);
    // élastique
    intEaseInElastic: Result := EaseInElastic(AStart, AEnd, AStep);
    intEaseOutElastic: Result := EaseOutElastic(AStart, AEnd, AStep);
    intEaseInOutElastic: Result := EaseInOutElastic(AStart, AEnd, AStep);
    intEaseOutInElastic: Result := EaseOutInElastic(AStart, AEnd, AStep);
    // retour
    intEaseInBack: Result := EaseInBack(AStart, AEnd, AStep);
    intEaseOutBack: Result := EaseOutBack(AStart, AEnd, AStep);
    intEaseInOutBack: Result := EaseInOutBack(AStart, AEnd, AStep);
    intEaseOutInBack: Result := EaseOutInBack(AStart, AEnd, AStep);
    // rebond
    intEaseInBounce: Result := EaseInBounce(AStart, AEnd, AStep);
    intEaseOutBounce: Result := EaseOutBounce(AStart, AEnd, AStep);
    intEaseInOutBounce: Result := EaseInOutBounce(AStart, AEnd, AStep);
    intEaseOutInBounce: Result := EaseOutInBounce(AStart, AEnd, AStep);
  end;
  if ABack then
    Result := AEnd - Result;
end;

function TMainForm.ComputeInterpolationInt(AStart, AEnd: Integer;
  AStep: Integer; AInter: TInterpolation; ABack: Boolean): Integer;
// *** interpolation entière ***
begin
  Result := Round(ComputeInterpolation(AStart, AEnd, AStep, AInter, ABack));
end;

Comme nous devions l'imaginer, la première fonction n'est qu'une sorte de gare de triage alors que la seconde utilise la première en arrondissant son résultat.

Pour le reste du code, il s'agit de réagir à des clics de souris. Nous permettrons à l'utilisateur de cliquer sur un bouton d'un groupe particulier de manière à ne déclencher les mouvements que pour cet unique groupe, ou sur un des panneaux, ce qui mettra tous les boutons en branle.

Voici le code activé par un clic sur un panneau :

 
Sélectionnez
procedure TMainForm.btnLinear2Click(Sender: TObject);
// *** lancement des animations ***
var
  Li, LWidth, LWidth2: Integer;
  LBack: Boolean;
begin
  tbarWait.Enabled := False;
  LWidth := pnlLeft.Width - btnLinear2.Width;
  LWidth2 := pnlRight.Width - btnLinear.Width;
  for LBack := False to True do
    for Li := 1 to fDuration do
    begin
      btnLinear.Left := ComputeInterpolationInt(0, LWidth, Li, intLinear, LBack);
      btnLinear2.Left := ComputeInterpolationInt(0, LWidth2, Li, intLinear, LBack);

      btnEaseInQuad.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInQuad, LBack);
      btnEaseOutQuad.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutQuad, LBack);
      btnEaseInOutQuad.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutQuad, LBack);
      btnEaseOutInQuad.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInQuad, LBack);

      btnEaseInCubic.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInCubic, LBack);
      btnEaseOutCubic.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutCubic, LBack);
      btnEaseInOutCubic.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutCubic, LBack);
      btnEaseOutInCubic.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInCubic, LBack);

      btnEaseInQuart.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInQuart, LBack);
      btnEaseOutQuart.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutQuart, LBack);
      btnEaseInOutQuart.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutQuart, LBack);
      btnEaseOutInQuart.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInQuart, LBack);

      btnEaseInQuint.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInQuint, LBack);
      btnEaseOutQuint.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutQuint, LBack);
      btnEaseInOutQuint.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutQuint, LBack);
      btnEaseOutInQuint.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInQuint, LBack);

      btnEaseInSine.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInSine, LBack);
      btnEaseOutSine.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutSine, LBack);
      btnEaseInOutSine.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutSine, LBack);
      btnEaseOutInSine.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInSine, LBack);

      btnEaseInExpo.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInExpo, LBack);
      btnEaseOutExpo.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutExpo, LBack);
      btnEaseInOutExpo.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutExpo, LBack);
      btnEaseOutInExpo.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInExpo, LBack);

      btnEaseInCirc.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInCirc, LBack);
      btnEaseOutCirc.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutCirc, LBack);
      btnEaseInOutCirc.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutCirc, LBack);
      btnEaseOutInCirc.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInCirc, LBack);

      btnEaseInElastic.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInElastic, LBack);
      btnEaseOutElastic.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutElastic, LBack);
      btnEaseInOutElastic.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutElastic, LBack);
      btnEaseOutInElastic.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInElastic, LBack);

      btnEaseInBack.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInBack, LBack);
      btnEaseOutBack.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutBack, LBack);
      btnEaseInOutBack.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutBack, LBack);
      btnEaseOutInBack.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInBack, LBack);

      btnEaseInBounce.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInBounce, LBack);
      btnEaseOutBounce.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseOutBounce, LBack);
      btnEaseInOutBounce.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseInOutBounce, LBack);
      btnEaseOutInBounce.Left := ComputeInterpolationInt(0, LWidth2, Li, intEaseOutInBounce, LBack);

      sleep(10);
      Repaint;
      Application.ProcessMessages;
    end;
  tbarWait.Enabled := True;
end;

Les fonctions In seront activées avec :

 
Sélectionnez
procedure TMainForm.btnEaseInQuadClick(Sender: TObject);
// *** fonctions IN ***
var
  Li, LWidth: Integer;
  LBack: Boolean;
begin
  tbarWait.Enabled := False;
  LWidth := pnlLeft.Width - btnLinear2.Width;
  for LBack := False to True do
    for Li := 1 to fDuration do
    begin
      btnLinear.Left := ComputeInterpolationInt(0, LWidth, Li, intLinear, LBack);
      btnEaseInQuad.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInQuad, LBack);
      btnEaseInCubic.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInCubic, LBack);
      btnEaseInQuart.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInQuart, LBack);
      btnEaseInQuint.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInQuint, LBack);
      btnEaseInSine.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInSine, LBack);
      btnEaseInExpo.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInExpo, LBack);
      btnEaseInCirc.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInCirc, LBack);
      btnEaseInElastic.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInElastic, LBack);
      btnEaseInBack.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInBack, LBack);
      btnEaseInBounce.Left := ComputeInterpolationInt(0, LWidth, Li, intEaseInBounce, LBack);
      sleep(10);
      pnlLeft.Repaint;
      Application.ProcessMessages;
    end;
  tbarWait.Enabled := True;
end;

Nous renvoyons au code fourni avec le tutoriel pour les autres types de fonction : le code est évidemment similaire.

Reste à gérer la durée qui est une propriété nommée Duration qui elle-même fait référence à un champ privé fDuration à travers un setter :

 
Sélectionnez
property Duration: Cardinal read fDuration write SetDuration default C_DefaultDuration;

Les méthodes associées sont simples et en relation avec le composant TTrackbar qui fournit la valeur brute de la durée:

 
Sélectionnez
procedure TMainForm.tbarWaitChange(Sender: TObject);
// *** changement de la vitesse ***
begin
  Duration := tbarWait.Position;
end;

procedure TMainForm.SetDuration(AValue: Cardinal);
// *** détermination de la durée d'interpolation ***
begin
  if fDuration = C_End - AValue then
    Exit;
  fDuration := C_End - AValue;
end;

Il ne reste que la création de la fiche à compléter pour obtenir une application fonctionnelle :

 
Sélectionnez
procedure TMainForm.FormCreate(Sender: TObject);
// *** création de la fiche ***
begin
  Duration := C_DefaultDuration;
  tbarWait.Max := C_End;
  tbarWait.Position := fDuration;
end;

Nous devons en effet renseigner la valeur de la durée par défaut et ajuster correctement les propriétés du composant TTrackbar.

Aucune action n'est nécessaire pour le TSplitter qui saura dimensionner les panneaux suivant le choix de l'utilisateur final.

Voici une vidéo qui montre notre application en action :

III. Conclusion

Vous voici parvenus au bout de votre première exploration des interpolations, et plus spécifiquement des courbes d'easing. L'épisode suivant va formaliser ce travail préparatoire en proposant une série de classes à réutiliser dans vos projets à venir.

Mes remerciements vont à Alcatîz et à BeanzMaster pour leur relecture technique et à xxx pour la correction orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2018 Gilles Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.