diff --git a/apps/mtnclock/README.md b/apps/mtnclock/README.md new file mode 100644 index 000000000..58538509d --- /dev/null +++ b/apps/mtnclock/README.md @@ -0,0 +1,21 @@ +# Mountain Pass Clock + +Based on the Pebble watchface Weather Land. + +Mountain Pass Clock changes depending on time (day/night) and weather conditions. + +This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification). To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather. + +The scene will change according to the following OpenWeatherMap conditions: clear, cloudy, overcast, lightning, drizzle, rain, fog and snow. Each weather condition has night/day scenes. + +If you choose not to set up weather (or are not connected to Gadgetbridge, for that matter), this clock will default to clear weather, and the scenery will still change from night to day. + +Special thanks to Serj for testing this on the original Bangle. + +## Images + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) +![](screenshot5.png) diff --git a/apps/mtnclock/app-icon.js b/apps/mtnclock/app-icon.js new file mode 100644 index 000000000..8324c248a --- /dev/null +++ b/apps/mtnclock/app-icon.js @@ -0,0 +1 @@ +atob("MDCEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVQAzAFVVVVVVVVVVVVVVVVVVVVVVVVVVVQLzIFVVVVVVAFVVVVVVVVVVVVVVVVVVUB//MQVVVVVQAQVVVVVVVVVVVVVVVVVVAB8RMQBVVVVQIwBVVVVVVVVVVVVVVVVQAQAAABAFVVUB8zAFVVVVVVVVVVVVVVVQExAiAYEFVVAD/zIFVVVVVVVVVVVVVVUBMzgzg4gQVQAvMvMAVVVVVVVVVVVVVVAIMzMzM4iABQACABAABVVVVVVVVVVVVQAjMzMzMziCABgQAQAiAFVVVVVVVVVVVQEzMzMzMzOIECMyIyKIEFVVVVVVVVVVUBMzMzMzMzM4gQIzMzM4gQVVVVVVVVVVAIMzMzMzMzM4iACDMzM4iABVVVVVVVVQAjMzMzMzMzMziCATMzMziCAFVVVVVVVQEzMzMzMzMzMzOIECMzMzOIEFVVVVVVUBMzMzMzMzMzMzM4gQIzMzM4gQVVVVVVAIMiMzMzMzMzMiM4iACDMzMiiABVVVVQAjIAIzMzMzMzIAIziCATMzIAKCAFVVVQE4AACDMzMzM4AACDOIECM4AACIEFVVUAERAiARERERERAiAREREAERAiAREAVVUAAAEyEAAAAAAAEyEAAAAAAAEyEAAAVVVVUBMzIQVVVVUBMzIQVVVVUBMzIQVVVVVVABEREQBVVVABEREQBVVVABEREQBVVVVVAAAAAABVVVAAAAAABVVVAAAAAABVVVVVUGIiJgVVVVUGIiJgVVVVUGIiJgVVVVVVACIiIgBVVVACIiIgBVVVACIiIgBVVVVQAiIiInAFVQAiIiInAFVQAiIiInAFVVVQEiIiInYFVQEiIiInYFVQEiIiInYFVVUAAAAAAAAAUAAAAAAAAAUAAAAAAAAAVVVVAHd3dwBVVVAHd3dwBVVVAHd3dwBVVVVQB3d3d3AFVQB3d3d3AFVQB3d3d3AFVVVQYiIiInYFVQYiIiInYFVQYiIiInYFVVUAIiIiIicAUAIiIiIicAUAIiIiIicAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAARAAAAUAAAARAAAAUAAAARAAAAVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEBVVVVVVVBEBVVVVVVVBEBVVVVVVVVVBEAFVVVVVVBEBVVVVVVQBEBVVVVVVVVVAAAAAAAAVVBEBVUAAAAAAABVVVVVVVUAASM/MyIQAAAAAAABIjPzMhAAVVVVVVABP///////MyIiIjP///////MQBVVVVQE////////////////////////zEFVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==") \ No newline at end of file diff --git a/apps/mtnclock/app.js b/apps/mtnclock/app.js new file mode 100644 index 000000000..28ba25882 --- /dev/null +++ b/apps/mtnclock/app.js @@ -0,0 +1,350 @@ +var data = require("Storage").readJSON("mtnclock.json", 1) || {}; + +//seeded RNG to generate stars, snow, etc +function sfc32(a, b, c, d) { + return function() { + a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; + var t = (a + b) | 0; + a = b ^ b >>> 9; + b = c + (c << 3) | 0; + c = (c << 21 | c >>> 11); + d = d + 1 | 0; + t = t + d | 0; + c = c + t | 0; + return (t >>> 0) / 4294967296; + }; +} + +//scale x, y coords to screen +function px(x) { + return x*g.getWidth()/100; +} + +function py(y) { + return y*g.getHeight()/100; +} + +function drawMtn(color, coord, dimen) { + //scale mountains to different sizes + g.setColor(color.mtn1).fillPoly([ + coord.x,coord.y, + coord.x,coord.y+dimen.h, + coord.x-dimen.w/2,coord.y+dimen.h + ]); + g.setColor(color.mtn2).fillPoly([ + coord.x,coord.y, + coord.x,coord.y+dimen.h, + coord.x+dimen.w/2,coord.y+dimen.h + ]); +} + +function drawTree(color, coord, dimen) { + //scale trees to different sizes + g.setColor(color.tree1).fillPoly([ + coord.x,coord.y, + coord.x-dimen.w/5,coord.y+dimen.h/4, + coord.x-dimen.w/12,coord.y+dimen.h/4, + coord.x-dimen.w/2.8,coord.y+1.95*dimen.h/4, + coord.x-dimen.w/8,coord.y+1.95*dimen.h/4, + coord.x-dimen.w/2,coord.y+3*dimen.h/4, + coord.x,coord.y+3*dimen.h/4 + ]); + g.setColor(color.tree2).fillPoly([ + coord.x,coord.y, + coord.x+dimen.w/5,coord.y+dimen.h/4, + coord.x+dimen.w/12,coord.y+dimen.h/4, + coord.x+dimen.w/2.8,coord.y+1.95*dimen.h/4, + coord.x+dimen.w/8,coord.y+1.95*dimen.h/4, + coord.x+dimen.w/2,coord.y+3*dimen.h/4, + coord.x,coord.y+3*dimen.h/4 + ]); + g.setColor(color.tree3).fillRect( + coord.x-dimen.w/12,coord.y+3*dimen.h/4, + coord.x+dimen.w/12,coord.y+dimen.h + ); +} + +function drawSnow(color, coord, size) { + g.setColor(color).drawLine(coord.x-px(size),coord.y-py(size),coord.x+px(size),coord.y+py(size)); + g.drawLine(coord.x-px(size),coord.y+py(size),coord.x+px(size),coord.y-py(size)); + g.drawLine(coord.x,coord.y+py(size),coord.x,coord.y-py(size)); + g.drawLine(coord.x-px(size),coord.y,coord.x+px(size),coord.y); +} + +function draw(color) { + +var seed; +var rand; + +g.clear(); +//background + g.setColor(color.bg1).fillRect( + px(0),py(0), + px(100),py(45) + ); + g.setColor(color.bg2).fillRect( + px(0),py(45), + px(100),py(100) + ); + //lightning + if (color.ltn) { + g.setColor(color.ltn).fillPoly([ + px(70),py(20), + px(60),py(28), + px(71),py(29), + px(63),py(40), + px(75),py(28), + px(64),py(27) + ]); + g.fillPoly([ + px(40),py(20), + px(30),py(28), + px(41),py(29), + px(33),py(40), + px(45),py(28), + px(34),py(27) + ]); + } + //stars + if (color.star) { + seed = 4; + rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed); + for (let i = 0; i < 40; i++) { + g.setColor(color.star).drawCircle(Math.floor(rand() * px(100)),Math.floor(rand() * py(33)),Math.floor(rand() * 2)); + } + } + //birds + if (color.bird) { + g.setColor(color.bird).fillCircle(px(17),py(12),px(5)).fillCircle(px(23),py(10),px(5)); + g.setColor(color.bg1).fillCircle(px(18),py(15),px(6)).fillCircle(px(24),py(13),px(6)); + g.setColor(color.bird).fillCircle(px(28),py(19),px(4)).fillCircle(px(33),py(19),px(4)); + g.setColor(color.bg1).fillCircle(px(28),py(21),px(5)).fillCircle(px(34),py(21),px(5)); + } + //sun/moon + if (color.sun) g.setColor(color.sun).fillCircle(px(65), py(22), py(20)); + //path + g.setColor(color.path).fillPoly([ + px(60),py(44), + px(39),py(55), + px(72),py(57), + px(30),py(100), + px(70),py(100), + px(78),py(55), + px(46),py(53) + ]); + //fog + if (color.fog) { + g.setColor(color.fog); + for (let i = 1; i <= 47; i = i+2) { + g.drawLine(px(0),py(i),px(100),py(i)); + } + } + //rain + if (color.rain1) { + g.setColor(color.rain1); + for (let i = 0; i <= 6; i++) { + g.drawLine(px(6+i*20),py(20),px(-14+i*20),py(45)); + } + if (color.rain2) { + for (let i = 0; i <= 6; i++) { + g.drawLine(px(16+i*20),py(20),px(-4+i*20),py(45)); + } + } + } + //snow + if (color.snow) { + seed = 11; + rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed); + for (let i = 0; i < 30; i++) { + drawSnow(color.snow, {x:Math.floor(rand() * px(100)), y:(Math.floor(rand() * py(25))+py(20))}, Math.floor(rand() * 3)); + } + } + //mountains + drawMtn({mtn1:color.mtn2, mtn2:color.mtn1}, {x:px(35), y:py(30)}, {w:px(38), h:py(17)}); + drawMtn({mtn1:color.mtn2, mtn2:color.mtn1}, {x:px(10), y:py(20)}, {w:px(50), h:py(30)}); + drawMtn({mtn1:color.mtn1, mtn2:color.mtn2}, {x:px(90), y:py(20)}, {w:px(70), h:py(30)}); + //lake + g.setColor(color.lake).fillEllipse(px(-15), py(52), px(30), py(57)); + //trees + drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(12),y:py(52)}, {w:px(13),h:py(13)}); + drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(48),y:py(52)}, {w:px(13),h:py(13)}); + drawTree({tree1:color.tree2, tree2:color.tree1, tree3:color.tree3}, {x:px(34),y:py(46)}, {w:px(6),h:py(6)}); + drawTree({tree1:color.tree1, tree2:color.tree2, tree3:color.tree3}, {x:px(70),y:py(46)}, {w:px(6),h:py(6)}); + drawTree({tree1:color.tree1, tree2:color.tree2, tree3:color.tree3}, {x:px(90),y:py(52)}, {w:px(13),h:py(13)}); + //clouds + if (color.cloud1) { + g.setColor(color.cloud1); + if (color.cloud2) g.fillRect(0, 0, px(100), py(10)); + g.fillCircle(px(3), py(12), py(4)); + g.fillCircle(px(10), py(12), py(5)); + g.fillCircle(px(16), py(11), py(6)); + g.fillCircle(px(24), py(10), py(8)); + g.fillCircle(px(30), py(11), py(6)); + g.fillCircle(px(35), py(12), py(5)); + g.fillCircle(px(40), py(12), py(6)); + g.fillCircle(px(48), py(13), py(5)); + g.fillCircle(px(55), py(14), py(5)); + g.fillCircle(px(60), py(12), py(5)); + g.fillCircle(px(65), py(11), py(6)); + g.fillCircle(px(75), py(10), py(8)); + g.fillCircle(px(85), py(11), py(6)); + g.fillCircle(px(90), py(12), py(5)); + g.fillCircle(px(97), py(13), py(4)); + } + + //clock text + (color.clock == undefined) ? g.setColor(0xFFFF) : g.setColor(color.clock); + g.setFont("Vector", py(20)).setFontAlign(-1, -1).drawString((require("locale").time(new Date(), 1).replace(" ", "")), px(2), py(67)); + g.setFont("Vector", py(10)).drawString(require('locale').dow(new Date(), 1)+" "+new Date().getDate()+" "+require('locale').month(new Date(), 1)+((data.temp == undefined) ? "" : " | "+require('locale').temp(Math.round(data.temp-273.15)).replace(".0", "")), px(2), py(87)); +} + +var i = 0; + +function setWeather() { + var a = {}; + //clear day/night is default weather + if ((data.code >= 800 && data.code <=802) || data.code == undefined) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-clear + a = { + bg1:0x4FFF, bg2:0x03E0, + sun:0xFD20, + path:0x8200, + mtn1:0x045f, mtn2:0x000F, + lake:0x000F, + tree1:0x07E0, tree2:0, tree3:0x7BE0, + bird:0xFFFF + }; + //day-cloudy + if (data.code == 801 || data.code == 802) a.cloud1 = 0xFFFF; + } + else { + //night-clear + a = { + bg1:0, bg2:0x0005, + sun:0xC618, + path:0, + mtn1:0x0210, mtn2:0x0010, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + star:0xFFFF + }; + //night-cloudy + if (data.code == 801 || data.code == 802) a.cloud1 = 0x4208; + } + } + else if (((data.code >= 300) && (data.code < 600)) || ((data.code >= 200) && (data.code < 300)) || data.code == 803 || data.code == 804) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-overcast + a = { + bg1:0xC618, bg2:0x0200, + path:0x3000, + mtn1:0x3B38, mtn2:0x0005, + lake:0x000F, + tree1:0x03E0, tree2:0, tree3:0x59E0, + cloud1:0x7BEF, cloud2:1 + }; + //day-lightning + if (data.code >= 200 && data.code < 300) a.ltn = 0xFFFF; + //day-drizzle + if ((data.code >= 300 && data.code < 600) || (data.code >= 200 && data.code <= 202) || (data.code >= 230 && data.code <= 232)) a.rain1 = 0xFFFF; + //day-rain + if ((data.code >= 500 && data.code < 600) || (data.code >= 200 && data.code <= 202)) a.rain2 = 1; + } + else { + //night-overcast + a = { + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + cloud1:0x4208, cloud2:1 + }; + //night-lightning + if (data.code >= 200 && data.code < 300) a.ltn = 0xFFFF; + //night-drizzle + if ((data.code >= 300 && data.code < 600) || (data.code >= 200 && data.code <= 202) || (data.code >= 230 && data.code <= 232)) a.rain1 = 0xC618; + //night-rain + if ((data.code >= 500 && data.code < 600) || (data.code >= 200 && data.code <= 202)) rain2 = 1; + } + } + else if ((data.code >= 700) && (data.code < 800)) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-fog + a = { + bg1:0xC618, bg2:0x0200, + path:0x3000, + mtn1:0x3B38, mtn2:0x0005, + lake:0x000F, + tree1:0x03E0, tree2:0, tree3:0x59E0, + fog:0xFFFF + }; + } + else { + //night-fog + a = { + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + fog:0x7BEF + }; + } + } + else if ((data.code >= 600) && (data.code < 700)) { + if (new Date().getHours() >= 7 && new Date().getHours() <= 19) { + //day-snow + a = { + bg1:0, bg2:0x7BEF, + path:0xC618, + mtn1:0xFFFF, mtn2:0x7BEF, + lake:0x07FF, + tree1:0xC618, tree2:0xC618, tree3:0x59E0, + cloud1:0x7BEF, cloud2:1, + snow:0xFFFF, + clock: 0 + }; + } + else { + //night-snow + a = { + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x39E7, tree2:0x39E7, tree3:0x59E0, + cloud1:0x4208, cloud2:1, + snow:0xFFFF + }; + } + } + draw(a); +} + +const _GB = global.GB; +global.GB = (event) => { + if (event.t==="weather") { + data = event; + require("Storage").write('mtnclock.json', event); + setWeather(); + } + if (_GB) setTimeout(_GB, 0, event); +}; + +var drawTimeout; + +//update watchface in next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + setWeather(); + queueDraw(); + }, 60000 - (Date.now() % 60000)); +} + +queueDraw(); +setWeather(); +Bangle.setUI("clock"); diff --git a/apps/mtnclock/app.png b/apps/mtnclock/app.png new file mode 100644 index 000000000..083304294 Binary files /dev/null and b/apps/mtnclock/app.png differ diff --git a/apps/mtnclock/metadata.json b/apps/mtnclock/metadata.json new file mode 100644 index 000000000..a3a173069 --- /dev/null +++ b/apps/mtnclock/metadata.json @@ -0,0 +1,25 @@ +{ + "id": "mtnclock", + "name": "Mountain Pass Clock", + "shortName": "Mtn Clock", + "version": "0.01", + "description": "A clock that changes scenery based on time and weather.", + "readme":"README.md", + "icon": "app.png", + "screenshots": [ + {"url":"screenshot1.png"}, + {"url":"screenshot2.png"}, + {"url":"screenshot3.png"}, + {"url":"screenshot4.png"}, + {"url":"screenshot5.png"} + ], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"mtnclock.app.js","url":"app.js"}, + {"name":"mtnclock.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"mtnclock.json"}] +} \ No newline at end of file diff --git a/apps/mtnclock/screenshot1.png b/apps/mtnclock/screenshot1.png new file mode 100644 index 000000000..a89bbe2db Binary files /dev/null and b/apps/mtnclock/screenshot1.png differ diff --git a/apps/mtnclock/screenshot2.png b/apps/mtnclock/screenshot2.png new file mode 100644 index 000000000..730edbab5 Binary files /dev/null and b/apps/mtnclock/screenshot2.png differ diff --git a/apps/mtnclock/screenshot3.png b/apps/mtnclock/screenshot3.png new file mode 100644 index 000000000..abd93d504 Binary files /dev/null and b/apps/mtnclock/screenshot3.png differ diff --git a/apps/mtnclock/screenshot4.png b/apps/mtnclock/screenshot4.png new file mode 100644 index 000000000..8f2e1de90 Binary files /dev/null and b/apps/mtnclock/screenshot4.png differ diff --git a/apps/mtnclock/screenshot5.png b/apps/mtnclock/screenshot5.png new file mode 100644 index 000000000..b34a3111c Binary files /dev/null and b/apps/mtnclock/screenshot5.png differ