From 078e11173c9eaaeb083f49e14b7946f41f3b801c Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:42:39 -0500 Subject: [PATCH 1/6] Update to V0.08 --- apps/boxclk/ChangeLog | 7 + apps/boxclk/app.js | 571 ++++++++++++++++++++------------------ apps/boxclk/metadata.json | 4 +- 3 files changed, 314 insertions(+), 268 deletions(-) diff --git a/apps/boxclk/ChangeLog b/apps/boxclk/ChangeLog index f7ee41904..62d1b4875 100644 --- a/apps/boxclk/ChangeLog +++ b/apps/boxclk/ChangeLog @@ -5,3 +5,10 @@ 0.05: Fixes step count not resetting after a new day starts 0.06: Added clockbackground app functionality 0.07: Allow custom backgrounds per boxclk config and from the clockbg module +0.08: Improves performance, responsiveness, and bug fixes +- [+] Added box size caching to reduce calculations +- [+] Improved step count with real-time updates +- [+] Improved battery level update logic to reduce unnecessary refreshes +- [+] Fixed optional seconds not displaying in time +- [+] Fixed drag handler by adding E.stopEventPropagation() +- [+] General code optimization and cleanup \ No newline at end of file diff --git a/apps/boxclk/app.js b/apps/boxclk/app.js index 67493aad9..139ad31a1 100644 --- a/apps/boxclk/app.js +++ b/apps/boxclk/app.js @@ -1,59 +1,115 @@ { - /** - * --------------------------------------------------------------- - * 1. Module dependencies and initial configurations - * --------------------------------------------------------------- - */ - + // 1. Module dependencies and initial configurations let background = require("clockbg"); let storage = require("Storage"); let locale = require("locale"); let widgets = require("widget_utils"); - let date = new Date(); let bgImage; let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0; let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json'; - // Add a condition to check if the file exists, if it does not, default to 'boxclk.json' if (!storage.read(fileName)) { fileName = 'boxclk.json'; } let boxesConfig = storage.readJSON(fileName, 1) || {}; let boxes = {}; - let boxPos = {}; - let isDragging = {}; - let wasDragging = {}; + let isDragging = false; let doubleTapTimer = null; let g_setColor; let saveIcon = require("heatshrink").decompress(atob("mEwwkEogA/AHdP/4AK+gWVDBQWNAAIuVGBAIB+UQdhMfGBAHBCxUAgIXHIwPyCxQwEJAgXB+MAl/zBwQGBn8ggQjBGAQXG+EA/4XI/8gBIQXTGAMPC6n/C6HzkREBC6YACC6QAFC57aHCYIXOOgLsEn4XPABIX/C6vykQAEl6/WgCQBC5imFAAT2BC5gCBI4oUCC5x0IC/4X/C4K8Bl4XJ+TCCC4wKBABkvC4tEEoMQCxcBB4IWEC4XyDBUBFwIXGJAIAOIwowDABoWGGB4uHDBwWJAH4AzA")); - /** - * --------------------------------------------------------------- - * 2. Graphical and visual configurations - * --------------------------------------------------------------- - */ - + // 2. Graphical and visual configurations let w = g.getWidth(); let h = g.getHeight(); - let totalWidth, totalHeight; let drawTimeout; - /** - * --------------------------------------------------------------- - * 3. Touchscreen Handlers - * --------------------------------------------------------------- - */ + // 3. Touch and drag handlers + let touchHandler = function(zone, e) { + let boxTouched = false; + let touchedBox = null; + + boxKeys.forEach((boxKey) => { + if (touchInText(e, boxes[boxKey], boxKey)) { + touchedBox = boxKey; + boxTouched = true; + } + }); + + if (boxTouched) { + // Toggle the selected state of the touched box + boxes[touchedBox].selected = !boxes[touchedBox].selected; + + // Update isDragging based on whether any box is selected + isDragging = boxKeys.some(key => boxes[key].selected); + + if (isDragging) { + widgets.hide(); + // Stop propagation of the touch event to prevent other handlers + E.stopEventPropagation(); + } else { + widgets.show(); + widgets.swipeOn(); + // Call updateBoxData when transitioning from dragging to not dragging + updateBoxData(); + } + } else { + // If tapped outside any box, deselect all boxes + deselectAllBoxes(); + } + + // Always redraw after a touch event + draw(); + + // Handle double tap for saving + if (!boxTouched && !isDragging) { + if (doubleTapTimer) { + clearTimeout(doubleTapTimer); + doubleTapTimer = null; + Object.keys(boxes).forEach((boxKey) => { + boxesConfig[boxKey].boxPos.x = (boxes[boxKey].pos.x / w).toFixed(3); + boxesConfig[boxKey].boxPos.y = (boxes[boxKey].pos.y / h).toFixed(3); + }); + storage.write(fileName, JSON.stringify(boxesConfig)); + displaySaveIcon(); + return; + } + + doubleTapTimer = setTimeout(() => { + doubleTapTimer = null; + }, 500); + } + }; + + let dragHandler = function(e) { + // Check if any box is being dragged + if (!isDragging) return; - let touchHandler; - let dragHandler; - let movementDistance = 0; - - /** - * --------------------------------------------------------------- - * 4. Font loading function - * --------------------------------------------------------------- - */ + // Stop propagation of the drag event to prevent other handlers + E.stopEventPropagation(); + + boxKeys.forEach(key => { + if (boxes[key].selected) { + let boxItem = boxes[key]; + if (!boxItem.cachedSize) { + calcBoxSize(boxItem); + } + let newX = boxItem.pos.x + e.dx; + let newY = boxItem.pos.y + e.dy; + + if (newX - boxItem.cachedSize.width / 2 >= 0 && + newX + boxItem.cachedSize.width / 2 <= w && + newY - boxItem.cachedSize.height / 2 >= 0 && + newY + boxItem.cachedSize.height / 2 <= h) { + boxItem.pos.x = newX; + boxItem.pos.y = newY; + } + } + }); + + draw(); + }; + // 4. Font loading function let loadCustomFont = function() { Graphics.prototype.setFontBrunoAce = function() { // Actual height 23 (24 - 2) @@ -61,50 +117,45 @@ E.toString(require('heatshrink').decompress(atob('ABMHwADBh4DKg4bKgIPDAYUfAYV/AYX/AQMD/gmC+ADBn/AByE/GIU8AYUwLxcfAYX/8AnB//4JIP/FgMP4F+CQQBBjwJBFYRbBAd43DHoJpBh/g/xPEK4ZfDgEEORKDDAY8////wADLfZrTCgITBnhEBAYJMBAYMPw4DCM4QDjhwDCjwDBn0+AYMf/gDBh/4AYMH+ADBLpc4ToK/NGYZfnAYcfL4U/x5fBW4LvB/7vC+LvBgHAsBfIn76Cn4WBcYQDFEgJ+CQQYDyH4L/BAZbHLNYjjCAZc8ngDunycBZ4KkBa4KwBnEHY4UB+BfMgf/ZgMH/4XBc4cf4F/gE+ZgRjwAYcfj5jBM4U4M4RQBM4UA8BjIngDFEYJ8BAYUDAYQvCM4ZxBC4V+AYQvBnkBQ4M8gabBJQPAI4WAAYM/GYQaBAYJKCnqyCn5OCn4aBAYIaBAYJPCU4IABnBhIuDXCFAMD+Z/BY4IDBQwOPwEfv6TDAYUPAcwrDAYQ7BAYY/BI4cD8bLCK4RfEAA0BRYTeDcwIrFn0Pw43Bg4DugYDBjxBBU4SvDMYMH/5QBgP/LAQAP8EHN4UPwADHB4YAHA'))), 46, atob("CBEdChgYGhgaGBsaCQ=="), - 32|65536 + 32 | 65536 ); }; }; - /** - * --------------------------------------------------------------- - * 5. Initial settings of boxes and their positions - * --------------------------------------------------------------- - */ + // 5. Initial settings of boxes and their positions + let isBool = (val, defaultVal) => val !== undefined ? Boolean(val) : defaultVal; for (let key in boxesConfig) { if (key === 'bg' && boxesConfig[key].img) { bgImage = storage.read(boxesConfig[key].img); } else if (key !== 'selectedConfig') { boxes[key] = Object.assign({}, boxesConfig[key]); + // Set default values for short, shortMonth, and disableSuffix + boxes[key].short = isBool(boxes[key].short, true); + boxes[key].shortMonth = isBool(boxes[key].shortMonth, true); + boxes[key].disableSuffix = isBool(boxes[key].disableSuffix, false); + + // Set box position + boxes[key].pos = { + x: w * boxes[key].boxPos.x, + y: h * boxes[key].boxPos.y + }; + // Cache box size + boxes[key].cachedSize = null; } } let boxKeys = Object.keys(boxes); - boxKeys.forEach((key) => { - let boxConfig = boxes[key]; - boxPos[key] = { - x: w * boxConfig.boxPos.x, - y: h * boxConfig.boxPos.y - }; - isDragging[key] = false; - wasDragging[key] = false; - }); + // 6. Text and drawing functions - /** - * --------------------------------------------------------------- - * 6. Text and drawing functions - * --------------------------------------------------------------- + /* + Overwrite the setColor function to allow the + use of (x) in g.theme.x as a string + in your JSON config ("fg", "bg", "fg2", "bg2", "fgH", "bgH") */ - - // Overwrite the setColor function to allow the - // use of (x) in g.theme.x as a string - // in your JSON config ("fg", "bg", "fg2", "bg2", "fgH", "bgH") let modSetColor = function() { - // Save the original setColor function g_setColor = g.setColor; - // Overwrite setColor with the new function g.setColor = function(color) { if (typeof color === "string" && color in g.theme) { g_setColor.call(g, g.theme[color]); @@ -115,7 +166,6 @@ }; let restoreSetColor = function() { - // Restore the original setColor function if (g_setColor) { g.setColor = g_setColor; } @@ -139,25 +189,6 @@ } }; - let calcBoxSize = function(boxItem) { - g.reset(); - g.setFontAlign(0,0); - g.setFont(boxItem.font, boxItem.fontSize); - let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline; - let fontHeight = g.getFontHeight() + 2 * boxItem.outline; - totalWidth = strWidth + 2 * boxItem.xPadding; - totalHeight = fontHeight + 2 * boxItem.yPadding; - }; - - let calcBoxPos = function(boxKey) { - return { - x1: boxPos[boxKey].x - totalWidth / 2, - y1: boxPos[boxKey].y - totalHeight / 2, - x2: boxPos[boxKey].x + totalWidth / 2, - y2: boxPos[boxKey].y + totalHeight / 2 - }; - }; - let displaySaveIcon = function() { draw(boxes); g.drawImage(saveIcon, w / 2 - 24, h / 2 - 24); @@ -168,33 +199,15 @@ }, 2000); }; - /** - * --------------------------------------------------------------- - * 7. String forming helper functions - * --------------------------------------------------------------- - */ - - let isBool = function(val, defaultVal) { - return typeof val !== 'undefined' ? Boolean(val) : defaultVal; - }; - + // 7. String forming helper functions let getDate = function(short, shortMonth, disableSuffix) { const date = new Date(); const dayOfMonth = date.getDate(); const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0); const year = date.getFullYear(); - let suffix; - if ([1, 21, 31].includes(dayOfMonth)) { - suffix = "st"; - } else if ([2, 22].includes(dayOfMonth)) { - suffix = "nd"; - } else if ([3, 23].includes(dayOfMonth)) { - suffix = "rd"; - } else { - suffix = "th"; - } - let dayOfMonthStr = disableSuffix ? dayOfMonth : dayOfMonth + suffix; - return month + " " + dayOfMonthStr + (short ? '' : (", " + year)); // not including year for short version + let suffix = ["st", "nd", "rd"][(dayOfMonth - 1) % 10] || "th"; + let dayOfMonthStr = disableSuffix ? dayOfMonth : `${dayOfMonth}${suffix}`; + return `${month} ${dayOfMonthStr}${short ? '' : `, ${year}`}`; }; let getDayOfWeek = function(date, short) { @@ -207,98 +220,179 @@ return short ? meridian[0] : meridian; }; - let modString = function(boxItem, data) { - let prefix = boxItem.prefix || ''; - let suffix = boxItem.suffix || ''; - return prefix + data + suffix; + let formatStr = function(boxItem, data) { + return `${boxItem.prefix || ''}${data}${boxItem.suffix || ''}`; }; - /** - * --------------------------------------------------------------- - * 8. Main draw function - * --------------------------------------------------------------- - */ + // 8. Main draw function and update logic + let lastDay = -1; + const BATTERY_UPDATE_INTERVAL = 300000; - let draw = (function() { - let updatePerMinute = true; + let updateBoxData = function() { + let date = new Date(); + let currentDay = date.getDate(); + let now = Date.now(); - return function(boxes) { - date = new Date(); - g.clear(); - - // Always draw backgrounds full screen - - if (bgImage) { // Check for bg in boxclk config - g.drawImage(bgImage, 0, 0); - } else { // Otherwise use clockbg module - background.fillRect(0, 0, g.getWidth(), g.getHeight()); - } - + if (boxes.time || boxes.meridian || boxes.date || boxes.dow) { if (boxes.time) { - boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0).trim()); - updatePerMinute = isBool(boxes.time.short, true); - } - if (boxes.meridian) { - boxes.meridian.string = modString(boxes.meridian, locale.meridian(date, isBool(boxes.meridian.short, true))); - } - if (boxes.date) { - boxes.date.string = ( - modString(boxes.date, - getDate(isBool(boxes.date.short, true), - isBool(boxes.date.shortMonth, true), - isBool(boxes.date.disableSuffix, false) - ))); - } - if (boxes.dow) { - boxes.dow.string = modString(boxes.dow, getDayOfWeek(date, isBool(boxes.dow.short, true))); - } - if (boxes.batt) { - boxes.batt.string = modString(boxes.batt, E.getBattery()); - } - if (boxes.step) { - boxes.step.string = modString(boxes.step, Bangle.getHealthStatus("day").steps); - } - boxKeys.forEach((boxKey) => { - let boxItem = boxes[boxKey]; - calcBoxSize(boxItem); - const pos = calcBoxPos(boxKey); - if (isDragging[boxKey]) { - g.setColor(boxItem.border); - g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2); + let showSeconds = !boxes.time.short; + let timeString = locale.time(date, 1).trim(); + if (showSeconds) { + let seconds = date.getSeconds().toString().padStart(2, '0'); + timeString += ':' + seconds; + } + let newTimeString = formatStr(boxes.time, timeString); + if (newTimeString !== boxes.time.string) { + boxes.time.string = newTimeString; + boxes.time.cachedSize = null; } - g.drawString( - boxItem, - boxItem.string, - boxPos[boxKey].x + boxItem.xOffset, - boxPos[boxKey].y + boxItem.yOffset - ); - }); - if (!Object.values(isDragging).some(Boolean)) { - if (drawTimeout) clearTimeout(drawTimeout); - let interval = updatePerMinute ? 60000 - (Date.now() % 60000) : 1000; - drawTimeout = setTimeout(() => draw(boxes), interval); } - }; - })(); - /** - * --------------------------------------------------------------- - * 9. Helper function for touch event - * --------------------------------------------------------------- - */ + if (boxes.meridian) { + let newMeridianString = formatStr(boxes.meridian, locale.meridian(date, boxes.meridian.short)); + if (newMeridianString !== boxes.meridian.string) { + boxes.meridian.string = newMeridianString; + boxes.meridian.cachedSize = null; + } + } + + if (boxes.date && currentDay !== lastDay) { + let newDateString = formatStr(boxes.date, + getDate(boxes.date.short, + boxes.date.shortMonth, + boxes.date.noSuffix) + ); + if (newDateString !== boxes.date.string) { + boxes.date.string = newDateString; + boxes.date.cachedSize = null; + } + } + + if (boxes.dow) { + let newDowString = formatStr(boxes.dow, getDayOfWeek(date, boxes.dow.short)); + if (newDowString !== boxes.dow.string) { + boxes.dow.string = newDowString; + boxes.dow.cachedSize = null; + } + } + + lastDay = currentDay; + } + + if (boxes.step) { + let newStepCount = Bangle.getHealthStatus("day").steps; + let newStepString = formatStr(boxes.step, newStepCount); + if (newStepString !== boxes.step.string) { + boxes.step.string = newStepString; + boxes.step.cachedSize = null; + } + } + + if (boxes.batt) { + if (!boxes.batt.lastUpdate || now - boxes.batt.lastUpdate >= BATTERY_UPDATE_INTERVAL) { + let currentLevel = E.getBattery(); + if (currentLevel !== boxes.batt.lastLevel) { + let newBattString = formatStr(boxes.batt, currentLevel); + if (newBattString !== boxes.batt.string) { + boxes.batt.string = newBattString; + boxes.batt.cachedSize = null; + boxes.batt.lastLevel = currentLevel; + } + } + boxes.batt.lastUpdate = now; + } + } + }; + + let draw = function() { + g.clear(); + + // Always draw backgrounds full screen + if (bgImage) { // Check for bg in boxclk config + g.drawImage(bgImage, 0, 0); + } else { // Otherwise use clockbg module + background.fillRect(0, 0, g.getWidth(), g.getHeight()); + } + + if (!isDragging) { + updateBoxData(); + } + + boxKeys.forEach((boxKey) => { + let boxItem = boxes[boxKey]; + + // Set font and alignment for each box individually + g.setFont(boxItem.font, boxItem.fontSize); + g.setFontAlign(0, 0); + + // Use cached size if available, otherwise calculate and cache + if (!boxItem.cachedSize) { + calcBoxSize(boxItem); + } + + const pos = { + x1: boxItem.pos.x - boxItem.cachedSize.width / 2, + y1: boxItem.pos.y - boxItem.cachedSize.height / 2, + x2: boxItem.pos.x + boxItem.cachedSize.width / 2, + y2: boxItem.pos.y + boxItem.cachedSize.height / 2 + }; + + if (boxItem.selected) { + g.setColor(boxItem.border); + g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2); + } + + g.drawString( + boxItem, + boxItem.string, + boxItem.pos.x + boxItem.xOffset, + boxItem.pos.y + boxItem.yOffset + ); + }); + + if (!isDragging) { + if (drawTimeout) clearTimeout(drawTimeout); + let updateInterval = boxes.time && !isBool(boxes.time.short, true) ? 1000 : 60000 - (Date.now() % 60000); + drawTimeout = setTimeout(draw, updateInterval); + } + }; + + // 9. Helper function for touch event + let calcBoxSize = function(boxItem) { + g.setFont(boxItem.font, boxItem.fontSize); + g.setFontAlign(0, 0); + + let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline; + let fontHeight = g.getFontHeight() + 2 * boxItem.outline; + let totalWidth = strWidth + 2 * boxItem.xPadding; + let totalHeight = fontHeight + 2 * boxItem.yPadding; + + boxItem.cachedSize = { + width: totalWidth, + height: totalHeight + }; + }; let touchInText = function(e, boxItem, boxKey) { - calcBoxSize(boxItem); - const pos = calcBoxPos(boxKey); + if (!boxItem.cachedSize) { + calcBoxSize(boxItem); + } + const pos = { + x1: boxItem.pos.x - boxItem.cachedSize.width / 2, + y1: boxItem.pos.y - boxItem.cachedSize.height / 2, + x2: boxItem.pos.x + boxItem.cachedSize.width / 2, + y2: boxItem.pos.y + boxItem.cachedSize.height / 2 + }; return e.x >= pos.x1 && - e.x <= pos.x2 && - e.y >= pos.y1 && - e.y <= pos.y2; + e.x <= pos.x2 && + e.y >= pos.y1 && + e.y <= pos.y2; }; let deselectAllBoxes = function() { - Object.keys(isDragging).forEach((boxKey) => { - isDragging[boxKey] = false; + isDragging = false; + boxKeys.forEach((boxKey) => { + boxes[boxKey].selected = false; }); restoreSetColor(); widgets.show(); @@ -306,96 +400,37 @@ modSetColor(); }; - /** - * --------------------------------------------------------------- - * 10. Setup function to configure event handlers - * --------------------------------------------------------------- - */ - + // 10. Setup function to configure event handlers let setup = function() { - // ------------------------------------ - // Define the touchHandler function - // ------------------------------------ - touchHandler = function(zone, e) { - wasDragging = Object.assign({}, isDragging); - let boxTouched = false; - boxKeys.forEach((boxKey) => { - if (touchInText(e, boxes[boxKey], boxKey)) { - isDragging[boxKey] = true; - wasDragging[boxKey] = true; - boxTouched = true; - } - }); - if (!boxTouched) { - if (!Object.values(isDragging).some(Boolean)) { // check if no boxes are being dragged - deselectAllBoxes(); - if (doubleTapTimer) { - clearTimeout(doubleTapTimer); - doubleTapTimer = null; - // Save boxesConfig on double tap outside of any box and when no boxes are being dragged - Object.keys(boxPos).forEach((boxKey) => { - boxesConfig[boxKey].boxPos.x = (boxPos[boxKey].x / w).toFixed(3); - boxesConfig[boxKey].boxPos.y = (boxPos[boxKey].y / h).toFixed(3); - }); - storage.write(fileName, JSON.stringify(boxesConfig)); - displaySaveIcon(); - return; - } - } else { - // if any box is being dragged, just deselect all without saving - deselectAllBoxes(); - } + Bangle.on('lock', function(isLocked) { + if (isLocked) { + // Screen is about to lock, deselect all boxes + deselectAllBoxes(); + // Redraw to reflect changes + draw(); } - if (Object.values(wasDragging).some(Boolean) || !boxTouched) { - draw(boxes); - } - doubleTapTimer = setTimeout(() => { - doubleTapTimer = null; - }, 500); // Increase or decrease this value based on the desired double tap timing - movementDistance = 0; - }; - - // ------------------------------------ - // Define the dragHandler function - // ------------------------------------ - dragHandler = function(e) { - // Check if any box is being dragged - if (!Object.values(isDragging).some(Boolean)) return; - // Calculate the movement distance - movementDistance += Math.abs(e.dx) + Math.abs(e.dy); - // Check if the movement distance exceeds a threshold - if (movementDistance > 1) { - boxKeys.forEach((boxKey) => { - if (isDragging[boxKey]) { - widgets.hide(); - let boxItem = boxes[boxKey]; - calcBoxSize(boxItem); - let newX = boxPos[boxKey].x + e.dx; - let newY = boxPos[boxKey].y + e.dy; - if (newX - totalWidth / 2 >= 0 && - newX + totalWidth / 2 <= w && - newY - totalHeight / 2 >= 0 && - newY + totalHeight / 2 <= h ) { - boxPos[boxKey].x = newX; - boxPos[boxKey].y = newY; - } - const pos = calcBoxPos(boxKey); - g.clearRect(pos.x1, pos.y1, pos.x2, pos.y2); - } - }); - draw(boxes); - } - }; + }); Bangle.on('touch', touchHandler); Bangle.on('drag', dragHandler); + if (boxes.step) { + boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps); + } + if (boxes.batt) { + boxes.batt.lastLevel = E.getBattery(); + boxes.batt.string = formatStr(boxes.batt, boxes.batt.lastLevel); + boxes.batt.lastUpdate = Date.now(); + } + Bangle.setUI({ - mode : "clock", - remove : function() { - // Remove event handlers, stop draw timer, remove custom font if used + mode: "clock", + remove: function() { + // Remove event handlers, stop draw timer, remove custom font Bangle.removeListener('touch', touchHandler); Bangle.removeListener('drag', dragHandler); + Bangle.removeListener('step'); + Bangle.removeAllListeners('lock'); if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; delete Graphics.prototype.setFontBrunoAce; @@ -406,17 +441,21 @@ } }); loadCustomFont(); - draw(boxes); + draw(); }; - /** - * --------------------------------------------------------------- - * 11. Main execution part - * --------------------------------------------------------------- - */ - + // 11. Main execution Bangle.loadWidgets(); widgets.swipeOn(); modSetColor(); setup(); -} + + // Event listener for real-time step updates + Bangle.on('step', function(up) { + if (boxes.step && !isDragging) { + boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps); + boxes.step.cachedSize = null; + draw(); + } + }); +} \ No newline at end of file diff --git a/apps/boxclk/metadata.json b/apps/boxclk/metadata.json index b4055f160..27e43c3be 100644 --- a/apps/boxclk/metadata.json +++ b/apps/boxclk/metadata.json @@ -1,7 +1,7 @@ { "id": "boxclk", "name": "Box Clock", - "version": "0.07", + "version": "0.08", "description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background", "icon": "app.png", "dependencies" : { "clockbg":"module" }, @@ -24,4 +24,4 @@ "data": [ {"name":"boxclk.json","url":"boxclk.json"} ] -} +} \ No newline at end of file From 16a5650c410616c8aef77896ab9360de29eca535 Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 6 Sep 2024 00:13:44 -0500 Subject: [PATCH 2/6] Remove redundant code --- apps/boxclk/app.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/boxclk/app.js b/apps/boxclk/app.js index 139ad31a1..1bb8bd9dc 100644 --- a/apps/boxclk/app.js +++ b/apps/boxclk/app.js @@ -44,13 +44,8 @@ if (isDragging) { widgets.hide(); - // Stop propagation of the touch event to prevent other handlers - E.stopEventPropagation(); } else { - widgets.show(); - widgets.swipeOn(); - // Call updateBoxData when transitioning from dragging to not dragging - updateBoxData(); + deselectAllBoxes(); } } else { // If tapped outside any box, deselect all boxes From 48e02ba13fef2d43adaa253ec2df69d8cf5c85fd Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:22:58 -0500 Subject: [PATCH 3/6] Move check for cachedSize into calcBoxSize --- apps/boxclk/app.js | 66 +++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/apps/boxclk/app.js b/apps/boxclk/app.js index 1bb8bd9dc..8f0449a23 100644 --- a/apps/boxclk/app.js +++ b/apps/boxclk/app.js @@ -85,9 +85,7 @@ boxKeys.forEach(key => { if (boxes[key].selected) { let boxItem = boxes[key]; - if (!boxItem.cachedSize) { - calcBoxSize(boxItem); - } + calcBoxSize(boxItem); let newX = boxItem.pos.x + e.dx; let newY = boxItem.pos.y + e.dy; @@ -320,17 +318,9 @@ g.setFont(boxItem.font, boxItem.fontSize); g.setFontAlign(0, 0); - // Use cached size if available, otherwise calculate and cache - if (!boxItem.cachedSize) { - calcBoxSize(boxItem); - } + calcBoxSize(boxItem); - const pos = { - x1: boxItem.pos.x - boxItem.cachedSize.width / 2, - y1: boxItem.pos.y - boxItem.cachedSize.height / 2, - x2: boxItem.pos.x + boxItem.cachedSize.width / 2, - y2: boxItem.pos.y + boxItem.cachedSize.height / 2 - }; + const pos = calcBoxPos(boxItem); if (boxItem.selected) { g.setColor(boxItem.border); @@ -353,31 +343,41 @@ }; // 9. Helper function for touch event - let calcBoxSize = function(boxItem) { - g.setFont(boxItem.font, boxItem.fontSize); - g.setFontAlign(0, 0); - - let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline; - let fontHeight = g.getFontHeight() + 2 * boxItem.outline; - let totalWidth = strWidth + 2 * boxItem.xPadding; - let totalHeight = fontHeight + 2 * boxItem.yPadding; - - boxItem.cachedSize = { - width: totalWidth, - height: totalHeight - }; - }; - - let touchInText = function(e, boxItem, boxKey) { - if (!boxItem.cachedSize) { - calcBoxSize(boxItem); - } - const pos = { + let calcBoxPos = function(boxItem) { + calcBoxSize(boxItem); + return { x1: boxItem.pos.x - boxItem.cachedSize.width / 2, y1: boxItem.pos.y - boxItem.cachedSize.height / 2, x2: boxItem.pos.x + boxItem.cachedSize.width / 2, y2: boxItem.pos.y + boxItem.cachedSize.height / 2 }; + }; + + // Use cached size if available, otherwise calculate and cache + let calcBoxSize = function(boxItem) { + if (boxItem.cachedSize) { + return boxItem.cachedSize; + } + + g.setFont(boxItem.font, boxItem.fontSize); + g.setFontAlign(0, 0); + + let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline; + let fontHeight = g.getFontHeight() + 2 * boxItem.outline; + let totalWidth = strWidth + 2 * boxItem.xPadding; + let totalHeight = fontHeight + 2 * boxItem.yPadding; + + boxItem.cachedSize = { + width: totalWidth, + height: totalHeight + }; + + return boxItem.cachedSize; + }; + + let touchInText = function(e, boxItem, boxKey) { + calcBoxSize(boxItem); + const pos = calcBoxPos(boxItem); return e.x >= pos.x1 && e.x <= pos.x2 && e.y >= pos.y1 && From 5f6fd9247df079b9678607f79e13f8ec8858ce50 Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:21:00 -0500 Subject: [PATCH 4/6] Simplified code by removing boxKeys array --- apps/boxclk/app.js | 85 +++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/apps/boxclk/app.js b/apps/boxclk/app.js index 8f0449a23..faaf4b7d2 100644 --- a/apps/boxclk/app.js +++ b/apps/boxclk/app.js @@ -28,19 +28,20 @@ let boxTouched = false; let touchedBox = null; - boxKeys.forEach((boxKey) => { - if (touchInText(e, boxes[boxKey], boxKey)) { + for (let boxKey in boxes) { + if (touchInText(e, boxes[boxKey])) { touchedBox = boxKey; boxTouched = true; + break; } - }); + } if (boxTouched) { // Toggle the selected state of the touched box boxes[touchedBox].selected = !boxes[touchedBox].selected; // Update isDragging based on whether any box is selected - isDragging = boxKeys.some(key => boxes[key].selected); + isDragging = Object.values(boxes).some(box => box.selected); if (isDragging) { widgets.hide(); @@ -60,10 +61,10 @@ if (doubleTapTimer) { clearTimeout(doubleTapTimer); doubleTapTimer = null; - Object.keys(boxes).forEach((boxKey) => { + for (let boxKey in boxes) { boxesConfig[boxKey].boxPos.x = (boxes[boxKey].pos.x / w).toFixed(3); boxesConfig[boxKey].boxPos.y = (boxes[boxKey].pos.y / h).toFixed(3); - }); + } storage.write(fileName, JSON.stringify(boxesConfig)); displaySaveIcon(); return; @@ -76,30 +77,30 @@ }; let dragHandler = function(e) { - // Check if any box is being dragged - if (!isDragging) return; - - // Stop propagation of the drag event to prevent other handlers - E.stopEventPropagation(); + // Check if any box is being dragged + if (!isDragging) return; - boxKeys.forEach(key => { - if (boxes[key].selected) { - let boxItem = boxes[key]; - calcBoxSize(boxItem); - let newX = boxItem.pos.x + e.dx; - let newY = boxItem.pos.y + e.dy; + // Stop propagation of the drag event to prevent other handlers + E.stopEventPropagation(); - if (newX - boxItem.cachedSize.width / 2 >= 0 && - newX + boxItem.cachedSize.width / 2 <= w && - newY - boxItem.cachedSize.height / 2 >= 0 && - newY + boxItem.cachedSize.height / 2 <= h) { - boxItem.pos.x = newX; - boxItem.pos.y = newY; - } - } - }); + for (let key in boxes) { + if (boxes[key].selected) { + let boxItem = boxes[key]; + calcBoxSize(boxItem); + let newX = boxItem.pos.x + e.dx; + let newY = boxItem.pos.y + e.dy; - draw(); + if (newX - boxItem.cachedSize.width / 2 >= 0 && + newX + boxItem.cachedSize.width / 2 <= w && + newY - boxItem.cachedSize.height / 2 >= 0 && + newY + boxItem.cachedSize.height / 2 <= h) { + boxItem.pos.x = newX; + boxItem.pos.y = newY; + } + } + } + + draw(); }; // 4. Font loading function @@ -299,42 +300,42 @@ let draw = function() { g.clear(); - + // Always draw backgrounds full screen if (bgImage) { // Check for bg in boxclk config g.drawImage(bgImage, 0, 0); } else { // Otherwise use clockbg module background.fillRect(0, 0, g.getWidth(), g.getHeight()); } - + if (!isDragging) { updateBoxData(); } - - boxKeys.forEach((boxKey) => { + + for (let boxKey in boxes) { let boxItem = boxes[boxKey]; - + // Set font and alignment for each box individually g.setFont(boxItem.font, boxItem.fontSize); g.setFontAlign(0, 0); - + calcBoxSize(boxItem); - + const pos = calcBoxPos(boxItem); - + if (boxItem.selected) { g.setColor(boxItem.border); g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2); } - + g.drawString( boxItem, boxItem.string, boxItem.pos.x + boxItem.xOffset, boxItem.pos.y + boxItem.yOffset ); - }); - + } + if (!isDragging) { if (drawTimeout) clearTimeout(drawTimeout); let updateInterval = boxes.time && !isBool(boxes.time.short, true) ? 1000 : 60000 - (Date.now() % 60000); @@ -375,7 +376,7 @@ return boxItem.cachedSize; }; - let touchInText = function(e, boxItem, boxKey) { + let touchInText = function(e, boxItem) { calcBoxSize(boxItem); const pos = calcBoxPos(boxItem); return e.x >= pos.x1 && @@ -386,9 +387,9 @@ let deselectAllBoxes = function() { isDragging = false; - boxKeys.forEach((boxKey) => { + for (let boxKey in boxes) { boxes[boxKey].selected = false; - }); + } restoreSetColor(); widgets.show(); widgets.swipeOn(); @@ -453,4 +454,4 @@ draw(); } }); -} \ No newline at end of file +} From 69d12f31148d0a34ef367d0f69a39b1b1c8f34a7 Mon Sep 17 00:00:00 2001 From: stweedo <108593831+stweedo@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:32:21 -0500 Subject: [PATCH 5/6] Remove boxKeys declaration --- apps/boxclk/app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/boxclk/app.js b/apps/boxclk/app.js index faaf4b7d2..ce54da25d 100644 --- a/apps/boxclk/app.js +++ b/apps/boxclk/app.js @@ -139,8 +139,6 @@ } } - let boxKeys = Object.keys(boxes); - // 6. Text and drawing functions /* From 4bf23b4fbb38473f93e8fd803707ae303e9ced5d Mon Sep 17 00:00:00 2001 From: stweedo Date: Sat, 7 Sep 2024 02:37:03 -0500 Subject: [PATCH 6/6] Revise event handlers --- apps/boxclk/app.js | 180 +++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 88 deletions(-) diff --git a/apps/boxclk/app.js b/apps/boxclk/app.js index ce54da25d..8287b64c8 100644 --- a/apps/boxclk/app.js +++ b/apps/boxclk/app.js @@ -23,84 +23,100 @@ let h = g.getHeight(); let drawTimeout; - // 3. Touch and drag handlers - let touchHandler = function(zone, e) { - let boxTouched = false; - let touchedBox = null; - - for (let boxKey in boxes) { - if (touchInText(e, boxes[boxKey])) { - touchedBox = boxKey; - boxTouched = true; - break; + // 3. Event handlers + let eventHandlers = { + touchHandler: function(zone, e) { + let boxTouched = false; + let touchedBox = null; + + for (let boxKey in boxes) { + if (touchInText(e, boxes[boxKey])) { + touchedBox = boxKey; + boxTouched = true; + break; + } } - } - - if (boxTouched) { + + if (boxTouched) { // Toggle the selected state of the touched box - boxes[touchedBox].selected = !boxes[touchedBox].selected; + boxes[touchedBox].selected = !boxes[touchedBox].selected; // Update isDragging based on whether any box is selected - isDragging = Object.values(boxes).some(box => box.selected); - - if (isDragging) { - widgets.hide(); + isDragging = Object.values(boxes).some(box => box.selected); + + if (isDragging) { + widgets.hide(); + } else { + deselectAllBoxes(); + } } else { + // If tapped outside any box, deselect all boxes deselectAllBoxes(); } - } else { - // If tapped outside any box, deselect all boxes - deselectAllBoxes(); - } - + // Always redraw after a touch event - draw(); - + draw(); + // Handle double tap for saving - if (!boxTouched && !isDragging) { - if (doubleTapTimer) { - clearTimeout(doubleTapTimer); - doubleTapTimer = null; - for (let boxKey in boxes) { - boxesConfig[boxKey].boxPos.x = (boxes[boxKey].pos.x / w).toFixed(3); - boxesConfig[boxKey].boxPos.y = (boxes[boxKey].pos.y / h).toFixed(3); + if (!boxTouched && !isDragging) { + if (doubleTapTimer) { + clearTimeout(doubleTapTimer); + doubleTapTimer = null; + for (let boxKey in boxes) { + boxesConfig[boxKey].boxPos.x = (boxes[boxKey].pos.x / w).toFixed(3); + boxesConfig[boxKey].boxPos.y = (boxes[boxKey].pos.y / h).toFixed(3); + } + storage.write(fileName, JSON.stringify(boxesConfig)); + displaySaveIcon(); + return; } - storage.write(fileName, JSON.stringify(boxesConfig)); - displaySaveIcon(); - return; + + doubleTapTimer = setTimeout(() => { + doubleTapTimer = null; + }, 500); } - - doubleTapTimer = setTimeout(() => { - doubleTapTimer = null; - }, 500); - } - }; - - let dragHandler = function(e) { - // Check if any box is being dragged - if (!isDragging) return; + }, + + dragHandler: function(e) { + if (!isDragging) return; // Stop propagation of the drag event to prevent other handlers - E.stopEventPropagation(); - - for (let key in boxes) { - if (boxes[key].selected) { - let boxItem = boxes[key]; - calcBoxSize(boxItem); - let newX = boxItem.pos.x + e.dx; - let newY = boxItem.pos.y + e.dy; - - if (newX - boxItem.cachedSize.width / 2 >= 0 && - newX + boxItem.cachedSize.width / 2 <= w && - newY - boxItem.cachedSize.height / 2 >= 0 && - newY + boxItem.cachedSize.height / 2 <= h) { - boxItem.pos.x = newX; - boxItem.pos.y = newY; + E.stopEventPropagation(); + + for (let key in boxes) { + if (boxes[key].selected) { + let boxItem = boxes[key]; + calcBoxSize(boxItem); + let newX = boxItem.pos.x + e.dx; + let newY = boxItem.pos.y + e.dy; + + if (newX - boxItem.cachedSize.width / 2 >= 0 && + newX + boxItem.cachedSize.width / 2 <= w && + newY - boxItem.cachedSize.height / 2 >= 0 && + newY + boxItem.cachedSize.height / 2 <= h) { + boxItem.pos.x = newX; + boxItem.pos.y = newY; + } } } + + draw(); + }, + + stepHandler: function(up) { + if (boxes.step && !isDragging) { + boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps); + boxes.step.cachedSize = null; + draw(); + } + }, + + lockHandler: function(isLocked) { + if (isLocked) { + deselectAllBoxes(); + draw(); + } } - - draw(); }; // 4. Font loading function @@ -396,35 +412,31 @@ // 10. Setup function to configure event handlers let setup = function() { - Bangle.on('lock', function(isLocked) { - if (isLocked) { - // Screen is about to lock, deselect all boxes - deselectAllBoxes(); - // Redraw to reflect changes - draw(); - } - }); - - Bangle.on('touch', touchHandler); - Bangle.on('drag', dragHandler); - + Bangle.on('lock', eventHandlers.lockHandler); + Bangle.on('touch', eventHandlers.touchHandler); + Bangle.on('drag', eventHandlers.dragHandler); + if (boxes.step) { boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps); + Bangle.on('step', eventHandlers.stepHandler); } + if (boxes.batt) { boxes.batt.lastLevel = E.getBattery(); boxes.batt.string = formatStr(boxes.batt, boxes.batt.lastLevel); boxes.batt.lastUpdate = Date.now(); } - + Bangle.setUI({ mode: "clock", remove: function() { // Remove event handlers, stop draw timer, remove custom font - Bangle.removeListener('touch', touchHandler); - Bangle.removeListener('drag', dragHandler); - Bangle.removeListener('step'); - Bangle.removeAllListeners('lock'); + Bangle.removeListener('touch', eventHandlers.touchHandler); + Bangle.removeListener('drag', eventHandlers.dragHandler); + Bangle.removeListener('lock', eventHandlers.lockHandler); + if (boxes.step) { + Bangle.removeListener('step', eventHandlers.stepHandler); + } if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; delete Graphics.prototype.setFontBrunoAce; @@ -434,6 +446,7 @@ widgets.show(); } }); + loadCustomFont(); draw(); }; @@ -443,13 +456,4 @@ widgets.swipeOn(); modSetColor(); setup(); - - // Event listener for real-time step updates - Bangle.on('step', function(up) { - if (boxes.step && !isDragging) { - boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps); - boxes.step.cachedSize = null; - draw(); - } - }); }