diff --git a/modules/ClockFace.js b/modules/ClockFace.js new file mode 100644 index 000000000..9818ae4e3 --- /dev/null +++ b/modules/ClockFace.js @@ -0,0 +1,103 @@ +/* +Most of the boilerplate needed to run a clock. +See ClockFace.md for documentation +*/ +function ClockFace(options) { + if ("function"=== typeof options) options = {draw: options}; // simple usage + // some validation, in the hopes of at least catching typos/basic mistakes + Object.keys(options).forEach(k => { + if (![ + "precision", + "init", "draw", "update", + "pause", "resume", + "up", "down", "upDown" + ].includes(k)) throw `Invalid ClockFace option: ${k}`; + }); + if (!options.draw && !options.update) throw "ClockFace needs at least one of draw() or update() functions"; + this.draw = options.draw || (t=> { + options.update.apply(this, [t, {d: true, h: true, m: true, s: true}]); + }); + this.update = options.update || (t => { + g.clear(); + options.draw.apply(this, [t, {d: true, h: true, m: true, s: true}]); + }); + if (options.precision===1000||options.precision===60000) throw "ClockFace precision is in seconds, not ms"; + this.precision = (options.precision || 60); + if (options.init) this.init = options.init; + if (options.pause) this._pause = options.pause; + if (options.resume) this._resume = options.resume; + if ((options.up || options.down) && options.upDown) throw "ClockFace up/down and upDown cannot be used together"; + if (options.up || options.down) this._upDown = (dir) => { + if (dir<0 && options.up) options.up.apply(this); + if (dir>0 && options.down) options.down.apply(this); + }; + if (options.upDown) this._upDown = options.upDown; +} + +ClockFace.prototype.tick = function() { + const time = new Date(); + const now = { + d: `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`, + h: time.getHours(), + m: time.getMinutes(), + s: time.getSeconds(), + }; + if (!this._last) { + g.clear(true); + Bangle.drawWidgets(); + g.reset(); + this.draw.apply(this, [time, {d: true, h: true, m: true, s: true}]); + } else { + let c = {d: false, h: false, m: false, s: false}; // changed + if (now.d!==this._last.d) c.d = c.h = c.m = c.s = true; + else if (now.h!==this._last.h) c.h = c.m = c.s = true; + else if (now.m!==this._last.m) c.m = c.s = true; + else if (now.s!==this._last.s) c.s = true; + g.reset(); + this.update.apply(this, [time, c]); + } + this._last = now; + if (this.paused) return; // called redraw() while still paused + // figure out timeout: if e.g. precision=60s, update at the start of a new minute + const interval = this.precision*1000; + this._timeout = setTimeout(() => this.tick(), interval-(Date.now()%interval)); +}; + +ClockFace.prototype.start = function() { + Bangle.loadWidgets(); + if (this.init) this.init.apply(this); + if (this._upDown) Bangle.setUI("clockupdown", d=>this._upDown.apply(this,[d])); + else Bangle.setUI("clock"); + delete this._last; + this.tick(); + + Bangle.on("lcdPower", on => { + if (on) this.resume(); + else this.pause(); + }); +}; + +ClockFace.prototype.pause = function() { + if (!this._timeout) return; // already paused + clearTimeout(this._timeout); + delete this._timeout; + this.paused = true; // apps might want to check this + if (this._pause) this._pause.apply(this); +}; +ClockFace.prototype.resume = function() { + if (this._timeout) return; // not paused + delete this._last; + delete this.paused; + if (this._resume) this._resume.apply(this); + this.tick(true); +}; + +/** + * Force a complete redraw + */ +ClockFace.prototype.redraw = function() { + delete this._last; + this.tick(); +}; + +exports = ClockFace; \ No newline at end of file diff --git a/modules/ClockFace.md b/modules/ClockFace.md new file mode 100644 index 000000000..1da6e6020 --- /dev/null +++ b/modules/ClockFace.md @@ -0,0 +1,110 @@ +ClockFace +========= + +This module handles most of the tasks needed to set up a clock, so you can +concentrate on drawing the time. + +Example +------- +Tthe [tutorial clock](https://www.espruino.com/Bangle.js+Clock) converted to use +this module: + +```js + +// Load fonts +require("Font7x11Numeric7Seg").add(Graphics); +// position on screen +const X = 160, Y = 140; + +var ClockFace = require("ClockFace"); +var clock = new ClockFace({ + precision: 1, // update every second + draw: function(d) { + // work out how to display the current time + var h = d.getHours(), m = d.getMinutes(); + var time = (" "+h).substr(-2)+":"+("0"+m).substr(-2); + // draw the current time (4x size 7 segment) + g.setFont("7x11Numeric7Seg", 4); + g.setFontAlign(1, 1); // align right bottom + g.drawString(time, X, Y, true /*clear background*/); + // draw the seconds (2x size 7 segment) + g.setFont("7x11Numeric7Seg", 2); + g.drawString(("0"+d.getSeconds()).substr(-2), X+30, Y, true /*clear background*/); + // draw the date, in a normal font + g.setFont("6x8"); + g.setFontAlign(0, 1); // align center bottom + // pad the date - this clears the background if the date were to change length + var dateStr = " "+require("locale").date(d)+" "; + g.drawString(dateStr, g.getWidth()/2, Y+15, true /*clear background*/); + } +}); +clock.start(); + +``` + + + +Complete Usage +-------------- + +```js + +var ClockFace = require("ClockFace"); +var clock = new ClockFace({ + precision: 1, // optional, defaults to 60: how often to call update(), in seconds + init: function() { // optional + // called only once before starting the clock, but after setting up the + // screen/widgets, so you can use Bangle.appRect + }, + draw: function(time, changed) { // at least draw or update is required + // (re)draw entire clockface, time is a Date object + // `changed` is the same format as for update() below, but always all true + }, + // The difference between draw() and update() is that the screen is cleared + // before draw() is called, so it needs to always redraw the entire clock + update: function(time, changed) { // at least draw or update is required + // redraw date/time, time is a Date object + // if you want, you can only redraw the changed parts: + if (changed.d) // redraw date (changed.h/m/s will also all be true) + if (changed.h) // redraw hours + if (changed.m) // redraw minutes + if (changed.s) // redraw seconds + }, + pause: function() { // optional, called when the screen turns off + // for example: turn off GPS/compass if the watch used it + }, + resume: function() { // optional, called when the screen turns on + // for example: turn GPS/compass back on + }, + up: function() { // optional, up handler + }, + down: function() { // optional, down handler + }, + upDown: function(dir) { // optional, combined up/down handler + if (dir === -1) // Up + else // (dir === 1): Down + }, + }); +clock.start(); + +``` + + +Simple Usage +------------ +Basic clocks can pass just a function to redraw the entire screen every minute: + +```js + +var ClockFace = require("ClockFace"); +var clock = new ClockFace(function(time) { + // draw the current time at the center of the screen + g.setFont("Vector:50").setFontAlign(0, 0) + .drawString( + require("locale").time(time, true), + Bangle.appRect.w/2, Bangle.appRect.h/2 + ); +}); +clock.start(); + +``` \ No newline at end of file