Les interpolations avec Lazarus (1/3)

Interpolations et fonctions d'easing

Gilles Vasseur

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.

- Titre : Les interpolations avec Lazarus (1/3)

- Auteur : Gilles Vasseur

- Parution : 29 juin 2018

- Public visé :

- Licence : 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 et 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.

Sommaire

- I. Introduction aux interpolations

- I-A. Les interpolations

- I-B. Une approche empirique des interpolations

- I-C. Les courbes d'easing

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

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

- II-B. L'implémentation des fonctions

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

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

- II-B-3. Les fonctions exponentielles

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

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

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

- II-B-7. Les fonctions avec rebonds

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

- III. Conclusion

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] :

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 :

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 :

Animation en format "webm" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://gilles-vasseur.developpez.com/tutoriels/transitions/bgra9/fichiers/testinterpolations.webm

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 :

À 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 :

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 :

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 :

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 :

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 :

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) :

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) :

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 :

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) ?

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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 :

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:

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 :

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 :

Animation en format "webm" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://gilles-vasseur.developpez.com/tutoriels/transitions/bgra9/fichiers/interpolations02.webm

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.