diff options
| -rw-r--r-- | gui/packages/desktop/src/main/keyframe-animation.js | 166 | ||||
| -rw-r--r-- | gui/packages/desktop/src/main/tray-icon-controller.js | 53 | ||||
| -rw-r--r-- | gui/packages/desktop/test/keyframe-animation.spec.js | 182 |
3 files changed, 75 insertions, 326 deletions
diff --git a/gui/packages/desktop/src/main/keyframe-animation.js b/gui/packages/desktop/src/main/keyframe-animation.js index 028c95769a..fe79eb35a4 100644 --- a/gui/packages/desktop/src/main/keyframe-animation.js +++ b/gui/packages/desktop/src/main/keyframe-animation.js @@ -1,35 +1,24 @@ // @flow -import { nativeImage } from 'electron'; -import type { NativeImage } from 'electron'; - -export type OnFrameFn = (image: NativeImage) => void; +export type OnFrameFn = (frame: number) => void; export type OnFinishFn = (void) => void; export type KeyframeAnimationOptions = { - startFrame?: number, - endFrame?: number, - beginFromCurrentState?: boolean, - advanceTo?: 'end', + start?: number, + end: number, }; export type KeyframeAnimationRange = [number, number]; export default class KeyframeAnimation { _speed: number = 200; // ms - _repeat: boolean = false; - _reverse: boolean = false; - _alternate: boolean = false; _onFrame: ?OnFrameFn; _onFinish: ?OnFinishFn; - _nativeImages: Array<NativeImage>; - _frameRange: KeyframeAnimationRange; - _numFrames: number; _currentFrame: number = 0; + _targetFrame: number = 0; _isRunning: boolean = false; _isFinished: boolean = false; - _isFirstRun: boolean = true; _timeout = null; @@ -40,7 +29,7 @@ export default class KeyframeAnimation { return this._onFrame; } - // called when animation finished for non-repeating animations. + // called when animation finished set onFinish(newValue: ?OnFinishFn) { this._onFinish = newValue; } @@ -56,106 +45,18 @@ export default class KeyframeAnimation { return this._speed; } - set repeat(newValue: boolean) { - this._repeat = newValue; - } - get repeat(): boolean { - return this._repeat; - } - - set reverse(newValue: boolean) { - this._reverse = newValue; - } - get reverse(): boolean { - return this._repeat; - } - - // alternates the animation direction when it reaches the end - // only for repeating animations - set alternate(newValue: boolean) { - this._alternate = !!newValue; - } - get alternate(): boolean { - return this._alternate; - } - - get nativeImages(): Array<NativeImage> { - return this._nativeImages.slice(); - } get isFinished(): boolean { return this._isFinished; } - // create animation from files matching filename pattern. i.e (bubble-frame-{}.png) - static fromFilePattern(filePattern: string, range: KeyframeAnimationRange): KeyframeAnimation { - const images: Array<NativeImage> = []; + play(options: KeyframeAnimationOptions) { + const { start, end } = options; - if (range.length !== 2 || range[0] > range[1]) { - throw new Error('the animation range is invalid'); + if (start !== undefined) { + this._currentFrame = start; } - for (let i = range[0]; i <= range[1]; i++) { - const filePath = filePattern.replace('{}', i.toString()); - const image = nativeImage.createFromPath(filePath); - images.push(image); - } - return new KeyframeAnimation(images); - } - - static fromFileSequence(files: Array<string>): KeyframeAnimation { - const images: Array<NativeImage> = files.map((filePath) => - nativeImage.createFromPath(filePath), - ); - return new KeyframeAnimation(images); - } - - constructor(images: Array<NativeImage>) { - const len = images.length; - if (len < 1) { - throw new Error('too few images in animation'); - } - - this._nativeImages = images.slice(); - this._numFrames = len; - this._frameRange = [0, len]; - } - - get currentImage(): NativeImage { - return this._nativeImages[this._currentFrame]; - } - - play(options: KeyframeAnimationOptions = {}) { - const { startFrame, endFrame, beginFromCurrentState, advanceTo } = options; - - if (startFrame !== undefined && endFrame !== undefined) { - if (startFrame < 0 || startFrame >= this._numFrames) { - throw new Error('Invalid start frame'); - } - - if (endFrame < 0 || endFrame >= this._numFrames) { - throw new Error('Invalid end frame'); - } - - if (startFrame < endFrame) { - this._frameRange = [startFrame, endFrame]; - } else { - this._frameRange = [endFrame, startFrame]; - } - } else { - this._frameRange = [0, this._numFrames - 1]; - } - - if (!beginFromCurrentState || this._isFirstRun) { - this._currentFrame = this._frameRange[this._reverse ? 1 : 0]; - } - - if (this._isFirstRun) { - this._isFirstRun = false; - } - - if (advanceTo === 'end') { - this._currentFrame = this._frameRange[this._reverse ? 0 : 1]; - } + this._targetFrame = end; this._isRunning = true; this._isFinished = false; @@ -184,12 +85,13 @@ export default class KeyframeAnimation { _render() { if (this._onFrame) { - this._onFrame(this._nativeImages[this._currentFrame]); + this._onFrame(this._currentFrame); } } _didFinish() { this._isFinished = true; + this._isRunning = false; if (this._onFinish) { this._onFinish(); @@ -199,10 +101,7 @@ export default class KeyframeAnimation { _onUpdateFrame() { this._advanceFrame(); - if (this._isFinished) { - // mark animation as not running when finished - this._isRunning = false; - } else { + if (!this._isFinished) { this._render(); // check once again since onFrame() may stop animation @@ -217,41 +116,12 @@ export default class KeyframeAnimation { return; } - const lastFrame = this._frameRange[this._reverse ? 0 : 1]; - if (this._currentFrame === lastFrame) { - // mark animation as finished if it's not repeating - if (!this._repeat) { - this._didFinish(); - return; - } - - // change animation direction if marked for alternation - if (this._alternate) { - this._reverse = !this._reverse; - - this._currentFrame = this._nextFrame(this._currentFrame, this._frameRange, this._reverse); - } else { - this._currentFrame = this._frameRange[this._reverse ? 1 : 0]; - } - } else { - this._currentFrame = this._nextFrame(this._currentFrame, this._frameRange, this._reverse); - } - } - - _nextFrame(cur: number, frameRange: KeyframeAnimationRange, isReverse: boolean): number { - if (isReverse) { - if (cur < frameRange[0]) { - return cur + 1; - } else if (cur > frameRange[0]) { - return cur - 1; - } + if (this._currentFrame === this._targetFrame) { + this._didFinish(); + } else if (this._currentFrame < this._targetFrame) { + this._currentFrame += 1; } else { - if (cur > frameRange[1]) { - return cur - 1; - } else if (cur < frameRange[1]) { - return cur + 1; - } + this._currentFrame -= 1; } - return cur; } } diff --git a/gui/packages/desktop/src/main/tray-icon-controller.js b/gui/packages/desktop/src/main/tray-icon-controller.js index 54187177f5..c6f1e4d321 100644 --- a/gui/packages/desktop/src/main/tray-icon-controller.js +++ b/gui/packages/desktop/src/main/tray-icon-controller.js @@ -2,22 +2,27 @@ import path from 'path'; import KeyframeAnimation from './keyframe-animation'; -import type { Tray } from 'electron'; +import { nativeImage } from 'electron'; +import type { NativeImage, Tray } from 'electron'; export type TrayIconType = 'unsecured' | 'securing' | 'secured'; export default class TrayIconController { _animation: ?KeyframeAnimation; _iconType: TrayIconType; + _iconImages: Array<NativeImage>; constructor(tray: Tray, initialType: TrayIconType) { - const animation = this._createAnimation(); - animation.onFrame = (img) => tray.setImage(img); - animation.reverse = this._isReverseAnimation(initialType); - animation.play({ advanceTo: 'end' }); + this._loadImages(); + this._iconType = initialType; + + const initialFrame = this._targetFrame(); + const animation = new KeyframeAnimation(); + animation.speed = 100; + animation.onFrame = (frameNumber) => tray.setImage(this._iconImages[frameNumber]); + animation.play({ start: initialFrame, end: initialFrame }); this._animation = animation; - this._iconType = initialType; } dispose() { @@ -36,27 +41,33 @@ export default class TrayIconController { return; } + this._iconType = type; + const animation = this._animation; - if (type === 'secured') { - animation.reverse = true; - animation.play({ beginFromCurrentState: true, startFrame: 8, endFrame: 9 }); - } else { - animation.reverse = this._isReverseAnimation(type); - animation.play({ beginFromCurrentState: true }); - } + const frame = this._targetFrame(); - this._iconType = type; + animation.play({ end: frame }); } - _createAnimation(): KeyframeAnimation { + _loadImages() { const basePath = path.resolve(path.join(__dirname, '../assets/images/menubar icons')); - const filePath = path.join(basePath, 'lock-{}.png'); - const animation = KeyframeAnimation.fromFilePattern(filePath, [1, 10]); - animation.speed = 100; - return animation; + const frames = Array.from({ length: 10 }, (_, i) => i + 1); + + this._iconImages = frames.map((frame) => + nativeImage.createFromPath(path.join(basePath, `lock-${frame}.png`)), + ); } - _isReverseAnimation(type: TrayIconType): boolean { - return type === 'unsecured'; + _targetFrame(): number { + switch (this._iconType) { + case 'unsecured': + return 0; + case 'securing': + return 9; + case 'secured': + return 8; + default: + throw new Error(`Unknown tray icon type: ${(this._iconType: empty)}`); + } } } diff --git a/gui/packages/desktop/test/keyframe-animation.spec.js b/gui/packages/desktop/test/keyframe-animation.spec.js index 84bfb51277..b893c98572 100644 --- a/gui/packages/desktop/test/keyframe-animation.spec.js +++ b/gui/packages/desktop/test/keyframe-animation.spec.js @@ -1,14 +1,12 @@ // @flow import KeyframeAnimation from '../src/main/keyframe-animation'; -import { nativeImage } from 'electron'; describe('lib/keyframe-animation', function() { this.timeout(1000); const newAnimation = () => { - const images = [1, 2, 3, 4, 5].map(() => nativeImage.createEmpty()); - const animation = new KeyframeAnimation(images); + const animation = new KeyframeAnimation(); animation.speed = 1; return animation; }; @@ -16,8 +14,8 @@ describe('lib/keyframe-animation', function() { it('should play sequence', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([0, 1, 2, 3, 4]); @@ -25,14 +23,14 @@ describe('lib/keyframe-animation', function() { done(); }; - animation.play(); + animation.play({ end: 4 }); }); it('should play one frame', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([3]); @@ -40,14 +38,14 @@ describe('lib/keyframe-animation', function() { done(); }; - animation.play({ startFrame: 3, endFrame: 3 }); + animation.play({ start: 3, end: 3 }); }); it('should play sequence with custom frames', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([2, 3, 4]); @@ -55,17 +53,14 @@ describe('lib/keyframe-animation', function() { done(); }; - animation.play({ - startFrame: 2, - endFrame: 4, - }); + animation.play({ start: 2, end: 4 }); }); it('should play sequence with custom frames in reverse', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([4, 3, 2]); @@ -73,18 +68,14 @@ describe('lib/keyframe-animation', function() { done(); }; - animation.reverse = true; - animation.play({ - startFrame: 4, - endFrame: 2, - }); + animation.play({ start: 4, end: 2 }); }); it('should begin from current state starting below range', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([0, 1, 2, 3, 4]); @@ -93,43 +84,14 @@ describe('lib/keyframe-animation', function() { }; animation._currentFrame = 0; - animation._isFirstRun = false; - - animation.play({ - beginFromCurrentState: true, - startFrame: 3, - endFrame: 4, - }); - }); - - it('should begin from current state starting below range reverse', (done) => { - const seq = []; - const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); - }; - animation.onFinish = () => { - expect(seq).to.be.deep.equal([0, 1, 2, 3]); - expect(animation._currentFrame).to.be.equal(3); - done(); - }; - - animation._currentFrame = 0; - animation._isFirstRun = false; - animation.reverse = true; - - animation.play({ - beginFromCurrentState: true, - startFrame: 3, - endFrame: 4, - }); + animation.play({ end: 4 }); }); it('should begin from current state starting above range', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([4, 3, 2]); @@ -138,20 +100,14 @@ describe('lib/keyframe-animation', function() { }; animation._currentFrame = 4; - animation._isFirstRun = false; - - animation.play({ - beginFromCurrentState: true, - startFrame: 1, - endFrame: 2, - }); + animation.play({ end: 2 }); }); it('should begin from current state starting above range reverse', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([4, 3, 2, 1]); @@ -160,21 +116,14 @@ describe('lib/keyframe-animation', function() { }; animation._currentFrame = 4; - animation._isFirstRun = false; - animation.reverse = true; - - animation.play({ - beginFromCurrentState: true, - startFrame: 1, - endFrame: 3, - }); + animation.play({ end: 1 }); }); it('should play sequence in reverse', (done) => { const seq = []; const animation = newAnimation(); - animation.onFrame = () => { - seq.push(animation._currentFrame); + animation.onFrame = (frame) => { + seq.push(frame); }; animation.onFinish = () => { expect(seq).to.be.deep.equal([4, 3, 2, 1, 0]); @@ -182,87 +131,6 @@ describe('lib/keyframe-animation', function() { done(); }; - animation.reverse = true; - animation.play(); - }); - - it('should play sequence on repeat', (done) => { - const seq = []; - const animation = newAnimation(); - const expectedFrames = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]; - - animation.onFrame = () => { - if (seq.length === expectedFrames.length) { - animation.stop(); - expect(seq).to.be.deep.equal(expectedFrames); - done(); - } else { - seq.push(animation._currentFrame); - } - }; - - animation.repeat = true; - animation.play(); - }); - - it('should play sequence on repeat in reverse', (done) => { - const seq = []; - const animation = newAnimation(); - const expectedFrames = [4, 3, 2, 1, 0, 4, 3, 2, 1, 0]; - - animation.onFrame = () => { - if (seq.length === expectedFrames.length) { - animation.stop(); - expect(seq).to.be.deep.equal(expectedFrames); - done(); - } else { - seq.push(animation._currentFrame); - } - }; - - animation.repeat = true; - animation.reverse = true; - animation.play(); - }); - - it('should alternate sequence', (done) => { - const seq = []; - const animation = newAnimation(); - const expectedFrames = [0, 1, 2, 3, 4, 3, 2, 1, 0]; - - animation.onFrame = () => { - if (seq.length === expectedFrames.length) { - animation.stop(); - expect(seq).to.be.deep.equal(expectedFrames); - done(); - } else { - seq.push(animation._currentFrame); - } - }; - - animation.repeat = true; - animation.alternate = true; - animation.play(); - }); - - it('should alternate reverse sequence', (done) => { - const seq = []; - const animation = newAnimation(); - const expectedFrames = [4, 3, 2, 1, 0, 1, 2, 3, 4]; - - animation.onFrame = () => { - if (seq.length === expectedFrames.length) { - animation.stop(); - expect(seq).to.be.deep.equal(expectedFrames); - done(); - } else { - seq.push(animation._currentFrame); - } - }; - - animation.repeat = true; - animation.reverse = true; - animation.alternate = true; - animation.play(); + animation.play({ start: 4, end: 0 }); }); }); |
