GrafX2 - Le moteur des opérations


Explication du principe :

Qu'appelle-t'on une opération? C'est l'emploi d'un outil de dessin. Par exemple, le tracé libre à la main d'un pinceau (ou d'une brosse) est une opération. Plus exactement, c'est tout comportement de la souris lorsqu'elle se trouve sur les pixels de l'image éditée. De ce fait, la gestion de la pipette se fait également au travers des opérations.

Le moteur chargé de gérer ces opérations est simplifié autant que possible. Son principe est le suivant : lorsque la souris est au-dessus de l'image, la boucle principale de gestion de GrafX2 (située dans MOTEUR.C) détermine la partie atomique d'instructions à exécuter, puis l'exécute, pour chaque combinaison d'un type d'opération (un entier, que l'on nommera pour les besoins de cette page ID_OP), d'un état de la souris (état de ses boutons plus exactement, codé sur un entier également), et d'un nombre de valeurs présentes dans une pile (pas la pile système, mais une pile de mots 16-bits spécialement mise en place pour les opérations, afin qu'elles y stockent des paramètres pour les opérations suivantes). On appelle "partie atomique" une séquence d'instructions qui doivent être exécutées intégralement à chaque itération de la boucle principale. Elle se contente de ne faire que ce qu'elle doit faire face à la situation qu'implique la combinaison (ID_OP,Code souris, Etat pile) pour laquelle elle est appelée.

En fait, le moteur doit être aidé pour déterminer les insctructions à exécuter. Chacune de ces parties atomiques est écrite sous la forme d'une fonction contenant tout le code qui lui est nécessaire. Ces fonctions sont ce que l'on a appelé des "fonction_action" (définition dans STRUCT.H), c'est à dire une simple fonction qui ne reçoit pas de paramètres et qui n'en renvoie pas. Un simple void Ma_fonction(void)...

Il n'est pas dans mon intention de vous apprendre à écrire ces fonctions atomiques. Dans le cas des opérations les plus simples à implémenter selon ce principe, la conception des parties atomiques est assez intuitive, mais il arrive dans certains cas que la méthode trouve certaines limites, il faut alors tricher un peu (mais gentiment! - en plaçant des valeurs arbitraires dans la pile uniquement pour permettre la transition vers une autre combinaison choisie, donc une autre partie atomique, par exemple). Vous pourrez trouver tous les exemples que vous voudrez dans le fichier OPERATIO.C.

Après avoir écrit les parties atomiques qui vont être nécessaires pour la gestion complète d'une opération (dans le fichier OPERATIO.C), et avoir renseigné les prototypes de ces fonctions (dans OPERATIO.H), il faut indiquer au moteur dans quelles conditions utiliser ces fonctions. Cela se fait dans la phase d'initialisation du programme (située dans le fichier INIT.C), où l'on renseigne chacune des fonctions atomiques, en décrivant les valeurs de combinaison qui doivent déclencher leur appel. La fonction appelée se contente d'en prendre note en remplissant un tableau nommé "Operation" (variable globale déclarée dans GLOBAL.H, et indexée par les 3 valeurs de la combinaison : ID_OP, code souris, état pile) à l'aide de l'adresse de la fonction à appeler. Ce tableau va servir au moteur à faire un appel direct de la fonction atomique d'après la combinaison, sans avoir à faire toute une série de tests (swich/case) fastidieux.

Comme nous avons pressenti qu'il arriverait fréquemment que des fonctions atomiques auraient besoin systématiquement d'effacer la souris pour faire des affichages à l'écran ou dans l'image, et de la rafficher ensuite, et dans le souci d'éviter de faire abusivement du copier/coller de code, nous avons rajouté un booléen dans le tableau Operation qui permet d'indiquer que la fonction atomique demande au moteur de se charger lui-même de l'effacement et de la restauration du curseur de la souris à l'écran. Finalement, je ne suis pas certain que cela s'est révelé d'une grande utilité, mais je vous le signale tout de même pour que vous compreniez l'existance du paramètre "Effacer souris" qu'il faut indiquer lorsqu'on renseigne une fonction atomique lors de l'initialisation.

Il est important de noter qu'une fonction atomique est appelée en permanence par la boucle principale, indépendamment du fait que la souris change d'état ou non. Ces appels répétés permettent par exemple d'avoir un spray qui continue à agir lorsqu'on presse le bouton de la souris sans la déplacer.

De plus, les fonctions atomiques n'ont pas à se soucier du positionnement de la souris car dès que la pile contient des données (généralement dès le premier clic), la souris est limitée par le moteur afin de ne pas sortir de l'image. Le moteur s'assure également que la souris ne franchis pas la barre de split en mode "loupe". Enfin, n'oubliez pas de vider complètement la pile en temps voulu, lorsque l'opération peut s'achever! ;-)


Informations pour l'implémentation :

Initialisation_operation(ID_OP, Etat souris, Taille pile, Callback, Effacer souris);


Comment rajouter une opération?

Dans CONST.H :
  • rajouter un identifiant (ID_OP) dans l'enum OPERATIONS (exemple : OPERATION_DUMMY)
Dans GLOBAL.H :
  • rajouter l'identifiant de la forme de curseur à utiliser pour cette opération dans la table CURSEUR_D_OPERATION[]. Faites bien attention à l'insérer au bon endroit (place correspondant à ID_OP).
Dans OPERATIO.C :
  • Ecrire chaque fonction atomique de l'opération.

Exemple :

void Dummy_1_0(void)
// Opération : OPERATION_DUMMY
// Click Souris: 1
// Taille_Pile : 0
// Souris effacée: Oui (précisé grâce à Initialiser_operation() dans INIT.C)
{
     Initialiser_debut_operation();
     Backup();

     // ACTIONS...

     Operation_PUSH(une_valeur_nécessaire_pour_les_prochaines_étapes);
     Operation_PUSH(une_autre_valeur_nécessaire_plus_tard);
}

(Pour cet exemple, l'étape suivante à définir sera donc une étape avec la taille de pile à 2)

  • Il y a une action par défaut qui se contente d'afficher les coordonnées de la souris lorsqu'on ne définit pas explicitement de fonction atomique gérant une certaine combinaison (ID_OP, Etat souris, Taille pile).
  • Si l'opération représente une interruption temporaire, et qu'il vous paraît juste de restaurer l'opération précédente une fois qu'elle est terminée (cas de la loupe, de la pipette, des prises de brosse, ...), rajouter l'ID_OP dans la section correspondante de la fonction Demarrer_pile_operation(), en début de fichier.
  • Si l'opération voit un intérêt à accepter que l'utilisateur change de couleur de pinceau en cours d'opération (cas du dessin continu, discontinu, le spray, et les lignes centrées), rajouter l'ID_OP dans la section correspondante de la fonction Demarrer_pile_operation(), en début de fichier.
Dans OPERATIO.H :
  • Ecrire le prototype des ces fonctions atomiques.
Dans INIT.C :
  • Dans Initialisation_des_operations(), pour chaque fonction atomique de l'opération, écrire un appel à Initialiser_operation(ID_OP, Etat souris, Taille pile, Callback, Effacer souris);
    • ID_OP : identifiant de l'opération dont dépend la fonction atomique (défini dans CONST.H)
    • Etat souris :
      • 0 = boutons relachés
      • 1 = bouton gauche enfoncé
      • 2 = bouton droit enfoncé
      • (note : l'état "boutons gauche et droit enfoncés" n'existe pas, seuls les 3 états ci-dessus sont autorisés)
    • Taille pile : nombre de paramètres dans la pile
    • Callback : nom de la fonction atomique à appeler pour la combinaison des 3 paramètres précédents
    • Effacer souris : booléen indiquant que le moteur se chargera d'effacer le curseur souris avant l'appel à la fonction atomique, et de le rafficher après sa sortie.

Exemple :

Initialiser_operation(OPERATION_DUMMY, 1, 0, Dummy_1_0, 1);