Faire du drag and drop avec flash en utilisant canvas
Par Laurentj le jeudi, février 11 2010, 15:18 - Technologies Web - Lien permanent
À Zoomorama, Flash nous rend bien service. De par ses performances au niveau rendu, il nous permet de développer notre navigateur de documents zoomable, notre "ZoomViewer", que l'on peut intégrer dans une page web. Seul souci avec Flash : il ne connait pas le drag and drop.
Vous aurez beau commencer un geste de drag à partir d'une appli flash, aucune application, même pas le navigateur html, ne reconnaitra cela comme étant un mécanisme de drag and drop. Pour la simple raison qu'il n'y aucune API disponible dans Flash pour notifier de tels évènements au système. C'est particulièrement balot, quand on sait que même en HTML (et en XUL), il y a tout ce qu'il faut. L'inverse est également vrai : dragguez quelque chose du navigateur (l'url de la barre de navigation), où encore d'une autre application, vers l'application flash, vous n'avez aucun moyen en Flash/ActionScript, de prendre en charge ces évènements, et donc de permettre un drop.
Cela est particulièrement ennuyeux dans notre logiciel ZoomCreator2, que nous développons, David et moi. C'est un éditeur permettant de créer des documents zoomorama. Nous réutilisons notre application flash, légèrement modifiée, pour la zone principale d'édition "wysiwyg". Notre logiciel est basé sur XulRunner, donc son interface est du XUL, CSS, HTML etc. Nous voudrions pouvoir faire du drag and drop de cette zone flash vers le reste de l'application et vice versa, ou même à partir de l'extérieur de l'application vers notre zone flash (pour drag and dropper des images d'un explorateur vers notre éditeur par exemple). Cependant, à cause des lacunes de flash, c'est impossible de l'implémenter.
Ou alors en rusant.
J'ai en effet trouvé un hack, mais un hack qui n'est pas trop moche à mon sens.
L'idée générale est la suivante : puisque Flash ne génère pas, ni ne reçoit des évènements de drag and drop, virons-le, cachons-le, temporairement, en le remplaçant par un élément XUL ou HTML. Ce remplacement s'effectuerait immédiatement lorsqu'on détecterait le début d'un drag dans l'application (évènement dragstart, ou dragenter quand le drag vient de l'extérieur). Pour cela, pas de souci. Reste à faire ce remplacement proprement dit.
La première solution imaginée a été de positionner un élément XUL ou HTML transparent, au dessus de la balise object de flash. Hélas, ça marchouillait, voir pas du tout. Impossible de mettre cet élément "au dessus", il restait à coté, certainement à cause du modèle de boîte XUL. Et de toute façon, même en HTML pure, il y a des soucis, qui différent selon les plateformes si mes souvenirs sont bons.
Je n'ai pas trop persisté dans cette direction, sachant que j'allais certainement arriver à une impasse. Et puis en fait, j'ai eu une autre idée, celle d'utiliser <canvas>. Pour les développeurs d'extensions et d'applications XUL (mais pas pour les applis web), <canvas> possède un méthode drawWindow. Vous lui donnez un objet window (typiquement, la fenêtre d'une page HTML), les coordonnées d'un rectangle, et ça affiche dans <canvas> une capture d'un morceau de la fenêtre correspondante aux coordonnées données. C'est particulièrement utilisé dans des extensions qui vous affichent vos bookmarks avec une capture de la page web correspondante, ou pour faire un "alt+tab' sexy etc.
Ainsi donc, dés que je détecte le début d'un drag dans l'application :
- je donne à la méthode
drawWindowd'un élément<canvas>la fenêtre de notre application XUL, en lui donnant les coordonnées (x,y + taille) de l'emplacement de l'affichage de la balise object de notre zone flash. - dans ce canvas, j'ai alors une capture de ce qu'affiche le flash.
- je rend invisible le flash, et fait apparaitre le canvas au même emplacement. Merci CSS et le modèle de boite XUL j'ai juste à faire en faite un
display:blocksur le canvas, canvas qui est un élement frère de ma balise object, et tout deux dans une boite XUL. Unoverflow:hiddensur cette boite fait que le canvas "pousse" l'object vers le bas, mais du coup invisible, et sans faire apparaitre de scrollbar. - tout ce passe instantanément, et on ne remarque rien.
En définitive donc, je n'ai plus de "trou noir" dans l'interface de l'application, que des éléments XUL et HTML sur toute la surface. Je suis à même alors de recevoir tout les évènements drag and drop sur ce canvas et de les traiter. Une fois traités (évènements dragend ou drop), je fais disparaitre le canvas, la balise object reprend sa place, le flash est de nouveau là. Ni vu ni connu.
Vivement WebGL et/ou l'accélération matérielle graphique pour canvas dans tout les navigateurs, Ce sera alors certainement suffisamment performant pour se passer de Flash pour notre technologie de zooming, et nous évitant tout ces hacks acrobatiques :-)
Commentaires
J'ai été confronté à ce problème avec flash, mais dans le contexte d'une application Java intégrant un player. Je n'avais pas eu trop de soucis à effectuer le d&d d'une image vers flash, mais l'inverse était plus pénible.
Dans votre cas, je me demande s'il n'y aurait pas eu une solution plus simple en utilisant ExternalInterface et Javascript (avec certainement un canvas).
J'imagine néanmoins que cette piste a pu être explorée :)
Par contre, ton canvas n'est qu'une bête image fixe non ? Si j'ai bien compris…
C'est pas possible de donner des feedback à l'utilisateur en colorant une zone pour indiquer qu'on peut droper dedans ?
À moins que ton canvas ne se rafraichisse toutes les x secondes…
tiens, çà me rappelle la technique que j'utilise pour pouvoir imprimer une page avec du flash sous linux. On "recopie" aussi la page avec drawWindow, on la met dans une iframe cachée, et on imprime l'iframe. C'est pratique que cette méthode prenne en fonctionne avec les plugins!
@stephane : mouai, mais essayer de recréer un semblant de drag and drop via flash, c'est bien plus casse c***** que mon hack de quelques lignes. L'avantage de ma méthode, c'est qu'on utilise les events du dnd comme on a l'habitude de faire, sans avoir à redévelopper une machinerie DND. Mais sinon oui, externalinterface et cie, on y a pensé et on l'utilise déjà énormément : on expose dans la page web (ou xul) le DOM du document XML lu par le flash, et donc on peut manipuler le document en javascript comme on le ferait avec une iframe. Résultat : on peut modifier dynamiquement le document afficher par notre appli flash, en ayant bien entendu les répercussions instantanées sur l'affichage dans le flash ;-)
@neonov : oui le canvas est une image fixe. pour le feedback utilisateur, vu qu'on peut droper partout dans notre zone flash, c'est pas trop grave. On est justement en train de reflechir à des moyens pour avoir du feedback précis (on peut pas droper du texte n'importe où quand même, donc effectivement ça reste interessant). à priori, on a ce qu'il faut dans l'api flash qu'on expose, pour savoir ce qu'il y a à tel coordonnée, donc de dessiner dans le canvas un "feedback". Mais cependant, je reconnais que c'est assez chaud :-)