diff --git a/apps/line_clock/ChangeLog b/apps/line_clock/ChangeLog new file mode 100644 index 000000000..504dc0efe --- /dev/null +++ b/apps/line_clock/ChangeLog @@ -0,0 +1 @@ +0.1: init app diff --git a/apps/line_clock/LICENSE b/apps/line_clock/LICENSE new file mode 100644 index 000000000..404cbc7a0 --- /dev/null +++ b/apps/line_clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Paul Spenke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/line_clock/README.md b/apps/line_clock/README.md new file mode 100644 index 000000000..5789acbbc --- /dev/null +++ b/apps/line_clock/README.md @@ -0,0 +1,11 @@ +# Line Clock + +This app displays a simple, different looking, analog clock. It considers the +currently configured "theme" (and may therefore look different than shown in +the screenshot on your watch depending on which theme you prefer). + +![](app-screenshot.png) + +## License + +[MIT License](LICENSE) diff --git a/apps/line_clock/app-icon.js b/apps/line_clock/app-icon.js new file mode 100644 index 000000000..eaaf719b4 --- /dev/null +++ b/apps/line_clock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgYMJh/4AgUD+AeKgIRDj/+n41O/4RQABcfIJYAEKZgAkL4U/8ARNBwIRP/+AGx6YBPSH/4ASPh/A/hfDAAZAHg/8gP/LguSoARHEwIRFiVJkDCFjgRHgEJkg4CcwQjIAAMEHAUDCoIRB46kIHAkH//xLIw4I8eAnCNKHAYAO/xxEABg4ByASPHAkBKAbUE/5xGhP//wRFv4RDOIYIB//ACQr1FHAIRJAA0TCAP/ZwIALgYRJVowRCj/4BIkBLIgABgRHC/KqFaI4RC5MkJBlPR4UECJizJJwoAKCKImVQAwAJv0HL5S6CbwIjLCKMAn4RDh0/LMKMhWaYAKA=")) diff --git a/apps/line_clock/app-icon.png b/apps/line_clock/app-icon.png new file mode 100644 index 000000000..275353812 Binary files /dev/null and b/apps/line_clock/app-icon.png differ diff --git a/apps/line_clock/app-screenshot.png b/apps/line_clock/app-screenshot.png new file mode 100644 index 000000000..9d7413388 Binary files /dev/null and b/apps/line_clock/app-screenshot.png differ diff --git a/apps/line_clock/app.js b/apps/line_clock/app.js new file mode 100644 index 000000000..eadc46fad --- /dev/null +++ b/apps/line_clock/app.js @@ -0,0 +1,287 @@ +const handWidth = 6; +const hourRadius = 4; +const hourWidth = 8; +const hourLength = 40; +const hourSLength = 20; +const radius = 220; +const lineOffset = 115; +const hourOffset = 32; +const numberOffset = 85; +const numberSize = 22; + +const storage = require('Storage'); + +const SETTINGS_FILE = "line_clock.setting.json"; + +let initialSettings = { + showLock: true, + showMinute: true, +}; + +let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || initialSettings; +for (const key in saved_settings) { + initialSettings[key] = saved_settings[key]; +} + +let gWidth = g.getWidth(), gCenterX = gWidth/2; +let gHeight = g.getHeight(), gCenterY = gHeight/2; + +let currentTime = new Date(); +let currentHour = currentTime.getHours(); +let currentMinute = currentTime.getMinutes(); + +let drawTimeout; + +function imgLock() { + return { + width : 16, height : 16, bpp : 1, + transparent : 0, + buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) + }; +} + +/** + * Retrieves the angle of the hour hand for the current time. + * + * @returns {number} The angle of the hour hand in degrees. + */ +function getHourHandAngle() { + let hourHandAngle = 30 * currentHour; + hourHandAngle += 0.5 * currentMinute; + return hourHandAngle; +} + +let hourAngle = getHourHandAngle(); + +/** + * Converts degrees to radians. + * + * @param {number} degrees - The degrees to be converted to radians. + * @return {number} - The equivalent value in radians. + */ +function degreesToRadians(degrees) { + return degrees * (Math.PI / 180); +} + +/** + * Rotates an array of points around a given angle and radius. + * + * @param {Array} points - The array of points to be rotated. + * @param {number} angle - The angle in degrees to rotate the points. + * @param {number} rad - The radius to offset the rotation. + * @returns {Array} - The array of rotated points. + */ +function rotatePoints(points, angle, rad) { + const ang = degreesToRadians(angle); + const hAng = degreesToRadians(hourAngle); + const rotatedPoints = []; + points.map(function(point) { + return { + x: point.x * Math.cos(ang) - point.y * Math.sin(ang), + y: point.x * Math.sin(ang) + point.y * Math.cos(ang) + }; + }).forEach(function(point) { + rotatedPoints.push(point.x + gCenterX - (rad * Math.sin(hAng))); + rotatedPoints.push(point.y + gCenterY + (rad * Math.cos(hAng))); + }); + return rotatedPoints; +} + +/** + * Draws a hand on the canvas. + * + * @function drawHand + * + * @returns {void} + */ +function drawHand() { + g.setColor(0xF800); + const halfWidth = handWidth / 2; + + const points = [{ + x: -halfWidth, + y: -gHeight + }, { + x: halfWidth, + y: -gHeight + }, { + x: halfWidth, + y: gHeight + }, { + x: -halfWidth, + y: gHeight + }]; + + g.fillPolyAA(rotatePoints(points, hourAngle, 0)); +} + +/** + * Retrieves the hour coordinates for a given small flag. + * @param {boolean} small - Determines if the flag is small. + * @returns {Array} - An array of hour coordinates. + */ +function getHourCoordinates(small) { + const dist = small ? (hourSLength - hourLength) : 0; + const halfWidth = hourWidth / 2; + const gh = gHeight + lineOffset; + return [{ + x: -halfWidth, + y: -gh - dist + }, { + x: halfWidth, + y: -gh - dist + }, { + x: halfWidth, + y: -gh + hourLength + }, { + x: -halfWidth, + y: -gh + hourLength + }]; +} + +/** + * Assign the given time to the hour dot on the clock face. + * + * @param {number} a - The time value to assign to the hour dot. + * @return {void} + */ +function hourDot(a) { + const h = gHeight + lineOffset; + const rotatedPoints = rotatePoints( + [{ + x: 0, + y: -h + hourLength - (hourRadius / 2) + }], a, radius + ); + g.fillCircle(rotatedPoints[0], rotatedPoints[1], hourRadius); +} + +/** + * Convert an hour into a number and display it on the clock face. + * + * @param {number} a - The hour to be converted (between 0 and 360 degrees). + */ +function hourNumber(a) { + const h = gHeight + lineOffset; + const rotatedPoints = rotatePoints( + [{ + x: 0, + y: -h + hourLength + hourOffset + }], a, radius + ); + g.drawString(String(a / 30), rotatedPoints[0], rotatedPoints[1]); +} + +/** + * Draws a number on the display. + * + * @param {number} n - The number to be drawn. + * @return {void} + */ +function drawNumber(n) { + const h = gHeight + lineOffset; + const halfWidth = handWidth / 2; + const rotatedPoints = rotatePoints( + [{ + x: 0, + y: -h + hourLength + numberOffset + }], hourAngle, radius + ); + g.setColor(0xF800); + g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize+ halfWidth); + g.setColor(g.theme.bg); + g.fillCircle(rotatedPoints[0], rotatedPoints[1], numberSize - halfWidth); + g.setColor(g.theme.fg); + g.setFont("Vector:"+numberSize); + g.drawString(String(n), rotatedPoints[0], rotatedPoints[1]); +} + +const hourPoints = getHourCoordinates(false); +const hourSPoints = getHourCoordinates(true); + +/** + * Draws an hour on a clock face. + * + * @param {number} h - The hour to be drawn on the clock face. + * @return {undefined} + */ +function drawHour(h) { + if (h === 0) { h= 12; } + if (h === 13) { h= 1; } + g.setColor(g.theme.fg); + g.setFont("Vector:32"); + const a = h * 30; + g.fillPolyAA(rotatePoints(hourPoints, a, radius)); + g.fillPolyAA(rotatePoints(hourSPoints, a + 15, radius)); + hourNumber(a); + hourDot(a + 5); + hourDot(a + 10); + hourDot(a + 20); + hourDot(a + 25); +} + +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function lockListenerBw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + draw(); +} +Bangle.on('lock', lockListenerBw); + +Bangle.setUI({ + mode : "clock", + // TODO implement https://www.espruino.com/Bangle.js+Fast+Load + // remove : function() { + // Bangle.removeListener('lock', lockListenerBw); + // if (drawTimeout) clearTimeout(drawTimeout); + // drawTimeout = undefined; + // } +}); + +/** + * Draws a clock on the canvas using the current time. + * + * @return {undefined} + */ +function draw() { + queueDraw(); + currentTime = new Date(); + currentHour = currentTime.getHours(); + if (currentHour > 12) { + currentHour -= 12; + } + currentMinute = currentTime.getMinutes(); + + hourAngle = getHourHandAngle(); + + g.clear(); + g.setFontAlign(0, 0); + + g.setColor(g.theme.bg); + g.fillRect(0, 0, gWidth, gHeight); + + if(initialSettings.showLock && Bangle.isLocked()){ + g.setColor(g.theme.fg); + g.drawImage(imgLock(), gWidth-16, 2); + } + + drawHour(currentHour); + drawHour(currentHour-1); + drawHour(currentHour+1); + + + drawHand(); + + if(initialSettings.showMinute){ + drawNumber(currentMinute); + } +} + +draw(); diff --git a/apps/line_clock/metadata.json b/apps/line_clock/metadata.json new file mode 100644 index 000000000..01393efdf --- /dev/null +++ b/apps/line_clock/metadata.json @@ -0,0 +1,19 @@ +{ "id": "line_clock", + "name": "Line Clock", + "shortName":"Line Clock", + "version":"0.1", + "description": "a readable analog clock", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "screenshots": [{"url":"app-screenshot.png"}], + "readme": "README.md", + "storage": [ + {"name":"line_clock.app.js","url":"app.js"}, + {"name":"line_clock.img","url":"app-icon.js","evaluate":true}, + {"name":"line_clock.settings.js","url":"settings.js"} + ], + "data":[{"name":"line_clock.setting.json"}] +} diff --git a/apps/line_clock/settings.js b/apps/line_clock/settings.js new file mode 100644 index 000000000..5da04e959 --- /dev/null +++ b/apps/line_clock/settings.js @@ -0,0 +1,37 @@ +(function(back) { + const SETTINGS_FILE = "line_clock.setting.json"; + + // initialize with default settings... + const storage = require('Storage') + let settings = { + showLock: true, + showMinute: true, + }; + let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; + for (const key in saved_settings) { + settings[key] = saved_settings[key] + } + + function save() { + storage.write(SETTINGS_FILE, settings) + } + + E.showMenu({ + '': { 'title': 'Line Clock' }, + '< Back': back, + 'Show Lock': { + value: settings.showLock, + onchange: () => { + settings.showLock = !settings.showLock; + save(); + }, + }, + 'Show Minute': { + value: settings.showMinute, + onchange: () => { + settings.showMinute = !settings.showMinute; + save(); + }, + } + }); + })