Move Tween class to anim namespace + changes

- Change from item to object, as it can be used to tween any property on any object really
- Make _handleFrame() private
- Minor documentation tweaks
This commit is contained in:
Jürg Lehni 2018-12-03 12:51:31 +01:00
parent 104d5eeef1
commit 9c684091f4
4 changed files with 91 additions and 59 deletions

@ -1 +1 @@
Subproject commit 2533ac8e1863262f3c28cd29bc940c6d2ecdf147 Subproject commit da249447ca037b67cad8193c59e8ce33fb40c61f

View file

@ -13,8 +13,16 @@
/** /**
* @name Tween * @name Tween
* *
* @class Allows tweening {@link Item} properties between two states for a given * @class Allows tweening `Object` properties between two states for a given
* duration. Tween instance is returned by {@link Item#tween(from,to,options)}. * duration. To tween properties on Paper.js {@link Item} instances,
* {@link Item#tween(from, to, options)} can be used, which returns created
* tween instance.
*
* @see Item#tween(from, to, options)
* @see Item#tween(to, options)
* @see Item#tween(options)
* @see Item#tweenTo(to, options)
* @see Item#tweenFrom(from, options)
*/ */
var Tween = Base.extend(Emitter, /** @lends Tween# */{ var Tween = Base.extend(Emitter, /** @lends Tween# */{
_class: 'Tween', _class: 'Tween',
@ -99,7 +107,7 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
/** /**
* Creates a new tween. * Creates a new tween.
* *
* @param {Item} item the item to tween * @param {Object} object the object to tween the properties on
* @param {Object} from the state at the start of the tweening * @param {Object} from the state at the start of the tweening
* @param {Object} to the state at the end of the tweening * @param {Object} to the state at the end of the tweening
* @param {Number} duration the duration of the tweening * @param {Number} duration the duration of the tweening
@ -108,8 +116,8 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
* @param {Boolean} [start=true] whether to start tweening automatically * @param {Boolean} [start=true] whether to start tweening automatically
* @return {Tween} the newly created tween * @return {Tween} the newly created tween
*/ */
initialize: function Tween(item, from, to, duration, easing, start) { initialize: function Tween(object, from, to, duration, easing, start) {
this.item = item; this.object = object;
var type = typeof easing; var type = typeof easing;
var isFunction = type === 'function'; var isFunction = type === 'function';
this.type = isFunction this.type = isFunction
@ -133,32 +141,18 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
} }
}, },
handleFrame: function(time) {
var startTime = this._startTime,
progress = startTime
? (time - startTime) / this.duration
: 0;
if (!startTime) {
this._startTime = time;
}
this.update(progress);
},
/** /**
* Set a function that will be executed when tween completes. * Set a function that will be executed when the tween completes.
* @param {Function} function the function to execute when tween completes * @param {Function} function the function to execute when the tween
* completes
* @return {Tween} * @return {Tween}
* *
* @example {@paperscript} * @example {@paperscript} // Tweens chaining: var circle = new
* // Tweens chaining: * Path.Circle({center: view.center, radius: 50, fillColor: 'blue'
* var item = new Path.Circle({
* center: view.center,
* radius: 50,
* fillColor: 'blue'
* }); * });
* // Tween color from blue to red. * // Tween color from blue to red.
* var tween = item.tweenTo({ fillColor: 'red' }, 2000); * var tween = item.tweenTo({ fillColor: 'red' }, 2000);
* // When first tween completes... * // When the first tween completes...
* tween.then(function() { * tween.then(function() {
* // ...tween color back to blue. * // ...tween color back to blue.
* item.tweenTo({ fillColor: 'blue' }, 2000); * item.tweenTo({ fillColor: 'blue' }, 2000);
@ -175,12 +169,12 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
* *
* @example {@paperscript} * @example {@paperscript}
* // Manually start tweening. * // Manually start tweening.
* var item = new Path.Circle({ * var circle = new Path.Circle({
* center: view.center, * center: view.center,
* radius: 50, * radius: 50,
* fillColor: 'blue' * fillColor: 'blue'
* }); * });
* var tween = item.tweenTo( * var tween = circle.tweenTo(
* { fillColor: 'red' }, * { fillColor: 'red' },
* { duration: 2000, start: false } * { duration: 2000, start: false }
* ); * );
@ -198,13 +192,13 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
* *
* @example {@paperscript} * @example {@paperscript}
* // Stop a tween before it completes. * // Stop a tween before it completes.
* var item = new Path.Circle({ * var circle = new Path.Circle({
* center: view.center, * center: view.center,
* radius: 50, * radius: 50,
* fillColor: 'blue' * fillColor: 'blue'
* }); * });
* // Start tweening from blue to red for 2 seconds. * // Start tweening from blue to red for 2 seconds.
* var tween = item.tweenTo({ fillColor: 'red' }, 2000); * var tween = circle.tweenTo({ fillColor: 'red' }, 2000);
* // After 1 second... * // After 1 second...
* setTimeout(function(){ * setTimeout(function(){
* // ...stop tweening. * // ...stop tweening.
@ -216,6 +210,7 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
return this; return this;
}, },
// DOCS: Document Tween#update(progress)
update: function(progress) { update: function(progress) {
if (this.running) { if (this.running) {
if (progress > 1) { if (progress > 1) {
@ -240,11 +235,12 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
value = (from && to && from.__add && to.__add) value = (from && to && from.__add && to.__add)
? to.__subtract(from).__multiply(factor).__add(from) ? to.__subtract(from).__multiply(factor).__add(from)
: ((to - from) * factor) + from; : ((to - from) * factor) + from;
this._setItemProperty(this._parsedKeys[key], value); this._setProperty(this._parsedKeys[key], value);
} }
if (!this.running && this._then) { if (!this.running && this._then) {
this._then(this.item); // TODO Look into what should be returned.
this._then(this.object);
} }
if (this.responds('update')) { if (this.responds('update')) {
this.emit('update', new Base({ this.emit('update', new Base({
@ -269,47 +265,67 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
* *
* @example {@paperscript} * @example {@paperscript}
* // Display tween progression values: * // Display tween progression values:
* var item = new Path.Circle({ * var circle = new Path.Circle({
* center: view.center, * center: view.center,
* radius: 50, * radius: 40,
* fillColor: 'blue' * fillColor: 'blue'
* }); * });
* var tween = item.tweenTo( * var tween = circle.tweenTo(
* { fillColor: 'red' }, * { fillColor: 'red' },
* { duration: 2000, easing: 'easeInCubic' } * {
* duration: 2000,
* easing: 'easeInCubic'
* }
* ); * );
* var progressText = new PointText(view.center + [60, -10]); * var progressText = new PointText(view.center + [60, -10]);
* var factorText = new PointText(view.center + [60, 10]); * var factorText = new PointText(view.center + [60, 10]);
*
* // Install event using onUpdate() property:
* tween.onUpdate = function(event) { * tween.onUpdate = function(event) {
* progressText.content = 'progress: ' + event.progress.toFixed(2); * progressText.content = 'progress: ' + event.progress.toFixed(2);
* factorText.content = 'factor: ' + event.factor.toFixed(2);
* }; * };
*
* // Install event using on('update') method:
* tween.on('update', function(event) {
* factorText.content = 'factor: ' + event.factor.toFixed(2);
* });
*/ */
_events: { _events: {
onUpdate: {} onUpdate: {}
}, },
_handleFrame: function(time) {
var startTime = this._startTime,
progress = startTime
? (time - startTime) / this.duration
: 0;
if (!startTime) {
this._startTime = time;
}
this.update(progress);
},
_getState: function(state) { _getState: function(state) {
var keys = this._keys, var keys = this._keys,
result = {}; result = {};
for (var i = 0, l = keys.length; i < l; i++) { for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i], var key = keys[i],
path = this._parsedKeys[key], path = this._parsedKeys[key],
current = this._getItemProperty(path), current = this._getProperty(path),
value; value;
if (state) { if (state) {
var resolved = this._resolveValue(current, state[key]); var resolved = this._resolveValue(current, state[key]);
// Temporarily set the resolved value, so we can retrieve the // Temporarily set the resolved value, so we can retrieve the
// coerced value from paper's internal magic. // coerced value from paper's internal magic.
this._setItemProperty(path, resolved); this._setProperty(path, resolved);
value = this._getItemProperty(path); value = this._getProperty(path);
// Clone the value if possible to prevent future changes. // Clone the value if possible to prevent future changes.
value = value.clone ? value.clone() : value; value = value && value.clone ? value.clone() : value;
this._setItemProperty(path, current); this._setProperty(path, current);
} else { } else {
// We want to get the current state at the time of the call, so // We want to get the current state at the time of the call, so
// we have to clone if possible to prevent future changes. // we have to clone if possible to prevent future changes.
value = current.clone ? current.clone() : current; value = current && current.clone ? current.clone() : current;
} }
result[key] = value; result[key] = value;
} }
@ -359,16 +375,16 @@ var Tween = Base.extend(Emitter, /** @lends Tween# */{
return parsed; return parsed;
}, },
_getItemProperty: function(path, offset) { _getProperty: function(path, offset) {
var obj = this.item; var obj = this.object;
for (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) { for (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) {
obj = obj[path[i]]; obj = obj[path[i]];
} }
return obj; return obj;
}, },
_setItemProperty: function(path, value) { _setProperty: function(path, value) {
var dest = this._getItemProperty(path, 1); var dest = this._getProperty(path, 1);
if (dest) { if (dest) {
dest[path[path.length - 1]] = value; dest[path[path.length - 1]] = value;
} }

View file

@ -4695,7 +4695,22 @@ new function() { // Injection scope for hit-test functions shared with project
* radius: view.bounds.height * 0.4, * radius: view.bounds.height * 0.4,
* center: view.center * center: view.center
* }); * });
* path.tween({ fillColor: 'blue' }, { fillColor: 'red' }, 3000); * path.tween(
* { fillColor: 'blue' },
* { fillColor: 'red' },
* 3000
* );
* @example {@paperscript height=100}
* // Tween rotation:
* var path = new Shape.Rectangle({
* fillColor: 'red',
* point: view.center,
* size: [50, 50]
* });
* path.tween({
* easing: 'easeInOutCubic',
* rotation: 180
* }, 2000);
*/ */
/** /**
* Tween item to a state. * Tween item to a state.
@ -4771,7 +4786,7 @@ new function() { // Injection scope for hit-test functions shared with project
), ),
tween = new Tween(this, from, to, duration, easing, start); tween = new Tween(this, from, to, duration, easing, start);
function onFrame(event) { function onFrame(event) {
tween.handleFrame(event.time * 1000); tween._handleFrame(event.time * 1000);
if (!tween.running) { if (!tween.running) {
this.off('frame', onFrame); this.off('frame', onFrame);
} }
@ -4787,14 +4802,14 @@ new function() { // Injection scope for hit-test functions shared with project
* Tween item to a state. * Tween item to a state.
* *
* @function * @function
* @param {Object} state the state at the end of the tweening * @param {Object} to the state at the end of the tweening
* @param {Object|Number} options the options or the duration * @param {Object|Number} options the options or the duration
* @return {Tween} * @return {Tween}
* *
* @see Item#tween(to, options) * @see Item#tween(to, options)
*/ */
tweenTo: function(state, options) { tweenTo: function(to, options) {
return this.tween(null, state, options); return this.tween(null, to, options);
}, },
/** /**
@ -4802,7 +4817,7 @@ new function() { // Injection scope for hit-test functions shared with project
* Tween item from a state to its state before the tweening. * Tween item from a state to its state before the tweening.
* *
* @function * @function
* @param {Object} state the state at the start of the tweening * @param {Object} from the state at the start of the tweening
* @param {Object|Number} options the options or the duration * @param {Object|Number} options the options or the duration
* @return {Tween} * @return {Tween}
* *
@ -4817,7 +4832,7 @@ new function() { // Injection scope for hit-test functions shared with project
* }); * });
* path.tweenFrom({ fillColor: 'red' }, { duration: 1000 }); * path.tweenFrom({ fillColor: 'red' }, { duration: 1000 });
*/ */
tweenFrom: function(state, options) { tweenFrom: function(from, options) {
return this.tween(state, null, options); return this.tween(from, null, options);
} }
}); });

View file

@ -65,7 +65,6 @@ var paper = function(self, undefined) {
/*#*/ include('item/SymbolItem.js'); /*#*/ include('item/SymbolItem.js');
/*#*/ include('item/SymbolDefinition.js'); /*#*/ include('item/SymbolDefinition.js');
/*#*/ include('item/HitResult.js'); /*#*/ include('item/HitResult.js');
/*#*/ include('item/Tween.js');
/*#*/ include('path/Segment.js'); /*#*/ include('path/Segment.js');
/*#*/ include('path/SegmentPoint.js'); /*#*/ include('path/SegmentPoint.js');
@ -103,6 +102,8 @@ var paper = function(self, undefined) {
/*#*/ include('tool/ToolEvent.js'); /*#*/ include('tool/ToolEvent.js');
/*#*/ include('tool/Tool.js'); /*#*/ include('tool/Tool.js');
/*#*/ include('anim/Tween.js');
/*#*/ include('net/Http.js'); /*#*/ include('net/Http.js');
/*#*/ include('canvas/CanvasProvider.js'); /*#*/ include('canvas/CanvasProvider.js');