mirror of https://github.com/espruino/BangleApps
1381 lines
46 KiB
JavaScript
1381 lines
46 KiB
JavaScript
let Layout = require('Layout');
|
|
|
|
let Caret = require("heatshrink").decompress(atob("hEUgMAsFgmEwjEYhkMg0GAYIHBBYIPBgAA=="));
|
|
|
|
let ScreenWidth = g.getWidth(), CenterX;
|
|
let ScreenHeight = g.getHeight(), CenterY, outerRadius;
|
|
|
|
Bangle.loadWidgets();
|
|
|
|
/**** updateClockFaceSize ****/
|
|
|
|
function updateClockFaceSize () {
|
|
CenterX = ScreenWidth/2;
|
|
CenterY = ScreenHeight/2;
|
|
|
|
outerRadius = Math.min(CenterX,CenterY);
|
|
|
|
if (global.WIDGETS == null) { return; }
|
|
|
|
let WidgetLayouts = {
|
|
tl:{ x:0, y:0, Direction:0 },
|
|
tr:{ x:ScreenWidth-1, y:0, Direction:1 },
|
|
bl:{ x:0, y:ScreenHeight-24, Direction:0 },
|
|
br:{ x:ScreenWidth-1, y:ScreenHeight-24, Direction:1 }
|
|
};
|
|
|
|
for (let Widget of WIDGETS) {
|
|
let WidgetLayout = WidgetLayouts[Widget.area]; // reference, not copy!
|
|
if (WidgetLayout == null) { continue; }
|
|
|
|
Widget.x = WidgetLayout.x - WidgetLayout.Direction * Widget.width;
|
|
Widget.y = WidgetLayout.y;
|
|
|
|
WidgetLayout.x += Widget.width * (1-2*WidgetLayout.Direction);
|
|
}
|
|
|
|
let x,y, dx,dy;
|
|
let cx = CenterX, cy = CenterY, r = outerRadius, r2 = r*r;
|
|
|
|
x = WidgetLayouts.tl.x; y = WidgetLayouts.tl.y+24; dx = x - cx; dy = y - cy;
|
|
if (dx*dx + dy*dy < r2) {
|
|
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy-24);
|
|
}
|
|
|
|
x = WidgetLayouts.tr.x; y = WidgetLayouts.tr.y+24; dx = x - cx; dy = y - cy;
|
|
if (dx*dx + dy*dy < r2) {
|
|
cy = CenterY + 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy-24);
|
|
}
|
|
|
|
x = WidgetLayouts.bl.x; y = WidgetLayouts.bl.y; dx = x - cx; dy = y - cy;
|
|
if (dx*dx + dy*dy < r2) {
|
|
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy);
|
|
}
|
|
|
|
x = WidgetLayouts.br.x; y = WidgetLayouts.br.y; dx = x - cx; dy = y - cy;
|
|
if (dx*dx + dy*dy < r2) {
|
|
cy = CenterY - 12; dy = y - cy; r2 = dx*dx + dy*dy; r = Math.min(Math.sqrt(r2),cy);
|
|
}
|
|
|
|
CenterX = cx; CenterY = cy; outerRadius = r - 4;
|
|
}
|
|
|
|
updateClockFaceSize();
|
|
|
|
/**** custom version of Bangle.drawWidgets (does not clear the widget areas) ****/
|
|
|
|
Bangle.drawWidgets = function () {
|
|
var w = g.getWidth(), h = g.getHeight();
|
|
|
|
var pos = {
|
|
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
|
|
tr:{x:w-1, y:0, r:1, c:0},
|
|
bl:{x:0, y:h-24, r:0, c:0},
|
|
br:{x:w-1, y:h-24, r:1, c:0}
|
|
};
|
|
|
|
if (global.WIDGETS) {
|
|
for (var wd of WIDGETS) {
|
|
var p = pos[wd.area];
|
|
if (!p) continue;
|
|
|
|
wd.x = p.x - p.r*wd.width;
|
|
wd.y = p.y;
|
|
|
|
p.x += wd.width*(1-2*p.r);
|
|
p.c++;
|
|
}
|
|
|
|
g.reset(); // also loads the current theme
|
|
|
|
if (pos.tl.c || pos.tr.c) {
|
|
g.setClipRect(0,h-24,w-1,h-1);
|
|
g.reset(); // also (re)loads the current theme
|
|
}
|
|
|
|
if (pos.bl.c || pos.br.c) {
|
|
g.setClipRect(0,h-24,w-1,h-1);
|
|
g.reset(); // also (re)loads the current theme
|
|
}
|
|
|
|
try {
|
|
for (wd of WIDGETS) {
|
|
g.clearRect(wd.x,wd.y, wd.x+wd.width-1,23);
|
|
wd.draw(wd);
|
|
}
|
|
} catch (e) { print(e); }
|
|
}
|
|
};
|
|
|
|
/**** EventConsumerAtPoint ****/
|
|
|
|
let activeLayout;
|
|
|
|
function EventConsumerAtPoint (HandlerName, x,y) {
|
|
let Layout = (activeLayout || {}).l;
|
|
if (Layout == null) { return; }
|
|
|
|
function ConsumerIn (Control) {
|
|
if (
|
|
(x < Control.x) || (x >= Control.x + Control.w) ||
|
|
(y < Control.y) || (y >= Control.y + Control.h)
|
|
) { return undefined; }
|
|
|
|
if (typeof Control[HandlerName] === 'function') { return Control; }
|
|
|
|
if (Control.c != null) {
|
|
let ControlList = Control.c;
|
|
for (let i = 0, l = ControlList.length; i < l; i++) {
|
|
let Consumer = ConsumerIn(ControlList[i]);
|
|
if (Consumer != null) { return Consumer; }
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
return ConsumerIn(Layout);
|
|
}
|
|
|
|
/**** dispatchTouchEvent ****/
|
|
|
|
function dispatchTouchEvent (DefaultHandler) {
|
|
function handleTouchEvent (Button, xy) {
|
|
if (activeLayout == null) {
|
|
if (typeof DefaultHandler === 'function') {
|
|
DefaultHandler();
|
|
}
|
|
} else {
|
|
let Control = EventConsumerAtPoint('onTouch', xy.x,xy.y);
|
|
if (Control != null) {
|
|
Control.onTouch(Control, Button, xy);
|
|
}
|
|
}
|
|
}
|
|
Bangle.on('touch',handleTouchEvent);
|
|
}
|
|
dispatchTouchEvent();
|
|
|
|
/**** dispatchStrokeEvent ****/
|
|
|
|
function dispatchStrokeEvent (DefaultHandler) {
|
|
function handleStrokeEvent (Coordinates) {
|
|
if (activeLayout == null) {
|
|
if (typeof DefaultHandler === 'function') {
|
|
DefaultHandler();
|
|
}
|
|
} else {
|
|
let Control = EventConsumerAtPoint('onStroke', Coordinates.xy[0],Coordinates.xy[1]);
|
|
if (Control != null) {
|
|
Control.onStroke(Control, Coordinates);
|
|
}
|
|
}
|
|
}
|
|
Bangle.on('stroke',handleStrokeEvent);
|
|
}
|
|
dispatchStrokeEvent();
|
|
/**** Label ****/
|
|
|
|
function Label (Text, Options) {
|
|
function renderLabel (Details) {
|
|
let x = Details.x, xAlignment = Details.halign || 0;
|
|
let y = Details.y, yAlignment = Details.valign || 0;
|
|
|
|
let Width = Details.w, halfWidth = Width/2;
|
|
let Height = Details.h, halfHeight = Height/2;
|
|
|
|
let Border = Details.border || 0, BorderColor = Details.BorderColor;
|
|
let Padding = Details.pad || 0;
|
|
let Hilite = Details.hilite || false;
|
|
let bold = Details.bold ? 1 : 0;
|
|
|
|
if (Hilite || (Details.bgCol != null)) {
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol);
|
|
g.clearRect(x,y, x + Width-1,y + Height-1);
|
|
}
|
|
|
|
if ((Border > 0) && (BorderColor !== null)) {// draw border of layout cell
|
|
g.setColor(BorderColor || Details.col || g.theme.fg);
|
|
|
|
switch (Border) {
|
|
case 1: g.drawRect(x,y, x+Width-1,y+Height-1); break;
|
|
case 2: g.drawRect(x,y, x+Width-1,y+Height-1);
|
|
g.drawRect(x+1,y+1, x+Width-2,y+Height-2); break;
|
|
default: g.fillPoly([
|
|
x,y, x+Width,y, x+Width,y+Height, x,y+Height, x,y,
|
|
x+Border,y+Border, x+Border,y+Height-Border,
|
|
x+Width-Border,y+Height-Border, x+Width-Border,y+Border,
|
|
x+Border,y+Border
|
|
]);
|
|
}
|
|
}
|
|
|
|
g.setClipRect(
|
|
x+Border+Padding,y+Border+Padding,
|
|
x + Width-Border-Padding-1,y + Height-Border-Padding-1
|
|
);
|
|
|
|
x += halfWidth + xAlignment*(halfWidth - Border - Padding);
|
|
y += halfHeight + yAlignment*(halfHeight - Border - Padding);
|
|
|
|
g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg);
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg);
|
|
|
|
if (Details.font != null) { g.setFont(Details.font); }
|
|
g.setFontAlign(xAlignment,yAlignment);
|
|
|
|
g.drawString(Details.label, x,y);
|
|
if (bold !== 0) {
|
|
g.drawString(Details.label, x+1,y);
|
|
g.drawString(Details.label, x,y+1);
|
|
g.drawString(Details.label, x+1,y+1);
|
|
}
|
|
}
|
|
|
|
let Result = Object.assign((
|
|
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
|
), {
|
|
type:'custom', render:renderLabel, label:Text || ''
|
|
});
|
|
let Border = Result.border || 0;
|
|
let Padding = Result.pad || 0;
|
|
|
|
let TextMetrics;
|
|
if (! Result.width || ! Result.height) {
|
|
if (Result.font == null) {
|
|
Result.font = g.getFont();
|
|
} else {
|
|
g.setFont(Result.font);
|
|
}
|
|
TextMetrics = g.stringMetrics(Result.label);
|
|
}
|
|
|
|
if (Result.col == null) { Result.col = g.getColor(); }
|
|
if (Result.bgCol == null) { Result.bgCol = g.getBgColor(); }
|
|
|
|
Result.width = Result.width || TextMetrics.width + 2*Border + 2*Padding;
|
|
Result.height = Result.height || TextMetrics.height + 2*Border + 2*Padding;
|
|
return Result;
|
|
}
|
|
|
|
/**** Image ****/
|
|
|
|
function Image (Image, Options) {
|
|
function renderImage (Details) {
|
|
let x = Details.x, xAlignment = Details.halign || 0;
|
|
let y = Details.y, yAlignment = Details.valign || 0;
|
|
|
|
let Width = Details.w, halfWidth = Width/2 - Details.ImageWidth/2;
|
|
let Height = Details.h, halfHeight = Height/2 - Details.ImageHeight/2;
|
|
|
|
let Border = Details.border || 0, BorderColor = Details.BorderColor;
|
|
let Padding = Details.pad || 0;
|
|
let Hilite = Details.hilite || false;
|
|
|
|
if (Hilite || (Details.bgCol != null)) {
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol);
|
|
g.clearRect(x,y, x + Width-1,y + Height-1);
|
|
}
|
|
|
|
if ((Border > 0) && (BorderColor !== null)) {// draw border of layout cell
|
|
g.setColor(BorderColor || Details.col || g.theme.fg);
|
|
|
|
switch (Border) {
|
|
case 1: g.drawRect(x,y, x+Width-1,y+Height-1); break;
|
|
case 2: g.drawRect(x,y, x+Width-1,y+Height-1);
|
|
g.drawRect(x+1,y+1, x+Width-2,y+Height-2); break;
|
|
default: g.fillPoly([
|
|
x,y, x+Width,y, x+Width,y+Height, x,y+Height, x,y,
|
|
x+Border,y+Border, x+Border,y+Height-Border,
|
|
x+Width-Border,y+Height-Border, x+Width-Border,y+Border,
|
|
x+Border,y+Border
|
|
]);
|
|
}
|
|
}
|
|
|
|
g.setClipRect(
|
|
x+Border+Padding,y+Border+Padding,
|
|
x + Width-Border-Padding-1,y + Height-Border-Padding-1
|
|
);
|
|
|
|
x += halfWidth + xAlignment*(halfWidth - Border - Padding);
|
|
y += halfHeight + yAlignment*(halfHeight - Border - Padding);
|
|
|
|
if ('rotate' in Details) { // "rotate" centers image at x,y!
|
|
x += Details.ImageWidth/2;
|
|
y += Details.ImageHeight/2;
|
|
}
|
|
|
|
g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg);
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg);
|
|
|
|
g.drawImage(Image, x,y, Details.ImageOptions);
|
|
}
|
|
|
|
let Result = Object.assign((
|
|
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
|
), {
|
|
type:'custom', render:renderImage, Image:Image
|
|
});
|
|
let ImageMetrics = g.imageMetrics(Image);
|
|
let Scale = Result.scale || 1;
|
|
let Border = Result.border || 0;
|
|
let Padding = Result.pad || 0;
|
|
|
|
Result.ImageWidth = Scale * ImageMetrics.width;
|
|
Result.ImageHeight = Scale * ImageMetrics.height;
|
|
|
|
if (('rotate' in Result) || ('scale' in Result) || ('frame' in Result)) {
|
|
Result.ImageOptions = {};
|
|
if ('rotate' in Result) { Result.ImageOptions.rotate = Result.rotate; }
|
|
if ('scale' in Result) { Result.ImageOptions.scale = Result.scale; }
|
|
if ('frame' in Result) { Result.ImageOptions.frame = Result.frame; }
|
|
}
|
|
|
|
Result.width = Result.width || Result.ImageWidth + 2*Border + 2*Padding;
|
|
Result.height = Result.height || Result.ImageHeight + 2*Border + 2*Padding;
|
|
return Result;
|
|
}
|
|
|
|
/**** Drawable ****/
|
|
|
|
function Drawable (Callback, Options) {
|
|
function renderDrawable (Details) {
|
|
let x = Details.x, xAlignment = Details.halign || 0;
|
|
let y = Details.y, yAlignment = Details.valign || 0;
|
|
|
|
let Width = Details.w, DrawableWidth = Details.DrawableWidth || Width;
|
|
let Height = Details.h, DrawableHeight = Details.DrawableHeight || Height;
|
|
|
|
let halfWidth = Width/2 - DrawableWidth/2;
|
|
let halfHeight = Height/2 - DrawableHeight/2;
|
|
|
|
let Border = Details.border || 0, BorderColor = Details.BorderColor;
|
|
let Padding = Details.pad || 0;
|
|
let Hilite = Details.hilite || false;
|
|
|
|
if (Hilite || (Details.bgCol != null)) {
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol);
|
|
g.clearRect(x,y, x + Width-1,y + Height-1);
|
|
}
|
|
|
|
if ((Border > 0) && (BorderColor !== null)) {// draw border of layout cell
|
|
g.setColor(BorderColor || Details.col || g.theme.fg);
|
|
|
|
switch (Border) {
|
|
case 1: g.drawRect(x,y, x+Width-1,y+Height-1); break;
|
|
case 2: g.drawRect(x,y, x+Width-1,y+Height-1);
|
|
g.drawRect(x+1,y+1, x+Width-2,y+Height-2); break;
|
|
default: g.fillPoly([
|
|
x,y, x+Width,y, x+Width,y+Height, x,y+Height, x,y,
|
|
x+Border,y+Border, x+Border,y+Height-Border,
|
|
x+Width-Border,y+Height-Border, x+Width-Border,y+Border,
|
|
x+Border,y+Border
|
|
]);
|
|
}
|
|
}
|
|
|
|
let DrawableX = x + halfWidth + xAlignment*(halfWidth - Border - Padding);
|
|
let DrawableY = y + halfHeight + yAlignment*(halfHeight - Border - Padding);
|
|
|
|
g.setClipRect(
|
|
Math.max(x+Border+Padding,DrawableX),
|
|
Math.max(y+Border+Padding,DrawableY),
|
|
Math.min(x+Width -Border-Padding,DrawableX+DrawableWidth)-1,
|
|
Math.min(y+Height-Border-Padding,DrawableY+DrawableHeight)-1
|
|
);
|
|
|
|
g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg);
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg);
|
|
|
|
Callback(DrawableX,DrawableY, DrawableWidth,DrawableHeight, Details);
|
|
}
|
|
|
|
let Result = Object.assign((
|
|
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
|
), {
|
|
type:'custom', render:renderDrawable, cb:Callback
|
|
});
|
|
let DrawableWidth = Result.DrawableWidth || 10;
|
|
let DrawableHeight = Result.DrawableHeight || 10;
|
|
|
|
let Border = Result.border || 0;
|
|
let Padding = Result.pad || 0;
|
|
|
|
Result.width = Result.width || DrawableWidth + 2*Border + 2*Padding;
|
|
Result.height = Result.height || DrawableHeight + 2*Border + 2*Padding;
|
|
return Result;
|
|
}
|
|
|
|
if (g.drawRoundedRect == null) {
|
|
g.drawRoundedRect = function drawRoundedRect (x1,y1, x2,y2, r) {
|
|
let x,y;
|
|
if (x1 > x2) { x = x1; x1 = x2; x2 = x; }
|
|
if (y1 > y2) { y = y1; y1 = y2; y2 = y; }
|
|
|
|
r = Math.min(r || 0, (x2-x1)/2, (y2-y1)/2);
|
|
|
|
let cx1 = x1+r, cx2 = x2-r;
|
|
let cy1 = y1+r, cy2 = y2-r;
|
|
|
|
this.drawLine(cx1,y1, cx2,y1);
|
|
this.drawLine(cx1,y2, cx2,y2);
|
|
this.drawLine(x1,cy1, x1,cy2);
|
|
this.drawLine(x2,cy1, x2,cy2);
|
|
|
|
x = r; y = 0;
|
|
|
|
let dx,dy, Error = 0;
|
|
while (y <= x) {
|
|
dy = 1 + 2*y; y++; Error -= dy;
|
|
if (Error < 0) {
|
|
dx = 1 - 2*x; x--; Error -= dx;
|
|
}
|
|
|
|
this.setPixel(cx1 - x, cy1 - y); this.setPixel(cx1 - y, cy1 - x);
|
|
this.setPixel(cx2 + x, cy1 - y); this.setPixel(cx2 + y, cy1 - x);
|
|
this.setPixel(cx2 + x, cy2 + y); this.setPixel(cx2 + y, cy2 + x);
|
|
this.setPixel(cx1 - x, cy2 + y); this.setPixel(cx1 - y, cy2 + x);
|
|
}
|
|
};
|
|
}
|
|
|
|
if (g.fillRoundedRect == null) {
|
|
g.fillRoundedRect = function fillRoundedRect (x1,y1, x2,y2, r) {
|
|
let x,y;
|
|
if (x1 > x2) { x = x1; x1 = x2; x2 = x; }
|
|
if (y1 > y2) { y = y1; y1 = y2; y2 = y; }
|
|
|
|
r = Math.min(r || 0, (x2-x1)/2, (y2-y1)/2);
|
|
|
|
let cx1 = x1+r, cx2 = x2-r;
|
|
let cy1 = y1+r, cy2 = y2-r;
|
|
|
|
this.fillRect(x1,cy1, x2,cy2);
|
|
|
|
x = r; y = 0;
|
|
|
|
let dx,dy, Error = 0;
|
|
while (y <= x) {
|
|
dy = 1 + 2*y; y++; Error -= dy;
|
|
if (Error < 0) {
|
|
dx = 1 - 2*x; x--; Error -= dx;
|
|
}
|
|
|
|
this.drawLine(cx1 - x, cy1 - y, cx2 + x, cy1 - y);
|
|
this.drawLine(cx1 - y, cy1 - x, cx2 + y, cy1 - x);
|
|
this.drawLine(cx1 - x, cy2 + y, cx2 + x, cy2 + y);
|
|
this.drawLine(cx1 - y, cy2 + x, cx2 + y, cy2 + x);
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
/**** Button ****/
|
|
|
|
function Button (Text, Options) {
|
|
function renderButton (Details) {
|
|
let x = Details.x, Width = Details.w, halfWidth = Width/2;
|
|
let y = Details.y, Height = Details.h, halfHeight = Height/2;
|
|
|
|
let Padding = Details.pad || 0;
|
|
let Hilite = Details.hilite || false;
|
|
|
|
if (Details.bgCol != null) {
|
|
g.setBgColor(Details.bgCol);
|
|
g.clearRect(x,y, x + Width-1,y + Height-1);
|
|
}
|
|
|
|
if (Hilite) {
|
|
g.setColor(g.theme.bgH); // no typo!
|
|
g.fillRoundedRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1,8);
|
|
}
|
|
|
|
g.setColor (Hilite ? g.theme.fgH : Details.col || g.theme.fg);
|
|
g.setBgColor(Hilite ? g.theme.bgH : Details.bgCol || g.theme.bg);
|
|
|
|
if (Details.font != null) { g.setFont(Details.font); }
|
|
g.setFontAlign(0,0);
|
|
|
|
g.drawRoundedRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1,8);
|
|
|
|
g.setClipRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1);
|
|
|
|
g.drawString(Details.label, x+halfWidth,y+halfHeight);
|
|
g.drawString(Details.label, x+halfWidth+1,y+halfHeight);
|
|
g.drawString(Details.label, x+halfWidth,y+halfHeight+1);
|
|
g.drawString(Details.label, x+halfWidth+1,y+halfHeight+1);
|
|
}
|
|
|
|
let Result = Object.assign((
|
|
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
|
), {
|
|
type:'custom', render:renderButton, label:Text || 'Tap'
|
|
});
|
|
let Padding = Result.pad || 0;
|
|
|
|
let TextMetrics;
|
|
if (! Result.width || ! Result.height) {
|
|
if (Result.font == null) {
|
|
Result.font = g.getFont();
|
|
} else {
|
|
g.setFont(Result.font);
|
|
}
|
|
TextMetrics = g.stringMetrics(Result.label);
|
|
}
|
|
|
|
Result.width = Result.width || TextMetrics.width + 2*10 + 2*Padding;
|
|
Result.height = Result.height || TextMetrics.height + 2*5 + 2*Padding;
|
|
return Result;
|
|
}
|
|
|
|
const Checkbox_checked = require("heatshrink").decompress(atob("ikUgMf/+GgEGoEAlEAgOAgEYsFhw8OjE54OB/EYh4OB+EYj+BwecjFw8OGg0YDocUgECsEAsP//A"));
|
|
const Checkbox_unchecked = require("heatshrink").decompress(atob("ikUgMf/+GgEGoEAlEAgOAgEYAjkUgECsEAsP//A="));
|
|
|
|
/**** Checkbox ****/
|
|
|
|
function Checkbox (Options) {
|
|
function renderCheckbox (Details) {
|
|
let x = Details.x, xAlignment = Details.halign || 0;
|
|
let y = Details.y, yAlignment = Details.valign || 0;
|
|
|
|
let Width = Details.w, halfWidth = Width/2 - 10;
|
|
let Height = Details.h, halfHeight = Height/2 - 10;
|
|
|
|
let Padding = Details.pad || 0;
|
|
|
|
if (Details.bgCol != null) {
|
|
g.setBgColor(Details.bgCol);
|
|
g.clearRect(x,y, x + Width-1,y + Height-1);
|
|
}
|
|
|
|
x += halfWidth + xAlignment*(halfWidth - Padding);
|
|
y += halfHeight + yAlignment*(halfHeight - Padding);
|
|
|
|
g.setColor (Details.col || g.theme.fg);
|
|
g.setBgColor(Details.bgCol || g.theme.bg);
|
|
|
|
g.drawImage(
|
|
Details.checked ? Checkbox_checked : Checkbox_unchecked, x,y
|
|
);
|
|
}
|
|
|
|
let Result = Object.assign((
|
|
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
|
), {
|
|
type:'custom', render:renderCheckbox, onTouch:toggleCheckbox
|
|
});
|
|
let Padding = Result.pad || 0;
|
|
|
|
Result.width = Result.width || 20 + 2*Padding;
|
|
Result.height = Result.height || 20 + 2*Padding;
|
|
|
|
if (Result.checked == null) { Result.checked = false; }
|
|
return Result;
|
|
}
|
|
|
|
/* private */ function toggleCheckbox (Control) {
|
|
g.reset();
|
|
|
|
Control.checked = ! Control.checked;
|
|
Control.render(Control);
|
|
|
|
if (typeof Control.onChange === 'function') {
|
|
Control.onChange(Control);
|
|
}
|
|
}
|
|
|
|
/**** toggleInnerCheckbox ****/
|
|
|
|
/* export */ function toggleInnerCheckbox (Control) {
|
|
if (Control.c == null) {
|
|
if (('checked' in Control) && ! ('GroupName' in Control)) {
|
|
toggleCheckbox(Control);
|
|
return true;
|
|
}
|
|
} else {
|
|
let ControlList = Control.c;
|
|
for (let i = 0, l = ControlList.length; i < l; i++) {
|
|
let done = toggleInnerCheckbox(ControlList[i]);
|
|
if (done) { return true; }
|
|
}
|
|
}
|
|
}
|
|
|
|
const Radiobutton_checked = require("heatshrink").decompress(atob("ikUgMB/EAsFgjEBwUAgkggFEgECoEAlEPgOB/EYj+BAgmA+EUCYciDodBwEYg0GgEfwA"));
|
|
const Radiobutton_unchecked = require("heatshrink").decompress(atob("ikUgMB/EAsFgjEBwUAgkggFEgECoEAlEAgOAgEYAhEUCYciDodBwEYg0GgEfwAA="));
|
|
|
|
/**** Radiobutton ****/
|
|
|
|
function Radiobutton (Options) {
|
|
function renderRadiobutton (Details) {
|
|
let x = Details.x, xAlignment = Details.halign || 0;
|
|
let y = Details.y, yAlignment = Details.valign || 0;
|
|
|
|
let Width = Details.w, halfWidth = Width/2 - 10;
|
|
let Height = Details.h, halfHeight = Height/2 - 10;
|
|
|
|
let Padding = Details.pad || 0;
|
|
|
|
if (Details.bgCol != null) {
|
|
g.setBgColor(Details.bgCol);
|
|
g.clearRect(x,y, x + Width-1,y + Height-1);
|
|
}
|
|
|
|
x += halfWidth + xAlignment*(halfWidth - Padding);
|
|
y += halfHeight + yAlignment*(halfHeight - Padding);
|
|
|
|
g.setColor (Details.col || g.theme.fg);
|
|
g.setBgColor(Details.bgCol || g.theme.bg);
|
|
|
|
g.drawImage(
|
|
Details.checked ? Radiobutton_checked : Radiobutton_unchecked, x,y
|
|
);
|
|
}
|
|
|
|
let Result = Object.assign((
|
|
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
|
|
), {
|
|
type:'custom', render:renderRadiobutton, onTouch:checkRadiobutton
|
|
});
|
|
let Padding = Result.pad || 0;
|
|
|
|
Result.width = Result.width || 20 + 2*Padding;
|
|
Result.height = Result.height || 20 + 2*Padding;
|
|
|
|
if (Result.checked == null) { Result.checked = false; }
|
|
return Result;
|
|
}
|
|
|
|
/* private */ function checkRadiobutton (Control) {
|
|
if (! Control.checked) {
|
|
uncheckRadiobuttonsIn((activeLayout || {}).l,Control.GroupName);
|
|
toggleRadiobutton(Control);
|
|
|
|
if (typeof Control.onChange === 'function') {
|
|
Control.onChange(Control);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* private */ function toggleRadiobutton (Control) {
|
|
g.reset();
|
|
|
|
Control.checked = ! Control.checked;
|
|
Control.render(Control);
|
|
}
|
|
|
|
/* private */ function uncheckRadiobuttonsIn (Control,GroupName) {
|
|
if ((Control == null) || (GroupName == null)) { return; }
|
|
|
|
if (Control.c == null) {
|
|
if (('checked' in Control) && (Control.GroupName === GroupName)) {
|
|
if (Control.checked) { toggleRadiobutton(Control); }
|
|
}
|
|
} else {
|
|
let ControlList = Control.c;
|
|
for (let i = 0, l = ControlList.length; i < l; i++) {
|
|
uncheckRadiobuttonsIn(ControlList[i],GroupName);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**** checkInnerRadiobutton ****/
|
|
|
|
/* export */ function checkInnerRadiobutton (Control) {
|
|
if (Control.c == null) {
|
|
if (('checked' in Control) && ('GroupName' in Control)) {
|
|
checkRadiobutton(Control);
|
|
return true;
|
|
}
|
|
} else {
|
|
let ControlList = Control.c;
|
|
for (let i = 0, l = ControlList.length; i < l; i++) {
|
|
let done = checkInnerRadiobutton(ControlList[i]);
|
|
if (done) { return true; }
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
let Theme = g.theme;
|
|
g.clear(true);
|
|
|
|
/**** Settings ****/
|
|
|
|
let Settings;
|
|
|
|
function readSettings () {
|
|
Settings = Object.assign({},
|
|
{
|
|
Face:'1-12', colored:true,
|
|
Hands:'rounded', withSeconds:true,
|
|
Foreground:'Theme', Background:'Theme', Seconds:'#FF0000'
|
|
},
|
|
require('Storage').readJSON('configurable_clock.json', true) || {}
|
|
);
|
|
|
|
prepareTransformedPolygon();
|
|
}
|
|
|
|
function saveSettings () {
|
|
require('Storage').writeJSON('configurable_clock.json', Settings);
|
|
prepareTransformedPolygon();
|
|
}
|
|
|
|
function prepareTransformedPolygon () {
|
|
switch (Settings.Hands) {
|
|
case 'simple': transformedPolygon = new Array(simpleHourHandPolygon.length); break;
|
|
case 'rounded': transformedPolygon = new Array(roundedHandPolygon.length); break;
|
|
case 'hollow': transformedPolygon = new Array(hollowHandPolygon.length);
|
|
}
|
|
}
|
|
|
|
//readSettings(); // not yet
|
|
|
|
|
|
/**** Hands ****/
|
|
|
|
let HourHandLength = outerRadius * 0.5;
|
|
let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2;
|
|
|
|
let MinuteHandLength = outerRadius * 0.8;
|
|
let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2;
|
|
|
|
let SecondHandLength = outerRadius * 0.9;
|
|
let SecondHandOffset = 10;
|
|
|
|
let twoPi = 2*Math.PI, deg2rad = Math.PI/180;
|
|
let Pi = Math.PI;
|
|
let halfPi = Math.PI/2;
|
|
|
|
let sin = Math.sin, cos = Math.cos;
|
|
|
|
/**** simple Hands ****/
|
|
|
|
let simpleHourHandPolygon = [
|
|
-halfHourHandWidth,halfHourHandWidth,
|
|
-halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
|
halfHourHandWidth,halfHourHandWidth-HourHandLength,
|
|
halfHourHandWidth,halfHourHandWidth,
|
|
];
|
|
|
|
let simpleMinuteHandPolygon = [
|
|
-halfMinuteHandWidth,halfMinuteHandWidth,
|
|
-halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
|
halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
|
|
halfMinuteHandWidth,halfMinuteHandWidth,
|
|
];
|
|
|
|
|
|
/**** rounded Hands ****/
|
|
|
|
let outerBoltRadius = halfHourHandWidth + 2;
|
|
let innerBoltRadius = outerBoltRadius - 4;
|
|
let roundedHandOffset = outerBoltRadius + 4;
|
|
|
|
let sine = [0, sin(30*deg2rad), sin(60*deg2rad), 1];
|
|
|
|
let roundedHandPolygon = [
|
|
-sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3],
|
|
sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0],
|
|
sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3],
|
|
-sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0],
|
|
];
|
|
|
|
let roundedHourHandPolygon = new Array(roundedHandPolygon.length);
|
|
for (let i = 0, l = roundedHandPolygon.length; i < l; i+=2) {
|
|
roundedHourHandPolygon[i] = halfHourHandWidth*roundedHandPolygon[i];
|
|
roundedHourHandPolygon[i+1] = halfHourHandWidth*roundedHandPolygon[i+1];
|
|
if (i < l/2) { roundedHourHandPolygon[i+1] -= HourHandLength; }
|
|
if (i > l/2) { roundedHourHandPolygon[i+1] += roundedHandOffset; }
|
|
}
|
|
let roundedMinuteHandPolygon = new Array(roundedHandPolygon.length);
|
|
for (let i = 0, l = roundedHandPolygon.length; i < l; i+=2) {
|
|
roundedMinuteHandPolygon[i] = halfMinuteHandWidth*roundedHandPolygon[i];
|
|
roundedMinuteHandPolygon[i+1] = halfMinuteHandWidth*roundedHandPolygon[i+1];
|
|
if (i < l/2) { roundedMinuteHandPolygon[i+1] -= MinuteHandLength; }
|
|
if (i > l/2) { roundedMinuteHandPolygon[i+1] += roundedHandOffset; }
|
|
}
|
|
|
|
|
|
/**** hollow Hands ****/
|
|
|
|
let BoltRadius = 3;
|
|
let hollowHandOffset = BoltRadius + 15;
|
|
|
|
let hollowHandPolygon = [
|
|
-sine[3],-sine[0], -sine[2],-sine[1], -sine[1],-sine[2], -sine[0],-sine[3],
|
|
sine[0],-sine[3], sine[1],-sine[2], sine[2],-sine[1], sine[3],-sine[0],
|
|
sine[3], sine[0], sine[2], sine[1], sine[1], sine[2], sine[0], sine[3],
|
|
0,0,
|
|
-sine[0], sine[3], -sine[1], sine[2], -sine[2], sine[1], -sine[3], sine[0]
|
|
];
|
|
|
|
let hollowHourHandPolygon = new Array(hollowHandPolygon.length);
|
|
for (let i = 0, l = hollowHandPolygon.length; i < l; i+=2) {
|
|
hollowHourHandPolygon[i] = halfHourHandWidth*hollowHandPolygon[i];
|
|
hollowHourHandPolygon[i+1] = halfHourHandWidth*hollowHandPolygon[i+1];
|
|
if (i < l/2) { hollowHourHandPolygon[i+1] -= HourHandLength; }
|
|
if (i > l/2) { hollowHourHandPolygon[i+1] -= hollowHandOffset; }
|
|
}
|
|
hollowHourHandPolygon[25] = -BoltRadius;
|
|
|
|
let hollowMinuteHandPolygon = new Array(hollowHandPolygon.length);
|
|
for (let i = 0, l = hollowHandPolygon.length; i < l; i+=2) {
|
|
hollowMinuteHandPolygon[i] = halfMinuteHandWidth*hollowHandPolygon[i];
|
|
hollowMinuteHandPolygon[i+1] = halfMinuteHandWidth*hollowHandPolygon[i+1];
|
|
if (i < l/2) { hollowMinuteHandPolygon[i+1] -= MinuteHandLength; }
|
|
if (i > l/2) { hollowMinuteHandPolygon[i+1] -= hollowHandOffset; }
|
|
}
|
|
hollowMinuteHandPolygon[25] = -BoltRadius;
|
|
|
|
|
|
|
|
/**** transform polygon ****/
|
|
|
|
let transformedPolygon;
|
|
|
|
function transformPolygon (originalPolygon, OriginX,OriginY, Phi) {
|
|
let sPhi = sin(Phi), cPhi = cos(Phi), x,y;
|
|
|
|
for (let i = 0, l = originalPolygon.length; i < l; i+=2) {
|
|
x = originalPolygon[i];
|
|
y = originalPolygon[i+1];
|
|
|
|
transformedPolygon[i] = OriginX + x*cPhi + y*sPhi;
|
|
transformedPolygon[i+1] = OriginY + x*sPhi - y*cPhi;
|
|
}
|
|
}
|
|
|
|
/**** refreshClock ****/
|
|
|
|
let Timer;
|
|
function refreshClock () {
|
|
activeLayout = null;
|
|
|
|
g.setTheme({
|
|
fg:(Settings.Foreground === 'Theme' ? Theme.fg : Settings.Foreground || '#000000'),
|
|
bg:(Settings.Background === 'Theme' ? Theme.bg : Settings.Background || '#FFFFFF')
|
|
});
|
|
g.clear(true); // also installs the current theme
|
|
|
|
Bangle.drawWidgets();
|
|
renderClock();
|
|
|
|
let Period = (Settings.withSeconds ? 1000 : 60000);
|
|
|
|
let Pause = Period - (Date.now() % Period);
|
|
Timer = setTimeout(refreshClock,Pause);
|
|
}
|
|
|
|
/**** renderClock ****/
|
|
|
|
function renderClock () {
|
|
g.setColor (Settings.Foreground === 'Theme' ? Theme.fg : Settings.Foreground || '#000000');
|
|
g.setBgColor(Settings.Background === 'Theme' ? Theme.bg : Settings.Background || '#FFFFFF');
|
|
|
|
switch (Settings.Face) {
|
|
case 'none':
|
|
break;
|
|
case '3,6,9,12':
|
|
g.setFont('Vector', 22);
|
|
|
|
g.setFontAlign(0,-1);
|
|
g.drawString('12', CenterX,CenterY-outerRadius);
|
|
|
|
g.setFontAlign(1,0);
|
|
g.drawString('3', CenterX+outerRadius,CenterY);
|
|
|
|
g.setFontAlign(0,1);
|
|
g.drawString('6', CenterX,CenterY+outerRadius);
|
|
|
|
g.setFontAlign(-1,0);
|
|
g.drawString('9', CenterX-outerRadius,CenterY);
|
|
break;
|
|
case '1-12':
|
|
let innerRadius = outerRadius * 0.9 - 10;
|
|
|
|
let dark = g.theme.dark;
|
|
|
|
let Saturations = [0.8,1.0,1.0,1.0,1.0,1.0,1.0,0.9,0.7,0.7,0.9,0.9];
|
|
let Brightnesses = [1.0,0.9,0.6,0.6,0.8,0.8,0.7,1.0,1.0,1.0,1.0,1.0,];
|
|
|
|
for (let i = 0; i < 60; i++) {
|
|
let Phi = i * twoPi/60;
|
|
|
|
let x = CenterX + outerRadius * sin(Phi);
|
|
let y = CenterY - outerRadius * cos(Phi);
|
|
|
|
if (Settings.colored) {
|
|
let j = Math.floor(i / 5);
|
|
let Saturation = (dark ? Saturations[j] : 1.0);
|
|
let Brightness = (dark ? 1.0 : Brightnesses[j]);
|
|
|
|
let Color = E.HSBtoRGB(i/60,Saturation,Brightness, true);
|
|
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
|
|
}
|
|
|
|
g.fillCircle(x,y, 1);
|
|
}
|
|
|
|
g.setFont('Vector', 20);
|
|
g.setFontAlign(0,0);
|
|
|
|
for (let i = 0; i < 12; i++) {
|
|
let Phi = i * twoPi/12;
|
|
|
|
let Radius = innerRadius;
|
|
if (i >= 10) { Radius -= 4; }
|
|
|
|
let x = CenterX + Radius * sin(Phi);
|
|
let y = CenterY - Radius * cos(Phi);
|
|
|
|
if (Settings.colored) {
|
|
let Saturation = (dark ? Saturations[i] : 1.0);
|
|
let Brightness = (dark ? 1.0 : Brightnesses[i]);
|
|
|
|
let Color = E.HSBtoRGB(i/12,Saturation,Brightness, true);
|
|
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
|
|
}
|
|
|
|
g.drawString(i == 0 ? '12' : '' + i, x,y);
|
|
}
|
|
}
|
|
|
|
let now = new Date();
|
|
|
|
let Hours = now.getHours() % 12;
|
|
let Minutes = now.getMinutes();
|
|
|
|
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
|
|
let MinutesAngle = (Minutes/60) * twoPi - Pi;
|
|
|
|
g.setColor(Settings.Foreground === 'Theme' ? Theme.fg : Settings.Foreground || '#000000');
|
|
|
|
switch (Settings.Hands) {
|
|
case 'simple':
|
|
transformPolygon(simpleHourHandPolygon, CenterX,CenterY, HoursAngle);
|
|
g.fillPoly(transformedPolygon);
|
|
|
|
transformPolygon(simpleMinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
|
g.fillPoly(transformedPolygon);
|
|
break;
|
|
case 'rounded':
|
|
transformPolygon(roundedHourHandPolygon, CenterX,CenterY, HoursAngle);
|
|
g.fillPoly(transformedPolygon);
|
|
|
|
transformPolygon(roundedMinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
|
g.fillPoly(transformedPolygon);
|
|
|
|
// g.setColor(Settings.Foreground === 'Theme' ? Theme.fg || '#000000');
|
|
g.fillCircle(CenterX,CenterY, outerBoltRadius);
|
|
|
|
g.setColor(Settings.Background === 'Theme' ? Theme.bg : Settings.Background || '#FFFFFF');
|
|
g.drawCircle(CenterX,CenterY, outerBoltRadius);
|
|
g.fillCircle(CenterX,CenterY, innerBoltRadius);
|
|
break;
|
|
case 'hollow':
|
|
transformPolygon(hollowHourHandPolygon, CenterX,CenterY, HoursAngle);
|
|
g.drawPoly(transformedPolygon,true);
|
|
|
|
transformPolygon(hollowMinuteHandPolygon, CenterX,CenterY, MinutesAngle);
|
|
g.drawPoly(transformedPolygon,true);
|
|
|
|
g.drawCircle(CenterX,CenterY, BoltRadius);
|
|
}
|
|
|
|
if (Settings.withSeconds) {
|
|
g.setColor(Settings.Seconds === 'Theme' ? Theme.fgH : Settings.Seconds || '#FF0000');
|
|
|
|
let Seconds = now.getSeconds();
|
|
let SecondsAngle = (Seconds/60) * twoPi - Pi;
|
|
|
|
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
|
|
|
|
g.drawLine(
|
|
CenterX + SecondHandOffset*sPhi,
|
|
CenterY - SecondHandOffset*cPhi,
|
|
CenterX - SecondHandLength*sPhi,
|
|
CenterY + SecondHandLength*cPhi
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**** MainScreen Logic ****/
|
|
|
|
let Changes = {}, KeysToChange;
|
|
|
|
let fullScreen = {
|
|
x:0,y:0, w:ScreenWidth,h:ScreenHeight, x2:ScreenWidth-1,y2:ScreenHeight-1
|
|
};
|
|
let AppRect;
|
|
|
|
function openMainScreen () {
|
|
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
|
|
if (AppRect == null) { AppRect = Bangle.appRect; Bangle.appRect = fullScreen; }
|
|
|
|
Bangle.buzz();
|
|
|
|
KeysToChange = 'Face colored Hands withSeconds Foreground Background Seconds';
|
|
|
|
g.setTheme({ fg:'#000000', bg:'#FFFFFF' });
|
|
g.clear(true); // also installs the current theme
|
|
|
|
(activeLayout = MainScreen).render();
|
|
}
|
|
|
|
function applySettings () { Bangle.buzz(); saveSettings(); Bangle.appRect = AppRect; refreshClock(); }
|
|
function withdrawSettings () { Bangle.buzz(); readSettings(); Bangle.appRect = AppRect; refreshClock(); }
|
|
|
|
/**** FacesScreen Logic ****/
|
|
|
|
function openFacesScreen () {
|
|
Bangle.buzz();
|
|
|
|
KeysToChange = 'Face colored';
|
|
Bangle.appRect = fullScreen;
|
|
refreshFacesScreen();
|
|
}
|
|
|
|
function refreshFacesScreen () {
|
|
activeLayout = FacesScreen;
|
|
activeLayout['none'].checked = ((Changes.Face || Settings.Face) === 'none');
|
|
activeLayout['3,6,9,12'].checked = ((Changes.Face || Settings.Face) === '3,6,9,12');
|
|
activeLayout['1-12'].checked = ((Changes.Face || Settings.Face) === '1-12');
|
|
activeLayout['colored'].checked = (Changes.colored == null ? Settings.colored : Changes.colored);
|
|
activeLayout.render();
|
|
}
|
|
|
|
function chooseFace (Control) { Bangle.buzz(); Changes.Face = Control.id; refreshFacesScreen(); }
|
|
function toggleColored () { Bangle.buzz(); Changes.colored = ! Changes.colored; refreshFacesScreen(); }
|
|
|
|
/**** HandsScreen Logic ****/
|
|
|
|
function openHandsScreen () {
|
|
Bangle.buzz();
|
|
|
|
KeysToChange = 'Hands withSeconds';
|
|
Bangle.appRect = fullScreen;
|
|
refreshHandsScreen();
|
|
}
|
|
|
|
function refreshHandsScreen () {
|
|
activeLayout = HandsScreen;
|
|
activeLayout['simple'].checked = ((Changes.Hands || Settings.Hands) === 'simple');
|
|
activeLayout['rounded'].checked = ((Changes.Hands || Settings.Hands) === 'rounded');
|
|
activeLayout['hollow'].checked = ((Changes.Hands || Settings.Hands) === 'hollow');
|
|
activeLayout['withSeconds'].checked = (Changes.withSeconds == null ? Settings.withSeconds : Changes.withSeconds);
|
|
activeLayout.render();
|
|
}
|
|
|
|
function chooseHand (Control) { Bangle.buzz(); Changes.Hands = Control.id; refreshHandsScreen(); }
|
|
function toggleSeconds () { Bangle.buzz(); Changes.withSeconds = ! Changes.withSeconds; refreshHandsScreen(); }
|
|
|
|
/**** ColorsScreen Logic ****/
|
|
|
|
function openColorsScreen () {
|
|
Bangle.buzz();
|
|
|
|
KeysToChange = 'Foreground Background Seconds';
|
|
Bangle.appRect = fullScreen;
|
|
refreshColorsScreen();
|
|
}
|
|
|
|
function refreshColorsScreen () {
|
|
let Foreground = (Changes.Foreground == null ? Settings.Foreground : Changes.Foreground);
|
|
let Background = (Changes.Background == null ? Settings.Background : Changes.Background);
|
|
let Seconds = (Changes.Seconds == null ? Settings.Seconds : Changes.Seconds);
|
|
|
|
activeLayout = ColorsScreen;
|
|
activeLayout['Foreground'].bgCol = (Foreground === 'Theme' ? Theme.fg : Foreground);
|
|
activeLayout['Background'].bgCol = (Background === 'Theme' ? Theme.bg : Background);
|
|
activeLayout['Seconds'].bgCol = (Seconds === 'Theme' ? Theme.fgH : Seconds);
|
|
activeLayout.render();
|
|
}
|
|
|
|
function selectForegroundColor () { ColorToChange = 'Foreground'; openColorChoiceScreen(); }
|
|
function selectBackgroundColor () { ColorToChange = 'Background'; openColorChoiceScreen(); }
|
|
function selectSecondsColor () { ColorToChange = 'Seconds'; openColorChoiceScreen(); }
|
|
|
|
/**** ColorChoiceScreen Logic ****/
|
|
|
|
let ColorToChange, chosenColor;
|
|
|
|
function openColorChoiceScreen () {
|
|
Bangle.buzz();
|
|
|
|
chosenColor = (
|
|
Changes[ColorToChange] == null ? Settings[ColorToChange] : Changes[ColorToChange]
|
|
);
|
|
Bangle.appRect = fullScreen;
|
|
refreshColorChoiceScreen();
|
|
}
|
|
|
|
function refreshColorChoiceScreen () {
|
|
activeLayout = ColorChoiceScreen;
|
|
activeLayout['#000000'].selected = (chosenColor === '#000000');
|
|
activeLayout['#FF0000'].selected = (chosenColor === '#FF0000');
|
|
activeLayout['#00FF00'].selected = (chosenColor === '#00FF00');
|
|
activeLayout['#0000FF'].selected = (chosenColor === '#0000FF');
|
|
activeLayout['#FFFF00'].selected = (chosenColor === '#FFFF00');
|
|
activeLayout['#FF00FF'].selected = (chosenColor === '#FF00FF');
|
|
activeLayout['#00FFFF'].selected = (chosenColor === '#00FFFF');
|
|
activeLayout['#FFFFFF'].selected = (chosenColor === '#FFFFFF');
|
|
activeLayout['Theme'].selected = (chosenColor === 'Theme');
|
|
activeLayout.render();
|
|
}
|
|
|
|
function chooseColor (Control) { Bangle.buzz(); chosenColor = Control.id; refreshColorChoiceScreen(); }
|
|
function chooseThemeColor () { Bangle.buzz(); chosenColor = 'Theme'; refreshColorChoiceScreen(); }
|
|
|
|
function applyColor () {
|
|
Changes[ColorToChange] = chosenColor;
|
|
openColorsScreen();
|
|
}
|
|
|
|
function withdrawColor () {
|
|
openColorsScreen();
|
|
}
|
|
|
|
/**** common logic for multiple screens ****/
|
|
|
|
function applyChanges () {
|
|
Settings = Object.assign(Settings,Changes);
|
|
Changes = {};
|
|
openMainScreen();
|
|
}
|
|
|
|
function withdrawChanges () {
|
|
Changes = {};
|
|
openMainScreen();
|
|
}
|
|
|
|
|
|
g.setFont12x20(); // does not seem to be respected in layout!
|
|
|
|
let OkCancelWidth = Math.max(
|
|
g.stringWidth('Ok'), g.stringWidth('Cancel')
|
|
) + 2*10;
|
|
|
|
let StdFont = { font:'12x20' };
|
|
let legible = Object.assign({ col:'#000000', bgCol:'#FFFFFF' }, StdFont);
|
|
let leftAligned = Object.assign({ halign:-1, valign:0 }, legible);
|
|
let ColorView = Object.assign({ width:30, border:1, BorderColor:'#000000' }, StdFont);
|
|
let ColorChoice = Object.assign({ DrawableWidth:30, DrawableHeight:30, onTouch:chooseColor }, StdFont);
|
|
|
|
/**** MainScreen ****/
|
|
|
|
let MainScreen = new Layout({
|
|
type:'v', c:[
|
|
Label('Settings', { common:legible, bold:true, filly:1 }),
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Label('Faces', { common:leftAligned, fillx:1 }),
|
|
Image(Caret, { common:leftAligned }),
|
|
{ width:4 },
|
|
], filly:1, onTouch:openFacesScreen },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Label('Hands', { common:leftAligned, fillx:1 }),
|
|
Image(Caret, { common:leftAligned }),
|
|
{ width:4 },
|
|
], filly:1, onTouch:openHandsScreen },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Label('Colors', { common:leftAligned, fillx:1 }),
|
|
Image(Caret, { common:leftAligned }),
|
|
{ width:4 },
|
|
], filly:1, onTouch:openColorsScreen },
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applySettings }),
|
|
{ width:4 },
|
|
Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawSettings }),
|
|
], filly:1 },
|
|
], bgCol:'#FFFFFF'
|
|
});
|
|
|
|
|
|
/**** FacesScreen ****/
|
|
|
|
let FacesScreen = new Layout({
|
|
type:'v', c:[
|
|
Label('Clock Faces', { common:legible, bold:true, filly:1 }),
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Radiobutton({ id:'none', GroupName:'Faces', common:legible, onChange:chooseFace }),
|
|
Label(' no Face', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:checkInnerRadiobutton },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Radiobutton({ id:'3,6,9,12', GroupName:'Faces', common:legible, onChange:chooseFace }),
|
|
Label(' 3, 6, 9 and 12', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:checkInnerRadiobutton },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Radiobutton({ id:'1-12', GroupName:'Faces', common:legible, onChange:chooseFace }),
|
|
Label(' numbers 1...12', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:checkInnerRadiobutton },
|
|
{ type:'h', c:[
|
|
{ width:30 },
|
|
Checkbox({ id:'colored', common:legible, onChange:toggleColored }),
|
|
Label(' colorful', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:toggleInnerCheckbox },
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyChanges }),
|
|
{ width:4 },
|
|
Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawChanges }),
|
|
], filly:1 },
|
|
], bgCol:'#FFFFFF'
|
|
});
|
|
|
|
|
|
/**** HandsScreen ****/
|
|
|
|
let HandsScreen = new Layout({
|
|
type:'v', c:[
|
|
Label('Clock Hands', { common:legible, bold:true, filly:1 }),
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Radiobutton({ id:'simple', GroupName:'Faces', common:legible, onChange:chooseHand }),
|
|
Label(' simple', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:checkInnerRadiobutton },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Radiobutton({ id:'rounded', GroupName:'Faces', common:legible, onChange:chooseHand }),
|
|
Label(' rounded + Bolt', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:checkInnerRadiobutton },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Radiobutton({ id:'hollow', GroupName:'Faces', common:legible, onChange:chooseHand }),
|
|
Label(' hollow + Bolt', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:checkInnerRadiobutton },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Checkbox({ id:'withSeconds', common:legible, onChange:toggleSeconds }),
|
|
Label(' show Seconds', { common:leftAligned, pad:4, fillx:1 }),
|
|
], filly:1, onTouch:toggleInnerCheckbox },
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyChanges }),
|
|
{ width:4 },
|
|
Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawChanges }),
|
|
], filly:1 },
|
|
], bgCol:'#FFFFFF'
|
|
});
|
|
|
|
|
|
/**** ColorsScreen ****/
|
|
|
|
let ColorsScreen = new Layout({
|
|
type:'v', c:[
|
|
Label('Clock Colors', { common:legible, bold:true, filly:1 }),
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Label('Foreground', { common:leftAligned, pad:4, fillx:1 }),
|
|
Label('', { id:'Foreground', common:ColorView, bgCol:Theme.fg }),
|
|
{ width:4 },
|
|
], filly:1, onTouch:selectForegroundColor },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Label('Background', { common:leftAligned, pad:4, fillx:1 }),
|
|
Label('', { id:'Background', common:ColorView, bgCol:Theme.bg }),
|
|
{ width:4 },
|
|
], filly:1, onTouch:selectBackgroundColor },
|
|
{ type:'h', c:[
|
|
{ width:4 },
|
|
Label('Seconds', { common:leftAligned, pad:4, fillx:1 }),
|
|
Label('', { id:'Seconds', common:ColorView, bgCol:Theme.fgH }),
|
|
{ width:4 },
|
|
], filly:1, onTouch:selectSecondsColor },
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyChanges }),
|
|
{ width:4 },
|
|
Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawChanges }),
|
|
], filly:1 },
|
|
], bgCol:'#FFFFFF'
|
|
});
|
|
|
|
|
|
/**** ColorChoiceScreen ****/
|
|
|
|
function drawColorChoice (x,y, Width,Height, Details) {
|
|
let selected = Details.selected;
|
|
if (selected) {
|
|
g.setColor('#FF0000');
|
|
g.fillPoly([
|
|
x,y, x+Width-1,y, x+Width-1,y+Height-1, x,y+Height-1, x,y,
|
|
x+3,y+3, x+3,y+Height-4, x+Width-4,y+Height-4, x+Width-4,y+3, x+3,y+3
|
|
]);
|
|
} else {
|
|
g.setColor('#000000');
|
|
g.drawRect(x+3,y+3, x+Width-4,y+Height-4);
|
|
}
|
|
|
|
g.setColor(Details.col);
|
|
g.fillRect(x+4,y+4, x+Width-5,y+Height-5);
|
|
}
|
|
|
|
let ColorChoiceScreen = new Layout({
|
|
type:'v', c:[
|
|
Label('Choose Color', { common:legible, bold:true, filly:1 }),
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
Drawable(drawColorChoice, { id:'#000000', common:ColorChoice, col:'#000000' }),
|
|
{ width:8 },
|
|
Drawable(drawColorChoice, { id:'#FF0000', common:ColorChoice, col:'#FF0000' }),
|
|
{ width:8 },
|
|
Drawable(drawColorChoice, { id:'#00FF00', common:ColorChoice, col:'#00FF00' }),
|
|
{ width:8 },
|
|
Drawable(drawColorChoice, { id:'#0000FF', common:ColorChoice, col:'#0000FF' }),
|
|
], filly:1 },
|
|
{ type:'h', c:[
|
|
Drawable(drawColorChoice, { id:'#FFFFFF', common:ColorChoice, col:'#FFFFFF' }),
|
|
{ width:8 },
|
|
Drawable(drawColorChoice, { id:'#FFFF00', common:ColorChoice, col:'#FFFF00' }),
|
|
{ width:8 },
|
|
Drawable(drawColorChoice, { id:'#FF00FF', common:ColorChoice, col:'#FF00FF' }),
|
|
{ width:8 },
|
|
Drawable(drawColorChoice, { id:'#00FFFF', common:ColorChoice, col:'#00FFFF' }),
|
|
], filly:1 },
|
|
{ type:'h', c:[
|
|
Label('use Theme:', { id:'Theme', common:leftAligned, pad:4 }),
|
|
{ width:10 },
|
|
Drawable(drawColorChoice, { id:'Theme', common:ColorChoice, col:Theme.fg }),
|
|
], filly:1, onTouch:chooseThemeColor },
|
|
{ height:4 },
|
|
{ type:'h', c:[
|
|
Button('Ok', { common:legible, width:OkCancelWidth, onTouch:applyColor }),
|
|
{ width:4 },
|
|
Button('Cancel', { common:legible, width:OkCancelWidth, onTouch:withdrawColor }),
|
|
], filly:1 },
|
|
], bgCol:'#FFFFFF'
|
|
});
|
|
|
|
|
|
|
|
readSettings();
|
|
|
|
Bangle.on('swipe', (Direction) => {
|
|
if (Direction === 0) { openMainScreen(); }
|
|
});
|
|
|
|
setTimeout(refreshClock, 500); // enqueue first draw request
|
|
|
|
Bangle.on('lcdPower', (on) => {
|
|
if (on) {
|
|
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
|
|
refreshClock();
|
|
}
|
|
});
|
|
|
|
Bangle.setUI('clock');
|