From 2f12b9ad6e0511360a7e6849b5246486357f0929 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 22 Jul 2023 21:46:10 +0200 Subject: [PATCH 1/5] kineticscroll - Initial implementation --- apps/kineticscroll/ChangeLog | 1 + apps/kineticscroll/README.md | 7 ++ apps/kineticscroll/app.png | Bin 0 -> 505 bytes apps/kineticscroll/boot.js | 175 +++++++++++++++++++++++++++++++ apps/kineticscroll/metadata.json | 14 +++ 5 files changed, 197 insertions(+) create mode 100644 apps/kineticscroll/ChangeLog create mode 100644 apps/kineticscroll/README.md create mode 100644 apps/kineticscroll/app.png create mode 100644 apps/kineticscroll/boot.js create mode 100644 apps/kineticscroll/metadata.json diff --git a/apps/kineticscroll/ChangeLog b/apps/kineticscroll/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/kineticscroll/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/kineticscroll/README.md b/apps/kineticscroll/README.md new file mode 100644 index 000000000..be451a7f4 --- /dev/null +++ b/apps/kineticscroll/README.md @@ -0,0 +1,7 @@ +# Kinetic scrolling + +This patches the default scroller implementation to use kinetic scrolling. It is based on the original implementation. + +## Creator + +[halemmerich](https://github.com/halemmerich) diff --git a/apps/kineticscroll/app.png b/apps/kineticscroll/app.png new file mode 100644 index 0000000000000000000000000000000000000000..f3184b5c07045352c5bb38d5bf62e20f330e9b58 GIT binary patch literal 505 zcmVKKo=MSs^B&R9)J~J z@q56y1mVhR-@tBG@oRyISB8m9mFQXtPJkt#of4bI6Y`PF$g3YGXY6NwS>m;nm>JJY zZN{8%=GPkB7Ga%~*fjnh73f%bwI*j&pv{@Gg{o)(kFqTYmG==U8x;+BvI%?wec4l0 zt^!v^j{PbcxHobfRnfqmk>fZ=9u*CEa%=5SD_4OVYlBxg1J_0ly|b+JGJ2XLkBSBy z*#HiJb-BJY?%bKg^mm8S*OkI5@Fx97Kohtya-2(SB-i~A4ZIp-w9sec`pFQlq;GHr zwk(nJ+DQ5aXF$^czEaM2wnJZq8goqeh%ap!wN3bnFZDgpl*#%cpM6n2S|-MzyoXeI zNDLiXlQSR2z#Yn?<$JXFXbc=kq9QAi$npNb(9z41Kp$oQIIQVKwJ}i7dKX%Om>B!~ v!7-&SrF*v~_M+QFezWIu=Od9wgu?g(>+wy_CP~wG00000NkvXXu0mjfFumUv literal 0 HcmV?d00001 diff --git a/apps/kineticscroll/boot.js b/apps/kineticscroll/boot.js new file mode 100644 index 000000000..f0bd0af74 --- /dev/null +++ b/apps/kineticscroll/boot.js @@ -0,0 +1,175 @@ +(function() { + 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) + remove = function() + select = function(idx, touch) + } + + returns { + scroll: int // current scroll amount + draw: function() // draw all + drawItem : function(idx) // draw specific item + isActive : function() // is this scroller still active? + } + + */ + if (!options) return Bangle.setUI(); // remove existing handlers + + const MAX_VELOCITY=100; + let scheduledDraw; + let velocity = 0; + let accDy = 0; + let scheduledBrake = setInterval(()=>{velocity*=0.9;}, 50); + let lastDragStart = 0; + let R = Bangle.appRect; + let menuScrollMin = 0|options.scrollMin; + let menuScrollMax = options.h*options.c - R.h; + if (menuScrollMax{ + if (e.y=0) && i { + let dy = velocity; + if (s.scroll - dy > menuScrollMax){ + dy = s.scroll - menuScrollMax; + velocity = 0; + } + if (s.scroll - dy < menuScrollMin){ + dy = s.scroll - menuScrollMin; + velocity = 0; + } + + s.scroll -= dy; + + let oldScroll = rScroll; + rScroll = s.scroll &~1; + let d = oldScroll-rScroll; + + if (Math.abs(velocity) > 0.01) + scheduledDraw = setTimeout(draw,0); + else + scheduledDraw = undefined; + + if (!d) { + return; + } + g.reset().setClipRect(R.x,R.y,R.x2,R.y2).scroll(0,d); + if (d < 0) { + let y = Math.max(R.y2-(1-d), R.y); + g.setClipRect(R.x,y,R.x2,R.y2); + let i = YtoIdx(y); + + for (y = idxToY(i);y < R.y2;y+=options.h) { + options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); + i++; + } + } else { // d>0 + let y = Math.min(R.y+d, R.y2); + g.setClipRect(R.x,R.y,R.x2,y); + let i = YtoIdx(y); + y = idxToY(i); + + for (y = idxToY(i);y > R.y-options.h;y-=options.h) { + options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); + i--; + } + } + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); + }; + + const dragHandler = e=>{ + if ((velocity <0 && e.dy>0) || (velocity > 0 && e.dy<0)){ + velocity *= -1; + accDy = 5 * velocity; + } + //velocity += e.dy * (Date.now() - lastDrag); + if (e.b > 0){ + if (!lastDragStart){ + lastDragStart = Date.now(); + velocity = 0; + accDy = 0; + } + accDy += e.dy; + } + velocity = accDy / (Date.now() - lastDragStart) * MAX_VELOCITY; + + if (lastDragStart && e.b == 0){ + accDy = 0; + lastDragStart = 0; + } + + velocity = E.clip(velocity,-MAX_VELOCITY,MAX_VELOCITY); + lastDrag=Date.now(); + if (!scheduledDraw){ + scheduledDraw = setTimeout(draw,0); + } + }; + + let uiOpts = { + mode : "custom", + back : options.back, + remove : ()=>{ + if (scheduledDraw) + clearTimeout(scheduledDraw); + clearInterval(scheduledBrake); + if (options.remove) options.remove(); + }, + drag : dragHandler, + touch : touchHandler + } + + if (options.remove) uiOpts.remove = () => { + if (scheduledDraw) + clearTimeout(scheduledDraw); + clearInterval(scheduledBrake); + if (options.remove) options.remove(); + } + + Bangle.setUI(uiOpts); + + + function idxToY(i) { + return i*options.h + R.y - rScroll; + } + function YtoIdx(y) { + return Math.floor((y + rScroll - R.y)/options.h); + } + + let s = { + scroll : E.clip(0|options.scroll,menuScrollMin,menuScrollMax), + draw : () => { + g.reset().clearRect(R).setClipRect(R.x,R.y,R.x2,R.y2); + let a = YtoIdx(R.y); + let b = Math.min(YtoIdx(R.y2),options.c-1); + for (let 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 => { + let y = idxToY(i); + g.reset().setClipRect(R.x,Math.max(y,R.y),R.x2,Math.min(y+options.h,R.y2)); + options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); + }, isActive : () => Bangle.touchHandler == touchHandler + }; + + let 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 + return s; + }; +})(); diff --git a/apps/kineticscroll/metadata.json b/apps/kineticscroll/metadata.json new file mode 100644 index 000000000..9311ea8d3 --- /dev/null +++ b/apps/kineticscroll/metadata.json @@ -0,0 +1,14 @@ +{ "id": "kineticscroll", + "name": "Kinetic Scroll", + "shortName":"Kinetic Scroll", + "version":"0.01", + "description": "Replacement for the system scroller with kinetic scrolling.", + "icon": "app.png", + "type": "bootloader", + "tags": "system", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"kineticscroll.boot.js","url":"boot.js"} + ] +} From 7c874199c4981ab95232e4ddf93d928d1f6e5762 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 23 Jul 2023 11:32:03 +0200 Subject: [PATCH 2/5] kineticscroll - Add minified version for performance --- apps/kineticscroll/boot.min.js | 4 ++++ apps/kineticscroll/metadata.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 apps/kineticscroll/boot.min.js diff --git a/apps/kineticscroll/boot.min.js b/apps/kineticscroll/boot.min.js new file mode 100644 index 000000000..8ab9958cb --- /dev/null +++ b/apps/kineticscroll/boot.min.js @@ -0,0 +1,4 @@ +(function(){E.showScroller=function(c){function n(a){return a*c.h+b.y-k}function p(a){return Math.floor((a+k-b.y)/c.h)}if(!c)return Bangle.setUI();let h,e=0,l=0,t=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,m=0|c.scrollMin,r=c.h*c.c-b.h;r{d.ym||0<=a)&&a{var a=e;f.scroll-a>r&&(a=f.scroll-r,e=0);f.scroll-aa){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=p(a);for(a=n(d);ab.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)}},w={mode:"custom",back:c.back,remove:()=>{h&&clearTimeout(h);clearInterval(t);c.remove&&c.remove()},drag:a=> +{if(0>e&&0a.dy)e*=-1,l=5*e;0{h&&clearTimeout(h);clearInterval(t);c.remove&&c.remove()});Bangle.setUI(w);let f={scroll:E.clip(0|c.scroll,m,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=p(b.y);let d=Math.min(p(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:n(a),w:b.w,h:c.h});g.setClipRect(0, +0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=n(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.touchHandler==u},k=f.scroll&-2;f.draw();g.flip();return f}})() \ No newline at end of file diff --git a/apps/kineticscroll/metadata.json b/apps/kineticscroll/metadata.json index 9311ea8d3..022d38291 100644 --- a/apps/kineticscroll/metadata.json +++ b/apps/kineticscroll/metadata.json @@ -9,6 +9,6 @@ "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ - {"name":"kineticscroll.boot.js","url":"boot.js"} + {"name":"kineticscroll.boot.js","url":"boot.min.js"} ] } From a5190d4d860661dec0b3ee3c0ad8e3f8f32a27a7 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 23 Jul 2023 11:53:31 +0200 Subject: [PATCH 3/5] kineticscroll - Fix setting a remove method for every scroller --- apps/kineticscroll/boot.js | 6 ------ apps/kineticscroll/boot.min.js | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/kineticscroll/boot.js b/apps/kineticscroll/boot.js index f0bd0af74..1da4da51c 100644 --- a/apps/kineticscroll/boot.js +++ b/apps/kineticscroll/boot.js @@ -123,12 +123,6 @@ let uiOpts = { mode : "custom", back : options.back, - remove : ()=>{ - if (scheduledDraw) - clearTimeout(scheduledDraw); - clearInterval(scheduledBrake); - if (options.remove) options.remove(); - }, drag : dragHandler, touch : touchHandler } diff --git a/apps/kineticscroll/boot.min.js b/apps/kineticscroll/boot.min.js index 8ab9958cb..2db3098a9 100644 --- a/apps/kineticscroll/boot.min.js +++ b/apps/kineticscroll/boot.min.js @@ -1,4 +1,4 @@ -(function(){E.showScroller=function(c){function n(a){return a*c.h+b.y-k}function p(a){return Math.floor((a+k-b.y)/c.h)}if(!c)return Bangle.setUI();let h,e=0,l=0,t=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,m=0|c.scrollMin,r=c.h*c.c-b.h;r{d.ym||0<=a)&&a{var a=e;f.scroll-a>r&&(a=f.scroll-r,e=0);f.scroll-aa){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=p(a);for(a=n(d);ab.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)}},w={mode:"custom",back:c.back,remove:()=>{h&&clearTimeout(h);clearInterval(t);c.remove&&c.remove()},drag:a=> -{if(0>e&&0a.dy)e*=-1,l=5*e;0{h&&clearTimeout(h);clearInterval(t);c.remove&&c.remove()});Bangle.setUI(w);let f={scroll:E.clip(0|c.scroll,m,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=p(b.y);let d=Math.min(p(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:n(a),w:b.w,h:c.h});g.setClipRect(0, -0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=n(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.touchHandler==u},k=f.scroll&-2;f.draw();g.flip();return f}})() \ No newline at end of file +(function(){E.showScroller=function(c){function m(a){return a*c.h+b.y-h}function n(a){return Math.floor((a+h-b.y)/c.h)}if(!c)return Bangle.setUI();let p,e=0,k=0,w=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,l=0|c.scrollMin,r=c.h*c.c-b.h;r{d.yl||0<=a)&&a{var a=e;f.scroll-a>r&&(a=f.scroll-r,e=0);f.scroll-aa){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=n(a);for(a=m(d);ab.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)}},v={mode:"custom",back:c.back,drag:a=>{if(0>e&&0a.dy)e*=-1,k=5*e;0{p&&clearTimeout(p);clearInterval(w);c.remove&&c.remove()});Bangle.setUI(v);let f={scroll:E.clip(0|c.scroll,l,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=n(b.y);let d=Math.min(n(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:m(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=m(a);g.reset().setClipRect(b.x, +Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.touchHandler==t},h=f.scroll&-2;f.draw();g.flip();return f}})() \ No newline at end of file From 2d5f9a5788a955a501bac942a1b6d556cd63bd8a Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 23 Jul 2023 11:53:31 +0200 Subject: [PATCH 4/5] kineticscroll - No check needed, remove method must always exist at this point --- apps/kineticscroll/boot.js | 2 +- apps/kineticscroll/boot.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/kineticscroll/boot.js b/apps/kineticscroll/boot.js index 1da4da51c..8c44c02ec 100644 --- a/apps/kineticscroll/boot.js +++ b/apps/kineticscroll/boot.js @@ -131,7 +131,7 @@ if (scheduledDraw) clearTimeout(scheduledDraw); clearInterval(scheduledBrake); - if (options.remove) options.remove(); + options.remove(); } Bangle.setUI(uiOpts); diff --git a/apps/kineticscroll/boot.min.js b/apps/kineticscroll/boot.min.js index 2db3098a9..4b549a4f7 100644 --- a/apps/kineticscroll/boot.min.js +++ b/apps/kineticscroll/boot.min.js @@ -1,4 +1,4 @@ (function(){E.showScroller=function(c){function m(a){return a*c.h+b.y-h}function n(a){return Math.floor((a+h-b.y)/c.h)}if(!c)return Bangle.setUI();let p,e=0,k=0,w=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,l=0|c.scrollMin,r=c.h*c.c-b.h;r{d.yl||0<=a)&&a{var a=e;f.scroll-a>r&&(a=f.scroll-r,e=0);f.scroll-aa){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=n(a);for(a=m(d);ab.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)}},v={mode:"custom",back:c.back,drag:a=>{if(0>e&&0a.dy)e*=-1,k=5*e;0{p&&clearTimeout(p);clearInterval(w);c.remove&&c.remove()});Bangle.setUI(v);let f={scroll:E.clip(0|c.scroll,l,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=n(b.y);let d=Math.min(n(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:m(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=m(a);g.reset().setClipRect(b.x, -Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.touchHandler==t},h=f.scroll&-2;f.draw();g.flip();return f}})() \ No newline at end of file +Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.touchHandler==t},h=f.scroll&-2;f.draw();g.flip();return f}})() From e614bdfb51cb35dfd59658f73445de02410ea5e1 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 23 Jul 2023 17:41:48 +0200 Subject: [PATCH 5/5] kineticscroll - Update to latest E.showScroller implementation --- apps/kineticscroll/boot.js | 22 +++++++++++++++++----- apps/kineticscroll/boot.min.js | 8 ++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/kineticscroll/boot.js b/apps/kineticscroll/boot.js index 8c44c02ec..1f1b7923a 100644 --- a/apps/kineticscroll/boot.js +++ b/apps/kineticscroll/boot.js @@ -31,7 +31,7 @@ let menuScrollMax = options.h*options.c - R.h; if (menuScrollMax{ + const touchHandler = (_,e)=>{ if (e.y=0) && i { + + const uiDraw = () => { + g.reset().clearRect(R).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); + } + + const draw = () => { let dy = velocity; if (s.scroll - dy > menuScrollMax){ dy = s.scroll - menuScrollMax; @@ -124,7 +134,8 @@ mode : "custom", back : options.back, drag : dragHandler, - touch : touchHandler + touch : touchHandler, + redraw : uiDraw } if (options.remove) uiOpts.remove = () => { @@ -136,6 +147,7 @@ Bangle.setUI(uiOpts); + function idxToY(i) { return i*options.h + R.y - rScroll; @@ -158,7 +170,7 @@ g.reset().setClipRect(R.x,Math.max(y,R.y),R.x2,Math.min(y+options.h,R.y2)); options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); - }, isActive : () => Bangle.touchHandler == touchHandler + }, isActive : () => Bangle.uiRedraw == uiDraw }; let rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither) diff --git a/apps/kineticscroll/boot.min.js b/apps/kineticscroll/boot.min.js index 4b549a4f7..42f0afa67 100644 --- a/apps/kineticscroll/boot.min.js +++ b/apps/kineticscroll/boot.min.js @@ -1,4 +1,4 @@ -(function(){E.showScroller=function(c){function m(a){return a*c.h+b.y-h}function n(a){return Math.floor((a+h-b.y)/c.h)}if(!c)return Bangle.setUI();let p,e=0,k=0,w=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,l=0|c.scrollMin,r=c.h*c.c-b.h;r{d.yl||0<=a)&&a{var a=e;f.scroll-a>r&&(a=f.scroll-r,e=0);f.scroll-aa){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=n(a);for(a=m(d);ab.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)}},v={mode:"custom",back:c.back,drag:a=>{if(0>e&&0a.dy)e*=-1,k=5*e;0{p&&clearTimeout(p);clearInterval(w);c.remove&&c.remove()});Bangle.setUI(v);let f={scroll:E.clip(0|c.scroll,l,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=n(b.y);let d=Math.min(n(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:m(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=m(a);g.reset().setClipRect(b.x, -Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.touchHandler==t},h=f.scroll&-2;f.draw();g.flip();return f}})() +(function(){E.showScroller=function(c){function k(a){return a*c.h+b.y-l}function h(a){return Math.floor((a+l-b.y)/c.h)}if(!c)return Bangle.setUI();let p,e=0,m=0,w=setInterval(()=>{e*=.9},50),q=0,b=Bangle.appRect,n=0|c.scrollMin,r=c.h*c.c-b.h;r{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);for(var a=h(b.y),d=Math.min(h(b.y2),c.c-1);a<=d;a++)c.draw(a,{x:b.x,y:k(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},u=()=>{var a=e;f.scroll-a>r&& +(a=f.scroll-r,e=0);f.scroll-aa){a=Math.max(b.y2-(1-a),b.y);g.setClipRect(b.x,a,b.x2,b.y2);var d=h(a);for(a=k(d);ab.y-c.h;a-=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()- +1)}};let v={mode:"custom",back:c.back,drag:a=>{if(0>e&&0a.dy)e*=-1,m=5*e;0{if(!(d.yn||0<=a)&&a{p&&clearTimeout(p);clearInterval(w);c.remove()});Bangle.setUI(v);let f={scroll:E.clip(0|c.scroll, +n,r),draw:()=>{g.reset().clearRect(b).setClipRect(b.x,b.y,b.x2,b.y2);var a=h(b.y);let d=Math.min(h(b.y2),c.c-1);for(;a<=d;a++)c.draw(a,{x:b.x,y:k(a),w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let d=k(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==t},l=f.scroll&-2;f.draw();g.flip();return f}})() \ No newline at end of file