S'amuser avec le système graphique Morph

From OFSET Wiki

Jump to: navigation, search

Traduction de : http://www.squeak.org/tutorials/morphic-tutorial-1.html, par John Maloney. (traduction : Vincent GOUEY; révision et adaptation à 3.8 en français : Olivier VASSEUR)


Ce tutoriel à été conçu pour être utilisé dans un projet Morph. Pour créer un projet Morph, appuyez sur le bouton de la souris quand le curseur se trouve sur la fenêtre de fond de Squeak, à l’écart de tout autre fenêtre. Dans le menu qui s’ouvre alors, choisissez « ouvrir... » puis « projet morph » . Une petite fenêtre Morph orange s’ouvre ; cliquez au milieu de cette fenêtre pour vous retrouver dans le nouveau projet Morph. (Si vous ne savez pas où vous êtes, cliquez pour faire apparaître un menu ; si le menu a pour titre « Monde », vous vous trouvez déjà dans un projet Morph)


Contents

[edit] Préparatifs

Nous devons tout d’abord créer un navigateur de classes et un espace de travail. Petite révision : appuyez sur le bouton de la souris dans l’écran blanc de Squeak. Lorsque le menu apparaît, choisissez « ouvrir... » puis « navigateur de classe ». Vous verrez s’ouvrir un navigateur vert. Ouvrez à nouveau le menu Monde et choisissez « ouvrir... », puis « espace de travail ».

Si vous venez de terminer le tutoriel précédent, vous avez déjà une catégorie appelée « mon travail ». Faites défiler jusqu’en bas la liste la plus à gauche du navigateur pour la trouver.

Dans le cas contraire, nous devons créer un endroit pour y ajouter votre travail. Cliquez avec le bouton droit sur le panneau en haut à gauche du navigateur pour obtenir un menu. [Option-clic sur un Macintosh. Les commandes Mac seront toujours indiquées ainsi entre crochets dans le reste du tutoriel]. Dans ce menu, choisissez « add item... » et tapez « mon travail », avant d’appuyer sur Entrée. Faites défiler la liste jusqu’en bas, et vous verrez que la catégorie est déjà sélectionnée.

Nous allons à nouveau construire un programme à partir de rien. Nous commencerons avec une classe vide, mais cette fois, nous voulons une sous-classe de Morph. Morph est la classe graphique générique du système d’objets Morph. Nous avons sous les yeux le modèle de création d’une nouvelle classe. Modifiez-le pour obtenir ceci :

Morph subclass: #TestMorph
 instanceVariableNames: 
 classVariableNames: 
 poolDictionaries: 
 category: 'mon travail'

Pour cela, sélectionnez d’abord « Object » et tapez le mot « Morph » avec un M majuscule. La nouvelle classe sera une sous-classe de Morph. Sélectionnez « NameOfClass » et tapez « TestMorph ». Effacez les noms de variables d’instance et de classe en supprimant le texte dans les guillemets après «classVariableNames » et « InstanceVariableNames ». Utilisez Alt-s pour valider [Commande-s].

Maintenant nous pouvons nous rendre dans l’espace de travail et créer une nouvelle instance de notre nouvelle classe. Tapez :

TestMorph new openInWorld.

Sélectionnez cette ligne et faites Alt-d [Commande-d] ou choisissez « Exécuter » dans le menu ouvert à l’aide du bouton droit [Option-clic].

[edit] Manipulations interactives d'un morph

Votre nouvel objet graphique, un carré bleu, se trouve dans le coin en haut à gauche. Vous avez obtenu un objet graphique sans avoir à taper le moindre code. Vous pouvez prendre cet objet et le déplacer. Il suffit de le saisir avec la souris. Notez comment l’objet Morph que vous avez saisi projette une ombre en dessous de lui, afin de vous aider à voir que vous l'avez bien soulevé au-dessus du reste. Si plusieurs objets se chevauchent, l’objet saisi passe automatiquement au premier plan, et l’ombre portée montre qu’il se trouve devant tout le reste.

Vous avez sans doute remarqué l’emploi inhabituel des majuscules dans « openInWorld ». Nous préférons du code qui se lit comme des phrases normales, mais en Smalltalk un sélecteur doit être composé d’une seule chaîne de caractères et ne peut pas contenir d’espaces. Nous utilisons donc la convention qui consiste à mettre une majuscule au début de chaque mot anglais, et à coller tous les mots sans inclure d’espaces. D’autres langages séparent les mots avec des tirets ou des soulignements.

Appuyez maintenant sur Alt tout en cliquant sur cette chose bleue [Commande-clic] et vous verrez alors toute une série de petits cercles de couleur autour de l'objet, que nous appellons un « halo ». Chacun d’entre eux est un raccourci pour envoyer une commande au Morph. Si je laisse le curseur de ma souris sur l’un de ces cercles, un petit ballon d’aide apparaît pour me dire à quelle commande correspond ce raccourci. Le ballon du cercle noir indique : « prendre ». Dans ce cas, si je déplace ce cercle noir avec la souris, cela aura le même effet que si j’avais déplacé le carré bleu lui-même. Cette fonction n’est pas si inutile qu’il n’y parait  : en effet, les Morphs ont parfois un comportement interactif, à la façon des boutons. Ce cercle permet de manipuler un objet qui associe un comportement aux clics de souris.

Image:halo.jpg

L’idée ici est que la plupart des systèmes, HyperCard par exemple, ont recours à des modes pour changer la taille d’un objet, etc., c’est-à-dire qu’ils ont des modes d’édition des objets et des modes d’utilisation. Dans le système Morph, vous pouvez obtenir un halo de n’importe quel objet, quel que soit son état. Le halo est un moyen sûr d’interagir avec un objet pendant qu’il est exécuté. Vous n’avez jamais à arrêter ses fonctions ou quoi que ce soit d’autre. Si cet objet était un bouton, vous pourriez l’éditer sans pourtant devoir l’activer ou le désactiver.

Voyons maintenant ce que peut faire le halo. Par exemple, le cercle vert permet de créer une copie de l’objet et le X dans le cercle rose de l’effacer (l’envoyer à la Corbeille). Une autre opération courante consiste à modifier la taille, ce que fait le cercle jaune. Évitez pour l’instant de trop manipuler le halo ; en particulier, n’utilisez pas le cercle bleu (tout en bas à gauche de l’objet).

[edit] Programmation d'un morph

[edit] Gestion d'évènements

Commençons maintenant à faire de ce carré un objet bien à nous. Écrivons quelques méthodes pour le personnaliser.

Tout d’abord, je veux que notre objet fasse quelque chose quand on clique dessus avec la souris. Pour cela, nous avons deux méthodes à implémenter. Cliquez sur le navigateur puis sur «  no messages » dans le troisième panneau. Copiez ceci à l’intérieur et validez en faisant « Alt-s » [Commande-s].

handlesMouseDown: evt
  ^ true.

Si c’est la première fois que vous validez une méthode dans cette image, vous devez maintenant taper vos initiales, qui seront enregistrées en même temps que les méthodes que vous modifiez. Cette méthode, « handlesMouseDown: », ordonne au Morph d’être un objet qui réagit aux clics de la souris.

L'autre méthode que nous devons ajouter représente ce qui doit être fait quand on clique sur l’objet avec la souris. Copiez ceci et collez-le à la place de la méthode précédente et validez en faisant « Alt-s » [Commande-s].

mouseDown: evt
  self position: self position + (10 @ 0).

Cette méthode déplace l’objet de 10 pixels vers la droite quand on cliquer dessus. Essayez de cliquer sur l’objet pour voir ce qui se passe.

Pour l’instant, nous avons deux méthodes. L’une d’elles renvoie true pour indiquer que nous voulons exécuter l’événement mouseDown: si l’on clique avec notre souris. L’autre correspond à l’évènement en question en modifiant la position de l’objet.

[edit] Dessiner le morph

Maintenant, je veux que le comportement de ce Morph consiste à se redessiner lui-même. Copiez cette méthode dans le navigateur et validez avec «  Alt-s » [Commande-s].

drawOn: aCanvas
  | colors |
  colors := Color wheel: 10.
  colors withIndexDo: [:c :i |
    aCanvas fillOval: (self bounds insetBy: self width // 25 * i + 1)
        color: c].

La troisième ligne de cette méthode (la première ligne de code) est une assignation vers la variable temporaire « colors ». Si vous voyez deux barres verticales et une liste de variables, ce sont des variables locales de cette méthode, souvent appelées variables temporaires. En Smalltalk et en Squeak, vous n’avez pas à les déclarer - le système vous demandera s’il s’agit effectivement de variables locales ou non. Et comme nous n’avons pas de « types » de variables, nous n’avons pas pas de déclarations de type. Toute variable peut contenir tout objet, quel qu’il soit. Nous avons donc assigné à « colors » le résultat de l’expression : « Color wheel: 10 » . Pour montrer ce que cela fait, sélectionnez« wheel: » et faites «  Alt-m » [Commande-m] pour savoir quelles méthodes iMMMMplémentent « wheel : » . Vous verrez qu’il y a deux implémentations – sélectionnez celle située dans la classe Color (« Color class wheel : »).

En Squeak, nous insérons souvent un petit commentaire en haut de la méthode. La première ligne décrit ce que fait « wheel: » et peut se traduire en français par « renvoie une collection de ThisMany couleurs régulièrement espacées dans la roue des couleurs ». thisMany (en français, « CeNombre ») est un argument. Les couleurs sont régulièrement espacées sur la roue des couleurs. C’est en fait un spectre des couleurs.

Lorsqu’il est facile de donner un exemple de l’effet de la méthode, nous le faisons également. Ici, le second commentaire est une expression qui peut être exécutée. Sélectionnez l’intérieur des guillemets et faites «  Alt-d » [Commande-d]. Sur le bord en haut de l’écran apparaît une bande de couleurs. Il s’agit d’une collection de couleurs régulièrement réparties dans le spectre (avec une faible luminosité et une faible saturation afin qu’elles ne soient pas trop voyantes).

Dans le monde de Squeak, nous pouvons gribouiller directement sur l’écran, mais il n’est pas ensuite nettoyé automatiquement. Je vais donc utiliser l’entrée « restaurer l’affichage » du menu afin de me débarrasser de ce qui est apparu en haut de l’écran.

Fin de cette digression, qui avait pour but de faire remarquer ce que « wheel: » voulait dire. Nous savons maintenant qu’il s’agit d’une collection de la classe Colors. Fermez la fenêtre intitulée « Implementors of wheel: » en cliquant sur le « X » en haut à gauche de la barre de titre.

La suite de la méthode parcourt cet ensemble de couleurs.

Ceci est un extrait de la méthode drawOn:
    colors withIndexDo: [:c :i |
        aCanvas fillOval: (selfbounds insetBy: self width // 25 * i + 1)
            color: c].

Le message « withIndexDo: » inclut à la fois une couleur « c », et un indice «  i » qui garde la trace de là ou nous en sommes dans la liste. Il vaudra 1 la première fois, 2 la deuxième fois et ainsi de suite jusqu’à 10. Nous avons besoin de savoir où nous en sommes dans la liste afin d’augmenter la distance entre les ovales extérieur et intérieur pour obtenir des ovales de plus en plus petits. Nous envoyons le message «  fillOval:color: » avec l’argument entre parenthèses – qui est ici un Rectangle inséré à une distance de (((self width)// 25) * i + 1) pixels à l’intérieur de la limite externe du Rectangle retournée par (self bounds) – et un second argument qui est la couleur. Au fur et à mesure que la valeur de l’indice « i » augmente, la distance entre les rectangles augmente également, retournant des rectangles intérieurs de plus en plus petits, dans lesquels nous dessinons un ovale que nous remplissons avec la couleur c.

Quand on regarde notre objet, on peut compter jusqu’à dix bandes de couleurs. Pas mal, n'est-ce pas ? De plus, comme nous envoyons en paramètre le résultat du message «  width » (en Français, « largeur ») à l'objet lui-même (« self »), celui-ci connaît sa taille. Faites apparaître le halo en faisant « alt-clic » [Commande-clic] sur l'objet, et tirez sur le cercle jaune. Lorsque vous changez sa taille, il se redessine, peu importe la taille que vous lui donnez.

[edit] Animer un morph

La prochaine chose que je veux faire est une sorte d’animation qui aura lieu quand je cliquerai dessus. Tout d’abord, il faut créer une liste de points. Quand je clique sur l’objet, je veux qu’il bouge d’un point à un autre jusqu’à ce qu’il n’ait plus de points où aller. Pour çela, j’ai besoin d’une variable contenant la liste des points.

Morph subclass: #TestMorph
    instanceVariableNames: 'path'
    classVariableNames: 
    poolDictionaries: 
    category: 'Mon travail'

Retournez dans le navigateur, et cliquez sur le bouton « instance », ce qui fera apparaître la définition de la classe TestMorph. Cliquez alors entre les guillemets simples qui suivent «  instanceVariableNames » (c’est l’emplacement réservé aux noms des variables d’instance) et tapez «  path » (comme dans le code ci-dessus). Ce sera le nom d’une nouvelle variable d’instance contenant la liste des points où afficher notre objet. Je vais maintenant cliquer sur le nom de la catégorie de messages appelé « as yet unclassified ».

Comme j’ai ajouté une variable d’instance, j’aimerais m’assurer qu’elle est initialisée à une certaine valeur. Écrivons le message suivant pour TestMorph :

initialize 
    super initialize.
    path := OrderedCollection new.

Pourquoi avons-nous besoin de la ligne «  super initialize » ? Bien que la définition de la classe TestMorph paraisse très simple, « path » n’est pas la seule variable d’instance qu’elle possède. Dans le second panneau du navigateur, cliquez avec le bouton droit [Commande-clic] pour obtenir un menu. Choisissez «  inst var refs » , vous verrez une liste des autres variables qu’elle a héritées de ses superclasses. Je ne vais en sélectionner aucune. Si je le faisais, je verrais apparaître toutes les méthodes utilisées par cette variable d’instance. Vous pouvez voir néanmoins qu’il y a six variables d’instance héritées de la classe Morph.

«  super initialize » envoie «  initialize » à ma superclasse, la classe Morph. Quand vous envoyez le message «  initialize » au receveur « super », vous l’envoyez en fait à « self », mais en vous assurant que c’est la version héritée de « initialize » qui est appelée et non celle que nous sommes en train de définir.

La principale instruction de notre version de « initialize » est « path := OrderedCollection new ». Ceci met une Collection vide dans notre variable « path ». Quand mon TestMorph reçoit un « initialize », il exécute d’abord toutes les initialisations que ferait un objet de la classe Morph, avant d’exécute sa propre initialisation. Le résultat est que toutes les variables d’instance héritées de la classe Morph seront initialisées, ainsi que la nouvelle variable que nous avons ajoutée.

Nous utilisons ici une convention, car le message « initialize » n’est normalement pas envoyé automatiquement à un nouvel objet. Toutefois, la classe Morph envoie systématiquement le message «initialize » à chaque nouvelle instance de Morph.

Le TestMorph que vous voyez à l’écran n’a pas été initialisé avec notre nouveau code. Il l’a été avant que nous définissions notre propre version de la méthode «initialize ». Effaçons-le et créons-en un nouveau. Faites apparaître le halo de l’objet en faisant alt-clic [Commande-clic] sur lui. Effacez votre objet en cliquant sur le cercle rose avec un « X ». Il est envoyé à la corbeille. À présent, créez un nouveau TestMorph (correctement initialisé cette fois) en exécutant :

TestMorph new openInWorld.

Nous allons faire faire à notre objet quelque chose de différent quand nous cliquerons dessus. Pour l'instant, « path » contient une Collection vide. Copiez cette méthode et acceptez-la.

startAnimation
    path := OrderedCollection new.
    0 to: 9 do: [:i | path add: self position + (0@(10 * i))].
    path := path, path reversed.
    self startStepping.

Dans la première ligne, nous créons une nouvelle OrderedCollection. Dans la ligne suivante, nous faisons quelque chose dix fois et ajoutons le résultat d’une expression dans la variable «  path ». Nous sauvegardons des points qui sont liés à la position courante, « self position », en lui ajoutant la position d'un point que nous construisons. L’abscisse du point construit vaut zéro, et après le signe « @ » (qui construit un point), l’ordonné du point construit est 10 fois « i », où «  i » est l’index de la boucle, allant de zéro à neuf (avec un pas de un). Le résultat final est la création de dix points dont l’ordonnée se déplace de la position initiale de (dix fois zéro) pixels jusqu’à (dix fois neuf) pixels. Chacun de ces nouveaux points est ajouté dans la variable « path ».

Dans la ligne qui suit nous complétons le contenu de la variable « path », qui devient (path, path reverse). « reverse » prend n’importe quelle collection et la met dans l’ordre inverse. L’opérateur virgule (« , ») n'est rien d'autre qu'un sélecteur de message qui concatène deux collections.

Nous avons pris notre liste de dix points dont l’ordonnée va de 0 à 90 et lui avons ajouté une liste de points qui vont, eux, de 90 à zéro. Nous avons donc maintenant vingt points qui débutent et se terminent à la position initiale en passant par une série de points intermédiaires. La dernière ligne de cette méthode revient à peu près à inscrire notre Morph dans un moteur de façon à ce qu’il reçoive en permanence un message nommé «  step ». Il s’agit donc de l’élément de base du moteur d’animation, le battement de cœur de l’animation. Maintenant, nous devons faire comprendre à notre Morph la méthode «  step ».

Step
    path size > 0 ifTrue: [self position: path removeFirst].

Cette méthode n’est pas difficile à comprendre. Elle dit : tant qu’il y a quelque chose dans la liste de points - c’est-à-dire aussi longtemps que la taille de notre variable « path » est strictement plus grande que zéro -déplacez-moi. Envoyez-moi le message « position : » avec pour argument le premier point de la collection et supprimez ce point de celle-ci. La méthode vérifie chaque fois qu’elle est appelée s’il y a bien un point où se rendre et à enlever.

Graphiquement, l’objet sautera instantanément au prochain point et vous verrez le changement dans la prochaine image de l’animation.

Tout ceci est maintenant prêt à être exécuter mais il ne se passe encore rien. En fait, c’est parce que nous n’utilisons la méthode « startAnimation » nulle part encore. Je pourrais trouver une technique pour envoyer ce message à l’objet, mais il est plus facile de faire en sorte que l’objet s’envoie lui-même le message quand on clique sur lui. mouseDown: evt self startAnimation.

mouseDown: evt
    self startAnimation.

Il y a deux façons de définir la méthode. Vous pouvez la coller par-dessus n’importe quelle méthode ou vous pouvez cliquer sur « mouseDown: » dans le panneau droit du navigateur et modifiez la méthode « mouseDown: » que nous avons définie précédemment. Lorsque vous acceptez la méthode, le résultat est le même quelle que soit la façon dont vous l’avez entrée.

Cliquez ensuite sur votre Morph, et vous devriez voir quelque chose se passer. Parfait, il s’anime, mais il va très, très lentement. Déçu, vous vous dites sans doute : Bof, pas terrible, je pensais que Squeak était quand même plus rapide que ça... Ce qui se passe, c’est que, par défaut, ce message " step " n’est envoyé à notre objet qu’une fois par seconde. Mais l’objet peut décider de la fréquence à laquelle le message « step » lui sera envoyé en implémentant une méthode « stepTime » :

stepTime
    ^ 50

Cette méthode retourne le temps en millisecondes entre chaque message «  step » que l’objet reçoit. Vous pouvez évidemment demander un délai de zéro seconde entre chaque message, mais c’est impossible à obtenir. La méthode revient en fait à indiquer la fréquence à laquelle vous souhaitez envoyer le message à l’animation.

Si nous cliquons maintenant sur notre objet, nous obtenons une animation beaucoup plus rapide. Je vais modifier « stepTime » et fixer le délai à dix millisecondes pour avoir cent images secondes. Voyons voir si ça fonctionne. Et bien, je ne sais pas si j’obtiens en effet une centaine d’images par secondes mais en tout cas, c’est très rapide. Comme vous le savez, je travaille pour « the animation company ». Cette animation débute et s’arrête instantanément alors que tout le monde sait qu’il faut une accélération et une décélération pour une animation de qualité. Voici une modification de la méthode « StartAnimation » qui utilise le carré de «  i » pour faire commencer l’animation doucement.

startAnimation
     path := OrderedCollection new.
     0 to: 29 do: [:i | path add: self position + (0@(i squared / 5.0))].
     path := path, path reversed.
    self startStepping.

J’ai modifié une seule ligne de cette méthode. Nous allons sauvegarder trente points dans la variable « path », que nous complèterons ensuite avec la méthode « reverse », pour obtenir finalement soixante points. L’autre modification consiste à remplacer « 10 fois i » par « i au carré divisé par cinq ». La valeur finale l’expression sera de 841 divisé par 5.

Vous obtenez une animation qui démarre tout doucement et qui accélère puis ralentit à nouveau en remontant vers la position initiale de l’objet. On dirait vraiment une balle qui rebondit.

C’est maintenant le bon moment pour commencer à s’amuser avec des Morph préexistants. Allons-y, alors. Si les tiroirs sur les bords de l’écran sont activés, cliquez sur « Accessoire » tout en bas de l’écran. Si ce n’est pas le cas, utilisez le menu qui apparaît quand vous cliquez avec le bouton droit de votre souris et choisissez « tiroirs… » puis cochez « montrer les tiroirs partagés ». Dans les deux cas, le tiroir accessoire contient une série d’objets que vous pouvez tirer et déposer sur l’écran. Une fois déposé un objet sur l’écran, faites un alt-clic [Commande-clic] sur celui-ci pour obtenir l’halo, et cliquez sur le cercle rouge pour voir apparaître le menu de l’objet. Voici une liste des Morphs avec lesquels vous pouvez vous amuser :

  • Le rectangle (RectangleMorph; à partir du menu de l’objet, essayez de cliquer sur « raised bevel » et «  inset bevel »)
  • L'ellipse ( EllipseMorph)
  • L'étoile (( StarMorph)
  • Le Morph poly (courbes) (CurveMorph; à partir du menu de l’objet, essayez de cliquer sur « montrer les poignées »)
  • Texte (n'oubliez pas d'utiliser la poignée jaune pour l'élargir)
  • Dans le tiroir « Multimédia » (à côté du tiroir « Accessoires »,vous trouverez la palette, qui vous permet de commencer un nouveau dessin dès qu'elle est déposée hors du tiroir. Quand vous avez terminé, cliquez sur « fini », et votre dessin devient un Morph.
Personal tools