diff --git a/apps/mtnclock/ChangeLog b/apps/mtnclock/ChangeLog index 532b77e0e1..c7224f5cf8 100644 --- a/apps/mtnclock/ChangeLog +++ b/apps/mtnclock/ChangeLog @@ -4,3 +4,4 @@ 0.04: Adding settings and the ability to show the widgets bar 0.05: Fix the widgets disappearing on weather update 0.06: Fix weather not correctly updating with Weather data v2 +0.07: Add clockinfo slots diff --git a/apps/mtnclock/README.md b/apps/mtnclock/README.md index 441754b839..e4cde8cd6e 100644 --- a/apps/mtnclock/README.md +++ b/apps/mtnclock/README.md @@ -1,21 +1,25 @@ -# 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), or a Bangle app that will update weather.json such as OWM Weather. 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) +# 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), or a Bangle app that will update weather.json such as OWM Weather. 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. + +You can add up to three clockinfos under the time, date, and weather. Accessing the menu to do so is done by tapping, or long-tapping on the Bangle.js 2. + +Special thanks to Serj for testing this on the original Bangle. + +## Images + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) +![](screenshot5.png) +![](screenshot6.png) +![](screenshot7.png) diff --git a/apps/mtnclock/app.js b/apps/mtnclock/app.js index dccade31f3..16535cb5b9 100644 --- a/apps/mtnclock/app.js +++ b/apps/mtnclock/app.js @@ -1,5 +1,13 @@ var data = require("Storage").readJSON("mtnclock.json", 1) || {}; +if (! Array.isArray(data.rows)) { + data.rows = []; + require("Storage").writeJSON("mtnclock.json", data); +} + +let clockinfosMain = {}; +let clockinfos = require("clock_info").load(); + let weather; try { weather = require('weather'); @@ -9,17 +17,17 @@ try { //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; - }; + 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 @@ -79,12 +87,11 @@ function drawSnow(color, coord, size) { } function draw(color) { + var seed; + var rand; -var seed; -var rand; - -g.clear(); -//background + g.clear(); + //background g.setColor(color.bg1).fillRect( px(0),py(0), px(100),py(45) @@ -96,20 +103,20 @@ g.clear(); //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) + 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) + 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 @@ -117,7 +124,7 @@ g.clear(); 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)); + g.setColor(color.star).drawCircle(Math.floor(rand() * px(100)),Math.floor(rand() * py(33)),Math.floor(rand() * 2)); } } //birds @@ -167,7 +174,7 @@ g.clear(); } } //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(43), y:py(28)}, {w:px(38), h:py(19)}); 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 @@ -201,8 +208,25 @@ g.clear(); //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)); + const start = 87; + data.rows.forEach(function(row, r) { + let a = row.menuA; + let b = row.menuB; + if (clockinfos[a] && clockinfos[a].items[b]) { + let ci = clockinfos[a].items[b]; + let text = ci.get().text; + if (!(clockinfosMain[a] && clockinfosMain[a][b])) { + clockinfosMain[a] = clockinfosMain[a] || {}; + clockinfosMain[a][b] = true; + ci.show(); + ci.on("redraw", clockinfoRedraw); + } + g.setFont("Vector", py(10)).setFontAlign(-1, -1).drawString(text, px(2), py((start - (data.rows.length - 1) * 10) + r * 10)); + } + }); + + g.setFont("Vector", py(20)).setFontAlign(-1, -1).drawString((require("locale").time(new Date(), 1).replace(" ", "")), px(2), py(start - ((data.rows.length + 2)*10))); + 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(start - data.rows.length * 10)); if (data.showWidgets) { Bangle.drawWidgets(); @@ -216,13 +240,13 @@ function setWeather() { 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 + 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; @@ -230,13 +254,13 @@ function setWeather() { 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 + 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; @@ -246,12 +270,12 @@ function setWeather() { 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 + 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; @@ -263,12 +287,12 @@ function setWeather() { 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 + 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; @@ -282,23 +306,23 @@ function setWeather() { 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 + 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 + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x0200, tree2:0, tree3:0x59E0, + fog:0x7BEF }; } } @@ -306,26 +330,26 @@ function setWeather() { 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 + 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 + bg1:0, bg2:0x0005, + path:0, + mtn1:0x0010, mtn2:0x000F, + lake:0x000F, + tree1:0x39E7, tree2:0x39E7, tree3:0x59E0, + cloud1:0x4208, cloud2:1, + snow:0xFFFF }; } } @@ -372,6 +396,18 @@ global.GB = (event) => { }; var drawTimeout; +var redrawTimeout; + +function clockinfoRedraw() { + // Limit redraws to every second + if (!redrawTimeout) { + redrawTimeout = setTimeout(function() { + setWeather(); + clearTimeout(redrawTimeout); + redrawTimeout = undefined; + }, 1000); + } +} //update watchface in next minute function queueDraw() { diff --git a/apps/mtnclock/metadata.json b/apps/mtnclock/metadata.json index c5338e8eb0..c88596d5fa 100644 --- a/apps/mtnclock/metadata.json +++ b/apps/mtnclock/metadata.json @@ -1,26 +1,27 @@ -{ - "id": "mtnclock", - "name": "Mountain Pass Clock", - "shortName": "Mtn Clock", - "version": "0.06", - "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.settings.js","url":"settings.js"}, - {"name":"mtnclock.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"mtnclock.json"}] -} +{ + "id": "mtnclock", + "name": "Mountain Pass Clock", + "shortName": "Mtn Clock", + "version": "0.07", + "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,clkinfo", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies" : { "clock_info":"module"}, + "allow_emulator": true, + "storage": [ + {"name":"mtnclock.app.js","url":"app.js"}, + {"name":"mtnclock.settings.js","url":"settings.js"}, + {"name":"mtnclock.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"mtnclock.json"}] +} diff --git a/apps/mtnclock/screenshot6.png b/apps/mtnclock/screenshot6.png new file mode 100644 index 0000000000..91a87089bf Binary files /dev/null and b/apps/mtnclock/screenshot6.png differ diff --git a/apps/mtnclock/screenshot7.png b/apps/mtnclock/screenshot7.png new file mode 100644 index 0000000000..0f0b6a4d7f Binary files /dev/null and b/apps/mtnclock/screenshot7.png differ diff --git a/apps/mtnclock/settings.js b/apps/mtnclock/settings.js index d2877e281e..0bf6cbf3a0 100644 --- a/apps/mtnclock/settings.js +++ b/apps/mtnclock/settings.js @@ -7,22 +7,128 @@ var SETTINGS = Object.assign({ // default values showWidgets: false, + rows: [] }, STORAGE.readJSON(FILE, true) || {}); function writeSettings() { STORAGE.writeJSON(FILE, SETTINGS); } + let showingClockinfos = false; + let clockinfosSettings = []; + let clockinfos = require("clock_info").load(); + // Show the menu - E.showMenu({ + let menu = { "" : { "title" : "Mountain Clock" }, "< Back" : () => back(), 'Show widgets': { value: !!SETTINGS.showWidgets, // !! converts undefined to false - onchange: value => { + onchange: (value) => { SETTINGS.showWidgets = value; writeSettings(); } }, - }); + 'Edit Clockinfos': () => { + showingClockinfos = true; + drawClockinfoSettings(); + }, + }; + + E.showMenu(menu); + + function drawClockinfoSettings() { + Bangle.setUI(undefined); + require("widget_utils").hide(); + g.clear(); + g.setColor(g.theme.fg); + g.setFont("Vector", py(10)).setFontAlign(-1, -1).drawString(" { + g.reset().clearRect(options.x-1, options.y, options.x+options.w+1, options.y+options.h); + if (options.focus) g.drawRect(options.x-1, options.y, options.x+options.w+1, options.y+options.h); + g.setFont("Vector", py(10)).setFontAlign(-1, 0).drawString(info.text, options.x, options.y+options.h/2); + } + }); + } + + function saveClockinfos() { + SETTINGS.rows = []; + clockinfosSettings.forEach(function(row, r) { + SETTINGS.rows[r] = { + menuA: row.menuA, + menuB: row.menuB + } + row.remove(); + }); + console.log(JSON.stringify(SETTINGS)); + writeSettings(); + } + + //scale x, y coords to screen + function px(x) { + return x*g.getWidth()/100; + } + + function py(y) { + return y*g.getHeight()/100; + } + + function checkTouchBack(xy) { + return xy.x <= px(33) && xy.y < py(25); + } + + function checkTouchMinus(xy) { + return xy.x > px(33) && xy.x < px(67) && xy.y < px(25); + } + + function checkTouchPlus(xy) { + return xy.x >= px(67) && xy.y < px(25); + } + + Bangle.on('touch', function(b, xy) { + if (!showingClockinfos) return; + // Bangle.js 2 supports long touch (type 2) + // On other devices, any touch will show the settings + if (checkTouchBack(xy)) { + showingClockinfos = false; + saveClockinfos(); + // call setWeather after a timeout because for some reason a clockinfo + // can still draw for a little bit despite calling remove() on it + setTimeout(() => { + Bangle.setUI({ + mode: "custom", + back: back, + }); + require("widget_utils").show(); + E.showMenu(menu); + }, 100); + } else if (checkTouchMinus(xy) && clockinfosSettings.length > 0) { + let cl = clockinfosSettings[clockinfosSettings.length - 1]; + cl.remove(); + g.reset().clearRect(cl.x, cl.y, cl.x+cl.w-2, cl.y+cl.h-1); + clockinfosSettings.pop(); + } else if (checkTouchPlus(xy) && clockinfosSettings.length < 3) { + addClockinfo(clockinfosSettings.length) + } + }); })