208 lines
7.7 KiB
JavaScript
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(); });
|
|
}
|