Instantly share code, notes, and snippets.
Last active
February 21, 2022 20:57
-
Star
0
(0)
You must be signed in to star a gist -
Fork
1
(1)
You must be signed in to fork a gist
-
Save gfwilliams/0aac0c7a020913a0f162571d9d3b1ee6 to your computer and use it in GitHub Desktop.
test code for new Bangle.js menu style
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
TODO | |
Better rounded rect | |
setUI could be better - maybe include 'back' widget/handling code? | |
submenus could allow 'back' without modifying the menu item | |
*/ | |
g.fillRRect = (x,y,w,h) => g.fillPoly([ | |
x+4,y, | |
x+w-4,y, | |
x+w,y+4, | |
x+w,y+h-4, | |
x+w-4,y+h, | |
x+4,y+h, | |
x,y+h-4, | |
x,y+4 | |
]); | |
Bangle.setUI = (function(mode, cb) { | |
if (Bangle.btnWatches) { | |
Bangle.btnWatches.forEach(clearWatch); | |
delete Bangle.btnWatches; | |
} | |
if (Bangle.dragHandler) { | |
Bangle.removeListener("drag", Bangle.dragHandler); | |
delete Bangle.dragHandler; | |
} | |
if (Bangle.touchHandler) { | |
Bangle.removeListener("touch", Bangle.touchHandler); | |
delete Bangle.touchHandler; | |
} | |
if (Bangle.uiRemove) { | |
Bangle.uiRemove(); | |
delete Bangle.uiRemove; | |
} | |
function b() { | |
try{Bangle.buzz(30);}catch(e){} | |
} | |
if (!mode) return; | |
else if (mode=="updown") { | |
var dy = 0; | |
Bangle.dragHandler = e=>{ | |
dy += e.dy; | |
if (!e.b) dy=0; | |
while (Math.abs(dy)>32) { | |
if (dy>0) { dy-=32; cb(1) } | |
else { dy+=32; cb(-1) } | |
Bangle.buzz(20); | |
} | |
}; | |
Bangle.on('drag',Bangle.dragHandler); | |
Bangle.touchHandler = d => {b();cb();}; | |
Bangle.on("touch", Bangle.touchHandler); | |
Bangle.btnWatches = [ | |
setWatch(function() { b();cb(); }, BTN1, {repeat:1}), | |
]; | |
} else if (mode=="leftright") { | |
var dx = 0; | |
Bangle.dragHandler = e=>{ | |
dx += e.dx; | |
if (!e.b) dx=0; | |
while (Math.abs(dx)>32) { | |
if (dx>0) { dx-=32; cb(1) } | |
else { dx+=32; cb(-1) } | |
Bangle.buzz(20); | |
} | |
}; | |
Bangle.on('drag',Bangle.dragHandler); | |
Bangle.touchHandler = d => {b();cb();}; | |
Bangle.on("touch", Bangle.touchHandler); | |
Bangle.btnWatches = [ | |
setWatch(function() { b();cb(); }, BTN1, {repeat:1}), | |
]; | |
} else if (mode=="clock") { | |
Bangle.CLOCK=1; | |
Bangle.btnWatches = [ | |
setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"falling"}) | |
]; | |
} else if (mode=="clockupdown") { | |
Bangle.CLOCK=1; | |
Bangle.touchHandler = (d,e) => { | |
if (e.x < 120) return; | |
b();cb((e.y > 88) ? 1 : -1); | |
}; | |
Bangle.on("touch", Bangle.touchHandler); | |
Bangle.btnWatches = [ | |
setWatch(Bangle.showLauncher, BTN1, {repeat:1,edge:"falling"}) | |
]; | |
} else if (mode=="touch") { | |
Bangle.touchHandler = (_,e) => {b();cb(e);}; | |
Bangle.on("touch", Bangle.touchHandler); | |
} else | |
throw new Error("Unknown UI mode"); | |
}); | |
E.showScroller = (function(options) { | |
/* options = { | |
h = height | |
c = # of items | |
scroll = initial scroll position | |
scrollMin = minimum scroll amount (can be negative) | |
draw = function(idx, rect) | |
select = function(idx) | |
} | |
returns { | |
draw = draw all | |
drawItem(idx) = draw specific item | |
} | |
*/ | |
Bangle.setUI(); // remove existing handlers | |
if (!options) return; | |
var menuShowing = false; | |
var R = Bangle.appRect; | |
var Y = Bangle.appRect.y; | |
var n = Math.ceil(R.h/options.h); | |
var menuScrollMin = 0|options.scrollMin; | |
var menuScrollMax = options.h*options.c - R.h; | |
if (menuScrollMax<menuScrollMin) menuScrollMax=menuScrollMin; | |
function idxToY(i) { | |
return i*options.h + R.y - rScroll; | |
} | |
function YtoIdx(y) { | |
return Math.floor((y + rScroll - R.y)/options.h); | |
} | |
var s = { | |
scroll : E.clip(0|options.scroll,menuScrollMin,menuScrollMax), | |
draw : () => { | |
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),options.c-1); | |
for (var i=a;i<=b;i++) | |
options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h}); | |
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); | |
}, drawItem : i => { | |
var y = idxToY(i); | |
g.reset().setClipRect(R.x,y,R.x2,y+options.h); | |
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); | |
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); | |
}}; | |
var rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither) | |
s.draw(); // draw the full scroller | |
g.flip(); // force an update now to make this snappier | |
Bangle.dragHandler = e=>{ | |
var dy = e.dy; | |
if (s.scroll - dy > menuScrollMax) | |
dy = s.scroll - menuScrollMax; | |
if (s.scroll - dy < menuScrollMin) | |
dy = s.scroll - menuScrollMin; | |
s.scroll -= dy; | |
var oldScroll = rScroll; | |
rScroll = s.scroll &~1; | |
dy = oldScroll-rScroll; | |
if (!dy) return; | |
g.reset().setClipRect(R.x,R.y,R.x2,R.y2); | |
g.scroll(0,dy); | |
var d = e.dy; | |
if (d < 0) { | |
g.setClipRect(R.x,R.y2-(1-d),R.x2,R.y2); | |
let i = YtoIdx(R.y2-(1-d)); | |
let y = idxToY(i); | |
while (y < R.y2) { | |
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); | |
i++; | |
y += options.h; | |
} | |
} else { // d>0 | |
g.setClipRect(R.x,R.y,R.x2,R.y+d); | |
let i = YtoIdx(R.y+d); | |
let y = idxToY(i); | |
while (y > R.y-options.h) { | |
options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); | |
y -= options.h; | |
i--; | |
} | |
} | |
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); | |
}; | |
Bangle.on('drag',Bangle.dragHandler); | |
Bangle.touchHandler = (_,e)=>{ | |
if (e.y<R.y-4) return; | |
var i = YtoIdx(e.y); | |
if ((menuScrollMin<0 || i>=0) && i<options.c) | |
options.select(i); | |
}; | |
Bangle.on("touch", Bangle.touchHandler); | |
return s; | |
}); | |
E.showMenu = function(menu) { | |
const H = 40; | |
if (menu===undefined) { | |
g.clearRect(Bangle.appRect); | |
return Bangle.setUI(); | |
} | |
var menuIcon = "\0\f\f\x81\0\xFF\xFF\xFF\0\0\0\0\x0F\xFF\xFF\xF0\0\0\0\0\xFF\xFF\xFF"; | |
var options = menu[""]||{}; | |
if (!options.title) options.title="Menu"; | |
var back = menu["< Back"]; | |
var keys = Object.keys(menu).filter(k=>k!="" && k!="< Back"); | |
keys.forEach(k => { | |
var item = menu[k]; | |
if ("object" != typeof item) return; | |
if ("boolean" == typeof item.value && | |
!item.format) | |
item.format = v=>"\0"+atob(v?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"); | |
}); | |
// Submenu for editing menu options... | |
function showSubMenu(item, title) { | |
if ("number"!=typeof item.value) | |
return console.log("Unhandled item type"); | |
if (item.min!==undefined && item.max!==undefined && | |
(item.max-item.min)<20) { | |
// show scrolling menu of options | |
E.showScroller({ | |
h : H, c : item.max+1-item.min, | |
scrollMin : -24, scroll : -24, // title is 24px, rendered at -1 | |
draw : (idx, r) => { | |
if (idx<0) // TITLE | |
return g.setFont("12x20").setFontAlign(-1,0).drawString( | |
menuIcon+" "+title, r.x+12, r.y+H-12); | |
g.setColor(g.theme.bg2).fillRRect(r.x+4,r.y+2,r.w-8, r.h-4); | |
var v = idx + item.min; | |
if (item.format) v=item.format(v); | |
g.setColor(g.theme.fg).setFont("12x20").setFontAlign(-1,0).drawString(v, r.x+12, r.y+H/2); | |
g.drawImage(/* 20x20 */atob(idx==item.value?"FBSBAAH4AH/gHgeDgBww8MY/xmf+bH/jz/88//PP/zz/88f+Nn/mY/xjDww4AcHgeAf+AB+A":"FBSBAAH4AH/gHgeDgBwwAMYABmAAbAADwAA8AAPAADwAA8AANgAGYABjAAw4AcHgeAf+AB+A"), r.x+r.w-32, r.y+H/2-10); | |
}, | |
select : function(idx) { | |
if (idx<0) return; // TITLE | |
Bangle.buzz(20); | |
item.value = item.min + idx; | |
if (item.onchange) item.onchange(item.value); | |
scr.scroll = s.scroll; // set scroll to prev position | |
show(); // redraw original menu | |
} | |
}); | |
} else { | |
// show simple box for scroll up/down | |
var R = Bangle.appRect; | |
var v = item.value; | |
g.reset().clearRect(Bangle.appRect); | |
g.setFont("12x20").setFontAlign(0,0).drawString( | |
menuIcon+" "+title, R.x+R.w/2,R.y+12); | |
function draw() { | |
var mx = R.x+R.w/2, my = 12+R.y+R.h/2; | |
g.reset().setColor(g.theme.bg2).fillRRect(R.x+24, R.y+36, R.w-48, R.h-48); | |
g.setColor(g.theme.fg).setFontVector(30).setFontAlign(0,0).drawString(item.format?item.format(v):v, mx, my); | |
g.fillPoly([mx,my-45, mx+15,my-30, mx-15,my-30]).fillPoly([mx,my+45, mx+15,my+30, mx-15,my+30]); | |
} | |
draw(); | |
Bangle.setUI("updown", dir => { | |
if (dir) { | |
v -= (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; | |
draw(); | |
} else { | |
item.value = v; | |
if (item.onchange) item.onchange(item.value); | |
scr.scroll = s.scroll; // set scroll to prev position | |
show(); // redraw original menu | |
} | |
}); | |
} | |
} | |
var l = { | |
draw : ()=>s.draw() | |
}; | |
var scr = { | |
h : H, c : keys.length+1/*title*/, | |
scrollMin : -24, scroll : -24, // title is 24px, rendered at -1 | |
draw : (idx, r) => { | |
if (idx<0) // TITLE | |
return g.setFont("12x20").setFontAlign(-1,0).drawString( | |
menuIcon+" "+options.title, r.x+12, r.y+H-12); | |
g.setColor(g.theme.bg2).fillRRect(r.x+4,r.y+2,r.w-8, r.h-4); | |
g.setColor(g.theme.fg).setFont("12x20"); | |
var pad = 24; | |
var item = menu[keys[idx]]; | |
if ("object" == typeof item) { | |
var v = item.value; | |
if (item.format) v=item.format(v); | |
if (g.stringMetrics(v).width > r.w/2) // bodge for broken wrapString with image | |
v = g.wrapString(v,r.w/2).join("\n"); | |
print(keys[idx],E.toJS(v)); | |
g.setFontAlign(1,0).drawString(v,r.x+r.w-8,r.y+H/2); | |
pad += g.stringWidth(v); | |
} else if ("function" == typeof item) { | |
g.drawImage(/* 9x18 */atob("CRKBAGA4Hg8DwPB4HgcDg8PB4eHg8HAwAA=="), r.x+r.w-21, r.y+H/2-9); | |
pad += 16; | |
} | |
g.setFontAlign(-1,0).drawString(g.wrapString(keys[idx],r.w-pad).join("\n"), r.x+12, r.y+H/2); | |
}, | |
select : function(idx) { | |
if (idx<0) return back&&back(); // title | |
var item = menu[keys[idx]]; | |
Bangle.buzz(20); | |
if ("function" == typeof item) item(l); | |
else if ("object" == typeof item) { | |
// if a bool, just toggle it | |
if ("boolean" == typeof item.value) { | |
item.value=!item.value; | |
if (item.onchange) item.onchange(item.value); | |
s.drawItem(idx); | |
} else showSubMenu(item, keys[idx]); | |
} | |
} | |
}; | |
function show() { | |
s = E.showScroller(scr); | |
if (back) { | |
var touchHandler = (_,e) => { | |
if (e.y<24 && e.x<48) back(); | |
}; | |
Bangle.on("touch", touchHandler); | |
WIDGETS = Object.assign({back:{ area:"tl", width:24, draw:e=>g.reset().setColor("#f00").drawImage(atob("GBiBAAAYAAH/gAf/4A//8B//+D///D///H/P/n+H/n8P/n4f/vwAP/wAP34f/n8P/n+H/n/P/j///D///B//+A//8Af/4AH/gAAYAA=="),e.x,e.y)}},WIDGETS); | |
Bangle.drawWidgets(); | |
Bangle.btnWatches = [ | |
setWatch(function() { | |
back(); | |
}, BTN1, {edge:"falling"}), | |
]; | |
Bangle.uiRemove = function() { | |
Bangle.removeListener("touch", touchHandler); | |
delete WIDGETS.back; | |
Bangle.drawWidgets(); | |
}; | |
} | |
} | |
show(); | |
return l; | |
}; | |
var boolean = true; | |
var number = 50; | |
var hidV = [false, "kbmedia", "kb", "com", "joy"]; | |
var hidN = ["Off", "Kbrd & Media", "Kbrd", "Kbrd & Mouse" ,"Joystick"]; | |
// First menu | |
var mainmenu = { | |
"" : { "title" : "Main Menu" }, | |
"< Back" : function() { print("Back") }, | |
"A menu item" : function() { LED1.toggle(); }, | |
"Submenu" : function() { E.showMenu(submenu); }, | |
"A Boolean" : { | |
value : boolean, | |
//format : v => v?"On":"Off", | |
onchange : v => { boolean=v; } | |
}, | |
"A Number" : { | |
value : number, | |
min:0,max:100,step:10, | |
onchange : v => { number=v; } | |
}, | |
"A Big Number" : { | |
value : 9000, | |
min:0,max:10000,step:1000, | |
onchange : v => { } | |
}, | |
'HID': { | |
value: 0, | |
min: 0, max: hidN.length-1, | |
format: v => hidN[v], | |
onchange: v => { | |
} | |
}, | |
"A very long menu item" : function() { }, | |
"Exit" : function() { E.showMenu(); }, // remove the menu | |
}; | |
var submenu = { | |
"" : { "title" : "SubMenu" }, | |
"< Back" : function() { E.showMenu(mainmenu); }, | |
"One" : undefined, // do nothing | |
"Two" : undefined, // do nothing | |
}; | |
Bangle.loadWidgets(); | |
Bangle.drawWidgets(); | |
var m = E.showMenu(mainmenu); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment