Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / draw / Sprite.js
1 /**
2  * @class Ext.draw.Sprite
3  * @extends Object
4  *
5  * A Sprite is an object rendered in a Drawing surface. There are different options and types of sprites.
6  * The configuration of a Sprite is an object with the following properties:
7  *
8  * - **type** - (String) The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'. 
9  * - **width** - (Number) Used in rectangle sprites, the width of the rectangle.
10  * - **height** - (Number) Used in rectangle sprites, the height of the rectangle.
11  * - **size** - (Number) Used in square sprites, the dimension of the square.
12  * - **radius** - (Number) Used in circle sprites, the radius of the circle.
13  * - **x** - (Number) The position along the x-axis.
14  * - **y** - (Number) The position along the y-axis.
15  * - **path** - (Array) Used in path sprites, the path of the sprite written in SVG-like path syntax.
16  * - **opacity** - (Number) The opacity of the sprite.
17  * - **fill** - (String) The fill color.
18  * - **stroke** - (String) The stroke color.
19  * - **stroke-width** - (Number) The width of the stroke.
20  * - **font** - (String) Used with text type sprites. The full font description. Uses the same syntax as the CSS `font` parameter.
21  * - **text** - (String) Used with text type sprites. The text itself.
22  * 
23  * Additionally there are three transform objects that can be set with `setAttributes` which are `translate`, `rotate` and
24  * `scale`.
25  * 
26  * For translate, the configuration object contains x and y attributes that indicate where to
27  * translate the object. For example:
28  * 
29  *     sprite.setAttributes({
30  *       translate: {
31  *        x: 10,
32  *        y: 10
33  *       }
34  *     }, true);
35  * 
36  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
37  * and a `degrees` attribute that specifies the rotation in degrees. For example:
38  * 
39  *     sprite.setAttributes({
40  *       rotate: {
41  *        degrees: 90
42  *       }
43  *     }, true);
44  * 
45  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
46  * 
47  *     sprite.setAttributes({
48  *       scale: {
49  *        x: 10,
50  *        y: 3
51  *       }
52  *     }, true);
53  *
54  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
55  *
56  *      var drawComponent = Ext.create('Ext.draw.Component', options here...);
57  *
58  *      var sprite = Ext.create('Ext.draw.Sprite', {
59  *          type: 'circle',
60  *          fill: '#ff0',
61  *          surface: drawComponent.surface,
62  *          radius: 5
63  *      });
64  *
65  * Sprites can also be added to the surface as a configuration object:
66  *
67  *      var sprite = drawComponent.surface.add({
68  *          type: 'circle',
69  *          fill: '#ff0',
70  *          radius: 5
71  *      });
72  *
73  * In order to properly apply properties and render the sprite we have to
74  * `show` the sprite setting the option `redraw` to `true`:
75  *
76  *      sprite.show(true);
77  *
78  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
79  * add method to append a new sprite to the canvas. For example:
80  *
81  *     drawComponent.surface.add({
82  *         type: 'circle',
83  *         fill: '#ffc',
84  *         radius: 100,
85  *         x: 100,
86  *         y: 100
87  *     });
88  */
89 Ext.define('Ext.draw.Sprite', {
90     /* Begin Definitions */
91
92     mixins: {
93         observable: 'Ext.util.Observable',
94         animate: 'Ext.util.Animate'
95     },
96
97     requires: ['Ext.draw.SpriteDD'],
98
99     /* End Definitions */
100
101     dirty: false,
102     dirtyHidden: false,
103     dirtyTransform: false,
104     dirtyPath: true,
105     dirtyFont: true,
106     zIndexDirty: true,
107     isSprite: true,
108     zIndex: 0,
109     fontProperties: [
110         'font',
111         'font-size',
112         'font-weight',
113         'font-style',
114         'font-family',
115         'text-anchor',
116         'text'
117     ],
118     pathProperties: [
119         'x',
120         'y',
121         'd',
122         'path',
123         'height',
124         'width',
125         'radius',
126         'r',
127         'rx',
128         'ry',
129         'cx',
130         'cy'
131     ],
132     constructor: function(config) {
133         var me = this;
134         config = config || {};
135         me.id = Ext.id(null, 'ext-sprite-');
136         me.transformations = [];
137         Ext.copyTo(this, config, 'surface,group,type,draggable');
138         //attribute bucket
139         me.bbox = {};
140         me.attr = {
141             zIndex: 0,
142             translation: {
143                 x: null,
144                 y: null
145             },
146             rotation: {
147                 degrees: null,
148                 x: null,
149                 y: null
150             },
151             scaling: {
152                 x: null,
153                 y: null,
154                 cx: null,
155                 cy: null
156             }
157         };
158         //delete not bucket attributes
159         delete config.surface;
160         delete config.group;
161         delete config.type;
162         delete config.draggable;
163         me.setAttributes(config);
164         me.addEvents(
165             'beforedestroy',
166             'destroy',
167             'render',
168             'mousedown',
169             'mouseup',
170             'mouseover',
171             'mouseout',
172             'mousemove',
173             'click'
174         );
175         me.mixins.observable.constructor.apply(this, arguments);
176     },
177
178     /**
179      * <p>If this Sprite is configured {@link #draggable}, this property will contain
180      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.</p>
181      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
182      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
183      * @type Ext.dd.DragSource.
184      * @property dd
185      */
186     initDraggable: function() {
187         var me = this;
188         me.draggable = true;
189         //create element if it doesn't exist.
190         if (!me.el) {
191             me.surface.createSpriteElement(me);
192         }
193         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
194         me.on('beforedestroy', me.dd.destroy, me.dd);
195     },
196
197     /**
198      * Change the attributes of the sprite.
199      * @param {Object} attrs attributes to be changed on the sprite.
200      * @param {Boolean} redraw Flag to immediatly draw the change.
201      * @return {Ext.draw.Sprite} this
202      */
203     setAttributes: function(attrs, redraw) {
204         var me = this,
205             fontProps = me.fontProperties,
206             fontPropsLength = fontProps.length,
207             pathProps = me.pathProperties,
208             pathPropsLength = pathProps.length,
209             hasSurface = !!me.surface,
210             custom = hasSurface && me.surface.customAttributes || {},
211             spriteAttrs = me.attr,
212             attr, i, translate, translation, rotate, rotation, scale, scaling;
213
214         attrs = Ext.apply({}, attrs);
215         for (attr in custom) {
216             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
217                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
218             }
219         }
220
221         // Flag a change in hidden
222         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
223             me.dirtyHidden = true;
224         }
225
226         // Flag path change
227         for (i = 0; i < pathPropsLength; i++) {
228             attr = pathProps[i];
229             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
230                 me.dirtyPath = true;
231                 break;
232             }
233         }
234
235         // Flag zIndex change
236         if ('zIndex' in attrs) {
237             me.zIndexDirty = true;
238         }
239
240         // Flag font/text change
241         for (i = 0; i < fontPropsLength; i++) {
242             attr = fontProps[i];
243             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
244                 me.dirtyFont = true;
245                 break;
246             }
247         }
248
249         translate = attrs.translate;
250         translation = spriteAttrs.translation;
251         if (translate) {
252             if ((translate.x && translate.x !== translation.x) ||
253                 (translate.y && translate.y !== translation.y)) {
254                 Ext.apply(translation, translate);
255                 me.dirtyTransform = true;
256             }
257             delete attrs.translate;
258         }
259
260         rotate = attrs.rotate;
261         rotation = spriteAttrs.rotation;
262         if (rotate) {
263             if ((rotate.x && rotate.x !== rotation.x) || 
264                 (rotate.y && rotate.y !== rotation.y) ||
265                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
266                 Ext.apply(rotation, rotate);
267                 me.dirtyTransform = true;
268             }
269             delete attrs.rotate;
270         }
271
272         scale = attrs.scale;
273         scaling = spriteAttrs.scaling;
274         if (scale) {
275             if ((scale.x && scale.x !== scaling.x) || 
276                 (scale.y && scale.y !== scaling.y) ||
277                 (scale.cx && scale.cx !== scaling.cx) ||
278                 (scale.cy && scale.cy !== scaling.cy)) {
279                 Ext.apply(scaling, scale);
280                 me.dirtyTransform = true;
281             }
282             delete attrs.scale;
283         }
284
285         Ext.apply(spriteAttrs, attrs);
286         me.dirty = true;
287
288         if (redraw === true && hasSurface) {
289             me.redraw();
290         }
291         return this;
292     },
293
294     /**
295      * Retrieve the bounding box of the sprite. This will be returned as an object with x, y, width, and height properties.
296      * @return {Object} bbox
297      */
298     getBBox: function() {
299         return this.surface.getBBox(this);
300     },
301     
302     setText: function(text) {
303         return this.surface.setText(this, text);
304     },
305
306     /**
307      * Hide the sprite.
308      * @param {Boolean} redraw Flag to immediatly draw the change.
309      * @return {Ext.draw.Sprite} this
310      */
311     hide: function(redraw) {
312         this.setAttributes({
313             hidden: true
314         }, redraw);
315         return this;
316     },
317
318     /**
319      * Show the sprite.
320      * @param {Boolean} redraw Flag to immediatly draw the change.
321      * @return {Ext.draw.Sprite} this
322      */
323     show: function(redraw) {
324         this.setAttributes({
325             hidden: false
326         }, redraw);
327         return this;
328     },
329
330     /**
331      * Remove the sprite.
332      */
333     remove: function() {
334         if (this.surface) {
335             this.surface.remove(this);
336             return true;
337         }
338         return false;
339     },
340
341     onRemove: function() {
342         this.surface.onRemove(this);
343     },
344
345     /**
346      * Removes the sprite and clears all listeners.
347      */
348     destroy: function() {
349         var me = this;
350         if (me.fireEvent('beforedestroy', me) !== false) {
351             me.remove();
352             me.surface.onDestroy(me);
353             me.clearListeners();
354             me.fireEvent('destroy');
355         }
356     },
357
358     /**
359      * Redraw the sprite.
360      * @return {Ext.draw.Sprite} this
361      */
362     redraw: function() {
363         this.surface.renderItem(this);
364         return this;
365     },
366
367     /**
368      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
369      * @param {String/Object} property The style property to be set, or an object of multiple styles.
370      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
371      * @return {Ext.draw.Sprite} this
372      */
373     setStyle: function() {
374         this.el.setStyle.apply(this.el, arguments);
375         return this;
376     },
377
378     /**
379      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
380      * is severly limited in VML.
381      * @param {String/Array} className The CSS class to add, or an array of classes
382      * @return {Ext.draw.Sprite} this
383      */
384     addCls: function(obj) {
385         this.surface.addCls(this, obj);
386         return this;
387     },
388
389     /**
390      * Removes one or more CSS classes from the element.
391      * @param {String/Array} className The CSS class to remove, or an array of classes.  Note this method
392      * is severly limited in VML.
393      * @return {Ext.draw.Sprite} this
394      */
395     removeCls: function(obj) {
396         this.surface.removeCls(this, obj);
397         return this;
398     }
399 });