Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Wheelmouse : faire réagir Impressario, textes et champs à la molette de la souris

Compatible Director MX2004. Cliquer pour en savoir plus sur les compatibilités.Par sebastien.portebois (Sébastien Portebois), le 06 décembre 2004

Vous pouvez également trouver ce tutoriel ici.
N'hésitez pas à m'envoyer directement vos réactions et commentaires pour me permettre d'améliorer ce court article ; vous pouvez aussi (et c'est recommandé) poster vos questions et vos remarques sur le sujet dédié du forum Director (j'y répondrai ainsi que les autres modérateurs, le débat en sera d'autant plus intéressant)

A. éléments requis

Pour cet article, en plus de Director, vous aurez besoin de

  • l'xtra wheelMouse de Gary Smith, disponible gratuitement pour PC.
  • l'xtra Impressario, d'Intégration Nouveaux Médias, pour les parties de scrolling PDF abordées (vous pouvez vous en passer pour faire scroller uniquement les acteurs texte et champs). Ce formidable Xtra est payant, mais une version d'essai de 30 jours est gratuitement disponible sur le site.

B. fonctionnement de l'xtra

L'xtra wheelMouse est un Xtra de script, c'est à dire qu'on n'a pas à le déposer sur la scène, et qu'il ne crée pas de nouvel acteur… il se contente (et c'est suffisant) d'ajouter de nouvelles méthodes Lingo. (à l'instar d'xtras plus connus comme fileIO, fileXtra, buddyAPI, propSave …)

Cet xtra doit possède deux méthodes publiques, et génère un seul événement… difficile de faire plus simple ;^)

Méthodes de l'xtra WheelMouse

  • startWheelMouse()
  • closeWheelMouse()

Evenement généré par l'xtra WheelMouse

  • WheelMouseEvent(nValeurDeScroll)

L'entier reçu en argument nous indique alors la 'quantité' de scroll effectué, et son signe la direction du défilement.
Les valeurs typiques sont donc de 1 et -1.
Pour commencer à recevoir cet événement, il suffit d'appeller la méthode startWheelMouse(), et lorsqu'on ne veut plus les recevoir (à la fin d'une animation par exemple), il suffit d'appeller closeWheelMouse().

Cet xtra a donc l'avantage d'une simplicité extrème, mais cette simplicité est son principal handicap : il serait très agréable d'avoir une méthode similaire au addListener d'Impressario2 qui permettrai d'indiquer des images-objets 'clientes' qui ne recevraient les événements que si la molette les survolle.

note : lors de vos tests, pensez que la molette de la souris a déjà une action sous Director (défilement de la scène), aussi il est plus pratique et efficace de tester en projecteur. Dans ce cas vous vous rendrez compte à quel point les nouvelles options de publication de DirectorMX2004 sont pratiques (Ctrl+Shift+S pour publier le projecteur : enregistrement automatique, création du projecteur puis son lancement… beaucoup plus rapide qu'à faire manuellement avec les anciennes versions!)

C. organisons nos scripts

Si nous avons simultanément plusieurs images-objets sensibles à la molette de la souris, par exemple un acteur texte, un acteur champs, et un acteur Impressario, comment gèrer l'évenement WheelMouseEvent pour que seulle l'image-objet pertinente réagisse ?

Plusieurs solutions sont envisageables :

  1. routage simple dans un script d'animation (gestion centralisée)
  2. diffusion générale de l'événement (chacun gère sa réaction)
  3. utilisation d'un manager pour router l'évenement et ne l'envoyer qu'aux sprites concernés.

Le routage simple consiste en un seul script d'animation, qui a donc un gestionnaire WheelMouseEvent, et qui va modifier directement les scroll des acteurs.
Cette solution est la plus simple à mettre en oeuvre, mais présente de nombreux défauts : si le projet contient de nombreuses sections différentes, des interactions avec la molette variées, ce script va rapidement devenir très volumineux et complexe, regroupant tout, et sera donc une source de bogues potentiels. Si par exemple un sprite pourra réagir diférement à la molette suivant son état (scroll, bloqué, zoom, changement d'opacité, …), toutes ces interactions spécifique à un sprite devraient être gérées depuis le script d'animation commun!

Une première tentative pour corriger ce problème et avoir une approche plus orienté objet serait de diffuser l'événement de scroll à tous les sprites, pour que chacun gère son action vis à vis de la molette.

Le script d'animation (inévitable car l'événement WheelMouseEvent ne peut être reçu que par un script d'animation, c'est une contrainte fixée par l'xtra) ressemblerait donc à :

on WheelMouseEvent(nValeurDeScroll)
	sendAllSprites(#WheelMouseEvent, nValeurDeScroll)
end

Ensuite chaque sprite voulant réagir à la molette n'a plus qu'à avoir un gestionnaire ressemblant à :

on WheelMouseEvent(me, nValeurDeScroll)
	-- ici faire le scroll ou autre suivant mon état
end

Ce script a l'avantage d'être simple à implémenter et de s'approcher d'une logique orientée-objet où chaque composant est responsable de ses réactions face à l'événement.
Un des premiers tests à ajouter ici serait de vérifier le survol, et d'ignorer l'événement si la souris ne survole pas le sprite, ce qui donnerait:

on WheelMouseEvent(me, nValeurDeScroll)
	rMe = sprite(me.spritenum).rect
	if not (the mouseloc).inside(rMe) then exit
 
	-- ici faire le scroll ou autre suivant mon état
end

Voici donc une première solution raisonnablement satisfaisante, et convenant à la plupart des situations… mais qui présente tout de même ses limites : nous n'avons ici aucune communication entre les éléments. Aussi nous pourrions souhaiter par exemple que si des sprites se chevauchent, seul celui 'au dessus' réagisse à la molette (ce qui n'est pas le cas dans l'approche présentée ci dessus).

On peut aussi souhaiter utiliser des touches de commutation, par exemple que Ctrl+molette controle le volume sonore, et n'agisse donc sur aucun scroll.

La meilleure solution pour gérer ceci sans retomber dans le travers du script d'animation qui gérerait les actions à effectuer est l'utilisation d'un manageur qui va recevoir les événements transmis par le script d'animation, et les router le cas échéant vers les sprites qui se seront enregistrés auprès de lui comme clients de cet événement. Ce manager pourra également dispatcher l'info le cas échéant à un gestionnaire de volume, d'historique (si on utilise la molette pour naviguer dans un historique) ou tout autre action…. on a donc une modélisation qui ressemble à :

- script d'animation qui intercepte l'événement WheelMouseEvent de l'xtra - script de 'dispatch', auprès duquel des sprites peuvent s'enregistrer pour recevoir les événements. C'est à ce script que le script d'animation transmet l'événement reçu de l'xtra.
Le dispatcheur fait les tests requis (survol, surveillance des touches de modifications, …), puis envoit des notifications au(x) seul(s) sprite(s) concerné(s).

- scripts clients, qui s'enregistrent auprès du dispatcheur, pour faire défiler leur contenu (de manière générique pour recevoir des notifications, pour ensuite faire défiler leur contenur, ou toute autre action à effectuer sur modification de la molette de la souris). On peut gérer également ici les touches de modificateur (shift/ctrl/alt). On voit ici tout l'intérêt du dispatcheur qui permet un niveau de filtrage supplémentaire, avec des touches de commutation générales (Alt+molette : contrôle du volume globale) et de commuation locale, au niveau des clients (Ctrl+molette : contrôle du zoom de l'élément survolé, indépendamment des autres objets de la scène)


figure 1 : diagramme d'organisation des scripts



Nous allons donc devoir coder 3 scripts:

  1. le script d'animation, qui sera trivial, un simple passage obligé par l'xtra,
  2. le dispatcheur, qui va implémenter un mode de fonctionnement intéressant : le singleton. C'est à dire que ce script s'arrangera toujours pour n'avoir qu'une seule instance à la fois, et cherchera tout seul cette instance pour ne pas en créer de deuxième. L'intérêt de gérer le singleton depuis le script lui même, c'est que son fonctionnement reste ainsi 'caché' pour les scripts qui dialoguent avec lui : peu leur importe de savoir comment il s'arrange, comment trouver l'instance…
  3. le script client, décliné en plusieurs variantes pour gérer les acteurs texte, champs et Impressario (et plus… mais nous en resterons ici pour aujourd'hui ;^)

D. Implémentation des scripts - partie 1 - script d'animation

Passons rapidement sur la partie la plus triviale. Ce script se contente de transférer l'événement envoyé par l'Xtra au script parent de gestion de la molette de la souris :

on WheelMouseEvent (nVal)
	-- on transfère l'évenement au dispatcher de scroll,
	oWheelMouseDispatcher = script("s_mouseWheelManager").new()
	call(#mWheelMouseEvent, [oWheelMouseDispatcher], nVal)
end

Que fait-on ici?

a.) on récupère l'instance du dispatcheur qui va gérer l'événement en appellant la méthode new() de son script (nous verrons ce mécanisme plus en détail dans quelques lignes),

b.) nous appellons ca méthode mWheelMouseEvent en lui passant en argument nVal. La syntaxe call(#mWheelMouseEvent, [oWheelMouseDispatcher], nVal) est équivalente à oWheelMouseDispatcher.mWheelMouseEvent(nVal), syntaxe à laquelle vous êtes peut être plus habitué.
La subtilité ici réside dans le traitement d'erreur : si oWheelMouseDispatcher n'est pas une instance de script, ou si cette instance n'a pas de gestionnaire défini pour l'événement mWheelMouseEvent, la notation que j'emploie en call() ne générera aucune erreur de script, alors que l'appel en notation pointée directe provoquera une erreur de script. Ceci permet d'éviter le cas où le script parent aurait été mal nommé, endommagé, supprimer par inadvertance, ou que sais-je… et d'assurer une lecture sans erreur de script, même en cas de problème.

Cette subtilité est équivalente à la différence entre sprite(1).uneMethode() et sendSprite(1, #uneMethode).
L'accès direct au gestionnaire dans la première notation pourra provoquer une erreur de script si le gestionnaire uneMethode n'est pas trouvé, alors que le sendSprite ne provoquera pas d'erreur si aucun comportement avec un gestionnaire on uneMethode (me) n'est trouvé sur le sprite 1.

Pour que le script d'animation recoive l'événement, il faut que l'Xtra ait été initialisé. C'est ce que fera le dispatcheur dès sa création.

E. Implémentation des scripts - partie 2 - création d'un singleton

Nous souhaitons donc n'avoir qu'une seule instance de notre script de dispatcheur, qui contiendra une liste stockant tous les sprites qui se seront enregistrés auprès de lui pour recevoir des notifications de mise à jour sur les modifications de la molette de la souris, et qui recevra ces notifications depuis le script d'animation.

Comment coder ce script?
Eliminons tout de suite l'idée d'en faire un comportement, car nous serions alors obliger de le déposer sur un sprite plutôt qu'un autre, avec une certaine portée dans le scénario, ce qui ne correspond pas à l'utilisation qu'on veut en faire.
Un script d'animation? Dans ce cas ce script ne pourrait pas avoir de variable d'instance (propriété), et nous devrions utiliser des globales…
très brouillon. Nous allons donc en faire un script parent.

Pour que l'instance 'reste en vie', il faut qu'au moins une référence vers elle existe…. dans ce cas nous avons (entre autres) 2 possibilités simples: ajouter une référence de ce script dans the actorList, ou dans une globale.
Pour des raisons de communication, je vais choisir the actorList, ce qui permettrait par exemple de simplifier le script d'animation pour qu'il se résume à:

on WheelMouseEvent (nVal)
	-- on transfère l'évenement au dispatcher de scroll
	call(#mWheelMouseEvent, the actorlist, nVal)
end

L'utilisation d'une globale pourrait se faire proprement à condition que seul le dispatcheur accède à cette globale… une globale 'privée' en quelque sorte. Le risque de ne pas respecter cette règle étant trop grand, et la facilité de faire des call sur the actorList me font choisir de ne pas utiliser de globale.

note : nous pourrions aussi utiliser une propriété de script non instancié via la notation me[#laPropriete]. La vitesse d'accès lente à cette valeur, et les risques d'erreur de nettoyage me font l'ignorer pour cet exemple.

Le dispatcheur doit toujours retourner la même instance, c'est à dire :

  • si c'est la première fois qu'on lui demande, il doit intialiser son instance, et la stocker dans the actorList,
  • sinon rechercher l'instance stockée dans the actorList et la retourner à l'appellant.

Nous aurons donc besoin d'une méthode pour récupérer l'instance stockée dans the actorList, par exemple :

on mWheelMouse_GetManager (me) ---------------------------------------
	--                                                      PUBLIC
	-- OUTPUT #object (me)
	-------
 
	return me
end -- mWheelMouse_GetManager handler

Et voilà. Un simple return me. Ainsi en faisant un simple oPrevious = call(#mWheelMouse_GetManager, the actorList) et en regardant si oPrevious contient une instance (le me retourné) ou rien, on aura l'instance existante ou on saura qu'on doit l'initialiser.
Nous pouvons commencer à coder plus avant notre script parent. Nous aurons donc un gestionnaire on new (me) très simple, qui - par convention - ne fera qu'appeller sa méthode d'initialisation privée:

on new (me)
	return me.miInit()
end

Cette méthode miInit commencera donc par rechercher une instance déjà existante, et l'utiliser si elle existe, sinon elle s'initialisera.
L'initialisation du script consistera à :

  • stocker une référence dans the actorList pour qu'on utilise cette instance par la suite, - créer une liste des clients éligibles, c'est à dire la liste dans laquelle on stockera les sprites qui s'enregistrent pour recevoir les notifications,
  • initialiser l'xtra en appellant sa méthode startWheelMouse() pour qu'il génère des événements WheelMouseEvent,
  • préparer son nettoyage, c'est à dire s'assurer de recevoir un événement stopMovie pour pouvoir fermer proprement l'xtra. Nous reviendrons sur ce point par la suite.

Le code de la méthode miInit devient donc:

on miInit (me) -------------------------------------------------------
	--                                                     PRIVATE
	-- > new  -------
 
	-- on regarde si une instance de ce script a déjà été créée
	oPrevious = call(#mWheelMouse_GetManager, the actorlist)
 
	-- si oui on utilisera cette instance 
	if objectp(oPrevious) then
		return oPrevious
	end if
 
 
	-- je suis la première instnce créée de ce script, je m'initialise
	(the actorlist).add(me)
	plspClients = list()
 
	-- on demande à WheelMouseXtra d'envoyer des événements
	if (the platform contains "indows") then
		-- l'xtra ne fonctionne que sous PC, on s'assure de ne pas générer
		-- d'erreur de script sous Mac
		startWheelMouse()
	end if
 
 
        ptoStopMovieCatcher = timeout().new("stopMovie"&&me.string, the maxInteger, #dummy, me)
	-- pre MX 2004 syntax
	-- ptoStopMovieCatcher = timeout("stopMovie"&&me.string).new(the maxInteger, #dummy, me)
 
	return me
 
end -- miInit handler

(note : si vous n'êtes pas sous Director MX 2004, vous devez mettre la ligne commençant par ptoStopMovieCatcher, et enlever les commentaires de la suivante. Ceci est dû aux modifications de syntaxe Lingo apparues dans MX2004 pour les objets timeouts)

Que faut-il remarquer?
Tout d'abord, je n'initialise l'xtra que sous Windows. C'est une contrainte fixée par l'xtra qui n'existe que pour Windows. L'avantage de faire les tests ici est que l'utilisation du script est transparente pour les sprites clients : si on est sous mac ils recevront bien l'instance du script de dispatcheur, pourront s'y enregistrer, et tout se passera comme prévu… bien entendu aucun événement de molette ne sera jamais reçu, mais ainsi aucune autre modification du code n'est nécessaire pour que le code reste le même sur les deux plateformes, sans générer d'erreur de script.

(note : je teste sur “indows' car je suis paranoïaque des variations de sensibilité à la casse, et ma mauvaise mémoire m'empêche de retenir si on a “Windows” ou “windows”… libre à vous d'adapter ce test pour avoir quelque chose de plus habituel ;^)

Enfin on voit que je crée un timeout, avec une période gigantesque (the maxInteger), et qui appelle un gestionnaire inexistant dans mon instance (#dummy, me). Cette astuce permet de recevoir les événements d'animation : les objets timeout (plus précisement les instances stockées dans the timeoutList) reçoivent les événements de Director : événements de frame, mais aussi - et c'est ce qui nous interesse ici - le stopMovie.
C'est un moyen simple d'être certain de recevoir un événement stopMovie dans une instance de script parent.

On peut donc ajouter un gestionnaire on stopMovie (me) dans notre script parent, qui appellera notre méthode de nettoyage:

on stopmovie me
	-- > reçu via le timeout
	me.miKill()
end
 
on miKill (me)
	-------------------------------------------------------
	--                                              PRIVATE
	--> endSprite
	-------
 
	(the actorlist).deleteone(me)
	plspClients = void
 
	me.miKillAncestor()
 
	-- on demande à WheelMouseXtra d'envoyer des événements
	if (the platform contains "indows") then 
		closeWheelMouse()
	end if 
 
	if not voidp(ptoStopMovieCatcher) then
		-- nettoyage du timeout
		ptoStopMovieCatcher.target = void
		ptoStopMovieCatcher.forget()
		ptoStopMovieCatcher = void
	end if
 
end -- miKill handler

La méthode miKillAncestor fait partie de mes squelettes de script, et peut être ignorée pour l'instant. On notera le même test pour l'appel de fermeture de l'xtra. Avec ces précautions nos scripts sont utilisables sans risques d'erreurs impromptues aussi bien sur Mac que sur PC (même s'ils ne seront vraiment utiles que sur PC). Enfin on voit qu'on nettoie bien le timeout et qu'on se supprime de the actorList, nettoyant ainsi toutes les références vers notre instance pour pouvoir être libéré de la mémoire.

Où en sommes nous?
Nous avons donc un script de dispatcheur qui nous retournera toujours la même instance de son 'me' quand on appellera sa méthode new() - c'est le principe du singleton - , qui se nettoie bien tout seul, et qui a une liste (plspClients) pour contenir les clients.

F. Implémentation des scripts - partie 3 - finition du dispatcheur

Il nous reste donc à :

  • ajouter les clients dans la liste plspClients,
  • recevoir l'événement #mWheelMouseEvent transmis par le script d'animation , et le transférer aux clients concernés.

Commençons par l'ajout des clients, qui sera trivial:

on mWheelMouse_AddListener (me, spTarget) ----------------------------
	--                                                      PUBLIC
	-- INPUTS
	--  spTarget <#sprite>
	-------
 
	-- on vérifie que tout est ok
	if not ilk(spTarget, #sprite) then exit
	if not listp(plspClients) then exit
 
	-- on l'ajoute si on ne l'a pas déjà
	if not plspClients.getpos(spTarget) then plspClients.add(spTarget)
 
end -- mWheelMouse_AddListener handler
on mWheelMouse_RemoveListener (me, spTarget) -------------------------
	--                                                      PUBLIC
	-- INPUTS
	--  spTarget <#sprite>
	-------
 
	-- on vérifie que tout est ok
	if not listp(plspClients) then exit
 
	plspClients.deleteone(spTarget)
 
end -- mWheelMouse_RemoveListener handler

Rien de méchant ici : de simples tests pour éviter tout problème en cas de mauvaise utilisation, et un add() ou un deleteOne() dans la liste pour ajouter/supprimer une référence vers un client.

Enfin, il nous suffit lorsqu'on reçoit un événement mMouseWheelEvent de chercher quel client enregistré est survolé, et de le notifier. Tout est déjà en place…

on mWheelMouseEvent (me, nScroll) ------------------------------------ 
	--                                                      PUBLIC
	-- Evenement transmis par le script d'animation lors d'une mise à 
	-- jour depuis l'Xtra
	-- 
	-- INPUTS
	--  nScroll <#integer>
	-------
 
	if not listp(plspClients) then exit
	if not count(plspClients) then exit -- personne d'intéressé 
 
	-- on cherche une cible
	spTarget = void
	nCount   = count(plspClients)
	xyMouse  = the mouseloc 
 
	repeat with n = nCount down to 1
 
		rTarget = (plspClients[n]).rect
		if not xyMouse.inside(rTarget) then next repeat
 
		-- la souris est par dessus ce sprite
		spTarget = plspClients[n]
		exit repeat
 
	end repeat -- n 
 
	-- on regarde si on a trouvé un sprite enregistré 
	-- survolé par la souris
	if voidp(spTarget) then exit
 
	-- on transmet au sprite l'information de scroll
	sendSprite(spTarget.spritenum, #mWheelMouseScroll, nScroll)
 
end -- mWheelMouseEvent handler

Encore une fois, le script ne comporte rien de méchant, et si vous supprimez tout mes tests qui ne sont là 'que' pour éviter une erreur de script dans les mauvaises utilisations possibles, vous vous rendez compte que le script est simple : on boucle dans la liste des clients, et pour chaque sprite on regarde s'il est survolé par la souris. Dès qu'on en trouve un on sort de la boucle, et on transmet la notification à ce sprite.

Nous avons donc réussi à avoir un script parent qui n'est créé que quand on a besoin de lui, et qui se nettoie tout seul. C'est à dire que l'instance qu'on aura de lui sera toujours la bonne, et que nous n'avons aucune modification à apporter aux gestionnaires startMovie ou stopMovie pour qu'il soit créé quand il fait et nettoyé au bon moment. Ce script parent recevra bien les événéments transféré par le script d'animation qui l'accompagnera, et transférera bien cet événements aux éventuels clients concernés qui se seront enregistrés auprès de lui.

Vous pouvez télécharger (.zip) le script du dispatcher pour en avoir la copie complète, nous en avons terminé avec lui.

G. implémentation des scripts - partie 4 - clients du dispatcher

Toute l'architecture est prête, il ne nous reste plus qu'à coder les comportements 'clients', qui vont s'enregistrer auprès du dispatcheur, et réagir lorsqu'ils reçoivent des notifications. Je ne regarderai ici que le cas du défilement pour des acteurs texte, champs et Impressario. Mais la structure est là, vous pouvez l'étendre à ce que vous voulez.

Commençons par l'enregistrement des clients auprès du dispatcheur. Celà va se faire tout simplement lors du beginSprite, en récupérant l'instance du dispatcheur, et en appellant sa méthode d'enregistrement en lui passant une référence vers notre image-objet :

property pspMe
 
 
on beginsprite me
pspMe = sprite(me.spritenum)
me.miInit()
end -- beginSprite
 
 
on miInit (me) -------------------------------------------------------
	--                                                     PRIVATE
	-- > beginSprite
	-------
 
	-- on se déclare comme client pour les événements de molette de souris
	-- on récupère le singleton du manager
	oWheelMouseManager = script("s_mouseWheelManager").new()
 
	if objectp(oWheelMouseManager) then
	-- et on s'inscrit
	oWheelMouseManager.mWheelMouse_AddListener(pspMe)
	end if
 
end -- miInit handler

On notera que l'xtra n'est initialisé que lorsque le script du dispatcheur est créé, c'est à dire uniquement à partir du moment où au moins un client l'a instancié pour s'y enregistrer. Ainsi, l'xtra n'est jamais sollicité, et ne génère jamais d'événement tant que personne n'est prêt à les recevoir.

Une fois cette initialisation faite, il faut penser au nettoyage. A partir du endSprite, on va chercher l'instance du dispatcheur et le notifier de notre désinscription:

-- on récupère le singleton du dispatcher
oWheelMouseManager = script("s_mouseWheelManager").new()
if objectp(oWheelMouseManager) then
	-- et on se désabonne
	oWheelMouseManager.mWheelMouse_RemoveListener(pspMe)
end if

Voilà. A partir d'ici le squelette de client est pret : il s'abonne, reçoit les notifications, et se désabonne avant de disparaitre.

Pour la suite, nous allons devoir décliner ce modèle de client suivant les différents type d'acteurs.
Tous les clients auront un gestionnaire on mWheelMouseScroll (me, nScroll). Commençons par celui des textes et champs. Il suffit de modifier la propriété scrollTop de l'acteur pour agir sur le défilement.
Il faut ajouter à ça quelques tests pour ne pas scroller au delà des limites, afin de garantir un fonctionnement cohérent :

on mWheelMouseScroll (me, nScroll) -----------------------------------
	--                                                      PUBLIC
	-- INPUTS
	--  nScroll <#integer>
	-------
 
	if not integerp(nScroll) then exit
 
	mMe = sprite(me.spritenum).member
 
	-- on regarde la vitesse de scrolling pour cet acteur
	case mMe.type of 
		#field: nScrollSpeed = mMe.lineheight
		#text : nScrollSpeed = mMe.fontsize
	end case
 
	nNewScrollTop = mMe.scrollTop 
	if (nScroll < 0) then
		nNewScrollTop = nNewScrollTop + nScrollSpeed
	else
		nNewScrollTop = nNewScrollTop - nScrollSpeed
	end if
 
	nNewScrollTop = max(0,nNewScrollTop)
 
	-- on cherche le scrollTop max  afficher
	nScrollTopMax = (mMe.charpostoloc(mMe.char.count)).locv
	nScrollTopMax = nScrollTopMax - pspMe.height
	nNewScrollTop = min(nNewScrollTop, nScrollTopMax)
 
	mMe.scrollTop = nNewScrollTop
 
end -- mWheelMouseScroll handler

Vous pouvez télécharger le script du comportement de client pour les acteurs texte et champs.

Il ne nous reste plus qu'à coder le script client pour les sprites Impressario.
La subtilité ici vient dans la gestion multipages des documents pdfs : nous devons modifier la propriété scrollV, en la comparant au scrollVMax, afin de voir si nous sommes en haut ou en bas de page. Le cas échéant nous devrons donc en plus demander à passer à la page suivante ou précédente.

Je vais en profiter pour ajouter la gestion de touches de modificateur. Par exemple, nous allons faire en sorte qu'ici la molette fasse défiler le document régulièrement, et que si la touche Shift est enfoncée, on passe directement à la page suivante/précédente.
Ajoutons encore au passage la gestion de la propriété trackView qui permet à partir de la version 2 de spécifier si les modifications d'affichages soient enregistrées ou non dans l'historique de navigation. L'intérêt de le mettre à FALSE est d'éviter d'avoir une multitude d'entrées dans l'historique correspondant à toutes les étapes du défilement.

Par soucis de simplicité, je me contenterai ici de le désactiver… dans une application profesionnelle je la désactiverai, et la réactiverai en forçant l'enregistrement d'une vue à partir du moment où le défilement est arrété depuis quelques instants… prenant ainsi en compte le défilement à la molette comme une seule opération de navigation (et non pas une multitudes de petites modifications)

Voici donc le gestionnaire de molette pour les clients Impressario :

on mWheelMouseScroll (me, nScroll) -----------------------------------
	--                                                      PUBLIC
	-- INPUTS
	--  nScroll <#integer>
	-------
 
	if not integerp(nScroll) then exit
 
	mMe = pspMe.member
 
	-- l'historique est géré à partir de la v2
	bHandleHistoryStack = (me.miGetImpressarioVersion() > 1)
 
	-- on désactive l'historique
	if bHandleHistoryStack then pspMe.trackView = false
 
	nPage    = pspMe.page
	nScrollV = pspMe.scrollV
 
	-- on gère les modificateur : Shift fait défiler page par page
	if the shiftDown then
		if (nScroll < 0) then nNewPage = nPage+1
		else                  nNewPage = nPage-1
 
		-- on s'assure que la page existe
		nNewPage = max(1, min(nNewPage, mMe.totalPages))
 
		pspMe.page = nNewPage
 
	else -- défilement régulier
 
		-- on regarde la vitesse de scrolling pour cet acteur
		nScrollSpeed = 10
 
		if (nScroll < 0) then nScrollV = pspMe.scrollV + nScrollSpeed
		else                  nScrollV = pspMe.scrollV - nScrollSpeed
 
		-- on regarde si on doiot naviguer entre les pages
		if (nScrollV < 0) then
			-- page précédente
			if (nPage > 1) then
				nPage    = nPage - 1
				nScrollV = pspMe.scrollVMax
			else
				-- si on est déjà à la première page, on reste bloqué
				nScrollV = 0
			end if
 
		else if (nScrollV > pspMe.scrollVMax) then
			-- page suivante
			if (nPage < pspMe.member.totalPages) then
				nPage    = nPage + 1
				nScrollV = 0
			else
				-- si on est déjà à la dernière page, on reste bloqué
				nScrollV = pspMe.scrollVMax
			end if
 
		end if
 
		pspMe.page    = nPage
		pspMe.scrollV = nScrollV
 
	end if
 
	-- on restaure l'historique
	if bHandleHistoryStack then pspMe.trackView = false
 
end -- mWheelMouseScroll handler

Vous pouvez télécharger le script client à déposer sur les images-objet Impressario.

Et pour finir, vous trouverez dans la démo disponible ci dessous l'implémentation de la molette de la souris dans le script de navigation des bookmarks, qui est entièrement géré en imaging Lingo. Vous aurez ainsi un exemple de plus où le script s'enregistre avec mWheelMouse_AddListener, se désabonne sur son endSprite, et reçoit les notification d'événements mMouseWheelEvent.

Vous observez par ailleurs que la molette de la souris n'interfère pas avec les scripts Impressario existants, et qu'une navigation à l'aide de la molette dans le pdf est reflétée par la mise à jour du naigateur de bookmarks.

H. démonstration de l'ensemble

Voici une démonstration de plusieurs sprites texte, champs et Impressario (PDF et navigateur de bookmarks) sur la même scène, interagissant avec la molette de la souris. Vous pourrez voir dans cette démo la gestion de touches Alt et Shift avec la molette pour contrôler des sprites Impressario.
Télécharger la source complète (avec Impressario) (zip, 1,5Mo)
Télécharger la démo complète (avec Impressario) (zip, 9Mo)
Télécharger la source allégée (sans Impressaio) (zipn 1,4 Mo)
Télécharger la démo allégée (sans Impressario) (zip, 1,7 Mo)

I. aller plus loin

Nous avons vu ici comment router les événements venant de l'Xtra, et effectuer une action adhoc en fonction de la rotation de la molette et de la postion de la souris.
Pour simplifier je me suis ici contenté de gérer le défilement. Mais on peut très aisément imaginer des fonctions supplémentaires : par exemple pour une image-objet, nous pourrions effectuer des actions différentes suivant l'outil sélectionné : défilement si l'outil 'main' et sélectionné, mais zoom avant/arrière si l'outil 'loupe' est sélectionné (c'est ce que j'ai implémenté dans la démo téléchargeable, si vous souhaitez décortiquer ce code)

De même on peut imaginer faire un contrôle du volume avec la molette de la souris, un changement de fieldOfView d'une caméra 3D, ou encore un changement de vitesse de lecture d'une vidéo….. bref nous avons le squelette pour gérer l'évenement #WheelMouseEvent, ensuite tout est imaginable :^)

Une alternative à l'xtra wheelMouse est l'xtra DirectInteraction, de DirectXtras, qui apporte l'événement DirectMouseWheel. Cet xtra n'étant pas gratuit, et cette méthode ne sembant rien apporter de plus de nécessaire que l'xtra gratuit wheelMouse utilisé ici, je n'ai pas testé plus avant cette solution.

J. Historique des modifications

6 décembre 2004 : rédaction initiale (Sébastien Portebois) sur cette page.

K. Mentions légales

Macromedia, Director et Xtra sont des marques déposées de Macromedia Inc. IntegrationNewMedia et Impressario sont des marques déposées d'Integration New Media Inc.