BangleApps/apps/cutelauncher/app.js

208 lines
7.7 KiB
JavaScript

{
let s = require('Storage');
let settings = Object.assign(
{
showClocks: false,
scrollbar: true
},
s.readJSON('cutelauncher.settings.json', true) || {}
);
// Borrowed caching from Icon Launcher, code by halemmerich.
let launchCache = s.readJSON('launch.cache.json', true) || {};
let launchHash = s.hash(/\.info/) + JSON.stringify(settings).length;
if (launchCache.hash != launchHash) {
launchCache = {
hash: launchHash,
apps: s
.list(/\.info$/)
.map((app) => {
var a = s.readJSON(app, 1);
return a && { name: a.name, type: a.type, icon: a.icon, sortorder: a.sortorder, src: a.src };
})
.filter((app) => app && (app.type == 'app' || (app.type == 'clock' && settings.showClocks) || !app.type))
.sort((a, b) => {
var n = (0 | a.sortorder) - (0 | b.sortorder);
if (n) return n; // do sortorder first
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
}),
};
s.writeJSON('launch.cache.json', launchCache);
}
let apps = launchCache.apps;
apps.forEach((app) => {
if (app.icon) app.icon = s.read(app.icon);
else app.icon = s.read('placeholder.img');
});
require('Font8x16').add(Graphics);
Bangle.drawWidgets = () => { };
Bangle.loadWidgets = () => { };
const ITEM_HEIGHT = 95;
// Create scroll indicator overlay
const overlayWidth = 30;
const overlayHeight = 35;
const overlay = Graphics.createArrayBuffer(overlayWidth, overlayHeight, 16, { msb: true });
// Function to create app backdrop
function createAppBackdrop(y) {
return [
58, y + 5, // Top edge
118, y + 5,
133 - 15 * 0.7, y + 5,
133 - 15 * 0.4, y + 5 + 15 * 0.1,
133 - 15 * 0.1, y + 5 + 15 * 0.4, // Top right corner
133, y + 5 + 15 * 0.7,
133, y + 20,
133, y + 75, // Right edge
133, y + 90 - 15 * 0.7,
133 - 15 * 0.1, y + 90 - 15 * 0.4,
133 - 15 * 0.4, y + 90 - 15 * 0.1, // Bottom right corner
133 - 15 * 0.7, y + 90,
118, y + 90,
58, y + 90, // Bottom edge
43 + 15 * 0.7, y + 90,
43 + 15 * 0.4, y + 90 - 15 * 0.1,
43 + 15 * 0.1, y + 90 - 15 * 0.4, // Bottom left corner
43, y + 90 - 15 * 0.7,
43, y + 75,
43, y + 20, // Left edge
43, y + 5 + 15 * 0.7,
43 + 15 * 0.1, y + 5 + 15 * 0.4,
43 + 15 * 0.4, y + 5 + 15 * 0.1, // Top left corner
43 + 15 * 0.7, y + 5
];
}
// Helper function for creating rounded rectangle points
function createRoundedRectPoints(x1, y1, x2, y2, r) {
return [
x1 + r, y1, // Top edge
x2 - r, y1,
x2 - r * 0.7, y1,
x2 - r * 0.4, y1 + r * 0.1,
x2 - r * 0.1, y1 + r * 0.4, // Top right corner
x2, y1 + r * 0.7,
x2, y1 + r,
x2, y2 - r, // Right edge
x2, y2 - r * 0.7,
x2 - r * 0.1, y2 - r * 0.4,
x2 - r * 0.4, y2 - r * 0.1, // Bottom right corner
x2 - r * 0.7, y2,
x2 - r, y2,
x1 + r, y2, // Bottom edge
x1 + r * 0.7, y2,
x1 + r * 0.4, y2 - r * 0.1,
x1 + r * 0.1, y2 - r * 0.4, // Bottom left corner
x1, y2 - r * 0.7,
x1, y2 - r,
x1, y1 + r, // Left edge
x1, y1 + r * 0.7,
x1 + r * 0.1, y1 + r * 0.4,
x1 + r * 0.4, y1 + r * 0.1, // Top left corner
x1 + r * 0.7, y1
];
}
// Update initScrollIndicator to use the new function
function initScrollIndicator() {
overlay.setBgColor(g.theme.bg).clear();
const points = createRoundedRectPoints(0, 0, overlayWidth, overlayHeight, 10);
overlay.setColor(g.theme.bgH).fillPoly(points);
// Add horizontal lines for scroll thumb aesthetic with outlines
const lineY1 = overlayHeight / 3;
const lineY2 = overlayHeight * 2 / 3;
const lineLeft = 9;
const lineRight = overlayWidth - 9;
// Draw inner lines (increased from ±1 to ±2)
overlay.setColor(g.theme.fg2);
overlay.fillRect(lineLeft - 2, lineY1 - 1, lineRight + 2, lineY1 + 1);
overlay.fillRect(lineLeft - 2, lineY2 - 1, lineRight + 2, lineY2 + 1);
overlay.fillRect(lineLeft, lineY1 - 2, lineRight, lineY1 + 2);
overlay.fillRect(lineLeft, lineY2 - 2, lineRight, lineY2 + 2);
}
initScrollIndicator();
// Function to update scroll indicator
function updateScrollIndicator(idx) {
const marginX = 1;
const marginY = 5;
let scrollPercent = (idx) / (apps.length - 1);
let scrollableHeight = g.getHeight() - marginY * 2 - overlayHeight;
let indicatorY = scrollPercent * scrollableHeight + marginY;
Bangle.setLCDOverlay(overlay, g.getWidth() - overlayWidth - marginX, indicatorY, { id: "scrollIndicator" });
}
let prev_idx = -1;
let second_call = false;
E.showScroller({
h: ITEM_HEIGHT,
c: apps.length,
draw: (idx, rect) => {
g.setFontAlign(0, -1, 0).setFont('8x16');
// Calculate icon dimensions
let icon = apps[idx].icon;
let iconSize = 48;
// Define rectangle size (independent of icon size)
const rectSize = 80;
const rectX = 48;
// Draw rounded rectangle background using the new function
const points = createAppBackdrop(rect.y);
g.setColor(g.theme.bg2).fillPoly(points);
// Draw icon centered in the top portion
let iconPadding = 8;
// Center icon within the rectangle
let iconXInRect = rectX + (rectSize - iconSize) / 2;
g.setColor(g.theme.fg).setBgColor(g.theme.bg2).drawImage(icon, iconXInRect, rect.y + iconPadding + 8);
// Draw app name with ellipsis if too long
const maxWidth = rectSize - 8;
let text = apps[idx].name;
let textWidth = g.stringWidth(text);
if (textWidth > maxWidth) {
const ellipsis = "...";
const ellipsisWidth = g.stringWidth(ellipsis);
while (textWidth + ellipsisWidth > maxWidth && text.length > 0) {
text = text.slice(0, -1);
textWidth = g.stringWidth(text);
}
text = text + ellipsis;
}
let textY = rect.y + iconPadding + iconSize + 15;
g.drawString(text, rectX + rectSize / 2, textY);
if (idx != prev_idx && !second_call && settings.scrollbar) {
updateScrollIndicator(idx);
if (prev_idx == -1) second_call = true;
prev_idx = idx;
} else if (second_call) second_call = false;
},
select: (idx) => {
// Launch the selected app
load(apps[idx].src);
},
remove: () => {
// Remove button handler
setWatch(() => { }, BTN1);
// Remove lock handler
Bangle.removeListener('lock');
// Clear the scroll overlay
Bangle.setLCDOverlay();
}
});
setWatch(Bangle.showClock, BTN1, { debounce: 100 });
// Add lock handler to show clock when locked
Bangle.on('lock', (on) => { if (on) Bangle.showClock(); });
}