summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gui/packages/desktop/src/main/keyframe-animation.js166
-rw-r--r--gui/packages/desktop/src/main/tray-icon-controller.js53
-rw-r--r--gui/packages/desktop/test/keyframe-animation.spec.js182
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 });
});
});