Merge pull request #3296 from halemmerich/kineticscroll

kineticscroll - Better scrolling behaviour
pull/3369/head
thyttan 2024-03-26 23:14:17 +01:00 committed by GitHub
commit 2ce749851d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 66 additions and 58 deletions

View File

@ -1,2 +1,4 @@
0.01: New App! 0.01: New App!
0.02: Minor code improvements 0.02: Minor code improvements
0.03: Better scrolling behaviour

View File

@ -20,12 +20,19 @@
*/ */
if (!options) return Bangle.setUI(); // remove existing handlers if (!options) return Bangle.setUI(); // remove existing handlers
const MAX_VELOCITY=100; const SPEED=100;
const LAST_DRAG_WAIT=150;
const MIN_VELOCITY=0.1;
let scheduledDraw; let scheduledDraw;
let velocity = 0; let velocity = 0;
let accDy = 0; let accDy = 0;
let scheduledBrake = setInterval(()=>{velocity*=0.9;}, 50); let direction = 0;
let lastTouchedDrag = 0;
let lastDragStart = 0; let lastDragStart = 0;
let R = Bangle.appRect; let R = Bangle.appRect;
let menuScrollMin = 0|options.scrollMin; let menuScrollMin = 0|options.scrollMin;
let menuScrollMax = options.h*options.c - R.h; let menuScrollMax = options.h*options.c - R.h;
@ -39,7 +46,6 @@
if ((menuScrollMin<0 || i>=0) && i<options.c){ if ((menuScrollMin<0 || i>=0) && i<options.c){
let yAbs = (e.y + rScroll - R.y); let yAbs = (e.y + rScroll - R.y);
let yInElement = yAbs - i*options.h; let yInElement = yAbs - i*options.h;
print("selected");
options.select(i, {x:e.x, y:yInElement}); options.select(i, {x:e.x, y:yInElement});
} }
}; };
@ -51,30 +57,33 @@
for (var i=a;i<=b;i++) for (var i=a;i<=b;i++)
options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h}); 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); g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
} };
const draw = () => { const draw = () => {
let dy = velocity; if (velocity > MIN_VELOCITY){
if (s.scroll - dy > menuScrollMax){ if (!scheduledDraw)
dy = s.scroll - menuScrollMax; scheduledDraw = setTimeout(draw, 0);
velocity *= 1-((Date.now() - lastTouchedDrag) / 8000);
if (velocity <= MIN_VELOCITY){
velocity = 0; velocity = 0;
} else {
s.scroll -= velocity * direction;
} }
if (s.scroll - dy < menuScrollMin){
dy = s.scroll - menuScrollMin;
velocity = 0;
} }
s.scroll -= dy; if (s.scroll > menuScrollMax){
s.scroll = menuScrollMax;
velocity = 0;
}
if (s.scroll < menuScrollMin){
s.scroll = menuScrollMin;
velocity = 0;
}
let oldScroll = rScroll; let oldScroll = rScroll;
rScroll = s.scroll &~1; rScroll = s.scroll &~1;
let d = oldScroll-rScroll; let d = oldScroll-rScroll;
if (Math.abs(velocity) > 0.01)
scheduledDraw = setTimeout(draw,0);
else
scheduledDraw = undefined;
if (!d) { if (!d) {
return; return;
} }
@ -100,34 +109,31 @@
} }
} }
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
scheduledDraw = undefined;
}; };
const dragHandler = e=>{ const dragHandler = e=>{
if ((velocity <0 && e.dy>0) || (velocity > 0 && e.dy<0)){ let now=Date.now();
velocity *= -1; direction = Math.sign(e.dy);
accDy = 5 * velocity; s.scroll -= e.dy;
}
//velocity += e.dy * (Date.now() - lastDrag);
if (e.b > 0){ if (e.b > 0){
if (!lastDragStart){ // Finger touches the display or direction has been reversed
lastDragStart = Date.now(); lastTouchedDrag = now;
if (!lastDragStart || (accDy * direction < 0 && e.dy * direction > 0)){
lastDragStart = lastTouchedDrag;
velocity = 0; velocity = 0;
accDy = 0; accDy = 0;
} }
accDy += e.dy;
}
velocity = accDy / (Date.now() - lastDragStart) * MAX_VELOCITY;
if (lastDragStart && e.b == 0){ accDy += e.dy;
accDy = 0; } else {
// Finger has left the display, only start scrolling kinetically when the last drag event is close enough
if (now - lastTouchedDrag < LAST_DRAG_WAIT){
velocity = direction * accDy / (now - lastDragStart) * SPEED;
}
lastDragStart = 0; lastDragStart = 0;
} }
draw();
velocity = E.clip(velocity,-MAX_VELOCITY,MAX_VELOCITY);
//lastDrag=Date.now();
if (!scheduledDraw){
scheduledDraw = setTimeout(draw,0);
}
}; };
let uiOpts = { let uiOpts = {
@ -136,22 +142,20 @@
drag : dragHandler, drag : dragHandler,
touch : touchHandler, touch : touchHandler,
redraw : uiDraw redraw : uiDraw
} };
if (options.remove) uiOpts.remove = () => { if (options.remove) uiOpts.remove = () => {
if (scheduledDraw) if (scheduledDraw)
clearTimeout(scheduledDraw); clearTimeout(scheduledDraw);
clearInterval(scheduledBrake);
options.remove(); options.remove();
} };
Bangle.setUI(uiOpts); Bangle.setUI(uiOpts);
function idxToY(i) { function idxToY(i) {
return i*options.h + R.y - rScroll; return i*options.h + R.y - rScroll;
} }
function YtoIdx(y) { function YtoIdx(y) {
return Math.floor((y + rScroll - R.y)/options.h); return Math.floor((y + rScroll - R.y)/options.h);
} }
@ -165,12 +169,14 @@
for (let i=a;i<=b;i++) for (let i=a;i<=b;i++)
options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h}); 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); g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}, drawItem : i => { },
drawItem : i => {
let y = idxToY(i); let y = idxToY(i);
g.reset().setClipRect(R.x,Math.max(y,R.y),R.x2,Math.min(y+options.h,R.y2)); 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}); options.draw(i, {x:R.x,y:y,w:R.w,h:options.h});
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}, isActive : () => Bangle.uiRedraw == uiDraw },
isActive : () => Bangle.uiRedraw == uiDraw
}; };
let rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither) let rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither)

View File

@ -1,4 +1,4 @@
(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<n&&(r=n);const t=()=>{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&& (function(){E.showScroller=function(c){function k(b){return b*c.h+a.y-l}function h(b){return Math.floor((b+l-a.y)/c.h)}if(!c)return Bangle.setUI();let n,f=0,p=0,q=0,t=0,u=0,a=Bangle.appRect,m=0|c.scrollMin,r=c.h*c.c-a.h;r<m&&(r=m);const v=()=>{g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);for(var b=h(a.y),d=Math.min(h(a.y2),c.c-1);b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},w=()=>{.1<f&&(n||(n=setTimeout(w,0)),f*=
(a=f.scroll-r,e=0);f.scroll-a<n&&(a=f.scroll-n,e=0);f.scroll-=a;a=l;l=f.scroll&-2;a-=l;p=.01<Math.abs(e)?setTimeout(u,0):void 0;if(a){g.reset().setClipRect(b.x,b.y,b.x2,b.y2).scroll(0,a);if(0>a){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);a<b.y2;a+=c.h)c.draw(d,{x:b.x,y:a,w:b.w,h:c.h}),d++}else for(a=Math.min(b.y+a,b.y2),g.setClipRect(b.x,b.y,b.x2,a),d=h(a),k(d),a=k(d);a>b.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-(Date.now()-t)/8E3,.1>=f?f=0:e.scroll-=f*q);e.scroll>r&&(e.scroll=r,f=0);e.scroll<m&&(e.scroll=m,f=0);var b=l;l=e.scroll&-2;if(b-=l){g.reset().setClipRect(a.x,a.y,a.x2,a.y2).scroll(0,b);if(0>b){b=Math.max(a.y2-(1-b),a.y);g.setClipRect(a.x,b,a.x2,a.y2);var d=h(b);for(b=k(d);b<a.y2;b+=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d++}else for(b=Math.min(a.y+b,a.y2),g.setClipRect(a.x,a.y,a.x2,b),d=h(b),k(d),b=k(d);b>a.y-c.h;b-=c.h)c.draw(d,{x:a.x,y:b,w:a.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&&0<a.dy||0<e&&0>a.dy)e*=-1,m=5*e;0<a.b&&(q||(q=Date.now(),m=e=0),m+=a.dy);e=m/(Date.now()-q)*100;q&&0==a.b&&(q=m=0);e=E.clip(e,-100,100);p||(p=setTimeout(u,0))},touch:(a,d)=>{if(!(d.y<b.y-4)&&(m=e=0,a=h(d.y),(0>n||0<=a)&&a<c.c)){let x=d.y+l-b.y-a*c.h;print("selected");c.select(a,{x:d.x,y:x})}},redraw:t};c.remove&&(v.remove=()=>{p&&clearTimeout(p);clearInterval(w);c.remove()});Bangle.setUI(v);let f={scroll:E.clip(0|c.scroll, 1);n=void 0}};let x={mode:"custom",back:c.back,drag:b=>{let d=Date.now();q=Math.sign(b.dy);e.scroll-=b.dy;if(0<b.b){t=d;if(!u||0>p*q&&0<b.dy*q)u=t,p=f=0;p+=b.dy}else 150>d-t&&(f=q*p/(d-u)*100),u=0;w()},touch:(b,d)=>{d.y<a.y-4||(p=f=0,b=h(d.y),(0>m||0<=b)&&b<c.c&&c.select(b,{x:d.x,y:d.y+l-a.y-b*c.h}))},redraw:v};c.remove&&(x.remove=()=>{n&&clearTimeout(n);c.remove()});Bangle.setUI(x);let e={scroll:E.clip(0|c.scroll,m,r),draw:()=>{g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);var b=h(a.y);let d=
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}})() Math.min(h(a.y2),c.c-1);for(;b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:b=>{let d=k(b);g.reset().setClipRect(a.x,Math.max(d,a.y),a.x2,Math.min(d+c.h,a.y2));c.draw(b,{x:a.x,y:d,w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==v},l=e.scroll&-2;e.draw();g.flip();return e}})()

View File

@ -1,7 +1,7 @@
{ "id": "kineticscroll", { "id": "kineticscroll",
"name": "Kinetic Scroll", "name": "Kinetic Scroll",
"shortName":"Kinetic Scroll", "shortName":"Kinetic Scroll",
"version": "0.02", "version": "0.03",
"description": "Replacement for the system scroller with kinetic scrolling.", "description": "Replacement for the system scroller with kinetic scrolling.",
"icon": "app.png", "icon": "app.png",
"type": "bootloader", "type": "bootloader",