mirror of https://github.com/espruino/BangleApps
iconlaunch: Release app
parent
0dd835014b
commit
3bdcc55c9b
|
@ -0,0 +1 @@
|
|||
0.01: Initial release
|
|
@ -0,0 +1,12 @@
|
|||
# Icon launcher
|
||||
|
||||
A launcher inspired by smartphones, with an icon-only scrollable menu.
|
||||
|
||||
This launcher shows 9 apps per screen, making it much faster to navigate versus the default launcher.
|
||||
|
||||

|
||||

|
||||
|
||||
## Technical note
|
||||
|
||||
The app uses `E.showScroller`'s code in the app but not the function itself because `E.showScroller` doesn't report the position of a press to the select function.
|
|
@ -0,0 +1,201 @@
|
|||
const s = require("Storage");
|
||||
const settings = s.readJSON("launch.json", true) || { showClocks: true, fullscreen: false };
|
||||
|
||||
if (!settings.fullscreen) {
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
var 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)
|
||||
);
|
||||
apps.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;
|
||||
});
|
||||
apps.forEach((app) => {
|
||||
if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||
});
|
||||
|
||||
let scroll = 0;
|
||||
let selectedItem = -1;
|
||||
const R = Bangle.appRect;
|
||||
|
||||
const iconSize = 48;
|
||||
|
||||
const appsN = Math.floor(R.w / iconSize);
|
||||
const whitespace = (R.w - appsN * iconSize) / (appsN + 1);
|
||||
|
||||
const itemSize = iconSize + whitespace;
|
||||
|
||||
function drawItem(itemI, r) {
|
||||
g.clearRect(r.x, r.y, r.x + r.w - 1, r.y + r.h - 1);
|
||||
let x = 0;
|
||||
for (let i = itemI * appsN; i < appsN * (itemI + 1); i++) {
|
||||
if (!apps[i]) break;
|
||||
x += whitespace;
|
||||
if (!apps[i].icon) {
|
||||
g.setFontAlign(0,0,0).setFont("12x20:2").drawString("?", x + r.x+iconSize/2, r.y + iconSize/2);
|
||||
} else {
|
||||
g.drawImage(apps[i].icon, x + r.x, r.y);
|
||||
}
|
||||
if (selectedItem == i) {
|
||||
g.drawRect(
|
||||
x + r.x - 1,
|
||||
r.y - 1,
|
||||
x + r.x + iconSize + 1,
|
||||
r.y + iconSize + 1
|
||||
);
|
||||
}
|
||||
x += iconSize;
|
||||
}
|
||||
drawText(itemI);
|
||||
}
|
||||
|
||||
function drawItemAuto(i) {
|
||||
var y = idxToY(i);
|
||||
g.reset().setClipRect(R.x, y, R.x2, y + itemSize);
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: y,
|
||||
w: R.w,
|
||||
h: itemSize
|
||||
});
|
||||
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
||||
}
|
||||
|
||||
let lastIsDown = false;
|
||||
|
||||
function drawText(i) {
|
||||
const selectedApp = apps[selectedItem];
|
||||
const idy = (selectedItem - (selectedItem % 3)) / 3;
|
||||
if (!selectedApp || i != idy) return;
|
||||
const appY = idxToY(idy) + iconSize / 2;
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setFont("12x20");
|
||||
const rect = g.stringMetrics(selectedApp.name);
|
||||
g.clearRect(
|
||||
R.w / 2 - rect.width / 2,
|
||||
appY - rect.height / 2,
|
||||
R.w / 2 + rect.width / 2,
|
||||
appY + rect.height / 2
|
||||
);
|
||||
g.drawString(selectedApp.name, R.w / 2, appY);
|
||||
}
|
||||
|
||||
function selectItem(id, e) {
|
||||
const iconN = E.clip(Math.floor((e.x - R.x) / itemSize), 0, appsN - 1);
|
||||
const appId = id * appsN + iconN;
|
||||
if (appId == selectedItem && apps[appId]) {
|
||||
const app = apps[appId];
|
||||
if (!app.src || s.read(app.src) === undefined) {
|
||||
E.showMessage( /*LANG*/ "App Source\nNot found");
|
||||
} else {
|
||||
load(app.src);
|
||||
}
|
||||
}
|
||||
selectedItem = appId;
|
||||
drawItems();
|
||||
}
|
||||
|
||||
function idxToY(i) {
|
||||
return i * itemSize + R.y - (scroll & ~1);
|
||||
}
|
||||
|
||||
function YtoIdx(y) {
|
||||
return Math.floor((y + (scroll & ~1) - R.y) / itemSize);
|
||||
}
|
||||
|
||||
function drawItems() {
|
||||
g.reset().clearRect(R.x, R.y, R.x2, R.y2);
|
||||
g.setClipRect(R.x, R.y, R.x2, R.y2);
|
||||
var a = YtoIdx(R.y);
|
||||
var b = Math.min(YtoIdx(R.y2), 99);
|
||||
for (var i = a; i <= b; i++)
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: idxToY(i),
|
||||
w: R.w,
|
||||
h: itemSize,
|
||||
});
|
||||
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
||||
}
|
||||
|
||||
drawItems();
|
||||
g.flip();
|
||||
|
||||
const itemsN = Math.ceil(apps.length / appsN);
|
||||
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
drag: (e) => {
|
||||
let dy = e.dy;
|
||||
if (scroll + R.h - dy > itemsN * itemSize) {
|
||||
dy = scroll + R.h - itemsN * itemSize;
|
||||
}
|
||||
if (scroll - dy < 0) {
|
||||
dy = scroll;
|
||||
}
|
||||
scroll -= dy;
|
||||
scroll = E.clip(scroll, 0, itemSize * (itemsN - 1));
|
||||
g.setClipRect(R.x, R.y, R.x2, R.y2);
|
||||
g.scroll(0, dy);
|
||||
if (dy < 0) {
|
||||
g.setClipRect(R.x, R.y2 - (1 - dy), R.x2, R.y2);
|
||||
let i = YtoIdx(R.y2 - (1 - dy));
|
||||
let y = idxToY(i);
|
||||
while (y < R.y2) {
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: y,
|
||||
w: R.w,
|
||||
h: itemSize,
|
||||
});
|
||||
i++;
|
||||
y += itemSize;
|
||||
}
|
||||
} else {
|
||||
// d>0
|
||||
g.setClipRect(R.x, R.y, R.x2, R.y + dy);
|
||||
let i = YtoIdx(R.y + dy);
|
||||
let y = idxToY(i);
|
||||
while (y > R.y - itemSize) {
|
||||
drawItem(i, {
|
||||
x: R.x,
|
||||
y: y,
|
||||
w: R.w,
|
||||
h: itemSize,
|
||||
});
|
||||
y -= itemSize;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
g.setClipRect(0, 0, g.getWidth() - 1, g.getHeight() - 1);
|
||||
},
|
||||
touch: (_, e) => {
|
||||
if (e.y < R.y - 4) return;
|
||||
var i = YtoIdx(e.y);
|
||||
selectItem(i, e);
|
||||
},
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 888 B |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "iconlaunch",
|
||||
"name": "Icon Launcher",
|
||||
"shortName" : "Icon launcher",
|
||||
"version": "0.01",
|
||||
"icon": "app.png",
|
||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||
"tags": "tool,system,launcher",
|
||||
"type": "launch",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{ "name": "iconlaunch.app.js", "url": "app.js" },
|
||||
{ "name": "iconlaunch.settings.js", "url": "settings.js" }
|
||||
],
|
||||
"screenshots": [{ "url": "screenshot1.png" }, { "url": "screenshot2.png" }],
|
||||
"readme": "README.md",
|
||||
"sortorder": -10
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -0,0 +1,28 @@
|
|||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
fullscreen: false
|
||||
}, require("Storage").readJSON("launch.json", true) || {});
|
||||
|
||||
let fonts = g.getFonts();
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require("Storage").write("launch.json",settings);
|
||||
}
|
||||
const appMenu = {
|
||||
"": { "title": /*LANG*/"Launcher" },
|
||||
/*LANG*/"< Back": back,
|
||||
/*LANG*/"Show Clocks": {
|
||||
value: settings.showClocks == true,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: (m) => { save("showClocks", m) }
|
||||
},
|
||||
/*LANG*/"Fullscreen": {
|
||||
value: settings.fullscreen == true,
|
||||
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
|
||||
onchange: (m) => { save("fullscreen", m) }
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu);
|
||||
});
|
Loading…
Reference in New Issue