var dataUtil = require("../utils/data_structure_util");
var Style = require("./Style");
var _vector_util = require("../utils/vector_util");
var vec2Copy = _vector_util.copy;
/* eslint-disable no-prototype-builtins */
/**
* @class qrenderer.graphic.GraphicStates
*
* States machine for managing graphic states
*
* @docauthor 大漠穷秋 <damoqiongqiu@126.com>
*/
var transitionProperties = ['position', 'rotation', 'scale', 'style', 'shape'];
var TransitionObject = function TransitionObject(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.
* @property {String}
* @default *
*/
property: '*',
/**
* @property {String}
* @default 'Linear'
*/
easing: 'Linear',
/**
* @property {Number}
* @default 'number'
*/
duration: 500,
/**
* @property {Number}
*/
delay: 0,
_fromStr: function _fromStr(str) {
var arr = str.split(/\s+/g);
this.property = arr[0];
this.duration = +arr[1];
this.delay = +arr[2];
this.easing = arr[3];
}
};
/**
* @method constructor GraphicStates
* @property {Number} [qlevel]
* @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 GraphicStates = function GraphicStates(opts) {
opts = opts || {};
this._states = {};
/**
* @property _el
*/
this._el = opts.el;
this._subStates = [];
this._transitionAnimationProcess = [];
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
* @property {String}
* @private
*/
_initialState: 'normal',
/**
* Current state
* @property {String}
* @private
*/
_currentState: '',
el: function el() {
return this._el;
},
_addState: function _addState(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 扩展到初始状态中
dataUtil.merge(state.style, el.style, false, false);
if (state.shape) {
dataUtil.merge(state.shape, el.shape, false, true);
} else {
state.shape = dataUtil.clone(el.shape, true);
}
for (var _name in this._states) {
if (this._states.hasOwnProperty(_name)) {
this._extendFromInitial(this._states[_name]);
}
}
}
},
_extendFromInitial: function _extendFromInitial(state) {
var initialState = this._states[this._initialState];
if (initialState && state !== initialState) {
dataUtil.merge(state, initialState, false, true);
}
},
setState: function setState(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.qlevel != null) {
el.qlevel = state.qlevel;
}
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.extendStyle(initialState.style, false);
}
if ( // Not initial state
name !== this._initialState // Not copied from initial state in _extendFromInitial method
&& initialState.style !== state.style) {
el.style.extendStyle(state.style, true);
}
}
if (state.shape) {
el.shape = dataUtil.clone(state.shape, true);
}
el.dirty();
}
}
for (var i = 0; i < this._subStates.length; i++) {
this._subStates.setState(name);
}
},
getState: function getState() {
return this._currentState;
},
transitionState: function transitionState(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.__qr) {
// El can be animated
var transitionCfg = state.transition;
var property = transitionCfg.property;
var animatingCount = 0;
var animationDone = function animationDone() {
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 _animProp(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 taps = this._transitionAnimationProcess;
if (availableProp) {
var obj = {};
if (stateObj[key] === elObj[key]) {
return 0;
}
obj[key] = stateObj[key];
var animationProcess = el.animate(subPropKey).when(transitionCfg.duration, obj).delay(transitionCfg.dealy).done(function () {
var idx = dataUtil.indexOf(taps, 1);
if (idx > 0) {
taps.splice(idx, 1);
}
done();
}).start(transitionCfg.easing);
taps.push(animationProcess);
return 1;
}
return 0;
},
_stopTransition: function _stopTransition() {
var taps = this._transitionAnimationProcess;
for (var i = 0; i < taps.length; i++) {
taps[i].stop();
}
taps.length = 0;
},
transiting: function transiting() {
return this._transitionAnimationProcess.length > 0;
},
addSubStates: function addSubStates(states) {
this._subStates.push(states);
},
removeSubStates: function removeSubStates(states) {
var idx = dataUtil.indexOf(this._subStates, states);
if (idx >= 0) {
this._subStates.splice(states, 1);
}
}
};
var _default = GraphicStates;
module.exports = _default;