Explication du code source dans SimpleWireframeSketcher
Les interfaces utilisant stylet permettent plus d'expressivité qu'une souris, permettant de mieux faire des mouvements fins (utiles pour l'écriture et les dessins) et d'exploiter la pression Dans les activités créatives (design, conceptualisation ...), faire des croquis et travailler avec des repésentations vagues encourage une exploration plus large d'idées Les interfaces multitactiles offrent plus de degrés de liberté qu'une souris (exemples: geste de pincement à deux doigts pour translater + tourner + changer de taille simultanément; gestes à plusieurs doigts pour activer des commandes) Les interfaces utilisant à la fois un stylet et le multitactile offrent une plus grande bande passante, exploitant les deux mains de l'utilisateur (et possiblement le modèle de chaîne cinématique de Yves Guiard), et permettant de rapidement basculer entre différents outils.
Malheureusement, les détails de programmation varient selon le plateforme. Le client peut avoir à récupérer les événements avec un "listener" qui se fait appellé (TUIO, Android, WM_TOUCH), ou bien le client peut avoir à appeller une méthode pour interroger ("poll") (MT4j, lecture directe de USB) avec une fréquence décidée par le client. Lorsque plusieurs doigts se déplacent en même temps, un événement distinct peut être généré pour chaque doigt (TUIO, MT4j, WM_TOUCH), ou bien chaque événement peut contenir les déplacements de plusieurs doigts en même temps (Android). Selon le plateforme, ça peut arriver que des événements sont générés même s'il n'y a pas eu de changement de coordonnées des doigts. Si on veut que le code de client évite de se redessiner pour rien, le client peut avoir à vérifier si au moins un des coordonnées a changé, rendant son code plus complexe. ...
(... suite:) On peut rencontrer des séquences d'événements différentes selon le plateforme. Programmer un client qui est robuste contre ces différences peut prendre du code supplémentaire. Exemple: sur Android, avec une tablette Galaxy Note, quand un stylet se rapproche de l'écran pour enfin le toucher et ensuite repartir, la séquence d'événements reçus est HOVER_ENTER, HOVER_MOVE, HOVER_EXIT, DOWN, MOVE, UP, HOVER_ENTER, HOVER_MOVE, HOVER_EXIT. Donc, HOVER_EXIT peut indiquer que le stylet est parti et hors de portée, comme il peut indiquer que le stylet entre en contact avec l'écran. Exemple: si le client doit faire des interrogations ("poll") pour récupérer les événements, il est possible que certaines transitions d'état seront perdues, si les interrogations ne sont pas assez fréquentes.
Un framework pour le multitactile On aimerait un framework (cadriciel) qui récupère les événements multitactiles (et de stylet et de souris) et les achemine vers un client, permettant d'érire le code du client sans s'attarder aux différences entre les plateformes. Migrer le code sur un autre plateforme demanderait simplement de changer la version de framework, sans changer le code du client.
Exigences pour le framework Le client définit une méthode processEvent() qui est appellée par le framework (comme un listener). Si le plateforme demande de faire un "poll" (interrogation), c'est le framework qui cache ce détail. La méthode processEvent() est appellée une fois pour chaque événement de doigt (ou stylet ou souris). Si le plateforme emballe plusieurs doigts en un seul événement, le framework cache ce détail et déballe les doigts en événements séparés.
Exigences pour le framework Le framework appelle processEvent() seulement s'il y a eu un changement de coordonnées ou autre changement d'état. Si le plateforme génère des événements même sans déplacement de doigt, le framework se charge de bloquer ces événements. Lorsque possible, le framework génère des événements sythétiques pour garantir des séquences logiques (exemple: si un stylet est en mode "hover" et passe en mode "drag", le framework génère un "down" entre les deux). Cela permet au code client d'être moins défensif et plus simple.
Exigences pour le framework Le framework devrait faciliter l'implémentation de widgets (multitactiles ou autres) par le client, sans imposer de widgets pré-définis sur le client. Ceci est fait en partie avec deux interfaces qui sont implementées par les widgets du client: Dispatcher (= parent) et Receiver (= enfant).
SimpleWireframeSketcher: Application Android à modifier
SimpleWireframeSketcher ToolbarButton Toolbar SimpleWireframeSketcher Toolbar DrawingCanvas ToolbarButton ToolbarButton ... DrawingCanvas A A achemine des événements vers B B
Croquis d'une application typique Bouton 1 Bouton 2 Bouton 3 Bouton 4 Barre d'outils 2 Barre d'outils 1 Canvas Fenêtre principale
Fenêtre principale Cheminement des événements Barre d'outils 1 Barre d'outils 2 Canvas Bouton 1 Bouton 2 Bouton 3 Bouton 4
Fenêtre principale Dispatcher Cheminement des événements Receiver Barre d'outils 1 Dispatcher Receiver Barre d'outils 2 Dispatcher Receiver Canvas Receiver Bouton 1 Receiver Bouton 2 Receiver Bouton 3 Receiver Bouton 4
SimpleWireframeSketcher A achemine des événements vers B, via les méthodes draw() et processEvent() A MultitouchFramework B SimpleWireframeSketcher DrawingCanvas Toolbar ToolbarButton
MultitouchDispatcher MultitouchReceiver B implémente A B A A contient une instance de B MultitouchFramework B A A contient plusieurs instances de B N B MultitouchDispatcher MultitouchReceiver SimpleWireframeSketcher DrawingCanvas Toolbar N ToolbarButton
MultitouchDispatcher MultitouchReceiver B implémente A B A A contient une instance de B MultitouchFramework B A A contient plusieurs instances de B N B MultitouchDispatcher MultitouchReceiver N MultitouchDispatcherImplementation N MultitouchCursor SimpleWireframeSketcher DrawingCanvas Toolbar N ToolbarButton
Exigences pour le framework Le framework devrait prendre en charge la gestion des "curseurs" actifs (pour doigts, stylet, souris). Chaque Cursor stocke l'historique complet de ses coordonnées. Un Dispatcher stocke les Cursors pour chacun de ses Receivers. En fait, pour économiser de la mémoire, tous les Cursors sont stockés par le Framework, et chaque Dispatcher stocke seulement un mappage pour associer les Cursors aux Receivers Chaque Receiver peut demander à son Dispatcher une référence vers son/ses Cursor(s). DispatcherImplementation implémente une politique par défaut pour acheminer les événements vers des Receivers. (SimpleWireframeSketcher et Toolbar se servent de DispatcherImplementation pour acheminer les événements vers leurs Receivers.) DispatcherImplementation génère automatiquement des événements de ENTER et EXIT, utiles pour certains widgets (Receivers).
Exigences pour le framework Le framework devrait rendre facile l'implémentation d'un widget qui répond seulement à ... ... certains inputs ou certains devices (doigt, stylet, souris), sans que le widget s'attarde à bloquer des doigts excessifs ou des devices non-compatibles. Chaque receiver peut définir son propre Receiver.maxNumAcceptedCursors() et Receiver.isDeviceAcceptable(...) ... un Cursor (exemple: un simple bouton) Le Receiver définit son maxNumAcceptedCursors() pour retourner 1, et il peut appeler Dispatcher.getUniqueCursorOfReceiver(this) pour obtenir son Cursor ... deux Cursors (exemple: un widget qui répond au geste de pincement) Le Receiver définit son maxNumAcceptedCursors() pour retourner 2, et quand il reçoit un événement associé au curseur C, il peut appeler Dispatcher.getOtherCursorOfReceiver(this,C) pour obtenir l'autre Cursor.
Exigences pour le framework Le framework devrait permettre aux widgets d'avoir des formes non-rectangulaires On permet donc à chaque Receiver de définir Receiver.isInside(x,y). Le Dispatcher s'en sert pour savoir quand un Cursor a franchi une frontière. Le framework devrait isoler le code qui dessine sur le canvas, pour faciliter la migration vers d'autres plateformes qui ont différentes façons de dessiner (exemples: java.awt.Graphics2D, android.graphics, JOGL) Le code pour dessiner sur un canvas se trouve dans GraphicsWrapper
MultitouchDispatcher MultitouchReceiver B implémente A MainActivity B GraphicsWrapper A A contient une instance de B MultitouchFramework B A A contient plusieurs instances de B N B MultitouchDispatcher MultitouchReceiver N MultitouchDispatcherImplementation N MultitouchCursor SimpleWireframeSketcher Drawing DrawingCanvas Toolbar N N Stroke ToolbarButton
▶ code client ▲ cadriciel ou application MainActivity GraphicsWrapper B implémente A MainActivity B GraphicsWrapper A A contient une instance de B MultitouchFramework B A A contient plusieurs instances de B N B MultitouchDispatcher MultitouchReceiver N MultitouchDispatcherImplementation N MultitouchCursor ▲ cadriciel SimpleWireframeSketcher Drawing DrawingCanvas Toolbar ▶ code client ou application N N Stroke ToolbarButton
La seule partie à changer si on veut migrer vers un autre plateforme. B implémente A MainActivity B GraphicsWrapper A A contient une instance de B MultitouchFramework B A A contient plusieurs instances de B N B MultitouchDispatcher MultitouchReceiver N MultitouchDispatcherImplementation N MultitouchCursor SimpleWireframeSketcher La seule partie à changer si on veut migrer vers un autre plateforme. Drawing DrawingCanvas Toolbar N N Stroke ToolbarButton