2024-09-15 10:20:17 +00:00
|
|
|
{
|
|
|
|
// Shared library for face drawing and settings loading.
|
|
|
|
let lib = require('tinyheads.lib.js');
|
|
|
|
|
|
|
|
// Read 12/24 from system settings
|
|
|
|
const is12Hour=(require("Storage").readJSON("setting.json",1)||{})["12hour"] || false;
|
|
|
|
|
|
|
|
// Tinyhead features are stored at a resolution of 18x21, this scales them to the best fit for the Banglejs2 screen
|
|
|
|
const scale=9;
|
|
|
|
|
|
|
|
const closedEyes = 25;
|
|
|
|
const scaredEyes = 26;
|
|
|
|
const scaredMouth = 4;
|
|
|
|
const disconnectedEyes = 3;
|
|
|
|
const glassesEyes = [18, 23, 24];
|
|
|
|
|
|
|
|
let drawTimeout, blinkTimeout, tapTimeout;
|
|
|
|
let activeEyesNum = lib.settings.eyesNum;
|
|
|
|
let eyesNum = activeEyesNum;
|
|
|
|
let activeMouthNum = lib.settings.mouthNum;
|
|
|
|
let mouthNum = activeMouthNum;
|
|
|
|
let helpShown = false;
|
|
|
|
let tapCount = 0;
|
|
|
|
let centerX, centerY, minuteHandLength, hourHandLength, handOutline;
|
2024-11-01 16:51:37 +00:00
|
|
|
let originalTheme = Object.assign({}, g.theme);
|
|
|
|
|
2024-09-15 10:20:17 +00:00
|
|
|
// Open the eyes and schedule the next blink
|
|
|
|
let blinkOpen = function blinkOpen() {
|
|
|
|
if (blinkTimeout) clearTimeout(blinkTimeout);
|
|
|
|
blinkTimeout = setTimeout(function() {
|
|
|
|
blinkTimeout = undefined;
|
|
|
|
blinkClose();
|
|
|
|
}, 3000 + (Math.random() * 10000));
|
|
|
|
eyesNum = activeEyesNum;
|
|
|
|
mouthNum = activeMouthNum;
|
|
|
|
drawTinyhead();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Close the eyes and schedule the next open
|
|
|
|
let blinkClose = function blinkClose() {
|
|
|
|
if (!glassesEyes.includes(activeEyesNum)) {
|
|
|
|
if (blinkTimeout) clearTimeout(blinkTimeout);
|
|
|
|
if (!Bangle.isCharging()) { // Keep eyes shut while charging
|
|
|
|
blinkTimeout = setTimeout(function() {
|
|
|
|
blinkTimeout = undefined;
|
|
|
|
blinkOpen();
|
|
|
|
}, E.getBattery() < 10 ? 6000 + (Math.random() * 6000) : 150); // Doze if battery low, otherwise quick blink
|
|
|
|
}
|
|
|
|
eyesNum = closedEyes; // Closed eyes
|
|
|
|
drawTinyhead();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Tinyhead is scared
|
|
|
|
let scared = function scared() {
|
|
|
|
if (!glassesEyes.includes(activeEyesNum)) {
|
|
|
|
if (blinkTimeout) clearTimeout(blinkTimeout);
|
|
|
|
blinkTimeout = setTimeout(function() {
|
|
|
|
blinkTimeout = undefined;
|
|
|
|
peek();
|
|
|
|
}, 4000); // Terrified for 4 seconds
|
|
|
|
eyesNum = scaredEyes;
|
|
|
|
}
|
|
|
|
if (mouthNum < 10) {
|
|
|
|
mouthNum = scaredMouth;
|
|
|
|
}
|
|
|
|
drawTinyhead();
|
|
|
|
};
|
|
|
|
|
|
|
|
// See if it's safe to open eyes
|
|
|
|
let peek = function peek() {
|
|
|
|
if (blinkTimeout) clearTimeout(blinkTimeout);
|
|
|
|
blinkTimeout = setTimeout(function() {
|
|
|
|
blinkTimeout = undefined;
|
|
|
|
blinkOpen();
|
|
|
|
}, 3000); // Peek for 3 seconds
|
|
|
|
drawTinyhead(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Draw the tinyhead
|
|
|
|
let drawTinyhead = function drawTinyhead(peek) {
|
|
|
|
// Set background to black, the tinyhead is slightly narrower than the banglejs2 screen
|
|
|
|
g.setBgColor(0, 0, 0);
|
|
|
|
g.clearRect(Bangle.appRect);
|
|
|
|
|
|
|
|
let offset = 0;
|
|
|
|
if (shouldDrawDigital()) {
|
|
|
|
offset = 30; // Move the head by half the clock height to keep more of the features in view
|
|
|
|
if (lib.settings.digitalPosition == 'bottom') {
|
|
|
|
offset = offset * -1; // Move the head up if clock is at the bottom
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lib.drawFace(scale, eyesNum, mouthNum, peek, offset);
|
|
|
|
// Draw widgets again as the face will have been drawn above them
|
|
|
|
|
|
|
|
drawClocks();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Draw analog and digital clocks
|
|
|
|
let drawClocks = function drawClocks() {
|
|
|
|
if (shouldDrawAnalog()) {
|
|
|
|
drawAnalog();
|
|
|
|
}
|
|
|
|
if (shouldDrawDigital()) {
|
|
|
|
drawDigital();
|
|
|
|
}
|
|
|
|
queueClocksDraw();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Draw digital clock
|
|
|
|
let drawDigital = function drawDigital() {
|
|
|
|
let width = lib.faceW * scale; // Set width to face width, which is slightly narrower than screen width
|
|
|
|
let height = 60;
|
|
|
|
let yOffset = Bangle.appRect.y;
|
|
|
|
let xOffset = (Bangle.appRect.w - width) / 2;
|
|
|
|
if (lib.settings.digitalPosition == 'bottom') {
|
|
|
|
yOffset = g.getHeight() - height;
|
|
|
|
}
|
|
|
|
|
|
|
|
g.setColor(0, 0, 0);
|
|
|
|
g.fillRect(xOffset, yOffset, width+xOffset, height+yOffset);
|
|
|
|
g.setColor(1, 1, 1);
|
|
|
|
g.fillRect(xOffset+10, yOffset+10, width-10+xOffset, height-10+yOffset);
|
|
|
|
|
|
|
|
let d = new Date();
|
|
|
|
let h = d.getHours(), m = ("0"+d.getMinutes()).substr(-2);
|
|
|
|
if (is12Hour){
|
|
|
|
h = h - 12;
|
|
|
|
}
|
|
|
|
h = ("0"+h).substr(-2);
|
|
|
|
|
|
|
|
g.setColor(0, 0, 0);
|
|
|
|
g.setFont("6x8:4x6");
|
|
|
|
g.drawString(h+":"+m, 22+xOffset, 7+yOffset);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Draw analog clock hands
|
|
|
|
let drawAnalog = function drawAnalog() {
|
|
|
|
let thickness = 4;
|
|
|
|
|
|
|
|
let d = new Date();
|
|
|
|
let h = d.getHours();
|
|
|
|
let m = d.getMinutes();
|
|
|
|
let hRot = (h + m/60) * Math.PI / 6; // Angle of hour hand
|
|
|
|
let mRot = m * Math.PI / 30; // Angle of minute hand
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
if (shouldDrawDigital()) {
|
|
|
|
offset = 30; // Adjust the analog center to match head position
|
|
|
|
if (lib.settings.digitalPosition == 'bottom') {
|
|
|
|
offset = offset * -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g.setColor(lib.settings.analogColour);
|
|
|
|
g.fillPolyAA(g.transformVertices([-thickness, 0, thickness, 0, thickness, -minuteHandLength, -thickness, -minuteHandLength ], {x: centerX, y: centerY+offset, rotate: mRot}));
|
|
|
|
g.setColor(handOutline);
|
|
|
|
g.drawPolyAA(g.transformVertices([-thickness, 0, thickness, 0, thickness, -minuteHandLength, -thickness, -minuteHandLength ], {x: centerX, y: centerY+offset, rotate: mRot}), true);
|
|
|
|
g.setColor(lib.settings.analogColour);
|
|
|
|
g.fillPolyAA(g.transformVertices([-thickness, 0, thickness, 0, thickness, -hourHandLength, -thickness, -hourHandLength ], {x: centerX, y: centerY+offset, rotate: hRot}));
|
|
|
|
g.setColor(handOutline);
|
|
|
|
g.drawPolyAA(g.transformVertices([-thickness, 0, thickness, 0, thickness, -hourHandLength, -thickness, -hourHandLength ], {x: centerX, y: centerY+offset, rotate: hRot}), true);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Schedule a redraw of the clocks
|
|
|
|
let queueClocksDraw = function queueClocksDraw() {
|
|
|
|
if (drawTimeout) clearTimeout(drawTimeout);
|
|
|
|
drawTimeout = setTimeout(function() {
|
|
|
|
drawTimeout = undefined;
|
|
|
|
drawTinyhead();
|
|
|
|
}, (60000) - (Date.now() % (60000)));
|
|
|
|
};
|
|
|
|
|
|
|
|
// Show clocks on unlock
|
|
|
|
let lockHandler = (on, reason) => {
|
|
|
|
if (lib.settings.showWidgets == 'unlock' && !on) {
|
|
|
|
require("widget_utils").show();
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
}
|
|
|
|
if (lib.settings.showWidgets == 'unlock' && on) {
|
|
|
|
require("widget_utils").hide();
|
|
|
|
}
|
|
|
|
if (lib.settings.analogClock == 'unlock' || lib.settings.digitalClock == 'unlock' || lib.settings.showWidgets == 'unlock') {
|
|
|
|
drawTinyhead();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Sleep while charging
|
|
|
|
let chargingHandler = charging => {
|
|
|
|
if (charging) {
|
|
|
|
blinkClose();
|
|
|
|
} else {
|
|
|
|
blinkOpen();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Status eyes on bt disconnect
|
|
|
|
let btDisconnectHandler = () => {
|
|
|
|
activeEyesNum = disconnectedEyes;
|
|
|
|
blinkOpen();
|
|
|
|
};
|
|
|
|
|
|
|
|
// reset eyes to normal
|
|
|
|
let btConnectHandler = () => {
|
|
|
|
activeEyesNum = lib.settings.eyesNum;
|
|
|
|
blinkOpen();
|
|
|
|
};
|
|
|
|
|
|
|
|
let shouldDrawAnalog = function() {
|
|
|
|
return lib.settings.analogClock == 'on' || (lib.settings.analogClock == 'unlock' && !Bangle.isLocked());
|
|
|
|
};
|
|
|
|
let shouldDrawDigital = function() {
|
|
|
|
return lib.settings.digitalClock == 'on' || (lib.settings.digitalClock == 'unlock' && !Bangle.isLocked());
|
|
|
|
};
|
|
|
|
|
|
|
|
let init = function init() {
|
2024-11-01 14:38:01 +00:00
|
|
|
g.setTheme({bg:lib.settings.hairColour,fg:lib.settings.faceColour,dark:true}).clear();
|
|
|
|
|
2024-09-15 10:20:17 +00:00
|
|
|
Bangle.on('lock', lockHandler);
|
|
|
|
Bangle.on('charging', chargingHandler);
|
|
|
|
if (lib.settings.btStatusEyes) {
|
|
|
|
NRF.on('connect', btConnectHandler);
|
|
|
|
NRF.on('disconnect', btDisconnectHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
activeEyesNum = lib.settings.eyesNum;
|
|
|
|
activeMouthNum = lib.settings.mouthNum;
|
|
|
|
if (!NRF.getSecurityStatus().connected && lib.settings.btStatusEyes) {
|
|
|
|
activeEyesNum = disconnectedEyes;
|
|
|
|
}
|
|
|
|
|
2024-11-01 16:51:37 +00:00
|
|
|
Bangle.setUI({
|
2024-09-15 10:20:17 +00:00
|
|
|
mode:"custom",
|
|
|
|
clock: true,
|
|
|
|
touch: (button, xy) => {
|
|
|
|
// Go direct to feature select in settings on long screen press
|
|
|
|
if (xy.type == 2) {
|
|
|
|
eval(require("Storage").read("tinyheads.settings.js"))(()=> {
|
2024-11-01 16:51:37 +00:00
|
|
|
g.setTheme(originalTheme);
|
2024-09-15 10:20:17 +00:00
|
|
|
E.showMenu();
|
|
|
|
init();
|
|
|
|
}, true, helpShown);
|
|
|
|
helpShown = true; // Only trigger the help screen once per run
|
|
|
|
} else {
|
|
|
|
if (tapTimeout) clearTimeout(tapTimeout);
|
|
|
|
tapTimeout = setTimeout(function() {
|
|
|
|
tapTimeout = undefined;
|
|
|
|
tapCount = 0;
|
|
|
|
}, 1500);
|
|
|
|
tapCount++;
|
|
|
|
if (tapCount == 3) {
|
|
|
|
scared();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
remove: function() {
|
2024-11-01 16:51:37 +00:00
|
|
|
g.setTheme(originalTheme);
|
2024-09-15 10:20:17 +00:00
|
|
|
// Clear timeouts and listeners for fast loading
|
|
|
|
if (drawTimeout) clearTimeout(drawTimeout);
|
|
|
|
if (blinkTimeout) clearTimeout(blinkTimeout);
|
|
|
|
Bangle.removeListener("charging", chargingHandler);
|
|
|
|
Bangle.removeListener("lock", lockHandler);
|
|
|
|
if (lib.settings.btStatusEyes) {
|
|
|
|
NRF.removeListener('connect', btConnectHandler);
|
|
|
|
NRF.removeListener('disconnect', btDisconnectHandler);
|
|
|
|
}
|
|
|
|
require("widget_utils").show();
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Always load widgets (needed for fast loading) and display if option selected
|
|
|
|
Bangle.loadWidgets();
|
|
|
|
if (lib.settings.showWidgets == 'on' || (lib.settings.showWidgets == 'unlock' && !Bangle.isLocked())) {
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
} else {
|
|
|
|
require("widget_utils").hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
centerX = (Bangle.appRect.w / 2) + Bangle.appRect.x;
|
|
|
|
centerY = (Bangle.appRect.h / 2) + Bangle.appRect.y;
|
|
|
|
|
|
|
|
minuteHandLength = Math.floor(Math.min(Bangle.appRect.w, Bangle.appRect.h) * 0.45);
|
|
|
|
hourHandLength = Math.floor(Math.min(Bangle.appRect.w, Bangle.appRect.h) * 0.30);
|
|
|
|
|
|
|
|
handOutline = g.toColor( // Calculate a contrasting colour for the hands edge
|
|
|
|
lib.settings.analogColour.substring(1,2)=='f' ? 0 : 1,
|
|
|
|
lib.settings.analogColour.substring(2,3)=='f' ? 0 : 1,
|
|
|
|
lib.settings.analogColour.substring(3,4)=='f' ? 0 : 1
|
|
|
|
);
|
|
|
|
|
|
|
|
// Opening the eyes triggers a face redraw and also starts the blink and clock timers
|
|
|
|
blinkOpen();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
init();
|
|
|
|
|
|
|
|
}
|