11 octobre 2004

BroadcasterMX : une hérésie !

Actuellement en train de recoder "maison" certaines classe fournies par Macromédia (principalement mx.transitions.Tween et mx.transitions.OnEnterFrameBeacon), j'ai encore du mal à comprendre pourquoi la classe BroadcasterMX est utilisée ... :\

En effet, il existe toujours son ancêtre historique du nom de AsBroadcaster (attention aux majuscules :P). Cette classe, présente depuis Flash 5, a l'immense avantage d'être native au player, codée en C (ou autre langage compilé), et donc bien plus performante !!! Son successeur est quant-à lui codé en AS2 (donc en bytecode interprété). Etant donné qu'il n'existe strictement aucune différence de fonctionnalité, pourquoi faire le sacrifice de l'éfficacité ??? :o

Quoi qu'il en soit, depuis pas si longtemps que ça, je suis devenu bien plus adepte du modèle EventDispatcher, dont je me sers dans les nouvelles classes que je publierai ici-même dans trés peu de temps ... 8)

9 octobre 2004

Strucutures de données : Grid et HashTable

J'entame un petit package 'data' qui contiendra différentes structures de données parfois bien utiles. L'idée m'est venue du besoin d'une HashTable, que j'avais eu l'occasion de manipuler durant mes quelques aventures avec Java. J'en ai ainsi profité pour refaire la classe Grid que vous connaissez déjà peut-être ...

Pour ceux qui n'ont pas la chance de la connaitre, la classe HashTable correspond à l'utilisation d'un tableau, sauf que les clés n'ont pas de contrainte et peuvent être de n'importe quel type, contrairement au tableau qui a besoin de clés numériques, ou au tableau associatif qui a besoin de clés alphanumériques. Il s'agit donc d'un "simple" système d'association clés/valeurs.
import com.lalex.data.*;
var ht:HashTable = new HashTable();
var k1 = new Date();
var k2 = ["un", "tableau", "comme", "clé"];
ht.put("clé", "valeur");
ht.put(k1, "date");
ht.put(k2, ["un", "tableau", "comme", "valeur"]);
trace(ht);
/** Sortie
[ clé => valeur
  Sat Oct 9 14:14:53 GMT+0200 2004 => date
  un,tableau,comme,clé => un,tableau,comme,valeur ]
*/

// Récupère un élément
trace("k2 : " + ht.get(k2));
/** Sortie
k2 : un,tableau,comme,valeur
*/

// Supprime un élément
ht.remove(k2);
trace(ht);
/** Sortie
[ clé => valeur
  Sat Oct 9 14:14:53 GMT+0200 2004 => date ]
*/
La classe Grid, quant à elle, est un simple tableau à deux dimensions, agrémenté de quelques méthodes assez pratiques. ;)import com.lalex.data.*;
var gr:Grid = new Grid(2,3);
gr.fill(1,2,3,4,5,6);
trace(gr);
/** Sortie
[[1 , 2]
 [3 , 4]
 [5 , 6]]
*/

// Inverse la grille
gr.reverse();
trace(gr);
/** Sortie
[[1 , 3 , 5]
 [2 , 4 , 6]]
*/

// Met tous les éléments de la grille au carré
gr.map(function(n) { return n*n; });
trace(gr);
/** Sortie
[[1 , 9 , 25]
 [4 , 16 , 36]]
*/

::Télecharger data.zip::

17 septembre 2004

Tween2 : quand Tween rencontre EventDispatcher

Sortant tout juste d'un développement utilisant des composants Macromedia à profusion, j'ai pas la même occasion eu l'opportunité de commencer à fréquenter le model évenementiel de ceux-ci, introduit par la classe EventDispatcher ... En effet, jusqu'à aujourd'hui, j'étais assez satisfait des services de AsBroadcaster (d'ailleurs, je ne trouve toujours pas d'utilité à la classe BroadcasterMX, offrant exactement la même chose mais en AS, donc moins performant) ...

Il ne m'a pas fallu longtemps pour me rendre compte de l'interêt de ce nouveau système, que ce soit en terme d'externalisation de l'interception des événements (même si c'était déjà possible avant), de performance acrues du fait de la diffusion propre à chaque évenement, mais aussi et surtout de souplesse grâce à la délégation d'évenement. D'ailleurs, je suis bien fan de la classe EventDelegate à mon sens bien plus élégante que Delegate, fourni avec la MAJ 7.2 de Flash.

J'ai donc suivi la voie tracée par Grant Skinner, qui propose ses classes XML2 et LoadVars2, utilisant EventDispatcher, en créant ma classe fille de l'illustre Tween de Penner (maintenant intégrée à la distribution de Flash MX 2004 en tant que mx.transitions.Tween), qui utilise dorénavant EventDispatcher.

Deux petits avantages mineurs amenés aussi par cette version sont qu'on ne peut du coup plus utiliser les méthodes "old-school" directement avec l'objet Tween, qui sont devenues des membres privés, forcant à externaliser le code sous forme d'écouteurs, et également le typage fort des paramètres du constructeur ... En gros, ca oblige un peu plus à faire les choses correctement ;)

Exemple d'utilisation :import com.lalex.animation.Tween2;
import com.robertpenner.easing.Expo;
var tw:Tween2 = new Tween2(myClip, "_x", Expo.easeOut, 100, 300, 32);
var listen:Object = {};
listen.motionChanged = function(e:Object) {
   trace("Clip position : " + e.position);
}
tw.addEventListener("motionChanged", listen);

On peut imaginer que les "classes" (?) telles que Mouse; Stage ou Keyboard pourraient être remplacées par leur "version 2" assez facilement (sans héritage celle-là par contre), mais cela mérite des tests : je ne suis pas persuadé des performances prodiguées par des évenements qui interviennent aussi souvent ... :o

::Télécharger Tween2.zip::

13 septembre 2004

Essential ActionScript 2.0 : la review

Colin Moock s'est imposé depuis son livre ActionScript for Flash MX : the Definitive Guide comme ayant écris l'ouvrage de référence des programmeurs Flash. Aujourd'hui, il remet le couvert et se penche sur le "nouveau" langage de Macromedia, introduit dans Flash MX 2004, avec Essential ActionScript 2.0.

Loin de remplacer son précédant ouvrage, cette nouvelle référence se veut comme étant un complément de celui-ci. En effet, alors que le premier était une liste quasi-exhaustive de l'AS1, EAS2 s'attaque à la philosophie de développement complètement revue d'AS2, du fait de son orientation objet. Il s'adresse donc en bloc aux programmeurs AS1 intermédiaires, ainsi qu'aux développeurs objets issus de langages plus "traditionnels" tels que Java ou C++, et aux programmeurs AS2 intermédiaires et même confirmés.

La première partie du livre aborde la syntaxe AS2. Mêlant habilement l'initiation à la programmation objet, et la description des instructions introduites par la nouvelle mouture d'ActionScript, cette partie fourni au futur programmeur AS2 tous les outils nécessaires à ses premiers développements. N'oubliant pas d'aller au fond des choses, Colin Moock décortique chaque instruction à l'extrême, soulignant les particularités des nombreuses situations qui peuvent se présenter au codeur face aux instructions AS2.
Parfois répétitif dans son discours, mais jamais rébarbatif, il mets systématiquement le doigt sur les erreurs que l'on fait fréquemment lorsque l'on aborde l'AS2, et souligne les avantages indéniables qu'apporte ce nouveau langage, mais aussi cette nouvelle façon d'aborder le développement dans Flash.
Non content de donner une explication théorique exhaustive des syntaxes de base, des exemples concrets sont apportés avant d’aborder chaque nouvelle notion importante, nous guidant ainsi au travers du développement d'une classe ImageViewer, dans laquelle sont utilisées les notions précédemment abordées de manière plus formelle.

La deuxième partie aborde les méthodes à utiliser lors du développement d'application. L'auteur nous y présente tout d'abord sa méthode de travail "de base" notamment en terme de hiérarchie des dossiers et documents. Sans être révolutionnaire, elle a le mérite d'être simple, claire et efficace. Si vous n'avez pas déjà vos propres habitudes de développement objet, je dirais tout simplement que l'essayer c'est l'adopter, et si vous les avez déjà, vous pourrez ainsi les confronter à celles qui sont données dans ce chapitre.
Les chapitres suivant nous emmènent vers la programmation "visuelle", tout d'abord en abordant les composants et leur utilisation, en exposant notamment leur intégration à une application concrète de convertisseur de devises, puis en consacrant un chapitre entier à l'héritage de MovieClip, nous en dévoilant ainsi toutes ses subtilités, et également les aspects à prendre en compte pour effectuer un choix entre l'héritage et la composition avec MovieClip.
Cette partie se finit en décortiquant les différents moyens mis à notre disposition pour distribuer un code, que ce soit en OpenSource ou sans mettre les fichiers sources à dispositions des utilisateurs, en énonçant clairement les avantages, inconvénients et difficultés de chacune des méthodes.

La troisième partie se penche sur 4 Design Patterns, parmi les plus utilisés dans Flash que sont l'Observer, le Singleton, le Model-View-Controller (MVC) et la Délégation d'événements. Plus qu'une description approfondie de ces patterns, il s'agit plutôt dans cette partie de montrer leur utilité au sein d'applications AS2, ainsi que leur implémentation au sein d'exemple toujours aussi concrets.

Je ne peux que supposer la réaction des différentes catégories de lecteurs visées par ce livre :

  • Pour les développeurs AS1, il introduit en douceur mais également en profondeur toutes les subtilités de AS2. Etant déjà convaincu, je ne peux que suggérer, mais je dirais que ce livre peut réussir à convaincre ceux qui sont encore réfractaire à l'AS2, en soulignant tous les avantages que cela emmène, que ce soit en terme d'organisation du code, mais aussi d'aide au développement prodiguée par les erreurs de compilation. Un coup de chapeau concernant le détail exhaustif des différentes possibilités qui existent quand on surcharge une méthode, et concernant le chapitre sur le gestion d'erreurs qui est un bijou de précision qui sont tout deux des bijoux de précision, allant vraiment au fond des choses.
  • Pour les développeurs Objet découvrant l'AS2, en plus de décrire précisément les différences avec des langages reconnus tels que Java, l'ouvrage s'attache à faire découvrir la philosophie de développement à aborder avec Flash, qui diffère de par son orientation clairement graphique. Pour ceux qui sont déjà à l'aise avec les Design Patterns, il s'agit aussi de voir de bonnes implémentations AS2, pour ainsi se plonger dans le langage.
  • Pour les développeurs AS2, qu'ils soient intermédiaires ou aguerris, il s'agit là d'une revue de détail, répondant la plupart du temps aux questions que l'on peut se poser sur quelques finesses du langage. Si l'on peut finir le livre en pensant ne pas avoir appris énormément par rapport à ce que l'on savait déjà, le simple fait de se remettre à programmer après cette lecture change la manière dont on voit le code. En gros, pour moi, j'ai vraiment l'impression que les qualités pédagogiques de l'auteur ont clarifié certaines notions qui m'étaient connues jusque là, mais peut-être un peu embrouillées ! :$

En conclusion, je dirais que Colin Moock nous offre un nouvel ouvrage de référence, qui saura satisfaire de par son contenu riche et détaillé tous les développeurs accomplis ou en devenir. Véritable fondation des connaissances à posséder en ActionScript 2.0, il s'avère la rampe de lancement idéale pour aller encore plus loin dans la Programmation Orientée Objet avec AS2.

Pour finir, je vous conseille de lire suite à ce livre celui sur les Design Patterns par le Gang Of Four, que j'ai moi-même lu sur les conseil de Francis BOURRE, et qui est d'ailleurs cités par Colin Moock dans la troisième partie de l'ouvrage. Il s'agit là d'une véritable mine d'or sur les DP.

25 août 2004

StageManager 0.1

Pour continuer dans la série des 0.1, voici aujourd'hui une classe que je termine tout juste, servant à gérer le positionnement et la taille de clips dans des animations Flash plein écran. En effet, alors qu'il est maintenant possible de créer des site Flash occupant la totalité d'une fenêtre grâce à la classe Stage, trés peu de site l'utilisent ...

J'ai donc crée ce set de classe afin de faciliter la gestion des clips dans cette situation précise. On peut ainsi décider de la position d'un clip et de son alignement sur la scène, ainsi que sa taille. Il est ainsi possible de profiter presque de la même souplesse qu'offre HTML pour des applications qui pourraient ainsi occuper la totalité de la surface de l'animation.

Voici un exemple d'utilisation:import com.lalex.stage.StageManager;
import com.lalex.movieclip.SuperClip;
var stageMgr:StageManager = StageManager.getInstance();
// Alignement en haut à gauche, sans marge, de largeur 100%, hauteur inchangée
stageMgr.addElement(new SuperClip(_root.taskbar), {align:"left,top", margin:0, size:"100%,none"});
// Aligné à droite, avec une marge (à droite) de 10px
stageMgr.addElement(new SuperClip(_root.rightMenu), {align:"right", margin:10});
// Aligné en bas avec une marge (verticale) de 5% de la hauteur de l'anim
stageMgr.addElement(new SuperClip(_root.footer), {align:"bottom", margin:"none,5%"});

Bien qu'il manque encore des fonctionnalités ou des options de positionnement, StageManager est dors et déjà utilisable, et la classe SuperClip devrait donner naissance à une fonctionnalité similaire aux méthodes de positionnement de Stéphan Guénette.

L'utilisation de la composition dans SuperClip permet ainsi de gérer des clips dont le redimensionnement devrait se faire de manière spécifique, comme des composants par exemple. Il suffirait alors de créer une classe qui implémente l'interface IClip. Bref, en créant la classe appropriée, vous pouvez gérer virtuellement tout objet qui s'affiche à l'écran. 8)

::Télécharger StageManager.zip::

20 août 2004

Propriétés virtuelles et méthodes

Dans un post précédent, on me demandait s'il n'était pas plus pratique de créer directement des propriétés virtuelles plutôt que des méthodes accesseurs et des propriétés virtuelles qui utilisent ces méthodes. Ma réponse est simple : NON !

C'est tout à fait flagrant dans le cadre de la problématique qui se pose à moi actuellement. Je désire créer une classe qui pourrait prendre en entrée un objet "affichable" qu'il est possible de positionner et de déplacer au moyen de méthodes prédéfinies. Je voudrais également que les composants v2 puissent être gérés par cette application.

Or, dans les composants v2, et plus particulièrement la classe UIObject, il est en effet possible de déplacer un composant et de le redimensionner respectivement au moyen des méthode move() et setSize() ... Par contre, le seul moyen de récupérer ses dimensions ou sa position est d'utiliser les propriétés virtuelles x, y, width, height ... Aucune méthode getX(), getY(), getWidth() ou getHeight() n'est au rendez-vous.

Il m'est donc impossible de créer une interface dont je puisse me servir pour le typage du paramètre de ma méthode, étant donné que les propriétés virtuelles ne peuvent être renseignées dans une interface ... et donc d'utiliser ces propriétés dans ma méthode ! :x A moins d'utiliser des hacks pour déjouer les erreurs du compilateur, mais les interfaces servant uniquement au moment de la compilation, ca n'aurait aucun sens ! 8|

Voici le code de Macromedia :/**
* width of object
* Read-Only:  use setSize() to change.
* @helpid 3982
*/

        function get width():Number
        {
                return _width;
        }
/**
* height of object
* Read-Only:  use setSize() to change.
* @helpid 3964
*/

        function get height():Number
        {
                return _height;
        }
/**
* x = left of object
* Read-Only:  use move() to change.
* @helpid 3983
*/

        function get x():Number
        {
                return _x;
        }
/**
* y = top of object
* Read-Only:  use move() to change.
* @helpid 3984
*/

        function get y():Number
        {
                return _y;
        }
Imaginez ensuite que le code ait été le suivant :/**
* width of object
* Read-Only:  use setSize() to change.
* @helpid 3982
*/

        function getWidth():Number
        {
                return _width;
        }
/**
* width of object
* Read-Only:  use setSize() to change.
* @helpid 3982
*/

        function get width():Number
        {
                return getWidth();
        }
// etc ... pour les autres propriétés

J'aurais alors pu créer une interface listant les méthodes setWidth(), setHeight(), etc... et ainsi utiliser mes classes "home-made" ou des composants. Les interfaces n'ont vraisemblablement pas été une préoccupation principale de Macromedia. D'ailleurs, on n'en trouve aucune dans les classes fournies avec Flash ! :o

19 août 2004

SuperColor 0.3 : le contraste en plus

On vient de me faire remarquer que la dernière version du color_toolkit de Robert PENNER permet également de gérer le contraste d'un clip. Je me suis donc empressé de rajouter ces fonctionnalités à ma classe SuperColor qui passe pour l'occasion en version 0.3.

J'en ai également profité pour modifier le nom du package : en effet, avec un nom de domaine comme www.lalex.com, il est plus logique de garder com.lalex en nom de package ... même si le coté commercial ne me plait pas vraiment ! :o

/**
 * Super Color
 * extended Color class
 *
 * -----------------------------------
 *
 * Based on <a href="http://www.robertpenner.com/">Robert PENNER</a> Color Toolkit v1.3
 *
 * Properties
 *
 *   _brightness
 *   _brightOffset
 *   _contrast
 *   _negative
 *
 *   _rgb
 *   _rgbStr
 *   _red
 *   _green
 *   _blue
 *
 *   _redPercent
 *   _greenPercent
 *   _bluePercent
 *
 *   _redOffset
 *   _greenOffset
 *   _blueOffset
 *
 * Methods
 *
 *   getTarget()
 *
 *   setRGBStr()
 *   getRGBStr()
 *   setRGB2()
 *   getRGB2()
 *   reset()
 *
 *   setBrightness()
 *   getBrightness()
 *   setBrightOffset()
 *   getBrightOffset()
 *       setContrast()
 *   getContrast()
 *
 *   getNegative()
 *   setNegative()
 *   invert()
 *
 *   setTint()
 *   getTint()
 *   setTint2()
 *   getTint2()
 *   setTintOffset()
 *   getTintOffset()
 *
 *   setRed()
 *   getRed()
 *   setGreen()
 *   getGreen()
 *   setBlue()
 *   getBlue()
 *
 *   setRedPercent()
 *   getRedPercent()
 *   setGreenPercent()
 *   getGreenPercent()
 *   setBluePercent()
 *   getBluePercent()
 *
 *   setRedOffset()
 *   getRedOffset()
 *   setGreenOffset()
 *   getGreenOffset()
 *   setBlueOffset()
 *   getBlueOffset()
 *
 * @version 0.3
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 19/08/2004
 */

class com.lalex.movieclip.SuperColor extends Color {
        private var _targetMC:MovieClip;
        /**
         * Constructor
         * @param clip Clip to apply the color
         */

        function SuperColor(clip:MovieClip) {
                super(clip);
                _targetMC = clip;
        }
        /**
         * Returns affected clip
         * @return Targeted MovieClip
         */

        public function getTarget():MovieClip {
                return _targetMC;
        }
                // ----------o RGB


        /**
         * Set an RGB value from an hexadecimal string
         * @param hexStr Hexadecimal value string
         */

        public function setRGBStr(hexStr:String) {
                // grab the last six characters of the string
                hexStr = hexStr.substr (-6, 6);
                setRGB (parseInt (hexStr, 16));
        }
        /**
         * Get the RGB value as a string
         * @return Hexadecimal value string
         */

        public function getRGBStr():String {
                var hexStr:String = getRGB().toString(16);
                // fill in zeroes as needed
                var toFill:Number = 6 - hexStr.length;
                while (toFill--) hexStr = "0" + hexStr;
                return hexStr.toUpperCase();
        }
        /**
         * Set red, green, and blue with normal numbers
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setRGB2(r:Number, g:Number, b:Number) {
                setRGB (r << 16 | g << 8 | b);
        } // Branden Hall - <a href="http://www.figleaf.com">www.figleaf.com</a>
        /**
         * @return Object with r, g, and b properties
         */

        public function getRGB2():Object {
                var t = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }
        /**
         * Reset the color object to normal
         */

        public function reset() : Void {
                setTransform ({ra:100, ga:100, ba:100, rb:0, gb:0, bb:0});
        }


                // ----------o Brightness


        /**     
         * Brighten just like Property Inspector of MovieClip
         * @param val Brightness between -100 and 100
         */

        public function setBrightness(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        ra = ga = ba =  100 - Math.abs(val);
                        rb = gb = bb = (val > 0) ? val * (256/100) : 0;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightness
         * @see setBrightness
         */

        public function getBrightness():Number {
                var trans:Object = getTransform();
                with (trans) {
                        return rb ? 100 - ra : ra - 100;
                }
        }
        /**
         * Set brightness offset
         * @param val Offset between -255 and 255
         */

        public function setBrightOffset(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = gb = bb = val;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightOffset
         * @see setBrightOffset
         */

        public function getBrightOffset():Number {
                return getTransform().rb;
        }


                // ----------o Contrast
        /**
         * Set contrast
         * @param val Percent between -100 and 100
         */

        public function setContrast(val:Number) {
                var trans:Object = {};
                trans.ra = trans.ga = trans.ba = val;
                trans.rb = trans.gb = trans.bb = 128 - (128/100 * val);
                setTransform(trans);
        }
        /**
         * @return Contrast set with setContrast
         * @see setContrast
         */

        public function getContrast():Number {
            return getTransform().ra;
        }
                // ----------o Negative and invert

        /**
         * Produce a negative image of the normal appearance
         * @param percent Between 0 and 100
         */

        public function setNegative(percent:Number) {
            var t:Object = {};
            t.ra = t.ga = t.ba = 100 - 2 * percent;
            t.rb = t.gb = t.bb = percent * (255/100);
            setTransform (t);
        }
        /**
         * @return Negative percentage
         * @see setNegative
         */

        public function getNegative():Number {
            return getTransform().rb * (100/255);
        }
        /**
         * Invert the current color values
         */

        public function invert() {
                var trans:Object = getTransform();
                with (trans) {
                        ra = -ra;
                        ga = -ga;
                        ba = -ba;
                        rb = 255 - rb;
                        gb = 255 - gb;
                        bb = 255 - bb;
                }
                setTransform (trans);
        }


                // ----------o Tint


        /**
         * Tint with a color just like Property Inspector
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         * @param percent Between 0 and 100
         */

        public function setTint(r:Number, g:Number, b:Number, percent:Number) {
                var ratio = percent / 100;
                var trans = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return tint object containing r, g, b, and percent properties
         * @see setTint
         */

        public function getTint():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.r = trans.rb * ratio;
                tint.g = trans.gb * ratio;
                tint.b = trans.bb * ratio;
                return tint;
        }
        /**
         * tint with a color - alternate approach
         * @param rgb Color number between 0 and 0xFFFFFF
         * @param percent Between 0 and 100
         */

        public function setTint2(rgb:Number, percent:Number) {
                var r:Number = (rgb >> 16) ;
                var g:Number = (rgb >> <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8)" class="smiley" /> & 0xFF;
                var b:Number = rgb & 0xFF;
                var ratio:Number = percent / 100;
                var trans:Object = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return a tint object containing rgb (a 0xFFFFFF number) and percent properties
         * @see setTint2
         */

        public function getTint2():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.rgb = (trans.rb*ratio)<<16 | (trans.gb*ratio)<<8 | trans.bb*ratio;
                return tint;
        }


                // ----------o Tint offset


        /**
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setTintOffset(r:Number, g:Number, b:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = r;
                        gb = g;
                        bb = b;
                }
                setTransform (trans);
        }
        /**
         * @return Object containing r, g, b properties
         * @see setTintOffset
         */

        public function getTintOffset():Object {
                var t:Object = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }


                // ----------o Color values

        /**
         * Set red value
         * @param amount Between 0 and 255
         */

        public function setRed(amount:Number) {
                var t = getTransform();
                setRGB (amount << 16 | t.gb << 8 | t.bb);
        }
        /**
         * Get red value
         * @return Value between 0 and 255
         * @see setRed
         */

        public function getRed():Number {
                return getTransform().rb;
        }
        /**
         * Set green value
         * @param amount Between 0 and 255
         */

        public function setGreen(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | amount << 8 | t.bb);
        }
        /**
         * Get green value
         * @return Value between 0 and 255
         * @see setGreen
         */

        public function getGreen():Number {
                return getTransform().gb;
        }
        /**
         * Set blue value
         * @param amount Between 0 and 255
         */

        public function setBlue(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | t.gb << 8 | amount);
        }
        /**
         * Get blue value
         * @return Value between 0 and 255
         * @see setBlue
         */

        public function getBlue():Number {
                return getTransform().bb;
        }


                // ----------o Color percentages

        /**
         * Set red percentage
         * @param percent Between -100 and 100
         */

        public function setRedPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ra = percent;
                setTransform (trans);
        }
        /**
         * Get red percentage
         * @return Value between -100 and 100
         * @see setRedPercent
         */

        public function getRedPercent():Number {
                return getTransform().ra;
        }
        /**
         * Set green percentage
         * @param percent Between -100 and 100
         */

        public function setGreenPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ga = percent;
                setTransform (trans);
        }
        /**
         * Get green percentage
         * @return Value between -100 and 100
         * @see setGreenPercent
         */

        public function getGreenPercent():Number {
                return getTransform().ga;
        }
        /**
         * Set blue percentage
         * @param percent Between -100 and 100
         */

        public function setBluePercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ba = percent;
                setTransform (trans);
        }
        /**
         * Get blue percentage
         * @return Value between -100 and 100
         * @see setBluePercent
         */

        public function getBluePercent():Number {
                return getTransform().ba;
        }


                // ----------o Color offsets

        /**
         * Set red offset
         * @param offset Between -255 and 255
         */

        public function setRedOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.rb = offset;
                setTransform (trans);
        }
        /**
         * Get red offset
         * @return Value between -255 and 255
         * @see setRedOffset
         */

        public function getRedOffset():Number {
                return getTransform().rb;
        }
        /**
         * Set green offset
         * @param offset Between -255 and 255
         */

        public function setGreenOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.gb = offset;
                setTransform (trans);
        }
        /**
         * Get green offset
         * @return Value between -255 and 255
         * @see setGreenOffset
         */

        public function getGreenOffset():Number {
                return getTransform().gb;
        }
        /**
         * Set blue offset
         * @param offset Between -255 and 255
         */

        public function setBlueOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.bb = offset;
                setTransform (trans);
        }
        /**
         * Get blue offset
         * @return Value between -255 and 255
         * @see setBlueOffset
         */

        public function getBlueOffset():Number {
                return getTransform().bb;
        }

                // ----------o Getter/Setter

        /**
         * RGB value
         * @type Number
         */

        function set _rgb(val:Number) {
                setRGB(val);
        }
        function get _rgb():Number {
                return getRGB();
        }
        /**
         * RGB value
         * @type String
         */

        function set _rgbStr(val:String) {
                setRGBStr(val);
        }
        function get _rgbStr():String {
                return getRGBStr();
        }
        /**
         * Brightness value
         * @type Number
         */

        function set _brightness(b:Number) {
                setBrightness(b);
        }
        function get _brightness():Number {
                return getBrightness();
        }
        /**
         * Brightness offset
         * @type Number
         */

        function set _brightOffset (b:Number) {
                setBrightOffset(b);
        }
        public function get _brightOffset():Number {
                return getBrightOffset();
        }
        /**
         * Contrast
         * @type Number
         */

        function set _contrast (c:Number) {
                setContrast(c);
        }
        public function get _contrast():Number {
                return getContrast();
        }
        /**
         * Negative percentage
         * @type Number
         */

        public function set _negative(p:Number) {
                setNegative(p);
        }
        public function get _negative():Number {
                return getNegative();
        }
        /**
         * Red value
         * @type Number
         */

        public function set _red(v:Number) {
                setRed(v);
        }
        public function get _red():Number {
                return getRed();
        }
        /**
         * Green value
         * @type Number
         */

        public function set _green(v:Number) {
                setGreen(v);
        }
        public function get _green():Number {
                return getGreen();
        }
        /**
         * Blue value
         * @type Number
         */

        public function set _blue(v:Number) {
                setBlue(v);
        }
        public function get _blue():Number {
                return getBlue();
        }
        /**
         * Red percentage
         * @type Number
         */

        public function set _redPercent(v:Number) {
                setRedPercent(v);
        }
        public function get _redPercent():Number {
                return getRedPercent();
        }
        /**
         * Green percentage
         * @type Number
         */

        public function set _greenPercent(v:Number) {
                setGreenPercent(v);
        }
        public function get _greenPercent():Number {
                return getGreenPercent();
        }
        /**
         * Blue percentage
         * @type Number
         */

        public function set _bluePercent(v:Number) {
                setBluePercent(v);
        }
        public function get _bluePercent():Number {
                return getBluePercent();
        }
        /**
         * Red offset
         * @type Number
         */

        public function set _redOffset(v:Number) {
                setRedOffset(v);
        }
        public function get _redOffset():Number {
                return getRedOffset();
        }
        /**
         * Green offset
         * @type Number
         */

        public function set _greenOffset(v:Number) {
                setGreenOffset(v);
        }
        public function get _greenOffset():Number {
                return getGreenOffset();
        }
        /**
         * Blue offset
         * @type Number
         */

        public function set _blueOffset(v:Number) {
                setBlueOffset(v);
        }
        public function get _blueOffset():Number {
                return getBlueOffset();
        }
}

::Télécharger SuperColor.zip::

5 août 2004

Cloner : le clonage non-thérapeutique d'objets

Suite à la discussion sur la "deep copy" et la "shallow copy" qu'a introduit zwetan, je me suis attaqué à une ébauche de classe permettant de faire du clonage. Je l'ai testée sur quelques types de bases et quelques classe maisons, et pour l'instant seules les classes MovieClip et Color m'ont posé un problème ... ce qui est logique à priori ! :P

Le principe court-circuite l'instanciation avec new, et se sert des proriétés __proto__ et __constructor__ pour créer le clone :/**
 * Cloner class
 *
 * @version 0.1
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 05/08/2004
 */

class net.lalex.core.Cloner {
        /**
         * Create a clone of an object
         * Works for all datatypes, except MovieClip and Color
         *
         * @param o Object to get a clone
         * @param deep Allow to disable deep copy
         */

        public static function clone(o:Object, deep:Boolean):Object {
                // Set the "deep" param to true by default
                if (deep == undefined) {
                        deep = true;
                }
                // Can't clone MovieClip instances
                if (o instanceof MovieClip || o instanceof Color) {
                        trace("Cloner error : can't clone MovieClip or Color instances !");
                // If object, create a clone and return it
                } else if (typeof o == "object") {
                        // Return objet
                        var ret:Object = {};
                        // Object's constructor
                        var cst:Function = o["__constructor__"];
                        // Assign original prototype to clone
                        ret.__proto__ = o.__proto__;
                        // Objects created with {} have no __constructor__ property
                        if (cst != undefined) {
                                ret["__constructor__"] = cst;
                                cst.apply(ret);
                        }
                        // Copy all properties from the original to the clone
                        // Only "object" properties need to be cloned for deep copy
                        // Also works for arrays ... <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8-)" class="smiley" />
                        for (var curProp:String in o) {
                                ret[curProp] = typeof o[curProp] == "object" && deep ? Cloner.clone(o[curProp]) : o[curProp];
                        }
                        return ret;
                }
                // If simple type, returns it
                return o;
        }
}
// Utilisation
import net.lalex.core.Cloner;
var monTableau:Array = [1,2,3,4,5];
// monClone contient un clone de monTableau
var monClone = Cloner.clone(monTableau);
Au début, pour créer la "base" du clone, j'utilisais l'instruction suivante :var ret:Object = new o["__constructor__"]();Cela ne marchait pour les objets créés avec les accolades, étant donné que ces objets ne possèdent pas de propriété __constructor__ comme Timothée Groleau nous le signale dans les commentaires de mon article sur le prototypes ... 8)

Wala, je suis bien évidemment ouvert à tout feedback, que ce soit pour signaler un disfonctionnement sur certaines classes ou types de données, ou pour suggérer une amélioration de cette implémentation du clonage ... ^^

SuperColor : récupérer la cible (et correction de bug)

Un des aspects pas pratiques de la classe Color est l'impossibilité de que l'on a de récupérer le clip ciblé par cet objet. J'ai donc un peu modifié ma classe SuperColor afin qu'elle contienne une méthode qui renvoie une référence au clip affecté par les changements de couleurs :
/**
 * Super Color
 * extended Color class
 *
 * -----------------------------------
 *
 * Based on <a href="http://www.robertpenner.com/">Robert PENNER</a> Color Toolkit v1.2
 *
 * Properties
 *
 *   _brightness
 *   _brightOffset
 *   _negative
 *
 *   _rgb
 *   _rgbStr
 *   _red
 *   _green
 *   _blue
 *
 *   _redPercent
 *   _greenPercent
 *   _bluePercent
 *
 *   _redOffset
 *   _greenOffset
 *   _blueOffset
 *
 * Methods
 *
 *   getTarget()
 *
 *   setRGBStr()
 *   getRGBStr()
 *   setRGB2()
 *   getRGB2()
 *   reset()
 *
 *   setBrightness()
 *   getBrightness()
 *   setBrightOffset()
 *   getBrightOffset()
 *
 *   setTint()
 *   getTint()
 *   setTint2()
 *   getTint2()
 *   setTintOffset()
 *   getTintOffset()
 *
 *   getNegative()
 *   setNegative()
 *   invert()
 *
 *   setRed()
 *   getRed()
 *   setGreen()
 *   getGreen()
 *   setBlue()
 *   getBlue()
 *
 *   setRedPercent()
 *   getRedPercent()
 *   setGreenPercent()
 *   getGreenPercent()
 *   setBluePercent()
 *   getBluePercent()
 *
 *   setRedOffset()
 *   getRedOffset()
 *   setGreenOffset()
 *   getGreenOffset()
 *   setBlueOffset()
 *   getBlueOffset()
 *
 * @version 0.2
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 05/08/2004
 */

class com.lalex.movieclip.SuperColor extends Color {
        private var _targetMC:MovieClip;
        /**
         * Constructor
         * @param clip Clip to apply the color
         */

        function SuperColor(clip:MovieClip) {
                super(clip);
                _targetMC = clip;
        }
        /**
         * Returns affected clip
         * @return Targeted MovieClip
         */

        public function getTarget():MovieClip {
                return _targetMC;
        }
                // ----------o RGB


        /**
         * Set an RGB value from an hexadecimal string
         * @param hexStr Hexadecimal value string
         */

        public function setRGBStr(hexStr:String) {
                // grab the last six characters of the string
                hexStr = hexStr.substr (-6, 6);
                setRGB (parseInt (hexStr, 16));
        }
        /**
         * Get the RGB value as a string
         * @return Hexadecimal value string
         */

        public function getRGBStr():String {
                var hexStr:String = getRGB().toString(16);
                // fill in zeroes as needed
                var toFill:Number = 6 - hexStr.length;
                while (toFill--) hexStr = "0" + hexStr;
                return hexStr.toUpperCase();
        }
        /**
         * Set red, green, and blue with normal numbers
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setRGB2(r:Number, g:Number, b:Number) {
                setRGB (r << 16 | g << 8 | b);
        } // Branden Hall - <a href="http://www.figleaf.com">www.figleaf.com</a>
        /**
         * @return Object with r, g, and b properties
         */

        public function getRGB2():Object {
                var t = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }
        /**
         * Reset the color object to normal
         */

        public function reset() : Void {
                setTransform ({ra:100, ga:100, ba:100, rb:0, gb:0, bb:0});
        }


                // ----------o Brightness


        /**     
         * Brighten just like Property Inspector of MovieClip
         * @param val Brightness between -100 and 100
         */

        function setBrightness(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        ra = ga = ba =  100 - Math.abs(val);
                        rb = gb = bb = (val > 0) ? val * (256/100) : 0;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightness
         * @see setBrightness
         */

        function getBrightness():Number {
                var trans:Object = getTransform();
                with (trans) {
                        return rb ? 100 - ra : ra - 100;
                }
        }
        /**
         * Set brightness offset
         * @param val Offset between -255 and 255
         */

        function setBrightOffset(val:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = gb = bb = val;
                }
                setTransform(trans);
        }
        /**
         * @return Brightness set with setBrightOffset
         * @see setBrightOffset
         */

        function getBrightOffset():Number {
                return getTransform().rb;
        }


                // ----------o Tint


        /**
         * Tint with a color just like Property Inspector
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         * @param percent Between 0 and 100
         */

        public function setTint(r:Number, g:Number, b:Number, percent:Number) {
                var ratio = percent / 100;
                var trans = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return tint object containing r, g, b, and percent properties
         * @see setTint
         */

        public function getTint():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.r = trans.rb * ratio;
                tint.g = trans.gb * ratio;
                tint.b = trans.bb * ratio;
                return tint;
        }
        /**
         * tint with a color - alternate approach
         * @param rgb Color number between 0 and 0xFFFFFF
         * @param percent Between 0 and 100
         */

        public function setTint2(rgb:Number, percent:Number) {
                var r:Number = (rgb >> 16) ;
                var g:Number = (rgb >> <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8)" class="smiley" /> & 0xFF;
                var b:Number = rgb & 0xFF;
                var ratio:Number = percent / 100;
                var trans:Object = {rb:r*ratio, gb:g*ratio, bb:b*ratio};
                trans.ra = trans.ga = trans.ba = 100 - percent;
                setTransform (trans);
        }
        /**
         * @return a tint object containing rgb (a 0xFFFFFF number) and percent properties
         * @see setTint2
         */

        public function getTint2():Object {
                var trans:Object = getTransform();
                var tint:Object = {percent: 100 - trans.ra};
                var ratio:Number = 100 / tint.percent;
                tint.rgb = (trans.rb*ratio)<<16 | (trans.gb*ratio)<<8 | trans.bb*ratio;
                return tint;
        }


                // ----------o Tint offset


        /**
         * @param r Red value between 0 and 255
         * @param g Greeb value between 0 and 255
         * @param b Blue value between 0 and 255
         */

        public function setTintOffset(r:Number, g:Number, b:Number) {
                var trans:Object = getTransform();
                with (trans) {
                        rb = r;
                        gb = g;
                        bb = b;
                }
                setTransform (trans);
        }
        /**
         * @return Object containing r, g, b properties
         * @see setTintOffset
         */

        public function getTintOffset():Object {
                var t:Object = getTransform();
                return {r:t.rb, g:t.gb, b:t.bb};
        }


                // ----------o Negative and invert

        /**
         * Produce a negative image of the normal appearance
         * @param percent Between 0 and 100
         */

        public function setNegative(percent:Number) {
            var t:Object = {};
            t.ra = t.ga = t.ba = 100 - 2 * percent;
            t.rb = t.gb = t.bb = percent * (255/100);
            setTransform (t);
        }
        /**
         * @return Negative percentage
         * @see setNegative
         */

        public function getNegative():Number {
            return getTransform().rb * (100/255);
        }
        /**
         * Invert the current color values
         */

        public function invert() {
                var trans:Object = getTransform();
                with (trans) {
                        ra = -ra;
                        ga = -ga;
                        ba = -ba;
                        rb = 255 - rb;
                        gb = 255 - gb;
                        bb = 255 - bb;
                }
                setTransform (trans);
        }


                // ----------o Color values

        /**
         * Set red value
         * @param amount Between 0 and 255
         */

        public function setRed(amount:Number) {
                var t = getTransform();
                setRGB (amount << 16 | t.gb << 8 | t.bb);
        }
        /**
         * Get red value
         * @return Value between 0 and 255
         * @see setRed
         */

        public function getRed():Number {
                return getTransform().rb;
        }
        /**
         * Set green value
         * @param amount Between 0 and 255
         */

        public function setGreen(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | amount << 8 | t.bb);
        }
        /**
         * Get green value
         * @return Value between 0 and 255
         * @see setGreen
         */

        public function getGreen():Number {
                return getTransform().gb;
        }
        /**
         * Set blue value
         * @param amount Between 0 and 255
         */

        public function setBlue(amount:Number) {
                var t = getTransform();
                setRGB (t.rb << 16 | t.gb << 8 | amount);
        }
        /**
         * Get blue value
         * @return Value between 0 and 255
         * @see setBlue
         */

        public function getBlue():Number {
                return getTransform().bb;
        }


                // ----------o Color percentages

        /**
         * Set red percentage
         * @param percent Between -100 and 100
         */

        public function setRedPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ra = percent;
                setTransform (trans);
        }
        /**
         * Get red percentage
         * @return Value between -100 and 100
         * @see setRedPercent
         */

        public function getRedPercent():Number {
                return getTransform().ra;
        }
        /**
         * Set green percentage
         * @param percent Between -100 and 100
         */

        public function setGreenPercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ga = percent;
                setTransform (trans);
        }
        /**
         * Get green percentage
         * @return Value between -100 and 100
         * @see setGreenPercent
         */

        public function getGreenPercent():Number {
                return getTransform().ga;
        }
        /**
         * Set blue percentage
         * @param percent Between -100 and 100
         */

        public function setBluePercent(percent:Number) {
                var trans:Object = getTransform();
                trans.ba = percent;
                setTransform (trans);
        }
        /**
         * Get blue percentage
         * @return Value between -100 and 100
         * @see setBluePercent
         */

        public function getBluePercent():Number {
                return getTransform().ba;
        }


                // ----------o Color offsets

        /**
         * Set red offset
         * @param offset Between -255 and 255
         */

        public function setRedOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.rb = offset;
                setTransform (trans);
        }
        /**
         * Get red offset
         * @return Value between -255 and 255
         * @see setRedOffset
         */

        public function getRedOffset():Number {
                return getTransform().rb;
        }
        /**
         * Set green offset
         * @param offset Between -255 and 255
         */

        public function setGreenOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.gb = offset;
                setTransform (trans);
        }
        /**
         * Get green offset
         * @return Value between -255 and 255
         * @see setGreenOffset
         */

        public function getGreenOffset():Number {
                return getTransform().gb;
        }
        /**
         * Set blue offset
         * @param offset Between -255 and 255
         */

        public function setBlueOffset(offset:Number) {
                var trans:Object = getTransform();
                trans.bb = offset;
                setTransform (trans);
        }
        /**
         * Get blue offset
         * @return Value between -255 and 255
         * @see setBlueOffset
         */

        public function getBlueOffset():Number {
                return getTransform().bb;
        }

                // ----------o Getter/Setter

        /**
         * RGB value
         * @type Number
         */

        function set _rgb(val:Number) {
                setRGB(val);
        }
        function get _rgb():Number {
                return getRGB();
        }
        /**
         * RGB value
         * @type String
         */

        function set _rgbStr(val:String) {
                setRGBStr(val);
        }
        function get _rgbStr():String {
                return getRGBStr();
        }
        /**
         * Brightness value
         * @type Number
         */

        function set _brightness(b:Number) {
                setBrightness(b);
        }
        function get _brightness():Number {
                return getBrightness();
        }
        /**
         * Brightness offset
         * @type Number
         */

        function set _brightOffset (b:Number) {
                setBrightOffset(b);
        }
        public function get _brightOffset():Number {
                return getBrightOffset();
        }
        /**
         * Negative percentage
         * @type Number
         */

        public function set _negative(p:Number) {
                setNegative(p);
        }
        public function get _negative():Number {
                return getNegative();
        }
        /**
         * Red value
         * @type Number
         */

        public function set _red(v:Number) {
                setRed(v);
        }
        public function get _red():Number {
                return getRed();
        }
        /**
         * Green value
         * @type Number
         */

        public function set _green(v:Number) {
                setGreen(v);
        }
        public function get _green():Number {
                return getGreen();
        }
        /**
         * Blue value
         * @type Number
         */

        public function set _blue(v:Number) {
                setBlue(v);
        }
        public function get _blue():Number {
                return getBlue();
        }
        /**
         * Red percentage
         * @type Number
         */

        public function set _redPercent(v:Number) {
                setRedPercent(v);
        }
        public function get _redPercent():Number {
                return getRedPercent();
        }
        /**
         * Green percentage
         * @type Number
         */

        public function set _greenPercent(v:Number) {
                setGreenPercent(v);
        }
        public function get _greenPercent():Number {
                return getGreenPercent();
        }
        /**
         * Blue percentage
         * @type Number
         */

        public function set _bluePercent(v:Number) {
                setBluePercent(v);
        }
        public function get _bluePercent():Number {
                return getBluePercent();
        }
        /**
         * Red offset
         * @type Number
         */

        public function set _redOffset(v:Number) {
                setRedOffset(v);
        }
        public function get _redOffset():Number {
                return getRedOffset();
        }
        /**
         * Green offset
         * @type Number
         */

        public function set _greenOffset(v:Number) {
                setGreenOffset(v);
        }
        public function get _greenOffset():Number {
                return getGreenOffset();
        }
        /**
         * Blue offset
         * @type Number
         */

        public function set _blueOffset(v:Number) {
                setBlueOffset(v);
        }
        public function get _blueOffset():Number {
                return getBlueOffset();
        }
}

Vous pouvez donc télécharger cette mise à jour si le coeur vous en dit, qui en plus corrige un ch'tit bug qui s'était glissé sur la propriété _blueOffset ! ;) Cette classe inaugure aussi le fait que je n'utiliserais plus le this systématique dans mes classes. :)

::Télécharger SuperColor.zip::

30 juillet 2004

Composition et __resolve : simplifiez vous la composition

En train de coder un gestionnaire de positionnement pour des clips sur la scène, je me vois confronté au problême bien connu du centrage des clips. En effet, les coordonnées _x et _y ne sont pas forcément celles du coin supérieur gauche du clip. Le canadien Stéphan Guénette s'est déjà penché sur cet épineux problême, et propose une solution bien pratique sur son blog ... 8)

Seulement, cette solution me pose quelques problêmes, tout d'abord parce qu'il s'agit d'une manipulation de prototypes, bien peu conseillée en AS2, et ensuite parce qu'à chaque accés à une propriété de positionnement, il s'effectue un appel à la méthode getBounds() ... Je ne me suis pas encore penché sur le second problême, mais concernant le premier, j'ai trouvé une petite astuce bien pratique pour accéder facilement aux propriétés d'un MovieClip membre d'une classe, et ce sans utiliser l'héritage.

Il s'agit en fait d'utiliser la méthode non documentée __resolve(), dont voici mon utilisation :dynamic class net.lalex.movieclip.SuperClip {
        // Clip ciblé
        private var _targetMC:MovieClip;
        // Constructeur
        public function SuperClip(mc:MovieClip) {
                _targetMC = mc;
        }
        // Redirige les requêtes non existantes vers le clip
        // contenu dans la propriété _targetMC
        public function __resolve(p:String) {
                // Si c'est une méthode, on l'applique au clip lui-même
                if (typeof _targetMC[p] == "function") {
                        return function() {
                                return _targetMC[p].apply(_targetMC, arguments);
                        }
                // Sinon, on retourne la propriété
                } else {
                        return _targetMC[p];
                }
        }
}
Comme vous le constatez, __resolve() permet d'intercepter les appels à des membres d'une classe qui n'existent pas, et d'en faire un peu ce que l'on veut. Le seul problême est que l'on ne peut pas accéder en écriture aux propriétés du clip. Comme il s'agit là d'une classe native, il faut donc ne pas oublier de créer les accesseurs pour modifier les propriétés (setX, setY, setRotation, etc...) ... mais si l'on s'en tient à l'utilisation des accesseurs pour la manipulation de propriétés lors du développement de classes "maisons", ce qui est bien plus conseillé en POO, cela fonctionne parfaitement ! :D Voici un exemple d'utilisation :import net.lalex.movieclip.SuperClip;
// Création de l'instance de SuperClip
var monClip:SuperClip = new SuperClip(_root.monCarre);
// Accés à une méthode du clip contenu dans monClip
// donc, une méthode de _root.monCarre
// Ici, il s'agit de mettre monCarre au dessus des tous les autres
monClip.swapDepths(monClip._parent.getNextHighestDepth());

Evidemment, ca tient tout autant de la bidouille que la solution avec les prototypes, mais je préfère malgré tout avoir une "vraie" classe ! :)

28 juillet 2004

Interfaces et héritage : compilo : 1 - LAlex : 0

Un aspect singulier des interfaces face à l'héritage est que le compilateur de Flash MX 2004 ne se contente pas de vérifier si une classe implémente l'interface, mais il vérifie aussi que les méthodes devant être implémentées le sont ...

Pour etre plus clair, j'ai découvert cela en faisant des tests en réimplémentant ma bibliothèque sur les matrices. Ma classe Matrix hérite d'un classe Grid. Cette classe Grid possède une méthode clone(). J'ai voulu créer une interface IClonable pour m'assurer qu'une matrice puisse être clonée, et ayant oublié de créer la méthode, je me suis rendu compte que le compilateur laissait passer malgré tout ... :o En fait, cette méthode existe déjà dans Grid, alors que Grid n'implémente pas IClonable ! :)

Voici le code que le compilateur laisse passer :/*
 * interface IClonable
 */

interface net.lalex.interfaces.IClonable {
        public function clone():Object;
}
/*
 * classe Grid
 */

class net.lalex.data.Grid extends Array {
        // Plein de code ...
        // Retourner un clone de la grille
        public function clone():Object {
                var ret:Grid = new Grid(_gridWidth, _gridHeight);
                ret.copyFrom(this);
                return ret;
        }
        // Copy les éléments d'une autre grille
        public function copyFrom(g:Grid):Void {
                // Code
        }
}
/*
 * classe Matrix
 */

class net.lalex.maths.Matrix extends net.lalex.data.Grid implements net.lalex.interfaces.IClonable {
        // Aucune méthode 'clone()'
}

Normal allez-vous me dire ? Oui, en effet. :D Mais je ne pensais pas que le compilateur allait si loin. Ca ne m'arrange pas en fait, car j'aurais bien voulu forcer la classe Matrix à surcharger la méthode clone() ... :(

28 juin 2004

Classe MultiLoader : impossible d'interrompre un chargement de fichier distant

En revenant un peu au développement AS, je me rends compte du nombre de fonctionnalités que j'ai développé plusieurs fois, et travaille donc actuellement à me créer un ensemble de classes utilitaires qui m'éviteront de perdre du temps sur des développements répétitif, comme le fait tout programmeur un minimum organisé ! :$ Par manque de temps et de projets nécessitant un investissement suffisant, je ne l'ai pas fait avant, et je m'y mets donc sérieusement ! 8)

Je travaille actuellement sur une classe de preload, un peu à la manière de MovieClipLoader, mais me permettant de charger un ensemble de fichiers de différents types les uns aprés les autres avec les différents évènements correspondant. Ces fichiers pourraient être des SWF externes, des sons, des fichiers XML, voire des vidéos (si c'est faisable, je n'ai encore jamais eu l'utilité de la vidéo dans Flash). Le fait de ne pas les charger simultanément vient tout simplement d'une préoccupation d'optimisation de la bande passante et de priorisation des téléchargements.
Il s'agit donc pour moi de stocker l'ensemble des fichiers à charger, et de lancer une méthode de ma classe. Le problème est que j'aurais aimé pouvoir commencer par récupérer le cumul des tailles de mes fichiers. J'ai d'abord pensé à créer des LoadVars sur chaque fichier, et à les interrompre une fois la taille récupérée, sans charger la totalité du fichier.

Or il semble que la suppression d'un objet LoadVars (ou XML, et je soupconne les autres types "loadables") ne met pas fin au téléchargement. En effet, la méthode onLoad se lance malgré le fait que j'ai utilisé delete sur l'objet de type LoadVars qui m'a servi à récupérer la taille du fichier ... :(this.tst = new LoadVars();
this.tst.onLoad = function() {
        trace("OK");
}
this.tst.load("<a href="http://www.lalex.com/monfichier.jpg">http://www.lalex.com/monfichier.jpg</a>");
this.onEnterFrame = function() {
        var tt = this.tst.getBytesTotal();
        trace(tt);
        if (tt != undefined) {
                delete this.tst;
                delete this.onEnterFrame;
        }
}

La sortie donne undefined
undefined
undefined
undefined
28449
OK <= aprés quelques secondes

Aprés un parcours des archives de FlashCoders, il semble qu'aucune solution n'a été trouvée. :( Apparemment, Flash crée automatiquement une référence vers l'objet, qui devient une référence anonyme si on le supprime, et empêche donc le garbage collector (ou ramasse-miettes en français :P) de faire son office ...

Si jamais entre temps, quelqu'un avait trouvé une solution à cet épineux problême, je serais ravi de l'apprendre ! :=)

Pour revenir à ma classe, je prévois également, une fois la solution "à la queue-leu-leu" implémentée, de faire un système de pools de téléchargement, qui permettrait de télécharger un nombre défini de fichiers simultanément. Par exemple, on pourrait ainsi décider de ne pas charger plus de 3 fichiers à la fois. Ainsi, 3 fichiers se téléchargeraient, et le premier à avoir fini laisserait sa place au suivant, etc... Je prévois aussi de faire un système de téléchargement par catégorie (d'abord le XML, puis les clips, puis les sons, etc...)

Je mettrai bientôt la classe à disposition ici même ... ;)

24 juin 2004

SuperColor : Gestion des effets de couleur en AS2







Pour les besoins d'un développement, j'ai eu besoin de faire une transition de type "fondu vers le blanc", qui donne un effet des plus esthétiques. Je me suis servi pour cela d'une classe qui hérite de color, et implémente des méthodes permettant de manipuler la luminosité, et d'autres paramètres liés à la couleur d'un clip. J'ai utilisé pour cela l'ensemble de fonctions développées par Robert PENNER, qu'il expose dans son livre, mais en AS1.

J'ai donc repris ses fonctions pour les intégrer dans une classe AS2, qui contient également des propriétés "virtuelles" créées avec des getter/setter. Cela permet d'utiliser facilement les Tween dessus, et donc de créer des interpolations de luminosité trés facilement. Cliquez sur l'image pour voir un petit exemple. 8)

Le code de l'animation que vous voyez est on ne peux plus simple :// Importation des classes
import com.lalex.movieclip.SuperColor;
import mx.transitions.Tween;
import mx.transitions.easing.Regular;
// Objet SuperColor
var myCol:SuperColor = new SuperColor(image);
// Objet Tween
var myTw:Tween = new Tween(myCol, "_brightOffset", Regular.easeInOut, 0, 255, 2, true);
myTw.stop();
// Evenements du Tween
var twEvent:Object = new Object();
myTw.addListener(twEvent);
var onMF = function(tw:Tween) {
        tw.yoyo();
        this.onMotionFinished = function(tw:Tween) {
                tw.yoyo();
                tw.stop();
                this.onMotionFinished = onMF;
        }
}
twEvent.onMotionFinished = onMF;
// Evenements sur l'image
image.onRelease = function() {
        myTw.start();
}
image.onRollOver = function() {
        click._visible = false;
}

::Télécharger SuperColor.zip::

22 avril 2004

Liens externes dans Flash : hack de MovieClip

Avec l'arrivée des standards dans la conception HTML, les habitudes de navigation des internautes changent peu à peu. Un cas trés spécial est celui de l'attribut target que l'on pouvait auparavant mettre dans un lien pour ouvrir ce même lien dans une nouvelle fenêtre.

Avec la supression de cet attribut au sein des standards XHTML, de nombreux webmasters (moi le premier :P) ont protesté contre le fait qu'ils ne pouvaient pas laisser une fenêtre de leur site ouverte et ainsi ne pouvaient plus controler la manière dont naviguent les internautes sur leur site. Personnellement, depuis que j'adopte autant que possible les standards, je trouve avec le recul que c'est plutôt à l'internaute de choisir ou il veut aller et surtout comment il veut y aller. Tous les navigateurs permettent maintenant d'ouvrir une nouvelle fenêtre en restant appuyé sur la touche SHIFT en même temps que l'on clique sur le lien. Cette habitude bien ancrée, il devient trés désagréable de se trouver confronté à une lien _blank alors qu'on n'en a pas envie ... :\

Afin de reproduire ce comportement dans mes animations Flash, je me sert d'un hack du prototype de MovieClip, normalement proscrit en AS2, mais qui me simplifie grandement la vie.

// On sauvegarde la méthode getURL d'origine
MovieClip.prototype.__getURL = MovieClip.prototype.getURL;
// On écrase getURL
MovieClip.prototype.getURL = function() {
        // On travaille sur une copie d'arguments
        var param = arguments.concat();
        // Si la touche SHIFT est appuyée
        // et qu'il ne s'agit pas d'un lien javascript ou asfunction
        // on met la cible à '_blank'
        if (Key.isDown(Key.SHIFT) && param[0].toLowerCase().indexOf("javascript:") < 0 && param[0].toLowerCase().indexOf("asfunction:") < 0) {
                param[1] = "_blank";
        }
        // On appelle l'ancienne méthode getURL
        MovieClip.prototype.__getURL.apply(this,param);
}

Ainsi, je ne spécifie plus la destination de mes liens, et quel que soit le getURL que je fais, il suffit d'appuyer sur SHIFT pour l'ouvrir dans une nouvelle fenêtre. Ce comportement est repris dans le menu des sites MediaBox. :)

A ce propos, vous aurez d'ailleurs peut-être remarqué que lors de l'affichage d'un texte HTML dans une zone de texte Flash, en cliquant droit sur un lien, un menu vous proposant d'ouvrir le lien dans une nouvelle fenêtre vous est proposé ! ;)

14 avril 2004

Economiser des lignes de code en ActionScript

Je corrige actuellement les soumissions de concurrents des Actionscript Awards, menés à Singapour. Une des contraintes étant le nombre de lignes de code (60 lignes max.), des développeurs ont déployé des trésors d'ingéniosité pour économiser des lignes d'instructions ! :) Ce qui complique évidemment la compréhension de ce qu'est sensé faire le code, et ainsi ne simplifie pas mon travail de correcteur ! :P

:!: L'ensemble de ces pratiques n'est évidemment pas à conseiller !!! :!: Etant donné que même si le code fonctionne quand-même, le lisibilité s'en trouve fortement réduite. Je ne les donne ici que par curiosité intellectuelle pour ceux qui voudraient se lancer dans ce type de contest.

Tout d'abord, il n'y a pas suffisamment de personnes qui savent que les commandes createEmptyMovieClip() et duplicateMovieClip() retournent une référence sur le clip crée (ou dupliqué). Il ets donc possible de récupérer cet objet nouvellement créé sur la même ligne. De même, l'argument objetInit peut se révéler trés utile dans cette course aux lignes :var mc = _root.monClipOriginal.duplicateMovieClip("monClone", _root.getNextHighestDepth(), {_x: 100, _y: 200, _alpha:50}); Ensuite, il existe l'éternel instruction ternaire qui remplace quasi-systématiquement le if. Bien évidemment, les if imbriqués deviennent des ? imbriqués ! :=)var myVar = (this._x > 10 ? 10 : 20);
// Equivalent de
var myVar;
if (this._x > 10) {
   myVar = 10;
} else {
   myVar = 20;
}
Penchons-nous maintenant sur l'opérateur d'affectation qu'est '='. En effet, une instrcution d'affectation retourne la valeur qui vient d'être affectée. Ainsi, il est possible d'affecter la même valeur à plusieurs valeurs d'un coup. monClip._xscale = monClip._yscale = 50; :!: Pour info, c'est pour cette raison que lorsqu'on crée un setter dans une classe, son utilisation fait appel au getter correspondant (plus d'infos chez petepx) Si on combine plusieurs astuces, on peut obtenir le code pour créer une ombre par exemple (en supposant que la profondeur en dessous du clip visé est vide)// Si, si, il s'agit d'une seule ligne de code !
(color = new Color(mc = monClip.duplicateMovieClip(monClip._name + "_shadow", monClip.getDepth()-1, {_x : monClip._x + 10, _y: monClip._y + 10, _alpha: 50}))).setRGB(0);
// Le clip d'ombre est dans mc
trace(mc);
Mais le truc qui m'a le plus surpris, parce que je n'y avais pas pensé une seule seconde, c'est l'utilisation de la virgule. En effet, la virgule est souvent utilisée conjointement à l'instruction var pour déclarer plusieurs variables d'un coup... mais l'on peut l'utiliser dans bien d'autres situations : // Utilisation "normale"
var maVar = 10, monAutreVar = 20;
// Utilisation détournée
(mc = _root.createEmptyMovieClip("newclip", 10)), mc._alpha=15;

Futé tout ca non ? ;) Si vous avez d'autres astuces du genre, ca me plairait bien de toutes les collecter, histoire de savoir jusqu'à quel point on peut être tordu à ce niveau la !!! :=)

9 avril 2004

Désactiver un formulaire avec Flash

Lors de la création du formulaire servant à rajouter un commentaire, je me suis retrouvé confronté au problème de la désactivation de celui-ci lorsque l'animation effectue certaines actions, comme inscrire une adresse dans les notification, insérer le commentaire, etc... Je suis donc allé fouiller dans le code source de MTGotoAndComment de Stéfane Funaro pour voir comment il faisait cela, et l'astuce mérite d'être connue. :)

Il s'agit en fait de baisser l'alpha du formulaire, et de mettre au dessus un clip transparent, de lui donner le comportement d'un bouton, et d'enlever le curseur en form de main. Je n'ai pas encore trouvé le moyen de créer ce "masque" en trouvant automatiquement les coordonnées. :( Voici un exemple de comment faire dans le cas d'un formulaire sous forme de clip :function initForm($frm:MovieClip, $x:Number, $y:Number, $w:Number, $h:Number):Void {
   // Creation du clip "masquant"
   var disab = $frm.createEmptyMovieClip("disableClip", $frm.getNextHighestDepth());
   with (disab) {
      // Dessin du clip avec un remplissage d'alpha 0
      beginFill(0,0);
      moveTo(0,0);
      lineTo(10,0);
      lineTo(10,10);
      lineTo(0,10);
      lineTo(0,0);
      // Coordonnées du formulaire dans le clip
      _x = $x;
      _y = $y;
      // Largeur du formulaire dans le clip
      _width = $w;
      _height = $h;
      // Création du bouton
      onRelease = function () {};
      useHandCursor = false;
      // Par défaut, le formulaire est activé
      _visible = false;
   }
   // Activer/désactiver le formulaire
   $frm.setEnabled = function($bool) {
      this.disableClip._visible = $bool;
      this._alpha = $bool ? 100 : 40;
   }
   // Récupérer l'état du formulaire
   $frm.getEnabled = function() {
      return this.disableClip._visible;
   }
   // "Switcher" entre les deux états du formulaire
   $frm.toggleEnabled = function() {
      this.setEnabled(!this.getEnabled());
   }
}

Vous avez ainsi à votre disposition les méthodes setEnabled, getEnabled et toggleEnabled pour votre clip contenant le formulaire qui contrôleront l'état du formulaire !!! 8)

5 avril 2004

Conditions multiples d'interruptions d'une instruction

Il arrive parfois que l'on ai besoin que plusieurs conditions soient remplies pour executer une instruction finale. Dans le cas où une des conditions n'est pas remplie, les tests suivants ne sont plus nécessaires, et il peut être important de ne pas les tester en terme d'optimisation. Quand ces conditions sont nombreuses, on peut se retrouver avec une grosse imbrication de if ... else, qui en plus de nuire terriblement à la lisisbilité du code, augmentent la complexité de celui-ci (la compléxité d'un code se calcule entre autre avec le nombre d'instructions if imbriquées.

Il est vrai qu'une simplification pourrait consister à utiliser des ET logiques (&&) dans un ordre défini, comme je l'explique dans ce post, mais si certaines conditions doivent être succédées par un traitement, ce système ne fonctionne plus.

Le problème s'est posé à moi pour la nouvelle version de mon moteur de blog, olrs de l'ajout d'un commentaire. En effet, si le nom est vide, je n'ai pas besoin de tester le contenu du commentaire. Si le commentaire est vide, je n'ai pas besoin de tester la valididté de l'email, etc... Si l'une de ces conditions est fausse, l'enregistrement du commentaire ne doit pas se faire. Cette astuce est valable pour tout langage, je vais donc l'illustrer ici en AS2.

Voici donc le code que l'on peut s'attendre à avoir :// Test du nom
if (!this.name.length) {
   trace("Le nom est obligatoire");
} else {
   // Test du commentaire
   if (!this.comment.length) {
      trace("Le commentaire est obligatoire");
   } else {
      // Test de l'adresse email
      if (!this.isValidEmail(this.email)) {
         trace("L'adresse email saisie n'est pas valide");
      } else {
         // Enregistrement du commentaire
         this.saveComment();
      }
   }
}
Evidemment, les moyens de simplifier le code sont nombreux : création d'une variable booleenne spécifiant si une erreur est survenue, stockage du message d'erreur pour n'avoir qu'une instruction trace(), etc... Mais si le code présenté ici est simple, ce n'est pas toujours le cas. Et quoi qu'il arrive, tester si une erreur est déja survenue rajoute un [i]if[/if] à chaque test de condition ... Une solution que j'ai trouvée consiste à utilise l'instruction do ... while conjugée avec break pour arrêter le traitement dés qu'une erreur est trouvée. Cela correspond en fait à un goto, que l'on rencontre dans certains langages:// Message d'erreur eventuel
var err = false;
// Première et dernière boucle
do {
   // Test du nom
   if (!this.name.length) {
      err = "Le nom est obligatoire";
      break;
   }
   // Test du commentaire
   if (!this.comment.length) {
      err = "Le commentaire est obligatoire";
      break;
   }
   // Test du mail
   if (!this.isValidEmail(this.email)) {
      err = "L'adresse email saisie n'est pas valide";
      break;
   }
   // Enregistrement du commentaire
   this.saveComment();
// Le while(false) interromps la boucle dés le premier passage
} while (false);
// S'il y a eu une erreur, on l'affiche
if (err) {
   trace(err);
}

Cet exemple présente une situation assez simple, qui est facilement optimisable d'autres manières. Mais cette utilisation du do...while peut-être bien pratique dans des situations plus complexes ... :)

23 février 2004

Nouvelle amélioration : les smileys

Comme vous le constaterez surement avec beaucoup de joie, les smileys sont de retour !!! :=) Il ne me reste plus qu'à faire le tour de ceux qui étaient présents sur la version précédente pour qu'ils apparaissent à nouveau. Cela donne l'occasion de rajouter un petit module nommé "Sourieur" a mes utilitaires DSPlay qui passe en version 0.4b, comprenant les modifications du BBCoder pour qu'il utilise ce nouveau module ... 8) Et un peu de français dans les noms de classe, ca fait plaisir non ? ;)

Bon, promis je vais me remettre un peu au Flash la, histoire de mettre en pratique quelques notions utilisées dans le moteur PHP du blog. En parlant de ca, petepx attaque sa série prometteuse sur les Design Patterns appliqué à l'AS2, par le célebrissime Singleton. A consulter de toute urgence : on attend la suite avec impatience !!! :D

10 février 2004

Lire le contenu d'un fichier texte avec LoadVars







On oublie trés souvent les possibilités étendues de la classe LoadVars, notamment son évenement onData, qui permet d'accéder au contenu brut d'un fichier chargé avec les méthodes load ou sendAndLoad. En effet, le paramètre transmis à cet évenement contient les données brutes du fichier, avant d'être analysées pour générer des propriétés dans l'objet. :o

Dans un cas "classique" d'utilisation de LoadVars, l'évenement onData recevrait donc une chaine de la forme mavar1=mavaleur1&mavar2=mavaleur2 ... mais si le fichier n'est pas formaté de cette manière, il reste tout de même possible d'accéder à son contenu ! 8)

Ainsi, il est possible de créer un lecteur de fichiers textes, tel que celui présenté ci-contre. Il va lire le contenu du fichier textfile.txt. Bien entendu, pour un affichage correct dans Flash, il faut que le fichier soit encodé en Unicode, afin de préserver les accents et autres fioritures des caractères latins. ;)

Le code tout simple de ce lecteur : // Si aucun chemin de fichier
// n'est passé en paramètre, on en met
// un par défaut
if (_root.file == undefined) {
        _root.file = "textfile.txt";
}
// Objet LoadVars
var lv = new LoadVars();
// Evenement onData
// le paramètre 'src' contient
// le contenu du fichier "loadé"
lv.onData = function (src) {
        var nlPos;
        // Supprime les retours chariot (
)
        // pour ne laisser que les retours à la ligne
        while ((nlPos=src.indexOf("
"
)) >= 0) {
                src = src.slice(0, nlPos) + src.slice(nlPos+1);
        }
        // On met le contenu du fichier dans la
        // zone de texte
        _root.dspText.text = src;
}
// Chargement du fichier
lv.load(_root.file);

::Télécharger textreader.zip::

9 février 2004

Bézier et Tweens : une vague 'naturelle'

Je me suis trés souvent demandé comment faire un mouvement de vague qui puisse paraître relativement "naturel". Trouver un mouvement suffisemment esthétique n'est pas chose facile, et bien que celui-ci ne soit pas encore complètement au point, il se rapproche beaucoup de ce que je voulais obtenir. 8)







L'aspect sympa est que j'utilise des Tweens (les équations AS2 de Penner doivent être installées pour compiler) pour faire varier les positions de deux points de contrôle de la courbe, et que je calcule la position du troisième point pour que la courbure ne fasse pas d'angle au milieu. Il s'agit d'un calcul vectoriel tout simple, que vous pouvez voir sur la première frame du fichier source (MX 2004).

Ainsi, la courbe semble "vivante", et le mouvement étant non-linéaire (la durée des deux Tweens est différente), ca semble moins mécanique. Je me demande si un mouvement circulaire du premier point de contrôle ne donnerait pas une impression plus proche de la "vague" ... a voir ! ;)

::Télécharger xp-curve-2.zip::

- page 2 de 4 -