|
|
|
@ -14,6 +14,17 @@ export class Gauge extends Widget {
|
|
|
|
|
private integralValueElement: HTMLElement; |
|
|
|
|
private decimalValueElement: HTMLElement; |
|
|
|
|
|
|
|
|
|
private displayedValue = 0.0; |
|
|
|
|
private targetValue = 0.0; |
|
|
|
|
private valueChangeRate = 0.0; |
|
|
|
|
private litBars = 0; |
|
|
|
|
|
|
|
|
|
private animating = false; |
|
|
|
|
private shuttingDown = false; |
|
|
|
|
|
|
|
|
|
private lastGaugeUpdate = 0.0; |
|
|
|
|
private lastAnimationTick = 0.0; |
|
|
|
|
|
|
|
|
|
constructor(vnode: any) { |
|
|
|
|
super(vnode); |
|
|
|
|
|
|
|
|
@ -25,6 +36,11 @@ export class Gauge extends Widget {
|
|
|
|
|
this.gaugeValue.onChange(() => this.updateGauge()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onbeforeremove(vnode: m.Vnode<{}, {}>) { |
|
|
|
|
this.shuttingDown = true; |
|
|
|
|
super.onbeforeremove(vnode); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
view(vnode: m.Vnode<{}, {}>): m.Children { |
|
|
|
|
return <div class="widget gauge" style={'flex: ' + this.widgetWidth}> |
|
|
|
|
<p><span class="integral-value"></span><span class="decimal-value"></span><span class="unit">{this.unit}</span></p> |
|
|
|
@ -34,6 +50,8 @@ export class Gauge extends Widget {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
updateGauge() { |
|
|
|
|
let now = Date.now() / 1000.0; |
|
|
|
|
|
|
|
|
|
let value = Math.max(0.0, Math.min(this.gaugeMaxValue, this.gaugeValue.get())); |
|
|
|
|
|
|
|
|
|
let integralValue = Math.floor(value); |
|
|
|
@ -44,10 +62,66 @@ export class Gauge extends Widget {
|
|
|
|
|
|
|
|
|
|
let ratio = value / this.gaugeMaxValue; |
|
|
|
|
let numBars = this.bars.length; |
|
|
|
|
let litBars = Math.round(ratio * numBars); |
|
|
|
|
let threshold = 0.33 / numBars; |
|
|
|
|
|
|
|
|
|
if(ratio > this.targetValue + threshold || ratio < this.targetValue - threshold) { |
|
|
|
|
let animationTime = this.lastGaugeUpdate == 0.0 ? 0.0001 : Math.max(0.0001, Math.min(0.75, now - this.lastGaugeUpdate)); |
|
|
|
|
|
|
|
|
|
this.valueChangeRate = (ratio - this.displayedValue) / animationTime; |
|
|
|
|
this.targetValue = ratio; |
|
|
|
|
|
|
|
|
|
this.enableAnimation(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.lastGaugeUpdate = now; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private enableAnimation() { |
|
|
|
|
if(this.animating) return; |
|
|
|
|
this.animating = true; |
|
|
|
|
this.lastAnimationTick = Date.now() / 1000.0 - 0.016; |
|
|
|
|
requestAnimationFrame(() => this.animate()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private animate() { |
|
|
|
|
if(this.shuttingDown) return; |
|
|
|
|
|
|
|
|
|
if(this.valueChangeRate == 0.0) { |
|
|
|
|
this.animating = false; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
requestAnimationFrame(() => this.animate()); |
|
|
|
|
|
|
|
|
|
let now = Date.now() / 1000.0; |
|
|
|
|
if(now > this.lastAnimationTick + 0.75) { |
|
|
|
|
this.displayedValue = this.targetValue; |
|
|
|
|
this.valueChangeRate = 0.0; |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
let dt = now - this.lastAnimationTick; |
|
|
|
|
if(dt < 0.04) return; // limit framerate to save battery
|
|
|
|
|
|
|
|
|
|
this.displayedValue += this.valueChangeRate * dt; |
|
|
|
|
if((this.displayedValue > this.targetValue) == (this.valueChangeRate > 0.0)) { |
|
|
|
|
this.displayedValue = this.targetValue; |
|
|
|
|
this.valueChangeRate = 0.0; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.lastAnimationTick = now; |
|
|
|
|
|
|
|
|
|
let numBars = this.bars.length; |
|
|
|
|
let litBars = Math.round(this.displayedValue * numBars); |
|
|
|
|
if(litBars == this.litBars) |
|
|
|
|
return; |
|
|
|
|
this.litBars = litBars; |
|
|
|
|
|
|
|
|
|
for(let barIdx = 0; barIdx < numBars; ++barIdx) { |
|
|
|
|
let bar = this.bars[barIdx]; |
|
|
|
|
bar.classList.toggle('lit', barIdx < litBars); |
|
|
|
|
let isLit = barIdx < litBars; |
|
|
|
|
if(bar.classList.contains('lit') != isLit) |
|
|
|
|
bar.classList.toggle('lit', isLit); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|