mirror of https://github.com/espruino/BangleApps
Slider: auto indentation
parent
bdf7aab65c
commit
03a1f10d45
|
@ -11,223 +11,223 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.create = function(cb, conf) {
|
exports.create = function(cb, conf) {
|
||||||
|
|
||||||
const R = Bangle.appRect;
|
const R = Bangle.appRect;
|
||||||
|
|
||||||
// Empty function added to cb if it's undefined.
|
// Empty function added to cb if it's undefined.
|
||||||
if (!cb) cb = ()=>{};
|
if (!cb) cb = ()=>{};
|
||||||
|
|
||||||
let o = {};
|
let o = {};
|
||||||
o.v = {}; // variables go here.
|
o.v = {}; // variables go here.
|
||||||
o.f = {}; // functions go here.
|
o.f = {}; // functions go here.
|
||||||
|
|
||||||
// Default configuration for the indicator, modified by parameter `conf`:
|
// Default configuration for the indicator, modified by parameter `conf`:
|
||||||
o.c = Object.assign({ // constants go here.
|
o.c = Object.assign({ // constants go here.
|
||||||
initLevel:0,
|
initLevel:0,
|
||||||
horizontal:false,
|
horizontal:false,
|
||||||
xStart:R.x2-R.w/4-4,
|
xStart:R.x2-R.w/4-4,
|
||||||
width:R.w/4,
|
width:R.w/4,
|
||||||
yStart:R.y+4,
|
yStart:R.y+4,
|
||||||
height:R.h-10,
|
height:R.h-10,
|
||||||
steps:30,
|
steps:30,
|
||||||
|
|
||||||
dragableSlider:true,
|
dragableSlider:true,
|
||||||
dragRect:R,
|
dragRect:R,
|
||||||
mode:"incr",
|
mode:"incr",
|
||||||
oversizeR:0,
|
oversizeR:0,
|
||||||
oversizeL:0,
|
oversizeL:0,
|
||||||
propagateDrag:false,
|
propagateDrag:false,
|
||||||
timeout:1,
|
timeout:1,
|
||||||
|
|
||||||
drawableSlider:true,
|
drawableSlider:true,
|
||||||
colorFG:g.theme.fg2,
|
colorFG:g.theme.fg2,
|
||||||
colorBG:g.theme.bg2,
|
colorBG:g.theme.bg2,
|
||||||
rounded:true,
|
rounded:true,
|
||||||
outerBorderSize:Math.round(2*R.w/176), // 176 is the # of pixels in a row on the Bangle.js 2's screen and typically also its app rectangles, used here to rescale to whatever pixel count is on the current app rectangle.
|
outerBorderSize:Math.round(2*R.w/176), // 176 is the # of pixels in a row on the Bangle.js 2's screen and typically also its app rectangles, used here to rescale to whatever pixel count is on the current app rectangle.
|
||||||
innerBorderSize:Math.round(2*R.w/176),
|
innerBorderSize:Math.round(2*R.w/176),
|
||||||
|
|
||||||
autoProgress:false,
|
autoProgress:false,
|
||||||
},conf);
|
},conf);
|
||||||
|
|
||||||
// If borders are bigger than the configured width, make them smaller to avoid glitches.
|
// If borders are bigger than the configured width, make them smaller to avoid glitches.
|
||||||
while (o.c.width <= 2*(o.c.outerBorderSize+o.c.innerBorderSize)) {
|
while (o.c.width <= 2*(o.c.outerBorderSize+o.c.innerBorderSize)) {
|
||||||
o.c.outerBorderSize--;
|
o.c.outerBorderSize--;
|
||||||
o.c.innerBorderSize--;
|
o.c.innerBorderSize--;
|
||||||
}
|
}
|
||||||
o.c.outerBorderSize = Math.max(0,o.c.outerBorderSize);
|
o.c.outerBorderSize = Math.max(0,o.c.outerBorderSize);
|
||||||
o.c.innerBorderSize = Math.max(0,o.c.innerBorderSize);
|
o.c.innerBorderSize = Math.max(0,o.c.innerBorderSize);
|
||||||
|
|
||||||
let totalBorderSize = o.c.outerBorderSize + o.c.innerBorderSize;
|
let totalBorderSize = o.c.outerBorderSize + o.c.innerBorderSize;
|
||||||
o.c.rounded = o.c.rounded?o.c.width/2:0;
|
o.c.rounded = o.c.rounded?o.c.width/2:0;
|
||||||
if (o.c.rounded) o.c._rounded = (o.c.width-2*totalBorderSize)/2;
|
if (o.c.rounded) o.c._rounded = (o.c.width-2*totalBorderSize)/2;
|
||||||
|
|
||||||
o.c.STEP_SIZE = ((o.c.height-2*totalBorderSize)-(!o.c.rounded?0:(2*o.c._rounded)))/o.c.steps;
|
o.c.STEP_SIZE = ((o.c.height-2*totalBorderSize)-(!o.c.rounded?0:(2*o.c._rounded)))/o.c.steps;
|
||||||
|
|
||||||
// If horizontal, flip things around.
|
// If horizontal, flip things around.
|
||||||
if (o.c.horizontal) {
|
if (o.c.horizontal) {
|
||||||
let mediator = o.c.xStart;
|
let mediator = o.c.xStart;
|
||||||
o.c.xStart = o.c.yStart;
|
o.c.xStart = o.c.yStart;
|
||||||
o.c.yStart = mediator;
|
o.c.yStart = mediator;
|
||||||
mediator = o.c.width;
|
mediator = o.c.width;
|
||||||
o.c.width = o.c.height;
|
o.c.width = o.c.height;
|
||||||
o.c.height = mediator;
|
o.c.height = mediator;
|
||||||
delete mediator;
|
delete mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make room for the border. Underscore indicates the area for the actual indicator bar without borders.
|
// Make room for the border. Underscore indicates the area for the actual indicator bar without borders.
|
||||||
o.c._xStart = o.c.xStart + totalBorderSize;
|
o.c._xStart = o.c.xStart + totalBorderSize;
|
||||||
o.c._width = o.c.width - 2*totalBorderSize;
|
o.c._width = o.c.width - 2*totalBorderSize;
|
||||||
o.c._yStart = o.c.yStart + totalBorderSize;
|
o.c._yStart = o.c.yStart + totalBorderSize;
|
||||||
o.c._height = o.c.height - 2*totalBorderSize;
|
o.c._height = o.c.height - 2*totalBorderSize;
|
||||||
|
|
||||||
// Add a rectangle object with x, y, x2, y2, w and h values.
|
// Add a rectangle object with x, y, x2, y2, w and h values.
|
||||||
o.c.r = {x:o.c.xStart, y:o.c.yStart, x2:o.c.xStart+o.c.width, y2:o.c.yStart+o.c.height, w:o.c.width, h:o.c.height};
|
o.c.r = {x:o.c.xStart, y:o.c.yStart, x2:o.c.xStart+o.c.width, y2:o.c.yStart+o.c.height, w:o.c.width, h:o.c.height};
|
||||||
|
|
||||||
// Initialize the level
|
// Initialize the level
|
||||||
o.v.level = o.c.initLevel;
|
o.v.level = o.c.initLevel;
|
||||||
|
|
||||||
// Only add interactivity if wanted.
|
// Only add interactivity if wanted.
|
||||||
if (o.c.dragableSlider) {
|
if (o.c.dragableSlider) {
|
||||||
|
|
||||||
let useMap = (o.c.mode==="map"||o.c.mode==="mapincr")?true:false;
|
let useMap = (o.c.mode==="map"||o.c.mode==="mapincr")?true:false;
|
||||||
let useIncr = (o.c.mode==="incr"||o.c.mode==="mapincr")?true:false;
|
let useIncr = (o.c.mode==="incr"||o.c.mode==="mapincr")?true:false;
|
||||||
|
|
||||||
const Y_MAX = g.getHeight()-1; // TODO: Should this take users screen calibration into account?
|
const Y_MAX = g.getHeight()-1; // TODO: Should this take users screen calibration into account?
|
||||||
|
|
||||||
o.v.ebLast = 0;
|
o.v.ebLast = 0;
|
||||||
o.v.dy = 0;
|
o.v.dy = 0;
|
||||||
|
|
||||||
o.f.wasOnDragRect = (exFirst, eyFirst)=>{
|
o.f.wasOnDragRect = (exFirst, eyFirst)=>{
|
||||||
"ram";
|
"ram";
|
||||||
return exFirst>o.c.dragRect.x && exFirst<o.c.dragRect.x2 && eyFirst>o.c.dragRect.y && eyFirst<o.c.dragRect.y2;
|
return exFirst>o.c.dragRect.x && exFirst<o.c.dragRect.x2 && eyFirst>o.c.dragRect.y && eyFirst<o.c.dragRect.y2;
|
||||||
};
|
};
|
||||||
|
|
||||||
o.f.wasOnIndicator = (exFirst)=>{
|
o.f.wasOnIndicator = (exFirst)=>{
|
||||||
"ram";
|
"ram";
|
||||||
if (!o.c.horizontal) return exFirst>o.c._xStart-o.c.oversizeL*o.c._width && exFirst<o.c._xStart+o.c._width+o.c.oversizeR*o.c._width;
|
if (!o.c.horizontal) return exFirst>o.c._xStart-o.c.oversizeL*o.c._width && exFirst<o.c._xStart+o.c._width+o.c.oversizeR*o.c._width;
|
||||||
if (o.c.horizontal) return exFirst>o.c._yStart-o.c.oversizeL*o.c._height && exFirst<o.c._yStart+o.c._height+o.c.oversizeR*o.c._height;
|
if (o.c.horizontal) return exFirst>o.c._yStart-o.c.oversizeL*o.c._height && exFirst<o.c._yStart+o.c._height+o.c.oversizeR*o.c._height;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to pass to `Bangle.on('drag', )`
|
// Function to pass to `Bangle.on('drag', )`
|
||||||
o.f.dragSlider = e=>{
|
o.f.dragSlider = e=>{
|
||||||
"ram";
|
"ram";
|
||||||
if (o.v.ebLast==0) {
|
if (o.v.ebLast==0) {
|
||||||
exFirst = o.c.horizontal?e.y:e.x;
|
exFirst = o.c.horizontal?e.y:e.x;
|
||||||
eyFirst = o.c.horizontal?e.x:e.y;
|
eyFirst = o.c.horizontal?e.x:e.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only react if on allowed area.
|
// Only react if on allowed area.
|
||||||
if (o.f.wasOnDragRect(exFirst, eyFirst)) {
|
if (o.f.wasOnDragRect(exFirst, eyFirst)) {
|
||||||
o.v.dragActive = true;
|
o.v.dragActive = true;
|
||||||
if (!o.c.propagateDrag) E.stopEventPropagation&&E.stopEventPropagation();
|
if (!o.c.propagateDrag) E.stopEventPropagation&&E.stopEventPropagation();
|
||||||
|
|
||||||
if (o.v.timeoutID) {clearTimeout(o.v.timeoutID); o.v.timeoutID = undefined;}
|
if (o.v.timeoutID) {clearTimeout(o.v.timeoutID); o.v.timeoutID = undefined;}
|
||||||
if (e.b==0 && !o.v.timeoutID && (o.c.timeout || o.c.timeout===0)) o.v.timeoutID = setTimeout(o.f.remove, 1000*o.c.timeout);
|
if (e.b==0 && !o.v.timeoutID && (o.c.timeout || o.c.timeout===0)) o.v.timeoutID = setTimeout(o.f.remove, 1000*o.c.timeout);
|
||||||
|
|
||||||
if (useMap && o.f.wasOnIndicator(exFirst)) { // If draging starts on the indicator, adjust one-to-one.
|
if (useMap && o.f.wasOnIndicator(exFirst)) { // If draging starts on the indicator, adjust one-to-one.
|
||||||
|
|
||||||
let input = !o.c.horizontal?
|
let input = !o.c.horizontal?
|
||||||
Math.min((Y_MAX-e.y)-o.c.yStart-3*o.c.rounded/4, o.c.height):
|
Math.min((Y_MAX-e.y)-o.c.yStart-3*o.c.rounded/4, o.c.height):
|
||||||
Math.min(e.x-o.c.xStart-3*o.c.rounded/4, o.c.width);
|
Math.min(e.x-o.c.xStart-3*o.c.rounded/4, o.c.width);
|
||||||
input = Math.round(input/o.c.STEP_SIZE);
|
input = Math.round(input/o.c.STEP_SIZE);
|
||||||
|
|
||||||
o.v.level = Math.min(Math.max(input,0),o.c.steps);
|
o.v.level = Math.min(Math.max(input,0),o.c.steps);
|
||||||
|
|
||||||
o.v.cbObj = {mode:"map", value:o.v.level};
|
o.v.cbObj = {mode:"map", value:o.v.level};
|
||||||
|
|
||||||
} else if (useIncr) { // Heavily inspired by "updown" mode of setUI.
|
} else if (useIncr) { // Heavily inspired by "updown" mode of setUI.
|
||||||
|
|
||||||
o.v.dy += o.c.horizontal?-e.dx:e.dy;
|
o.v.dy += o.c.horizontal?-e.dx:e.dy;
|
||||||
//if (!e.b) o.v.dy=0;
|
//if (!e.b) o.v.dy=0;
|
||||||
|
|
||||||
while (Math.abs(o.v.dy)>32) {
|
while (Math.abs(o.v.dy)>32) {
|
||||||
let incr;
|
let incr;
|
||||||
if (o.v.dy>0) { o.v.dy-=32; incr = 1;}
|
if (o.v.dy>0) { o.v.dy-=32; incr = 1;}
|
||||||
else { o.v.dy+=32; incr = -1;}
|
else { o.v.dy+=32; incr = -1;}
|
||||||
Bangle.buzz(20);
|
Bangle.buzz(20);
|
||||||
|
|
||||||
o.v.level = Math.min(Math.max(o.v.level-incr,0),o.c.steps);
|
o.v.level = Math.min(Math.max(o.v.level-incr,0),o.c.steps);
|
||||||
|
|
||||||
o.v.cbObj = {mode:"incr", value:incr};
|
o.v.cbObj = {mode:"incr", value:incr};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (o.v.cbObj && (o.v.level!==o.v.prevLevel||o.v.level===0||o.v.level===o.c.steps)) {
|
|
||||||
cb(o.v.cbObj.mode, o.v.cbObj.value);
|
|
||||||
o.f.draw&&o.f.draw(o.v.level);
|
|
||||||
}
|
|
||||||
o.v.cbObj = null;
|
|
||||||
o.v.prevLevel = o.v.level;
|
|
||||||
o.v.ebLast = e.b;
|
|
||||||
}
|
}
|
||||||
};
|
if (o.v.cbObj && (o.v.level!==o.v.prevLevel||o.v.level===0||o.v.level===o.c.steps)) {
|
||||||
|
cb(o.v.cbObj.mode, o.v.cbObj.value);
|
||||||
// Cleanup.
|
o.f.draw&&o.f.draw(o.v.level);
|
||||||
o.f.remove = ()=> {
|
|
||||||
Bangle.removeListener('drag', o.f.dragSlider);
|
|
||||||
o.v.dragActive = false;
|
|
||||||
o.v.timeoutID = undefined;
|
|
||||||
cb("remove", o.v.level);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add standard slider graphics only if wanted.
|
|
||||||
if (o.c.drawableSlider) {
|
|
||||||
|
|
||||||
// Function for getting the indication bars size.
|
|
||||||
o.f.updateBar = (levelHeight)=>{
|
|
||||||
"ram";
|
|
||||||
if (!o.c.horizontal) return {x:o.c._xStart,y:o.c._yStart+o.c._height-levelHeight,w:o.c._width,y2:o.c._yStart+o.c._height,r:o.c.rounded};
|
|
||||||
if (o.c.horizontal) return {x:o.c._xStart,y:o.c._yStart,w:levelHeight,h:o.c._height,r:o.c.rounded};
|
|
||||||
};
|
|
||||||
|
|
||||||
o.c.borderRect = {x:o.c._xStart-totalBorderSize,y:o.c._yStart-totalBorderSize,w:o.c._width+2*totalBorderSize,h:o.c._height+2*totalBorderSize,r:o.c.rounded};
|
|
||||||
|
|
||||||
o.c.hollowRect = {x:o.c._xStart-o.c.innerBorderSize,y:o.c._yStart-o.c.innerBorderSize,w:o.c._width+2*o.c.innerBorderSize,h:o.c._height+2*o.c.innerBorderSize,r:o.c.rounded};
|
|
||||||
|
|
||||||
// Standard slider drawing method.
|
|
||||||
o.f.draw = (level)=>{
|
|
||||||
"ram";
|
|
||||||
|
|
||||||
g.setColor(o.c.colorFG).fillRect(o.c.borderRect). // To get outer border...
|
|
||||||
setColor(o.c.colorBG).fillRect(o.c.hollowRect). // ... and here it's made hollow.
|
|
||||||
setColor(0==level?o.c.colorBG:o.c.colorFG).fillRect(o.f.updateBar((!o.c.rounded?0:(2*o.c._rounded))+level*o.c.STEP_SIZE)); // Here the bar is drawn.
|
|
||||||
if (o.c.rounded && level===0) { // Hollow circle indicates level zero when slider is rounded.
|
|
||||||
g.setColor(o.c.colorFG).fillCircle(o.c._xStart+o.c._rounded, o.c._yStart+o.c._height-o.c._rounded, o.c._rounded).
|
|
||||||
setColor(o.c.colorBG).fillCircle(o.c._xStart+o.c._rounded, o.c._yStart+o.c._height-o.c._rounded, o.c._rounded-o.c.outerBorderSize);
|
|
||||||
}
|
}
|
||||||
};
|
o.v.cbObj = null;
|
||||||
}
|
o.v.prevLevel = o.v.level;
|
||||||
|
o.v.ebLast = e.b;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Add logic for auto progressing the slider only if wanted.
|
// Cleanup.
|
||||||
if (o.c.autoProgress) {
|
o.f.remove = ()=> {
|
||||||
o.f.autoUpdate = ()=>{
|
Bangle.removeListener('drag', o.f.dragSlider);
|
||||||
o.v.level = o.v.autoInitLevel + Math.round((Date.now()-o.v.autoInitTime)/1000);
|
o.v.dragActive = false;
|
||||||
if (o.v.level>o.c.steps) o.v.level=o.c.steps;
|
o.v.timeoutID = undefined;
|
||||||
cb("auto", o.v.level);
|
cb("remove", o.v.level);
|
||||||
o.f.draw&&o.f.draw(o.v.level);
|
};
|
||||||
if (o.v.level==o.c.steps) {o.f.stopAutoUpdate();}
|
}
|
||||||
};
|
|
||||||
o.f.initAutoValues = ()=>{
|
|
||||||
o.v.autoInitTime=Date.now();
|
|
||||||
o.v.autoInitLevel=o.v.level;
|
|
||||||
};
|
|
||||||
o.f.startAutoUpdate = (intervalSeconds)=>{
|
|
||||||
if (!intervalSeconds) intervalSeconds = 1;
|
|
||||||
o.f.stopAutoUpdate();
|
|
||||||
o.f.initAutoValues();
|
|
||||||
o.f.draw&&o.f.draw(o.v.level);
|
|
||||||
o.v.autoIntervalID = setInterval(o.f.autoUpdate,1000*intervalSeconds);
|
|
||||||
};
|
|
||||||
o.f.stopAutoUpdate = ()=>{
|
|
||||||
if (o.v.autoIntervalID) {
|
|
||||||
clearInterval(o.v.autoIntervalID);
|
|
||||||
o.v.autoIntervalID = undefined;
|
|
||||||
}
|
|
||||||
o.v.autoInitLevel = undefined;
|
|
||||||
o.v.autoInitTime = undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return o;
|
// Add standard slider graphics only if wanted.
|
||||||
};
|
if (o.c.drawableSlider) {
|
||||||
|
|
||||||
|
// Function for getting the indication bars size.
|
||||||
|
o.f.updateBar = (levelHeight)=>{
|
||||||
|
"ram";
|
||||||
|
if (!o.c.horizontal) return {x:o.c._xStart,y:o.c._yStart+o.c._height-levelHeight,w:o.c._width,y2:o.c._yStart+o.c._height,r:o.c.rounded};
|
||||||
|
if (o.c.horizontal) return {x:o.c._xStart,y:o.c._yStart,w:levelHeight,h:o.c._height,r:o.c.rounded};
|
||||||
|
};
|
||||||
|
|
||||||
|
o.c.borderRect = {x:o.c._xStart-totalBorderSize,y:o.c._yStart-totalBorderSize,w:o.c._width+2*totalBorderSize,h:o.c._height+2*totalBorderSize,r:o.c.rounded};
|
||||||
|
|
||||||
|
o.c.hollowRect = {x:o.c._xStart-o.c.innerBorderSize,y:o.c._yStart-o.c.innerBorderSize,w:o.c._width+2*o.c.innerBorderSize,h:o.c._height+2*o.c.innerBorderSize,r:o.c.rounded};
|
||||||
|
|
||||||
|
// Standard slider drawing method.
|
||||||
|
o.f.draw = (level)=>{
|
||||||
|
"ram";
|
||||||
|
|
||||||
|
g.setColor(o.c.colorFG).fillRect(o.c.borderRect). // To get outer border...
|
||||||
|
setColor(o.c.colorBG).fillRect(o.c.hollowRect). // ... and here it's made hollow.
|
||||||
|
setColor(0==level?o.c.colorBG:o.c.colorFG).fillRect(o.f.updateBar((!o.c.rounded?0:(2*o.c._rounded))+level*o.c.STEP_SIZE)); // Here the bar is drawn.
|
||||||
|
if (o.c.rounded && level===0) { // Hollow circle indicates level zero when slider is rounded.
|
||||||
|
g.setColor(o.c.colorFG).fillCircle(o.c._xStart+o.c._rounded, o.c._yStart+o.c._height-o.c._rounded, o.c._rounded).
|
||||||
|
setColor(o.c.colorBG).fillCircle(o.c._xStart+o.c._rounded, o.c._yStart+o.c._height-o.c._rounded, o.c._rounded-o.c.outerBorderSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add logic for auto progressing the slider only if wanted.
|
||||||
|
if (o.c.autoProgress) {
|
||||||
|
o.f.autoUpdate = ()=>{
|
||||||
|
o.v.level = o.v.autoInitLevel + Math.round((Date.now()-o.v.autoInitTime)/1000);
|
||||||
|
if (o.v.level>o.c.steps) o.v.level=o.c.steps;
|
||||||
|
cb("auto", o.v.level);
|
||||||
|
o.f.draw&&o.f.draw(o.v.level);
|
||||||
|
if (o.v.level==o.c.steps) {o.f.stopAutoUpdate();}
|
||||||
|
};
|
||||||
|
o.f.initAutoValues = ()=>{
|
||||||
|
o.v.autoInitTime=Date.now();
|
||||||
|
o.v.autoInitLevel=o.v.level;
|
||||||
|
};
|
||||||
|
o.f.startAutoUpdate = (intervalSeconds)=>{
|
||||||
|
if (!intervalSeconds) intervalSeconds = 1;
|
||||||
|
o.f.stopAutoUpdate();
|
||||||
|
o.f.initAutoValues();
|
||||||
|
o.f.draw&&o.f.draw(o.v.level);
|
||||||
|
o.v.autoIntervalID = setInterval(o.f.autoUpdate,1000*intervalSeconds);
|
||||||
|
};
|
||||||
|
o.f.stopAutoUpdate = ()=>{
|
||||||
|
if (o.v.autoIntervalID) {
|
||||||
|
clearInterval(o.v.autoIntervalID);
|
||||||
|
o.v.autoIntervalID = undefined;
|
||||||
|
}
|
||||||
|
o.v.autoInitLevel = undefined;
|
||||||
|
o.v.autoInitTime = undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue