BangleApps_old/apps/mtimer/timepicker.lib.js

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
}