| /** |
| * 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' |
| */ |
| import * as zrUtil from '../core/util'; |
| import Style from './Style'; |
| import { copy as vec2Copy } from '../core/vector'; |
| 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]) { |
| if (!state[propName].hasOwnProperty(key)) { |
| continue; |
| } |
| |
| var path = propName + '.' + key; |
| |
| if (propPathMap[path]) { |
| continue; |
| } |
| |
| 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); |
| } |
| } |
| }; |
| export default GraphicStates; |