{ "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 }