import { Flags, TWEEN } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { LoopType } from '../const';
const __ = {
private: Symbol('private'),
}
const Flag = {
Enable: 1 << 0
}
// #region Private Functions
// Build tween values.
function _buildTweenValues(object) {
if (Utils.isNumber(object)) {
return { value: object };
}
else if (Utils.isArray(object)) {
return { value: object };
}
return { value: object };
}
// Update tween repeat.
function _updateTweenRepeat(tween) {
let repeat = tween._times_value;
if (repeat === undefined) {
tween.repeat(Infinity);
}
else if (repeat < 0) {
tween.repeat(Infinity);
}
}
// Create tween.
function _createTween(from, to, duration, delayTime, lerpType, noInterp) {
return new TWEEN.Tween(from)
.to(to, duration)
.delay(delayTime)
.easing(lerpType)
.noInterp(noInterp);
}
// #endregion
/**
* @class TweenManager
* The tween manager.
* @memberof THING
*/
class TweenManager {
/**
* The tween manager to update value(s) smoothly.
*/
constructor() {
this[__.private] = {};
let _private = this[__.private];
_private.flags = new Flags();
_private.flags.enable(Flag.Enable, true);
_private.elapsedTimeMilliseconds = 0;
_private.tickTimeMilliseconds = 0;
}
dispose() {
TWEEN.removeAll();
}
now() {
let _private = this[__.private];
// We must care about the elapsed time in 1 frame, so need to calculate elapsed time from current frame begin
let elapsedTimeMilliseconds = Utils.getCurrentTimeMilliseconds() - _private.tickTimeMilliseconds;
return _private.elapsedTimeMilliseconds + elapsedTimeMilliseconds;
}
/**
* Update.
* @param {Number} deltaTime The delta time in seconds.
* @private
*/
update(deltaTime) {
let _private = this[__.private];
if (!_private.flags.has(Flag.Enable)) {
return;
}
_private.tickTimeMilliseconds = Utils.getCurrentTimeMilliseconds();
_private.elapsedTimeMilliseconds += deltaTime * 1000;
TWEEN.update(_private.elapsedTimeMilliseconds);
}
/**
* @typedef {Object} LerpToResult
* @property {Function} start The start function.
* @property {Function} stop The stop function.
* @property {Function} times The repeat times setting function.
* @property {Function} easing The lerp setting function.
* @property {Function} looping The looping setting function.
* @property {Function} onRepeat When repeat callback function.
* @property {Function} onStart When start callback function.
* @property {Function} onStop When start callback function.
* @property {Function} onUpdate When update callback function.
* @property {Function} onComplete When complete callback function.
*/
/**
* Start lerp to.
* @param {Number|Object} from The source.
* @param {Number|Object} to The target.
* @param {Number} [duration=1000] The lerp time in milliseconds.
* @param {Number} [delayTime=0] The delay time in milliseconds.
* @param {Object} options The optional parameters.
* @param {Boolean} [options.noInterp = false] Whether to set it directly without interpolation.
* @returns {LerpToResult}
* @example
* const box = new THING.Box();
* THING.App.current.tween.lerpTo({ opacity: 1}, { opacity: 0.2}, 2000).onUpdate(ev => {
* box.style.opacity = ev.value.opacity;
* });
* @public
*/
lerpTo(from, to, duration = 1000, delayTime = 0, options = {}) {
let source = _buildTweenValues(from);
let target = _buildTweenValues(to);
let noInterp = Utils.parseBoolean(options.noInterp, false);
let tween;
let that = this;
let result = {
_from: source,
_to: target,
start: function () {
tween.start(that.now());
return this;
},
stop: function () {
Utils.clearTimeout(tween._startTimer);
tween.stop();
return this;
},
pause: function () {
if (this._onPause) {
this._onPause({});
}
tween.pause();
return this;
},
resume: function () {
if (this._onResume) {
this._onResume({});
}
tween.resume();
return this;
},
times: function (value) {
tween._times_value = value;
// In tween, times always add more 1 time, so we need to decrease it
if (value > 0) {
value--;
}
tween.repeat(value);
return this;
},
easing: function (value) {
tween.easing(value);
return this;
},
looping: function (value) {
switch (value) {
case LoopType.Repeat:
_updateTweenRepeat(tween);
tween.yoyo(false);
break;
case LoopType.PingPong:
_updateTweenRepeat(tween);
tween.yoyo(true);
break;
default:
tween.yoyo(false);
break;
}
return this;
},
onRepeat: function (callback) {
this._onRepeat = callback;
return this;
},
onParseValue: function (callback) {
this._onParseValue = callback;
return this;
},
onStart: function (callback) {
this._onStart = callback;
return this;
},
onStop: function (callback) {
this._onStop = callback;
return this;
},
onResume: function (callback) {
this._onResume = callback;
return this;
},
onPause: function (callback) {
this._onPause = callback;
return this;
},
onUpdate: function (callback) {
this._onUpdate = callback;
return this;
},
onComplete: function (callback) {
this._onComplete = callback;
return this;
},
};
tween = _createTween(source, target, duration, delayTime, TWEEN.Easing.Linear.None, noInterp);
tween.onRepeat(ev => {
if (result._onRepeat) {
result._onRepeat(ev);
}
});
tween.onStart(ev => {
if (result._onStart) {
result._onStart(ev);
}
});
tween.onParseValue((start, end, key) => {
if (result._onParseValue) {
result._onParseValue(start, end, key);
}
});
tween.onStop(ev => {
if (result._onStop) {
result._onStop(ev);
}
});
tween.onUpdate((ev, progress) => {
if (result._onUpdate) {
result._onUpdate(ev, progress);
}
});
tween.onComplete(ev => {
if (result._onComplete) {
result._onComplete(ev);
}
});
// Here we make it a little delay to start in order to set attribute by result operations
tween._startTimer = Utils.setTimeout(() => {
tween.start(that.now());
}, 0); // We use '0' here to make sure start timer is available
if (_DEBUG) {
result._tween = tween;
}
return result;
}
// lerpPoints(value, time = 1000, delayTime = 0) {
// let result = this.lerpTo(0, 1, time, delayTime);
// result.onUpdatePoint = function (callback) {
// result._onUpdatePoint = callback;
// };
// // Copy path to move
// let path = value.slice(0);
// // Check whether it's closure path
// if (param['closure']) {
// path.push(value[0]);
// }
// // Get the steps of path
// let steps = MathUtils.getPointsSteps(path);
// if (steps.length) {
// // The reverse mode
// let reverseMode = false;
// // The current index of path
// let curIndex = -1;
// result.onRepeat(() => {
// // Reset start index
// curIndex = -1;
// if (loopType == LoopType.PingPong) {
// reverseMode = !reverseMode;
// }
// });
// result.onUpdate((ev) => {
// let progress = ev.value.progress;
// // Get the position by progress
// let { point, index, from, to } = MathUtils.lerpPoints(path, steps, progress);
// // Exchange from and to if it's in reserve mode
// if (reverseMode) {
// let cur = from;
// from = to;
// to = cur;
// }
// // Check whether reach next path
// if (curIndex != index) {
// if (to) {
// if (next) {
// next({ object, from, to });
// }
// }
// curIndex = index;
// }
// // Notify outside
// if (result._onUpdatePoint) {
// result._onUpdatePoint({ point, from, to, progress });
// }
// });
// }
// return result;
// }
get enable() {
let _private = this[__.private];
return _private.flags.has(Flag.Enable);
}
set enable(value) {
let _private = this[__.private];
_private.flags.enable(Flag.Enable, value);
}
}
export { TweenManager }