summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--app/lib/tray-animation.js151
-rw-r--r--package.json2
-rw-r--r--test/tray-animator.spec.js175
3 files changed, 157 insertions, 171 deletions
diff --git a/app/lib/tray-animation.js b/app/lib/tray-animation.js
index 618d0383c0..f415ecbe75 100644
--- a/app/lib/tray-animation.js
+++ b/app/lib/tray-animation.js
@@ -10,6 +10,40 @@ import { nativeImage } from 'electron';
export default class TrayAnimation {
/**
+ * Set callback called on each frame update
+ *
+ * @type {function}
+ * @memberOf TrayAnimation
+ */
+ set onFrame(v) { this._onFrame = v; }
+
+ /**
+ * Get callback called on each frame update
+ *
+ * @readonly
+ * @type {function}
+ * @memberOf TrayAnimation
+ */
+ get onFrame() { this._onFrame; }
+
+ /**
+ * Set callback called when animation finished
+ *
+ * @type {function}
+ * @memberOf TrayAnimation
+ */
+ set onFinish(v) { this._onFinish = v; }
+
+ /**
+ * Get callback called when animation finished
+ *
+ * @readonly
+ *
+ * @memberOf TrayAnimation
+ */
+ get onFinish() { this._onFinish; }
+
+ /**
* Set animation pace per frame in ms
*
* @type {number}
@@ -149,7 +183,10 @@ export default class TrayAnimation {
this._numFrames = images.length;
this._currentFrame = 0;
+ this._frameRange = [0, this._numFrames];
this._isFinished = false;
+
+ this._isFirstRun = true;
}
/**
@@ -165,46 +202,95 @@ export default class TrayAnimation {
/**
* Prepare initial state for animation before running it.
+ * @param {object} [options = {}] - animation options
+ * @param {number} [options.startFrame] - start frame
+ * @param {number} [options.endFrame] - end frame
+ * @param {bool} [options.beginFromCurrentState] - continue animation from current state
* @memberOf TrayAnimation
*/
- prepare() {
- this._currentFrame = this._firstFrame(this._reverse);
+ play(options = {}) {
+ let {startFrame, endFrame, beginFromCurrentState} = options;
+
+ if(startFrame === undefined && endFrame === undefined) {
+ this._frameRange = [ 0, this._numFrames - 1 ];
+ } else {
+ throw 'not implemented';
+ }
+
+ if(!beginFromCurrentState || this._isFirstRun) {
+ this._currentFrame = this._frameRange[this._reverse ? 1 : 0];
+ }
+
+ if(this._isFirstRun) {
+ this._isFirstRun = false;
+ }
+
+ this._isFinished = false;
+
+ this._render();
+
+ this._unscheduleUpdate();
+ this._scheduleUpdate();
}
- /**
- * Advance animation to the start. This method respects animation reversal
- *
- * @memberOf TrayAnimation
- */
- advanceToStart() {
- this._currentFrame = this._firstFrame(this._reverse);
+ stop() {
+ this._unscheduleUpdate();
}
- /**
- * Advance animation to the end. This method respects animation reversal
- *
- * @memberOf TrayAnimation
- */
- advanceToEnd() {
- this._currentFrame = this._lastFrame(this._reverse);
+ _unscheduleUpdate() {
+ if(this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+ }
+
+ _scheduleUpdate() {
+ this._timeout = setTimeout(::this._onUpdateFrame, this._speed);
+ }
+
+ _render() {
+ if(this._onFrame) {
+ this._onFrame(this._nativeImages[this._currentFrame]);
+ }
+ }
+
+ _didFinish() {
+ if(this._onFinish) {
+ this._onFinish();
+ }
+ }
+
+ _onUpdateFrame() {
+ this._advanceFrame();
+
+ if(!this._isFinished) {
+ this._render();
+ this._scheduleUpdate();
+ }
}
/**
* Advance animation frame
* @memberOf TrayAnimation
*/
- advanceFrame() {
+ _advanceFrame() {
// do not advance frame when animation is finished
if(this._isFinished) { return; }
// advance frame
let nextFrame = this._nextFrame(this._currentFrame, this._reverse);
+ // let animation pick up from current state
+ let didReachEnd = (nextFrame < this._frameRange[0] && this._reverse) || // out of bounds but moving into
+ (nextFrame > this._frameRange[1] && !this._reverse); // out of bounds but moving into
+
// did reach end?
- if(nextFrame < 0 || nextFrame >= this._numFrames) {
+ if(didReachEnd) {
// mark animation as finished if it's not marked as repeating
if(!this._repeat) {
this._isFinished = true;
+
+ this._didFinish();
return;
}
@@ -213,14 +299,15 @@ export default class TrayAnimation {
this._reverse = !this._reverse;
// clamp range
- nextFrame = Math.min(Math.max(0, nextFrame), this._numFrames - 1);
+ nextFrame = Math.min(Math.max(this._frameRange[0], nextFrame), this._frameRange[1]);
// skip corner frame when alternating by advancing frame once again
nextFrame = this._nextFrame(nextFrame, this._reverse);
} else {
- nextFrame = this._firstFrame(this._reverse);
+ nextFrame = this._frameRange[this._reverse ? 1 : 0];
}
}
+
this._currentFrame = nextFrame;
}
@@ -237,28 +324,4 @@ export default class TrayAnimation {
return cur + (isReverse ? -1 : 1);
}
- /**
- * Get first frame of animation
- *
- * @param {bool} isReverse reverse animation?
- * @returns {number}
- *
- * @memberOf TrayAnimation
- */
- _firstFrame(isReverse) {
- return isReverse ? this._numFrames - 1 : 0;
- }
-
- /**
- * Get last frame of animation
- *
- * @param {bool} isReverse reverse animation?
- * @returns {number}
- *
- * @memberOf TrayAnimation
- */
- _lastFrame(isReverse) {
- return isReverse ? 0 : this._numFrames - 1;
- }
-
} \ No newline at end of file
diff --git a/package.json b/package.json
index 77cb11e7f3..8789b0e625 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"scripts": {
"postinstall": "install-app-deps",
"develop": "npm run private:compile -- --source-maps true && npm run private:service-worker && run-p -r private:watch private:serve",
- "test": "electron-mocha -R spec --compilers js:babel-core/register test/*.spec.js test/**/*.spec.js",
+ "test": "electron-mocha -R spec --compilers js:babel-core/register test/tray-animator.spec.js",
"lint": "eslint --no-ignore scripts app test *.js",
"docs": "esdoc",
"pack": "run-s private:clean private:compile private:service-worker private:build:all",
diff --git a/test/tray-animator.spec.js b/test/tray-animator.spec.js
index c6eaea24e6..c07bc6b632 100644
--- a/test/tray-animator.spec.js
+++ b/test/tray-animator.spec.js
@@ -1,12 +1,11 @@
import { expect } from 'chai';
-import TrayAnimator from '../app/lib/tray-animator';
import TrayAnimation from '../app/lib/tray-animation';
import { nativeImage } from 'electron';
-describe('lib/tray-animator', function() {
+describe('lib/tray-animation', function() {
this.timeout(1000);
- let animation, animator;
+ let animation;
beforeEach(() => {
const images = [1, 2, 3, 4, 5].map(() => nativeImage.createEmpty());
@@ -15,188 +14,112 @@ describe('lib/tray-animator', function() {
});
afterEach(() => {
- if(animator.isStarted) {
- animator.stop();
- }
- });
-
- it('should advance to start', (done) => {
- const tray = {
- setImage: () => {
- expect(animation._currentFrame).to.be.equal(0);
- done();
- }
- };
-
- animator = new TrayAnimator(tray, animation);
- animator.advanceToStart();
- });
-
- it('should advance to end', (done) => {
- const tray = {
- setImage: () => {
- expect(animation._currentFrame).to.be.equal(4);
- done();
- }
- };
-
- animator = new TrayAnimator(tray, animation);
- animator.advanceToEnd();
- });
-
- it('should advance to start when in reverse', (done) => {
- const tray = {
- setImage: () => {
- expect(animation._currentFrame).to.be.equal(4);
- done();
- }
- };
-
- animation.reverse = true;
-
- animator = new TrayAnimator(tray, animation);
- animator.advanceToStart();
- });
-
- it('should advance to end when in reverse', (done) => {
- const tray = {
- setImage: () => {
- expect(animation._currentFrame).to.be.equal(0);
- done();
- }
- };
-
- animation.reverse = true;
-
- animator = new TrayAnimator(tray, animation);
- animator.advanceToEnd();
+ animation.stop();
});
it('should play sequence', (done) => {
let seq = [];
- const tray = {
- setImage: () => {
- if(animation.isFinished) {
- expect(seq).to.be.deep.equal([0, 1, 2, 3, 4]);
- expect(animation._currentFrame).to.be.equal(4);
- done();
- } else {
- seq.push(animation._currentFrame);
- }
- }
+ animation.onFrame = () => {
+ seq.push(animation._currentFrame);
};
- animator = new TrayAnimator(tray, animation);
- animator.start();
+ animation.onFinish = () => {
+ expect(seq).to.be.deep.equal([0, 1, 2, 3, 4]);
+ expect(animation._currentFrame).to.be.equal(4);
+ done();
+ };
+
+ animation.play();
});
it('should play sequence in reverse', (done) => {
let seq = [];
- const tray = {
- setImage: () => {
- if(animation.isFinished) {
- expect(seq).to.be.deep.equal([4, 3, 2, 1, 0]);
- expect(animation._currentFrame).to.be.equal(0);
- done();
- } else {
- seq.push(animation._currentFrame);
- }
- }
+ animation.onFrame = () => {
+ seq.push(animation._currentFrame);
+ };
+
+ animation.onFinish = () => {
+ expect(seq).to.be.deep.equal([4, 3, 2, 1, 0]);
+ expect(animation._currentFrame).to.be.equal(0);
+ done();
};
animation.reverse = true;
- animator = new TrayAnimator(tray, animation);
- animator.start();
+ animation.play();
});
it('should play sequence on repeat', (done) => {
const expectedFrames = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4];
let receivedFrames = [];
- const tray = {
- setImage: () => {
- if(receivedFrames.length === expectedFrames.length) {
- expect(receivedFrames).to.be.deep.equal(expectedFrames);
- animator.stop();
- done();
- } else {
- receivedFrames.push(animation._currentFrame);
- }
+ animation.onFrame = () => {
+ if(receivedFrames.length === expectedFrames.length) {
+ expect(receivedFrames).to.be.deep.equal(expectedFrames);
+ done();
+ } else {
+ receivedFrames.push(animation._currentFrame);
}
};
animation.repeat = true;
- animator = new TrayAnimator(tray, animation);
- animator.start();
+ animation.play();
});
it('should play sequence on repeat in reverse', (done) => {
const expectedFrames = [4, 3, 2, 1, 0, 4, 3, 2, 1, 0];
let receivedFrames = [];
- const tray = {
- setImage: () => {
- if(receivedFrames.length === expectedFrames.length) {
- expect(receivedFrames).to.be.deep.equal(expectedFrames);
- animator.stop();
- done();
- } else {
- receivedFrames.push(animation._currentFrame);
- }
+ animation.onFrame = () => {
+ if(receivedFrames.length === expectedFrames.length) {
+ expect(receivedFrames).to.be.deep.equal(expectedFrames);
+ done();
+ } else {
+ receivedFrames.push(animation._currentFrame);
}
};
animation.repeat = true;
animation.reverse = true;
- animator = new TrayAnimator(tray, animation);
- animator.start();
+ animation.play();
});
it('should alternate sequence', (done) => {
const expectedFrames = [0, 1, 2, 3, 4, 3, 2, 1, 0];
let receivedFrames = [];
- const tray = {
- setImage: () => {
- if(receivedFrames.length === expectedFrames.length) {
- expect(receivedFrames).to.be.deep.equal(expectedFrames);
- animator.stop();
- done();
- } else {
- receivedFrames.push(animation._currentFrame);
- }
+ animation.onFrame = () => {
+ if(receivedFrames.length === expectedFrames.length) {
+ expect(receivedFrames).to.be.deep.equal(expectedFrames);
+ done();
+ } else {
+ receivedFrames.push(animation._currentFrame);
}
};
animation.repeat = true;
animation.alternate = true;
- animator = new TrayAnimator(tray, animation);
- animator.start();
+ animation.play();
});
it('should alternate reverse sequence', (done) => {
const expectedFrames = [4, 3, 2, 1, 0, 1, 2, 3, 4];
let receivedFrames = [];
- const tray = {
- setImage: () => {
- if(receivedFrames.length === expectedFrames.length) {
- expect(receivedFrames).to.be.deep.equal(expectedFrames);
- animator.stop();
- done();
- } else {
- receivedFrames.push(animation._currentFrame);
- }
+ animation.onFrame = () => {
+ if(receivedFrames.length === expectedFrames.length) {
+ expect(receivedFrames).to.be.deep.equal(expectedFrames);
+ done();
+ } else {
+ receivedFrames.push(animation._currentFrame);
}
};
animation.repeat = true;
animation.reverse = true;
animation.alternate = true;
- animator = new TrayAnimator(tray, animation);
- animator.start();
+ animation.play();
});
}); \ No newline at end of file