/**
* @class Ext.draw.modifier.Modifier
*
* Each sprite has a stack of modifiers. The resulting attributes of sprite is
* the content of the stack top. When setting attributes to a sprite,
* changes will be pushed-down though the stack of modifiers and pop-back the
* additive changes; When modifier is triggered to change the attribute of a
* sprite, it will pop-up the changes to the top.
*/
Ext.define('Ext.draw.modifier.Modifier', {
isModifier: true,
mixins: {
observable: 'Ext.mixin.Observable'
},
config: {
/**
* @private
* @cfg {Ext.draw.modifier.Modifier} lower Modifier that receives the push-down changes.
*/
lower: null,
/**
* @private
* @cfg {Ext.draw.modifier.Modifier} upper Modifier that receives the pop-up changes.
*/
upper: null,
/**
* @cfg {Ext.draw.sprite.Sprite} sprite The sprite to which the modifier belongs.
*/
sprite: null
},
constructor: function(config) {
this.mixins.observable.constructor.call(this, config);
},
updateUpper: function(upper) {
if (upper) {
upper.setLower(this);
}
},
updateLower: function(lower) {
if (lower) {
lower.setUpper(this);
}
},
/**
* @private
* Validate attribute set before use.
*
* @param {Object} attr The attribute to be validated. Note that it may be already initialized,
* so do not override properties that have already been used.
*/
prepareAttributes: function(attr) {
if (this._lower) {
this._lower.prepareAttributes(attr);
}
},
/**
* @private
* Invoked when changes need to be popped up to the top.
* @param {Object} attr The source attributes.
* @param {Object} changes The changes to be popped up.
*/
popUp: function(attr, changes) {
if (this._upper) {
this._upper.popUp(attr, changes);
}
else {
Ext.apply(attr, changes);
}
},
/**
* @private
*
* This method will filter out the properties from the `changes` object, if they
* have the same values as in the `attr` object (sprite's attributes).
*
* If the `receiver` object is provided, the attributes with the new values will be
* copied from the `changes` object to the `receiver` object, and the `changes`
* object will be left unchanged.
*
* The method returns the `receiver` object, if it was provided, or the `changes`
* object otherwise.
*
* The method also handles a special case when a sprite attribute that is meant to be
* animated was set to a certain value (e.g. 5), that is different from the original
* value (e.g. 3) of the attribute, and immediately set to another value again, that
* is the same as the original value (3). In this case, the attribute's current
* value is still the original value, because the attribute hasn't started animating
* yet, so a comparison against the current value is not appropriate, and the target
* value (value at the end of animation, 5) should be used for comparison instead, so
* that 3 won't be filtered out.
*/
filterChanges: function(attr, changes, receiver) {
var targets = attr.targets,
name, value;
if (receiver) {
for (name in changes) {
value = changes[name];
if (value !== attr[name] || (targets && value !== targets[name])) {
receiver[name] = value;
}
}
}
else {
for (name in changes) {
value = changes[name];
if (value === attr[name] && (!targets || value === targets[name])) {
delete changes[name];
}
}
}
return receiver || changes;
},
/**
* @private
* Invoked when changes need to be pushed down to the sprite.
* @param {Object} attr The source attributes.
* @param {Object} changes The changes to make. This object might be changed unexpectedly
* inside the method.
* @return {Mixed}
*/
pushDown: function(attr, changes) {
return this._lower
? this._lower.pushDown(attr, changes)
: this.filterChanges(attr, changes);
}
});