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