diff --git a/apps/swscroll/ChangeLog b/apps/swscroll/ChangeLog
new file mode 100644
index 000000000..c650baf72
--- /dev/null
+++ b/apps/swscroll/ChangeLog
@@ -0,0 +1 @@
+0.01: Inital release.
diff --git a/apps/swscroll/README.md b/apps/swscroll/README.md
new file mode 100644
index 000000000..f97d59b71
--- /dev/null
+++ b/apps/swscroll/README.md
@@ -0,0 +1,9 @@
+This first release seems servicable in testing so far.
+
+To get the standard menu scrolling back, just remove this app from your Bangle.
+
+TODO:
+- Maybe have how much of "trailing space" there are after the last entry should be dynamic in size, now it's always 8 pixels which corresponds to if there are a widget field and a menu title present.
+- I want to change the size of menu entries to be a little bigger vertically.
+
+Drag List Down icon by Icons8
diff --git a/apps/swscroll/app.png b/apps/swscroll/app.png
new file mode 100644
index 000000000..7abd582c2
Binary files /dev/null and b/apps/swscroll/app.png differ
diff --git a/apps/swscroll/boot.js b/apps/swscroll/boot.js
new file mode 100644
index 000000000..fc5650cad
--- /dev/null
+++ b/apps/swscroll/boot.js
@@ -0,0 +1,100 @@
+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
+ }
+ */
+if (!options) return Bangle.setUI(); // remove existing handlers
+
+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 {
+ 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.setUI({
+ mode : "custom",
+ back : options.back,
+ swipe : (hor,ver)=>{
+ pixels = 120;
+ var dy = ver*pixels;
+ if (s.scroll - dy > menuScrollMax)
+ dy = s.scroll - menuScrollMax-8; // Makes it so the last 'page' has the same position as previous pages. This should be done dynamically (change the static 8 to be a variable) so the offset is correct even when no widget field or title field is present.
+ if (s.scroll - dy < menuScrollMin)
+ dy = s.scroll - menuScrollMin;
+ s.scroll -= dy;
+ var oldScroll = rScroll;
+ rScroll = s.scroll &~1;
+ dy = oldScroll-rScroll;
+ if (!dy || options.c<=3) return; //options.c<=3 should maybe be dynamic, so 3 would be replaced by a variable dependent on R=Bangle.appRect. It's here so we don't try to scroll if all entries fit in the app rectangle.
+ g.reset().setClipRect(R.x,R.y,R.x2,R.y2);
+ g.scroll(0,dy);
+ var d = ver*pixels;
+ 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);
+ //print(i, options.c, options.c-i); //debugging info
+ while (y < R.y2 - (options.h*((options.c-i)<=0)) ) { //- (options.h*((options.c-i)<=0)) makes sure we don't go beyond the menu entries in the menu object "options". This has to do with "dy = s.scroll - menuScrollMax-8" above.
+ 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);
+ //print(i, options.c, options.c-i); //debugging info
+ 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);
+ }, touch : (_,e)=>{
+ if (e.y=0) && i