Tracer les fonctions « easing » de WPF

homme forme fonction

Les fonctions de easing de WPF, autrement appellées les fonctions d’accélération sont des fonctions qui permettent de ralentir ou d’accélérer le mouvement d’une animation selon une formule mathématique. Dans ce billet nous allons voir comment réaliser une petite application WPF qui liste et dessine les fonctions de Easing disponibles dans le framework WPF.

Commençons par le design de la fenêtre principale qui contiendra :

  • un canvas pour dessiner les fonctions
  • une liste déroulante pour choisir la fonction de easing à représenter
  • une case à cocher pour activer / désactiver les animations, car on souhaite faire bouger des plots sur les courbes du graphiques, afin de mettre en évidence les effets d’accélération et de ralentissement introduits par les formules

cela devra ressembler à quelque chose comme ça :

Easing Function Demo Design Window
Easing function demo : window design

le code XAML de la fenêtre principale (MainWindow.xaml) est le suivant :

<Window x:Class="FranckGaspoz.Wordpress.WPF.CSharp.EasingFunctionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EasingFunctionDemo"
        mc:Ignorable="d"
        Title="EasingFunctionDemo" 
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen"
        Height="402" 
        Width="519"
        SizeToContent="WidthAndHeight"
        >
    <Grid>
        <Grid.Resources>
            <!-- brush de fond du diagramme -->
            <DrawingBrush 
                x:Key="BackBrush"
                Viewport="0,0,10,10" 
                ViewportUnits="Absolute"
                TileMode="Tile">
                <DrawingBrush.Drawing>
                    <DrawingGroup>
                        <GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z" Brush="#EEEEEE" />
                        <GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#EEEEEE" />
                    </DrawingGroup>
                </DrawingBrush.Drawing>
            </DrawingBrush>
        </Grid.Resources>
        <!-- layout principal -->
        <Grid 
            Name="GR_Layout"
            Background="WhiteSmoke">
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="320"/>
                <RowDefinition Height="20"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="470"/>
                <ColumnDefinition Width="20"/>
            </Grid.ColumnDefinitions>
            <!-- barre de contrôles -->
            <StackPanel
                Grid.ColumnSpan="3"
                HorizontalAlignment="Left"
                Orientation="Horizontal"
                VerticalAlignment="Center"
                Margin="0"                
                >
                <Label Content="easing function: "/>
                <!-- liste des fonctions -->
                <ComboBox
                    Name="CB_EaseFunctions"
                    Width="Auto"
                    SelectionChanged="CB_EaseFunctions_SelectionChanged"
                    />
                <!-- activation/désactivation des animations -->
                <CheckBox 
                    Margin="8,0,0,0"
                    VerticalAlignment="Center"
                    IsChecked="{Binding AnimationEnabled}"
                    Name="CKB_AnimationsOn"
                    Click="CKB_AnimationsOn_Click">
                    animation enabled
                </CheckBox>
            </StackPanel>
            <Border
                BorderThickness="1"
                BorderBrush="Gray"
                Grid.Row="1"
                Grid.Column="1">
                <!-- et enfin la zone de dessin -->
                <Canvas                                     
                    Background="{StaticResource BackBrush}"
                    Name="Canvas"
                />
            </Border>
        </Grid>
    </Grid>
</Window>

Le code behind est relativement simple et s’articule autour des 4 propriétés de la classe :

// drawer du graph
EasingFunctionDrawer drawer = new EasingFunctionDrawer();

// liste des fonctions
BindingList<string> EasingFunctions = new BindingList<string>();

// fonction de easing en cours d'affichage
EasingFunctionBase EasingFunction = null;

// indique si l'animation est activée ou non
public bool AnimationEnabled
{
   get { return (bool)GetValue(AnimationEnabledProperty); }
   set { SetValue(AnimationEnabledProperty, value); }
}

// propriété de dépendance pour lier le CheckBox
public static readonly DependencyProperty AnimationEnabledProperty = DependencyProperty.Register("AnimationEnabled",
typeof(bool), typeof(MainWindow), new PropertyMetadata(true));

 

La liste des fonctions est construite par introspection des classes du namespace System.Windows.Media.Animation dans lequel les classes qui nous interressent respectent la convention de nommage {fonctionName}Easing

La méthode InitGraph est appellée lorsque le graphique doit être redessinné (constructeur de la fenêtre, sélection dans la liste ou clic sur checkbox)

Toutes les tâches de dessin sont confiées à la classe EasingFunctionDrawer, dont la méthode qui permet de réprésenter une fonction de easing :

/// <summary>
/// dessine une fonction de easing sur un canvas
/// </summary>
/// <param name="canvas">canvas cible</param>
/// <param name="origin"> coordonnées de l'origine du graph</param>
/// <param name="Length">taille du graphique (carré englobant)</param>
/// <param name="easeColor">couleur de trait de la fonction</param>
/// <param name="easingFunc">fonction de easing</param>
/// <param name="scaley">échelle sur l'axe Y (facteur de réduction)</param>
internal void Draw(
    Canvas canvas,
    Point origin,
    double Length,
    Brush easeColor,
    EasingFunctionBase easingFunc,
    double scaley
    )
{
    double stp = 0.025/10d,d,x,y;
    // l'axe des X est parcouru linéairement
    for (double a = 0;a<=1;a+=stp)
    {
        x = origin.X + a * Length;
        y = origin.Y;
        /**
         * la fonction de easing est appellée pour déterminer la valeur
         * pour la position X, qui est ici fournit comme paramètre de
         * temps normalisé t (pour f(t) ou f est la fonction de easing)
         */
        d = easingFunc.Ease(a);
        // l'ordonnée est déterminée selon la valeur de la fonction de easing
        y = origin.Y - d * Length / scaley;
        // le point est tracé
        DrawPlot(canvas, x, y, easeColor);
    }
}

 

L’animation d’un plot qui suit la courbe est réalisée via la méthode Animate(…) :

/// <summary>
/// anime un point qui parcourt la représentaiton d'une fonction de easing
/// </summary>
/// <param name="canvas">canvas cible</param>
/// <param name="origin">origine du graphique</param>
/// <param name="Length">taille du graphique (carré englobant)</param>
/// <param name="easeColor">couleur de trait de la fonction</param>
/// <param name="easingFunc">fonction de easing</param>
/// <param name="scaley">échelle sur l'axe Y (facteur de réduction)</param>
internal void Animate(
    Canvas canvas,
    Point origin,
    double Length,
    Brush easeColor,
    EasingFunctionBase easingFunc,
    double scaley
    )
{
    var duration = TimeSpan.FromSeconds(3);
    var r = DrawPlot(
        canvas,
        origin.X,
        origin.Y,
        easeColor,
        6d);
    var ta = new TranslateTransform(0,0);
    r.RenderTransform = ta;
    // progression linéaire sur l'axe des X (axe de temps)
    var x_move =
        new DoubleAnimation()
        {
            From = 0,
            To = Length,
            Duration = duration,
            AutoReverse = true,
            RepeatBehavior = RepeatBehavior.Forever
        };
    // progression selon la fonction de ease sur l'axe des Y
    var y_move =
        new DoubleAnimation()
        {
            From = 0,
            To = - Length/scaley,
            Duration = duration,
            EasingFunction = easingFunc,
            AutoReverse = true,
            RepeatBehavior = RepeatBehavior.Forever
        };<br />
    // démarre l'animation
    ta.BeginAnimation(
        TranslateTransform.XProperty,
        x_move
        );<br />
    ta.BeginAnimation(
        TranslateTransform.YProperty,
        y_move
        );
}

 

Voici un exemple du résultat :

Easing Function Demo
Easing Function Demo : Elastic Ease
download sample code
téléchargez le code source de l’exemple (solution Visual Studio 2015) : WPF C# Easing Function demo code source (VS2015 project) MIT-Licensecode source placé sous licence libre MIT

Laisser un commentaire