new custom menu: menuwheel

pull/1005/head
Richard de Boer 2021-12-04 22:13:44 +01:00
parent a385c09922
commit 45a292bc5d
11 changed files with 257 additions and 0 deletions

View File

@ -4675,5 +4675,23 @@
"data": [
{"name":"pooqroman.json"}
]
},
{
"id": "menuwheel",
"name": "Wheel Menus",
"version": "0.01",
"description": "Replace Bangle.js 2's menus with a version that contains variable-size text and a back button",
"readme": "README.md",
"icon": "icon.png",
"screenshots": [
{"url":"screenshot_b1_dark.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_light.png"},
{"url":"screenshot_b2_dark.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_light.png"}
],
"type": "boot",
"tags": "system",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"menuwheel.boot.js","url":"boot.js"}
]
}
]

1
apps/menuwheel/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New menu!

25
apps/menuwheel/README.md Normal file
View File

@ -0,0 +1,25 @@
# Wheel Menu
Replace Bangle.js 2's menus with a version that contains variable-size text and a back button.
Bangle.js 1:
![Dark Mode Screenshot](screenshot_b1_dark.png)
![Light Mode Screenshot](screenshot_b1_light.png)
Bangle.js 2:
![Dark Mode Screenshot](screenshot_b2_dark.png)
![Editing Screenshot](screenshot_b2_edit.png)
![Light Mode Screenshot](screenshot_b2_light.png)
## Features
If the menu contains "Back" or "Exit", it is shown as a button instead.
The menu wraps around, with a divider between the last and first items.
## Controls
Bangle.js 1: Use BTN1/BTN3 to scroll through items, BTN2 to open/edit the selected item.
Bangle.js 2: Swipe up/down to scroll through items, tap/BTN to open/edit the selected item.
Press the back button (if present) to go back.

213
apps/menuwheel/boot.js Normal file
View File

@ -0,0 +1,213 @@
E.showMenu = function(items) {
g.clearRect(Bangle.appRect); // clear screen if no menu supplied
// clean up back button listener
if (Bangle.backHandler) Bangle.removeListener('touch', Bangle.backHandler)
delete Bangle.backHandler;
if (!items) {
Bangle.setUI();
return;
}
var B2 = process.env.HWVERSION===2,
loc = require("locale"),
menuItems = Object.keys(items),
options = items[""];
if (options) menuItems.splice(menuItems.indexOf(""),1);
if (!(options instanceof Object)) options = {};
// show "< Back" item (or similar) as button instead (i.e. remove from the menu)
var back,backLbl;
for (var b of ['Back', 'Exit', 'Cancel']) {
if (!items[b] && items['< '+b]) b = '< '+b;
back = items[b];
if (typeof back === "function") {
backLbl = loc.translate(b);
menuItems.splice(menuItems.indexOf(b),1);
break;
}
else back = undefined;
}
// font sizes
var small = B2?15:22,
large = B2?30:45;
if (options.selected === undefined) options.selected = 0;
var ar = Bangle.appRect,
x = ar.x,
x2 = ar.x2,
w = ar.w,
y = ar.y,
y2 = ar.y2;
if (options.title) y += 22;
var wrap = menuItems.length>3; // don't wrap if all items are always in view anyway
var vc=Math.round((y+y2)/2), // vertical center
hc = Math.round((x+x2)/2), // horizontal center
ih = large+small*2; // active item height
var getItem = idx => {
// we wrap out-of-range indexes
while (idx<0) idx+=menuItems.length;
idx = idx%menuItems.length;
var name = menuItems[idx];
var item = items[name];
var v;
if ("object"== typeof item) {
v = item.value;
if (item.format) v = item.format(v);
v = loc.translate(""+v);
}
return {lbl: loc.translate(name), v: v};
};
var l = {
lastIdx : null, // we want a complete redraw on first run
draw : function() {
var idx = options.selected,
edit = l.selectEdit;
g.reset();
// don't highlight whole item when editing
g.setColor(edit?g.theme.fg:g.theme.fgH)
.setBgColor(edit?g.theme.bg:g.theme.bgH)
.setFont('Vector', large);
var item = getItem(idx),
lw = g.stringWidth(item.lbl)+2;
if (lw+2 >= w) { // label width doesn't fit at large size: scale it down
g.setFont('Vector', Math.floor(large*ar.w/lw));
}
g.clearRect(x,vc-ih/2,x2,vc+ih/2)
.setFontAlign(0,0,0).drawString(item.lbl,hc,vc);
if (item.v !== undefined) {
g.setColor(g.theme.fgH).setBgColor(g.theme.bgH) // always highlighted: either as part of item, or while editing
.setFontAlign(0,1,0)
.setFont('Vector', small)
.clearRect(x,vc+ih/2-small-2,x2,vc+ih/2)
.drawString(item.v,hc,vc+ih/2-1);
if (edit) {
g.drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",x2-23,vc+ih/2-small+(B2?1:5),{scale:2});
}
}
if (l.lastIdx !== idx) {
// we scrolled: redraw all
l.lastIdx=idx;
g.reset();
if (options.title) {
if (B2) g.setFont('12x20');
else g.setFont('6x8',2);
g.drawLine(x, y-2, x2, y-2)
.setFontAlign(0,1,0)
.drawString(options.title, (x+x2)/2, y-2);
}
// clear prev/next items area
g.clearRect(x,y,x2,vc-ih/2-1)
.clearRect(x,vc+ih/2+1,x2,y2);
// get display label by index
var lbl = idx => {
var item = getItem(idx);
if (item.v !== undefined) item.lbl+=': '+item.v;
return item.lbl;
}
// previous two items
g.setFontAlign(0, 1)
if (wrap||idx>0) g.setFont('Vector', small).drawString(lbl(idx-1), hc, vc-ih/2-5);
if (wrap||idx>1) g.setFont('Vector', small/2).drawString(lbl(idx-2), hc, vc-ih/2-small-10);
// next two items
g.setFontAlign(0, -1);
if (wrap||idx<menuItems.length-1) g.setFont('Vector', small).drawString(lbl(idx+1), hc, vc+ih/2+5);
if (wrap||idx<menuItems.length-2) g.setFont('Vector', small/2).drawString(lbl(idx+2), hc, vc+ih/2+small+10);
if (wrap) {
// draw divider between first/last items
var div = y => g.drawLine(x, y, x2, y);
if (idx===0) div(vc-ih/2-1);
if (idx===1) div(vc-ih/2-small-8);
// if (s === 2) div(vc-ih/2-small*1.5-13);
if (idx===menuItems.length-1) div(vc+ih/2+1);
if (idx===menuItems.length-2) div(vc+ih/2+small+6);
// if (s === 2) div(vc+ih/2+small*1.5+13);
}
if (back) {
g.setBgColor(g.theme.bg2)
.setFont('Vector', small);
var bw=g.stringWidth(backLbl);
g.clearRect(x,y, x+bw+2, y+small+2);
var bx1=x, by1=y, bx2=x+bw+2, by2=y+small+2;
// g.drawRect(x,y, x+bw+2, y+small+2);
var poly = [ // button outline
bx1+2,by1,
bx2-2,by1,
bx2, by1+2,
bx2, by2-2,
bx2-2,by2,
bx1+2,by2,
bx1, by2-2,
bx1, by1+2,
]
g.setColor(g.theme.bg2).fillPoly(poly, true)
.setColor(g.theme.fg2).drawPoly(poly, true)
.setFontAlign(-1,-1,0).drawString(backLbl, x+2,y+2);
}
}
g.flip();
},
select : function() { // same as default menu
var item = items[menuItems[options.selected]];
if ("function" == typeof item) {l.lastIdx=null; item(l);} // force a redraw after callback
else if ("object" == typeof item) {
// if a number, go into 'edit mode'
if ("number" == typeof item.value)
l.selectEdit = l.selectEdit?undefined:item;
else { // else just toggle bools
if ("boolean" == typeof item.value) item.value=!item.value;
if (item.onchange) {l.lastIdx=null; item.onchange(item.value);} // force a redraw after callback
}
l.draw();
}
},
move : function(dir) {
if (l.selectEdit) { // same as default menu
var item = l.selectEdit;
item.value -= (dir||1)*(item.step||1);
if (item.min!==undefined && item.value<item.min) item.value = item.wrap ? item.max : item.min;
if (item.max!==undefined && item.value>item.max) item.value = item.wrap ? item.min : item.max;
if (item.onchange) {l.lastIdx=null; item.onchange(item.value);} // force a redraw after callback
} else {
if (B2) dir=-dir; // swipe vs button scrolling
if (!wrap && (options.selected+dir<0 || options.selected+dir>=menuItems.length)) {
return;
}
options.selected = (options.selected+dir+menuItems.length)%menuItems.length;
}
l.draw();
}
};
l.draw();
Bangle.setUI("updown",dir => {
if (dir) l.move(dir);
else l.select();
});
if (back) {
// we have a back button: check touches before passing them to setUI's touchHandler
if (B2) {
Bangle.removeListener('touch', Bangle.touchHandler);
Bangle.backHandler = (b, xy) => {
// anywhere top-left (but above the active item) = back button
if (xy.x<hc && xy.y<vc-ih/2) back();
else Bangle.touchHandler(b, xy);
}
} else {
// Bangle.js 1 has no touchHandler
Bangle.backHandler = (b) => {
// left side = back button
if (b===1) back();
}
}
// note: backHandler is cleaned up at the top of this file
Bangle.on('touch', Bangle.backHandler);
}
return l;
};

BIN
apps/menuwheel/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB