diff --git a/apps/mosaic/ChangeLog b/apps/mosaic/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/mosaic/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/mosaic/metadata.json b/apps/mosaic/metadata.json new file mode 100644 index 000000000..267c0de55 --- /dev/null +++ b/apps/mosaic/metadata.json @@ -0,0 +1,19 @@ +{ + "id":"mosaic", + "name":"Mosaic Clock", + "shortName": "Mosaic Clock", + "version": "0.01", + "description": "A fabulously colourful clock", + "readme": "README.md", + "icon":"mosaic.png", + "screenshots": [{"url":"mosaic-scr1.png"},{"url":"mosaic-scr2.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS", "BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"mosaic.app.js","url":"mosaic.app.js"}, + {"name":"mosaic.settings.js","url":"mosaic.settings.js"}, + {"name":"mosaic.img","url":"mosaic.icon.js","evaluate":true} + ] + } diff --git a/apps/mosaic/mosaic.app.js b/apps/mosaic/mosaic.app.js new file mode 100644 index 000000000..8b008b848 --- /dev/null +++ b/apps/mosaic/mosaic.app.js @@ -0,0 +1,103 @@ +Array.prototype.sample = function(){ + return this[Math.floor(Math.random()*this.length)]; +}; + +const SETTINGS_FILE = "mosaic.settings.json"; +let settings; +let theme; +let timeout = 60; +let drawTimeout; +let colours = [ + '#f00', '#00f', '#0f0', '#ff0', '#f0f', '#0ff', + '#8f0', '#f08', '#f80', '#80f', '#0f8', '#08f', +]; +let digits = [ + E.toArrayBuffer(atob("BQcB/Gtax+A=")), + E.toArrayBuffer(atob("BQeCAX9c1zXNc1zX9A==")), + E.toArrayBuffer(atob("BQcB/Hsbx+A=")), + E.toArrayBuffer(atob("BQcB/Hsex+A=")), + E.toArrayBuffer(atob("BQeCAf/zPM8D/Nc1/A==")), + E.toArrayBuffer(atob("BQcB/G8ex+A=")), + E.toArrayBuffer(atob("BQcB/G8ax+A=")), + E.toArrayBuffer(atob("BQeCAf/wP81zXNc1/A==")), + E.toArrayBuffer(atob("BQcB/Gsax+A=")), + E.toArrayBuffer(atob("BQcB/Gsex+A=")) +]; + +function loadSettings() { + settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'showWidgets': false, 'theme':'System'}; +} + +function loadThemeColors() { + theme = {fg: g.theme.fg, bg: g.theme.bg}; + if (settings.theme === "Dark") { + theme.fg = g.toColor(1,1,1); + theme.bg = g.toColor(0,0,0); + } + else if (settings.theme === "Light") { + theme.fg = g.toColor(0,0,0); + theme.bg = g.toColor(1,1,1); + } +} + +function queueDraw(seconds) { + let millisecs = seconds * 1000; + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, millisecs - (Date.now() % millisecs)); +} + +function draw() { + // draw colourful grid + for (let i_x = 0; i_x < num_squares_w; i_x++) { + for (let i_y = 0; i_y < num_squares_h; i_y++) { + g.setColor(colours.sample()).fillRect( + o_w+i_x*s, o_h+i_y*s, o_w+i_x*s+s, o_h+i_y*s+s + ); + } + } + let t = new Date(); + g.setBgColor(theme.fg); + g.setColor(theme.bg); + g.drawImage(digits[Math.floor(t.getHours()/10)], (mid_x-5)*s+o_w, (mid_y-7)*s+o_h, {scale:s}); + g.drawImage(digits[t.getHours() % 10], (mid_x+1)*s+o_w, (mid_y-7)*s+o_h, {scale:s}); + g.drawImage(digits[Math.floor(t.getMinutes()/10)], (mid_x-5)*s+o_w, (mid_y+1)*s+o_h, {scale:s}); + g.drawImage(digits[t.getMinutes() % 10], (mid_x+1)*s+o_w, (mid_y+1)*s+o_h, {scale:s}); + + queueDraw(timeout); +} + +g.clear(); +loadSettings(); +loadThemeColors(); + +offset_widgets = settings.showWidgets ? 24 : 0; +let available_height = g.getHeight() - offset_widgets; + +// Calculate grid size and offsets +let s = Math.floor(available_height/17); +let num_squares_w = Math.round(g.getWidth()/s) - 1; +let num_squares_h = Math.round(available_height/s) - 1; +let o_w = Math.floor((g.getWidth() - num_squares_w * s)/2); +let o_h = Math.floor((g.getHeight() - num_squares_h * s+offset_widgets)/2); +let mid_x = Math.floor(num_squares_w/2); +let mid_y = Math.floor((num_squares_h-1)/2); + +draw(); + +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI('clock'); +if (settings.showWidgets) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} diff --git a/apps/mosaic/mosaic.icon.js b/apps/mosaic/mosaic.icon.js new file mode 100644 index 000000000..4d781cf68 --- /dev/null +++ b/apps/mosaic/mosaic.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcAtu27dt3MkyVJkgHC23UA4WSCP4R/CP4RFBAfSA4VJA4QCFCP4R/CP4RJ7oaMCP4R/CP4RFbge9BoYID3IQCkgR/CP4R/CIoA==")) diff --git a/apps/mosaic/mosaic.png b/apps/mosaic/mosaic.png new file mode 100644 index 000000000..f0c814d3b Binary files /dev/null and b/apps/mosaic/mosaic.png differ diff --git a/apps/mosaic/mosaic.settings.js b/apps/mosaic/mosaic.settings.js new file mode 100644 index 000000000..dcf725b84 --- /dev/null +++ b/apps/mosaic/mosaic.settings.js @@ -0,0 +1,44 @@ +(function(back) { + const SETTINGS_FILE = "mosaic.settings.json"; + + // initialize with default settings... + let s = {'showWidgets': false, 'theme':'System'} + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + let settings = storage.readJSON(SETTINGS_FILE, 1) || s; + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + var theme_options = ['System', 'Light', 'Dark']; + + E.showMenu({ + '': { 'title': 'Mosaic Clock' }, + '< Back': back, + 'Show Widgets': { + value: settings.showWidgets, + format: () => (settings.showWidgets ? 'Yes' : 'No'), + onchange: () => { + settings.showWidgets = !settings.showWidgets; + save(); + } + }, + 'Theme': { + value: 0 | theme_options.indexOf(s.theme), + min: 0, max: theme_options.length - 1, + format: v => theme_options[v], + onchange: v => { + s.theme = theme_options[v]; + save(); + } + }, + }); +}) diff --git a/apps/mosaic/pixel digits.xcf b/apps/mosaic/pixel digits.xcf new file mode 100644 index 000000000..f2c05891c Binary files /dev/null and b/apps/mosaic/pixel digits.xcf differ