351 lines
11 KiB
JavaScript
351 lines
11 KiB
JavaScript
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
|