BangleApps/apps/gpstrek/app.js

1565 lines
47 KiB
JavaScript

{ //run in own scope for fast switch
const MODE_MENU = 0;
const MODE_MAP = 1;
const MODE_SLICES = 2;
const STORAGE = require("Storage");
const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144;
const SETTINGS = Object.assign(
require('Storage').readJSON("gpstrek.default.json", true) || {},
require('Storage').readJSON("gpstrek.json", true) || {}
);
let init = function(){
global.screen = 1;
global.drawTimeout = undefined;
global.lastDrawnScreen = 0;
global.firstDraw = true;
global.slices = [];
global.maxSlicePages = 1;
global.scheduleDraw = false;
Bangle.loadWidgets();
WIDGETS.gpstrek.start(false);
if (!WIDGETS.gpstrek.getState().mode) WIDGETS.gpstrek.getState().mode = MODE_MENU;
};
init();
scheduleDraw = true;
let parseNumber = function(toParse){
if (toParse.includes(".")) return parseFloat(toParse);
return parseFloat("" + toParse + ".0");
};
let parseWaypoint = function(filename, offset, result){
result.lat = parseNumber(STORAGE.read(filename, offset, 11));
result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12));
return offset + 12;
};
let parseWaypointWithElevation = function (filename, offset, result){
offset = parseWaypoint(filename, offset, result);
result.alt = parseNumber(STORAGE.read(filename, offset, 6));
return offset + 6;
};
let parseWaypointWithName = function(filename, offset, result){
offset = parseWaypoint(filename, offset, result);
return parseName(filename, offset, result);
};
let parseName = function(filename, offset, result){
let nameLength = STORAGE.read(filename, offset, 2) - 0;
result.name = STORAGE.read(filename, offset += 2, nameLength);
return offset + nameLength;
};
let parseWaypointWithElevationAndName = function(filename, offset, result){
offset = parseWaypointWithElevation(filename, offset, result);
return parseName(filename, offset, result);
};
let cache = {};
let cachedOffsets = [];
let getFromCache = function(filename, offset, result){
if(filename == cache.filename && cache[offset]) {
Object.assign(result, cache[offset]);
result.access = Date.now();
return offset + cache[offset].fileLength;
}
return offset;
};
let cacheCleanup = function (){
if (SETTINGS.cacheMinFreeMem){
while (cachedOffsets.length > 0 && process.memory(false).free < SETTINGS.cacheMinFreeMem){
cache[cachedOffsets.shift()] = undefined;
}
}
if (SETTINGS.cacheMaxEntries){
while (cachedOffsets.length > SETTINGS.cacheMaxEntries){
cache[cachedOffsets.shift()] = undefined;
}
}
};
let cacheAdd = function (filename, result) {
cacheCleanup();
if(cache.filename != filename) {
cache = {};
cache.filename = filename;
}
cache[result.fileOffset] = result;
cachedOffsets.push(result.fileOffset);
};
let getEntry = function(filename, offset, result, noCaching){
if (offset < 0) return offset;
let cacheOffset = getFromCache(filename, offset, result);
if (cacheOffset != offset) return cacheOffset;
result.fileOffset = offset;
let type = STORAGE.read(filename, offset++, 1);
if (type == "") return -1;
switch (type){
case "A":
offset = parseWaypoint(filename, offset, result);
break;
case "B":
offset = parseWaypointWithName(filename, offset, result);
break;
case "C":
offset = parseWaypointWithElevation(filename, offset, result);
break;
case "D":
offset = parseWaypointWithElevationAndName(filename, offset, result);
break;
default:
print("Unknown entry type", type);
return -1;
}
offset++;
result.fileLength = offset - result.fileOffset;
if (!noCaching && (SETTINGS.cacheMaxEntries || SETTINGS.cacheMinFreeMem)){
cacheAdd(filename, result);
}
return offset;
};
const labels = ["N","NE","E","SE","S","SW","W","NW"];
const loc = require("locale");
let matchFontSize = function(graphics, text, height, width){
graphics.setFontVector(height);
//let metrics;
let size = 1;
while (graphics.stringMetrics(text).width > 0.90 * width){
size -= 0.05;
graphics.setFont("Vector",Math.floor(height*size));
}
};
let getDoubleLineSlice = function(title1,title2,provider1,provider2){
return {
draw: function (graphics, x, y, height, width){
lastDrawn = Date.now();
if (typeof title1 == "function") title1 = title1();
if (typeof title2 == "function") title2 = title2();
graphics.clearRect(x,y,x+width,y+height);
lastValue1 = provider1();
matchFontSize(graphics, title1 + lastValue1, Math.floor(height*0.5), width);
graphics.setFontAlign(-1,-1);
graphics.drawString(title1, x+2, y);
graphics.setFontAlign(1,-1);
graphics.drawString(lastValue1, x+width, y);
lastValue2 = provider2();
matchFontSize(graphics, title2 + lastValue2, Math.floor(height*0.5), width);
graphics.setFontAlign(-1,-1);
graphics.drawString(title2, x+2, y+(height*0.5));
graphics.setFontAlign(1,-1);
graphics.drawString(lastValue2, x+width, y+(height*0.5));
}
};
};
const arrow = Graphics.createImage(`
X
XXX
XXXXX
XXX XXX
XXX XXX
XXX XXX
`);
const thinarrow = Graphics.createImage(`
X
XXX
XX XX
XX XX
XX XX
`);
const cross = Graphics.createImage(`
XX XX
XX XX
XX XX
XX XX
XXX
XX XX
XX XX
XX XX
XX XX
`);
const move = Graphics.createImage(`
X
XXX
X X X
X
X
X X X
X X X
XXXXXXXXXXXXX
X X X
X X X
X
X
X X X
XXX
X
`);
const point = Graphics.createImage(`
XX
XXXX
XX XX
XX XX
XX XX
XX XX
XXXX
XX
`);
let isGpsCourse = function(){
return WIDGETS.gpstrek.getState().currentPos && isFinite(WIDGETS.gpstrek.getState().currentPos.course);
};
let isMapOverview = false;
let forceMapRedraw = false;
let activeTimeouts = [];
let taskQueue = [];
let queueProcessing = false;
let addToTaskQueue = function (func, data){
if (queueProcessing) print("Adding during processing, this should not happen");
taskQueue.push({f:func,d:data});
};
let prependTaskQueue = function (func, data, force){
if (queueProcessing && !force) print("Prepending during processing, this should not happen");
taskQueue.unshift({f:func,d:data});
};
let runQueue = function(inTimeouts){
if (taskQueue.length > 0){
let current = taskQueue.shift();
if (inTimeouts){
let id = setTimeout(()=>{
current.f(current.d);
activeTimeouts = activeTimeouts.filter((c)=>c!=id);
runQueue(inTimeouts);
},SETTINGS.queueWaitingTime);
activeTimeouts.push(id);
} else {
current.f(current.d);
runQueue(inTimeouts);
}
}
};
let processTaskQueue = function(inTimeouts){
if (queueProcessing) return;
addToTaskQueue(()=>{queueProcessing=false;});
queueProcessing = true;
runQueue(inTimeouts);
};
let clearTaskQueue = function(){
taskQueue = [];
for (let c of activeTimeouts){
clearTimeout(c);
}
queueProcessing = false;
};
let getMapSlice = function(){
let lastDrawn = 0;
let lastCourse = 0;
let lastStart;
let lastCurrent;
return {
draw: function (graphics, x, y, height, width){
let s = WIDGETS.gpstrek.getState();
let course = 0;
if (isGpsCourse())
course = s.currentPos.course;
else
course = getAveragedCompass();
let route = s.route;
if (!route) return;
let waypoint = get(route);
let currentRouteIndex = getWaypointIndex(route);
let startingPoint = Bangle.project(waypoint);
let current = startingPoint;
let currentPosFromGPS = false;
if (s.currentPos.lat) {
current = Bangle.project(s.currentPos);
currentPosFromGPS = true;
} else {
// in case of no actual position assume previous waypoint as current
let prevPoint = getPrev(route, currentRouteIndex);
if (prevPoint && prevPoint.lat) current = Bangle.project(prevPoint);
}
const interfaceHeight = g.getHeight()*0.1;
const errorMarkerSize=3;
let compassHeight = height*0.4;
if (!SETTINGS.mapCompass) compassHeight=0;
if (compassHeight > g.getHeight()*0.1) compassHeight = g.getHeight()*0.1;
let compassCenterX = x + errorMarkerSize + 8 + compassHeight;
let compassCenterY = y + errorMarkerSize + 8 + compassHeight;
let mapCenterX = isMapOverview?mapOverviewX:x+(width-10)/2+compassHeight+5;
let mapCenterY = isMapOverview?mapOverviewY:y+height*0.4;
let mapScale = isMapOverview?mapOverviewScale : mapLiveScale;
let mapRot = require("graphics_utils").degreesToRadians(180-(isMapOverview?0:course));
let mapTrans = {
scale: mapScale,
rotate: mapRot,
x: mapCenterX,
y: mapCenterY
};
let drawInterface = function(){
graphics.setClipRect(x,y,x+width,y+height);
graphics.setFont("Vector",25).setFontAlign(0,0);
graphics.setColor(graphics.theme.fg);
graphics.clearRect(x,y+height-interfaceHeight,x+width,y+height-1);
graphics.drawRect(x,y+height-interfaceHeight,x+width/4,y+height-1);
graphics.drawString("-", x+width*0.125,y+height-interfaceHeight*0.5);
graphics.drawRect(x+width*0.75,y+height-interfaceHeight,x+width-1,y+height-1);
graphics.drawString("+", x+width*0.875,y+height-interfaceHeight*0.5);
let refs = [10,20,50,100,200,300,400,500,800,1000,2000,5000,10000,50000];
let l = width*0.4;
let scale = refs[0];
for (let c of refs){
if (c*mapScale > l)
break;
else
scale = c;
}
let scaleString = loc.distance(scale, 2);
let scaleHeight = interfaceHeight * 0.2;
graphics.setFontAlign(-1,-1).setFont("Vector",12);
graphics.drawString(scaleString,x+width*0.31,y+height-interfaceHeight, true);
if (isFinite(scale)){
graphics.drawLine(x+width*0.3,y+height-scaleHeight,x+width*0.3+scale*mapScale,y+height-scaleHeight);
graphics.drawLine(x+width*0.3,y+height-scaleHeight,x+width*0.3,y+height-interfaceHeight);
graphics.drawLine(x+width*0.3+scale*mapScale,y+height-scaleHeight,x+width*0.3+scale*mapScale,y+height-interfaceHeight);
}
};
let drawMapCompass = function(){
graphics.setClipRect(x,y,x+width,y+height-interfaceHeight-1);
graphics.setFont6x15();
let compass = [ 0,0, 0, compassHeight, 0, -compassHeight, compassHeight,0,-compassHeight,0 ];
compass = graphics.transformVertices(compass, {
rotate:require("graphics_utils").degreesToRadians(180-course),
x: compassCenterX,
y: compassCenterY
});
graphics.setFontAlign(0,0);
graphics.setColor(graphics.theme.bg);
graphics.fillCircle(compassCenterX, compassCenterY,compassHeight+7);
graphics.setColor(graphics.theme.fg);
graphics.drawCircle(compassCenterX, compassCenterY,compassHeight);
graphics.drawString("N", compass[2], compass[3], true);
graphics.drawString("S", compass[4], compass[5], true);
graphics.drawString("W", compass[6], compass[7], true);
graphics.drawString("E", compass[8], compass[9], true);
if(!isGpsCourse() && !isMapOverview) {
let xh = E.clip(s.acc.x*compassHeight, -compassHeight, compassHeight);
let yh = E.clip(s.acc.y*compassHeight, -compassHeight, compassHeight);
graphics.fillCircle(compassCenterX + xh, compassCenterY + yh,3);
} else if (isMapOverview){
graphics.setColor(0,0,1);
graphics.fillCircle(compassCenterX, compassCenterY,3);
}
};
let drawCurrentPos = function(){
graphics.setClipRect(x,y,x+width,y+height-interfaceHeight-1);
graphics.setColor(graphics.theme.fg);
if (currentPosFromGPS) {
let pos = graphics.transformVertices([ startingPoint.x - current.x, (startingPoint.y - current.y)*-1 ], mapTrans);
if (!isMapOverview){
if (pos[0] < x) { pos[0] = x + errorMarkerSize + 5; graphics.setColor(1,0,0).fillRect(x,y,x+errorMarkerSize,y+height);}
if (pos[0] > x + width) {pos[0] = x + width - errorMarkerSize - 5; graphics.setColor(1,0,0).fillRect(x + width - errorMarkerSize,y,x + width ,y+height);}
if (pos[1] < y) {pos[1] = y + errorMarkerSize + 1; graphics.setColor(1,0,0).fillRect(x,y,x + width,y+errorMarkerSize);}
if (pos[1] > y + height - interfaceHeight -1) { pos[1] = y + height - errorMarkerSize - 5-interfaceHeight-1; graphics.setColor(1,0,0).fillRect(x,y + height - errorMarkerSize-interfaceHeight-1,x + width ,y+height-interfaceHeight-1);}
}
if (isMapOverview) {
graphics.drawImage(arrow, pos[0],pos[1], {rotate: require("graphics_utils").degreesToRadians(course)});
} else {
graphics.drawImage(arrow, pos[0]-arrow.width/2,pos[1]);
}
graphics.setColor(0,1,0);
graphics.fillRect(mapCenterX-1,mapCenterY-1, mapCenterX+1,mapCenterY+1);
graphics.drawCircle(mapCenterX,mapCenterY, mapScale*SETTINGS.waypointChangeDist);
graphics.setColor(graphics.theme.fg);
} else {
graphics.setColor(0,1,0);
graphics.fillCircle(mapCenterX,mapCenterY, 5);
graphics.setColor(graphics.theme.fg);
}
};
let courseChanged = Math.abs(lastCourse - course) > SETTINGS.minCourseChange;
let oldEnough = Date.now() - lastDrawn > SETTINGS.mapRefresh;
let startChanged = (!lastStart || lastStart.x != startingPoint.x || lastStart.y != startingPoint.y);
let neededChange = (SETTINGS.minPosChange/(isMapOverview?mapOverviewScale:mapLiveScale));
let currentChanged = (!lastCurrent || (Math.abs(lastCurrent.x - current.x)) > neededChange|| (Math.abs(lastCurrent.y - current.y)) > neededChange);
let liveChanged = !isMapOverview && (startChanged || currentChanged || courseChanged);
let refreshMap = forceMapRedraw || (oldEnough && (liveChanged));
let renderInTimeouts = isMapOverview || !Bangle.isLocked();
if (refreshMap) {
clearTaskQueue();
//clear map view
graphics.clearRect(x,y,x+width,y+height-interfaceHeight-1);
if (isMapOverview && scrolling){
addToTaskQueue(()=>{
graphics.setColor(graphics.theme.fg);
graphics.drawImage(move, compassCenterX-compassHeight, compassCenterY-compassHeight);
});
}
if (!isMapOverview){
drawCurrentPos();
}
if (SETTINGS.mapCompass && !isMapOverview && renderInTimeouts){
drawMapCompass();
}
if (renderInTimeouts) drawInterface();
forceMapRedraw = false;
lastDrawn = Date.now();
let drawPath = function(reverse, startingIndex, maxWaypoints){
let data = {
i:startingIndex,
poly:[],
maxWaypoints: maxWaypoints,
breakLoop: false,
dist: 0
};
let drawChunk = function(data){
if (data.breakLoop) return;
graphics.setColor(graphics.theme.fg);
graphics.setClipRect(x,y,x+width,y+height-interfaceHeight-1);
let finish;
let last;
let toDraw;
let named = [];
let dir = [];
for (let j = 0; j < SETTINGS.mapChunkSize; j++){
data.i = data.i + (reverse?-1:1);
let p = get(route, data.i);
if (!p || !p.lat) {
data.i = data.i + (reverse?1:-1);
data.breakLoop = true;
break;
}
if (data.maxWaypoints && Math.abs(startingIndex - data.i) > data.maxWaypoints) {
data.breakLoop = true;
last = true;
break;
}
toDraw = Bangle.project(p);
if (SETTINGS.mapDirection){
let lastWp = get(route, data.i - (reverse?-1:1));
if (lastWp) data.dist+=distance(lastWp,p);
if (!isMapOverview && data.dist > 20/mapScale){
dir.push({i:data.poly.length,b:require("graphics_utils").degreesToRadians(bearing(lastWp,p)-(reverse?0:180))});
data.dist=0;
}
}
if (p.name)
named.push({i:data.poly.length,n:p.name});
data.poly.push(startingPoint.x-toDraw.x);
data.poly.push((startingPoint.y-toDraw.y)*-1);
}
finish = isLast(route, getWaypointIndex(route, data.i));
data.poly = graphics.transformVertices(data.poly, mapTrans);
graphics.drawPoly(data.poly, false);
if (!isMapOverview && (data.poly[data.poly.length-2] < (x - 10)
|| data.poly[data.poly.length-2] > (x + width + 10)
|| data.poly[data.poly.length-1] < (y - 10)
|| data.poly[data.poly.length-1] > (y + height + 10))) data.breakLoop = true;
graphics.setFont6x15().setFontAlign(-1,0);
for (let c of named){
if (data.i != 0 || currentPosFromGPS){
graphics.drawImage(point, Math.round(data.poly[c.i]-point.width/2), Math.round(data.poly[c.i+1]-point.height/2));
}
graphics.drawString(c.n, data.poly[c.i] + 10, data.poly[c.i+1]);
}
for (let c of dir){
graphics.drawImage(thinarrow, data.poly[c.i], data.poly[c.i+1], {rotate: c.b});
}
if (finish)
graphics.drawImage(finishIcon, data.poly[data.poly.length - 2] -5, data.poly[data.poly.length - 1] - 4);
else if (last) {
graphics.drawImage(cross, data.poly[data.poly.length - 2] - cross.width/2, data.poly[data.poly.length - 1] - cross.height/2);
}
//Add last drawn point to get closed path
if (toDraw) {
data.poly = [ startingPoint.x-toDraw.x, (startingPoint.y-toDraw.y)*-1];
toDraw = null;
}
if (!data.breakLoop){
prependTaskQueue(drawChunk, data, true);
}
};
addToTaskQueue(drawChunk, data);
};
drawPath(true, currentRouteIndex+1);
drawPath(false, currentRouteIndex-1);
addToTaskQueue(drawInterface);
addToTaskQueue(drawCurrentPos);
addToTaskQueue(()=>{
lastCourse = course;
lastStart = startingPoint;
lastCurrent = current;
})
processTaskQueue(renderInTimeouts);
}
if (SETTINGS.mapCompass && !isMapOverview){
drawMapCompass();
}
}
};
};
let getTargetSlice = function(targetDataSource){
let nameIndex = 0;
return {
draw: function (graphics, x, y, height, width){
graphics.clearRect(x,y,x+width,y+height);
if (targetDataSource.icon){
graphics.drawImage(targetDataSource.icon,x,y + (height - 16)/2);
x += 16;
width -= 16;
}
let start = targetDataSource.getStart();
let target = targetDataSource.getTarget();
if (!target || !start) return;
let dist = distance(start,target);
if (isNaN(dist)) dist = Infinity;
let bearingString = bearing(start,target) + "°";
if (target.name) {
graphics.setFont("Vector",Math.floor(height*0.5));
let scrolledName = (target.name || "").substring(nameIndex);
if (graphics.stringMetrics(scrolledName).width > width){
nameIndex++;
} else {
nameIndex = 0;
}
graphics.drawString(scrolledName, x+2, y);
let distanceString = loc.distance(dist,2);
matchFontSize(graphics, distanceString + bearingString, height*0.5, width);
graphics.drawString(bearingString, x+2, y+(height*0.5));
graphics.setFontAlign(1,-1);
graphics.drawString(distanceString, x + width, y+(height*0.5));
} else {
graphics.setFont("Vector",Math.floor(height*1));
let bearingString = bearing(start,target) + "°";
let formattedDist = loc.distance(dist,2);
let distNum = (formattedDist.match(/[0-9\.]+/) || [Infinity])[0];
matchFontSize(graphics, bearingString + distNum, height*0.8, width);
graphics.setFontAlign(-1,-1);
graphics.drawString(bearingString, x+2, y);
graphics.setFontAlign(1,-1);
graphics.drawString(distNum, x + width, y);
graphics.setFont("Vector",Math.floor(height*0.25));
graphics.setFontAlign(-1,1);
if (targetDataSource.getProgress){
graphics.drawString(targetDataSource.getProgress(), x + 2, y + height);
}
graphics.setFontAlign(1,1);
if (isFinite(distNum) && distNum != Infinity)
graphics.drawString(formattedDist.match(/[a-zA-Z]+/), x + width, y + height);
}
}
};
};
let drawCompass = function(graphics, x, y, height, width, increment, start){
graphics.setFont12x20();
graphics.setFontAlign(0,-1);
graphics.setColor(graphics.theme.fg);
let frag = 0 - start%15;
if (frag>0) frag = 0;
let xpos = 0 + frag*increment;
for (let i=start;i<=720;i+=15){
var res = i + frag;
if (res%90==0) {
graphics.drawString(labels[Math.floor(res/45)%8],xpos,y+height*0.1);
graphics.fillRect(xpos-1,Math.floor(y+height*0.8),xpos+1,Math.floor(y+height));
} else if (res%45==0) {
graphics.drawString(labels[Math.floor(res/45)%8],xpos,y+height*0.1);
graphics.fillRect(xpos-1,Math.floor(y+height*0.7),xpos+1,Math.floor(y+height));
} else if (res%15==0) {
graphics.fillRect(xpos,Math.floor(y+height*0.9),xpos+1,Math.floor(y+height));
}
xpos+=increment*15;
if (xpos > width + 20) break;
}
};
let getCompassSlice = function(){
let compassDataSource = {
getCourse: function (){
if(isGpsCourse()) return WIDGETS.gpstrek.getState().currentPos.course;
return getAveragedCompass();
},
getPoints: function (){
let points = [];
let s = WIDGETS.gpstrek.getState();
if (s.currentPos && s.currentPos.lon && s.route){
points.push({bearing:bearing(s.currentPos, getLast(s.route)), icon: finishIcon});
}
if (s.currentPos && s.currentPos.lon && s.waypoint){
points.push({bearing:bearing(s.currentPos, s.waypoint), icon: finishIcon});
}
if (s.currentPos && s.currentPos.lon && s.route){
points.push({bearing:bearing(s.currentPos, get(s.route)), color:"#0f0"});
}
return points;
},
getMarkers: function (){
return [{xpos:0.5, width:10, height:10, linecolor:g.theme.fg, fillcolor:"#f00"}];
}
};
let lastDrawnValue = 0;
//const buffers = 4;
//let buf = [];
return {
refresh: function(){
return Math.abs(lastDrawnValue - compassDataSource.getCourse()) > SETTINGS.minCourseChange;
},
draw: function (graphics, x,y,height,width){
const max = 180;
const increment=width/max;
let s = WIDGETS.gpstrek.getState();
let course = isGpsCourse() ? s.currentPos.course : getAveragedCompass();
lastDrawnValue = course;
graphics.clearRect(x,y,x+width,y+height);
var start = course - 90;
if (isNaN(course)) start = -90;
if (start<0) start+=360;
start = start % 360;
if (s.acc && !isGpsCourse()){
drawCompass(graphics,0,y+width*0.05,height-width*0.05,width,increment,start);
} else {
drawCompass(graphics,0,y,height,width,increment,start);
}
if (compassDataSource.getPoints){
let points = compassDataSource.getPoints(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getPoints())
for (let p of points){
g.reset();
var bpos = p.bearing - course;
if (bpos>180) bpos -=360;
if (bpos<-180) bpos +=360;
bpos+=120;
let min = 0;
let max = 180;
if (bpos<=min){
bpos = Math.floor(width*0.05);
} else if (bpos>=max) {
bpos = Math.ceil(width*0.95);
} else {
bpos=Math.round(bpos*increment);
}
if (p.color){
graphics.setColor(p.color);
}
if (p.icon){
graphics.drawImage(p.icon, bpos,y+height-12, {rotate:0,scale:2});
} else {
graphics.fillCircle(bpos,y+height-12,Math.floor(width*0.03));
}
}
}
if (compassDataSource.getMarkers){
let markers = compassDataSource.getMarkers(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getMarkers())
for (let m of markers){
g.reset();
g.setColor(m.fillcolor);
let mpos = m.xpos * width;
if (m.xpos < 0.05) mpos = Math.floor(width*0.05);
if (m.xpos > 0.95) mpos = Math.ceil(width*0.95);
g.fillPoly(triangle(mpos,y+height-m.height, m.height, m.width));
g.setColor(m.linecolor);
g.drawPoly(triangle(mpos,y+height-m.height, m.height, m.width),true);
}
}
graphics.setColor(g.theme.fg);
graphics.fillRect(x,y,Math.floor(width*0.05),y+height);
graphics.fillRect(Math.ceil(width*0.95),y,width,y+height);
if (s.acc && !isGpsCourse()) {
let xh = E.clip(width*0.5-height/2+(((s.acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2);
let yh = E.clip(y+(((s.acc.y+1)/2)*height),y,y+height);
graphics.fillRect(width*0.5 - height/2, y, width*0.5 + height/2, y + Math.floor(width*0.05));
graphics.setColor(g.theme.bg);
graphics.drawLine(width*0.5 - 5, y, width*0.5 - 5, y + Math.floor(width*0.05));
graphics.drawLine(width*0.5 + 5, y, width*0.5 + 5, y + Math.floor(width*0.05));
graphics.fillRect(xh-1,y,xh+1,y+Math.floor(width*0.05));
let left = Math.floor(width*0.05);
let right = Math.ceil(width*0.95);
graphics.drawLine(0,y+height/2-5,left,y+height/2-5);
graphics.drawLine(right,y+height/2-5,x+width,y+height/2-5);
graphics.drawLine(0,y+height/2+5,left,y+height/2+5);
graphics.drawLine(right,y+height/2+5,x+width,y+height/2+5);
graphics.fillRect(0,yh-1,left,yh+1);
graphics.fillRect(right,yh-1,x+width,yh+1);
}
graphics.setColor(g.theme.fg);
graphics.drawRect(Math.floor(width*0.05),y,Math.ceil(width*0.95),y+height);
}
};
};
let radians = function(a) {
return a*Math.PI/180;
};
let degrees = function(a) {
let d = a*180/Math.PI;
return (d+360)%360;
};
let bearing = function(a,b){
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
let delta = radians(b.lon-a.lon);
let alat = radians(a.lat);
let blat = radians(b.lat);
let y = Math.sin(delta) * Math.cos(blat);
let x = Math.cos(alat)*Math.sin(blat) -
Math.sin(alat)*Math.cos(blat)*Math.cos(delta);
return Math.round(degrees(Math.atan2(y, x)));
};
let distance = function(a,b){
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
let x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
let y = radians(b.lat-a.lat);
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
};
let getAveragedCompass = function(){
return Math.round(WIDGETS.gpstrek.getState().avgComp);
};
let triangle = function(x, y, width, height){
return [
Math.round(x),Math.round(y),
Math.round(x+width * 0.5), Math.round(y+height),
Math.round(x-width * 0.5), Math.round(y+height)
];
};
mapOverviewX = g.getWidth()/2;
mapOverviewY = g.getHeight()/2;
mapOverviewScale = SETTINGS.overviewScale;
mapLiveScale = SETTINGS.mapScale;
let onAction = function(_,xy){
clearTaskQueue();
forceMapRedraw = true;
if (WIDGETS.gpstrek.getState().mode == MODE_MAP){
stopDrawing();
if (xy && xy.y > Bangle.appRect.y+Bangle.appRect.h-g.getHeight()*0.2 && xy.y <= Bangle.appRect.y2){
if (xy.x < Bangle.appRect.x + Bangle.appRect.w/2)
if (isMapOverview) {
mapOverviewScale /= 1.33;
} else {
mapLiveScale /= 1.33;
}
else
if (isMapOverview) {
mapOverviewScale *= 1.33;
} else {
mapLiveScale *= 1.33;
}
} else if (isMapOverview && xy && xy.y > Bangle.appRect.y){
scrolling = !scrolling;
}
startDrawing();
}
};
let scrolling = false;
let onSwipe = function(dirLR,dirUD){
let s = WIDGETS.gpstrek.getState();
clearTaskQueue();
forceMapRedraw = true;
if (s.mode == MODE_MAP){
if (!scrolling){
if (dirLR > 0) {
switchMode(MODE_MENU);
} else if (dirLR < 0) {
switchMode(MODE_SLICES);
}
if (dirUD){
isMapOverview = !isMapOverview;
if (!isMapOverview){
mapOverviewX = g.getWidth()/2;
mapOverviewY = g.getHeight()/2;
scrolling = false;
}
startDrawing();
}
} else {
mapOverviewX += dirLR * SETTINGS.overviewScroll;
mapOverviewY += dirUD * SETTINGS.overviewScroll;
}
} else if (s.mode == MODE_SLICES){
if (dirLR > 0) {
if (s.route){
switchMode(MODE_MAP);
} else {
switchMode(MODE_MENU);
}
} else if (dirLR < 0) {
switchMode(MODE_MENU);
}
if (dirUD){
setSlicesPage(dirUD);
}
} else {
if (dirLR > 0) {
switchMode(MODE_SLICES);
} else if (dirLR < 0) {
if (s.route){
switchMode(MODE_MAP);
} else {
switchMode(MODE_SLICES);
}
}
}
};
let setButtons = function(){
Bangle.removeListener("swipe", onSwipe);
let options = {
mode: "custom",
swipe: onSwipe,
btn: onAction,
touch: onAction,
};
Bangle.setUI(options);
};
let getFileSize = function(filename){
return STORAGE.readArrayBuffer(filename).byteLength;
};
let writeUint24 = function(filename, number, offset, size){
let b = new ArrayBuffer(3);
let n = new Uint24Array(b,0,1);
n[0] = number;
STORAGE.write(filename,b,offset,size);
};
let writeUint32 = function(filename, number, offset, size){
let b = new ArrayBuffer(4);
let n = new Uint32Array(b,0,1);
n[0] = number;
STORAGE.write(filename,b,offset,size);
};
let getRouteIndex = function(route){
if (!route.indexToOffset) loadIndex(route);
return route.indexToOffset;
}
let getIndexFileName = function(filename){
return filename.substring(0,filename.length-1) + "i";
}
let loadIndex = function(routeInfo){
routeInfo.indexToOffset = new Uint24Array(STORAGE.readArrayBuffer(getIndexFileName(routeInfo.filename)),4,routeInfo.count);
}
let loadRouteData = function(filename, progressMonitor){
let routeInfo = {};
routeInfo.filename = filename;
let c = {};
let scanOffset = 0;
routeInfo.length = 0;
routeInfo.mirror = false;
let lastSeenWaypoint;
let lastSeenAlt;
let waypoint = {};
let count = 0;
routeInfo.up = 0;
routeInfo.down = 0;
let trfHash = E.CRC32(STORAGE.read(filename));
let size = getFileSize(filename);
let indexFileName = filename.substring(0,filename.length-1) + "i";
//shortest possible entry is 24 characters + linebreak, 3 bytes per entry, 4 bytes hash in front
let indexFileSize = Math.ceil(size/25*3 + 4);
let createIndexFile = !STORAGE.read(indexFileName);
if (!createIndexFile){
let currentHash = new Uint32Array(STORAGE.readArrayBuffer(indexFileName),0,1)[0];
if (currentHash != trfHash){
createIndexFile = true;
}
}
// write hash into index file, will be recreated by this
if (createIndexFile)
writeUint32(indexFileName, trfHash, 0, indexFileSize);
while ((scanOffset = getEntry(filename, scanOffset, waypoint, true)) > 0) {
if (count % 5 == 0) progressMonitor(scanOffset, "Loading", size);
if (lastSeenWaypoint){
routeInfo.length += distance(lastSeenWaypoint, waypoint);
let diff = waypoint.alt - lastSeenAlt;
if (waypoint.alt && lastSeenAlt && diff > 3){
if (lastSeenAlt < waypoint.alt){
routeInfo.up += diff;
} else {
routeInfo.down += diff;
}
}
}
if (createIndexFile) writeUint24(indexFileName, waypoint.fileOffset, 4 + count * 3, indexFileSize);
count++;
lastSeenWaypoint = waypoint;
if (isFinite(waypoint.alt)) lastSeenAlt = waypoint.alt;
waypoint = {};
}
routeInfo.count = count;
loadIndex(routeInfo);
set(routeInfo, 0);
return routeInfo;
};
let hasPrev = function(route, index){
if (isNaN(index)) index = getWaypointIndex(route);
return index > 0;
};
let hasNext = function(route, index, count){
if (!count) count = 1;
if (isNaN(index)) index = route.index;
return getWaypointIndex(route, index) + count < (getRouteIndex(route).length);
};
let getNext = function(route, index, count){
if (!count) count = 1;
if (isNaN(index)) index = getWaypointIndex(route);
index += count;
if (index >= getRouteIndex(route).length || index < 0) return;
let result = {};
getEntry(route.filename, getRouteIndex(route)[getWaypointIndex(route, index)], result);
return result;
};
let get = function(route, index){
if (isNaN(index)) index = getWaypointIndex(route);
if (index >= getRouteIndex(route).length || index < 0) return;
let result = {};
getEntry(route.filename, getRouteIndex(route)[getWaypointIndex(route, index)], result);
return result;
};
let getWaypointIndex = function(route, index){
if (isNaN(index)) index = route.index;
return route.mirror?getRouteIndex(route).length-1-index:index;
};
let setWaypointIndex = function(route, waypointIndex){
if (route.mirror)
route.index = getRouteIndex(route).length - 1 - waypointIndex;
else
route.index = waypointIndex;
};
let getPrev = function(route, index){
return getNext(route, index, -1);
};
let next = function(route){
if (!hasNext(route)) return;
set(route, getWaypointIndex(route)+1);
};
let set = function(route, index){
if (!route) return;
route.index = getWaypointIndex(route, index);
};
let prev = function(route){
if (!hasPrev(route)) return;
set(route, getWaypointIndex(route)-1);
};
let getLast = function(route){
return get(route, getRouteIndex(route).length - 1);
};
let isLast = function(route, index){
if (isNaN(index)) index = route.index;
index = getWaypointIndex(route, index);
return getRouteIndex(route).length - 1 == index;
};
let removeMenu = function(){
let s = WIDGETS.gpstrek.getState();
E.showMenu();
switch(searchNeeded){
case 1:
setClosestWaypoint(s.route, getWaypointIndex(s.route), showProgress);
break;
case 2:
setClosestWaypoint(s.route, 0, showProgress)
break;
}
searchNeeded = 0;
onSwipe(-1,0);
};
let showProgress = function(progress, title, max){
let message = title? title: "Loading";
if (max){
message += " " + E.clip((progress/max*100),0,100).toFixed(0) +"%";
} else {
let dots = progress % 4;
for (let i = 0; i < dots; i++) message += ".";
for (let i = dots; i < 4; i++) message += " ";
}
E.showMessage(message);
};
let handleLoading = function(c){
E.showMenu();
let s = WIDGETS.gpstrek.getState();
s.route = loadRouteData(c, showProgress);
if(SETTINGS.autosearch && searchNeeded < 2) searchNeeded = 2;
s.waypoint = null;
};
let showRouteSelector = function(){
var menu = {
"" : {
back : showRouteMenu,
}
};
STORAGE.list(/\.trf$/).sort().forEach((file)=>{
menu[file] = ()=>{handleLoading(file); showRouteMenu()};
});
E.showMenu(menu);
};
// 1 for complete search, 2 for starting at current waypoint
let searchNeeded = 0;
let showRouteMenu = function(){
var menu = {
"" : {
"title" : "Route",
back : showMenu,
},
"Select file" : showRouteSelector
};
let s = WIDGETS.gpstrek.getState();
if (s.route){
menu.Mirror = {
value: s && s.route && !!s.route.mirror || false,
onchange: v=>{
if (s.route.mirror != v){
s.route.mirror = v;
if(SETTINGS.autosearch)
if (searchNeeded < 2) searchNeeded = 2;
else
setWaypointIndex(s.route, 0);
}
}
};
menu['Select closest waypoint'] = function () {
if (s.currentPos && s.currentPos.lat){
if (searchNeeded < 2) searchNeeded = 2;
removeMenu();
} else {
E.showAlert("No position").then(()=>{E.showMenu(menu);});
}
};
menu['Select closest waypoint (not visited)'] = function () {
if (s.currentPos && s.currentPos.lat){
if (searchNeeded < 1) searchNeeded = 1;
removeMenu();
} else {
E.showAlert("No position").then(()=>{E.showMenu(menu);});
}
};
menu['Select waypoint'] = {
value : getWaypointIndex(s.route),
min:0,max:getRouteIndex(s.route).length-1,step:1,
onchange : v => { setWaypointIndex(s.route, v); }
};
menu['Select waypoint as current position'] = function (){
let c = get(s.route);
s.currentPos.lat = c.lat;
s.currentPos.lon = c.lon;
s.currentPos.alt = c.alt;
removeMenu();
};
}
if (s.route && hasPrev(s.route))
menu['Previous waypoint'] = function() { prev(s.route); removeMenu(); };
if (s.route && hasNext(s.route))
menu['Next waypoint'] = function() { next(s.route); removeMenu(); };
E.showMenu(menu);
};
let showWaypointSelector = function(){
let waypoints = require("waypoints").load();
var menu = {
"" : {
back : showWaypointMenu,
}
};
waypoints.forEach((wp,c)=>{
menu[waypoints[c].name] = function (){
let s = WIDGETS.gpstrek.getState();
s.waypoint = waypoints[c];
s.waypointIndex = c;
s.route = null;
removeMenu();
};
});
E.showMenu(menu);
};
let showCalibrationMenu = function(){
let menu = {
"" : {
"title" : "Calibration",
back : showMenu,
},
"Barometer (GPS)" : ()=>{
let s = WIDGETS.gpstrek.getState();
if (!s.currentPos || isNaN(s.currentPos.alt)){
E.showAlert("No GPS altitude").then(()=>{E.showMenu(menu);});
} else {
s.calibAltDiff = s.altitude - s.currentPos.alt;
E.showAlert("Calibrated Altitude Difference: " + s.calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
}
},
"Barometer (Manual)" : {
value : Math.round(WIDGETS.gpstrek.getState().currentPos && (WIDGETS.gpstrek.getState().currentPos.alt != undefined && isFinite(WIDGETS.gpstrek.getState().currentPos.alt)) ? WIDGETS.gpstrek.getState().currentPos.alt: WIDGETS.gpstrek.getState().altitude),
min:-2000,max: 10000,step:1,
onchange : v => { WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - v; }
},
"Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();},
};
E.showMenu(menu);
};
let showWaypointMenu = function(){
let menu = {
"" : {
"title" : "Waypoint",
back : showMenu,
},
"Select waypoint" : showWaypointSelector,
};
E.showMenu(menu);
};
let showBackgroundMenu = function(){
let menu = {
"" : {
"title" : "Background",
back : showMenu,
},
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); showMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); showMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
};
E.showMenu(menu);
};
let showMenu = function(){
var mainmenu = {
"" : {
"title" : "Main",
back : removeMenu,
},
"Route" : showRouteMenu,
"Waypoint" : showWaypointMenu,
"Background" : showBackgroundMenu,
"Calibration": showCalibrationMenu,
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});},
};
E.showMenu(mainmenu);
Bangle.removeListener("swipe", onSwipe);
Bangle.on("swipe",onSwipe);
};
let switchMenu = function(){
stopDrawing();
showMenu();
};
let stopDrawing = function(){
if (global.drawTimeout) clearTimeout(global.drawTimeout);
global.drawTimeout = undefined;
scheduleDraw = false;
};
let startDrawing = function(){
firstDraw = true;
scheduleDraw = true;
draw();
drawInTimeout();
};
let drawInTimeout = function(){
if (global.drawTimeout) return;
drawTimeout = setTimeout(()=>{
drawTimeout = undefined;
draw();
},Bangle.isLocked()?SETTINGS.refreshLocked:SETTINGS.refresh);
};
let switchNav = function(){
setButtons();
startDrawing();
};
let setSlicesPage = function(change){
page_slices -= change;
if (page_slices >= maxSlicePages){
page_slices = 0;
}
if (page_slices < 0){
page_slices = maxSlicePages - 1;
}
drawInTimeout();
};
let setClosestWaypoint = function(route, startindex, progress, maxDist){
let s = WIDGETS.gpstrek.getState();
let stopSearchAfterFirstMatch = !isFinite(startindex);
if (!startindex) startindex = 0;
if (startindex >= getRouteIndex(s.route).length) startindex = getRouteIndex(s.route).length - 1;
if (startindex < 0) startindex = 0;
if (!s.currentPos.lat){
set(route, startindex);
return;
}
if (!maxDist) maxDist = Number.MAX_VALUE;
let minDist = maxDist;
let mincount = 0;
let currentPos = s.currentPos;
let count = 0;
let wp;
do {
if (progress && (count % 5 == 0)) progress(count+startindex, "Searching", getRouteIndex(route).length);
wp = getNext(route, startindex, count);
if (!wp) break;
let curDist = distance(currentPos, wp);
if (curDist > maxDist) {
break;
}
if (curDist < minDist){
minDist = curDist;
mincount = count;
} else {
if (stopSearchAfterFirstMatch) break;
}
count++;
} while (wp);
set(route, startindex + mincount);
};
const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
const waypointData = {
icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"),
getProgress: function() {
return (getWaypointIndex(WIDGETS.gpstrek.getState().route) + 1) + "/" + getRouteIndex(WIDGETS.gpstrek.getState().route).length;
},
getTarget: function (){
return get(WIDGETS.gpstrek.getState().route);
},
getStart: function (){
return WIDGETS.gpstrek.getState().currentPos;
}
};
const finishData = {
icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="),
getTarget: function (){
let s = WIDGETS.gpstrek.getState();
if (s.route) return getLast(s.route);
if (s.waypoint) return s.waypoint;
},
getStart: function (){
return WIDGETS.gpstrek.getState().currentPos;
}
};
let getSliceHeight = function(number){
return Math.floor(Bangle.appRect.h/SETTINGS.numberOfSlices);
};
let compassSlice = getCompassSlice();
let mapSlice = getMapSlice();
let waypointSlice = getTargetSlice(waypointData);
let finishSlice = getTargetSlice(finishData);
let eleSlice = getDoubleLineSlice("Up","Down",()=>{
let s = WIDGETS.gpstrek.getState();
return loc.distance(s.up,3) + "/" + (s.route ? loc.distance(s.route.up,3):"---");
},()=>{
let s = WIDGETS.gpstrek.getState();
return loc.distance(s.down,3) + "/" + (s.route ? loc.distance(s.route.down,3): "---");
});
let statusSlice = getDoubleLineSlice("Speed","Alt",()=>{
let speed = 0;
let s = WIDGETS.gpstrek.getState();
if (s.currentPos && s.currentPos.speed) speed = s.currentPos.speed;
return loc.speed(speed,2);
},()=>{
let alt = Infinity;
let s = WIDGETS.gpstrek.getState();
if (isFinite(s.altitude)){
alt = isNaN(s.calibAltDiff) ? s.altitude : (s.altitude - s.calibAltDiff);
}
if (s.currentPos && s.currentPos.alt) alt = s.currentPos.alt;
if (isNaN(alt)) return "---";
return loc.distance(alt,3);
});
let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{
return getAveragedCompass() + "°";
},()=>{
let course = "---°";
let s = WIDGETS.gpstrek.getState();
if (s.currentPos && s.currentPos.course) course = s.currentPos.course.toFixed(0) + "°";
return course;
});
let healthSlice = getDoubleLineSlice("Heart","Steps",()=>{
return WIDGETS.gpstrek.getState().bpm || "---";
},()=>{
return isFinite(WIDGETS.gpstrek.getState().steps)? WIDGETS.gpstrek.getState().steps: "---";
});
let system2Slice = getDoubleLineSlice("Bat","Storage",()=>{
return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + (analogRead(D3)*4.2/BAT_FULL).toFixed(2) + "V";
},()=>{
return (STORAGE.getFree()/1024).toFixed(0)+"kB";
});
let systemSlice = getDoubleLineSlice("RAM","WP Cache",()=>{
let ram = process.memory(false);
return ((ram.blocksize * ram.free)/1024).toFixed(0)+"kB";
},()=>{
return cachedOffsets.length?cachedOffsets.length:0;
});
let clear = function() {
g.clearRect(Bangle.appRect);
};
let minimumDistance = Number.MAX_VALUE;
let lastSearch = 0;
let autosearchCounter = 0;
let updateRouting = function() {
let s = WIDGETS.gpstrek.getState();
if (s.mode != MODE_MENU && s.route && s.currentPos.lat) {
let currentDistanceToTarget = distance(s.currentPos,get(s.route));
if (currentDistanceToTarget < minimumDistance){
minimumDistance = currentDistanceToTarget;
}
if (SETTINGS.autosearch && autosearchCounter < SETTINGS.autosearchLimit && !isMapOverview && lastSearch + 15000 < Date.now() && minimumDistance < currentDistanceToTarget - SETTINGS.waypointChangeDist){
Bangle.buzz(1000);
setClosestWaypoint(s.route, getWaypointIndex(s.route), null, SETTINGS.maxDistForAutosearch);
minimumDistance = Number.MAX_VALUE;
lastSearch = Date.now();
autosearchCounter++;
}
while (hasNext(s.route) && distance(s.currentPos,get(s.route)) < SETTINGS.waypointChangeDist) {
next(s.route);
minimumDistance = Number.MAX_VALUE;
autosearchCounter = 0;
}
}
};
let updateSlices = function(){
let s = WIDGETS.gpstrek.getState();
slices = [ compassSlice ];
if (s.currentPos && s.currentPos.lat && s.route && !isLast(s.route)) {
slices.push(waypointSlice);
}
if (s.currentPos && s.currentPos.lat && (s.route || s.waypoint)) {
slices.push(finishSlice);
}
slices.push(eleSlice);
slices.push(statusSlice);
slices.push(status2Slice);
slices.push(healthSlice);
slices.push(systemSlice);
slices.push(system2Slice);
maxSlicePages = Math.ceil(slices.length/SETTINGS.numberOfSlices);
};
let page_slices = 0;
let switchMode = function(targetMode){
switch (targetMode){
case MODE_MENU:
WIDGETS.gpstrek.getState().mode = MODE_MENU;
switchMenu();
break;
case MODE_MAP:
case MODE_SLICES:
WIDGETS.gpstrek.getState().mode = targetMode;
E.showMenu();
switchNav();
break;
}
}
let lastDrawnMode;
let lastDrawnPage;
let drawMap = function(){
g.reset();
mapSlice.draw(g,Bangle.appRect.x,Bangle.appRect.y, Bangle.appRect.h,Bangle.appRect.w);
}
let drawSlices = function(){
let s = WIDGETS.gpstrek.getState();
updateSlices();
let ypos = Bangle.appRect.y;
let force = lastDrawnPage != page_slices || firstDraw;
if (force){
clear();
}
let firstSlice = page_slices*SETTINGS.numberOfSlices;
let sliceHeight = getSliceHeight();
let slicesToDraw = slices.slice(firstSlice,firstSlice + SETTINGS.numberOfSlices);
for (let slice of slicesToDraw) {
g.reset();
if (!slice.refresh || slice.refresh() || force)
slice.draw(g,0,ypos,sliceHeight,g.getWidth());
ypos += sliceHeight+1;
g.drawLine(0,ypos-1,g.getWidth(),ypos-1);
}
lastDrawnPage = page_slices;
}
let draw = function(){
let s = WIDGETS.gpstrek.getState();
if (s.mode == MODE_MENU) return;
if (lastDrawnMode != s.mode)
firstDraw = true;
if (firstDraw) {
g.clear();
Bangle.drawWidgets();
}
switch (s.mode) {
case MODE_MAP:
drawMap();
break;
case MODE_SLICES:
drawSlices();
break;
}
firstDraw = false;
lastDrawnMode = s.mode;
if (scheduleDraw){
drawInTimeout();
}
};
clear();
switchMode(MODE_SLICES);
setInterval(updateRouting, 500);
}