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.} {position} * @property {Array.|number} {rotation} * @property {Array.} {scale} * @property {Object} style * * @property {Function} onenter * @property {Function} onleave * @property {Function} ontransition * @property {Array.} 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;