451 lines
15 KiB
JavaScript
451 lines
15 KiB
JavaScript
{
|
|
"use strict";
|
|
const r = Bangle.appRect;
|
|
const midX = (r.x + r.x2 + 1) / 2;
|
|
const midY = (r.y + r.x2 + 1) / 2;
|
|
const Poly = require("poly").Poly;
|
|
|
|
/**
|
|
* @param
|
|
* [{
|
|
* x1: start x ,y1: start y,
|
|
* x2: end x ,y2: end y,
|
|
* Scale1: scale to start with. Sent to draw function.
|
|
* Scale2: scale to end with.
|
|
* draw: function(x, y, scale) draw function callback
|
|
* }, ...]
|
|
* @param drawFinal callback for final drawing
|
|
*/
|
|
function MultiAnimation(objArr, drawFinal) {
|
|
const defaultSteps = 3;
|
|
this.stop = false;
|
|
this.steps = 0; // number of animation steps
|
|
this.step = 0;
|
|
this.delayFactor = 0;
|
|
this.done = false;
|
|
this.objArr = objArr;
|
|
this.drawFinal = drawFinal;
|
|
let delayFactor = 0;
|
|
for (let obj of this.objArr) {
|
|
this.steps = (obj.steps < this.steps || obj.steps === undefined) ? this.steps : obj.steps; // set # of steps to obj with greatest # of steps
|
|
obj.x1 = obj.x1 === undefined ? 1 : obj.x1;
|
|
obj.y1 = obj.y1 === undefined ? 1 : obj.y1;
|
|
obj.x2 = obj.x2 === undefined ? 1 : obj.x2;
|
|
obj.y2 = obj.y2 === undefined ? 1 : obj.y2;
|
|
obj.x = obj.x1;
|
|
obj.y = obj.y1;
|
|
let deltaX = (obj.x2 - obj.x1);
|
|
let deltaY = (obj.y2 - obj.y1);
|
|
let delta = (Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)));
|
|
if (deltaX === 0 || deltaY === 0) obj.ratio = 0;
|
|
else obj.ratio = ((Math.abs(deltaX) > Math.abs(deltaY)) ? deltaY / deltaX : deltaX / deltaY);
|
|
let maxIsX = Math.abs(deltaX) > Math.abs(deltaY) ? true : false;
|
|
let inc = maxIsX ? (deltaX / this.steps) : (deltaY / this.steps);
|
|
if (maxIsX) {
|
|
obj.xInc = inc;
|
|
obj.yInc = inc * obj.ratio;
|
|
} else {
|
|
obj.xInc = inc * obj.ratio;
|
|
obj.yInc = inc;
|
|
}
|
|
obj.scale1 = (obj.scale1 === undefined) ? 1 : obj.scale1;
|
|
obj.scale2 = (obj.scale2 === undefined) ? 1 : obj.scale2;
|
|
obj.scale = obj.scale1;
|
|
obj.scaleInc = (obj.scale2 - obj.scale1) / this.steps;
|
|
this.done = false;
|
|
obj.delayFactor = delta / Math.max(deltaX, deltaY);
|
|
this.delayFactor = this.delayFactor > obj.delayFactor ? this.delayFactor : obj.delayFactor;
|
|
delete obj.delayFactor;
|
|
this.steps = this.steps === 0 ? defaultSteps : this.steps;
|
|
}
|
|
this.animate = function() {
|
|
return new Promise((resolve) => {
|
|
this.stop = false;
|
|
(draw = () => {
|
|
if (!this.done) {
|
|
g.clearRect(Bangle.appRect);;;
|
|
for (let obj of this.objArr) {
|
|
if (this.stop) return;
|
|
if (obj.scale === 0) return;
|
|
obj.draw(obj.x, obj.y, obj.scale);
|
|
if (this.step < this.steps) {
|
|
obj.x += obj.xInc;
|
|
obj.y += obj.yInc;
|
|
obj.scale += obj.scaleInc;
|
|
this.done = false;
|
|
} else {
|
|
obj.x = obj.x1;
|
|
obj.y = obj.y1;
|
|
obj.scale = obj.scale1;
|
|
this.done = true;
|
|
}
|
|
}
|
|
if (this.stop) resolve();
|
|
this.step++;
|
|
this.drawTimeoutID = setTimeout(draw, 30);
|
|
} else {
|
|
if (this.stop) resolve();
|
|
g.clearRect(Bangle.appRect);;;
|
|
this.drawFinal();
|
|
this.done = false;
|
|
this.step = 0;
|
|
resolve();
|
|
}
|
|
})(); //draw
|
|
});
|
|
}
|
|
|
|
this.abort = function() {
|
|
if (this.drawTimeoutID) clearTimeout(this.drawTimeoutID);
|
|
this.stop = true;
|
|
delete this.drawTimeoutID;
|
|
}
|
|
} //multiAnimation
|
|
|
|
function drawEasedRect(x, y, x1, y1) {
|
|
const a = 4;
|
|
g.drawPoly([x + a, y, x1 - a, y, x1, y + a, x1, y1 - a, x1 - a, y1, x + a, y1, x, y1 - a, x, y + a], true);
|
|
}
|
|
|
|
function drawTriangle(a, r, x, y) {
|
|
let tri = new Poly(3);
|
|
tri.addRadialPoint(a, r, x, y);
|
|
tri.addRadialPoint(a + 2.0944, r, x, y);
|
|
tri.addRadialPoint(a - 2.0944, r, x, y);
|
|
g.fillPoly(tri.getPoints(), true);
|
|
}
|
|
|
|
function drawRing(col) {
|
|
col = col == undefined ? col = [0, 1, 1] : col
|
|
let t = 20; //thickness of ring
|
|
let x = midX;
|
|
let y = midY;
|
|
let r = midX;
|
|
g.setColor.apply(g, col);
|
|
g.fillCircle(x, y, r).setColor(g.theme.bg).fillCircle(x, y, r - t).setColor(g.theme.fg);
|
|
}
|
|
|
|
const Segment = function(params) {
|
|
this.type = params.type;
|
|
this.label = params.label;
|
|
this.x = params.x;
|
|
this.y = params.y;
|
|
this.poly = params.poly;
|
|
this.minValue = params.minValue;
|
|
this.maxValue = params.maxValue;
|
|
this.value = params.value;
|
|
|
|
this.drawSegment = function(scale, x, y) {
|
|
scale = scale === undefined ? 1 : scale;
|
|
x = x === undefined ? midX : x;
|
|
y = y === undefined ? midY : y;
|
|
const fillCol = g.theme.bg;
|
|
const olCol = g.theme.fg;
|
|
const poly = g.transformVertices(this.poly.getPoints(), { x: x, y: y, scale: scale });
|
|
g.setColor(fillCol).fillPoly(poly).setColor(olCol).drawPoly(poly);
|
|
return this;
|
|
} //drawSegment
|
|
|
|
this.drawValue = function(scale, x, y, withBox) {
|
|
g.setColor(g.theme.fg);
|
|
scale = scale === undefined ? 1 : scale;
|
|
x = x === undefined ? this.x : x;
|
|
y = y === undefined ? this.y - Math.round(6 * scale) : y;
|
|
const size = Math.round(30 * scale);
|
|
let str = this.value;
|
|
|
|
if (typeof(this.value) === "number") str = str < 10 ? "0" + str : str;
|
|
if (typeof(this.value) === "string") str = str === 'AM' ? "AM" : "PM";
|
|
|
|
g.setFont("Vector: " + size).setFontAlign(0, 0);
|
|
|
|
let halfStrW = g.stringWidth("00") / 2;
|
|
let halfStrH = g.getFontHeight() / 2;
|
|
if (x <= halfStrW || y <= halfStrH) {
|
|
x = halfStrW + 5;
|
|
y = halfStrH + 2;
|
|
}
|
|
if (withBox) {
|
|
drawEasedRect(x - halfStrW - 4, y - halfStrH - 2, x + halfStrW, y + (halfStrH - 2));
|
|
}
|
|
g.drawString(str, x, y);
|
|
return this;
|
|
} //drawValue
|
|
|
|
this.drawLabel = function(scale, x, y) {
|
|
g.setColor(g.theme.fg);
|
|
scale = scale === undefined ? 1 : scale;
|
|
const size = Math.round(20 * scale)
|
|
x = x === undefined ? this.x : x;
|
|
y = y === undefined ? this.y + Math.round(17 * scale) : y;
|
|
g.setFont("Vector:" + size).setFontAlign(0, 0).drawString(this.label, x, y);
|
|
return this;
|
|
} //drawLabel
|
|
return this;
|
|
} // Segment
|
|
|
|
// animation factories:
|
|
const ToNumberPicker = function(t, segPart) {
|
|
return new MultiAnimation([
|
|
{ draw: () => drawRing() },
|
|
{ draw: () => t.drawTime() },
|
|
{ steps: 2, x1: segPart.x, y1: segPart.y, x2: 0, y2: 0, draw: (x, y) => segPart.drawValue(1, x, y) },
|
|
],
|
|
() => {
|
|
drawRing();
|
|
t.drawTime();
|
|
segPart.drawValue(1.5, 0, 0, true);
|
|
}
|
|
);
|
|
}
|
|
|
|
const ToTimePicker = function(t, segPart) {
|
|
return new MultiAnimation([
|
|
{ draw: () => drawRing() },
|
|
{ steps: 2, x1: 0, y1: 0, x2: segPart.x, y2: segPart.y, draw: (x, y) => segPart.drawValue(1, x, y) },
|
|
],
|
|
() => t.draw()
|
|
);
|
|
}
|
|
|
|
exports.TimePicker = function(options) {
|
|
// if meridiem is set, this will be an alarm time picker
|
|
// TODO maybe make the defaults as variables rather than hard coded
|
|
if (options.meridiem !== undefined) { // meridiem: "midday", as in ante meridiem and post meridiem ie. am/pm
|
|
this.seconds = {value: 0};
|
|
this.meridiem = new Segment({
|
|
type: "MER",
|
|
x: r.x2 - 29,
|
|
y: midY,
|
|
label: "",
|
|
maxValue: undefined,
|
|
minValue: undefined,
|
|
value: options.meridiem === undefined ? "AM" : options.meridiem
|
|
});
|
|
} else { // else it's a timer time picker
|
|
this.meridiem = {value: undefined};
|
|
this.seconds = new Segment({
|
|
type: "S",
|
|
x: r.x2 - 29,
|
|
y: midY,
|
|
label: "S",
|
|
minValue: 0,
|
|
maxValue: 60,
|
|
value: options.s === undefined ? 0 : options.s
|
|
});
|
|
this.seconds.toNumberPicker = ToNumberPicker(this, this.seconds);
|
|
this.seconds.toTimePicker = ToTimePicker(this, this.seconds);
|
|
}
|
|
|
|
this.hours = new Segment({
|
|
type: "H",
|
|
x: r.x + 29,
|
|
y: midY,
|
|
label: "H",
|
|
minValue: options.meridiem ? 1 : 0,
|
|
maxValue: 12,
|
|
value: options.h === undefined ? 0 : options.h
|
|
});
|
|
|
|
this.minutes = new Segment({
|
|
type: "M",
|
|
x: midX,
|
|
y: r.y + 29,
|
|
label: "M",
|
|
minValue: 0,
|
|
maxValue: 59,
|
|
value: options.m === undefined ? 0 : options.m
|
|
});
|
|
|
|
this.hours.toNumberPicker = ToNumberPicker(this, this.hours);
|
|
this.hours.toTimePicker = ToTimePicker(this, this.hours);
|
|
this.minutes.toNumberPicker = ToNumberPicker(this, this.minutes);
|
|
this.minutes.toTimePicker = ToTimePicker(this, this.minutes);
|
|
this.startFunc = options.start;
|
|
this.cancelFunc = options.cancel;
|
|
this.touchStart = true;
|
|
this.firstTouch = true;
|
|
this.segment = undefined;
|
|
this.btid = undefined;
|
|
this.buzzLen = undefined;
|
|
this.buzzStrength = undefined;
|
|
this.count = 0;
|
|
|
|
this.setTime = function(hms) {
|
|
this.hours.value = hms.h;
|
|
this.minutes.value = hms.m;
|
|
if (this.meridiem.value !== undefined) {
|
|
this.meridiem.value = hms.meridiem;
|
|
this.seconds.value = 0;
|
|
} else {
|
|
this.seconds.value = hms.s;
|
|
}
|
|
}
|
|
|
|
this.resetTime = function() {
|
|
this.hours.value = 0;
|
|
this.minutes.value = 0;
|
|
this.seconds.value = 0;
|
|
if (this.meridem.value !== undefined) this.meridiem.value = "AM";
|
|
|
|
}
|
|
|
|
this.drawScreen = function() {
|
|
g.setColor(0, 1, 1);
|
|
g.fillRect({x: r.x, y: midY - 29, x2: midX - 29, y2: midY + 29, r: 18}); // 9 o'clock
|
|
g.fillRect({x: midX - 29, y: r.y, x2: midX + 29, y2: midY - 29, r: 18}); // 12 o'clock
|
|
g.fillRect({x: r.x2 - 58, y: midY - 29, x2: r.x2, y2: midY + 29, r: 18}); // 3 o'clock
|
|
|
|
//g.setColor(0.25,0.25,0.25);
|
|
g.setColor(g.theme.bg);
|
|
g.fillRect({x: r.x + 3, y: midY - 26, x2: midX - 32, y2: midY + 26, r: 15}); // 9 o'clock
|
|
g.fillRect({x: midX - 26, y: r.y + 3, x2: midX + 26, y2: midY - 32, r: 15}); // 12 o'clock
|
|
g.fillRect({x: r.x2 - 54, y: midY - 26, x2: r.x2 - 3, y2: midY + 26, r: 15}); // 3 o'clock
|
|
// g.fillRect({x: r.x + 3, y: r.y2 - 42, x2: r.x + 77, y2: r.y2 - 3, r: 15}); // bottom left
|
|
// g.fillRect({x: r.x2 - 77, y: r.y2 - 42, x2: r.x2 - 3, y2: r.y2 - 3, r: 18}); // bottom right
|
|
};
|
|
|
|
this.drawButtons = function() {
|
|
g.setColor(0, 1, 1);
|
|
g.fillRect({x: midX - 26, y: midY - 26, x2: midX + 26, y2: midY + 26, r: 18}); // center
|
|
g.fillRect({x: midX - 29, y: r.y2, x2: midX + 29, y2: midY + 29, r: 18}); // 6 o'clock
|
|
|
|
g.setColor(g.theme.bg);
|
|
g.fillRect({x: midX - 26 + 3, y: midY - 26 + 3, x2: midX + 26 - 3, y2: midY + 26 - 3, r: 15}); // center
|
|
g.fillRect({x: midX - 26, y: r.y2 - 3, x2: midX + 26, y2: midY + 32, r: 15}); // 6 o'clock
|
|
|
|
g.setColor(g.theme.fg);
|
|
drawTriangle(0, 20, midX, r.y2 - 29);
|
|
g.setFontAlign(0, 0).setFont("Vector:35").drawString("x", midX + 2, midY - 1);
|
|
}
|
|
|
|
this.draw = function() {
|
|
g.clearRect(Bangle.appRect);;;
|
|
this.drawScreen();
|
|
this.hours.drawValue().drawLabel();
|
|
this.minutes.drawValue().drawLabel();
|
|
if (this.meridiem.value !== undefined) {
|
|
this.meridiem.drawValue();
|
|
} else {
|
|
this.seconds.drawValue().drawLabel();
|
|
}
|
|
this.drawButtons();
|
|
Bangle.timePickerDragHandler = this.dragHandler.bind(this);
|
|
//setTimeout(() => {
|
|
Bangle.setUI({mode: "custom", drag: Bangle.timePickerDragHandler});
|
|
//}, 100);
|
|
}
|
|
|
|
this.drawTime = function() {
|
|
let x = midX;
|
|
let y = midY;
|
|
let scale = 1;
|
|
let size = 30 * scale;
|
|
g.setFont("Vector:" + size);
|
|
let wC = g.stringWidth(":");
|
|
let w2 = g.stringWidth("00");
|
|
|
|
let hScale = (this.segment.type === "H") ? 1.25 : scale;
|
|
let mScale = (this.segment.type === "M") ? 1.25 : scale;
|
|
let sScale = (this.segment.type === "S") ? 1.25 : scale;
|
|
|
|
this.hours.drawValue(hScale, x - (w2 + wC), y, true);
|
|
this.minutes.drawValue(mScale, x, y, true);
|
|
if (this.meridiem.value !== undefined) {
|
|
this.meridiem.drawValue(1, x + w2 + wC, y, true);
|
|
} else {
|
|
this.seconds.drawValue(sScale, x + w2 + wC, y, true);
|
|
}
|
|
} //drawTime
|
|
|
|
this.selected = function(x, y) {
|
|
x = x - midX; // set the origin to the center of the screen
|
|
y = y - midY;
|
|
let s = Math.abs(x / y); // slope
|
|
if (y > -25 && y < 25 && x > -25 && x < 25) return { type: "C" };
|
|
if (y < 0 && s < 1) return this.minutes; // top half, slope is less than 1/1 i.e. within this arc: \/
|
|
else if (y > 0 && s < 1) return { type: "B" }; // bottom half, slope less than 1/1: /\
|
|
else if (x < 0) return this.hours; // on left within this arc: >
|
|
else if (x > 0) return this.meridiem.value !== undefined? this.meridiem : this.seconds; // on right within this arc: <
|
|
else return undefined;
|
|
} //selected
|
|
|
|
this.dragHandler = function(d) {
|
|
if (d.b === 0) { // released finger, show main screen
|
|
if (this.firstTouch === true) {
|
|
this.firstTouch = false;
|
|
return;
|
|
}
|
|
if (this.segment.toNumberPicker !== undefined) {
|
|
this.segment.toNumberPicker.abort();
|
|
g.clearRect(Bangle.appRect);;;
|
|
this.draw();
|
|
}
|
|
this.touchStart = true; // for next time finger touches
|
|
return;
|
|
}
|
|
if (this.touchStart) { // inital screen touch, show number picker
|
|
this.segment = this.selected(d.x, d.y); // where did we touch
|
|
if (!this.segment) return;
|
|
else if (this.segment.type === "C") {
|
|
Bangle.setUI();
|
|
delete Bangle.timePickerDragHandler;
|
|
|
|
//setTimeout(this.cancelFunc, 100);
|
|
this.cancelFunc();
|
|
|
|
return;
|
|
} else if (this.segment.type === "B") { // touched start button
|
|
Bangle.setUI();
|
|
delete Bangle.timePickerDragHandler;
|
|
this.startFunc({ h: this.hours.value, m: this.minutes.value, s: this.seconds.value, meridiem: this.meridiem.value});
|
|
return;
|
|
} else if (this.segment.type === "MER") {
|
|
this.meridiem.value = this.meridiem.value === 'AM' ? 'PM' : 'AM';
|
|
this.draw();
|
|
return;
|
|
} else {
|
|
this.segment.toNumberPicker.animate();
|
|
this.touchStart = false;
|
|
return;
|
|
}
|
|
}
|
|
if (!this.btid) {
|
|
this.buzzLen = 25;
|
|
this.buzzStrength = 0.75;
|
|
this.btid = setTimeout(() => { this.buzzLen = 50; this.buzzStrength = 1}, 150);
|
|
}
|
|
|
|
const delta = Math.max(Math.abs(d.dx), Math.abs(d.dy));
|
|
let inc = 3;
|
|
if (delta > 3) { inc = 30; }
|
|
else if (delta > 2) { inc = 20; }
|
|
|
|
var dirIsCW = true; // direction is clockwise
|
|
if (d.dx > 0 && d.y < midY) this.count += inc;
|
|
else if (d.dx < 0 && d.y > midY) this.count += inc;
|
|
else if (d.dy > 0 && d.x > midX) this.count += inc;
|
|
else if (d.dy < 0 && d.x < midX) this.count += inc;
|
|
else {
|
|
this.count -= inc;
|
|
dirIsCW = false; // direction is CCW
|
|
}
|
|
|
|
if (Math.abs(this.count) >= 60) {
|
|
Bangle.buzz(this.buzzLen, this.buzzStrength);
|
|
this.segment.value = dirIsCW ? this.segment.value + 1 : this.segment.value - 1;
|
|
if (this.segment.value > this.segment.maxValue) this.segment.value = this.segment.minValue;
|
|
else if (this.segment.value < this.segment.minValue) this.segment.value = this.segment.maxValue;
|
|
this.count = 0;
|
|
offset = 0;
|
|
g.clearRect(Bangle.appRect);;;
|
|
drawRing();
|
|
this.drawTime();
|
|
this.segment.drawValue(1.5, 0, 0, true);
|
|
clearTimeout(this.btid);
|
|
this.btid = null;
|
|
}
|
|
} // dragHandler
|
|
} // TimePicker
|
|
}
|