1
0
Fork 0

Merge pull request #1174 from rozek/master

ColorWheel
master
Gordon Williams 2022-01-04 10:15:36 +00:00 committed by GitHub
commit 1b6c52d81c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1492 additions and 0 deletions

View File

@ -5098,6 +5098,89 @@
{"name":"ltherm.img","url":"icon.js","evaluate":true}
]
},
{
"id":"colorwheel",
"name":"Color Wheel",
"tags":"app,tool",
"version":"0.01",
"description":"a tappable wheel of good-looking colors",
"readme":"README.md",
"supports":["BANGLEJS2"],
"allow_emulator":true,
"icon":"colorwheel.png",
"storage": [
{"name":"colorwheel.app.js","url":"app.js"},
{"name":"colorwheel.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "minimal_clock",
"name": "Minimal Analog Clock",
"shortName":"Minimal Clock",
"version":"0.03",
"description": "a minimal analog clock - just with some hands and no clock face",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"minimal_clock.app.js","url":"app.js"},
{"name":"minimal_clock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "simple_clock",
"name": "Simple Analog Clock",
"shortName":"Simple Clock",
"version":"0.02",
"description": "a simple, yet stylish, analog clock",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"simple_clock.app.js","url":"app.js"},
{"name":"simple_clock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "colorful_clock",
"name": "Colorful Analog Clock",
"shortName":"Colorful Clock",
"version":"0.02",
"description": "a colorful analog clock",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"colorful_clock.app.js","url":"app.js"},
{"name":"colorful_clock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "themesetter",
"name": "Theme Setter",
"shortName":"Theme Setter",
"version":"0.04",
"description": "a comfortable way to configure theme colors",
"icon": "app-icon.png",
"type": "app",
"tags": "tool",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"screenshots": [{"url":"app-screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"themesetter.app.js","url":"app.js"},
{"name":"themesetter.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "widviztime",
"name": "Widget Autohide Widget",

View File

@ -0,0 +1,14 @@
# Colorful Analog Clock #
This app displays an analog clock with a colorful face. It considers the
currently configured "theme" (and may therefore look different than shown in
the screenshot on your watch depending on which theme you prefer).
![](app-screenshot.png)
This clock also acts as an example for the building blocks found in the author's
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
## License ##
[MIT License](LICENSE)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgZC/AB0BkmCCBsShEAiFBkARLwEBkGSgEECBQdBCIMSAwMYCBEKCgeQgE2gFgCA0C6Moiw1Dk0AhoRGikACIIHDgzECCI5ECg/gEYOACI+ggMti2ACIUkCIImCABARCAAMNCYIADgu0hdACI1twHACQm0rdoCI0BEYsoilRCI9sUgkBoG2rY1JgYjDCINLCI4fCa5ARHAAggCfYIjLgUB0AECCIy7BFwUCR4IKChIRFm1ACJAjGgwRL+AFDiwREI4YABn41FI4hxFn6IJPoh1B/AQFUI4ABh4RGUIsEyARC4ALEwAjECIl/CIkECIsICId+EQkkwEIA4gRDAAojBLwwHFexAADhaFDgETBw6UChdgA4cbCIKuGggCBCIMDCIkQCI8BEwMbCgMSAQIRGgGQQoQRCEYJrGAAMGIgZKDmBzIjARFTwpuHAARoGAAsMwQVCzARLAAPbtq5KAH4AEA"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

247
apps/colorful_clock/app.js Normal file
View File

@ -0,0 +1,247 @@
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
Bangle.loadWidgets();
/**** updateClockFaceSize ****/
function updateClockFaceSize () {
CenterX = ScreenWidth/2;
CenterY = ScreenHeight/2;
outerRadius = Math.min(CenterX,CenterY) * 0.9;
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.sqrt(r2);
}
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.sqrt(r2);
}
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.sqrt(r2);
}
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.sqrt(r2);
}
CenterX = cx; CenterY = cy; outerRadius = r * 0.9;
}
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); }
}
};
let innerRadius = Math.min(CenterX,CenterY) * 0.8 - 14;
let HourHandLength = outerRadius * 0.5;
let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2;
let MinuteHandLength = outerRadius * 0.7;
let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2;
let SecondHandLength = outerRadius * 0.9;
let SecondHandOffset = 6;
let twoPi = 2*Math.PI;
let Pi = Math.PI;
let halfPi = Math.PI/2;
let sin = Math.sin, cos = Math.cos;
let HourHandPolygon = [
-halfHourHandWidth,halfHourHandWidth,
-halfHourHandWidth,halfHourHandWidth-HourHandLength,
halfHourHandWidth,halfHourHandWidth-HourHandLength,
halfHourHandWidth,halfHourHandWidth,
];
let MinuteHandPolygon = [
-halfMinuteHandWidth,halfMinuteHandWidth,
-halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
halfMinuteHandWidth,halfMinuteHandWidth,
];
/**** drawClockFace ****/
function drawClockFace () {
for (let i = 0; i < 60; i++) {
let Phi = i * twoPi/60;
let x = CenterX + outerRadius * sin(Phi);
let y = CenterY - outerRadius * cos(Phi);
let Color = E.HSBtoRGB(i/60,1,1, 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);
let Color = E.HSBtoRGB(i/12,1,1, true);
g.setColor(Color[0]/255,Color[1]/255,Color[2]/255);
g.drawString(i == 0 ? '12' : '' + i, x,y);
}
}
/**** transforme polygon ****/
let transformedPolygon = new Array(HourHandPolygon.length);
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;
}
}
/**** draw clock hands ****/
function drawClockHands () {
let now = new Date();
let Hours = now.getHours() % 12;
let Minutes = now.getMinutes();
let Seconds = now.getSeconds();
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
let MinutesAngle = (Minutes/60) * twoPi - Pi;
let SecondsAngle = (Seconds/60) * twoPi - Pi;
g.setColor(g.theme.fg);
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
g.fillPoly(transformedPolygon);
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
g.fillPoly(transformedPolygon);
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
g.setColor(g.theme.fg2);
g.drawLine(
CenterX + SecondHandOffset*sPhi,
CenterY - SecondHandOffset*cPhi,
CenterX - SecondHandLength*sPhi,
CenterY + SecondHandLength*cPhi
);
}
/**** refreshDisplay ****/
let Timer;
function refreshDisplay () {
g.clear(true); // also loads current theme
Bangle.drawWidgets();
drawClockFace();
drawClockHands();
let Pause = 1000 - (Date.now() % 1000);
Timer = setTimeout(refreshDisplay,Pause);
}
setTimeout(refreshDisplay, 500); // enqueue first draw request
Bangle.on('lcdPower', (on) => {
if (on) {
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
refreshDisplay();
}
});
Bangle.loadWidgets();
Bangle.setUI('clock');

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

21
apps/colorwheel/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Andreas Rozek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

12
apps/colorwheel/README.md Normal file
View File

@ -0,0 +1,12 @@
# ColorWheel #
Choosing the right color on a Bangle.js 2 is not always easy. This little app therefore displays a wheel of rather good looking colors and reveals the associated color code by tapping on it
![](BangleJS2-ColorWheel-2.png)
![](BangleJS2-ColorWheel-3.png)
Please note: you may also tap outside the wheel (for black) or inside it (for white).
## License ##
[MIT License](LICENSE)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgYtr4cEiAQMku27ckyVICBUDlmy5YRBpMkCBEE7dtEYYRBwARHm3LEY3QCA0BEAIjFk3boARFhoOBEYs06dNCIogCEYoHCNAoOCEYlNEYUgCIcbEZekCIYODtgjHmgRHtu3///yQrESQfTBIYQBAAPNEYU2SQUGEYd/CIf9EYYRCDAYRF/4KBCIioBAwPfCAn/+wKC7QjEmgRG/xADZAIyBAwIQFCIgjFmoRKEYL4DRgQAFGoojKCIoje7Nly1ZEYLzCkojLNYIRCNZAjIkm/EZ4RH/1ZEYYRMWYpZDy4jJrARBggRBlrYG+VJEYgRBDIVfCIgtCy1QCIZhDCAfkKIW24ARBgJeBEYNbvoQBvOkCIQjDgE2EYYCD2gRCyQQCgEGEYYRBzVp0wRCyAREEY+2CIWAEY4OCEYoQDAAMbEY/SpMgCAkCjIjHzVJEQoABWYIjF7VICI8BBwYjDe4IAHSQ3QCBBuBLgQjCCBIAChu26dMCBgAdA"))

80
apps/colorwheel/app.js Normal file
View File

@ -0,0 +1,80 @@
//----------------------------------------------------------------------------//
//-- ColorWheel - draws a "wheel" of good looking colors --//
//----------------------------------------------------------------------------//
let ColorList = [
'#0000FF', '#8000FF', '#FF00FF', '#FF0080', '#FF0000', '#FF8000',
'#FFFF00', '#80FF00', '#00FF00', '#00FF80', '#00FFFF', '#0080FF'
];
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
let innerRadius = outerRadius*0.5;
let sin = Math.sin, cos = Math.cos;
let twoPi = 2*Math.PI, halfPi = Math.PI/2;
let DeltaPhi = twoPi/72;
let Epsilon = 0.001;
g.clear();
g.setColor(0,0,0);
g.fillRect(0,0, ScreenWidth,ScreenHeight);
for (let i = 0; i < 12; i++) {
let Phi0 = i * twoPi/12, Phi1 = (i+1) * twoPi/12;
let Polygon = [];
for (let Phi = Phi0; Phi <= Phi1+Epsilon; Phi += DeltaPhi) {
Polygon.push(CenterX + outerRadius * sin(Phi));
Polygon.push(CenterY - outerRadius * cos(Phi));
}
for (let Phi = Phi1; Phi >= Phi0-Epsilon; Phi -= DeltaPhi) {
Polygon.push(CenterX + innerRadius * sin(Phi));
Polygon.push(CenterY - innerRadius * cos(Phi));
}
g.setColor(ColorList[i]);
g.fillPoly(Polygon);
}
g.setColor(1,1,1);
g.fillCircle(CenterX,CenterY, innerRadius);
g.setFont12x20();
g.setFontAlign(0,0);
g.setColor(0,0,0);
g.drawString('Tap', CenterX,CenterY-20);
g.drawString('on a', CenterX,CenterY);
g.drawString('Color', CenterX,CenterY+20);
Bangle.on('touch', function (Button,Position) {
Bangle.buzz();
let dx = Position.x - CenterX;
let dy = Position.y - CenterY;
let Radius = Math.sqrt(dx*dx + dy*dy);
let Color;
switch (true) {
case (Radius > outerRadius): Color = '#000000'; break;
case (Radius < innerRadius): Color = '#FFFFFF'; break;
default:
let Phi = Math.atan2(dy,dx) + halfPi;
if (Phi < 0) { Phi += twoPi; }
if (Phi > twoPi) { Phi -= twoPi; }
let Index = Math.floor(12*Phi/twoPi);
Color = ColorList[Index];
}
g.setColor(1,1,1);
g.fillCircle(CenterX,CenterY, innerRadius);
g.setColor(0,0,0);
g.drawString(Color, CenterX,CenterY);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Andreas Rozek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,15 @@
# Minimal Analog Clock #
This app displays the perhaps most basic analog clock one can think of - just
some clock hands and no clock face. It considers the currently configured
"theme" (and may therefore look different than shown in the screenshot on your
watch depending on which theme you prefer).
![](app-screenshot.png)
This clock also acts as an example for the building blocks found in the author's
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
## License ##
[MIT License](LICENSE)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/AEBhCjgCBgeAgF8AoXggHwCIXwgfADAX8h4TBAAM+jwkDj/4AocPDwIACgdgBYgoCAAMEuB+/AH4="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

230
apps/minimal_clock/app.js Normal file
View File

@ -0,0 +1,230 @@
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
Bangle.loadWidgets();
/**** updateClockFaceSize ****/
function updateClockFaceSize () {
CenterX = ScreenWidth/2;
CenterY = ScreenHeight/2;
outerRadius = Math.min(CenterX,CenterY) * 0.9;
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.sqrt(r2);
}
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.sqrt(r2);
}
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.sqrt(r2);
}
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.sqrt(r2);
}
CenterX = cx; CenterY = cy; outerRadius = r * 0.9;
}
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); }
}
};
let HourHandLength = outerRadius * 0.5;
let HourHandWidth = 2*5, halfHourHandWidth = HourHandWidth/2;
let MinuteHandLength = outerRadius * 0.7;
let MinuteHandWidth = 2*3, halfMinuteHandWidth = MinuteHandWidth/2;
let SecondHandLength = outerRadius * 0.9;
let SecondHandOffset = halfHourHandWidth + 10;
let outerBoltRadius = halfHourHandWidth + 2, innerBoltRadius = outerBoltRadius - 4;
let HandOffset = outerBoltRadius + 4;
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;
let sine = [0, sin(30*deg2rad), sin(60*deg2rad), 1];
let HandPolygon = [
-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 HourHandPolygon = new Array(HandPolygon.length);
for (let i = 0, l = HandPolygon.length; i < l; i+=2) {
HourHandPolygon[i] = halfHourHandWidth*HandPolygon[i];
HourHandPolygon[i+1] = halfHourHandWidth*HandPolygon[i+1];
if (i < l/2) { HourHandPolygon[i+1] -= HourHandLength; }
if (i > l/2) { HourHandPolygon[i+1] += HandOffset; }
}
let MinuteHandPolygon = new Array(HandPolygon.length);
for (let i = 0, l = HandPolygon.length; i < l; i+=2) {
MinuteHandPolygon[i] = halfMinuteHandWidth*HandPolygon[i];
MinuteHandPolygon[i+1] = halfMinuteHandWidth*HandPolygon[i+1];
if (i < l/2) { MinuteHandPolygon[i+1] -= MinuteHandLength; }
if (i > l/2) { MinuteHandPolygon[i+1] += HandOffset; }
}
/**** transforme polygon ****/
let transformedPolygon = new Array(HandPolygon.length);
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;
}
}
/**** draw clock hands ****/
function drawClockHands () {
let now = new Date();
let Hours = now.getHours() % 12;
let Minutes = now.getMinutes();
let Seconds = now.getSeconds();
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
let MinutesAngle = (Minutes/60) * twoPi - Pi;
let SecondsAngle = (Seconds/60) * twoPi - Pi;
g.setColor(g.theme.fg);
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
g.fillPoly(transformedPolygon);
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
g.fillPoly(transformedPolygon);
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
g.setColor(g.theme.fg2);
g.drawLine(
CenterX + SecondHandOffset*sPhi,
CenterY - SecondHandOffset*cPhi,
CenterX - SecondHandLength*sPhi,
CenterY + SecondHandLength*cPhi
);
g.setColor(g.theme.fg);
g.fillCircle(CenterX,CenterY, outerBoltRadius);
g.setColor(g.theme.bg);
g.drawCircle(CenterX,CenterY, outerBoltRadius);
g.fillCircle(CenterX,CenterY, innerBoltRadius);
}
/**** refreshDisplay ****/
let Timer;
function refreshDisplay () {
g.clear(true); // also loads current theme
Bangle.drawWidgets();
drawClockHands();
let Pause = 1000 - (Date.now() % 1000);
Timer = setTimeout(refreshDisplay,Pause);
}
setTimeout(refreshDisplay, 500); // enqueue first draw request
Bangle.on('lcdPower', (on) => {
if (on) {
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
refreshDisplay();
}
});
Bangle.loadWidgets();
Bangle.setUI('clock');

View File

@ -0,0 +1,14 @@
# Simple Analog Clock #
This app displays a simple, yet stylish, analog clock. It considers the
currently configured "theme" (and may therefore look different than shown in
the screenshot on your watch depending on which theme you prefer).
![](app-screenshot.png)
This clock also acts as an example for the building blocks found in the author's
[GitHub repository](https://github.com/rozek/banglejs-2-activities)
## License ##
[MIT License](LICENSE)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwcBIf4AOgPAjgROh/A/+AEZ8DCKH8Gp/4Gp0QCKAARgQRigFACMUICMT7SEcUAkAvK/EAv//BpH8eoYOBAQP//0ECIrvDCIQABj4TB8AREj4RCgIyFn4RJh5HBCJQ1DAA0/UKBuJQZIRgL4wRL4ARhAH4AIg4RQdIwRcnAjiLKIA/ACI="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

230
apps/simple_clock/app.js Normal file
View File

@ -0,0 +1,230 @@
let ScreenWidth = g.getWidth(), CenterX = ScreenWidth/2;
let ScreenHeight = g.getHeight(), CenterY = ScreenHeight/2;
let outerRadius = Math.min(CenterX,CenterY) * 0.9;
Bangle.loadWidgets();
/**** updateClockFaceSize ****/
function updateClockFaceSize () {
CenterX = ScreenWidth/2;
CenterY = ScreenHeight/2;
outerRadius = Math.min(CenterX,CenterY) * 0.9;
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.sqrt(r2);
}
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.sqrt(r2);
}
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.sqrt(r2);
}
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.sqrt(r2);
}
CenterX = cx; CenterY = cy; outerRadius = r * 0.9;
}
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); }
}
};
let HourHandLength = outerRadius * 0.5;
let HourHandWidth = 2*3, halfHourHandWidth = HourHandWidth/2;
let MinuteHandLength = outerRadius * 0.7;
let MinuteHandWidth = 2*2, halfMinuteHandWidth = MinuteHandWidth/2;
let SecondHandLength = outerRadius * 0.9;
let SecondHandOffset = 6;
let twoPi = 2*Math.PI;
let Pi = Math.PI;
let halfPi = Math.PI/2;
let sin = Math.sin, cos = Math.cos;
let HourHandPolygon = [
-halfHourHandWidth,halfHourHandWidth,
-halfHourHandWidth,halfHourHandWidth-HourHandLength,
halfHourHandWidth,halfHourHandWidth-HourHandLength,
halfHourHandWidth,halfHourHandWidth,
];
let MinuteHandPolygon = [
-halfMinuteHandWidth,halfMinuteHandWidth,
-halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
halfMinuteHandWidth,halfMinuteHandWidth-MinuteHandLength,
halfMinuteHandWidth,halfMinuteHandWidth,
];
/**** drawClockFace ****/
function drawClockFace () {
g.setColor(g.theme.fg);
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);
}
/**** transforme polygon ****/
let transformedPolygon = new Array(HourHandPolygon.length);
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;
}
}
/**** draw clock hands ****/
function drawClockHands () {
let now = new Date();
let Hours = now.getHours() % 12;
let Minutes = now.getMinutes();
let Seconds = now.getSeconds();
let HoursAngle = (Hours+(Minutes/60))/12 * twoPi - Pi;
let MinutesAngle = (Minutes/60) * twoPi - Pi;
let SecondsAngle = (Seconds/60) * twoPi - Pi;
g.setColor(g.theme.fg);
transformPolygon(HourHandPolygon, CenterX,CenterY, HoursAngle);
g.fillPoly(transformedPolygon);
transformPolygon(MinuteHandPolygon, CenterX,CenterY, MinutesAngle);
g.fillPoly(transformedPolygon);
let sPhi = Math.sin(SecondsAngle), cPhi = Math.cos(SecondsAngle);
g.setColor(g.theme.fg2);
g.drawLine(
CenterX + SecondHandOffset*sPhi,
CenterY - SecondHandOffset*cPhi,
CenterX - SecondHandLength*sPhi,
CenterY + SecondHandLength*cPhi
);
}
/**** refreshDisplay ****/
let Timer;
function refreshDisplay () {
g.clear(true); // also loads current theme
Bangle.drawWidgets();
drawClockFace();
drawClockHands();
let Pause = 1000 - (Date.now() % 1000);
Timer = setTimeout(refreshDisplay,Pause);
}
setTimeout(refreshDisplay, 500); // enqueue first draw request
Bangle.on('lcdPower', (on) => {
if (on) {
if (Timer != null) { clearTimeout(Timer); Timer = undefined; }
refreshDisplay();
}
});
Bangle.loadWidgets();
Bangle.setUI('clock');

View File

@ -0,0 +1,22 @@
# Theme Setter #
This little tool allows you to configure the global theme of all Bangle.js apps
(provided that they do not override global settings) in a more comfortable way
than through the settings menu.
![](ThemeSetter-MainScreen.png)
![](ThemeSetter-DetailSelectionScreen.png)
![](ThemeSetter-ColorSelectionScreen.png)
![](ThemeSetter-ThemePreviewScreen.png)
This app also acts as an example for a non-trivial Bangle.js application
using the "layout" library, custom controls and generic event dispatching.
See [GitHub](https://github.com/rozek/banglejs-2-activities) for details.
## License ##
[MIT License](LICENSE)
## Credits ##
The icon for this app was taken from [icons8.com](https://icons8.com/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgP/ACHgDAQWBApfjCoXxAqHwg4FP+PHApY7EApheEAq3+g4FD/EPAofAj4QDgAQECwgQQ8E/Cwg+EAvYAhA=="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

498
apps/themesetter/app.js Normal file
View File

@ -0,0 +1,498 @@
let Layout = require('Layout');
let ScreenWidth = g.getWidth(), halfWidth = ScreenWidth/2;
let ScreenHeight = g.getHeight();
let normalizedColorSet = {
black:g.toColor(0,0,0), white: g.toColor(1,1,1),
red: g.toColor(1,0,0), yellow: g.toColor(1,1,0),
green:g.toColor(0,1,0), magenta:g.toColor(1,0,1),
blue: g.toColor(0,0,1), cyan: g.toColor(0,1,1)
};
let activeTheme = g.theme; // currently active theme
let pendingTheme = Object.assign({},activeTheme);
let chosenDetail = null; // one of 'fg','bg','fg2','bg2','fgH','bgH'
/**** Label ****/
function Label (Text, Options) {
function renderLabel (Details) {
let halfWidth = Details.w/2, xAlignment = Details.halign || 0;
let halfHeight = Details.h/2, yAlignment = Details.valign || 0;
let Padding = Details.pad || 0;
g.setColor(Details.col || g.theme.fg || '#000000');
if (Details.font != null) { g.setFont(Details.font); }
g.setFontAlign(xAlignment,yAlignment);
let x = Details.x + halfWidth + xAlignment*(halfWidth+Padding);
let y = Details.y + halfHeight + yAlignment*(halfHeight+Padding);
g.drawString(Details.label, x,y);
if (Details.bold) {
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 TextMetrics;
if (! Result.width || ! Result.height) {
if (Result.font != null) { g.setFont(Result.font); }
TextMetrics = g.stringMetrics(Result.label);
}
Result.width = Result.width || TextMetrics.width + 2*(Result.pad || 0);
Result.height = Result.height || TextMetrics.height + 2*(Result.pad || 0);
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);
}
};
}
/**** 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;
g.setColor(Details.col || g.theme.fg || '#000000');
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.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 TextMetrics;
if (! Result.width || ! Result.height) {
if (Options.font != null) { g.setFont(Options.font); }
TextMetrics = g.stringMetrics(Result.label);
}
Result.width = Result.width || TextMetrics.width + 2*10 + 2*(Result.pad || 0);
Result.height = Result.height || TextMetrics.height + 2*5 + 2*(Result.pad || 0);
return Result;
}
/**** ColorDemo ****/
function ColorDemo (Text, Options) {
function renderDemo (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;
if (Details.font != null) { g.setFont(Details.font); }
g.setFontAlign(0,0);
g.setColor(Details.bg); // do not use "bgCol"!
g.fillRect(x+Padding, y+Padding, x+Width-Padding, y+Height-Padding);
g.setColor(Details.fg);
g.drawString(Details.label, x+halfWidth,y+halfHeight);
}
let Result = Object.assign((
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
), {
type:'custom', render:renderDemo, label:Text || 'Test'
});
let TextMetrics;
if (! Result.width || ! Result.height) {
if (Result.font != null) { g.setFont(Result.font); }
TextMetrics = g.stringMetrics(Result.label);
}
Result.width = Result.width || TextMetrics.width + 2*2 + 2*(Result.pad || 0);
Result.height = Result.height || TextMetrics.height + 2*2 + 2*(Result.pad || 0);
return Result;
}
/**** ColorView ****/
function ColorView (Color, Options) {
function renderColorView (Details) {
let x = Details.x, Width = Details.w;
let y = Details.y, Height = Details.h;
let Padding = Details.pad || 0;
g.setColor('#000000');
g.drawRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1);
g.setColor(Details.col);
g.fillRect(x+Padding+2, y+Padding+2, x+Width-Padding-3, y+Height-Padding-3);
}
let Result = Object.assign((
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
), {
type:'custom', render:renderColorView, col:Color
});
Result.width = Math.max(10, Result.width || 10) + 2*(Result.pad || 0);
Result.height = Math.max(10, Result.height || 10) + 2*(Result.pad || 0);
return Result;
}
/**** ColorSelectionView ****/
function ColorSelectionView (Color, Options) {
function renderColorView (Details) {
let x = Details.x, Width = Details.w;
let y = Details.y, Height = Details.h;
let Padding = Details.pad || 0;
if (Details.selected) {
g.setColor(Details.selected ? '#FF0000' : '#000000');
g.fillRect(x+Padding,y+Padding, x+Width-Padding-1,y+Height-Padding-1);
g.setColor('#FFFFFF');
g.drawRect(x+Padding+4,y+Padding+4, x+Width-Padding-5,y+Height-Padding-5);
} else {
g.setColor('#000000');
g.drawRect(x+Padding+3,y+Padding+3, x+Width-Padding-4,y+Height-Padding-4);
}
g.setColor(Details.col);
g.fillRect(x+Padding+5, y+Padding+5, x+Width-Padding-6, y+Height-Padding-6);
}
let Result = Object.assign((
Options == null ? {} : Object.assign({}, Options.common || {}, Options)
), {
type:'custom', render:renderColorView, col:Color
});
Result.width = Math.max(10, Result.width || 10) + 2*(Result.pad || 0);
Result.height = Math.max(10, Result.height || 10) + 2*(Result.pad || 0);
return Result;
}
/**** EventConsumerAtPoint ****/
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 () {
function handleTouchEvent (Button, xy) {
let Control = EventConsumerAtPoint('onTouch', xy.x,xy.y);
if (Control != null) {
Control.onTouch(Control, Button, xy);
}
}
Bangle.on('touch',handleTouchEvent);
}
dispatchTouchEvent();
/**** dispatchStrokeEvent ****/
function dispatchStrokeEvent () {
function handleStrokeEvent (Coordinates) {
let Control = EventConsumerAtPoint('onStroke', Coordinates.xy[0],Coordinates.xy[1]);
if (Control != null) {
Control.onStroke(Control, Coordinates);
}
}
Bangle.on('stroke',handleStrokeEvent);
}
dispatchStrokeEvent();
let ScreenSet = {};
g.setFont12x20(); // does not seem to be respected in layout!
let leftColumnWidth = Math.max(
g.stringWidth('Normal '), g.stringWidth('Accented '), g.stringWidth('Hilighted ')
);
let StdFont = { font:'12x20' };
let legible = Object.assign({ col:'#000000', bgCol:'#FFFFFF' }, StdFont);
let leftAligned = Object.assign({ halign:-1, valign:0 }, legible);
let MainLabel = Object.assign({ pad:4, width:leftColumnWidth }, leftAligned);
let halfWidthButton = Object.assign({ pad:4, width:halfWidth }, legible);
ScreenSet['MainScreen'] = new Layout({
type:'v', c:[
Label('Current Theme', { common:legible, pad:8, bold:true, filly:1 }),
{ type:'h', c:[
Label('Normal', { common:MainLabel }),
ColorDemo(' Demo ',{ common:StdFont, pad:2, id:'NormalDemo' }),
] },
{ type:'h', c:[
Label('Accented', { common:MainLabel }),
ColorDemo(' Demo ',{ common:StdFont, pad:2, id:'AccentedDemo' }),
] },
{ type:'h', c:[
Label('Hilighted', { common:MainLabel }),
ColorDemo(' Demo ',{ common:StdFont, pad:2, id:'HilitedDemo' }),
] },
{ height:4 },
{ type:'h', c:[
Button('Exit', { common:halfWidthButton, onTouch:() => load() }),
Button('Config', { common:halfWidthButton, onTouch:() => gotoScreen('DetailSelectionScreen') })
], filly:1 }
]
});
let LabelWidth = Math.max(
g.stringWidth('Fg '), g.stringWidth('Fg2 '), g.stringWidth('FgH '),
g.stringWidth('Bg '), g.stringWidth('Bg2 '), g.stringWidth('BgH ')
);
let LabelHeight = g.stringMetrics('FgH').height;
let DetailLabel = Object.assign({ pad:4, width:LabelWidth }, leftAligned);
let DetailView = { width:30, height:LabelHeight, pad:2 };
ScreenSet['DetailSelectionScreen'] = new Layout({
type:'v', c:[
Label('Configure Detail', { font:'12x20', pad:8, col:'#000000', bgCol:'#FFFFFF', bold:true, filly:1 }),
{ type:'h', c:[
Label('fg', { common:DetailLabel, onTouch:() => configureDetail('fg') }),
ColorView(0, { common:DetailView, onTouch:() => configureDetail('fg'), id:'fgView' }),
{ width:20 },
Label('bg', { common:DetailLabel, onTouch:() => configureDetail('bg') }),
ColorView(0, { common:DetailView, onTouch:() => configureDetail('bg'), id:'bgView' }),
] },
{ type:'h', c:[
Label('fg2', { common:DetailLabel, onTouch:() => configureDetail('fg2') }),
ColorView(0, { common:DetailView, onTouch:() => configureDetail('fg2'), id:'fg2View' }),
{ width:20 },
Label('bg2', { common:DetailLabel, onTouch:() => configureDetail('bg2') }),
ColorView(0, { common:DetailView, onTouch:() => configureDetail('bg2'), id:'bg2View' }),
] },
{ type:'h', c:[
Label('fgH', { common:DetailLabel, onTouch:() => configureDetail('fgH') }),
ColorView(0, { common:DetailView, onTouch:() => configureDetail('fgH'), id:'fgHView' }),
{ width:20 },
Label('bgH', { common:DetailLabel, onTouch:() => configureDetail('bgH') }),
ColorView(0, { common:DetailView, onTouch:() => configureDetail('bgH'), id:'bgHView' }),
] },
{ type:'h', c:[
Button('Save', { common:halfWidthButton, onTouch:() => { applyChanges(); gotoScreen('MainScreen'); } }),
Button('Cancel', { common:halfWidthButton, onTouch:() => gotoScreen('MainScreen') })
], filly:1 },
]
});
let StdSelectionView = { width:40, height:40, pad:2 };
ScreenSet['ColorSelectionScreen'] = new Layout({
type:'v', c:[
Label('Choose Color', { font:'12x20', pad:8, col:'#000000', bgCol:'#FFFFFF', bold:true, filly:1 }),
{ type:'h', c:[
ColorSelectionView('#000000',{ common:StdSelectionView, id:'black',
onTouch:() => selectColor(0,0,0) }),
ColorSelectionView('#FF0000',{ common:StdSelectionView, id:'red',
onTouch:() => selectColor(1,0,0) }),
ColorSelectionView('#00FF00',{ common:StdSelectionView, id:'green',
onTouch:() => selectColor(0,1,0) }),
ColorSelectionView('#0000FF',{ common:StdSelectionView, id:'blue',
onTouch:() => selectColor(0,0,1) }),
] },
{ type:'h', c:[
ColorSelectionView('#FFFFFF',{ common:StdSelectionView, id:'white',
onTouch:() => selectColor(1,1,1) }),
ColorSelectionView('#FFFF00',{ common:StdSelectionView, id:'yellow',
onTouch:() => selectColor(1,1,0) }),
ColorSelectionView('#FF00FF',{ common:StdSelectionView, id:'magenta',
onTouch:() => selectColor(1,0,1) }),
ColorSelectionView('#00FFFF',{ common:StdSelectionView, id:'cyan',
onTouch:() => selectColor(0,1,1) }),
] },
{ height:4 },
{ type:'h', c:[
Button('Back', { common:halfWidthButton, onTouch:() => gotoScreen('DetailSelectionScreen') }),
Button('Preview', { common:halfWidthButton, onTouch:() => gotoScreen('ThemePreviewScreen') })
], filly:1 },
]
});
ScreenSet['ThemePreviewScreen'] = new Layout({
type:'v', c:[
Label('Theme Preview', { common:legible, bold:true, filly:1 }),
{ type:'h', c:[
Label('Normal', { common:MainLabel }),
ColorDemo(' Test ',{ common:StdFont, pad:2, id:'NormalTest' }),
] },
{ type:'h', c:[
Label('Accented', { common:MainLabel }),
ColorDemo(' Test ',{ common:StdFont, pad:2, id:'AccentedTest' }),
] },
{ type:'h', c:[
Label('Hilighted', { common:MainLabel }),
ColorDemo(' Test ',{ common:StdFont, pad:2, id:'HilitedTest' }),
] },
{ height:4 },
{ type:'h', c:[
Button('Back', { common:legible, pad:4, onTouch:() => gotoScreen('ColorSelectionScreen') })
], filly:1 }
]
});
/**** applyChanges ****/
function applyChanges () {
let pendingBg = pendingTheme.bg;
let R = ((pendingBg >> 11) & 0b11111) / 0b11111;
let G = ((pendingBg >> 5) & 0b111111) / 0b111111;
let B = (pendingBg & 0b11111) / 0b11111;
pendingTheme.dark = (0.2126*R + 0.7152*G + 0.0722*B < 0.5);
activeTheme = Object.assign(activeTheme,pendingTheme);
let globalSettings = Object.assign(
require('Storage').readJSON('setting.json', true) || {},
{ theme:activeTheme }
);
require('Storage').writeJSON('setting.json', globalSettings);
}
/**** configureDetail ****/
function configureDetail (Detail) {
chosenDetail = Detail;
gotoScreen('ColorSelectionScreen');
}
/**** updateColorSelection ****/
function updateColorSelection () {
let selectedColor = pendingTheme[chosenDetail];
for (let Key in normalizedColorSet) {
if (normalizedColorSet.hasOwnProperty(Key)) {
activeLayout[Key].selected = (selectedColor === normalizedColorSet[Key]);
}
}
}
/**** selectColor ****/
function selectColor (R,G,B) {
let selectedColor = g.toColor(R,G,B);
pendingTheme[chosenDetail] = selectedColor;
updateColorSelection();
g.clear();
activeLayout.render();
}
/**** gotoScreen ****/
let activeLayout;
function gotoScreen (ScreenName) {
activeLayout = ScreenSet[ScreenName];
switch (ScreenName) {
case 'MainScreen':
activeLayout['NormalDemo'].fg = activeTheme.fg;
activeLayout['NormalDemo'].bg = activeTheme.bg;
activeLayout['AccentedDemo'].fg = activeTheme.fg2;
activeLayout['AccentedDemo'].bg = activeTheme.bg2;
activeLayout['HilitedDemo'].fg = activeTheme.fgH;
activeLayout['HilitedDemo'].bg = activeTheme.bgH;
break;
case 'DetailSelectionScreen':
activeLayout['fgView'].col = pendingTheme.fg;
activeLayout['bgView'].col = pendingTheme.bg;
activeLayout['fg2View'].col = pendingTheme.fg2;
activeLayout['bg2View'].col = pendingTheme.bg2;
activeLayout['fgHView'].col = pendingTheme.fgH;
activeLayout['bgHView'].col = pendingTheme.bgH;
break;
case 'ColorSelectionScreen':
updateColorSelection();
break;
case 'ThemePreviewScreen':
activeLayout['NormalTest'].fg = pendingTheme.fg;
activeLayout['NormalTest'].bg = pendingTheme.bg;
activeLayout['AccentedTest'].fg = pendingTheme.fg2;
activeLayout['AccentedTest'].bg = pendingTheme.bg2;
activeLayout['HilitedTest'].fg = pendingTheme.fgH;
activeLayout['HilitedTest'].bg = pendingTheme.bgH;
}
g.setColor('#000000'); g.setBgColor('#FFFFFF'); // assert legibility
g.clear();
activeLayout.render();
}
gotoScreen('MainScreen');