400 lines
9.8 KiB
JavaScript
400 lines
9.8 KiB
JavaScript
var zrUtil = require("../core/util");
|
|
|
|
var Style = require("./Style");
|
|
|
|
var _vector = require("../core/vector");
|
|
|
|
var vec2Copy = _vector.copy;
|
|
|
|
/**
|
|
* States machine for managing graphic states
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} IGraphicState
|
|
* @property {number} [zlevel]
|
|
* @property {number} [z]
|
|
* @property {Array.<number>} {position}
|
|
* @property {Array.<number>|number} {rotation}
|
|
* @property {Array.<number>} {scale}
|
|
* @property {Object} style
|
|
*
|
|
* @property {Function} onenter
|
|
* @property {Function} onleave
|
|
* @property {Function} ontransition
|
|
* @property {Array.<IGraphicStateTransition|string>} transition
|
|
* Transition object or a string descriptor like '* 30 0 Linear'
|
|
*/
|
|
var transitionProperties = ['position', 'rotation', 'scale', 'style', 'shape'];
|
|
/**
|
|
* @module zrender/graphic/States~TransitionObject
|
|
*/
|
|
|
|
var TransitionObject = function (opts) {
|
|
if (typeof opts === 'string') {
|
|
this._fromStr(opts);
|
|
} else if (opts) {
|
|
opts.property && (this.property = opts.property);
|
|
opts.duration != null && (this.duration = opts.duration);
|
|
opts.easing && (this.easing = opts.easing);
|
|
opts.delay && (this.delay = opts.delay);
|
|
}
|
|
|
|
if (this.property !== '*') {
|
|
this.property = this.property.split(',');
|
|
} else {
|
|
this.property = transitionProperties;
|
|
}
|
|
};
|
|
|
|
TransitionObject.prototype = {
|
|
constructor: TransitionObject,
|
|
|
|
/**
|
|
* List of all transition properties. Splitted by comma. Must not have spaces in the string.
|
|
* e.g. 'position,style.color'. '*' will match all the valid properties.
|
|
* @type {string}
|
|
* @default *
|
|
*/
|
|
property: '*',
|
|
|
|
/**
|
|
* @type {string}
|
|
* @default 'Linear'
|
|
*/
|
|
easing: 'Linear',
|
|
|
|
/**
|
|
* @type {number}
|
|
* @default 'number'
|
|
*/
|
|
duration: 500,
|
|
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
delay: 0,
|
|
_fromStr: function (str) {
|
|
var arr = str.split(/\s+/g);
|
|
this.property = arr[0];
|
|
this.duration = +arr[1];
|
|
this.delay = +arr[2];
|
|
this.easing = arr[3];
|
|
}
|
|
};
|
|
/**
|
|
* @alias module:zrender/graphic/States
|
|
*/
|
|
|
|
var GraphicStates = function (opts) {
|
|
opts = opts || {};
|
|
this._states = {};
|
|
/**
|
|
* Target element
|
|
* @type {zrender/graphic/Displayable|zrender/container/Group}
|
|
*/
|
|
|
|
this._el = opts.el;
|
|
this._subStates = [];
|
|
this._transitionAnimators = [];
|
|
|
|
if (opts.initialState) {
|
|
this._initialState = opts.initialState;
|
|
}
|
|
|
|
var optsStates = opts.states;
|
|
|
|
if (optsStates) {
|
|
for (var name in optsStates) {
|
|
if (optsStates.hasOwnProperty(name)) {
|
|
var state = optsStates[name];
|
|
|
|
this._addState(name, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.setState(this._initialState);
|
|
};
|
|
|
|
GraphicStates.prototype = {
|
|
constructor: GraphicStates,
|
|
|
|
/**
|
|
* All other state will be extended from initial state
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
_initialState: 'normal',
|
|
|
|
/**
|
|
* Current state
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
_currentState: '',
|
|
el: function () {
|
|
return this._el;
|
|
},
|
|
_addState: function (name, state) {
|
|
this._states[name] = state;
|
|
|
|
if (state.transition) {
|
|
state.transition = new TransitionObject(state.transition);
|
|
} // Extend from initial state
|
|
|
|
|
|
if (name !== this._initialState) {
|
|
this._extendFromInitial(state);
|
|
} else {
|
|
var el = this._el; // setState 的时候自带的 style 和 shape 都会被直接覆盖
|
|
// 所以这边先把自带的 style 和 shape 扩展到初始状态中
|
|
|
|
zrUtil.merge(state.style, el.style, false, false);
|
|
|
|
if (state.shape) {
|
|
zrUtil.merge(state.shape, el.shape, false, true);
|
|
} else {
|
|
state.shape = zrUtil.clone(el.shape, true);
|
|
}
|
|
|
|
for (var name in this._states) {
|
|
if (this._states.hasOwnProperty(name)) {
|
|
this._extendFromInitial(this._states[name]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_extendFromInitial: function (state) {
|
|
var initialState = this._states[this._initialState];
|
|
|
|
if (initialState && state !== initialState) {
|
|
zrUtil.merge(state, initialState, false, true);
|
|
}
|
|
},
|
|
setState: function (name, silent) {
|
|
if (name === this._currentState && !this.transiting()) {
|
|
return;
|
|
}
|
|
|
|
var state = this._states[name];
|
|
|
|
if (state) {
|
|
this._stopTransition();
|
|
|
|
if (!silent) {
|
|
var prevState = this._states[this._currentState];
|
|
|
|
if (prevState) {
|
|
prevState.onleave && prevState.onleave.call(this);
|
|
}
|
|
|
|
state.onenter && state.onenter.call(this);
|
|
}
|
|
|
|
this._currentState = name;
|
|
|
|
if (this._el) {
|
|
var el = this._el; // Setting attributes
|
|
|
|
if (state.zlevel != null) {
|
|
el.zlevel = state.zlevel;
|
|
}
|
|
|
|
if (state.z != null) {
|
|
el.z = state.z;
|
|
} // SRT
|
|
|
|
|
|
state.position && vec2Copy(el.position, state.position);
|
|
state.scale && vec2Copy(el.scale, state.scale);
|
|
|
|
if (state.rotation != null) {
|
|
el.rotation = state.rotation;
|
|
} // Style
|
|
|
|
|
|
if (state.style) {
|
|
var initialState = this._states[this._initialState];
|
|
el.style = new Style();
|
|
|
|
if (initialState) {
|
|
el.style.extendFrom(initialState.style, false);
|
|
}
|
|
|
|
if ( // Not initial state
|
|
name !== this._initialState // Not copied from initial state in _extendFromInitial method
|
|
&& initialState.style !== state.style) {
|
|
el.style.extendFrom(state.style, true);
|
|
}
|
|
}
|
|
|
|
if (state.shape) {
|
|
el.shape = zrUtil.clone(state.shape, true);
|
|
}
|
|
|
|
el.dirty();
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < this._subStates.length; i++) {
|
|
this._subStates.setState(name);
|
|
}
|
|
},
|
|
getState: function () {
|
|
return this._currentState;
|
|
},
|
|
transitionState: function (target, done) {
|
|
if (target === this._currentState && !this.transiting()) {
|
|
return;
|
|
}
|
|
|
|
var state = this._states[target];
|
|
var styleShapeReg = /$[style|shape]\./;
|
|
var self = this; // Animation 去重
|
|
|
|
var propPathMap = {};
|
|
|
|
if (state) {
|
|
self._stopTransition();
|
|
|
|
var el = self._el;
|
|
|
|
if (state.transition && el && el.__zr) {
|
|
// El can be animated
|
|
var transitionCfg = state.transition;
|
|
var property = transitionCfg.property;
|
|
var animatingCount = 0;
|
|
|
|
var animationDone = function () {
|
|
animatingCount--;
|
|
|
|
if (animatingCount === 0) {
|
|
self.setState(target);
|
|
done && done();
|
|
}
|
|
};
|
|
|
|
for (var i = 0; i < property.length; i++) {
|
|
var propName = property[i]; // Animating all the properties in style or shape
|
|
|
|
if (propName === 'style' || propName === 'shape') {
|
|
if (state[propName]) {
|
|
for (var key in state[propName]) {
|
|
/* eslint-disable max-depth */
|
|
if (!state[propName].hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
|
|
var path = propName + '.' + key;
|
|
|
|
if (propPathMap[path]) {
|
|
continue;
|
|
}
|
|
/* eslint-enable max-depth */
|
|
|
|
|
|
propPathMap[path] = 1;
|
|
animatingCount += self._animProp(state, propName, key, transitionCfg, animationDone);
|
|
}
|
|
}
|
|
} else {
|
|
if (propPathMap[propName]) {
|
|
continue;
|
|
}
|
|
|
|
propPathMap[propName] = 1; // Animating particular property in style or style
|
|
|
|
if (propName.match(styleShapeReg)) {
|
|
// remove 'style.', 'shape.' prefix
|
|
var subProp = propName.slice(0, 5);
|
|
propName = propName.slice(6);
|
|
animatingCount += self._animProp(state, subProp, propName, transitionCfg, animationDone);
|
|
} else {
|
|
animatingCount += self._animProp(state, '', propName, transitionCfg, animationDone);
|
|
}
|
|
}
|
|
} // No transition properties
|
|
|
|
|
|
if (animatingCount === 0) {
|
|
self.setState(target);
|
|
done && done();
|
|
}
|
|
} else {
|
|
self.setState(target);
|
|
done && done();
|
|
}
|
|
}
|
|
|
|
var subStates = self._subStates;
|
|
|
|
for (var i = 0; i < subStates.length; i++) {
|
|
subStates.transitionState(target);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Do transition animation of particular property
|
|
* @param {Object} state
|
|
* @param {string} subPropKey
|
|
* @param {string} key
|
|
* @param {Object} transitionCfg
|
|
* @param {Function} done
|
|
* @private
|
|
*/
|
|
_animProp: function (state, subPropKey, key, transitionCfg, done) {
|
|
var el = this._el;
|
|
var stateObj = subPropKey ? state[subPropKey] : state;
|
|
var elObj = subPropKey ? el[subPropKey] : el;
|
|
var availableProp = stateObj && key in stateObj && elObj && key in elObj;
|
|
var transitionAnimators = this._transitionAnimators;
|
|
|
|
if (availableProp) {
|
|
var obj = {};
|
|
|
|
if (stateObj[key] === elObj[key]) {
|
|
return 0;
|
|
}
|
|
|
|
obj[key] = stateObj[key];
|
|
var animator = el.animate(subPropKey).when(transitionCfg.duration, obj).delay(transitionCfg.dealy).done(function () {
|
|
var idx = zrUtil.indexOf(transitionAnimators, 1);
|
|
|
|
if (idx > 0) {
|
|
transitionAnimators.splice(idx, 1);
|
|
}
|
|
|
|
done();
|
|
}).start(transitionCfg.easing);
|
|
transitionAnimators.push(animator);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
_stopTransition: function () {
|
|
var transitionAnimators = this._transitionAnimators;
|
|
|
|
for (var i = 0; i < transitionAnimators.length; i++) {
|
|
transitionAnimators[i].stop();
|
|
}
|
|
|
|
transitionAnimators.length = 0;
|
|
},
|
|
transiting: function () {
|
|
return this._transitionAnimators.length > 0;
|
|
},
|
|
addSubStates: function (states) {
|
|
this._subStates.push(states);
|
|
},
|
|
removeSubStates: function (states) {
|
|
var idx = zrUtil.indexOf(this._subStates, states);
|
|
|
|
if (idx >= 0) {
|
|
this._subStates.splice(states, 1);
|
|
}
|
|
}
|
|
};
|
|
var _default = GraphicStates;
|
|
module.exports = _default; |