diff --git a/apps/elapsed_t/ChangeLog b/apps/elapsed_t/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/elapsed_t/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/elapsed_t/README.md b/apps/elapsed_t/README.md new file mode 100644 index 000000000..f0c72a607 --- /dev/null +++ b/apps/elapsed_t/README.md @@ -0,0 +1,23 @@ +# Elapsed Time Clock +A clock that calculates the time difference between now (in blue/cyan) and any given target date (in red/orange). + +The results is show in years, months, days, hours, minutes, seconds. To save battery life, the seconds are shown only when the watch is unlocked, or can be disabled entirely. + +The time difference is positive if the target date is in the past and negative if it is in the future. + +![Screenshot 1](screenshot1.png) +![Screenshot 2](screenshot2.png) +![Screenshot 3](screenshot3.png) +![Screenshot 4](screenshot4.png) + +# Settings +## Time and date formats: +- time can be shown in 24h or in AM/PM format +- date can be shown in DD/MM/YYYY, MM/DD/YYYY or YYYY-MM-DD format + +## Display years and months +You can select if the difference is shown with years, months and days, or just days. + +# TODO +- add the option to set an alarm to the target date +- add an offset to said alarm (e.g. x hours/days... before/after) diff --git a/apps/elapsed_t/app-icon.js b/apps/elapsed_t/app-icon.js new file mode 100644 index 000000000..0e9a434fc --- /dev/null +++ b/apps/elapsed_t/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcA/4A/AH8kyVJARAQE/YRLn4RD/IRT5cs2QCEEbQgFAQYjIrMlAQwjR5JHIsv2pNkz3JsgjKl/yEAO/I5l/+REBz/7I5f/EYf/I5Vf//2rNlz//8gjJAgIjE/hHIy7xEAAQjIDoIAG+RHHCA///wjHCJIjHMoI1HEY+zCI6zJv4dCFIX9R5PPR4vsEZNJCILXC/77JyXLn4jD/b7KpMnI4fZBARHHpcsEYW2AQIjKARBHIDoICECJIjRpZKCAQYjbCMH/CJVLCAgA/AHYA==")) diff --git a/apps/elapsed_t/app.js b/apps/elapsed_t/app.js new file mode 100644 index 000000000..f799c4f4c --- /dev/null +++ b/apps/elapsed_t/app.js @@ -0,0 +1,437 @@ +const APP_NAME = "elapsed_t"; + +//const COLOUR_BLACK = 0x0; +//const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25) +const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5) +const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75) +const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0) +const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1) +//const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0) +//const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1) +//const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0) +//const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5) +const COLOUR_CYAN = "#00FFFF"; +const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0) + +const SCREEN_WIDTH = g.getWidth(); +const SCREEN_HEIGHT = g.getHeight(); +const BIG_FONT_SIZE = 38; +const SMALL_FONT_SIZE = 22; + +var arrowFont = atob("BwA4AcAOAHADgBwA4McfOf3e/+P+D+A+AOA="); // contains only the > character + +var now = new Date(); + +var settings = Object.assign({ + // default values + displaySeconds: true, + displayMonthsYears: true, + dateFormat: 0, + time24: true +}, require('Storage').readJSON(APP_NAME + ".settings.json", true) || {}); + +var temp_displaySeconds = settings.displaySeconds; + +var data = Object.assign({ + // default values + target: { + isSet: false, + Y: now.getFullYear(), + M: now.getMonth() + 1, // Month is zero-based, so add 1 + D: now.getDate(), + h: now.getHours(), + m: now.getMinutes(), + s: now.getSeconds() + } +}, require('Storage').readJSON(APP_NAME + ".data.json", true) || {}); + +function writeData() { + require('Storage').writeJSON(APP_NAME + ".data.json", data); +} + +function writeSettings() { + require('Storage').writeJSON(APP_NAME + ".settings.json", settings); + temp_displaySeconds = settings.temp_displaySeconds; +} + +let inMenu = false; + +Bangle.on('touch', function (zone, e) { + if (!inMenu) { + if (drawTimeout) clearTimeout(drawTimeout); + E.showMenu(menu); + inMenu = true; + } +}); + +function pad2(number) { + return (String(number).padStart(2, '0')); +} + +function formatDateTime(date, dateFormat, time24, showSeconds) { + var formattedDateTime = { + date: "", + time: "" + }; + + var DD = pad2(date.getDate()); + var MM = pad2(date.getMonth() + 1); // Month is zero-based + var YYYY = date.getFullYear(); + var h = date.getHours(); + var hh = pad2(date.getHours()); + var mm = pad2(date.getMinutes()); + var ss = pad2(date.getSeconds()); + + switch (dateFormat) { + case 0: + formattedDateTime.date = `${DD}/${MM}/${YYYY}`; + break; + + case 1: + formattedDateTime.date = `${MM}/${DD}/${YYYY}`; + break; + + case 2: + formattedDateTime.date = `${YYYY}-${MM}-${DD}`; + break; + + default: + formattedDateTime.date = `${YYYY}-${MM}-${DD}`; + break; + } + + if (time24) { + formattedDateTime.time = `${hh}:${mm}${showSeconds ? `:${ss}` : ''}`; + } else { + var ampm = (h >= 12 ? 'PM' : 'AM'); + var h_ampm = h % 12; + h_ampm = (h_ampm == 0 ? 12 : h_ampm); + formattedDateTime.time = `${h_ampm}:${mm}${showSeconds ? `:${ss}` : ''}${ampm}`; + } + + return formattedDateTime; +} + +function howManyDaysInMonth(month, year) { + return new Date(year, month, 0).getDate(); +} + +function handleExceedingDay() { + var maxDays = howManyDaysInMonth(data.target.M, data.target.Y); + menu.Day.max = maxDays; + if (data.target.D > maxDays) { + menu.Day.value = maxDays; + data.target.D = maxDays; + } +} + +function setTarget(set) { + if (set) { + target = new Date( + data.target.Y, + data.target.M - 1, + data.target.D, + data.target.h, + data.target.m, + data.target.s + ); + data.target.isSet = true; + } else { + target = new Date(); + Object.assign( + data, + { + target: { + isSet: false, + Y: now.getFullYear(), + M: now.getMonth() + 1, // Month is zero-based, so add 1 + D: now.getDate(), + h: now.getHours(), + m: now.getMinutes(), + s: now.getSeconds() + } + } + ); + } + + writeData(); +} + +var target; +setTarget(data.target.isSet); + +var drawTimeout; +var queueMillis = 1000; + +var menu = { + "": { + "title": "Set target", + back: function () { + E.showMenu(); + Bangle.setUI("clock"); + inMenu = false; + draw(); + } + }, + 'Day': { + value: data.target.D, + min: 1, max: 31, wrap: true, + onchange: v => { + data.target.D = v; + } + }, + 'Month': { + value: data.target.M, + min: 1, max: 12, noList: true, wrap: true, + onchange: v => { + data.target.M = v; + handleExceedingDay(); + } + }, + 'Year': { + value: data.target.Y, + min: 1900, max: 2100, + onchange: v => { + data.target.Y = v; + handleExceedingDay(); + } + }, + 'Hours': { + value: data.target.h, + min: 0, max: 23, wrap: true, + onchange: v => { + data.target.h = v; + }, + format: function (v) { return pad2(v); } + }, + 'Minutes': { + value: data.target.m, + min: 0, max: 59, wrap: true, + onchange: v => { + data.target.m = v; + }, + format: function (v) { return pad2(v); } + }, + 'Seconds': { + value: data.target.s, + min: 0, max: 59, wrap: true, + onchange: v => { + data.target.s = v; + }, + format: function (v) { return pad2(v); } + }, + 'Save': function () { + E.showMenu(); + inMenu = false; + Bangle.setUI("clock"); + setTarget(true); + writeSettings(); + temp_displaySeconds = settings.displaySeconds; + updateQueueMillis(settings.displaySeconds); + draw(); + }, + 'Reset': function () { + E.showMenu(); + inMenu = false; + Bangle.setUI("clock"); + setTarget(false); + updateQueueMillis(settings.displaySeconds); + draw(); + } +}; + +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + var delay = queueMillis - (Date.now() % queueMillis); + if (queueMillis == 60000 && signIsNegative()) { + delay += 1000; + } + + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, delay); +} + +function updateQueueMillis(displaySeconds) { + if (displaySeconds) { + queueMillis = 1000; + } else { + queueMillis = 60000; + } +} + +Bangle.on('lock', function (on, reason) { + if (on) { // screen is locked + temp_displaySeconds = false; + updateQueueMillis(false); + draw(); + } else { // screen is unlocked + temp_displaySeconds = settings.displaySeconds; + updateQueueMillis(temp_displaySeconds); + draw(); + } +}); + +function signIsNegative() { + var now = new Date(); + return (now < target); +} + +function diffToTarget() { + var diff = { + sign: "+", + Y: "0", + M: "0", + D: "0", + hh: "00", + mm: "00", + ss: "00" + }; + + if (!data.target.isSet) { + return (diff); + } + + var now = new Date(); + diff.sign = now < target ? '-' : '+'; + + if (settings.displayMonthsYears) { + var start; + var end; + + if (now > target) { + start = target; + end = now; + } else { + start = now; + end = target; + } + + diff.Y = end.getFullYear() - start.getFullYear(); + diff.M = end.getMonth() - start.getMonth(); + diff.D = end.getDate() - start.getDate(); + diff.hh = end.getHours() - start.getHours(); + diff.mm = end.getMinutes() - start.getMinutes(); + diff.ss = end.getSeconds() - start.getSeconds(); + + // Adjust negative differences + if (diff.ss < 0) { + diff.ss += 60; + diff.mm--; + } + if (diff.mm < 0) { + diff.mm += 60; + diff.hh--; + } + if (diff.hh < 0) { + diff.hh += 24; + diff.D--; + } + if (diff.D < 0) { + var lastMonthDays = new Date(end.getFullYear(), end.getMonth(), 0).getDate(); + diff.D += lastMonthDays; + diff.M--; + } + if (diff.M < 0) { + diff.M += 12; + diff.Y--; + } + + + } else { + var timeDifference = target - now; + timeDifference = Math.abs(timeDifference); + + // Calculate days, hours, minutes, and seconds + diff.D = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); + diff.hh = Math.floor((timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + diff.mm = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); + diff.ss = Math.floor((timeDifference % (1000 * 60)) / 1000); + } + + // add zero padding + diff.hh = pad2(diff.hh); + diff.mm = pad2(diff.mm); + diff.ss = pad2(diff.ss); + + return diff; +} + +function draw() { + var nowFormatted = formatDateTime(new Date(), settings.dateFormat, settings.time24, temp_displaySeconds); + var targetFormatted = formatDateTime(target, settings.dateFormat, settings.time24, true); + var diff = diffToTarget(); + + var diffYMD; + if (settings.displayMonthsYears) + diffYMD = `${diff.sign}${diff.Y}Y ${diff.M}M ${diff.D}D`; + else + diffYMD = `${diff.sign}${diff.D}D`; + + var diff_hhmm = `${diff.hh}:${diff.mm}`; + + g.clearRect(0, 24, SCREEN_WIDTH, SCREEN_HEIGHT); + //console.log("drawing"); + + let y = 24; //Bangle.getAppRect().y; + + // draw current date + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_CYAN : COLOUR_BLUE); + g.drawString(nowFormatted.date, 4, y); + y += SMALL_FONT_SIZE; + + // draw current time + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_CYAN : COLOUR_BLUE); + g.drawString(nowFormatted.time, 4, y); + y += SMALL_FONT_SIZE; + + // draw arrow + g.setFontCustom(arrowFont, 62, 16, 13).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_ORANGE : COLOUR_RED); + g.drawString(">", 4, y + 3); + + if (data.target.isSet) { + // draw target date + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_ORANGE : COLOUR_RED); + g.drawString(targetFormatted.date, 4 + 16 + 6, y); + y += SMALL_FONT_SIZE; + + // draw target time + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_ORANGE : COLOUR_RED); + g.drawString(targetFormatted.time, 4, y); + y += SMALL_FONT_SIZE + 4; + + } else { + // draw NOT SET + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_ORANGE : COLOUR_RED); + g.drawString("NOT SET", 4 + 16 + 6, y); + y += 2 * SMALL_FONT_SIZE + 4; + } + + // draw separator + g.setColor(g.theme.fg); + g.drawLine(0, y - 4, SCREEN_WIDTH, y - 4); + + // draw diffYMD + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(0, -1).setColor(g.theme.fg); + g.drawString(diffYMD, SCREEN_WIDTH / 2, y); + y += SMALL_FONT_SIZE; + + // draw diff_hhmm + g.setFont("Vector", BIG_FONT_SIZE).setFontAlign(0, -1).setColor(g.theme.fg); + g.drawString(diff_hhmm, SCREEN_WIDTH / 2, y); + + // draw diff_ss + if (temp_displaySeconds) { + g.setFont("Vector", SMALL_FONT_SIZE).setFontAlign(-1, -1).setColor(g.theme.dark ? COLOUR_LIGHT_GREY : COLOUR_GREY); + g.drawString(diff.ss, SCREEN_WIDTH / 2 + 52, y + 13); + } + + queueDraw(); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +Bangle.setUI("clock"); + +draw(); diff --git a/apps/elapsed_t/app.png b/apps/elapsed_t/app.png new file mode 100644 index 000000000..c2cac4fa1 Binary files /dev/null and b/apps/elapsed_t/app.png differ diff --git a/apps/elapsed_t/metadata.json b/apps/elapsed_t/metadata.json new file mode 100644 index 000000000..fe9af630f --- /dev/null +++ b/apps/elapsed_t/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "elapsed_t", + "name": "Elapsed Time Clock", + "shortName": "Elapsed Time", + "type": "clock", + "version":"0.01", + "description": "A clock that calculates the time difference between now and any given target date.", + "tags": "clock,tool", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"elapsed_t.app.js","url":"app.js"}, + {"name":"elapsed_t.settings.js","url":"settings.js"}, + {"name":"elapsed_t.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"elapsed_t.data.json"}], + "icon": "app.png", + "readme": "README.md", + "screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }, { "url": "screenshot3.png" }, { "url": "screenshot4.png" }], + "allow_emulator":true +} diff --git a/apps/elapsed_t/screenshot1.png b/apps/elapsed_t/screenshot1.png new file mode 100644 index 000000000..d15a5a9ae Binary files /dev/null and b/apps/elapsed_t/screenshot1.png differ diff --git a/apps/elapsed_t/screenshot2.png b/apps/elapsed_t/screenshot2.png new file mode 100644 index 000000000..67dd4f186 Binary files /dev/null and b/apps/elapsed_t/screenshot2.png differ diff --git a/apps/elapsed_t/screenshot3.png b/apps/elapsed_t/screenshot3.png new file mode 100644 index 000000000..8ca6212f6 Binary files /dev/null and b/apps/elapsed_t/screenshot3.png differ diff --git a/apps/elapsed_t/screenshot4.png b/apps/elapsed_t/screenshot4.png new file mode 100644 index 000000000..e2a10ab62 Binary files /dev/null and b/apps/elapsed_t/screenshot4.png differ diff --git a/apps/elapsed_t/settings.js b/apps/elapsed_t/settings.js new file mode 100644 index 000000000..d3a7cb357 --- /dev/null +++ b/apps/elapsed_t/settings.js @@ -0,0 +1,55 @@ +(function(back) { + var APP_NAME = "elapsed_t"; + var FILE = APP_NAME + ".settings.json"; + // Load settings + var settings = Object.assign({ + // default values + displaySeconds: true, + displayMonthsYears: true, + dateFormat: 0, + time24: true + }, require('Storage').readJSON(APP_NAME + ".settings.json", true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + var dateFormats = ["DD/MM/YYYY", "MM/DD/YYYY", "YYYY-MM-DD"]; + + // Show the menu + E.showMenu({ + "" : { "title" : "Elapsed Time" }, + "< Back" : () => back(), + 'Show\nseconds': { + value: !!settings.displaySeconds, + onchange: v => { + settings.displaySeconds = v; + writeSettings(); + } + }, + 'Show months/\nyears': { + value: !!settings.displayMonthsYears, + onchange: v => { + settings.displayMonthsYears = v; + writeSettings(); + } + }, + 'Time format': { + value: !!settings.time24, + onchange: v => { + settings.time24 = v; + writeSettings(); + }, + format: function (v) {return v ? "24h" : "AM/PM";} + }, + 'Date format': { + value: settings.dateFormat, + min: 0, max: 2, wrap: true, + onchange: v => { + settings.dateFormat = v; + writeSettings(); + }, + format: function (v) {return dateFormats[v];} + } + }); +}) diff --git a/apps/vpw_clock/ChangeLog b/apps/vpw_clock/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/vpw_clock/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/vpw_clock/README.md b/apps/vpw_clock/README.md new file mode 100644 index 000000000..521c08365 --- /dev/null +++ b/apps/vpw_clock/README.md @@ -0,0 +1,17 @@ +# Vaporwave Sunset Clock +This is a simple clock with a Vaporwave Sunset theme. + +![Screenshot](screenshot.png) +![Screenshot 2](screenshot2.png) +![Screenshot 3](screenshot3.png) + +# Settings + +You can select the text color: +- white +- ref +- purple + +# Todo + +- add support for AM/PM time \ No newline at end of file diff --git a/apps/vpw_clock/app-icon.js b/apps/vpw_clock/app-icon.js new file mode 100644 index 000000000..f90e4e6c6 --- /dev/null +++ b/apps/vpw_clock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcAtu27YC/AX4C/AX4C/AVnXroRO3oDBtwRMv9p02atPTCJf7AwgRK74gBEYWmpoRJ7wGF5oRJ8wjFzoRJ+nTps0AQYRI24jGzc2CJAgEAQQREBQc1EAYCDugWDEYe/EZm+/fvAR33799ARwj/EfruIARAj/AQ88+fPARwjRA")) diff --git a/apps/vpw_clock/app.js b/apps/vpw_clock/app.js new file mode 100644 index 000000000..41e44ec10 --- /dev/null +++ b/apps/vpw_clock/app.js @@ -0,0 +1,161 @@ +const COLOUR_BLACK = 0x0; +const COLOUR_WHITE = 0xffff; +//const COLOUR_DARK_GREY = "#3F3F3F"; +//const COLOUR_GREY = "#7F7F7F"; +//const COLOUR_LIGHT_GREY = "#BFBFBF"; +const COLOUR_RED = "#FF0000"; +//const COLOUR_BLUE = "#0000FF"; +//const COLOUR_YELLOW = "#FFFF00"; +//const COLOUR_LIGHT_CYAN = "#7FFFFF"; +//const COLOUR_DARK_YELLOW = "#7F7F00"; +//const COLOUR_DARK_CYAN = "#007F7F"; +//const COLOUR_ORANGE = "#FF7F00"; +const COLOUR_VPW_GREEN = 0xf0f; +//const COLOUR_MAGENTA = "#ff00ff"; +const COLOUR_PURPLE = "#8000FF"; + +var settings = Object.assign({ + // default values + foregroundColor: 0 +}, require('Storage').readJSON("vpw_clock.settings.json", true) || {}); + +var foregroundColor = COLOUR_BLACK; + +switch (settings.foregroundColor) { + case 0: + foregroundColor = COLOUR_RED; + break; + + case 1: + foregroundColor = COLOUR_PURPLE; + break; + + case 2: + foregroundColor = COLOUR_WHITE; + break; + + default: + foregroundColor = COLOUR_BLACK; // to detect problems + break; +} + +Graphics.prototype.setFontMadeSunflower = function () { + // Actual height 46 (45 - 0) + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('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'))), + 46, + atob("DxMiFyAgIiAgICEgDw=="), + 60 | 65536 + ); +}; + +var sun_img = require("heatshrink").decompress(atob("2GwwZC/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AUjdt23btuAH/MNHwQCD7CA8AQlsIGsGHwwCD2BAzgI+IAQfAIOVp22bARZAxjaAKAQdsIOGatOmARuAIF02QBgCEIFsBQBwCDQlsNQB4CC7BBsQCACDIFcDQCACDsBBqjSDUtBBq6dtmwCTIFMGQCQCD0BBognTps0AScwINEmQa2mIE8BmmTpICVwBBnQCoCCIM8NknSpoCV6BBmhKDYzRBmfyACJIMyAXQdECpMkyQCXoBBlQbVgIMkSQbVIIMkaQbVoQf6DmyVpkwCZIMqDapJB/IP5BnghB/IL0gIP5B/IP5B/IP5B/IP5B/IP5B/IP5B4gBB/ILxAjIP5B/IP4AGiRBapBB/IP5BogRBaoBB/IP4CCIEgABIP5B/AAcJILGQIM0BILGAIP5BogBBYIE8AghBWkBB/INUAIKxApgESIKlIINUCIKlAINUAIKhArgEJIKWQINkBIKWAINkAIKRAtAAJBQIF8AiRBOpBBwgBBOIGMAhJBMyBBygEEIJUgIGYABiRBIpBA1ZBLC0QxSA4AH4A/AH4A/AGA")); + +function drawPolygonWithGrid(x1, y1, x2, y2, x3, y3, x4, y4, M, N) { + // Draw the polygon + g.drawLine(x1, y1, x2, y2); + g.drawLine(x2, y2, x3, y3); + g.drawLine(x3, y3, x4, y4); + g.drawLine(x4, y4, x1, y1); + + for (let i = 1; i < N; i++) { + let xi1 = x1 + i * ((x2 - x1) / N); + let yi1 = y1 + i * ((y2 - y1) / N); + + let xi2 = x4 - i * ((x4 - x3) / N); + let yi2 = y4 - i * ((y4 - y3) / N); + + g.drawLine(xi1, yi1, xi2, yi2); + } + + for (let j = 1; j < M; j++) { + let xi1 = x1 + j * ((x4 - x1) / M); + let yi1 = y1 + j * ((y4 - y1) / M); + + let xi2 = x2 - j * ((x2 - x3) / M); + let yi2 = y2 - j * ((y2 - y3) / M); + + g.drawLine(xi1, yi1, xi2, yi2); + } +} + +var SCREEN_WIDTH = 176; +var SCREEN_HEIGHT = 176; +var GROUND_HEIGHT = 176 - 45; + +var GRID_BASE_OFFSET = 100; + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + var x = g.getWidth() / 2; + var y = 24 + 20; + + g.reset().clearRect(0, 24, g.getWidth(), g.getHeight()); + + //sky + g.setColor(COLOUR_VPW_GREEN); + g.fillRect(0, 24, SCREEN_WIDTH, GROUND_HEIGHT - 1); + + g.drawImage(sun_img, 0, 0); + + //ground + g.setColor("#8000FF"); + g.fillRect(0, GROUND_HEIGHT, 176, SCREEN_HEIGHT); + + //lines + g.setColor(COLOUR_WHITE); + drawPolygonWithGrid(0, GROUND_HEIGHT, + SCREEN_WIDTH, GROUND_HEIGHT, + SCREEN_WIDTH + GRID_BASE_OFFSET, SCREEN_HEIGHT - 1, + 0 - GRID_BASE_OFFSET, SCREEN_HEIGHT - 1, + 7, //vertical + 15); //horizontal + + // work out locale-friendly date/time + var date = new Date(); + var timeStr = require("locale").time(date, 1); + var dateStr = require("locale").date(date).toUpperCase(); + var dowStr = require("locale").dow(date).toUpperCase(); + // draw time + g.setFontAlign(0, 0).setFontMadeSunflower().setColor(foregroundColor); + g.drawString(timeStr, x, y + 20); + // draw date + y += 35; + g.setFontAlign(0, 0, 1).setFont("6x8"); + g.drawString(dateStr, g.getWidth() - 8, g.getHeight() / 2); + // draw the day of the week + g.setFontAlign(0, 0, 3).setFont("6x8"); + g.drawString(dowStr, 8, g.getHeight() / 2); + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.setTheme({ bg: COLOUR_VPW_GREEN, fg: foregroundColor, dark: true }).clear(); +// draw immediately at first, queue update +draw(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', on => { + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/vpw_clock/app.png b/apps/vpw_clock/app.png new file mode 100644 index 000000000..73c69d5a1 Binary files /dev/null and b/apps/vpw_clock/app.png differ diff --git a/apps/vpw_clock/metadata.json b/apps/vpw_clock/metadata.json new file mode 100644 index 000000000..0c2311e00 --- /dev/null +++ b/apps/vpw_clock/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "vpw_clock", + "name": "Vaporwave Sunset Clock", + "shortName": "Vaporwave Sunset", + "type": "clock", + "version":"0.01", + "description": "A clock with a vaporwave sunset theme.", + "tags": "clock", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"vpw_clock.app.js","url":"app.js"}, + {"name":"vpw_clock.settings.js","url":"settings.js"}, + {"name":"vpw_clock.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"vpw_clock.settings.json"}], + "icon": "app.png", + "readme": "README.md", + "screenshots": [{ "url": "screenshot.png" }, { "url": "screenshot2.png" }, { "url": "screenshot3.png" }], + "allow_emulator":true +} diff --git a/apps/vpw_clock/screenshot.png b/apps/vpw_clock/screenshot.png new file mode 100644 index 000000000..d0b2f1926 Binary files /dev/null and b/apps/vpw_clock/screenshot.png differ diff --git a/apps/vpw_clock/screenshot2.png b/apps/vpw_clock/screenshot2.png new file mode 100644 index 000000000..3e4bdfd44 Binary files /dev/null and b/apps/vpw_clock/screenshot2.png differ diff --git a/apps/vpw_clock/screenshot3.png b/apps/vpw_clock/screenshot3.png new file mode 100644 index 000000000..9bd90ec01 Binary files /dev/null and b/apps/vpw_clock/screenshot3.png differ diff --git a/apps/vpw_clock/settings.js b/apps/vpw_clock/settings.js new file mode 100644 index 000000000..5e267f39c --- /dev/null +++ b/apps/vpw_clock/settings.js @@ -0,0 +1,28 @@ +(function(back) { + var FILE = "vpw_clock.settings.json"; + // Load settings + var settings = Object.assign({ + foregroundColor: 0, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + var foregroundColors = ["Red", "Purple", "White"]; + + // Show the menu + E.showMenu({ + "" : { "title" : "Vaporwave Sunset" }, + "< Back" : () => back(), + 'Foreground color': { + value: 0|settings.foregroundColor, // 0| converts undefined to 0 + min: 0, max: 2, + onchange: v => { + settings.foregroundColor = v; + writeSettings(); + }, + format: function (v) {return foregroundColors[v];} + }, + }); + }) \ No newline at end of file