exports.Scroller = function(options) { this.getScrollerY = function() { return Bangle.appRect.y + this.topBarH; } this.dragging = false; this.firstTouch = true; this.selectedX = 0; this.h = options.h; this.w = Bangle.appRect.w; this.c = options.c; this.drawFunc = options.draw; this.drawEmpty = options.drawEmpty; this.selectFunc = options.select; this.touchX = undefined; this.button1Func = options.button1; this.button2Func = options.button2; this.drawButton1 = options.drawButton1; this.drawButton2 = options.drawButton2; this.buttonW = 55; this.buttonsW = (this.drawButton1 !== undefined ? this.buttonW : 0) + (this.drawButton2 !== undefined ? this.buttonW : 0); this.touchStart = true; this.mode = undefined; this.selected = undefined; /** minH: maxH; drawMin: function(r) drawMax: function(r) handler: function(r) */ this.topBar = options.topBar; this.topBarH = this.topBar.maxH; this.topBarIsMax = true; this.topBarIsMin = false; this.maxCountOneScreen = Math.floor((Bangle.appRect.y2 - Bangle.appRect.y - this.topBar.maxH) / this.h); this.y0 = Bangle.appRect.y + this.topBarH; this.x = 0; this.dy = 0; this.clearScroller = function() { g.clearRect(Bangle.appRect.x, Bangle.appRect.y + this.topBarH, Bangle.appRect.x2, Bangle.appRect.y2); }; this.getMinY = function() { let minY = Bangle.appRect.y2 - (this.c * this.h); return minY > this.topBarH ? this.topBarH : minY; }; this.minY = this.getMinY(); this.dragHandler = function(d) { if (this.firstTouch) { this.firstTouch = false; if (d.b === 0) return; } if (d.b === 1) this.dragging = true; else this.dragging = false; if (this.mode === undefined) { if (this.topBarIsMax && d.y < Bangle.appRect.y + this.topBar.maxH) { this.topBar.handler(d, {x: Bangle.appRect.x, y: Bangle.appRect.y, x2: Bangle.appRect.x2, y2: Bangle.appRect.y + this.topBar.maxH - 1}); return; } this.selected = Math.floor((d.y - this.y0) / this.h); if (this.selected > this.c - 1 || this.selected < 0) this.selected = undefined; if (d.x != undefined) this.touchX = d.x; // need to save the last touch position if (d.dy === 0 && d.dx === 0 && this.touchStart === true) { // initial finger press, we'll check after release if (d.b === 0) { // finger released, must have been no dragging, call select func if (this.selected !== undefined) { const idx = this.selected; const bounds = this.getBounds(idx); this.selectFunc(idx, bounds, this.touchX); } return; } } else if (Math.abs(d.dy) > Math.abs(d.dx)) { // drag up/down this.touchStart = false; this.selectedX = this.x; this.mode = "ud"; } else if (Math.abs(d.dx) > Math.abs(d.dy) && this.selected != undefined) { // drag left/right this.touchStart = false; this.mode = "lr"; } } if (this.mode === "ud") this.udHandler(d); else if (this.mode === "lr") this.lrHandler(d); else if (this.mode === "button") this.buttonHandler(d); }; this.udHandler = function(d) { this.minY = this.getMinY(); if (d.b === 0) { // released finger if (this.c > this.maxCountOneScreen) { if (this.topBarH > (this.topBar.maxH + this.topBar.minH) / 2) { this.topBarH = this.topBar.maxH; this.topBarIsMax = true; this.topBarIsMin = false; } else { this.topBarH = this.topBar.minH; this.topBarIsMax = false; this.topBarIsMin = true; } // make sure the top bar ends in one of the 2 states when we stop scrolling } let done = false; this.y0 += this.dy; // move a little extra after finger release. Feels more natural. if (this.y0 > this.topBarH) { this.y0 = this.topBarH; // overscroll top done = true; } else if (this.y0 < this.minY) { this.y0 = this.minY; // overscroll bottom done = true; } this.drawAll(); this.touchStart = true; // released finger, touchStart is true for next time this.mode = undefined; // reset mode, we don't know if we will continue scrolling up/down if (done) { return; } } this.x = 0; this.y0 += (d.dy * 1.2); // scroll a little faster than finger is moving to compensate for lag if (this.c > this.maxCountOneScreen) { if (d.dy > 0 && this.topBarH < this.topBar.maxH) { // drag top to bottom this.topBarH += d.dy; if (this.topBarH > this.topBar.maxH) { this.topBarH = this.topBar.maxH; } } else if (d.dy < 0 && this.topBarH > this.topBar.minH) { // drag bottom to top this.topBarH += d.dy; if (this.topBarH < this.topBar.minH) { this.topBarH = this.topBar.minH; } } } if (this.y0 > this.topBarH + 30) this.y0 = this.topBarH + 30; // max overscroll top else if (this.y0 < this.minY-30) this.y0 = this.minY-30; // max overscroll bottom this.clearScroller(); this.drawAll(); }; this.lrHandler = function(d) { if (d.b === 0) { // released finger if (this.buttonsW === 0) return; if (this.selectedX <= -(this.buttonsW) + 20) { // left swipe buttons width less 20px this.selectedX = -(this.buttonsW); this.drawLR(this.selected); this.mode = "button"; } else { this.selectedX = this.x; this.mode = undefined; this.drawAll(); } this.touchStart = true; return; } if (d.dx > 0 && this.selectedX >= 0) return; // ignore swipe right else { this.selectedX += d.dx; this.drawLR(this.selected); } }; this.buttonHandler = function(d) { if (Math.abs(d.dx > 1)) { this.mode = "lr"; return; } if (this.touchStart && d.b === 0) { this.mode = undefined; this.selectedX = this.x; let b1x1, b1x2, b2x1, b2x2; if (this.drawButton2 === undefined) { b1x1 = this.x + this.w - this.buttonW; b1x2 = this.x + this.w; } else { b1x1 = this.x + this.w - this.buttonsW; b1x2 = this.x + this.w - this.buttonW; } b2x1 = this.x + this.w - this.buttonW; if (d.x > b1x1 && d.x < b1x2) { // button 1: delete this.removingSelection = this.selected; this.c--; this.minY = this.getMinY(); this.animateDelete(this.selected); this.button1Func(this.selected); } else if (d.x > b2x1) { this.button2Func(this.selected); } return; } }; this.getBounds = function(idx) { let x = this.x; let y = this.y0 + (idx * this.h); let x2 = x + this.w - 1; let y2 = y + this.h - 1; return {x: x, y: y, x2: x2, y2: y2, w: this.w, h: this.h}; } this.draw = function(idx) { this.drawFunc(idx, this.getBounds(idx), this.dragging); }; this.scrollToCenter = function(idx) { this.idxMidY = this.getScrollerY() + ((Bangle.appRect.h - this.h) / 2) - (idx * this.h); this.dir = this.idxMidY > this.y0 ? 1 : -1; const scroll = function(idx) { let done = false; this.y0 += 30 * this.dir; if (Math.abs(this.y0) - Math.abs(this.idxMidY) <= 30) { this.y0 = this.idxMidY; done = true; } this.drawAll(); if (done) { delete this.idxMidY; delete this.dir; delete Bangle.scroll; return; } else { setTimeout(() => Bangle.scroll(idx), 30); return; } } Bangle.scroll = scroll.bind(this, idx); Bangle.scroll(idx); }; this.drawTopBar = function() { if (this.c <= this.maxCountOneScreen) { this.topBarH = this.topBar.maxH; this.topBarIsMax = true; this.topBarIsMin = false; } g.setClipRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y + this.topBarH - 1); if (this.topBarH < this.topBar.maxH) { this.topBar.drawMin({x: Bangle.appRect.x, y: Bangle.appRect.y, x2: Bangle.appRect.x2, y2: Bangle.appRect.y + this.topBarH - 1}) } else { this.topBar.drawMax({x: Bangle.appRect.x, y: Bangle.appRect.y, x2: Bangle.appRect.x2, y2: Bangle.appRect.y + this.topBarH - 1}) } }; this.drawAll = function() { this.drawTopBar(); g.setClipRect(Bangle.appRect.x, this.getScrollerY(), Bangle.appRect.x2, Bangle.appRect.y2); this.clearScroller(); if (this.c === 0) { this.drawEmpty({x: Bangle.appRect.x, y: this.getScrollerY(), x2: Bangle.appRect.x2, y2: Bangle.appRect.y2}); return; } let x = this.x; let y = this.y0; let x2 = x + this.w - 1; let y2 = y + this.h - 1; const startIdx = this.y0 < 0 ? Math.floor(-this.y0 / this.h) : 0; // How many are fully off screen? We won't draw them. const count = Math.ceil(Bangle.appRect.h / this.h); // How many we should draw let drawn = 0; for (let i = 0; i < this.c; i++) { if (i >= startIdx && drawn <= count) { if (i === this.removingSelection) { g.clearRect(x, y, x2, y + this.removingH - 1); y += this.removingH; y2 += this.removingH; } if ( (this.mode === "lr" || this.mode === "button") && i === this.selected ) { this.drawLR(this.selected); } else { this.drawFunc(i, { x: x, y: y, x2: x2, y2: y2, w: this.w, h: this.h }, this.dragging); } drawn++; } y += this.h; y2 += this.h; } }; this.drawLR = function(selected) { // const idx = selected; // const x = this.x; const y = this.y0 + (this.h * selected); const x2 = this.x + this.w - 1; const y2 = y + this.h - 1; // const w = this.buttonW; // const h = this.h; g.clearRect(this.x, y, x2, y2); this.drawMenu(selected, { x: x2 - (2 * this.buttonW), y: y, x2: x2 - this.buttonW - 1, y2: y2, w: this.buttonW, h: this.h }, { x: x2 - this.buttonW, y: y, x2: x2, y2: y2, w: this.buttonW, h: this.h }); this.drawFunc(this.selected, { x: this.selectedX, y: y, x2: this.selectedX + this.w - 1, y2: y2, h: this.h, w: this.w }); }; this.animateDelete = function(selected) { Bangle.animateDelete = this.animateDelete.bind(this); this.removingSelection = selected; this.minY = this.getMinY(); this.removingH = this.removingH === undefined ? this.h : this.removingH - 30; if (this.removingH < 1) this.removingH = 1; if (selected === this.c || this.y0 < this.minY) { if (this.y0 < Bangle.appRect.y + this.topBarH) this.y0 += this.removingH; if (this.y0 > Bangle.appRect.y + this.topBarH) this.y0 -= this.removingH; } this.drawAll(); if (this.removingH > 1) { setTimeout(() => Bangle.animateDelete(selected), 30); } else { if (this.y0 < this.minY) { this.y0 = this.minY; } this.removingSelection = undefined; this.removingH = undefined; delete Bangle.animateDelete; this.drawAll(); } }; this.drawMenu = function(_idx, r1, r2) { const col = g.getColor(); if (this.drawButton2 !== undefined) this.drawButton2(r2); else r1 = r2; if (this.drawButton1 !== undefined) this.drawButton1(r1); g.setColor(col); }; this.remove = function() { Bangle.setUI(); g.reset(); }; this.reload = function() { this.minY = this.getMinY(); Bangle.setUI(); this.firstTouch = true; Bangle.dragHandler = this.dragHandler.bind(this); Bangle.setUI({mode: "custom", drag: Bangle.dragHandler, btn: (_n) => Bangle.showClock() }); }; } // Scroller