/** * @private * Flyweight object to process the attributes of a sprite. * A single instance of the AttributeDefinition is created per sprite class. * See `onClassCreated` and `onClassExtended` callbacks * of the {@link Ext.draw.sprite.Sprite} for more info. */Ext.define('Ext.draw.sprite.AttributeDefinition', { requires: [ 'Ext.draw.sprite.AttributeParser', 'Ext.draw.sprite.AnimationParser' ], config: { /** * @cfg {Object} defaults Defines the default values of attributes. */ defaults: { $value: {}, lazy: true }, /** * @cfg {Object} aliases Defines the alternative names for attributes. */ aliases: {}, /** * @cfg {Object} animationProcessors Defines the process used to animate between attributes. * One doesn't have to define animation processors for sprite attributes that use * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser} singleton. * For such attributes matching animation processors from the {@link Ext.draw.sprite.AnimationParser} * singleton will be used automatically. * However, if you have a custom processor for an attribute that should support * animation, you must provide a corresponding animation processor for it here. * For more information on animation processors please see {@link Ext.draw.sprite.AnimationParser} * documentation. */ animationProcessors: {}, /** * @cfg {Object} processors Defines the preprocessing used on the attributes. * One can define a custom processor function here or use the name of a predefined * processor from the {@link Ext.draw.sprite.AttributeParser} singleton. */ processors: { // A plus side of lazy initialization is that the 'processors' and 'defaults' will // only be applied for those sprite classes that are actually instantiated. $value: {}, lazy: true }, /** * @cfg {Object} dirtyTriggers * @deprecated Use the {@link #triggers} config instead. */ dirtyTriggers: {}, /** * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed. * For example, the config below indicates that the 'size' updater * of a {@link Ext.draw.sprite.Square square} sprite has to be called * when the 'size' attribute changes. * * triggers: { * size: 'size' // Use comma-separated values here if multiple updaters have to be called. * } // Note that the order is _not_ guaranteed. * * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call) * set attributes themselves and those attributes have triggers defined for them, * then their updaters will be called after all current updaters finish execution. * * The updater functions themselves are defined in the {@link #updaters} config, * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag, * indicating that this attribute should be applied to a Canvas context (or whatever emulates it). * @since 5.1.0 */ triggers: {}, /** * @cfg {Object} updaters Defines the postprocessing used by the attribute. * Inside the updater function 'this' refers to the sprite that the attributes belong to. * In case of an instancing sprite 'this' will refer to the instancing template. * The two parameters passed to the updater function are the attributes object * of the sprite or instance, and the names of attributes that triggered this updater call. * * The example below shows how the 'size' updater changes other attributes * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes. * * updaters: { * size: function (attr) { * var size = attr.size; * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater. * x: attr.x - size, * y: attr.y - size, * height: 2 * size, * width: 2 * size * }); * } * } */ updaters: {} }, inheritableStatics: { /** * @private * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'. * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums}, * {@link Ext.draw.sprite.AttributeParser#limited limited}. */ processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/ }, // The sprite class for which AttributeDefinition instance is created. spriteClass: null, constructor: function (config) { var me = this; me.initConfig(config); }, applyDefaults: function (defaults, oldDefaults) { oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults)); return oldDefaults; }, applyAliases: function (aliases, oldAliases) { return Ext.apply(oldAliases || {}, aliases); }, applyProcessors: function (processors, oldProcessors) { this.getAnimationProcessors(); // Apply custom animation processors first. var result = oldProcessors || {}, defaultProcessor = Ext.draw.sprite.AttributeParser, processorFactoryRe = this.self.processorFactoryRe, animationProcessors = {}, anyAnimationProcessors, name, match, fn; for (name in processors) { fn = processors[name]; if (typeof fn === 'string') { match = fn.match(processorFactoryRe); if (match) { // enums(... , limited(... or something of that nature. fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(',')); } else if (defaultProcessor[fn]) { // Names of animation parsers match the names of attribute parsers. animationProcessors[name] = fn; anyAnimationProcessors = true; fn = defaultProcessor[fn]; } } //<debug> if (!Ext.isFunction(fn)) { Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found."); } //</debug> result[name] = fn; } if (anyAnimationProcessors) { this.setAnimationProcessors(animationProcessors); } return result; }, applyAnimationProcessors: function (animationProcessors, oldAnimationProcessors) { var parser = Ext.draw.sprite.AnimationParser, name, item; if (!oldAnimationProcessors) { oldAnimationProcessors = {}; } for (name in animationProcessors) { item = animationProcessors[name]; if (item === 'none') { oldAnimationProcessors[name] = null; } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) { if (item in parser) { // The while loop is used to resolve aliases, e.g. `num: 'number'`, // where `number` maps to a parser object or is an alias too. while (Ext.isString(parser[item])) { item = parser[item]; } oldAnimationProcessors[name] = parser[item]; } } else if (Ext.isObject(item)) { oldAnimationProcessors[name] = item; } } return oldAnimationProcessors; }, updateDirtyTriggers: function (dirtyTriggers) { this.setTriggers(dirtyTriggers); }, applyTriggers: function (triggers, oldTriggers) { if (!oldTriggers) { oldTriggers = {}; } for (var name in triggers) { oldTriggers[name] = triggers[name].split(','); } return oldTriggers; }, applyUpdaters: function (updaters, oldUpdaters) { return Ext.apply(oldUpdaters || {}, updaters); }, batchedNormalize: function (batchedChanges, keepUnrecognized) { if (!batchedChanges) { return {}; } var processors = this.getProcessors(), aliases = this.getAliases(), translation = batchedChanges.translation || batchedChanges.translate, normalized = {}, i, ln, name, val, rotation, scaling, matrix, subVal, split; if ('rotation' in batchedChanges) { rotation = batchedChanges.rotation; } else { rotation = ('rotate' in batchedChanges) ? batchedChanges.rotate : undefined; } if ('scaling' in batchedChanges) { scaling = batchedChanges.scaling; } else { scaling = ('scale' in batchedChanges) ? batchedChanges.scale : undefined; } if (typeof scaling !== 'undefined') { if (Ext.isNumber(scaling)) { normalized.scalingX = scaling; normalized.scalingY = scaling; } else { if ('x' in scaling) { normalized.scalingX = scaling.x; } if ('y' in scaling) { normalized.scalingY = scaling.y; } if ('centerX' in scaling) { normalized.scalingCenterX = scaling.centerX; } if ('centerY' in scaling) { normalized.scalingCenterY = scaling.centerY; } } } if (typeof rotation !== 'undefined') { if (Ext.isNumber(rotation)) { rotation = Ext.draw.Draw.rad(rotation); normalized.rotationRads = rotation; } else { if ('rads' in rotation) { normalized.rotationRads = rotation.rads; } else if ('degrees' in rotation) { if (Ext.isArray(rotation.degrees)) { normalized.rotationRads = Ext.Array.map(rotation.degrees, function (deg) { return Ext.draw.Draw.rad(deg); }); } else { normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees); } } if ('centerX' in rotation) { normalized.rotationCenterX = rotation.centerX; } if ('centerY' in rotation) { normalized.rotationCenterY = rotation.centerY; } } } if (typeof translation !== 'undefined') { if ('x' in translation) { normalized.translationX = translation.x; } if ('y' in translation) { normalized.translationY = translation.y; } } if ('matrix' in batchedChanges) { matrix = Ext.draw.Matrix.create(batchedChanges.matrix); split = matrix.split(); normalized.matrix = matrix; normalized.rotationRads = split.rotation; normalized.rotationCenterX = 0; normalized.rotationCenterY = 0; normalized.scalingX = split.scaleX; normalized.scalingY = split.scaleY; normalized.scalingCenterX = 0; normalized.scalingCenterY = 0; normalized.translationX = split.translateX; normalized.translationY = split.translateY; } for (name in batchedChanges) { val = batchedChanges[name]; if (typeof val === 'undefined') { continue; } else if (Ext.isArray(val)) { if (name in aliases) { name = aliases[name]; } if (name in processors) { normalized[name] = []; for (i = 0, ln = val.length; i < ln; i++) { subVal = processors[name].call(this, val[i]); if (typeof subVal !== 'undefined') { normalized[name][i] = subVal; } } } else if (keepUnrecognized){ normalized[name] = val; } } else { if (name in aliases) { name = aliases[name]; } if (name in processors) { val = processors[name].call(this, val); if (typeof val !== 'undefined') { normalized[name] = val; } } else if (keepUnrecognized){ normalized[name] = val; } } } return normalized; }, /** * Normalizes the changes given via their processors before they are applied as attributes. * * @param {Object} changes The changes given. * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through as normalized values. * @return {Object} The normalized values. */ normalize: function (changes, keepUnrecognized) { if (!changes) { return {}; } var processors = this.getProcessors(), aliases = this.getAliases(), translation = changes.translation || changes.translate, normalized = {}, name, val, rotation, scaling, matrix, split; if ('rotation' in changes) { rotation = changes.rotation; } else { rotation = ('rotate' in changes) ? changes.rotate : undefined; } if ('scaling' in changes) { scaling = changes.scaling; } else { scaling = ('scale' in changes) ? changes.scale : undefined; } if (translation) { if ('x' in translation) { normalized.translationX = translation.x; } if ('y' in translation) { normalized.translationY = translation.y; } } if (typeof scaling !== 'undefined') { if (Ext.isNumber(scaling)) { normalized.scalingX = scaling; normalized.scalingY = scaling; } else { if ('x' in scaling) { normalized.scalingX = scaling.x; } if ('y' in scaling) { normalized.scalingY = scaling.y; } if ('centerX' in scaling) { normalized.scalingCenterX = scaling.centerX; } if ('centerY' in scaling) { normalized.scalingCenterY = scaling.centerY; } } } if (typeof rotation !== 'undefined') { if (Ext.isNumber(rotation)) { rotation = Ext.draw.Draw.rad(rotation); normalized.rotationRads = rotation; } else { if ('rads' in rotation) { normalized.rotationRads = rotation.rads; } else if ('degrees' in rotation) { normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees); } if ('centerX' in rotation) { normalized.rotationCenterX = rotation.centerX; } if ('centerY' in rotation) { normalized.rotationCenterY = rotation.centerY; } } } if ('matrix' in changes) { matrix = Ext.draw.Matrix.create(changes.matrix); split = matrix.split(); // This will NOT update the transformation matrix of a sprite // with the given elements. It will attempt to extract the // individual transformation attributes from the transformation matrix // elements provided. Then the extracted attributes will be used by // the sprite's 'applyTransformations' method to calculate // the transformation matrix of the sprite. // It's not possible to recover all the information from the given // transformation matrix elements. Shearing and centers of rotation // and scaling are not recovered. // Ideally, this should work like sprite.transform([elements], true), // i.e. update the transformation matrix of a sprite directly, // without attempting to update sprite's transformation attributes. // But we are not changing the behavior (just yet) for compatibility // reasons. normalized.matrix = matrix; normalized.rotationRads = split.rotation; normalized.rotationCenterX = 0; normalized.rotationCenterY = 0; normalized.scalingX = split.scaleX; normalized.scalingY = split.scaleY; normalized.scalingCenterX = 0; normalized.scalingCenterY = 0; normalized.translationX = split.translateX; normalized.translationY = split.translateY; } for (name in changes) { val = changes[name]; if (typeof val === 'undefined') { continue; } if (name in aliases) { name = aliases[name]; } if (name in processors) { val = processors[name].call(this, val); if (typeof val !== 'undefined') { normalized[name] = val; } } else if (keepUnrecognized){ normalized[name] = val; } } return normalized; }, setBypassingNormalization: function (attr, modifierStack, changes) { return modifierStack.pushDown(attr, changes); }, set: function (attr, modifierStack, changes) { changes = this.normalize(changes); return this.setBypassingNormalization(attr, modifierStack, changes); }});