From 760952ee6e7734e22d9b644f7f1bbb907213586e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 24 Sep 2016 01:31:52 +0200 Subject: polishing chrome badge --- lib/wallet/chromeBadge.ts | 115 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 20 deletions(-) diff --git a/lib/wallet/chromeBadge.ts b/lib/wallet/chromeBadge.ts index 7ff36154c..79a2d6c10 100644 --- a/lib/wallet/chromeBadge.ts +++ b/lib/wallet/chromeBadge.ts @@ -26,37 +26,72 @@ import { function rAF(cb: (ts: number) => void) { window.setTimeout(() => { cb(performance.now()); - }, 100); + }, 100 /* 100 ms delay between frames */ ); } export class ChromeBadge implements Badge { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D; - talerLogo: HTMLImageElement; /** * True if animation running. The animation * might still be running even if we're not busy anymore, * just to transition to the "normal" state in a animated way. */ animationRunning: boolean = false; + + /** + * Is the wallet still busy? Note that we do not stop the + * animation immediately when the wallet goes idle, but + * instead slowly close the gap. + */ isBusy: boolean = false; + + /** + * Current rotation angle, ranges from 0 to rotationAngleMax. + */ rotationAngle: number = 0; + + /** + * While animating, how wide is the current gap in the circle? + * Ranges from 0 to openMax. + */ + gapWidth: number = 0; + + /** + * Maximum value for our rotationAngle, corresponds to 2 Pi. + */ static rotationAngleMax = 1000; + /** + * How fast do we rotate? Given in rotation angle (relative to rotationAngleMax) per millisecond. + */ + static rotationSpeed = 0.5; + + /** + * How fast to we open? Given in rotation angle (relative to rotationAngleMax) per millisecond. + */ + static openSpeed = 0.15; + + /** + * How fast to we close? Given as a multiplication factor per frame update. + */ + static closeSpeed = 0.7; + + /** + * How far do we open? Given relative to rotationAngleMax. + */ + static openMax = 100; + constructor(window?: Window) { // Allow injecting another window for testing let bg = window || chrome.extension.getBackgroundPage(); this.canvas = bg.document.createElement("canvas"); + // Note: changing the width here means changing the font + // size in draw() as well! this.canvas.width = 32; this.canvas.height = 32; this.ctx = this.canvas.getContext("2d")!; - - this.talerLogo = bg.document.getElementById("taler-logo") as HTMLImageElement; - if (!(this.talerLogo instanceof HTMLImageElement)) { - throw Error(); - } - this.draw(); } @@ -66,16 +101,39 @@ export class ChromeBadge implements Badge { private draw() { // Reset to identity this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.ctx.setTransform(1, 0, 0, 1, 0, 0); - this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2); - this.ctx.rotate(this.rotationAngle / ChromeBadge.rotationAngleMax * Math.PI * 2); + // move into the center, off by 1 for aligning the "T" with the bottom + // of the circle. + this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2 + 1); + // pick sans-serif font; note: 14px is based on the 32px width above! + this.ctx.font = "14px sans-serif"; + // draw the "T" perfectly centered (x and y) to the current position + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "middle"; + this.ctx.strokeText("T", 0, 0); + // now move really into the center + this.ctx.translate(0, -1); + // start drawing the (possibly open) circle + this.ctx.beginPath(); + if (this.animationRunning) { + /* Draw circle around the "T" with an opening of this.gapWidth */ + this.ctx.arc (0, 0, + this.canvas.width / 4, /* radius */ + this.rotationAngle / ChromeBadge.rotationAngleMax * Math.PI * 2, + ((this.rotationAngle + ChromeBadge.rotationAngleMax - this.gapWidth) / ChromeBadge.rotationAngleMax) * Math.PI * 2, + false); + } + else + { + /* Draw full circle */ + this.ctx.arc (0, 0, + this.canvas.width / 4, /* radius */ + 0, + Math.PI * 2, + false); + } + this.ctx.stroke(); + // go back to the origin this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2); - this.ctx.drawImage(this.talerLogo, - 0, - 0, - this.talerLogo.width, - this.talerLogo.height, - 0, 0, this.canvas.width, this.canvas.height); // Allow running outside the extension for testing if (chrome && chrome.browserAction) { @@ -101,13 +159,30 @@ export class ChromeBadge implements Badge { start = timestamp; } let delta = (timestamp - start); - if (!this.isBusy && this.rotationAngle + delta >= ChromeBadge.rotationAngleMax) { + if (!this.isBusy && 0 == this.gapWidth) { // stop if we're close enough to origin this.rotationAngle = 0; } else { - this.rotationAngle = (this.rotationAngle + timestamp - start) % ChromeBadge.rotationAngleMax; + this.rotationAngle = (this.rotationAngle + (timestamp - start) * ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax; } - if (this.isBusy || this.rotationAngle != 0) { + if (this.isBusy) + { + if (this.gapWidth < ChromeBadge.openMax) + this.gapWidth += ChromeBadge.openSpeed * (timestamp - start); + if (this.gapWidth > ChromeBadge.openMax) + this.gapWidth = ChromeBadge.openMax; + } + else + { + if (this.gapWidth > 0) + { + this.gapWidth--; + this.gapWidth *= ChromeBadge.closeSpeed; + } + } + + + if (this.isBusy || this.gapWidth > 0) { start = timestamp; rAF(step); } else { -- cgit v1.2.3