Morphic: First Class Animations

pull/29/head
jmoenig 2016-11-25 14:40:28 +01:00
rodzic 3c43d59bc3
commit 3d8ef5519c
2 zmienionych plików z 244 dodań i 23 usunięć

Wyświetl plik

@ -55,7 +55,8 @@
(7) turtle graphics (7) turtle graphics
(8) damage list housekeeping (8) damage list housekeeping
(9) supporting high-resolution "retina" screens (9) supporting high-resolution "retina" screens
(10) minifying morphic.js (10 animations
(11) minifying morphic.js
VIII. acknowledgements VIII. acknowledgements
IX. contributors IX. contributors
@ -66,6 +67,7 @@
indentation indicating inheritance. Refer to this list to get a indentation indicating inheritance. Refer to this list to get a
contextual overview: contextual overview:
Animation
Color Color
Node Node
Morph Morph
@ -108,6 +110,7 @@
Global settings Global settings
Global functions Global functions
Animation
Color Color
Point Point
Rectangle Rectangle
@ -1035,7 +1038,37 @@
stage (high-resolution) into a sprite-costume (normal resolution). stage (high-resolution) into a sprite-costume (normal resolution).
(10) minifying morphic.js (10) animations
---------------
Animations handle gradual transitions between one state and another over a
period of time. Transition effects can be specified using easing functions.
An easing function maps a fraction of the transition time to a fraction of
the state delta. This way accelerating / decelerating and bouncing sliding
effects can be accomplished.
Animations are generic and not limited to motion, i.e. they can also handle
other transitions such as color changes, transparency fadings, growing,
shrinking, turning etc.
Animations need to be stepped by a scheduler, e. g. an interval function.
In Morphic the preferred way to run an animation is to register it with
the World by adding it to the World's animation queue. The World steps each
registered animation once per display cycle independently of the Morphic
stepping mechanism.
For an example how to use animations look at how the Morph's methods
glideTo()
fadeTo()
and
slideBackTo()
are implemented.
(11) minifying morphic.js
------------------------- -------------------------
Coming from Smalltalk and being a Squeaker at heart I am a huge fan Coming from Smalltalk and being a Squeaker at heart I am a huge fan
of browsing the code itself to make sense of it. Therefore I have of browsing the code itself to make sense of it. Therefore I have
@ -1103,7 +1136,7 @@
/*global window, HTMLCanvasElement, FileReader, Audio, FileList*/ /*global window, HTMLCanvasElement, FileReader, Audio, FileList*/
var morphicVersion = '2016-November-24'; var morphicVersion = '2016-November-25';
var modules = {}; // keep track of additional loaded modules var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@ -1732,6 +1765,95 @@ function normalizeCanvas(aCanvas, getCopy) {
return aCanvas; return aCanvas;
} }
// Animations //////////////////////////////////////////////////////////////
/*
Animations handle gradual transitions between one state and another over a
period of time. Transition effects can be specified using easing functions.
An easing function maps a fraction of the transition time to a fraction of
the state delta. This way accelerating / decelerating and bouncing sliding
effects can be accomplished.
Animations are generic and not limited to motion, i.e. they can also handle
other transitions such as color changes, transparency fadings, growing,
shrinking, turning etc.
Animations need to be stepped by a scheduler, e. g. an interval function.
In Morphic the preferred way to run an animation is to register it with
the World by adding it to the World's animation queue. The World steps each
registered animation once per display cycle independently of the Morphic
stepping mechanism.
For an example how to use animations look at how the Morph's methods
glideTo()
fadeTo()
and
slideBackTo()
are implemented.
*/
// Animation instance creation:
function Animation(setter, getter, delta, duration, easing, onComplete) {
this.setter = setter; // function
this.getter = getter; // function
this.delta = delta || 0; // number
this.duration = duration || 0; // milliseconds
this.easing = isString(easing) ? // string or function
this.easings[easing] || this.easings.sinusoidal
: easing || this.easings.sinusoidal;
this.onComplete = onComplete || null; // optional callback
this.endTime = null;
this.destination = null;
this.isActive = false;
this.start();
}
Animation.prototype.easings = {
// dictionary of a few pre-defined easing functions used to transgress the
//
linear: function (t) {return t; },
sinusoidal: function (t) {return 1 - Math.cos(radians(t * 90)); },
quadratic: function (t) {
return t < 0.5 ?
2 * t * t
: ((4 - (2 * t)) * t) - 1;
},
cubic: function (t) {
return t < 0.5 ?
4 * t * t * t
: ((t - 1) * ((2 * t) - 2) * ((2 * t) - 2)) + 1;
}
};
Animation.prototype.start = function () {
// (re-) activate the animation, e.g. if is has previously completed,
// make sure to plug it into something that repeatedly triggers step(),
// e.g. the World's animations queue
this.endTime = Date.now() + this.duration;
this.destination = this.getter.call(this) + this.delta;
this.isActive = true;
};
Animation.prototype.step = function () {
if (!this.isActive) {return; }
var now = Date.now();
if (now > this.endTime) {
this.setter(this.destination);
this.isActive = false;
if (this.onComplete) {this.onComplete(); }
} else {
this.setter(
this.destination -
(this.delta * this.easing((this.endTime - now) / this.duration))
);
}
};
// Colors ////////////////////////////////////////////////////////////// // Colors //////////////////////////////////////////////////////////////
// Color instance creation: // Color instance creation:
@ -3613,31 +3735,88 @@ Morph.prototype.situation = function () {
return null; return null;
}; };
Morph.prototype.slideBackTo = function (situation, inSteps, onBeforeDrop) { Morph.prototype.slideBackTo = function (
var steps = inSteps || 5, situation,
pos = situation.origin.position().add(situation.position), msecs,
xStep = -(this.left() - pos.x) / steps, onBeforeDrop,
yStep = -(this.top() - pos.y) / steps, onComplete
stepCount = 0, ) {
oldStep = this.step, var pos = situation.origin.position().add(situation.position),
oldFps = this.fps,
myself = this; myself = this;
this.glideTo(
this.fps = 0; pos,
this.step = function () { msecs,
myself.moveBy(new Point(xStep, yStep)); null, // easing
stepCount += 1; function () {
if (stepCount === steps) {
situation.origin.add(myself); situation.origin.add(myself);
if (onBeforeDrop) {onBeforeDrop(); } if (onBeforeDrop) {onBeforeDrop(); }
if (myself.justDropped) {myself.justDropped(); } if (myself.justDropped) {myself.justDropped(); }
if (situation.origin.reactToDropOf) { if (situation.origin.reactToDropOf) {
situation.origin.reactToDropOf(myself); situation.origin.reactToDropOf(myself);
} }
myself.step = oldStep; if (onComplete) {onComplete(); }
myself.fps = oldFps;
} }
}; );
};
// Morph animating:
Morph.prototype.glideTo = function (endPoint, msecs, easing, onComplete) {
var world = this.world(),
myself = this;
world.animations.push(new Animation(
function (x) {myself.setLeft(x); },
function () {return myself.left(); },
-(this.left() - endPoint.x),
msecs || 100,
easing,
onComplete
));
world.animations.push(new Animation(
function (y) {myself.setTop(y); },
function () {return myself.top(); },
-(this.top() - endPoint.y),
msecs || 100,
easing
));
};
Morph.prototype.fadeTo = function (endAlpha, msecs, easing, onComplete) {
// include all my children, restore all original transparencies
// on completion, so I can be recovered
var world = this.world(),
myself = this,
oldAlpha = this.alpha;
this.children.forEach(function (child) {
child.fadeTo(endAlpha, msecs, easing);
});
world.animations.push(new Animation(
function (n) {
myself.alpha = n;
myself.changed();
},
function () {return myself.alpha; },
endAlpha - this.alpha,
msecs || 200,
easing,
function () {
myself.alpha = oldAlpha;
if (onComplete) {onComplete(); }
}
));
};
Morph.prototype.perish = function (msecs, onComplete) {
var myself = this;
this.fadeTo(
0,
msecs || 100,
null,
function () {
myself.destroy();
if (onComplete) {onComplete(); }
}
);
}; };
// Morph utilities: // Morph utilities:
@ -10764,6 +10943,7 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) {
} }
this.isDevMode = false; this.isDevMode = false;
this.broken = []; this.broken = [];
this.animations = [];
this.hand = new HandMorph(this); this.hand = new HandMorph(this);
this.keyboardReceiver = null; this.keyboardReceiver = null;
this.cursor = null; this.cursor = null;
@ -10801,6 +10981,13 @@ WorldMorph.prototype.updateBroken = function () {
this.broken = []; this.broken = [];
}; };
WorldMorph.prototype.stepAnimations = function () {
this.animations.forEach(function (anim) {anim.step(); });
this.animations = this.animations.filter(function (anim) {
return anim.isActive;
});
};
WorldMorph.prototype.condenseDamages = function () { WorldMorph.prototype.condenseDamages = function () {
// collapse clustered damaged rectangles into their unions, // collapse clustered damaged rectangles into their unions,
// thereby reducing the array of brokens to a manageable size // thereby reducing the array of brokens to a manageable size
@ -10831,6 +11018,7 @@ WorldMorph.prototype.condenseDamages = function () {
WorldMorph.prototype.doOneCycle = function () { WorldMorph.prototype.doOneCycle = function () {
this.stepFrame(); this.stepFrame();
this.stepAnimations();
this.updateBroken(); this.updateBroken();
}; };

Wyświetl plik

@ -9,7 +9,7 @@
Copyright (C) 2016 by Jens Mönig Copyright (C) 2016 by Jens Mönig
this documentation last changed: July 14, 2016 this documentation last changed: November 25, 2016
This file is part of Snap!. This file is part of Snap!.
@ -56,7 +56,8 @@
(7) turtle graphics (7) turtle graphics
(8) damage list housekeeping (8) damage list housekeeping
(9) supporting high-resolution "retina" screens (9) supporting high-resolution "retina" screens
(10) minifying morphic.js (10 animations
(11) minifying morphic.js
VIII. acknowledgements VIII. acknowledgements
IX. contributors IX. contributors
@ -67,6 +68,7 @@
indentation indicating inheritance. Refer to this list to get a indentation indicating inheritance. Refer to this list to get a
contextual overview: contextual overview:
Animation
Color Color
Node Node
Morph Morph
@ -109,6 +111,7 @@
Global settings Global settings
Global functions Global functions
Animation
Color Color
Point Point
Rectangle Rectangle
@ -1036,7 +1039,37 @@
stage (high-resolution) into a sprite-costume (normal resolution). stage (high-resolution) into a sprite-costume (normal resolution).
(10) minifying morphic.js (10) animations
---------------
Animations handle gradual transitions between one state and another over a
period of time. Transition effects can be specified using easing functions.
An easing function maps a fraction of the transition time to a fraction of
the state delta. This way accelerating / decelerating and bouncing sliding
effects can be accomplished.
Animations are generic and not limited to motion, i.e. they can also handle
other transitions such as color changes, transparency fadings, growing,
shrinking, turning etc.
Animations need to be stepped by a scheduler, e. g. an interval function.
In Morphic the preferred way to run an animation is to register it with
the World by adding it to the World's animation queue. The World steps each
registered animation once per display cycle independently of the Morphic
stepping mechanism.
For an example how to use animations look at how the Morph's methods
glideTo()
fadeTo()
and
slideBackTo()
are implemented.
(11) minifying morphic.js
------------------------- -------------------------
Coming from Smalltalk and being a Squeaker at heart I am a huge fan Coming from Smalltalk and being a Squeaker at heart I am a huge fan
of browsing the code itself to make sense of it. Therefore I have of browsing the code itself to make sense of it. Therefore I have