commit
2a7da3a4be
|
|
@ -5,3 +5,10 @@
|
||||||
0.05: Fixes step count not resetting after a new day starts
|
0.05: Fixes step count not resetting after a new day starts
|
||||||
0.06: Added clockbackground app functionality
|
0.06: Added clockbackground app functionality
|
||||||
0.07: Allow custom backgrounds per boxclk config and from the clockbg module
|
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
|
||||||
|
|
@ -1,59 +1,125 @@
|
||||||
{
|
{
|
||||||
/**
|
// 1. Module dependencies and initial configurations
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* 1. Module dependencies and initial configurations
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
let background = require("clockbg");
|
let background = require("clockbg");
|
||||||
let storage = require("Storage");
|
let storage = require("Storage");
|
||||||
let locale = require("locale");
|
let locale = require("locale");
|
||||||
let widgets = require("widget_utils");
|
let widgets = require("widget_utils");
|
||||||
let date = new Date();
|
|
||||||
let bgImage;
|
let bgImage;
|
||||||
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
|
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
|
||||||
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
|
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)) {
|
if (!storage.read(fileName)) {
|
||||||
fileName = 'boxclk.json';
|
fileName = 'boxclk.json';
|
||||||
}
|
}
|
||||||
let boxesConfig = storage.readJSON(fileName, 1) || {};
|
let boxesConfig = storage.readJSON(fileName, 1) || {};
|
||||||
let boxes = {};
|
let boxes = {};
|
||||||
let boxPos = {};
|
let isDragging = false;
|
||||||
let isDragging = {};
|
|
||||||
let wasDragging = {};
|
|
||||||
let doubleTapTimer = null;
|
let doubleTapTimer = null;
|
||||||
let g_setColor;
|
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"));
|
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 w = g.getWidth();
|
||||||
let h = g.getHeight();
|
let h = g.getHeight();
|
||||||
let totalWidth, totalHeight;
|
|
||||||
let drawTimeout;
|
let drawTimeout;
|
||||||
|
|
||||||
/**
|
// 3. Event handlers
|
||||||
* ---------------------------------------------------------------
|
let eventHandlers = {
|
||||||
* 3. Touchscreen Handlers
|
touchHandler: function(zone, e) {
|
||||||
* ---------------------------------------------------------------
|
let boxTouched = false;
|
||||||
*/
|
let touchedBox = null;
|
||||||
|
|
||||||
let touchHandler;
|
for (let boxKey in boxes) {
|
||||||
let dragHandler;
|
if (touchInText(e, boxes[boxKey])) {
|
||||||
let movementDistance = 0;
|
touchedBox = boxKey;
|
||||||
|
boxTouched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
if (boxTouched) {
|
||||||
* ---------------------------------------------------------------
|
// Toggle the selected state of the touched box
|
||||||
* 4. Font loading function
|
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();
|
||||||
|
} else {
|
||||||
|
deselectAllBoxes();
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleTapTimer = setTimeout(() => {
|
||||||
|
doubleTapTimer = null;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. Font loading function
|
||||||
let loadCustomFont = function() {
|
let loadCustomFont = function() {
|
||||||
Graphics.prototype.setFontBrunoAce = function() {
|
Graphics.prototype.setFontBrunoAce = function() {
|
||||||
// Actual height 23 (24 - 2)
|
// Actual height 23 (24 - 2)
|
||||||
|
|
@ -66,45 +132,38 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 5. Initial settings of boxes and their positions
|
||||||
* ---------------------------------------------------------------
|
let isBool = (val, defaultVal) => val !== undefined ? Boolean(val) : defaultVal;
|
||||||
* 5. Initial settings of boxes and their positions
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
for (let key in boxesConfig) {
|
for (let key in boxesConfig) {
|
||||||
if (key === 'bg' && boxesConfig[key].img) {
|
if (key === 'bg' && boxesConfig[key].img) {
|
||||||
bgImage = storage.read(boxesConfig[key].img);
|
bgImage = storage.read(boxesConfig[key].img);
|
||||||
} else if (key !== 'selectedConfig') {
|
} else if (key !== 'selectedConfig') {
|
||||||
boxes[key] = Object.assign({}, boxesConfig[key]);
|
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);
|
||||||
|
|
||||||
let boxKeys = Object.keys(boxes);
|
// Set box position
|
||||||
|
boxes[key].pos = {
|
||||||
boxKeys.forEach((key) => {
|
x: w * boxes[key].boxPos.x,
|
||||||
let boxConfig = boxes[key];
|
y: h * boxes[key].boxPos.y
|
||||||
boxPos[key] = {
|
|
||||||
x: w * boxConfig.boxPos.x,
|
|
||||||
y: h * boxConfig.boxPos.y
|
|
||||||
};
|
};
|
||||||
isDragging[key] = false;
|
// Cache box size
|
||||||
wasDragging[key] = false;
|
boxes[key].cachedSize = null;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// 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() {
|
let modSetColor = function() {
|
||||||
// Save the original setColor function
|
|
||||||
g_setColor = g.setColor;
|
g_setColor = g.setColor;
|
||||||
// Overwrite setColor with the new function
|
|
||||||
g.setColor = function(color) {
|
g.setColor = function(color) {
|
||||||
if (typeof color === "string" && color in g.theme) {
|
if (typeof color === "string" && color in g.theme) {
|
||||||
g_setColor.call(g, g.theme[color]);
|
g_setColor.call(g, g.theme[color]);
|
||||||
|
|
@ -115,7 +174,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let restoreSetColor = function() {
|
let restoreSetColor = function() {
|
||||||
// Restore the original setColor function
|
|
||||||
if (g_setColor) {
|
if (g_setColor) {
|
||||||
g.setColor = g_setColor;
|
g.setColor = g_setColor;
|
||||||
}
|
}
|
||||||
|
|
@ -139,25 +197,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() {
|
let displaySaveIcon = function() {
|
||||||
draw(boxes);
|
draw(boxes);
|
||||||
g.drawImage(saveIcon, w / 2 - 24, h / 2 - 24);
|
g.drawImage(saveIcon, w / 2 - 24, h / 2 - 24);
|
||||||
|
|
@ -168,33 +207,15 @@
|
||||||
}, 2000);
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 7. String forming helper functions
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* 7. String forming helper functions
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
let isBool = function(val, defaultVal) {
|
|
||||||
return typeof val !== 'undefined' ? Boolean(val) : defaultVal;
|
|
||||||
};
|
|
||||||
|
|
||||||
let getDate = function(short, shortMonth, disableSuffix) {
|
let getDate = function(short, shortMonth, disableSuffix) {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const dayOfMonth = date.getDate();
|
const dayOfMonth = date.getDate();
|
||||||
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
|
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
let suffix;
|
let suffix = ["st", "nd", "rd"][(dayOfMonth - 1) % 10] || "th";
|
||||||
if ([1, 21, 31].includes(dayOfMonth)) {
|
let dayOfMonthStr = disableSuffix ? dayOfMonth : `${dayOfMonth}${suffix}`;
|
||||||
suffix = "st";
|
return `${month} ${dayOfMonthStr}${short ? '' : `, ${year}`}`;
|
||||||
} 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 getDayOfWeek = function(date, short) {
|
let getDayOfWeek = function(date, short) {
|
||||||
|
|
@ -207,89 +228,171 @@
|
||||||
return short ? meridian[0] : meridian;
|
return short ? meridian[0] : meridian;
|
||||||
};
|
};
|
||||||
|
|
||||||
let modString = function(boxItem, data) {
|
let formatStr = function(boxItem, data) {
|
||||||
let prefix = boxItem.prefix || '';
|
return `${boxItem.prefix || ''}${data}${boxItem.suffix || ''}`;
|
||||||
let suffix = boxItem.suffix || '';
|
|
||||||
return prefix + data + suffix;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 8. Main draw function and update logic
|
||||||
* ---------------------------------------------------------------
|
let lastDay = -1;
|
||||||
* 8. Main draw function
|
const BATTERY_UPDATE_INTERVAL = 300000;
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
let draw = (function() {
|
let updateBoxData = function() {
|
||||||
let updatePerMinute = true;
|
let date = new Date();
|
||||||
|
let currentDay = date.getDate();
|
||||||
|
let now = Date.now();
|
||||||
|
|
||||||
return function(boxes) {
|
if (boxes.time || boxes.meridian || boxes.date || boxes.dow) {
|
||||||
date = new Date();
|
if (boxes.time) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
g.clear();
|
||||||
|
|
||||||
// Always draw backgrounds full screen
|
// Always draw backgrounds full screen
|
||||||
|
|
||||||
if (bgImage) { // Check for bg in boxclk config
|
if (bgImage) { // Check for bg in boxclk config
|
||||||
g.drawImage(bgImage, 0, 0);
|
g.drawImage(bgImage, 0, 0);
|
||||||
} else { // Otherwise use clockbg module
|
} else { // Otherwise use clockbg module
|
||||||
background.fillRect(0, 0, g.getWidth(), g.getHeight());
|
background.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boxes.time) {
|
if (!isDragging) {
|
||||||
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0).trim());
|
updateBoxData();
|
||||||
updatePerMinute = isBool(boxes.time.short, true);
|
|
||||||
}
|
}
|
||||||
if (boxes.meridian) {
|
|
||||||
boxes.meridian.string = modString(boxes.meridian, locale.meridian(date, isBool(boxes.meridian.short, true)));
|
for (let boxKey in boxes) {
|
||||||
}
|
|
||||||
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];
|
let boxItem = boxes[boxKey];
|
||||||
|
|
||||||
|
// Set font and alignment for each box individually
|
||||||
|
g.setFont(boxItem.font, boxItem.fontSize);
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
|
||||||
calcBoxSize(boxItem);
|
calcBoxSize(boxItem);
|
||||||
const pos = calcBoxPos(boxKey);
|
|
||||||
if (isDragging[boxKey]) {
|
const pos = calcBoxPos(boxItem);
|
||||||
|
|
||||||
|
if (boxItem.selected) {
|
||||||
g.setColor(boxItem.border);
|
g.setColor(boxItem.border);
|
||||||
g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2);
|
g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2);
|
||||||
}
|
}
|
||||||
|
|
||||||
g.drawString(
|
g.drawString(
|
||||||
boxItem,
|
boxItem,
|
||||||
boxItem.string,
|
boxItem.string,
|
||||||
boxPos[boxKey].x + boxItem.xOffset,
|
boxItem.pos.x + boxItem.xOffset,
|
||||||
boxPos[boxKey].y + boxItem.yOffset
|
boxItem.pos.y + boxItem.yOffset
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
if (!Object.values(isDragging).some(Boolean)) {
|
|
||||||
|
if (!isDragging) {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
let interval = updatePerMinute ? 60000 - (Date.now() % 60000) : 1000;
|
let updateInterval = boxes.time && !isBool(boxes.time.short, true) ? 1000 : 60000 - (Date.now() % 60000);
|
||||||
drawTimeout = setTimeout(() => draw(boxes), interval);
|
drawTimeout = setTimeout(draw, updateInterval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
// 9. Helper function for touch event
|
||||||
* ---------------------------------------------------------------
|
let calcBoxPos = function(boxItem) {
|
||||||
* 9. Helper function for touch event
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
let touchInText = function(e, boxItem, boxKey) {
|
|
||||||
calcBoxSize(boxItem);
|
calcBoxSize(boxItem);
|
||||||
const pos = calcBoxPos(boxKey);
|
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) {
|
||||||
|
calcBoxSize(boxItem);
|
||||||
|
const pos = calcBoxPos(boxItem);
|
||||||
return e.x >= pos.x1 &&
|
return e.x >= pos.x1 &&
|
||||||
e.x <= pos.x2 &&
|
e.x <= pos.x2 &&
|
||||||
e.y >= pos.y1 &&
|
e.y >= pos.y1 &&
|
||||||
|
|
@ -297,105 +400,43 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let deselectAllBoxes = function() {
|
let deselectAllBoxes = function() {
|
||||||
Object.keys(isDragging).forEach((boxKey) => {
|
isDragging = false;
|
||||||
isDragging[boxKey] = false;
|
for (let boxKey in boxes) {
|
||||||
});
|
boxes[boxKey].selected = false;
|
||||||
|
}
|
||||||
restoreSetColor();
|
restoreSetColor();
|
||||||
widgets.show();
|
widgets.show();
|
||||||
widgets.swipeOn();
|
widgets.swipeOn();
|
||||||
modSetColor();
|
modSetColor();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 10. Setup function to configure event handlers
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* 10. Setup function to configure event handlers
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
let setup = function() {
|
let setup = function() {
|
||||||
// ------------------------------------
|
Bangle.on('lock', eventHandlers.lockHandler);
|
||||||
// Define the touchHandler function
|
Bangle.on('touch', eventHandlers.touchHandler);
|
||||||
// ------------------------------------
|
Bangle.on('drag', eventHandlers.dragHandler);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------
|
if (boxes.step) {
|
||||||
// Define the dragHandler function
|
boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps);
|
||||||
// ------------------------------------
|
Bangle.on('step', eventHandlers.stepHandler);
|
||||||
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);
|
if (boxes.batt) {
|
||||||
Bangle.on('drag', dragHandler);
|
boxes.batt.lastLevel = E.getBattery();
|
||||||
|
boxes.batt.string = formatStr(boxes.batt, boxes.batt.lastLevel);
|
||||||
|
boxes.batt.lastUpdate = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
Bangle.setUI({
|
Bangle.setUI({
|
||||||
mode: "clock",
|
mode: "clock",
|
||||||
remove: function() {
|
remove: function() {
|
||||||
// Remove event handlers, stop draw timer, remove custom font if used
|
// Remove event handlers, stop draw timer, remove custom font
|
||||||
Bangle.removeListener('touch', touchHandler);
|
Bangle.removeListener('touch', eventHandlers.touchHandler);
|
||||||
Bangle.removeListener('drag', dragHandler);
|
Bangle.removeListener('drag', eventHandlers.dragHandler);
|
||||||
|
Bangle.removeListener('lock', eventHandlers.lockHandler);
|
||||||
|
if (boxes.step) {
|
||||||
|
Bangle.removeListener('step', eventHandlers.stepHandler);
|
||||||
|
}
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
delete Graphics.prototype.setFontBrunoAce;
|
delete Graphics.prototype.setFontBrunoAce;
|
||||||
|
|
@ -405,16 +446,12 @@
|
||||||
widgets.show();
|
widgets.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
loadCustomFont();
|
loadCustomFont();
|
||||||
draw(boxes);
|
draw();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 11. Main execution
|
||||||
* ---------------------------------------------------------------
|
|
||||||
* 11. Main execution part
|
|
||||||
* ---------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
widgets.swipeOn();
|
widgets.swipeOn();
|
||||||
modSetColor();
|
modSetColor();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boxclk",
|
"id": "boxclk",
|
||||||
"name": "Box Clock",
|
"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",
|
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"dependencies" : { "clockbg":"module" },
|
"dependencies" : { "clockbg":"module" },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue