Skip to content

Commit e03c8c8

Browse files
authored
Merge pull request #2216 from myxor/slopeclockpp
Slope Clock ++
2 parents 1295556 + 92d540a commit e03c8c8

File tree

6 files changed

+236
-0
lines changed

6 files changed

+236
-0
lines changed

apps/slopeclockpp/ChangeLog

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.01: New App!

apps/slopeclockpp/app-icon.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/slopeclockpp/app.js

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
Graphics.prototype.setFontPaytoneOne = function(scale) {
2+
// Actual height 81 (91 - 11)
3+
this.setFontCustom(
4+
E.toString(require('heatshrink').decompress(atob('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'))),
5+
46,
6+
atob("ITZOMzs7SDxHNUdGIQ=="),
7+
113+(scale<<8)+(1<<16)
8+
);
9+
return this;
10+
};
11+
12+
{ // must be inside our own scope here so that when we are unloaded everything disappears
13+
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
14+
let drawTimeout;
15+
16+
let g2 = Graphics.createArrayBuffer(g.getWidth(),90,1,{msb:true});
17+
let g2img = {
18+
width:g2.getWidth(), height:g2.getHeight(), bpp:1,
19+
buffer:g2.buffer, transparent:0
20+
};
21+
const slope = 20;
22+
const offsy = 20; // offset of numbers from middle
23+
const fontBorder = 4; // offset from left/right
24+
const slopeBorder = 10, slopeBorderUpper = 4; // fudge-factor to move minutes down from slope
25+
let R,x,y; // middle of the clock face
26+
let dateStr = "";
27+
let bgColors = g.theme.dark ? ["#ff0","#0ff","#f0f"] : ["#f00","#0f0","#00f"];
28+
let bgColor = bgColors[(Math.random()*bgColors.length)|0];
29+
30+
31+
// Draw the hour, and the minute into an offscreen buffer
32+
let draw = function() {
33+
R = Bangle.appRect;
34+
x = R.w / 2;
35+
y = R.y + R.h / 2 - 12; // 12 = room for date
36+
var date = new Date();
37+
var hourStr = date.getHours();
38+
var minStr = date.getMinutes().toString().padStart(2,0);
39+
dateStr = require("locale").dow(date, 1).toUpperCase()+ " "+
40+
require("locale").date(date, 0).toUpperCase();
41+
42+
// Draw hour
43+
g.reset().clearRect(R); // clear whole background (w/o widgets)
44+
g.setFontAlign(-1, 0).setFont("PaytoneOne");
45+
g.drawString(hourStr, fontBorder, y-offsy);
46+
// add slope in background color
47+
g.setColor(g.theme.bg).fillPoly([0,y+slope-slopeBorderUpper, R.w,y-slope-slopeBorderUpper,
48+
R.w,y-slope, 0,y+slope]);
49+
50+
// Draw minute to offscreen buffer
51+
g2.setColor(0).fillRect(0,0,g2.getWidth(),g2.getHeight()).setFontAlign(1, 0).setFont("PaytoneOne");
52+
g2.setColor(1).drawString(minStr, g2.getWidth()-fontBorder, g2.getHeight()/2);
53+
g2.setColor(0).fillPoly([0,0, g2.getWidth(),0, 0,slope*2]);
54+
// start the animation *in*
55+
animate(true);
56+
57+
// queue next draw
58+
if (drawTimeout) clearTimeout(drawTimeout);
59+
drawTimeout = setTimeout(function() {
60+
drawTimeout = undefined;
61+
animate(false, function() {
62+
draw();
63+
});
64+
}, 60000 - (Date.now() % 60000));
65+
};
66+
67+
let isAnimIn = true;
68+
let animInterval;
69+
// Draw *just* the minute image
70+
let drawMinute = function() {
71+
var yo = slopeBorder + offsy + y - 2*slope*minuteX/R.w;
72+
// draw over the slanty bit
73+
g.setColor(bgColor).fillPoly([0,y+slope, R.w,y-slope, R.w,R.h+R.y, 0,R.h+R.y]);
74+
// draw the minutes
75+
g.setColor(g.theme.bg).drawImage(g2img, x+minuteX-(g2.getWidth()/2), yo-(g2.getHeight()/2));
76+
};
77+
let animate = function(isIn, callback) {
78+
if (animInterval) clearInterval(animInterval);
79+
isAnimIn = isIn;
80+
minuteX = isAnimIn ? -g2.getWidth() : 0;
81+
drawMinute();
82+
animInterval = setInterval(function() {
83+
minuteX += 8;
84+
let stop = false;
85+
if (isAnimIn && minuteX>=0) {
86+
minuteX=0;
87+
stop = true;
88+
} else if (!isAnimIn && minuteX>=R.w)
89+
stop = true;
90+
drawMinute();
91+
if (stop) {
92+
clearInterval(animInterval);
93+
animInterval=undefined;
94+
if (isAnimIn) {
95+
// draw the date
96+
g.setColor(g.theme.bg).setFontAlign(0, 0).setFont("6x15").drawString(dateStr, R.x + R.w/2, R.y+R.h-9);
97+
98+
// draw steps to bottom left
99+
const steps = getSteps();
100+
if (steps > 0)
101+
g.setFontAlign(-1, 0).drawString(shortValue(steps), 3, R.y+R.h-30);
102+
103+
// draw weather to top right
104+
const weather = getWeather();
105+
const tempString = weather ? require("locale").temp(weather.temp - 273.15) : undefined;
106+
const code = weather ? weather.code : -1;
107+
if (code > -1) {
108+
g.setColor(g.theme.fg).setFontAlign(1, 0).drawString(tempString, R.w - 3, y-slope-slopeBorderUpper);
109+
const icon = getWeatherIconByCode(code);
110+
if (icon) g.drawImage(icon, R.w - 3 - 15, y-slope-slopeBorderUpper - 15 - 15);
111+
}
112+
}
113+
if (callback) callback();
114+
}
115+
}, 20);
116+
};
117+
118+
let getSteps = function() {
119+
if (Bangle.getHealthStatus) {
120+
return Bangle.getHealthStatus("day").steps;
121+
}
122+
if (WIDGETS && WIDGETS.wpedom !== undefined) {
123+
return WIDGETS.wpedom.getSteps();
124+
}
125+
return 0;
126+
};
127+
128+
let shortValue = function(v) {
129+
if (isNaN(v)) return '-';
130+
if (v <= 999) return v;
131+
if (v >= 1000 && v < 10000) {
132+
v = Math.floor(v / 100) * 100;
133+
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
134+
}
135+
if (v >= 10000) {
136+
v = Math.floor(v / 1000) * 1000;
137+
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
138+
}
139+
};
140+
141+
let getWeather = function() {
142+
let jsonWeather = require("Storage").readJSON('weather.json');
143+
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
144+
};
145+
146+
/*
147+
* Choose weather icon to display based on weather conditition code
148+
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
149+
*/
150+
let getWeatherIconByCode = function(code) {
151+
let codeGroup = Math.round(code / 100);
152+
153+
// weather icons:
154+
let weatherCloudy = atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
155+
let weatherSunny = atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
156+
let weatherMoon = atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
157+
let weatherPartlyCloudy = atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
158+
let weatherRainy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
159+
let weatherPartlyRainy = atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
160+
let weatherSnowy = atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
161+
let weatherFoggy = atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
162+
let weatherStormy = atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
163+
let unknown = undefined;
164+
165+
switch (codeGroup) {
166+
case 2:
167+
return weatherStormy;
168+
case 3:
169+
return weatherCloudy;
170+
case 5:
171+
switch (code) {
172+
case 511:
173+
return weatherSnowy;
174+
case 520:
175+
return weatherPartlyRainy;
176+
case 521:
177+
return weatherPartlyRainy;
178+
case 522:
179+
return weatherPartlyRainy;
180+
case 531:
181+
return weatherPartlyRainy;
182+
default:
183+
return weatherRainy;
184+
}
185+
case 6:
186+
return weatherSnowy;
187+
case 7:
188+
return weatherFoggy;
189+
case 8:
190+
switch (code) {
191+
case 800:
192+
return weatherSunny;
193+
case 801:
194+
return weatherPartlyCloudy;
195+
case 802:
196+
return weatherPartlyCloudy;
197+
default:
198+
return weatherCloudy;
199+
}
200+
default:
201+
return unknown;
202+
}
203+
}
204+
205+
// Show launcher when middle button pressed
206+
Bangle.setUI({
207+
mode : "clock",
208+
remove : function() {
209+
// Called to unload all of the clock app
210+
if (animInterval) clearInterval(animInterval);
211+
animInterval = undefined;
212+
if (drawTimeout) clearTimeout(drawTimeout);
213+
drawTimeout = undefined;
214+
delete Graphics.prototype.setFontPaytoneOne;
215+
}});
216+
// Load widgets
217+
Bangle.loadWidgets();
218+
draw();
219+
setTimeout(Bangle.drawWidgets,0);
220+
}

apps/slopeclockpp/app.png

10.4 KB
Loading

apps/slopeclockpp/metadata.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{ "id": "slopeclockpp",
2+
"name": "Slope Clock ++",
3+
"version":"0.01",
4+
"description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows weather and steps.",
5+
"icon": "app.png",
6+
"screenshots": [{"url":"screenshot.png"}],
7+
"type": "clock",
8+
"tags": "clock",
9+
"supports" : ["BANGLEJS2"],
10+
"storage": [
11+
{"name":"slopeclockpp.app.js","url":"app.js"},
12+
{"name":"slopeclockpp.img","url":"app-icon.js","evaluate":true}
13+
]
14+
}

apps/slopeclockpp/screenshot.png

15.2 KB
Loading

0 commit comments

Comments
 (0)