diff options
| -rw-r--r-- | app/lib/tray-animation.js | 151 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | test/tray-animator.spec.js | 175 |
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 |
