Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / draw / Sprite.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.draw.Sprite
17  * @extends Object
18  *
19  * A Sprite is an object rendered in a Drawing surface. There are different options and types of sprites.
20  * The configuration of a Sprite is an object with the following properties:
21  *
22  * - **type** - (String) The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'. 
23  * - **group** - (String/Array) The group that this sprite belongs to, or an array of groups. Only relevant when added to a {@link Ext.draw.Surface}.
24  * - **width** - (Number) Used in rectangle sprites, the width of the rectangle.
25  * - **height** - (Number) Used in rectangle sprites, the height of the rectangle.
26  * - **size** - (Number) Used in square sprites, the dimension of the square.
27  * - **radius** - (Number) Used in circle sprites, the radius of the circle.
28  * - **x** - (Number) The position along the x-axis.
29  * - **y** - (Number) The position along the y-axis.
30  * - **path** - (Array) Used in path sprites, the path of the sprite written in SVG-like path syntax.
31  * - **opacity** - (Number) The opacity of the sprite.
32  * - **fill** - (String) The fill color.
33  * - **stroke** - (String) The stroke color.
34  * - **stroke-width** - (Number) The width of the stroke.
35  * - **font** - (String) Used with text type sprites. The full font description. Uses the same syntax as the CSS `font` parameter.
36  * - **text** - (String) Used with text type sprites. The text itself.
37  * - **translate** - (Object) Defines a translation for the Sprite. There's more information on this property below.
38  * - **rotate** - (Object) Defines a rotation for the Sprite. There's more information on this property below.
39  * - **scale** - (Object) Defines a scaling for the Sprite. There's more information on this property below.
40  * 
41  *
42  * ## Translation
43  * 
44  * For translate, the configuration object contains x and y attributes that indicate where to
45  * translate the object. For example:
46  * 
47  *     sprite.setAttributes({
48  *       translate: {
49  *        x: 10,
50  *        y: 10
51  *       }
52  *     }, true);
53  *
54  *
55  * ## Rotation
56  * 
57  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
58  * and a `degrees` attribute that specifies the rotation in degrees. For example:
59  * 
60  *     sprite.setAttributes({
61  *       rotate: {
62  *        degrees: 90
63  *       }
64  *     }, true);
65  *
66  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
67  *
68  *     sprite.setAttributes({
69  *       rotate: {
70  *        x: 0,
71  *        y: 0,
72  *        degrees: 90
73  *       }
74  *     }, true);
75  *
76  * will create a rotation around the `(0, 0)` axis.
77  *
78  *
79  * ## Scaling
80  * 
81  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
82  * 
83  *     sprite.setAttributes({
84  *       scale: {
85  *        x: 10,
86  *        y: 3
87  *       }
88  *     }, true);
89  *
90  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
91  *
92  *     sprite.setAttributes({
93  *       scale: {
94  *        cx: 0,
95  *        cy: 0,
96  *        x: 10,
97  *        y: 3
98  *       }
99  *     }, true);
100  *
101  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
102  * 
103  * 
104  * ## Creating and adding a Sprite to a Surface
105  * 
106  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
107  *
108  *      var drawComponent = Ext.create('Ext.draw.Component', options here...);
109  *
110  *      var sprite = Ext.create('Ext.draw.Sprite', {
111  *          type: 'circle',
112  *          fill: '#ff0',
113  *          surface: drawComponent.surface,
114  *          radius: 5
115  *      });
116  *
117  * Sprites can also be added to the surface as a configuration object:
118  *
119  *      var sprite = drawComponent.surface.add({
120  *          type: 'circle',
121  *          fill: '#ff0',
122  *          radius: 5
123  *      });
124  *
125  * In order to properly apply properties and render the sprite we have to
126  * `show` the sprite setting the option `redraw` to `true`:
127  *
128  *      sprite.show(true);
129  *
130  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
131  * add method to append a new sprite to the canvas. For example:
132  *
133  *     drawComponent.surface.add({
134  *         type: 'circle',
135  *         fill: '#ffc',
136  *         radius: 100,
137  *         x: 100,
138  *         y: 100
139  *     });
140  */
141 Ext.define('Ext.draw.Sprite', {
142     
143     /**
144      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
145      */
146     
147     /**
148      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
149      */
150     
151     /**
152      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
153      */
154     
155     /**
156      * @cfg {Number} size Used in square sprites, the dimension of the square
157      */
158     
159     /**
160      * @cfg {Number} radius Used in circle sprites, the radius of the circle
161      */
162     
163     /**
164      * @cfg {Number} x The position along the x-axis
165      */
166     
167     /**
168      * @cfg {Number} y The position along the y-axis
169      */
170     
171     /**
172      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
173      */
174     
175     /**
176      * @cfg {Number} opacity The opacity of the sprite
177      */
178     
179     /**
180      * @cfg {String} fill The fill color
181      */
182     
183     /**
184      * @cfg {String} stroke The stroke color
185      */
186     
187     /**
188      * @cfg {Number} stroke-width The width of the stroke
189      */
190     
191     /**
192      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
193      */
194     
195     /**
196      * @cfg {String} text Used with text type sprites. The text itself
197      */
198     
199     /**
200      * @cfg {String/Array} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
201      * {@link Ext.draw.Surface}
202      */
203     
204     /* Begin Definitions */
205
206     mixins: {
207         observable: 'Ext.util.Observable',
208         animate: 'Ext.util.Animate'
209     },
210
211     requires: ['Ext.draw.SpriteDD'],
212
213     /* End Definitions */
214
215     dirty: false,
216     dirtyHidden: false,
217     dirtyTransform: false,
218     dirtyPath: true,
219     dirtyFont: true,
220     zIndexDirty: true,
221     isSprite: true,
222     zIndex: 0,
223     fontProperties: [
224         'font',
225         'font-size',
226         'font-weight',
227         'font-style',
228         'font-family',
229         'text-anchor',
230         'text'
231     ],
232     pathProperties: [
233         'x',
234         'y',
235         'd',
236         'path',
237         'height',
238         'width',
239         'radius',
240         'r',
241         'rx',
242         'ry',
243         'cx',
244         'cy'
245     ],
246     constructor: function(config) {
247         var me = this;
248         config = config || {};
249         me.id = Ext.id(null, 'ext-sprite-');
250         me.transformations = [];
251         Ext.copyTo(this, config, 'surface,group,type,draggable');
252         //attribute bucket
253         me.bbox = {};
254         me.attr = {
255             zIndex: 0,
256             translation: {
257                 x: null,
258                 y: null
259             },
260             rotation: {
261                 degrees: null,
262                 x: null,
263                 y: null
264             },
265             scaling: {
266                 x: null,
267                 y: null,
268                 cx: null,
269                 cy: null
270             }
271         };
272         //delete not bucket attributes
273         delete config.surface;
274         delete config.group;
275         delete config.type;
276         delete config.draggable;
277         me.setAttributes(config);
278         me.addEvents(
279             'beforedestroy',
280             'destroy',
281             'render',
282             'mousedown',
283             'mouseup',
284             'mouseover',
285             'mouseout',
286             'mousemove',
287             'click'
288         );
289         me.mixins.observable.constructor.apply(this, arguments);
290     },
291
292     /**
293      * <p>If this Sprite is configured {@link #draggable}, this property will contain
294      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.</p>
295      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
296      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
297      * @type Ext.dd.DragSource.
298      * @property dd
299      */
300     initDraggable: function() {
301         var me = this;
302         me.draggable = true;
303         //create element if it doesn't exist.
304         if (!me.el) {
305             me.surface.createSpriteElement(me);
306         }
307         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
308         me.on('beforedestroy', me.dd.destroy, me.dd);
309     },
310
311     /**
312      * Change the attributes of the sprite.
313      * @param {Object} attrs attributes to be changed on the sprite.
314      * @param {Boolean} redraw Flag to immediatly draw the change.
315      * @return {Ext.draw.Sprite} this
316      */
317     setAttributes: function(attrs, redraw) {
318         var me = this,
319             fontProps = me.fontProperties,
320             fontPropsLength = fontProps.length,
321             pathProps = me.pathProperties,
322             pathPropsLength = pathProps.length,
323             hasSurface = !!me.surface,
324             custom = hasSurface && me.surface.customAttributes || {},
325             spriteAttrs = me.attr,
326             attr, i, translate, translation, rotate, rotation, scale, scaling;
327
328         attrs = Ext.apply({}, attrs);
329         for (attr in custom) {
330             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
331                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
332             }
333         }
334
335         // Flag a change in hidden
336         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
337             me.dirtyHidden = true;
338         }
339
340         // Flag path change
341         for (i = 0; i < pathPropsLength; i++) {
342             attr = pathProps[i];
343             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
344                 me.dirtyPath = true;
345                 break;
346             }
347         }
348
349         // Flag zIndex change
350         if ('zIndex' in attrs) {
351             me.zIndexDirty = true;
352         }
353
354         // Flag font/text change
355         for (i = 0; i < fontPropsLength; i++) {
356             attr = fontProps[i];
357             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
358                 me.dirtyFont = true;
359                 break;
360             }
361         }
362
363         translate = attrs.translate;
364         translation = spriteAttrs.translation;
365         if (translate) {
366             if ((translate.x && translate.x !== translation.x) ||
367                 (translate.y && translate.y !== translation.y)) {
368                 Ext.apply(translation, translate);
369                 me.dirtyTransform = true;
370             }
371             delete attrs.translate;
372         }
373
374         rotate = attrs.rotate;
375         rotation = spriteAttrs.rotation;
376         if (rotate) {
377             if ((rotate.x && rotate.x !== rotation.x) || 
378                 (rotate.y && rotate.y !== rotation.y) ||
379                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
380                 Ext.apply(rotation, rotate);
381                 me.dirtyTransform = true;
382             }
383             delete attrs.rotate;
384         }
385
386         scale = attrs.scale;
387         scaling = spriteAttrs.scaling;
388         if (scale) {
389             if ((scale.x && scale.x !== scaling.x) || 
390                 (scale.y && scale.y !== scaling.y) ||
391                 (scale.cx && scale.cx !== scaling.cx) ||
392                 (scale.cy && scale.cy !== scaling.cy)) {
393                 Ext.apply(scaling, scale);
394                 me.dirtyTransform = true;
395             }
396             delete attrs.scale;
397         }
398
399         Ext.apply(spriteAttrs, attrs);
400         me.dirty = true;
401
402         if (redraw === true && hasSurface) {
403             me.redraw();
404         }
405         return this;
406     },
407
408     /**
409      * Retrieve the bounding box of the sprite. This will be returned as an object with x, y, width, and height properties.
410      * @return {Object} bbox
411      */
412     getBBox: function() {
413         return this.surface.getBBox(this);
414     },
415     
416     setText: function(text) {
417         return this.surface.setText(this, text);
418     },
419
420     /**
421      * Hide the sprite.
422      * @param {Boolean} redraw Flag to immediatly draw the change.
423      * @return {Ext.draw.Sprite} this
424      */
425     hide: function(redraw) {
426         this.setAttributes({
427             hidden: true
428         }, redraw);
429         return this;
430     },
431
432     /**
433      * Show the sprite.
434      * @param {Boolean} redraw Flag to immediatly draw the change.
435      * @return {Ext.draw.Sprite} this
436      */
437     show: function(redraw) {
438         this.setAttributes({
439             hidden: false
440         }, redraw);
441         return this;
442     },
443
444     /**
445      * Remove the sprite.
446      */
447     remove: function() {
448         if (this.surface) {
449             this.surface.remove(this);
450             return true;
451         }
452         return false;
453     },
454
455     onRemove: function() {
456         this.surface.onRemove(this);
457     },
458
459     /**
460      * Removes the sprite and clears all listeners.
461      */
462     destroy: function() {
463         var me = this;
464         if (me.fireEvent('beforedestroy', me) !== false) {
465             me.remove();
466             me.surface.onDestroy(me);
467             me.clearListeners();
468             me.fireEvent('destroy');
469         }
470     },
471
472     /**
473      * Redraw the sprite.
474      * @return {Ext.draw.Sprite} this
475      */
476     redraw: function() {
477         this.surface.renderItem(this);
478         return this;
479     },
480
481     /**
482      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
483      * @param {String/Object} property The style property to be set, or an object of multiple styles.
484      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
485      * @return {Ext.draw.Sprite} this
486      */
487     setStyle: function() {
488         this.el.setStyle.apply(this.el, arguments);
489         return this;
490     },
491
492     /**
493      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
494      * is severly limited in VML.
495      * @param {String/Array} className The CSS class to add, or an array of classes
496      * @return {Ext.draw.Sprite} this
497      */
498     addCls: function(obj) {
499         this.surface.addCls(this, obj);
500         return this;
501     },
502
503     /**
504      * Removes one or more CSS classes from the element.
505      * @param {String/Array} className The CSS class to remove, or an array of classes.  Note this method
506      * is severly limited in VML.
507      * @return {Ext.draw.Sprite} this
508      */
509     removeCls: function(obj) {
510         this.surface.removeCls(this, obj);
511         return this;
512     }
513 });
514