]> git.proxmox.com Git - sencha-touch.git/blob - src/src/draw/sprite/Sprite.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / draw / sprite / Sprite.js
1 /**
2 * A sprite is an object rendered in a drawing {@link Ext.draw.Surface}.
3 * The Sprite class itself is an abstract class and is not meant to be used directly.
4 * Every sprite in the Draw and Chart packages is a subclass of the Ext.draw.sprite.Sprite.
5 * The standard Sprite subclasses are:
6 *
7 * * {@link Ext.draw.sprite.Path} - A sprite that represents a path.
8 * * {@link Ext.draw.sprite.Rect} - A sprite that represents a rectangle.
9 * * {@link Ext.draw.sprite.Circle} - A sprite that represents a circle.
10 * * {@link Ext.draw.sprite.Sector} - A sprite representing a pie slice.
11 * * {@link Ext.draw.sprite.Arc} - A sprite that represents a circular arc.
12 * * {@link Ext.draw.sprite.Ellipse} - A sprite that represents an ellipse.
13 * * {@link Ext.draw.sprite.EllipticalArc} - A sprite that represents an elliptical arc.
14 * * {@link Ext.draw.sprite.Text} - A sprite that represents text.
15 * * {@link Ext.draw.sprite.Image} - A sprite that represents an image.
16 * * {@link Ext.draw.sprite.Instancing} - A sprite that represents multiple instances based on the given template.
17 * * {@link Ext.draw.sprite.Composite} - Represents a group of sprites.
18 *
19 * Sprites can be created with a reference to a {@link Ext.draw.Surface}
20 *
21 * var drawComponent = Ext.create('Ext.draw.Component', {
22 * // ...
23 * });
24 *
25 * var sprite = Ext.create('Ext.draw.sprite.Sprite', {
26 * type: 'circle',
27 * fill: '#ff0',
28 * surface: drawComponent.getSurface('main'),
29 * radius: 5
30 * });
31 *
32 * Sprites can also be added to the surface as a configuration object:
33 *
34 * var sprite = drawComponent.getSurface('main').add({
35 * type: 'circle',
36 * fill: '#ff0',
37 * radius: 5
38 * });
39 */
40 Ext.define('Ext.draw.sprite.Sprite', {
41 alias: 'sprite.sprite',
42
43 mixins: {
44 observable: 'Ext.mixin.Observable'
45 },
46
47 requires: [
48 'Ext.draw.Draw',
49 'Ext.draw.gradient.Gradient',
50 'Ext.draw.sprite.AttributeDefinition',
51 'Ext.draw.sprite.AttributeParser',
52 'Ext.draw.modifier.Target',
53 'Ext.draw.modifier.Animation',
54 'Ext.draw.modifier.Highlight'
55 ],
56
57 isSprite: true,
58
59 inheritableStatics: {
60 def: {
61 processors: {
62 /**
63 * @cfg {String} [strokeStyle="none"] The color of the stroke (a CSS color value).
64 */
65 strokeStyle: "color",
66
67 /**
68 * @cfg {String} [fillStyle="none"] The color of the shape (a CSS color value).
69 */
70 fillStyle: "color",
71
72 /**
73 * @cfg {Number} [strokeOpacity=1] The opacity of the stroke. Limited from 0 to 1.
74 */
75 strokeOpacity: "limited01",
76
77 /**
78 * @cfg {Number} [fillOpacity=1] The opacity of the fill. Limited from 0 to 1.
79 */
80 fillOpacity: "limited01",
81
82 /**
83 * @cfg {Number} [lineWidth=1] The width of the line stroke.
84 */
85 lineWidth: "number",
86
87 /**
88 * @cfg {String} [lineCap="butt"] The style of the line caps.
89 */
90 lineCap: "enums(butt,round,square)",
91
92 /**
93 * @cfg {String} [lineJoin="miter"] The style of the line join.
94 */
95 lineJoin: "enums(round,bevel,miter)",
96
97 /**
98 * @cfg {Array} An array of non-negative numbers specifying a dash/space sequence.
99 */
100 lineDash: "data",
101
102 /**
103 * @cfg {Number} A number specifying how far into the line dash sequence drawing commences.
104 */
105 lineDashOffset: "number",
106
107 /**
108 * @cfg {Number} [miterLimit=1] Sets the distance between the inner corner and the outer corner where two lines meet.
109 */
110 miterLimit: "number",
111
112 /**
113 * @cfg {String} [shadowColor="none"] The color of the shadow (a CSS color value).
114 */
115 shadowColor: "color",
116
117 /**
118 * @cfg {Number} [shadowOffsetX=0] The offset of the sprite's shadow on the x-axis.
119 */
120 shadowOffsetX: "number",
121
122 /**
123 * @cfg {Number} [shadowOffsetY=0] The offset of the sprite's shadow on the y-axis.
124 */
125 shadowOffsetY: "number",
126
127 /**
128 * @cfg {Number} [shadowBlur=0] The amount blur used on the shadow.
129 */
130 shadowBlur: "number",
131
132 /**
133 * @cfg {Number} [globalAlpha=1] The opacity of the sprite. Limited from 0 to 1.
134 */
135 globalAlpha: "limited01",
136 globalCompositeOperation: "enums(source-over,destination-over,source-in,destination-in,source-out,destination-out,source-atop,destination-atop,lighter,xor,copy)",
137
138 /**
139 * @cfg {Boolean} [hidden=false] Determines whether or not the sprite is hidden.
140 */
141 hidden: "bool",
142
143 /**
144 * @cfg {Boolean} [transformFillStroke=false] Determines whether the fill and stroke are affected by sprite transformations.
145 */
146 transformFillStroke: "bool",
147
148 /**
149 * @cfg {Number} [zIndex=0] The stacking order of the sprite.
150 */
151 zIndex: "number",
152
153 /**
154 * @cfg {Number} [translationX=0] The translation of the sprite on the x-axis.
155 */
156 translationX: "number",
157
158 /**
159 * @cfg {Number} [translationY=0] The translation of the sprite on the y-axis.
160 */
161 translationY: "number",
162
163 /**
164 * @cfg {Number} [rotationRads=0] The degree of rotation of the sprite.
165 */
166 rotationRads: "number",
167
168 /**
169 * @cfg {Number} [rotationCenterX=null] The central coordinate of the sprite's scale operation on the x-axis.
170 */
171 rotationCenterX: "number",
172
173 /**
174 * @cfg {Number} [rotationCenterY=null] The central coordinate of the sprite's rotate operation on the y-axis.
175 */
176 rotationCenterY: "number",
177
178 /**
179 * @cfg {Number} [scalingX=1] The scaling of the sprite on the x-axis.
180 */
181 scalingX: "number",
182
183 /**
184 * @cfg {Number} [scalingY=1] The scaling of the sprite on the y-axis.
185 */
186 scalingY: "number",
187
188 /**
189 * @cfg {Number} [scalingCenterX=null] The central coordinate of the sprite's scale operation on the x-axis.
190 */
191 scalingCenterX: "number",
192
193 /**
194 * @cfg {Number} [scalingCenterY=null] The central coordinate of the sprite's scale operation on the y-axis.
195 */
196 scalingCenterY: "number",
197
198 constrainGradients: "bool"
199 },
200
201 aliases: {
202 "stroke": "strokeStyle",
203 "fill": "fillStyle",
204 "color": "fillStyle",
205 "stroke-width": "lineWidth",
206 "stroke-linecap": "lineCap",
207 "stroke-linejoin": "lineJoin",
208 "stroke-miterlimit": "miterLimit",
209 "text-anchor": "textAlign",
210 "opacity": "globalAlpha",
211
212 translateX: "translationX",
213 translateY: "translationY",
214 rotateRads: "rotationRads",
215 rotateCenterX: "rotationCenterX",
216 rotateCenterY: "rotationCenterY",
217 scaleX: "scalingX",
218 scaleY: "scalingY",
219 scaleCenterX: "scalingCenterX",
220 scaleCenterY: "scalingCenterY"
221 },
222
223 defaults: {
224 hidden: false,
225 zIndex: 0,
226
227 strokeStyle: "none",
228 fillStyle: "none",
229 lineWidth: 1,
230 lineDash: [],
231 lineDashOffset: 0,
232 lineCap: "butt",
233 lineJoin: "miter",
234 miterLimit: 1,
235
236 shadowColor: "none",
237 shadowOffsetX: 0,
238 shadowOffsetY: 0,
239 shadowBlur: 0,
240
241 globalAlpha: 1,
242 strokeOpacity: 1,
243 fillOpacity: 1,
244 transformFillStroke: false,
245
246 translationX: 0,
247 translationY: 0,
248 rotationRads: 0,
249 rotationCenterX: null,
250 rotationCenterY: null,
251 scalingX: 1,
252 scalingY: 1,
253 scalingCenterX: null,
254 scalingCenterY: null,
255
256 constrainGradients: false
257 },
258
259 dirtyTriggers: {
260 hidden: "canvas",
261 zIndex: "zIndex",
262
263 globalAlpha: "canvas",
264 globalCompositeOperation: "canvas",
265
266 transformFillStroke: "canvas",
267 strokeStyle: "canvas",
268 fillStyle: "canvas",
269 strokeOpacity: "canvas",
270 fillOpacity: "canvas",
271
272 lineWidth: "canvas",
273 lineCap: "canvas",
274 lineJoin: "canvas",
275 lineDash: "canvas",
276 lineDashOffset: "canvas",
277 miterLimit: "canvas",
278
279 shadowColor: "canvas",
280 shadowOffsetX: "canvas",
281 shadowOffsetY: "canvas",
282 shadowBlur: "canvas",
283
284 translationX: "transform",
285 translationY: "transform",
286 rotationRads: "transform",
287 rotationCenterX: "transform",
288 rotationCenterY: "transform",
289 scalingX: "transform",
290 scalingY: "transform",
291 scalingCenterX: "transform",
292 scalingCenterY: "transform",
293
294 constrainGradients: "canvas"
295 },
296
297 updaters: {
298 "bbox": function (attrs) {
299 attrs.bbox.plain.dirty = true;
300 attrs.bbox.transform.dirty = true;
301 if (
302 attrs.rotationRads !== 0 && (attrs.rotationCenterX === null || attrs.rotationCenterY === null) ||
303 ((attrs.scalingX !== 1 || attrs.scalingY !== 1) &&
304 (attrs.scalingCenterX === null || attrs.scalingCenterY === null)
305 )
306 ) {
307 if (!attrs.dirtyFlags.transform) {
308 attrs.dirtyFlags.transform = [];
309 }
310 }
311 },
312
313 "zIndex": function (attrs) {
314 attrs.dirtyZIndex = true;
315 },
316
317 "transform": function (attrs) {
318 attrs.dirtyTransform = true;
319 attrs.bbox.transform.dirty = true;
320 }
321 }
322 }
323 },
324
325 /**
326 * @property {Object} attr
327 * The visual attributes of the sprite, e.g. strokeStyle, fillStyle, lineWidth...
328 */
329 attr: {},
330
331 config: {
332 parent: null
333 },
334
335 onClassExtended: function (Class, member) {
336 var initCfg = Class.superclass.self.def.initialConfig,
337 cfg;
338
339 if (member.inheritableStatics && member.inheritableStatics.def) {
340 cfg = Ext.merge({}, initCfg, member.inheritableStatics.def);
341 Class.def = Ext.create("Ext.draw.sprite.AttributeDefinition", cfg);
342 delete member.inheritableStatics.def;
343 } else {
344 Class.def = Ext.create("Ext.draw.sprite.AttributeDefinition", initCfg);
345 }
346 },
347
348 constructor: function (config) {
349 if (this.$className === 'Ext.draw.sprite.Sprite') {
350 throw 'Ext.draw.sprite.Sprite is an abstract class';
351 }
352 config = config || {};
353 var me = this;
354
355 me.id = config.id || Ext.id(null, 'ext-sprite-');
356 me.attr = {};
357 me.initConfig(config);
358 var modifiers = Ext.Array.from(config.modifiers, true);
359 me.prepareModifiers(modifiers);
360 me.initializeAttributes();
361 me.setAttributes(me.self.def.getDefaults(), true);
362 me.setAttributes(config);
363 },
364
365 getDirty: function () {
366 return this.attr.dirty;
367 },
368
369 setDirty: function (dirty) {
370 if ((this.attr.dirty = dirty)) {
371 if (this._parent) {
372 this._parent.setDirty(true);
373 }
374 }
375 },
376
377 addModifier: function (modifier, reinitializeAttributes) {
378 var me = this;
379 if (!(modifier instanceof Ext.draw.modifier.Modifier)) {
380 modifier = Ext.factory(modifier, null, null, 'modifier');
381 }
382 modifier.setSprite(this);
383 if (modifier.preFx || modifier.config && modifier.config.preFx) {
384 if (me.fx.getPrevious()) {
385 me.fx.getPrevious().setNext(modifier);
386 }
387 modifier.setNext(me.fx);
388 } else {
389 me.topModifier.getPrevious().setNext(modifier);
390 modifier.setNext(me.topModifier);
391 }
392 if (reinitializeAttributes) {
393 me.initializeAttributes();
394 }
395 return modifier;
396 },
397
398 prepareModifiers: function (additionalModifiers) {
399 // Set defaults
400 var me = this,
401 modifier, i, ln;
402
403 me.topModifier = new Ext.draw.modifier.Target({sprite: me});
404
405 // Link modifiers
406 me.fx = new Ext.draw.modifier.Animation({sprite: me});
407 me.fx.setNext(me.topModifier);
408
409 for (i = 0, ln = additionalModifiers.length; i < ln; i++) {
410 me.addModifier(additionalModifiers[i], false);
411 }
412 },
413
414 initializeAttributes: function () {
415 var me = this;
416 me.topModifier.prepareAttributes(me.attr);
417 },
418
419 updateDirtyFlags: function (attrs) {
420 var me = this,
421 dirtyFlags = attrs.dirtyFlags, flags,
422 updaters = me.self.def._updaters,
423 any = false,
424 dirty = false,
425 flag;
426
427 do {
428 any = false;
429 for (flag in dirtyFlags) {
430 me.updateDirtyFlags = Ext.emptyFn;
431 flags = dirtyFlags[flag];
432 delete dirtyFlags[flag];
433 if (updaters[flag]) {
434 updaters[flag].call(me, attrs, flags);
435 }
436 any = true;
437 delete me.updateDirtyFlags;
438 }
439 dirty = dirty || any;
440 } while (any);
441
442 if (dirty) {
443 me.setDirty(true);
444 }
445 },
446
447 /**
448 * Set attributes of the sprite.
449 *
450 * @param {Object} changes The content of the change.
451 * @param {Boolean} [bypassNormalization] `true` to avoid normalization of the given changes.
452 * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
453 * The content of object may be destroyed.
454 */
455 setAttributes: function (changes, bypassNormalization, avoidCopy) {
456 var attributes = this.attr;
457 if (bypassNormalization) {
458 if (avoidCopy) {
459 this.topModifier.pushDown(attributes, changes);
460 } else {
461 this.topModifier.pushDown(attributes, Ext.apply({}, changes));
462 }
463 } else {
464 this.topModifier.pushDown(attributes, this.self.def.normalize(changes));
465 }
466 },
467
468 /**
469 * Set attributes of the sprite, assuming the names and values have already been
470 * normalized.
471 *
472 * @deprecated Use setAttributes directy with bypassNormalization argument being `true`.
473 * @param {Object} changes The content of the change.
474 * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
475 * The content of object may be destroyed.
476 */
477 setAttributesBypassingNormalization: function (changes, avoidCopy) {
478 return this.setAttributes(changes, true, avoidCopy);
479 },
480
481 /**
482 * Returns the bounding box for the given Sprite as calculated with the Canvas engine.
483 *
484 * @param {Boolean} [isWithoutTransform] Whether to calculate the bounding box with the current transforms or not.
485 */
486 getBBox: function (isWithoutTransform) {
487 var me = this,
488 attr = me.attr,
489 bbox = attr.bbox,
490 plain = bbox.plain,
491 transform = bbox.transform;
492 if (plain.dirty) {
493 me.updatePlainBBox(plain);
494 plain.dirty = false;
495 }
496 if (isWithoutTransform) {
497 return plain;
498 } else {
499 me.applyTransformations();
500 if (transform.dirty) {
501 me.updateTransformedBBox(transform, plain);
502 transform.dirty = false;
503 }
504 return transform;
505 }
506 },
507
508 /**
509 * @protected
510 * Subclass will fill the plain object with `x`, `y`, `width`, `height` information of the plain bounding box of
511 * this sprite.
512 *
513 * @param {Object} plain Target object.
514 */
515 updatePlainBBox: Ext.emptyFn,
516
517 /**
518 * @protected
519 * Subclass will fill the plain object with `x`, `y`, `width`, `height` information of the transformed
520 * bounding box of this sprite.
521 *
522 * @param {Object} transform Target object.
523 * @param {Object} plain Auxiliary object providing information of plain object.
524 */
525 updateTransformedBBox: function (transform, plain) {
526 this.attr.matrix.transformBBox(plain, 0, transform);
527 },
528
529 /**
530 * Subclass can rewrite this function to gain better performance.
531 * @param {Boolean} isWithoutTransform
532 * @return {Array}
533 */
534 getBBoxCenter: function (isWithoutTransform) {
535 var bbox = this.getBBox(isWithoutTransform);
536 if (bbox) {
537 return [
538 bbox.x + bbox.width * 0.5,
539 bbox.y + bbox.height * 0.5
540 ];
541 } else {
542 return [0, 0];
543 }
544 },
545
546 /**
547 * Hide the sprite.
548 * @return {Ext.draw.sprite.Sprite} this
549 * @chainable
550 */
551 hide: function () {
552 this.attr.hidden = true;
553 this.setDirty(true);
554 return this;
555 },
556
557 /**
558 * Show the sprite.
559 * @return {Ext.draw.sprite.Sprite} this
560 * @chainable
561 */
562 show: function () {
563 this.attr.hidden = false;
564 this.setDirty(true);
565 return this;
566 },
567
568 /**
569 * Applies sprite's attributes to the given context.
570 * @param {Object} ctx Context to apply sprite's attributes to.
571 * @param {Array} region The region of the context to be affected by gradients.
572 */
573 useAttributes: function (ctx, region) {
574 this.applyTransformations();
575 var attrs = this.attr,
576 canvasAttributes = attrs.canvasAttributes,
577 strokeStyle = canvasAttributes.strokeStyle,
578 fillStyle = canvasAttributes.fillStyle,
579 lineDash = canvasAttributes.lineDash,
580 lineDashOffset = canvasAttributes.lineDashOffset,
581 id;
582
583 if (strokeStyle) {
584 if (strokeStyle.isGradient) {
585 ctx.strokeStyle = 'black';
586 ctx.strokeGradient = strokeStyle;
587 } else {
588 ctx.strokeGradient = false;
589 }
590 }
591
592 if (fillStyle) {
593 if (fillStyle.isGradient) {
594 ctx.fillStyle = 'black';
595 ctx.fillGradient = fillStyle;
596 } else {
597 ctx.fillGradient = false;
598 }
599 }
600
601 if (lineDash && ctx.setLineDash) {
602 ctx.setLineDash(lineDash);
603 }
604
605 if (lineDashOffset && typeof ctx.lineDashOffset === 'number') {
606 ctx.lineDashOffset = lineDashOffset;
607 }
608
609 for (id in canvasAttributes) {
610 if (canvasAttributes[id] !== undefined && canvasAttributes[id] !== ctx[id]) {
611 ctx[id] = canvasAttributes[id];
612 }
613 }
614
615 if(attrs.constrainGradients) {
616 ctx.setGradientBBox({x: region[0], y: region[1], width: region[2], height: region[3]});
617 } else {
618 ctx.setGradientBBox(this.getBBox(attrs.transformFillStroke));
619 }
620 },
621
622 /**
623 * @private
624 *
625 * Calculates forward and inverse transform matrices.
626 * @param {Boolean} force Forces recalculation of transform matrices even when sprite's transform attributes supposedly haven't changed.
627 */
628 applyTransformations: function (force) {
629 if (!force && !this.attr.dirtyTransform) {
630 return;
631 }
632 var me = this,
633 attr = me.attr,
634 center = me.getBBoxCenter(true),
635 centerX = center[0],
636 centerY = center[1],
637
638 x = attr.translationX,
639 y = attr.translationY,
640
641 sx = attr.scalingX,
642 sy = attr.scalingY === null ? attr.scalingX : attr.scalingY,
643 scx = attr.scalingCenterX === null ? centerX : attr.scalingCenterX,
644 scy = attr.scalingCenterY === null ? centerY : attr.scalingCenterY,
645
646 rad = attr.rotationRads,
647 rcx = attr.rotationCenterX === null ? centerX : attr.rotationCenterX,
648 rcy = attr.rotationCenterY === null ? centerY : attr.rotationCenterY,
649
650 cos = Math.cos(rad),
651 sin = Math.sin(rad);
652
653 if (sx === 1 && sy === 1) {
654 scx = 0;
655 scy = 0;
656 }
657
658 if (rad === 0) {
659 rcx = 0;
660 rcy = 0;
661 }
662
663 attr.matrix.elements = [
664 cos * sx, sin * sy,
665 -sin * sx, cos * sy,
666 scx + (rcx - cos * rcx - scx + rcy * sin) * sx + x,
667 scy + (rcy - cos * rcy - scy + rcx * -sin) * sy + y
668 ];
669 attr.matrix.inverse(attr.inverseMatrix);
670 attr.dirtyTransform = false;
671 attr.bbox.transform.dirty = true;
672 },
673
674 /**
675 * Called before rendering.
676 */
677 preRender: Ext.emptyFn,
678
679 /**
680 * Render method.
681 * @param {Ext.draw.Surface} surface The surface.
682 * @param {Object} ctx A context object compatible with CanvasRenderingContext2D.
683 * @param {Array} region The clip region (or called dirty rect) of the current rendering. Not be confused
684 * with `surface.getRegion()`.
685 *
686 * @return {*} returns `false` to stop rendering in this frame. All the sprite haven't been rendered
687 * will have their dirty flag untouched.
688 */
689 render: Ext.emptyFn,
690
691 repaint: function () {
692 var parent = this.getParent();
693 while (parent && !(parent instanceof Ext.draw.Surface)) {
694 parent = parent.getParent();
695 }
696 if (parent) {
697 parent.renderFrame();
698 }
699 },
700
701 /**
702 * Removes the sprite and clears all listeners.
703 */
704 destroy: function () {
705 var me = this, modifier = me.topModifier, curr;
706 while (modifier) {
707 curr = modifier;
708 modifier = modifier.getPrevious();
709 curr.destroy();
710 }
711 delete me.attr;
712
713 me.destroy = Ext.emptyFn;
714 if (me.fireEvent('beforedestroy', me) !== false) {
715 me.fireEvent('destroy', me);
716 }
717 this.callSuper();
718 }
719 }, function () {
720 this.def = Ext.create("Ext.draw.sprite.AttributeDefinition", this.def);
721 });
722