Merge remote-tracking branch 'upstream/master'
|
@ -29,3 +29,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
|||
* Added progress bar on Bangle.js for uploads
|
||||
* Provide a proper error message in case JSON decode fails
|
||||
* Check you're connecting with a Bangle.js of the correct version
|
||||
* Allow 'data' style app files to be uploaded with the app (and switch over settings files for various apps)
|
||||
|
|
15
README.md
|
@ -249,14 +249,20 @@ and which gives information about the app for the Launcher.
|
|||
{"name":"appid.js", // filename to use in storage.
|
||||
// If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file
|
||||
"url":"", // URL of file to load (currently relative to apps/)
|
||||
"content":"..." // if supplied, this content is loaded directly
|
||||
"evaluate":true // if supplied, data isn't quoted into a String before upload
|
||||
"content":"...", // if supplied, this content is loaded directly
|
||||
"evaluate":true, // if supplied, data isn't quoted into a String before upload
|
||||
// (eg it's evaluated as JS)
|
||||
"noOverwrite":true // if supplied, this file will not be overwritten if it
|
||||
// already exists
|
||||
},
|
||||
]
|
||||
"data": [ // list of files the app writes to
|
||||
{"name":"appid.data.json", // filename used in storage
|
||||
"storageFile":true // if supplied, file is treated as storageFile
|
||||
"url":"", // if supplied URL of file to load (currently relative to apps/)
|
||||
"content":"...", // if supplied, this content is loaded directly
|
||||
"evaluate":true, // if supplied, data isn't quoted into a String before upload
|
||||
// (eg it's evaluated as JS)
|
||||
},
|
||||
{"wildcard":"appid.data.*" // wildcard of filenames used in storage
|
||||
}, // this is mutually exclusive with using "name"
|
||||
|
@ -439,13 +445,16 @@ The screen is parted in a widget and app area for lcd mode `direct`(default).
|
|||
| areas | as rectangle or point |
|
||||
| :-:| :-: |
|
||||
| Widget | (0,0,239,23) |
|
||||
| Apps | (0,24,239,239) |
|
||||
| Widget bottom bar (optional) | (0,216,239,239) |
|
||||
| Apps | (0,24,239,239) (see below) |
|
||||
| BTN1 | (230, 55) |
|
||||
| BTN2 | (230, 140) |
|
||||
| BTN3 | (230, 210) |
|
||||
| BTN4 | (0,0,119, 239)|
|
||||
| BTN5 | (120,0,239,239) |
|
||||
|
||||
- If there are widgets at the bottom of the screen, apps should actually keep the bottom 24px free, so should keep to the area (0,24,239,215)
|
||||
|
||||
- Use `g.setFontAlign(0, 0, 3)` to draw rotated string to BTN1-BTN3 with `g.drawString()`.
|
||||
|
||||
- For BTN4-5 the touch area is named
|
||||
|
|
611
apps.json
|
@ -4,11 +4,12 @@
|
|||
"tags": "tool,system",
|
||||
"type":"bootloader",
|
||||
"icon": "bootloader.png",
|
||||
"version":"0.22",
|
||||
"version":"0.25",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"storage": [
|
||||
{"name":".boot0","url":"boot0.js"},
|
||||
{"name":".bootcde","url":"bootloader.js"}
|
||||
{"name":".bootcde","url":"bootloader.js"},
|
||||
{"name":"bootupdate.js","url":"bootupdate.js"}
|
||||
],
|
||||
"sortorder" : -10
|
||||
},
|
||||
|
@ -41,7 +42,7 @@
|
|||
"name": "Launcher (Default)",
|
||||
"shortName":"Launcher",
|
||||
"icon": "app.png",
|
||||
"version":"0.04",
|
||||
"version":"0.06",
|
||||
"description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"tags": "tool,system,launcher",
|
||||
"type":"launch",
|
||||
|
@ -53,7 +54,7 @@
|
|||
{ "id": "about",
|
||||
"name": "About",
|
||||
"icon": "app.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
||||
"tags": "tool,system",
|
||||
"allow_emulator":true,
|
||||
|
@ -65,7 +66,7 @@
|
|||
{ "id": "locale",
|
||||
"name": "Languages",
|
||||
"icon": "locale.png",
|
||||
"version":"0.08",
|
||||
"version":"0.09",
|
||||
"description": "Translations for different countries",
|
||||
"tags": "tool,system,locale,translate",
|
||||
"type": "locale",
|
||||
|
@ -80,7 +81,7 @@
|
|||
"name": "Notifications (default)",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.05",
|
||||
"version":"0.08",
|
||||
"description": "A handler for displaying notifications that displays them in a bar at the top of the screen",
|
||||
"tags": "widget",
|
||||
"type": "notify",
|
||||
|
@ -93,7 +94,7 @@
|
|||
"name": "Fullscreen Notifications",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.06",
|
||||
"version":"0.08",
|
||||
"description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.",
|
||||
"tags": "widget",
|
||||
"type": "notify",
|
||||
|
@ -104,7 +105,7 @@
|
|||
{ "id": "welcome",
|
||||
"name": "Welcome",
|
||||
"icon": "app.png",
|
||||
"version":"0.09",
|
||||
"version":"0.10",
|
||||
"description": "Appears at first boot and explains how to use Bangle.js",
|
||||
"tags": "start,welcome",
|
||||
"allow_emulator":true,
|
||||
|
@ -139,7 +140,7 @@
|
|||
{ "id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"version":"0.18",
|
||||
"version":"0.22",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||
"tags": "tool,system,android,widget",
|
||||
"readme": "README.md",
|
||||
|
@ -171,22 +172,24 @@
|
|||
{ "id": "setting",
|
||||
"name": "Settings",
|
||||
"icon": "settings.png",
|
||||
"version":"0.23",
|
||||
"version":"0.26",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"tags": "tool,system",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"setting.app.js","url":"settings.js"},
|
||||
{"name":"setting.boot.js","url":"boot.js"},
|
||||
{"name":"setting.img","url":"settings-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"setting.json", "url":"settings.min.json","evaluate":true}
|
||||
],
|
||||
"sortorder" : -2
|
||||
},
|
||||
{ "id": "alarm",
|
||||
"name": "Default Alarm",
|
||||
"shortName":"Alarms",
|
||||
"icon": "app.png",
|
||||
"version":"0.10",
|
||||
"version":"0.11",
|
||||
"description": "Set and respond to alarms",
|
||||
"tags": "tool,alarm,widget",
|
||||
"storage": [
|
||||
|
@ -213,6 +216,69 @@
|
|||
{"name":"wclock.img","url":"clock-word-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "fontclock",
|
||||
"name": "Font Clock",
|
||||
"icon": "fontclock.png",
|
||||
"version":"0.01",
|
||||
"description": "Choose the font and design of clock face from a library of available designs",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":false,
|
||||
"readme": "README.md",
|
||||
"custom":"custom.html",
|
||||
"storage": [
|
||||
{"name":"fontclock.app.js","url":"fontclock.js"},
|
||||
{"name":"fontclock.img","url":"fontclock-icon.js","evaluate":true},
|
||||
{"name":"fontclock.hand.js","url":"fontclock.hand.js"},
|
||||
{"name":"fontclock.thinhand.js","url":"fontclock.thinhand.js"},
|
||||
{"name":"fontclock.thickhand.js","url":"fontclock.thickhand.js"},
|
||||
{"name":"fontclock.hourscriber.js","url":"fontclock.hourscriber.js"},
|
||||
{"name":"fontclock.font.js","url":"fontclock.font.js"},
|
||||
{"name":"fontclock.font.abril_ff50.js","url":"fontclock.font.abril_ff50.js"},
|
||||
{"name":"fontclock.font.cpstc58.js","url":"fontclock.font.cpstc58.js"},
|
||||
{"name":"fontclock.font.mntn25.js","url":"fontclock.font.mntn25.js"},
|
||||
{"name":"fontclock.font.mntn50.js","url":"fontclock.font.mntn50.js"},
|
||||
{"name":"fontclock.font.vector25.js","url":"fontclock.font.vector25.js"},
|
||||
{"name":"fontclock.font.vector50.js","url":"fontclock.font.vector50.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "slidingtext",
|
||||
"name": "Sliding Clock",
|
||||
"icon": "slidingtext.png",
|
||||
"version":"0.05",
|
||||
"description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":false,
|
||||
"readme": "README.md",
|
||||
"custom":"custom.html",
|
||||
"storage": [
|
||||
{"name":"slidingtext.app.js","url":"slidingtext.js"},
|
||||
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true},
|
||||
{"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"},
|
||||
{"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"},
|
||||
{"name":"slidingtext.utils.en.js","url":"slidingtext.utils.en.js"},
|
||||
{"name":"slidingtext.locale.es.js","url":"slidingtext.locale.es.js"},
|
||||
{"name":"slidingtext.locale.fr.js","url":"slidingtext.locale.fr.js"},
|
||||
{"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"},
|
||||
{"name":"slidingtext.locale.de.js","url":"slidingtext.locale.de.js"},
|
||||
{"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "sweepclock",
|
||||
"name": "Sweep Clock",
|
||||
"icon": "sweepclock.png",
|
||||
"version":"0.04",
|
||||
"description": "Smooth sweep secondhand with single hour numeral. Use button 1 to toggle the numeral font, button 3 to change the colour theme and button 4 to change the date placement",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"sweepclock.app.js","url":"sweepclock.js"},
|
||||
{"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "imgclock",
|
||||
"name": "Image background clock",
|
||||
"shortName":"Image Clock",
|
||||
|
@ -246,7 +312,7 @@
|
|||
{ "id": "aclock",
|
||||
"name": "Analog Clock",
|
||||
"icon": "clock-analog.png",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"description": "An Analog Clock",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
|
@ -269,6 +335,30 @@
|
|||
{"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "geissclk",
|
||||
"name": "Geiss Clock",
|
||||
"icon": "clock.png",
|
||||
"version":"0.02",
|
||||
"description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"storage": [
|
||||
{"name":"geissclk.app.js","url":"clock.js"},
|
||||
{"name":"geissclk.precompute.js","url":"precompute.js"},
|
||||
{"name":"geissclk.img","url":"clock-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"geissclk.0.map"},
|
||||
{"name":"geissclk.1.map"},
|
||||
{"name":"geissclk.2.map"},
|
||||
{"name":"geissclk.3.map"},
|
||||
{"name":"geissclk.4.map"},
|
||||
{"name":"geissclk.5.map"},
|
||||
{"name":"geissclk.0.pal"},
|
||||
{"name":"geissclk.1.pal"},
|
||||
{"name":"geissclk.2.pal"}
|
||||
]
|
||||
},
|
||||
{ "id": "trex",
|
||||
"name": "T-Rex",
|
||||
"icon": "trex.png",
|
||||
|
@ -363,7 +453,7 @@
|
|||
{ "id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.17",
|
||||
"version":"0.19",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -388,14 +478,16 @@
|
|||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"gpsnav.app.js","url":"app.min.js"},
|
||||
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
|
||||
{"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"waypoints.json","url":"waypoints.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "heart",
|
||||
"name": "Heart Rate Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"version":"0.05",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record your heart rate. Can run in background",
|
||||
"tags": "tool,health,widget",
|
||||
|
@ -479,7 +571,7 @@
|
|||
{ "id": "widbat",
|
||||
"name": "Battery Level Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "Show the current battery level and charging status in the top right of the clock",
|
||||
"tags": "widget,battery",
|
||||
"type":"widget",
|
||||
|
@ -487,6 +579,17 @@
|
|||
{"name":"widbat.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "widlock",
|
||||
"name": "Lock Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
|
||||
"tags": "widget,lock",
|
||||
"type":"widget",
|
||||
"storage": [
|
||||
{"name":"widlock.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "widbatpc",
|
||||
"name": "Battery Level Widget (with percentage)",
|
||||
"shortName": "Battery Widget",
|
||||
|
@ -508,7 +611,7 @@
|
|||
"shortName": "Battery Warning",
|
||||
"icon": "widget.png",
|
||||
"readme": "README.md",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Show a warning when the battery runs low.",
|
||||
"tags": "tool,battery",
|
||||
"type":"widget",
|
||||
|
@ -532,6 +635,21 @@
|
|||
{"name":"widbt.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "widchime",
|
||||
"name": "Hour Chime",
|
||||
"icon": "widget.png",
|
||||
"version":"0.02",
|
||||
"description": "Buzz or beep on every whole hour.",
|
||||
"tags": "widget",
|
||||
"type": "widget",
|
||||
"storage": [
|
||||
{"name":"widchime.wid.js","url":"widget.js"},
|
||||
{"name":"widchime.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"widchime.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "widram",
|
||||
"name": "RAM Widget",
|
||||
"shortName":"RAM Widget",
|
||||
|
@ -547,7 +665,7 @@
|
|||
{ "id": "hrm",
|
||||
"name": "Heart Rate Monitor",
|
||||
"icon": "heartrate.png",
|
||||
"version":"0.02",
|
||||
"version":"0.04",
|
||||
"description": "Measure your heart rate and see live sensor data",
|
||||
"tags": "health",
|
||||
"storage": [
|
||||
|
@ -673,7 +791,7 @@
|
|||
{ "id": "route",
|
||||
"name": "Route Viewer",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Upload a KML file of a route, and have your watch display a map with how far around it you are",
|
||||
"tags": "",
|
||||
"custom": "custom.html",
|
||||
|
@ -739,10 +857,23 @@
|
|||
{"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "s7clk",
|
||||
"name": "Simple 7 segment Clock",
|
||||
"icon": "icon.png",
|
||||
"version":"0.02",
|
||||
"description": "A simple 7 segment Clock with date",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"s7clk.app.js","url":"app.js"},
|
||||
{"name":"s7clk.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "vibrclock",
|
||||
"name": "Vibrate Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
|
@ -970,7 +1101,7 @@
|
|||
"name": "Large Digit Blob Clock",
|
||||
"shortName" : "Blob Clock",
|
||||
"icon": "clock-blob.png",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "A clock with big digits",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
|
@ -1007,7 +1138,7 @@
|
|||
{ "id": "widpedom",
|
||||
"name": "Pedometer widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.11",
|
||||
"version":"0.13",
|
||||
"description": "Daily pedometer widget",
|
||||
"tags": "widget",
|
||||
"type":"widget",
|
||||
|
@ -1138,7 +1269,7 @@
|
|||
{ "id": "marioclock",
|
||||
"name": "Mario Clock",
|
||||
"icon": "marioclock.png",
|
||||
"version":"0.14",
|
||||
"version":"0.15",
|
||||
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
||||
"tags": "clock,mario,retro",
|
||||
"type": "clock",
|
||||
|
@ -1153,7 +1284,7 @@
|
|||
"name": "Commandline-Clock",
|
||||
"shortName":"CLI-Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.11",
|
||||
"version":"0.12",
|
||||
"description": "Simple CLI-Styled Clock",
|
||||
"tags": "clock,cli,command,bash,shell",
|
||||
"type":"clock",
|
||||
|
@ -1190,7 +1321,7 @@
|
|||
{ "id": "dotclock",
|
||||
"name": "Dot Clock",
|
||||
"icon": "clock-dot.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A Minimal Dot Analog Clock",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
|
@ -1492,7 +1623,7 @@
|
|||
"name": "Digital Assistant, not EDITH",
|
||||
"shortName": "DANE",
|
||||
"icon": "app.png",
|
||||
"version": "0.15",
|
||||
"version": "0.16",
|
||||
"description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
@ -1553,7 +1684,7 @@
|
|||
"name": "BangleRun",
|
||||
"shortName": "BangleRun",
|
||||
"icon": "banglerun.png",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"interface": "interface.html",
|
||||
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
|
||||
"tags": "run,running,fitness,outdoors",
|
||||
|
@ -1636,9 +1767,9 @@
|
|||
{
|
||||
"id": "rclock",
|
||||
"name": "Round clock with seconds, minutes and date",
|
||||
"shortName":"Round Clock",
|
||||
"shortName": "Round Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.03",
|
||||
"version": "0.05",
|
||||
"description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
@ -1647,6 +1778,20 @@
|
|||
{"name":"rclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fclock",
|
||||
"name": "fclock",
|
||||
"shortName": "F Clock",
|
||||
"icon": "app.png",
|
||||
"version": "0.01",
|
||||
"description": "Simple design of a digital clock",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"fclock.app.js","url":"fclock.app.js"},
|
||||
{"name":"fclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "hamloc",
|
||||
"name": "QTH Locator / Maidenhead Locator System",
|
||||
"shortName": "QTH Locator",
|
||||
|
@ -1835,7 +1980,7 @@
|
|||
"id": "beebclock",
|
||||
"name": "Beeb Clock",
|
||||
"icon": "beebclock.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Clock face that may be coincidentally familiar to BBC viewers",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
@ -2035,7 +2180,7 @@
|
|||
"name": "SleepPhaseAlarm",
|
||||
"shortName":"SleepPhaseAlarm",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
|
||||
"tags": "alarm",
|
||||
"storage": [
|
||||
|
@ -2139,7 +2284,7 @@
|
|||
{ "id": "multiclock",
|
||||
"name": "Multi Clock",
|
||||
"icon": "multiclock.png",
|
||||
"version":"0.11",
|
||||
"version":"0.13",
|
||||
"description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3",
|
||||
"readme": "README.md",
|
||||
"tags": "clock",
|
||||
|
@ -2153,8 +2298,6 @@
|
|||
{"name":"txt.face.js","url":"txt.js"},
|
||||
{"name":"timdat.face.js","url":"timdat.js"},
|
||||
{"name":"ped.face.js","url":"ped.js"},
|
||||
{"name":"gps.face.js","url":"gps.js"},
|
||||
{"name":"osref.face.js","url":"osref.js"},
|
||||
{"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
@ -2162,7 +2305,7 @@
|
|||
"name": "Apple Notification Widget",
|
||||
"shortName":"ANCS Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps",
|
||||
"readme": "README.md",
|
||||
"tags": "widget",
|
||||
|
@ -2259,7 +2402,7 @@
|
|||
{"id": "counter",
|
||||
"name": "Counter",
|
||||
"icon": "counter_icon.png",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Simple counter",
|
||||
"tags": "tool",
|
||||
"allow_emulator": true,
|
||||
|
@ -2336,9 +2479,11 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"worldclock.app.js","url":"app.js"},
|
||||
{"name":"worldclock.settings.json"},
|
||||
{"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [
|
||||
{"name":"worldclock.settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "digiclock",
|
||||
"name": "Digital Clock Face",
|
||||
|
@ -2530,7 +2675,7 @@
|
|||
"name": "Hard Alarm",
|
||||
"shortName":"HardAlarm",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Make sure you wake up! Count to the right number to turn off the alarm",
|
||||
"tags": "tool,alarm,widget",
|
||||
"storage": [
|
||||
|
@ -2581,14 +2726,16 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"breath.app.js","url":"app.js"},
|
||||
{"name":"breath.settings.json","url":"settings.json"},
|
||||
{"name":"breath.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"breath.settings.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "lazyclock",
|
||||
"name": "Lazy Clock",
|
||||
"icon": "lazyclock.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"readme": "README.md",
|
||||
"description": "Tells the time, roughly",
|
||||
"tags": "clock",
|
||||
|
@ -2639,11 +2786,11 @@
|
|||
]
|
||||
},
|
||||
{ "id": "speedalt",
|
||||
"name": "GPS Speedo and Altimeter",
|
||||
"shortName":"GPS Speed Alt",
|
||||
"name": "GPS Adventure Sports",
|
||||
"shortName":"GPS Adv Sport",
|
||||
"icon": "app.png",
|
||||
"version":"0.07",
|
||||
"description": "GPS speed and altitude display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
|
||||
"version":"1.02",
|
||||
"description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
|
||||
"tags": "tool,outdoors",
|
||||
"type":"app",
|
||||
"allow_emulator":true,
|
||||
|
@ -2679,9 +2826,11 @@
|
|||
"storage": [
|
||||
{"name":"gpsservice.app.js","url":"app.js"},
|
||||
{"name":"gpsservice.settings.js","url":"settings.js"},
|
||||
{"name":"gpsservice.settings.json","url":"settings.json"},
|
||||
{"name":"gpsservice.wid.js","url":"widget.js"},
|
||||
{"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gpsservice.settings.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "mclockplus",
|
||||
|
@ -2714,12 +2863,15 @@
|
|||
"name": "Planetarium",
|
||||
"shortName":"Planetarium",
|
||||
"icon": "planetarium.png",
|
||||
"version":"0.02",
|
||||
"readme": "README.md",
|
||||
"version":"0.03",
|
||||
"description": "Planetarium showing up to 500 stars using the watch location and time",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"planetarium.app.js","url":"planetarium.app.js"},
|
||||
{"name":"planetarium.data.csv","url":"planetarium.data.csv"},
|
||||
{"name":"planetarium.const.csv","url":"planetarium.const.csv"},
|
||||
{"name":"planetarium.extra.csv","url":"planetarium.extra.csv"},
|
||||
{"name":"planetarium.settings.js","url":"settings.js"},
|
||||
{"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true}
|
||||
],
|
||||
|
@ -2730,14 +2882,14 @@
|
|||
{ "id": "tapelauncher",
|
||||
"name": "Tape Launcher",
|
||||
"icon": "icon.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons",
|
||||
"readme": "README.md",
|
||||
"tags": "tool,system,launcher",
|
||||
"type":"launch",
|
||||
"storage": [
|
||||
{"name":"tapelaunch.app.js","url":"app.js"},
|
||||
{"name":"tapelaunch.img","url":"icon.js","evaluate":true}
|
||||
{"name":"tapelauncher.app.js","url":"app.js"},
|
||||
{"name":"tapelauncher.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "oblique",
|
||||
|
@ -2750,6 +2902,361 @@
|
|||
{"name":"oblique.app.js","url":"app.js"},
|
||||
{"name":"oblique.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "testuserinput",
|
||||
"name": "Test User Input",
|
||||
"shortName":"Test User Input",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.",
|
||||
"readme": "README.md",
|
||||
"tags": "input,interface,buttons,touch,UI",
|
||||
"storage": [
|
||||
{"name":"testuserinput.app.js","url":"app.js"},
|
||||
{"name":"testuserinput.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "gpssetup",
|
||||
"name": "GPS Setup",
|
||||
"shortName":"GPS Setup",
|
||||
"icon": "gpssetup.png",
|
||||
"version":"0.02",
|
||||
"description": "Configure the GPS power options and store them in the GPS nvram",
|
||||
"tags": "gps, tools, outdoors",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gpssetup","url":"gpssetup.js"},
|
||||
{"name":"gpssetup.settings.js","url":"settings.js"},
|
||||
{"name":"gpssetup.app.js","url":"app.js"},
|
||||
{"name":"gpssetup.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gpssetup.settings.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "walkersclock",
|
||||
"name": "Walkers Clock",
|
||||
"shortName":"Walkers Clock",
|
||||
"icon": "walkersclock48.png",
|
||||
"version":"0.04",
|
||||
"description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference",
|
||||
"type":"clock",
|
||||
"tags": "clock, gps, tools, outdoors",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"walkersclock.app.js","url":"app.js"},
|
||||
{"name":"walkersclock.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "widgps",
|
||||
"name": "GPS Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.02",
|
||||
"description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later",
|
||||
"tags": "widget,gps",
|
||||
"type":"widget",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widgps.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "widhrt",
|
||||
"name": "HRM Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.02",
|
||||
"description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later",
|
||||
"tags": "widget, hrm",
|
||||
"type":"widget",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widhrt.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "countdowntimer",
|
||||
"name" : "Countdown Timer",
|
||||
"icon": "countdowntimer.png",
|
||||
"version": "0.01",
|
||||
"description": "A simple countdown timer with a focus on usability",
|
||||
"tags": "timer, tool",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name": "countdowntimer.app.js", "url": "countdowntimer.js"},
|
||||
{"name": "countdowntimer.img", "url": "countdowntimer-icon.js", "evaluate": true}
|
||||
]
|
||||
},
|
||||
{ "id": "helloworld",
|
||||
"name": "hello, world!",
|
||||
"shortName":"hello world",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"description": "A cross cultural hello world!/hola mundo! app with colors and languages",
|
||||
"readme": "README.md",
|
||||
"tags": "input,interface,buttons,touch",
|
||||
"storage": [
|
||||
{"name":"helloworld.app.js","url":"app.js"},
|
||||
{"name":"helloworld.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "widcom",
|
||||
"name": "Compass Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later",
|
||||
"tags": "widget, compass",
|
||||
"type":"widget",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"widcom.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "arrow",
|
||||
"name": "Arrow Compass",
|
||||
"icon": "arrow.png",
|
||||
"type":"app",
|
||||
"version":"0.04",
|
||||
"description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass",
|
||||
"tags": "tool,outdoors",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"arrow.app.js","url":"app.js"},
|
||||
{"name":"arrow.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "waypointer",
|
||||
"name": "Way Pointer",
|
||||
"icon": "waypointer.png",
|
||||
"version":"0.01",
|
||||
"description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"readme": "README.md",
|
||||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"waypointer.app.js","url":"app.js"},
|
||||
{"name":"waypointer.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"waypoints.json","url":"waypoints.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "color_catalog",
|
||||
"name": "Colors Catalog",
|
||||
"shortName":"Colors Catalog",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
|
||||
"readme": "README.md",
|
||||
"tags": "Color, input,buttons,touch,UI",
|
||||
"storage": [
|
||||
{"name":"color_catalog.app.js","url":"app.js"},
|
||||
{"name":"color_catalog.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "UI4swatch",
|
||||
"name": "UI 4 swatch",
|
||||
"shortName":"UI 4 swatch",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.",
|
||||
"readme": "README.md",
|
||||
"tags": "Color, input,buttons,touch,UI",
|
||||
"storage": [
|
||||
{"name":"UI4swatch.app.js","url":"app.js"},
|
||||
{"name":"UI4swatch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "simplest",
|
||||
"name": "Simplest Clock",
|
||||
"icon": "simplest.png",
|
||||
"version":"0.01",
|
||||
"description": "The simplest working clock, acts as a tutorial piece",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"simplest.app.js","url":"app.js"},
|
||||
{"name":"simplest.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "stepo",
|
||||
"name": "Stepometer Clock",
|
||||
"icon": "stepo.png",
|
||||
"version":"0.03",
|
||||
"description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"stepo.app.js","url":"app.js"},
|
||||
{"name":"stepo.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "gbmusic",
|
||||
"name": "Gadgetbridge Music Controls",
|
||||
"shortName":"Music Controls",
|
||||
"icon": "icon.png",
|
||||
"version":"0.05",
|
||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||
"tags": "tools,bluetooth,gadgetbridge,music",
|
||||
"type":"app",
|
||||
"allow_emulator": false,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gbmusic.app.js","url":"app.js"},
|
||||
{"name":"gbmusic.settings.js","url":"settings.js"},
|
||||
{"name":"gbmusic.wid.js","url":"widget.js"},
|
||||
{"name":"gbmusic.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gbmusic.json"},
|
||||
{"name":"gbmusic.load.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "battleship",
|
||||
"name":"Battleship",
|
||||
"icon":"battleship-icon.png",
|
||||
"version": "0.01",
|
||||
"readme": "README.md",
|
||||
"description": "The classic game of battleship",
|
||||
"tags": "game",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{
|
||||
"name": "battleship.app.js",
|
||||
"url": "battleship.js"
|
||||
},
|
||||
{
|
||||
"name": "battleship.img",
|
||||
"url": "battleship-icon.js",
|
||||
"evaluate": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "id": "kitchen",
|
||||
"name": "Kitchen Combo",
|
||||
"icon": "kitchen.png",
|
||||
"version":"0.10",
|
||||
"description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"type":"clock",
|
||||
"readme": "README.md",
|
||||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"kitchen.app.js","url":"kitchen.app.js"},
|
||||
{"name":"stepo.kit.js","url":"stepo.kit.js"},
|
||||
{"name":"gps.kit.js","url":"gps.kit.js"},
|
||||
{"name":"digi.kit.js","url":"digi.kit.js"},
|
||||
{"name":"heart.kit.js","url":"heart.kit.js"},
|
||||
{"name":"swatch.kit.js","url":"swatch.kit.js"},
|
||||
{"name":"compass.kit.js","url":"compass.kit.js"},
|
||||
{"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"waypoints.json","url":"waypoints.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "qmsched",
|
||||
"name": "Quiet Mode Schedule",
|
||||
"shortName":"Quiet Mode",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||
"readme": "README.md",
|
||||
"tags": "tool",
|
||||
"storage": [
|
||||
{"name":"qmsched","url":"lib.js"},
|
||||
{"name":"qmsched.app.js","url":"app.js"},
|
||||
{"name":"qmsched.boot.js","url":"boot.js"},
|
||||
{"name":"qmsched.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"qmsched.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hourstrike",
|
||||
"name": "Hour Strike",
|
||||
"shortName": "Hour Strike",
|
||||
"icon": "app-icon.png",
|
||||
"version": "0.07",
|
||||
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
|
||||
"tags": "tool,alarm",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"hourstrike.app.js","url":"app.js"},
|
||||
{"name":"hourstrike.boot.js","url":"boot.js"},
|
||||
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "whereworld",
|
||||
"name": "Where in the World?",
|
||||
"shortName" : "Where World",
|
||||
"icon": "app.png",
|
||||
"version": "0.01",
|
||||
"description": "Shows your current location on the world map",
|
||||
"tags": "gps",
|
||||
"storage": [
|
||||
{"name":"whereworld.app.js","url":"app.js"},
|
||||
{"name":"whereworld.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"whereworld.worldmap","url":"worldmap"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "omnitrix",
|
||||
"name":"Omnitrix",
|
||||
"icon":"omnitrix.png",
|
||||
"version": "0.01",
|
||||
"readme": "README.md",
|
||||
"description": "An Omnitrix Showpiece",
|
||||
"tags": "game",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"omnitrix.app.js","url":"omnitrix.app.js"},
|
||||
{"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "batclock",
|
||||
"name": "Bat Clock",
|
||||
"shortName":"Bat Clock",
|
||||
"icon": "bat-clock.png",
|
||||
"version":"0.01",
|
||||
"description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"batclock.app.js","url":"bat-clock.app.js"},
|
||||
{"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id":"doztime",
|
||||
"name":"Dozenal Time",
|
||||
"shortName":"Dozenal Time",
|
||||
"icon":"app.png",
|
||||
"version":"0.01",
|
||||
"description":"A dozenal Holocene calendar and dozenal diurnal clock",
|
||||
"tags":"clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"doztime.app.js","url":"app.js"},
|
||||
{"name":"doztime.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id":"gbtwist",
|
||||
"name":"Gadgetbridge Twist Control",
|
||||
"shortName":"Twist Control",
|
||||
"icon":"app.png",
|
||||
"version":"0.01",
|
||||
"description":"Shake your wrist to control your music app via Gadgetbridge",
|
||||
"tags":"tools,bluetooth,gadgetbridge,music",
|
||||
"type":"app",
|
||||
"allow_emulator":false,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gbtwist.app.js","url":"app.js"},
|
||||
{"name":"gbtwist.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: 1st ver, defining a common UI/UX
|
|
@ -0,0 +1,38 @@
|
|||
# UI/UX for Espruino Smartwatches
|
||||
|
||||
This is a very basic app that defines a common UI/UX for espruino smartwatchesm and specifically for the *bangle.js*, also it displays dinamically calculated x,y position coordinates and screen areas based in detected smartwatch models.
|
||||
|
||||
|
||||
Launcher icon
|
||||
|
||||

|
||||
|
||||
1st screen - Main page
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Open and see x,y coordinates for areas
|
||||
Interact with buttons or touch screen to print the event or leave the app
|
||||
|
||||
## Features
|
||||
|
||||
Colours, font, user input,, load widgets
|
||||
|
||||
|
||||
## Controls
|
||||
Press left area - Prints Touch1
|
||||
Press righ area - Prints Touch2
|
||||
Press center area - Prints Touch3
|
||||
Swipe Left - Prints <--
|
||||
Swipe Right - Prints -->
|
||||
BTN1 - Prints Button1
|
||||
BTN2 - Prints Button2
|
||||
BTN3 - Quit to Launcher
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
This app is so basic that probably the easiest is to just edit the code
|
||||
Otherwise you can contact me [here](https://github.com/dapgo)
|
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 75 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AEMimYASkQXBkYXTmQXzn//mYBBn/zC6nzC6wvTR/4XG/4ASC/4XlQgwOCkQAEdAIXEdI4OBBIwvGC5JHNC/J3XL/53/C/4JGO/533L4wAJC65HNC9rKFAB4XBAH4AeA="))
|
|
@ -0,0 +1,196 @@
|
|||
/* UI/UX for Espruino and Bangle.js
|
||||
Testing USer interface and User usability
|
||||
v20200307
|
||||
identify device and dimensions
|
||||
max printable position max_x-1 i.e 239
|
||||
*/
|
||||
|
||||
var colbackg='#111111';//black
|
||||
var colorange='#e56e06'; //RGB format rrggbb
|
||||
var v_color_lines=0xFFFF; //White hex format
|
||||
var v_color_b_area='#111111';
|
||||
var v_color_text=0x07E0;//'#FB0E01';//red
|
||||
var v_font1size=10; //out of quotes
|
||||
var v_font2size=12;
|
||||
var v_font_banner_size=30;
|
||||
var v_clicks=0;
|
||||
//pend identify widget area dinamically
|
||||
var v_model=process.env.BOARD;
|
||||
console.log("device="+v_model);
|
||||
|
||||
var x_max_screen=g.getWidth();//240;
|
||||
var y_max_screen=g.getHeight(); //240;
|
||||
var y_wg_bottom=g.getHeight()-25;
|
||||
var y_wg_top=25;
|
||||
if (v_model=='BANGLEJS') {
|
||||
var x_btn_area=215;
|
||||
var x_max_usable_area=x_btn_area;//Pend! only for bangle.js
|
||||
var y_btn2=124; //harcoded for bangle.js cuz it is not the half of display height
|
||||
} else x_max_usable_area=240;
|
||||
|
||||
|
||||
var x_mid_screen=x_max_screen/2;
|
||||
|
||||
//PEND comment
|
||||
console.log("*** UI dimensions***");
|
||||
console.log("x="+x_max_screen);
|
||||
console.log("y_wg_bottom="+y_wg_bottom);
|
||||
|
||||
|
||||
|
||||
//the biggest usable area, button area not included
|
||||
function ClearActiveArea(){
|
||||
g.setColor(v_color_b_area);
|
||||
g.fillRect(0,y_wg_top,x_max_usable_area,y_wg_bottom); //fill all screen except widget areas
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
//Clean fill top area with a color
|
||||
function ClearBannerArea(){
|
||||
g.setColor(v_color_b_area);
|
||||
g.fillRect(55,28,185,60);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
function PrintUserInput(boton){
|
||||
//var v_font_banner_size=30;//now global size= almost px height
|
||||
console.log("Pressed touch/BTN",boton);
|
||||
ClearBannerArea();
|
||||
g.setColor(colorange);
|
||||
//print in banner area
|
||||
g.setFontVector(v_font_banner_size).drawString(boton, 63, 29);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function PrintBtn1(boton){
|
||||
console.log("Pressed BTN1");
|
||||
PrintUserInput("Button1");
|
||||
|
||||
}
|
||||
|
||||
function PrintBtn2(boton){
|
||||
console.log("Pressed BTN2");
|
||||
PrintUserInput("Button2");
|
||||
}
|
||||
|
||||
function DrawBangleButtons(){
|
||||
g.setFontVector(v_font1size);
|
||||
g.setColor(v_color_lines);//White
|
||||
//line-separation with buttons area
|
||||
g.drawLine(x_btn_area, 35, x_btn_area, 180);//vline right sep buttons
|
||||
|
||||
//x for Border position in 2 lines
|
||||
g.drawString("x=",x_max_screen-g.stringWidth("x= "),68);
|
||||
g.drawString(x_max_screen,x_max_screen-g.stringWidth("3ch"),78);
|
||||
|
||||
g.drawString("Dwn", x_max_screen-g.stringWidth("Dwn"),y_wg_top+v_font1size+1);
|
||||
//above Btn2
|
||||
//g.setFontVector(v_font1size).drawString("Off", x_max_screen-g.stringWidth("Off"),y_btn2-(2*v_font1size));
|
||||
g.drawString("Set", x_max_screen-g.stringWidth("Set"),y_btn2-v_font1size);
|
||||
//above Btn3
|
||||
g.drawString("Quit", x_max_screen-g.stringWidth("Quit"),y_wg_bottom-(2*v_font1size));
|
||||
g.flip();
|
||||
g.setColor(v_color_text); //green
|
||||
g.setFontVector(v_font1size);
|
||||
g.drawString("B1", x_max_screen-g.stringWidth("B1"),y_wg_top);
|
||||
g.drawString("B2", x_max_screen-g.stringWidth("B2"),y_btn2);
|
||||
g.drawString("B3",x_max_screen-g.stringWidth("B3"),y_wg_bottom-v_font1size);
|
||||
g.flip();
|
||||
}
|
||||
function DrawBottomInfoBanner(){
|
||||
/* External Vars:v_color_text,v_font2size,x_max_usable_area,y_wg_bottom
|
||||
*/
|
||||
g.setColor(v_color_text);
|
||||
var info_text1="Swipe: Next/Back screen";
|
||||
var info_text2="Touch: Left=Up Right=Down";
|
||||
//aligned left of max usable area
|
||||
g.setFontVector(v_font2size);
|
||||
g.drawString(info_text2, x_max_usable_area-g.stringWidth(info_text2)-2 ,y_wg_bottom-(2*v_font2size));
|
||||
g.drawString(info_text1, x_max_usable_area-g.stringWidth(info_text1)-2 ,y_wg_bottom-v_font2size);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function PrintAreas(){
|
||||
console.log("********************************");
|
||||
console.log("Log: *** Print Areas in screen");
|
||||
ClearActiveArea();
|
||||
g.setColor(v_color_lines);
|
||||
|
||||
// **** Borders and Separation Lines for areas
|
||||
g.drawLine(1, 35, 1, 180);//line for left border
|
||||
//
|
||||
g.drawLine(x_max_screen-1, 50, x_max_screen-1, 75);//vlide right border
|
||||
g.drawLine(x_mid_screen, 80, x_mid_screen, 105);//vline middle separation part1 up
|
||||
g.setFontVector(v_font2size).drawString("Output area for "+v_model,(x_max_usable_area-g.stringWidth("Output area for "+v_model))/2,67);
|
||||
g.setFontVector(v_font2size).drawString("x="+x_mid_screen,x_mid_screen-g.stringWidth("x=xxx"),85);
|
||||
g.drawLine(x_mid_screen, 140, x_mid_screen, 180);//vline middle separation part2 down
|
||||
|
||||
|
||||
//y=26 after widget line y=215 below widget line
|
||||
|
||||
if (v_model=='BANGLEJS') DrawBangleButtons();
|
||||
|
||||
g.setFontVector(v_font2size);
|
||||
g.setColor(v_color_text);
|
||||
g.drawString("Touch", 80,110);
|
||||
g.drawString("Middle area", 80,125);
|
||||
g.drawString("Left area", 15, 145);
|
||||
g.drawString("Right area", 140,145);
|
||||
g.flip();
|
||||
DrawBottomInfoBanner();
|
||||
}
|
||||
|
||||
function UserInput(){
|
||||
Bangle.on('touch', function(button){
|
||||
switch(button){
|
||||
case 1:
|
||||
PrintUserInput("Touch 1");//left
|
||||
break;
|
||||
case 2:
|
||||
PrintUserInput("Touch 2");//right
|
||||
break;
|
||||
case 3:
|
||||
PrintUserInput("Touch 3");//center 1+2
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (v_model=='BANGLEJS') {
|
||||
//only the name of the function
|
||||
setWatch(PrintBtn1, BTN1, { repeat: true });
|
||||
setWatch(PrintBtn2, BTN2, { repeat: true });
|
||||
setWatch(Bangle.showLauncher, BTN3, { repeat: true });
|
||||
}
|
||||
Bangle.on('swipe', dir => {
|
||||
if(dir == 1) PrintUserInput(" --->");
|
||||
else PrintUserInput(" <---");
|
||||
});
|
||||
console.log("Log: Input conditions loaded");
|
||||
} //end of UserInput
|
||||
|
||||
//Main code
|
||||
ClearActiveArea();
|
||||
PrintAreas();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
||||
|
||||
//optional lines below and above both widget areas
|
||||
g.setColor(v_color_lines);
|
||||
//line-separation with top widget area
|
||||
g.drawLine(60, y_wg_top, 180, y_wg_top);
|
||||
g.setFontVector(v_font2size).drawString("y="+y_wg_top,10,y_wg_top+1);
|
||||
//line-separation with bottom widget area
|
||||
g.drawLine(60, y_wg_bottom, 180, y_wg_bottom);
|
||||
g.setFontVector(v_font2size).drawString("y="+y_wg_bottom,10,y_wg_bottom-v_font2size);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
g.flip();
|
||||
//end optional
|
||||
UserInput();
|
After Width: | Height: | Size: 718 B |
|
@ -5,3 +5,4 @@
|
|||
0.05: Actual pixels as of 27 Apr 2020
|
||||
0.06: Actual pixels as of 12 Jun 2020
|
||||
0.07: Pressing a button now exits immediately (fix #618)
|
||||
0.08: Make about (mostly) work on non-240px screens
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.11: shift face down for widget area, maximize face size, 0 pad single digit date, use locale for date
|
||||
0.12: Fix regression after 0.11
|
||||
0.13: Fix broken date padding (fix #376)
|
||||
0.14: Remove hardcoded hour buzz (you can install widchime if you miss it)
|
||||
|
|
|
@ -113,9 +113,6 @@ const onMinute = () => {
|
|||
g.setColor(1, 1, 0.9);
|
||||
// Minute
|
||||
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
|
||||
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
drawDate();
|
||||
};
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Make alarm scheduling more reliable
|
||||
0.09: Add per alarm auto-snooze option
|
||||
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
|
||||
0.11: Respect Quiet Mode
|
||||
|
|
|
@ -38,6 +38,7 @@ function showAlarm(alarm) {
|
|||
load();
|
||||
});
|
||||
function buzz() {
|
||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
|
||||
Bangle.buzz(100).then(()=>{
|
||||
setTimeout(()=>{
|
||||
Bangle.buzz(100).then(function() {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.01: First version
|
||||
0.02: Moved arrow image load to global scope
|
||||
0.03: faster drawCompass() function, does not cause buttons to become unresponsive
|
||||
0.04: removed LCD1.write() as it was keeping LCD on
|
|
@ -0,0 +1,41 @@
|
|||
# Arrow Compass
|
||||
|
||||
A variation of jeffmer's Navigation Compass. The compass points
|
||||
North and shows the current heading.
|
||||
|
||||
This is a tilt and roll compensated compass with a linear
|
||||
display. The compass will display the same direction that it shows
|
||||
when flat as when it is tilted (rotation around the W-S axis) or
|
||||
rolled (rotation around the N-S) axis. *Even with compensation, it
|
||||
would be beyond foolish to rely solely on this app for any serious
|
||||
navigational purpose.*
|
||||
|
||||

|
||||
|
||||
## Calibration
|
||||
|
||||
Correct operation of this app depends critically on calibration. When
|
||||
first run on a Bangle, the app will request calibration. This lasts
|
||||
for 30 seconds during which you should move the watch slowly through
|
||||
figures of 8. It is important that during calibration the watch is
|
||||
fully rotated around each of it axes. If the app does give the
|
||||
correct direction heading or is not stable with respect to tilt and
|
||||
roll - redo the calibration by pressing *BTN3*. Calibration data is
|
||||
recorded in a storage file named `magnav.json`.
|
||||
|
||||
It is also worth noting that the presence of the magnetic charging
|
||||
clamps will require the compass to be recalibrated after every
|
||||
charge.
|
||||
|
||||
## Controls
|
||||
|
||||
*BTN1* - switches to your selected clock app.
|
||||
|
||||
*BTN2* - switches to the app launcher.
|
||||
|
||||
*BTN3* - invokes calibration ( can be cancelled if pressed accidentally)
|
||||
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
This app is based in the work done by [jeffmer](https://github.com/jeffmer/JeffsBangleAppsDev)
|
|
@ -0,0 +1,187 @@
|
|||
var pal1color = new Uint16Array([0x0000,0xFFC0],0,1);
|
||||
var pal2color = new Uint16Array([0x0000,0xffff],0,1);
|
||||
var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true});
|
||||
var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true});
|
||||
var intervalRef;
|
||||
var bearing=0; // always point north
|
||||
var heading = 0;
|
||||
var oldHeading = 0;
|
||||
var candraw = false;
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
|
||||
function flip1(x,y) {
|
||||
g.drawImage({width:128,height:128,bpp:1,buffer:buf1.buffer, palette:pal1color},x,y);
|
||||
buf1.clear();
|
||||
}
|
||||
|
||||
function flip2(x,y) {
|
||||
g.drawImage({width:80,height:40,bpp:1,buffer:buf2.buffer, palette:pal2color},x,y);
|
||||
buf2.clear();
|
||||
}
|
||||
|
||||
function radians(d) {
|
||||
return (d*Math.PI) / 180;
|
||||
}
|
||||
|
||||
// takes 32ms
|
||||
function drawCompass(hd) {
|
||||
if(!candraw) return;
|
||||
if (Math.abs(hd - oldHeading) < 2) return 0;
|
||||
hd=hd*Math.PI/180;
|
||||
var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760];
|
||||
|
||||
// using polar cordinates, 64,64 is the offset from the 0,0 origin
|
||||
var poly = [
|
||||
64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]),
|
||||
64+44.7214*Math.sin(hd+p[1]), 64-44.7214*Math.cos(hd+p[1]),
|
||||
64+28.2843*Math.sin(hd+p[2]), 64-28.2843*Math.cos(hd+p[2]),
|
||||
64+63.2455*Math.sin(hd+p[3]), 64-63.2455*Math.cos(hd+p[3]),
|
||||
64+63.2455*Math.sin(hd+p[4]), 64-63.2455*Math.cos(hd+p[4]),
|
||||
64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]),
|
||||
64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6])
|
||||
];
|
||||
|
||||
buf1.fillPoly(poly);
|
||||
flip1(56, 56);
|
||||
}
|
||||
|
||||
// stops violent compass swings and wobbles, takes 3ms
|
||||
function newHeading(m,h){
|
||||
var s = Math.abs(m - h);
|
||||
var delta = (m>h)?1:-1;
|
||||
if (s>=180){s=360-s; delta = -delta;}
|
||||
if (s<2) return h;
|
||||
var hd = h + delta*(1 + Math.round(s/5));
|
||||
if (hd<0) hd+=360;
|
||||
if (hd>360)hd-= 360;
|
||||
return hd;
|
||||
}
|
||||
|
||||
// takes approx 7ms
|
||||
function tiltfixread(O,S){
|
||||
var start = Date.now();
|
||||
var m = Bangle.getCompass();
|
||||
var g = Bangle.getAccel();
|
||||
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||
if (d<0) d+=360;
|
||||
var phi = Math.atan(-g.x/-g.z);
|
||||
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||
if (psi<0) psi+=360;
|
||||
return psi;
|
||||
}
|
||||
|
||||
function reading() {
|
||||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
var dir = bearing - heading;
|
||||
if (dir < 0) dir += 360;
|
||||
if (dir > 360) dir -= 360;
|
||||
drawCompass(dir); // we want compass to show us where to go
|
||||
oldHeading = dir;
|
||||
buf2.setColor(1);
|
||||
buf2.setFontAlign(-1,-1);
|
||||
buf2.setFont("Vector",38);
|
||||
var course = Math.round(heading);
|
||||
var cs = course.toString();
|
||||
cs = course<10?"00"+cs : course<100 ?"0"+cs : cs;
|
||||
buf2.drawString(cs,0,0);
|
||||
flip2(90, 200);
|
||||
}
|
||||
|
||||
function calibrate(){
|
||||
var max={x:-32000, y:-32000, z:-32000},
|
||||
min={x:32000, y:32000, z:32000};
|
||||
var ref = setInterval(()=>{
|
||||
var m = Bangle.getCompass();
|
||||
max.x = m.x>max.x?m.x:max.x;
|
||||
max.y = m.y>max.y?m.y:max.y;
|
||||
max.z = m.z>max.z?m.z:max.z;
|
||||
min.x = m.x<min.x?m.x:min.x;
|
||||
min.y = m.y<min.y?m.y:min.y;
|
||||
min.z = m.z<min.z?m.z:min.z;
|
||||
}, 100);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
if(ref) clearInterval(ref);
|
||||
var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2};
|
||||
var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2};
|
||||
var avg = (delta.x+delta.y+delta.z)/3;
|
||||
var scale = {x:avg/delta.x, y:avg/delta.y, z:avg/delta.z};
|
||||
resolve({offset:offset,scale:scale});
|
||||
},30000);
|
||||
});
|
||||
}
|
||||
|
||||
function docalibrate(e,first){
|
||||
const title = "Calibrate";
|
||||
const msg = "takes 30 seconds";
|
||||
function action(b){
|
||||
if (b) {
|
||||
buf1.setColor(1);
|
||||
buf1.setFont("Vector", 20);
|
||||
buf1.setFontAlign(0,-1);
|
||||
buf1.drawString("Figure 8s",64, 0);
|
||||
buf1.drawString("to",64, 40);
|
||||
buf1.drawString("Calibrate",64, 80);
|
||||
flip1(56,56);
|
||||
|
||||
calibrate().then((r)=>{
|
||||
require("Storage").write("magnav.json",r);
|
||||
Bangle.buzz();
|
||||
CALIBDATA = r;
|
||||
startdraw();
|
||||
setButtons();
|
||||
});
|
||||
} else {
|
||||
startdraw();
|
||||
setTimeout(setButtons,1000);
|
||||
}
|
||||
}
|
||||
if (first===undefined) first=false;
|
||||
stopdraw();
|
||||
clearWatch();
|
||||
if (first)
|
||||
E.showAlert(msg,title).then(action.bind(null,true));
|
||||
else
|
||||
E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action);
|
||||
}
|
||||
|
||||
function startdraw(){
|
||||
g.clear();
|
||||
g.setColor(1,1,1);
|
||||
Bangle.drawWidgets();
|
||||
candraw = true;
|
||||
intervalRef = setInterval(reading,500);
|
||||
}
|
||||
|
||||
function stopdraw() {
|
||||
candraw=false;
|
||||
if(intervalRef) {clearInterval(intervalRef);}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(()=>{load();}, BTN1, {repeat:false,edge:"falling"});
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"});
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on) {
|
||||
startdraw();
|
||||
} else {
|
||||
stopdraw();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('kill',()=>{Bangle.setCompassPower(0);});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setCompassPower(1);
|
||||
startdraw();
|
||||
setButtons();
|
After Width: | Height: | Size: 934 B |
After Width: | Height: | Size: 53 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mUywIebg/4AocP//AAoUf//+BYgMDh/+j/8Dol/wEAgYFBg/wgEBFIV+AQIVCh4fBnwFBgISBj8AhgJCh+Ag4BB4ED8ED+ASCAYJDBnkAvkAIYIWBjw8B/EB8AcBn//gF4DwJdBAQMA/EP738FYM8g/nz+A+EPgHx8YKBgfAjF4sAKBHIItBBQJMBFoJEBHII1BIQIDCvAUCAYYUBHIIDBMIXACgQpBRAIUBMIIrBDAIWCVYaiBTYQJCn4FBQgIIBEYKrDQ4MBVYUf8CQCCoP/w6DBAAKIBAocHAoIwBBgb5DDoYAZA="))
|
|
@ -7,3 +7,7 @@
|
|||
0.07: Fixed GPS update, added guards against NaN values
|
||||
0.08: Fix issue with GPS coordinates being wrong after the first one
|
||||
0.09: Another GPS fix (log raw coordinates - not filtered ones)
|
||||
0.10: Removed kalman filtering to allow distance log to work
|
||||
Only log data every 5 seconds (not 1 sec)
|
||||
Don't create a file until the first log entry is ready
|
||||
Add labels for buttons
|
||||
|
|
|
@ -1 +1 @@
|
|||
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,r){g.setColor(0),g.fillRect(n-60,r,n+60,r+30),g.setColor(65535),g.drawString(t,n,r)}function r(r){var e;g.setFontVector(30),g.setFontAlign(0,-1,0),n((r.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),r=Math.floor(n/3600),e=Math.floor(n/60)%60,o=n%60;return(r?r+":":"")+("0"+e).substr(-2)+":"+("0"+o).substr(-2)}(r.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),r=Math.floor(n/60),e=n%60;return("0"+r).substr(-2)+"'"+("0"+e).substr(-2)+'"'}(r.speed),60,115),n(r.hr.toFixed(0),180,115),n(r.steps.toFixed(0),60,175),n(r.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(r.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(e=new Date).getHours()).substr(-2)+":"+("0"+e.getMinutes()).substr(-2),120,220),g.setColor(t[r.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(r.status,200,220)}function e(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),r(t),Bangle.drawWidgets()}var o;function a(t){t.status===o.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),r=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(r,"w"),t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n")}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Running,r(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(o||(o={}));const s={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,v:NaN,t:NaN,dt:NaN,pError:NaN,vError:NaN,hr:60,hrError:100,file:null,drawing:!1,status:o.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var i;i=s,Bangle.on("GPS",t=>function(t,n){t.lat=n.lat,t.lon=n.lon,t.alt=n.alt,t.vel=n.speed/3.6,t.fix=n.fix,t.dop=n.hdop,t.gpsValid=t.fix>0&&t.dop<=5,function(t){const n=Date.now();let r=(n-t.t)/1e3;if(isFinite(r)||(r=0),t.t=n,t.dt+=r,t.status===o.Running&&(t.duration+=r),!t.gpsValid)return;const e=6371008.8+t.alt,a=t.lat*Math.PI/180,s=t.lon*Math.PI/180,i=e*Math.cos(a)*Math.cos(s),d=e*Math.cos(a)*Math.sin(s),u=e*Math.sin(a),g=t.vel;if(!t.x)return t.x=i,t.y=d,t.z=u,t.v=g,t.pError=2.5*t.dop,void(t.vError=.05*t.dop);const l=i-t.x,c=d-t.y,p=u-t.z,f=g-t.v,N=Math.sqrt(l*l+c*c+p*p),h=Math.abs(f);t.pError+=t.v*t.dt,t.dt=0;const S=N+2.5*t.dop,E=h+.05*t.dop,w=t.pError/(t.pError+S)||0,x=t.vError/(t.vError+E)||0;t.x+=l*w,t.y+=c*w,t.z+=p*w,t.v+=f*x,t.pError+=(S-t.pError)*w,t.vError+=(E-t.vError)*x,t.status===o.Running&&(t.distance+=N*w,t.speed=t.distance/t.duration||0,t.cadence=60*t.steps/t.duration||0)}(t),r(t),t.gpsValid&&t.status===o.Running&&function(t){t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(t)}(i,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const r=n.bpm-t.hr,e=Math.abs(r)+101-n.confidence,o=t.hrError/(t.hrError+e)||0;t.hr+=r*o,t.hrError+=(e-t.hrError)*o}(t,n)),Bangle.setHRMPower(1)}(s),function(t){Bangle.on("step",()=>function(t){t.status===o.Running&&(t.steps+=1)}(t))}(s),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&e(t)}),e(t)}(s),setWatch(()=>a(s),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===o.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Stopped,r(t)}(s),BTN3,{repeat:!0,edge:"falling"})}();
|
||||
!function(){"use strict";var t;!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(t||(t={}));const n={STOP:63488,PAUSE:65504,RUN:2016};function e(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function i(i){var s;g.setFontVector(30),g.setFontAlign(0,-1,0),e((i.distance/1e3).toFixed(2),60,55),e(function(t){const n=Math.round(t),e=Math.floor(n/3600),i=Math.floor(n/60)%60,s=n%60;return(e?e+":":"")+("0"+i).substr(-2)+":"+("0"+s).substr(-2)}(i.duration),172,55),e(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),i=n%60;return("0"+e).substr(-2)+"'"+("0"+i).substr(-2)+'"'}(i.speed),60,115),e(i.hr.toFixed(0),172,115),e(i.steps.toFixed(0),60,175),e(i.cadence.toFixed(0),172,175),g.setFont("6x8",2),g.setColor(i.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(s=new Date).getHours()).substr(-2)+":"+("0"+s.getMinutes()).substr(-2),120,220),g.setColor(n[i.status]),g.fillRect(160,216,230,240),g.setColor(0),g.drawString(i.status,200,220),g.setFont("6x8").setFontAlign(0,0,1).setColor(-1),i.status===t.Paused?g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1):i.status===t.Running?g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1):g.drawString("START",236,60,1).drawString(" ",236,180,1)}function s(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),i(t),Bangle.drawWidgets()}function a(n){n.status===t.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.fileWritten=!1}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Running,i(n)}const r={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,t:NaN,timeSinceLog:0,hr:60,hrError:100,file:null,fileWritten:!1,drawing:!1,status:t.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var o;o=r,Bangle.on("GPS",n=>function(n,e){n.lat=e.lat,n.lon=e.lon,n.alt=e.alt,n.vel=e.speed/3.6,n.fix=e.fix,n.dop=e.hdop,n.gpsValid=n.fix>0,function(n){const e=Date.now();let i=(e-n.t)/1e3;if(isFinite(i)||(i=0),n.t=e,n.timeSinceLog+=i,n.status===t.Running&&(n.duration+=i),!n.gpsValid)return;const s=6371008.8+n.alt,a=n.lat*Math.PI/180,r=n.lon*Math.PI/180,o=s*Math.cos(a)*Math.cos(r),g=s*Math.cos(a)*Math.sin(r),d=s*Math.sin(a);if(!n.x)return n.x=o,n.y=g,void(n.z=d);const u=o-n.x,l=g-n.y,c=d-n.z,f=Math.sqrt(u*u+l*l+c*c);n.x=o,n.y=g,n.z=d,n.status===t.Running&&(n.distance+=f,n.speed=n.distance/n.duration||0,n.cadence=60*n.steps/n.duration||0)}(n),i(n),n.gpsValid&&n.status===t.Running&&n.timeSinceLog>5&&(n.timeSinceLog=0,function(t){t.fileWritten||(t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n"),t.fileWritten=!0),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(n))}(o,n)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,i=Math.abs(e)+101-n.confidence,s=t.hrError/(t.hrError+i)||0;t.hr+=e*s,t.hrError+=(i-t.hrError)*s}(t,n)),Bangle.setHRMPower(1)}(r),function(n){Bangle.on("step",()=>function(n){n.status===t.Running&&(n.steps+=1)}(n))}(r),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&s(t)}),s(t)}(r),setWatch(()=>a(r),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(n){n.status===t.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Stopped,i(n)}(r),BTN3,{repeat:!0,edge:"falling"})}();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppState } from './state';
|
||||
import { ActivityStatus, AppState } from './state';
|
||||
|
||||
declare var Bangle: any;
|
||||
declare var g: any;
|
||||
|
@ -26,11 +26,11 @@ function drawBackground(): void {
|
|||
g.setFont('6x8', 2);
|
||||
g.setFontAlign(0, -1, 0);
|
||||
g.drawString('DIST (KM)', 60, 32);
|
||||
g.drawString('TIME', 180, 32);
|
||||
g.drawString('TIME', 172, 32);
|
||||
g.drawString('PACE', 60, 92);
|
||||
g.drawString('HEART', 180, 92);
|
||||
g.drawString('HEART', 172, 92);
|
||||
g.drawString('STEPS', 60, 152);
|
||||
g.drawString('CADENCE', 180, 152);
|
||||
g.drawString('CADENCE', 172, 152);
|
||||
}
|
||||
|
||||
function drawValue(value: string, x: number, y: number) {
|
||||
|
@ -45,11 +45,11 @@ function draw(state: AppState): void {
|
|||
g.setFontAlign(0, -1, 0);
|
||||
|
||||
drawValue(formatDistance(state.distance), 60, 55);
|
||||
drawValue(formatTime(state.duration), 180, 55);
|
||||
drawValue(formatTime(state.duration), 172, 55);
|
||||
drawValue(formatPace(state.speed), 60, 115);
|
||||
drawValue(state.hr.toFixed(0), 180, 115);
|
||||
drawValue(state.hr.toFixed(0), 172, 115);
|
||||
drawValue(state.steps.toFixed(0), 60, 175);
|
||||
drawValue(state.cadence.toFixed(0), 180, 175);
|
||||
drawValue(state.cadence.toFixed(0), 172, 175);
|
||||
|
||||
g.setFont('6x8', 2);
|
||||
|
||||
|
@ -64,9 +64,18 @@ function draw(state: AppState): void {
|
|||
g.drawString(formatClock(new Date()), 120, 220);
|
||||
|
||||
g.setColor(STATUS_COLORS[state.status]);
|
||||
g.fillRect(160, 216, 240, 240);
|
||||
g.fillRect(160, 216, 230, 240);
|
||||
g.setColor(0x0000);
|
||||
g.drawString(state.status, 200, 220);
|
||||
|
||||
g.setFont("6x8").setFontAlign(0,0,1).setColor(-1);
|
||||
if (state.status === ActivityStatus.Paused) {
|
||||
g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1);
|
||||
} else if (state.status === ActivityStatus.Running) {
|
||||
g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1);
|
||||
} else {
|
||||
g.drawString("START",236,60,1).drawString(" ",236,180,1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawAll(state: AppState) {
|
||||
|
|
|
@ -14,8 +14,6 @@ interface GpsEvent {
|
|||
}
|
||||
|
||||
const EARTH_RADIUS = 6371008.8;
|
||||
const POS_ACCURACY = 2.5;
|
||||
const VEL_ACCURACY = 0.05;
|
||||
|
||||
function initGps(state: AppState): void {
|
||||
Bangle.on('GPS', (gps: GpsEvent) => readGps(state, gps));
|
||||
|
@ -29,13 +27,17 @@ function readGps(state: AppState, gps: GpsEvent): void {
|
|||
state.vel = gps.speed / 3.6;
|
||||
state.fix = gps.fix;
|
||||
state.dop = gps.hdop;
|
||||
|
||||
state.gpsValid = state.fix > 0 && state.dop <= 5;
|
||||
state.gpsValid = state.fix > 0;
|
||||
|
||||
updateGps(state);
|
||||
draw(state);
|
||||
|
||||
if (state.gpsValid && state.status === ActivityStatus.Running) {
|
||||
/* Only log GPS data every 5 secs if we
|
||||
have a fix and we're running. */
|
||||
if (state.gpsValid &&
|
||||
state.status === ActivityStatus.Running &&
|
||||
state.timeSinceLog > 5) {
|
||||
state.timeSinceLog = 0;
|
||||
updateLog(state);
|
||||
}
|
||||
}
|
||||
|
@ -44,9 +46,8 @@ function updateGps(state: AppState): void {
|
|||
const t = Date.now();
|
||||
let dt = (t - state.t) / 1000;
|
||||
if (!isFinite(dt)) dt=0;
|
||||
|
||||
state.t = t;
|
||||
state.dt += dt;
|
||||
state.timeSinceLog += dt;
|
||||
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.duration += dt;
|
||||
|
@ -62,52 +63,25 @@ function updateGps(state: AppState): void {
|
|||
const x = r * Math.cos(lat) * Math.cos(lon);
|
||||
const y = r * Math.cos(lat) * Math.sin(lon);
|
||||
const z = r * Math.sin(lat);
|
||||
const v = state.vel;
|
||||
|
||||
if (!state.x) {
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.z = z;
|
||||
state.v = v;
|
||||
state.pError = state.dop * POS_ACCURACY;
|
||||
state.vError = state.dop * VEL_ACCURACY;
|
||||
return;
|
||||
}
|
||||
|
||||
const dx = x - state.x;
|
||||
const dy = y - state.y;
|
||||
const dz = z - state.z;
|
||||
const dv = v - state.v;
|
||||
const dpMag = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
const dvMag = Math.abs(dv);
|
||||
|
||||
state.pError += state.v * state.dt;
|
||||
state.dt = 0;
|
||||
|
||||
const pError = dpMag + state.dop * POS_ACCURACY;
|
||||
const vError = dvMag + state.dop * VEL_ACCURACY;
|
||||
|
||||
const pGain = (state.pError / (state.pError + pError)) || 0;
|
||||
const vGain = (state.vError / (state.vError + vError)) || 0;
|
||||
|
||||
state.x += dx * pGain;
|
||||
state.y += dy * pGain;
|
||||
state.z += dz * pGain;
|
||||
state.v += dv * vGain;
|
||||
state.pError += (pError - state.pError) * pGain;
|
||||
state.vError += (vError - state.vError) * vGain;
|
||||
|
||||
/*// we're not currently updating lat/lon with the kalman filter
|
||||
// as it seems not to update them correctly at the moment
|
||||
// and we only use them for logging (where it makes sense to use
|
||||
// raw GPS coordinates)
|
||||
const pMag = Math.sqrt(state.x * state.x + state.y * state.y + state.z * state.z);
|
||||
state.lat = (Math.asin(state.z / pMag) * 180 / Math.PI) || 0;
|
||||
state.lon = (Math.atan2(state.y, state.x) * 180 / Math.PI) || 0;
|
||||
state.alt = pMag - EARTH_RADIUS;*/
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.z = z;
|
||||
|
||||
if (state.status === ActivityStatus.Running) {
|
||||
state.distance += dpMag * pGain;
|
||||
state.distance += dpMag;
|
||||
state.speed = (state.distance / state.duration) || 0;
|
||||
state.cadence = (60 * state.steps / state.duration) || 0;
|
||||
}
|
||||
|
|
|
@ -8,19 +8,23 @@ function initLog(state: AppState): void {
|
|||
const time = datetime.substr(9, 6);
|
||||
const filename = `banglerun_${date}_${time}`;
|
||||
state.file = require('Storage').open(filename, 'w');
|
||||
state.file.write([
|
||||
'timestamp',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'altitude',
|
||||
'duration',
|
||||
'distance',
|
||||
'heartrate',
|
||||
'steps',
|
||||
].join(',') + '\n');
|
||||
state.fileWritten = false;
|
||||
}
|
||||
|
||||
function updateLog(state: AppState): void {
|
||||
if (!state.fileWritten) {
|
||||
state.file.write([
|
||||
'timestamp',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'altitude',
|
||||
'duration',
|
||||
'distance',
|
||||
'heartrate',
|
||||
'steps',
|
||||
].join(',') + '\n');
|
||||
state.fileWritten = true;
|
||||
}
|
||||
state.file.write([
|
||||
Date.now().toFixed(0),
|
||||
state.lat.toFixed(6),
|
||||
|
|
|
@ -14,15 +14,14 @@ interface AppState {
|
|||
dop: number;
|
||||
gpsValid: boolean;
|
||||
|
||||
// GPS Kalman data
|
||||
// Absolute position data
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
v: number;
|
||||
// Last fix time
|
||||
t: number;
|
||||
dt: number;
|
||||
pError: number;
|
||||
vError: number;
|
||||
// Last time we saved log info
|
||||
timeSinceLog : number;
|
||||
|
||||
// HRM data
|
||||
hr: number,
|
||||
|
@ -30,6 +29,7 @@ interface AppState {
|
|||
|
||||
// Logger data
|
||||
file: File;
|
||||
fileWritten: boolean;
|
||||
|
||||
// Drawing data
|
||||
drawing: boolean;
|
||||
|
@ -62,16 +62,14 @@ function initState(): AppState {
|
|||
x: NaN,
|
||||
y: NaN,
|
||||
z: NaN,
|
||||
v: NaN,
|
||||
t: NaN,
|
||||
dt: NaN,
|
||||
pError: NaN,
|
||||
vError: NaN,
|
||||
timeSinceLog : 0,
|
||||
|
||||
hr: 60,
|
||||
hrError: 100,
|
||||
|
||||
file: null,
|
||||
fileWritten: false,
|
||||
|
||||
drawing: false,
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: App Created!
|
|
@ -0,0 +1,25 @@
|
|||
<div align="center">
|
||||
<h1> BatClock </h1>
|
||||
<p align="center">
|
||||
<img src="screenshot.png" height="240px">
|
||||
</p>
|
||||
<h3>Based on Morphing Clock, with an awesome "The Dark Knight" themed logo.
|
||||
</h3>
|
||||
<br>
|
||||
<img src="https://img.shields.io/badge/Made%20with-JavaScript-f0db4f?style=for-the-badge&logo=javascipt" alt="Made with JavaScipt"> <img src="https://img.shields.io/badge/Open%20Source%20Hardware-%E2%9A%99-blue?style=for-the-badge&logo=open-source-initiative" alt="Open Source Hardware"> <img src="https://img.shields.io/badge/espruino-%E2%98%95-white?style=for-the-badge" alt="Espruino"> <img src="https://img.shields.io/badge/Built%20With-%E2%99%A1-critical?style=for-the-badge&logo=github" alt="Built with Love">
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
## Requests
|
||||
|
||||
Please leave bug reports and requests by raising an issue [here](https://github.com/ra101/BangleApps).
|
||||
|
||||
<br>
|
||||
|
||||
## Creator
|
||||
|
||||
[< RA >](https://github.com/ra101)
|
|
@ -0,0 +1,263 @@
|
|||
// Initailize Variables
|
||||
|
||||
var is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
var locale = require("locale");
|
||||
var CHARW = 9; // how tall are digits?
|
||||
var CHARP = 1; // how chunky are digits?
|
||||
var Y = 105; // start height
|
||||
|
||||
// Offscreen buffer
|
||||
var buf = Graphics.createArrayBuffer(CHARW + CHARP * 2, CHARW * 2 + CHARP * 2, 1, {
|
||||
msb: true
|
||||
});
|
||||
var bufimg = {
|
||||
width: buf.getWidth(),
|
||||
height: buf.getHeight(),
|
||||
buffer: buf.buffer
|
||||
};
|
||||
|
||||
// The last time that we displayed
|
||||
var lastTime = "-----";
|
||||
|
||||
// If animating, this is the interval's id
|
||||
var animInterval;
|
||||
var timeInterval;
|
||||
|
||||
// Background Image
|
||||
const bg_crack = require("heatshrink").decompress(atob("+HwgJC/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/gEIDrkYDrkwDrlgDrnADvMBwAdcLDkCBhcQDp8EfdMMDrkGDrkHDrkDCB8wDpj8PuD8M4AdO8ANaFp/wgEIDrV4gEEQ5gtBDprhN/wOMngCBLRcEj7EMj5qBBxcMgamMFYWEBAgzFjEA/AdMUgMGDpUQgEeDA0gAgd/DoNCDpUwS4J4GhgED/ZnBkIdKsACBaYxRDgPxBgMRDpVAYogADgJUBfoPgQoQYEwECAoYSCKQgACuD9CwE4d4ISCEQ0BdpRDCh4ECghOCDoUEAgUDZpQaBgEcYIUCRQQdGLogAGgZHBK4MfCQLaEKYj6FAAzNBPQMPwEBiAdEhDkHAA84WwUfGgOYDopCCEIYAIjxbCj4gBCYhZEjAdLg/GAYMfLwNMDogZDmAdLgP8AYM/+EAkgdEPoafEABF8AQP/DoMWDpDbEPBJcB//4bIJ3IQYJPBDpMPOwPPLoMLDo8BAgQqEAAqzBv8H/wUEDpEGSxXB/kD/4ZFAYY3Dgh4KOoIdBC4JuCDog3Dhg7K//gAQIbBVQYdDhwDChA7Kv/AEAUMDo8ejgDBvBZLwBZB+0cmAdGn+eFYL+BAAj9DAAscBQYdDnw3KDpMYDo1uDqiJDDofGDqceYo0BwwdTh0EHYsBd4YdRgQHFgQlDDqIHGggdUh4HGhB/GDqK2DgAdUg4dESwMQDqqvCHYcgIAgdXoEQDqcDDoQAD4EYDq47DwEwDqcCDoTqCgMA+AdTKQICBg04IQMD8AdXAAUGg4dXLAUAh0PDijKBgMeAoUeAgYASsEBx4EBkEengdViEAw4dCn14DqsMgHHDoUzuAdVgOAsYEBmF3WSoABvExDoeADq0P/ADBjH8DiwABDIUPeoIA/AH4A/AAUgDvVgC60DAonwDq0MAgcBDq84OYn4DisB8EAg4CBgF+DqsHwADB/+DwE8DqseEIf/8AdWvAEDn/wDqsfOYR8C//+Oqn/OwQACh//DqcPGY8DDqZwBRqodG4AdcDjcAcywAGsAdcAH4A/AH4A/AH4AnmBA/AEUBw//+Adaj///AOLFRsD//vCBk8DpkH85ZBg/ABxGAjwdMBoP/8JbJgeAg4dMsEAv49COxHAgfQQpeAAQOD//gBo4dBgCjLggED/53HgcAE4N8ap5tBBA0PDoSWNW4eAGgKiGDoIMBDp0GKQQAEjkAuAKBbxIdHAIIAE/gdDUQ4AHjEAhiyFV4M4DoLeIAAwSBDosAvAKCgU8Dp1gHoQADiEwDoS0JoBQFQ4IUCIYkAj/+WgL8IaA0AGgJYFDoJ6BWg8IAwsPDoJbBDo0PDQMBWgwTFgE/BI4dDAwpvDGYIAEvYCBGIMQBAV8NQMOAwS0Fgh+GEoInCMod8KgIdDH4bACDpbxDvkA/gGDg6WELAwICXgQWCiFmQQIdDgPwDA4ACjACBgQCBKQQdCj/8CIbVBABLrCg0AoEHBAViKgIdEnwdKZoQdCUQUQscAgYdEh5zIZoZ0CoClDsIDBvymE8AdJFAUcDovhKg94LRQdDcIngFAMeBIk8Dpi2CDofADoK6CAAQFFDp6fDAAcHSxQdICYUDBIkBE4QAJiAjFDoUBCAq0KDpAQJnAdQggdKYgodIhAdNg4dNhgdNga0LC4IsDDpUAWhaEBcIYdLuALKmDnEDpYpRVBaHCABD8EiAdLgSDMaIUYchgAJgKSBJIUYNZYAKSQQdDH4QATgwdFDisAggCBG4R2WHA0fTYIAUDQQ7CDq8wdgQdZkAdFa4IAUoAFEDoqhCABxSFjIdVgJSFd4odRXJAACgz1WDosCDv4dYgYdWhiiLDv4dSgGADv4dYoAdzgitWDpkgLLgddACwd/Dv4d/Dv4drADsIDv6z/LOQA/AH4A/AH4A/AGw"));
|
||||
const batman = require("heatshrink").decompress(atob("+HwwJC/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4Ash//AA/gCQ8BCRH8DrsHDrf4BRIdSCQN/BQ/wDqOAgEfDqEDCI/+BYJ4IDqP4BZQdRCIYdMgYdPSw5HCgFwDpnABgSWHDoqmBRJJMDDpcwDocPWRIqJ/gLCvA7LCAZmIBgZZEHY6mEgI7Lg4dK8AdLMwd5DoaIHDojSHDoYFBDpQKCAAU/YBLjEB5TSKFYQdLaAiFJDo5pGaAjfJQoQdDUo/4DorwHb4QdDBxQdLNAQdDQw4dGJQ54CDoZ2GDo4TCB458CJI6GDDpn/DIXwdo69DAAhLHABrgCAAguIABgcGDrzDHABjfCDscPDqf8DssHDqf4DssDDuPwDssBHePgHbgdIgAdx4Ad/Dv4dywBZ/Dv6z/aP4d/Dv4drgIdT8Adm/gDBh4ZLB4YdIAAkfDhP+DBhePGxoAFn4dIDiUAg4cH/AdTepDpIABlfSbAADagzOCACcDDovwDqp4GOyodHoAdWeIocWSwqUWAAMHDof4Dq8BDofgDq6WEWS4ABv4dCwAd2j4cB/wcYaQbQYaQjQYAAMDDoPwDrjuZgEBDrkADoPADvF/Dr2ADrU//4caDoP+DrcfDrkPDrv8DvMH/Ad5gfwDv4AXgIdd8Ad/ADHADv4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHY"));
|
||||
|
||||
|
||||
/* Get array of lines from digit d to d+1.
|
||||
n is the amount (0..1)
|
||||
maxFive is true is this digit only counts 0..5 */
|
||||
const DIGITS = {
|
||||
" ": n => [],
|
||||
"0": n => [
|
||||
[n, 0, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[n, 2, 1, 2],
|
||||
[n, 1, n, 2],
|
||||
[n, 0, n, 1]
|
||||
],
|
||||
"1": n => [
|
||||
[1 - n, 0, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1 - n, 1, 1, 1],
|
||||
[1 - n, 1, 1 - n, 2],
|
||||
[1 - n, 2, 1, 2]
|
||||
],
|
||||
"2": n => [
|
||||
[0, 0, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[0, 1, 1, 1],
|
||||
[0, 1 + n, 0, 2],
|
||||
[1, 2 - n, 1, 2],
|
||||
[0, 2, 1, 2]
|
||||
],
|
||||
"3": n => [
|
||||
[0, 0, 1 - n, 0],
|
||||
[0, 0, 0, n],
|
||||
[1, 0, 1, 1],
|
||||
[0, 1, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[n, 2, 1, 2]
|
||||
],
|
||||
"4": n => [
|
||||
[0, 0, 0, 1],
|
||||
[1, 0, 1 - n, 0],
|
||||
[1, 0, 1, 1 - n],
|
||||
[0, 1, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[1 - n, 2, 1, 2]
|
||||
],
|
||||
"5to0": n => [ // 5 -> 0
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 1, 0],
|
||||
[n, 1, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[0, 2, 1, 2],
|
||||
[0, 2, 0, 2],
|
||||
[1, 1 - n, 1, 1],
|
||||
[0, 1, 0, 1 + n]
|
||||
],
|
||||
"5to6": n => [ // 5 -> 6
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 1, 0],
|
||||
[0, 1, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[0, 2, 1, 2],
|
||||
[0, 2 - n, 0, 2]
|
||||
],
|
||||
"6": n => [
|
||||
[0, 0, 0, 1 - n],
|
||||
[0, 0, 1, 0],
|
||||
[n, 1, 1, 1],
|
||||
[1, 1 - n, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[n, 2, 1, 2],
|
||||
[0, 1 - n, 0, 2 - 2 * n]
|
||||
],
|
||||
"7": n => [
|
||||
[0, 0, 0, n],
|
||||
[0, 0, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1 - n, 1, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[1 - n, 2, 1, 2],
|
||||
[1 - n, 1, 1 - n, 2]
|
||||
],
|
||||
"8": n => [
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[0, 1, 1, 1],
|
||||
[1, 1, 1, 2],
|
||||
[0, 2, 1, 2],
|
||||
[0, 1, 0, 2 - n]
|
||||
],
|
||||
"9": n => [
|
||||
[0, 0, 0, 1],
|
||||
[0, 0, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[0, 1, 1 - n, 1],
|
||||
[0, 1, 0, 1 + n],
|
||||
[1, 1, 1, 2],
|
||||
[0, 2, 1, 2]
|
||||
],
|
||||
":": n => [
|
||||
[0.4, 0.4, 0.6, 0.4],
|
||||
[0.6, 0.4, 0.6, 0.6],
|
||||
[0.6, 0.6, 0.4, 0.6],
|
||||
[0.4, 0.4, 0.4, 0.6],
|
||||
[0.4, 1.4, 0.6, 1.4],
|
||||
[0.6, 1.4, 0.6, 1.6],
|
||||
[0.6, 1.6, 0.4, 1.6],
|
||||
[0.4, 1.4, 0.4, 1.6]
|
||||
]
|
||||
};
|
||||
|
||||
/* Draw a transition between lastText and thisText.
|
||||
'n' is the amount - 0..1 */
|
||||
function drawDigits(lastText, thisText, n) {
|
||||
"ram"
|
||||
const p = CHARP; // padding around digits
|
||||
const s = CHARW; // character size
|
||||
var x = 80; // x offset
|
||||
g.reset();
|
||||
g.setColor(0, 0, 0);
|
||||
g.setBgColor(1, 1, 1);
|
||||
for (var i = 0; i < lastText.length; i++) {
|
||||
var lastCh = lastText[i];
|
||||
var thisCh = thisText[i];
|
||||
if (thisCh == ":") x -= 4;
|
||||
if (lastCh != thisCh) {
|
||||
var ch, chn = n;
|
||||
if ((thisCh - 1 == lastCh ||
|
||||
(thisCh == 0 && lastCh == 5) ||
|
||||
(thisCh == 0 && lastCh == 9)))
|
||||
ch = lastCh;
|
||||
else {
|
||||
ch = thisCh;
|
||||
chn = 0;
|
||||
}
|
||||
buf.clear();
|
||||
if (ch == "5") ch = (lastCh == 5 && thisCh == 0) ? "5to0" : "5to6";
|
||||
var l = DIGITS[ch](chn);
|
||||
l.forEach(c => {
|
||||
if (c[0] != c[2]) // horiz
|
||||
buf.fillRect(p + c[0] * s, c[1] * s, p + c[2] * s, 2 * p + c[3] * s);
|
||||
else if (c[1] != c[3]) // vert
|
||||
buf.fillRect(c[0] * s, p + c[1] * s, 2 * p + c[2] * s, p + c[3] * s);
|
||||
});
|
||||
g.drawImage(bufimg, x, Y);
|
||||
}
|
||||
if (thisCh == ":") x -= 4;
|
||||
x += s + p + 7;
|
||||
}
|
||||
}
|
||||
|
||||
function drawEverythingElse() {
|
||||
var x = (CHARW + CHARP + 6) * 5 + 80;
|
||||
var y = Y + 2 * CHARW + CHARP;
|
||||
var d = new Date();
|
||||
g.reset();
|
||||
g.setBgColor(1, 1, 1);
|
||||
g.setColor(1, 0, 0);
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString(("0" + d.getSeconds()).substr(-2), x, y - 8, true);
|
||||
// meridian
|
||||
if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x,
|
||||
+4, true);
|
||||
// date
|
||||
g.setFontAlign(0, -1);
|
||||
var date = locale.date(d, false);
|
||||
g.drawString(date, g.getWidth() / 2, y + 8, true);
|
||||
}
|
||||
|
||||
/* Show the current time, and animate if needed */
|
||||
function showTime() {
|
||||
if (animInterval) return; // in animation - quit
|
||||
var d = new Date();
|
||||
var hours = d.getHours();
|
||||
if (is12Hour) hours = ((hours + 11) % 12) + 1;
|
||||
var t = (" " + hours).substr(-2) + ":" +
|
||||
("0" + d.getMinutes()).substr(-2);
|
||||
var l = lastTime;
|
||||
// same - don't animate
|
||||
if (t == l || l == "-----") {
|
||||
drawDigits(l, t, 0);
|
||||
drawEverythingElse();
|
||||
lastTime = t;
|
||||
return;
|
||||
}
|
||||
var n = 0;
|
||||
animInterval = setInterval(function () {
|
||||
n += 1 / 10;
|
||||
if (n >= 1) {
|
||||
n = 1;
|
||||
clearInterval(animInterval);
|
||||
animInterval = undefined;
|
||||
}
|
||||
drawDigits(l, t, n);
|
||||
}, 20);
|
||||
lastTime = t;
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (animInterval) {
|
||||
clearInterval(animInterval);
|
||||
animInterval = undefined;
|
||||
}
|
||||
if (timeInterval) {
|
||||
clearInterval(timeInterval);
|
||||
timeInterval = undefined;
|
||||
}
|
||||
if (on) {
|
||||
showTime();
|
||||
timeInterval = setInterval(showTime, 1000);
|
||||
} else {
|
||||
lastTime = "-----";
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
|
||||
// Draw Backgound before displaying time
|
||||
g.setColor(0, 0.5, 0).drawImage(bg_crack);
|
||||
g.setColor(1, 1, 1).drawImage(batman);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Update time once a second
|
||||
timeInterval = setInterval(showTime, 1000);
|
||||
showTime();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, {
|
||||
repeat: false,
|
||||
edge: "falling"
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/ACc//8AgP//kf/+AAoP4h4FD8ED/+Dwf/4AFB8fHBwYACwEAAoYqB/2vxAFD1uuAoePAont13+AoXvxwLCEYsAv4QCAAMf/AFDh/gAocH4AFDgYFEgJLBAH4AaA=="))
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 114 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,18 @@
|
|||
# Battleship
|
||||
|
||||
The classic game of battleship.
|
||||
|
||||
## Usage
|
||||
|
||||
In the beginning, each player is required to place
|
||||
all ships in his fleet on the field.
|
||||
Navigation of the cursor is performed using BTN1 and
|
||||
BTN3 as well as left and right on the touch screen.
|
||||
To place a ship use BTN2 to initialize a placement
|
||||
and BTN2 again to complete it.
|
||||
|
||||
In the next phase the players take alternating turns
|
||||
in trying to hit an opposing ship.
|
||||
|
||||
After a player succeeds in sinking the entire opposing
|
||||
fleet the game ends.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwyBC/AH4A/AH4A/AH4A/AH4AEgeGAIVmAogBHBoRV/LZQBaLf4BK9EMlMMpQBClIJBMf5dM08Utcdh0luABNCIMUpYZBMO5bK1hZPAJYdBMecDswxFhkqktQLrYBEqAlBMI1mXdq5dYpxhqFYsc5pdnAIM16VeuQ1FLs67pAIM9+WP7GHrFN2JhjEYsMlRdtv9Yu34v8YlnNHolmL8GnktQLtkXt3XuwBB/ADBhfIYLq9FimsLtd2+9m21u25hFouQIIq9eLtVu+1eu1myxhIYLi9GtZdps3Wq11u83w94t3WMIZfDmsOL78dhxdo2tOmhZBy/5AIILCYYhfBr0TIopfUswZDLsc+iRRBr2Vp0UL4X2L4teL4JhEAIMD05fYPIfoLstWuk9+dGiZhDu83w+Ys3Wr11MI8UlJfbhkqLsdOqk16U96ZhHAINWqoBBMI8kxZfcpRddmvRLoNGmct2M12RhLqxhKlmsL/ZhDkuRloBBL4JhRL4JhCkhfdlJffAIhhajmLL7cD9BfkuBfCMJlGMJEUhJfcwxflMLFUgenL7sdhxhpnvxp0RnvSMIXzMI89yJFFL7MUpZfnmuxq0xAIbDEMI0k1hdXMJGnL9HRL5BhD+RhBovzHoJfcszBE1hhnovxp0RYoMtyJhG+ck9qhEL7DBIqBhnAIlxMInSNIK9dL5GGhkqLMstyEt2BhJilKXr5hJimsLsdGiABBnvwMIsc5hdjMJXNL7812BfDopfEFoJdnL4VmYc2QXYJdBMoJdC1gxFL8phJhkqktQYr4hBEoJdtMJcD07FdXIWnLuJjGG4xjCpcc9xZPCIMMpZb5YpwBF9EMlMMpQBClIJBC5hd2YpwBWLfZldKf4A/AH4A/AH4A/AH4A/AAo"))
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,130 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="battleship-icon.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 12.7 12.7"
|
||||
height="48"
|
||||
width="48"
|
||||
inkscape:export-filename="battleship-icon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient848"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop844"
|
||||
offset="0"
|
||||
style="stop-color:#88e7cd;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop846"
|
||||
offset="1"
|
||||
style="stop-color:#88e7cd;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="6.3499999"
|
||||
x2="6.3499999"
|
||||
y1="3.0260465"
|
||||
x1="9.7076998"
|
||||
id="linearGradient850"
|
||||
xlink:href="#linearGradient848"
|
||||
inkscape:collect="always" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-height="1013"
|
||||
inkscape:window-width="1920"
|
||||
units="px"
|
||||
showgrid="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="px"
|
||||
inkscape:cy="50.37804"
|
||||
inkscape:cx="120.0705"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<path
|
||||
id="path833"
|
||||
d="M 11.074702,6.3499999 A 4.7247024,4.7247024 0 0 1 6.3499999,11.074702 4.7247024,4.7247024 0 0 1 1.6252975,6.3499999 4.7247024,4.7247024 0 0 1 6.3499999,1.6252975 4.7247024,4.7247024 0 0 1 11.074702,6.3499999 Z"
|
||||
style="fill:#23ae87;fill-opacity:1;stroke:none;stroke-width:0.765;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
d="M 9.7076995,3.0260466 A 4.7247024,4.7247024 0 0 1 9.8531236,9.5203211 L 6.3499999,6.3499999 Z"
|
||||
sodipodi:arc-type="slice"
|
||||
sodipodi:end="0.73556977"
|
||||
sodipodi:start="5.5028377"
|
||||
sodipodi:ry="4.7247024"
|
||||
sodipodi:rx="4.7247024"
|
||||
sodipodi:cy="6.3499999"
|
||||
sodipodi:cx="6.3499999"
|
||||
sodipodi:type="arc"
|
||||
style="fill:url(#linearGradient850);fill-opacity:1;stroke:none;stroke-width:0.765;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="circle835" />
|
||||
<circle
|
||||
style="fill:none;stroke-width:0.765;stroke-linecap:round;stroke-linejoin:round;stroke:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
|
||||
id="circle837"
|
||||
cx="6.3499999"
|
||||
cy="6.3499999"
|
||||
r="4.7247024" />
|
||||
<path
|
||||
id="path852"
|
||||
d="M 6.3499999,6.3499999 9.7076994,3.0260467"
|
||||
style="fill:#00ff00;stroke:#93ffc9;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#007033;stroke-width:0.765;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 11.074702,6.3499999 A 4.7247024,4.7247024 0 0 1 6.3499999,11.074702 4.7247024,4.7247024 0 0 1 1.6252975,6.3499999 4.7247024,4.7247024 0 0 1 6.3499999,1.6252975 4.7247024,4.7247024 0 0 1 11.074702,6.3499999 Z"
|
||||
id="path842" />
|
||||
<circle
|
||||
r="0.46234927"
|
||||
cy="5.3087621"
|
||||
cx="8.8007536"
|
||||
id="path839"
|
||||
style="fill:#93ffc9;fill-opacity:1;stroke:none;stroke-width:0.38708;stroke-linecap:round;stroke-linejoin:round" />
|
||||
<circle
|
||||
style="fill:#57c78f;fill-opacity:1;stroke:none;stroke-width:0.38708;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="circle841"
|
||||
cx="8.2715864"
|
||||
cy="7.9545956"
|
||||
r="0.46234927" />
|
||||
<circle
|
||||
r="0.46234927"
|
||||
cy="9.3416424"
|
||||
cx="8.2715864"
|
||||
id="circle843"
|
||||
style="fill:#43c082;fill-opacity:1;stroke:none;stroke-width:0.38708;stroke-linecap:round;stroke-linejoin:round" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
|
@ -0,0 +1,321 @@
|
|||
const FIELD_WIDTH = [11, 11, 15]; // for each phase
|
||||
const FIELD_HEIGHT = FIELD_WIDTH;
|
||||
const FIELD_LINE_WIDTH = 2;
|
||||
const FIELD_MARGIN = 2;
|
||||
const FIELD_COUNT_X = 10;
|
||||
const FIELD_COUNT_Y = FIELD_COUNT_X;
|
||||
const MARGIN_LEFT = 16;
|
||||
const MARGIN_TOP = 42;
|
||||
const HEADING_COLOR = ['#FF7070', '#7070FF']; // for each player
|
||||
const FIELD_LINE_COLOR = '#FFFFFF';
|
||||
const FIELD_BG_COLOR_REGULAR = '#808080';
|
||||
const FIELD_BG_COLOR_SELECTED = '#FFFFFF';
|
||||
const SHIP_COLOR_PLACED = '#507090';
|
||||
const SHIP_COLOR_AVAIL = '#204070';
|
||||
const STATE_HIT_COLOR = ['#B00000', '#0000B0']; // for each player
|
||||
const STATE_MISS_COLOR = '#404040';
|
||||
const SHIP_CAPS = [
|
||||
1, // Carrier (type 0, size 5)
|
||||
2, // Battleship (type 1, size 4)
|
||||
3, // Destroyer (type 2, size 3)
|
||||
4 // Patrol Boat (type 3, size 2)
|
||||
];
|
||||
const FULL_HITS = SHIP_CAPS.reduce((a, c, i) => a + c*(5 -i), 0);
|
||||
const INDICATOR_LAYOUT = [
|
||||
[0, 1, 1, 3],
|
||||
[2, 2, 2, 3, 3, 3]
|
||||
];
|
||||
const INDICATORS = INDICATOR_LAYOUT.reduce((a, c, i) => {
|
||||
let y = FIELD_COUNT_Y + 1 + i;
|
||||
let x1 = 0;
|
||||
c.forEach(type => {
|
||||
let size = 5 - type;
|
||||
let x2 = x1 + size - 1;
|
||||
a.push({ "type": type, "position": [x1, y, x2, y] });
|
||||
x1 += size;
|
||||
});
|
||||
return a;
|
||||
}, []).sort((l, r) => (l.type - r.type)*FIELD_COUNT_X*FIELD_COUNT_Y
|
||||
+ (l.position[0] + l.position[1]*FIELD_COUNT_X
|
||||
- (r.position[0] + r.position[1]*FIELD_COUNT_X)));
|
||||
|
||||
let phase = 0;
|
||||
let player = 0;
|
||||
let selected = [-10, -10];
|
||||
let to_add = null;
|
||||
let to_rem = null;
|
||||
let placements = [[],[]];
|
||||
let field_states = [new Array(100).fill(0), new Array(FIELD_COUNT_X*FIELD_COUNT_Y).fill(0)];
|
||||
let current = [[0, 0],[0, 0]];
|
||||
let behaviours = []; // depending on phase
|
||||
|
||||
function getLeftOffset(x) {
|
||||
return MARGIN_LEFT + x*(FIELD_WIDTH[phase] + FIELD_MARGIN + 1);
|
||||
}
|
||||
|
||||
function getTopOffset(y) {
|
||||
return MARGIN_TOP + y*(FIELD_HEIGHT[phase] + FIELD_MARGIN + 1);
|
||||
}
|
||||
|
||||
function getFieldState(x, y) {
|
||||
return field_states[player][x + FIELD_COUNT_X*y];
|
||||
}
|
||||
|
||||
function setFieldState(x, y, value) {
|
||||
field_states[player][x + FIELD_COUNT_X*y] = value;
|
||||
}
|
||||
|
||||
function updateFieldStates() {
|
||||
placements.forEach((ps, i) => {
|
||||
ps.forEach(p => {
|
||||
let pos = p.position;
|
||||
for (let x = pos[0]; x <= pos[2]; x++)
|
||||
for (let y = pos[1]; y <= pos[3]; y++) {
|
||||
field_states[i][x + FIELD_COUNT_X*y] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getHitCount() {
|
||||
return field_states[player].reduce(
|
||||
(v, state) => state == 3 ? v + 1 : v,
|
||||
0);
|
||||
}
|
||||
|
||||
function drawField(x, y, selected) {
|
||||
let x1 = getLeftOffset(x);
|
||||
let y1 = getTopOffset(y);
|
||||
let x2 = x1 + FIELD_WIDTH[phase];
|
||||
let y2 = y1 + FIELD_HEIGHT[phase];
|
||||
let field_state = getFieldState(x, y);
|
||||
g.setColor(selected ? FIELD_BG_COLOR_SELECTED : FIELD_BG_COLOR_REGULAR);
|
||||
g.fillRect(x1, y1, x2, y2);
|
||||
g.setColor(FIELD_LINE_COLOR);
|
||||
g.drawRect(x1, y1, x2, y2);
|
||||
switch (field_state) {
|
||||
case 2:
|
||||
g.setColor(STATE_MISS_COLOR);
|
||||
g.fillCircle(x1 + FIELD_WIDTH[phase]/2 + 1, y1 + FIELD_HEIGHT[phase]/2 + 1, FIELD_WIDTH[phase]/2 - 3);
|
||||
break;
|
||||
case 3:
|
||||
g.setColor(STATE_HIT_COLOR[player]);
|
||||
g.fillCircle(x1 + FIELD_WIDTH[phase]/2 + 1, y1 + FIELD_HEIGHT[phase]/2 + 1, FIELD_WIDTH[phase]/2 - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function drawFields(x1, y1, x2, y2) {
|
||||
let l = getLeftOffset(x1);
|
||||
let t = getTopOffset(y1);
|
||||
let r = getLeftOffset(x2) + FIELD_WIDTH[phase] + FIELD_MARGIN;
|
||||
let b = getTopOffset(y2) + FIELD_HEIGHT[phase] + FIELD_MARGIN;
|
||||
g.clearRect(l, t, r, b);
|
||||
for (let x = x1; x <= x2; x++)
|
||||
for (let y = y1; y <= y2; y++) {
|
||||
drawField(x, y, x == current[player][0] && y == current[player][1]);
|
||||
}
|
||||
}
|
||||
|
||||
function drawShip(x1, y1, x2, y2, color) {
|
||||
g.setColor(color);
|
||||
let diam = Math.min(FIELD_HEIGHT[phase], FIELD_WIDTH[phase]) - 3;
|
||||
let rad = diam/2;
|
||||
let cx1 = getLeftOffset(x1) + FIELD_WIDTH[phase]/2 + 1;
|
||||
let cy1 = getTopOffset(y1) + FIELD_HEIGHT[phase]/2 + 1;
|
||||
let cx2 = getLeftOffset(x2) + FIELD_WIDTH[phase]/2 + 1;
|
||||
let cy2 = getTopOffset(y2) + FIELD_HEIGHT[phase]/2 + 1;
|
||||
if (x1 == x2) {
|
||||
g.fillRect(cx1 - rad, cy1, cx1 + rad, cy2);
|
||||
} else {
|
||||
g.fillRect(cx1, cy1 - rad, cx2, cy1 + rad);
|
||||
}
|
||||
g.fillCircle(cx1, cy1, rad);
|
||||
g.fillCircle(cx2, cy2, rad);
|
||||
}
|
||||
|
||||
function hasCollision(pos) {
|
||||
return placements[player].some(
|
||||
p => pos[0] <= p.position[2]
|
||||
&& pos[2] >= p.position[0]
|
||||
&& pos[1] <= p.position[3]
|
||||
&& pos[3] >= p.position[1]);
|
||||
}
|
||||
|
||||
function isAvailable(type) {
|
||||
let count = placements[player].reduce(
|
||||
(v, p) => p.type == type ? v + 1 : v,
|
||||
0);
|
||||
return count < SHIP_CAPS[type];
|
||||
}
|
||||
|
||||
function determineChanges() {
|
||||
to_rem = to_add;
|
||||
to_add = null;
|
||||
if (selected[0] == current[player][0] && selected[1] == current[player][1]) return;
|
||||
if (selected[0] == current[player][0]) {
|
||||
let size = Math.abs(selected[1] - current[player][1]) + 1;
|
||||
if (size < 2 || size > 5 ) return;
|
||||
let y1 = Math.min(selected[1], current[player][1]);
|
||||
let y2 = Math.max(selected[1], current[player][1]);
|
||||
let pos = [current[player][0], y1, current[player][0], y2];
|
||||
let type = 5 - size;
|
||||
if (!hasCollision(pos) && isAvailable(type)) {
|
||||
to_add = { "type": type, "position": pos };
|
||||
}
|
||||
}
|
||||
if (selected[1] == current[player][1]) {
|
||||
let size = Math.abs(selected[0] - current[player][0]) + 1;
|
||||
if (size < 2 || size > 5 ) return;
|
||||
let x1 = Math.min(selected[0], current[player][0]);
|
||||
let x2 = Math.max(selected[0], current[player][0]);
|
||||
let pos = [x1, current[player][1], x2, current[player][1]];
|
||||
let type = 5 - size;
|
||||
if (!hasCollision(pos) && isAvailable(type)) {
|
||||
to_add = { "type": type, "position": pos };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addPlacement(descriptor) {
|
||||
placements[player].push(descriptor);
|
||||
placements[player].sort((l, r) => l.type - r.type);
|
||||
}
|
||||
|
||||
function drawShipPlacements() {
|
||||
if (to_rem) {
|
||||
drawFields.apply(null, to_rem.position);
|
||||
}
|
||||
placements[player].forEach(
|
||||
p => drawShip.apply(null, p.position.concat([SHIP_COLOR_PLACED])));
|
||||
if (to_add) {
|
||||
drawShip.apply(null, to_add.position.concat([SHIP_COLOR_PLACED]));
|
||||
}
|
||||
}
|
||||
|
||||
function drawShipIndicator() {
|
||||
let p = to_add
|
||||
? placements[player].concat(to_add).sort((l, r) => l.type - r.type)
|
||||
: placements[player];
|
||||
let pi = 0;
|
||||
INDICATORS.forEach(indicator => {
|
||||
let color = SHIP_COLOR_AVAIL;
|
||||
if (pi < p.length && p[pi].type == indicator.type) {
|
||||
pi += 1;
|
||||
color = SHIP_COLOR_PLACED;
|
||||
}
|
||||
drawShip.apply(null, indicator.position.concat(color));
|
||||
});
|
||||
}
|
||||
|
||||
function drawHeading(text) {
|
||||
g.clearRect(0, 20, 100, 32);
|
||||
g.setColor(HEADING_COLOR[player]);
|
||||
g.setFont('4x6', 2.8);
|
||||
g.drawString(text, MARGIN_LEFT, 20);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
g.clear();
|
||||
drawHeading('Player ' + (player + 1));
|
||||
drawFields(0, 0, 9, 9);
|
||||
}
|
||||
|
||||
function showResults() {
|
||||
let text1 = 'Player ' + (player + 1) + ' won!';
|
||||
let text2 = 'Congratulations!';
|
||||
g.clear();
|
||||
g.clearRect(0, 20, 100, 32);
|
||||
g.setColor(HEADING_COLOR[player]);
|
||||
g.setFont('Vector', 20);
|
||||
g.drawString(text1, MARGIN_LEFT, 80);
|
||||
g.drawString(text2, MARGIN_LEFT, 120);
|
||||
}
|
||||
|
||||
function moveSelection(dx, dy) {
|
||||
let x = current[player][0];
|
||||
let y = current[player][1];
|
||||
drawField(x, y, false);
|
||||
current[player][0] = x = (x + dx + FIELD_COUNT_X)%FIELD_COUNT_X;
|
||||
current[player][1] = y = (y + dy + FIELD_COUNT_Y)%FIELD_COUNT_Y;
|
||||
drawField(x, y, true);
|
||||
}
|
||||
|
||||
behaviours.push({
|
||||
"move": (dx, dy) => {
|
||||
moveSelection(dx, dy);
|
||||
determineChanges();
|
||||
drawShipPlacements();
|
||||
drawShipIndicator();
|
||||
},
|
||||
"action": _ => {
|
||||
if (to_add) {
|
||||
addPlacement(to_add);
|
||||
to_add = null;
|
||||
selected = [-10, -10];
|
||||
if (placements[player].length == 10) {
|
||||
behaviours[phase].transition();
|
||||
}
|
||||
} else {
|
||||
selected = [current[player][0], current[player][1]];
|
||||
}
|
||||
},
|
||||
"transition": _ => {
|
||||
current[0] = [0, 0];
|
||||
player = 1;
|
||||
phase = 1;
|
||||
reset();
|
||||
drawShipIndicator();
|
||||
}
|
||||
});
|
||||
|
||||
behaviours.push({
|
||||
"move": behaviours[0].move,
|
||||
"action": behaviours[0].action,
|
||||
"transition": _ => {
|
||||
current[1] = [0, 0];
|
||||
player = 0;
|
||||
phase = 2;
|
||||
updateFieldStates();
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
behaviours.push({
|
||||
"move": (dx, dy) => moveSelection(dx, dy),
|
||||
"action": _ => {
|
||||
let x = current[player][0];
|
||||
let y = current[player][1];
|
||||
let field_state = getFieldState(x, y);
|
||||
if (field_state > 1) return;
|
||||
setFieldState(x, y, field_state + 2);
|
||||
drawField(x, y, true);
|
||||
Bangle.buzz(200 + field_state*800, 0.5 + field_state*0.5);
|
||||
if (getHitCount() < FULL_HITS) {
|
||||
player = (player + 1)%2;
|
||||
setTimeout(reset, 1000);
|
||||
} else {
|
||||
setTimeout(behaviours[phase].transition, 1000);
|
||||
}
|
||||
},
|
||||
"transition": _ => {
|
||||
phase = 3;
|
||||
showResults();
|
||||
}
|
||||
});
|
||||
|
||||
behaviours.push({
|
||||
"move": _ => {},
|
||||
"action": _ => {}
|
||||
});
|
||||
|
||||
reset();
|
||||
drawShipIndicator();
|
||||
|
||||
setWatch(_ => behaviours[phase].move(0, -1), BTN1, {repeat: true, debounce: 100});
|
||||
setWatch(_ => behaviours[phase].move(0, 1), BTN3, {repeat: true, debounce: 100});
|
||||
setWatch(_ => behaviours[phase].move(-1, 0), BTN4, {repeat: true, debounce: 100});
|
||||
setWatch(_ => behaviours[phase].move(1, 0), BTN5, {repeat: true, debounce: 100});
|
||||
setWatch(_ => behaviours[phase].action(), BTN2, {repeat: true, debounce: 100});
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial commit. Not very efficient, and widgets not working for some reason.
|
||||
0.02: Fixes; widget support
|
||||
0.03: Remove hardcoded hour buzz (you can install widchime if you miss it)
|
||||
|
|
|
@ -262,12 +262,6 @@ Graphics.prototype.drawRotLine = function (sina, cosa, cx, cy, r1, r2) {
|
|||
g.drawRotLine(Math.sin(a), Math.cos(a), CX, CY+TM, RC1, R1);
|
||||
}
|
||||
|
||||
// Clock chime on the hour.
|
||||
if (hours >= 0 && minutes === 0)
|
||||
try {
|
||||
Bangle.buzz();
|
||||
} catch (e) { }
|
||||
|
||||
// And draw widgets if we're in that mode
|
||||
if (with_widgets)
|
||||
Bangle.drawWidgets();
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
Remove 'faceUp' check as it's automatic
|
||||
0.03: Modified for use with new bootloader and firmware
|
||||
0.04: Modified to account for changes in the behavior of Graphics.fillPoly
|
||||
0.05: Slight increase to draw speed after LCD on
|
||||
|
|
|
@ -86,9 +86,9 @@ function clearTimers() {
|
|||
}
|
||||
function startTimers() {
|
||||
g.clear();
|
||||
redraw();
|
||||
Bangle.drawWidgets();
|
||||
intervalRef = setInterval(redraw,1000);
|
||||
redraw();
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
startTimers();
|
||||
|
|
|
@ -21,3 +21,6 @@
|
|||
0.20: Allow Gadgetbridge to work even with programmable:off
|
||||
0.21: Handle echo off char from Gadgetbridge app when programmable:off (fix #558)
|
||||
0.22: Stop LCD timeout being disabled on first run (when there is no settings.json)
|
||||
0.23: Move to a precalculated .boot0 file which should speed up load time
|
||||
0.24: Add Bangle.setUI polyfill
|
||||
0.25: Fix error in 'no clock app' message
|
||||
|
|
|
@ -1,68 +1,2 @@
|
|||
// This ALWAYS runs at boot
|
||||
E.setFlags({pretokenise:1});
|
||||
// Load settings...
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
if (s.ble!==false) {
|
||||
if (s.HID) { // Human interface device
|
||||
if (s.HID=="joy") Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));
|
||||
else if (s.HID=="kb") Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));
|
||||
else /*kbmedia*/Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));
|
||||
NRF.setServices({}, {uart:true, hid:Bangle.HID});
|
||||
}
|
||||
}
|
||||
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log) Terminal.setConsole(true); // if showing debug, force REPL onto terminal
|
||||
else E.setConsole(null,{force:true}); // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
||||
/* If not programmable add our own handler for Bluetooth data
|
||||
to allow Gadgetbridge commands to be received*/
|
||||
Bluetooth.line="";
|
||||
Bluetooth.on('data',function(d) {
|
||||
var l = (Bluetooth.line + d).split("\n");
|
||||
Bluetooth.line = l.pop();
|
||||
l.forEach(n=>Bluetooth.emit("line",n));
|
||||
});
|
||||
Bluetooth.on('line',function(l) {
|
||||
if (l.startsWith('\x10')) l=l.slice(1);
|
||||
if (l.startsWith('GB({') && l.endsWith('})') && global.GB)
|
||||
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
|
||||
});
|
||||
} else {
|
||||
if (s.log && !NRF.getSecurityStatus().connected) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection)
|
||||
else Bluetooth.setConsole(true); // else if no debug, force REPL to Bluetooth
|
||||
}
|
||||
// we just reset, so BLE should be on.
|
||||
// Don't disconnect if something is already connected to us
|
||||
if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep();
|
||||
// Set time, vibrate, beep, etc
|
||||
if (!Bangle.F_BEEPSET) {
|
||||
if (!s.vibrate) Bangle.buzz=Promise.resolve;
|
||||
if (s.beep===false) Bangle.beep=Promise.resolve;
|
||||
else if (s.beep=="vib") Bangle.beep = function (time, freq) {
|
||||
return new Promise(function(resolve) {
|
||||
if ((0|freq)<=0) freq=4000;
|
||||
if ((0|time)<=0) time=200;
|
||||
if (time>5000) time=5000;
|
||||
analogWrite(D13,0.1,{freq:freq});
|
||||
setTimeout(function() {
|
||||
digitalWrite(D13,0);
|
||||
resolve();
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
}
|
||||
if (s.timeout!==undefined) Bangle.setLCDTimeout(s.timeout);
|
||||
if (!s.timeout) Bangle.setLCDPower(1);
|
||||
E.setTimeZone(s.timezone);
|
||||
delete s;
|
||||
// Draw out of memory errors onto the screen
|
||||
E.on('errorFlag', function(errorFlags) {
|
||||
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
||||
print("Interpreter error:", errorFlags);
|
||||
E.getErrorFlags(); // clear flags so we get called next time
|
||||
});
|
||||
// stop users doing bad things!
|
||||
global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }
|
||||
// Load *.boot.js files
|
||||
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
|
||||
eval(require('Storage').read(bootFile));
|
||||
});
|
||||
// Initially this runs and rewrites itself
|
||||
eval(require('Storage').read('bootupdate.js'));
|
||||
|
|
|
@ -14,11 +14,7 @@ if (!clockApp) {
|
|||
if (clockApp)
|
||||
clockApp = require("Storage").read(clockApp.src);
|
||||
}
|
||||
if (!clockApp) clockApp=`E.showMessage("No Clock Found");
|
||||
setWatch(() => {
|
||||
Bangle.showLauncher();
|
||||
}, BTN2, {repeat:false,edge:"falling"});)
|
||||
`;
|
||||
if (!clockApp) clockApp=`E.showMessage("No Clock Found");setWatch(()=>{Bangle.showLauncher();}, BTN2, {repeat:false,edge:"falling"});`;
|
||||
// check to see if our clock is wrong - if it is use GPS time
|
||||
if ((new Date()).getFullYear()<2000) {
|
||||
E.showMessage("Searching for\nGPS time");
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/* This rewrites boot0.js based on current settings. If settings changed then it
|
||||
recalculates, but this avoids us doing a whole bunch of reconfiguration most
|
||||
of the time. */
|
||||
E.showMessage("Updating boot0...");
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
var boot = "";
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))!=${CRC}) { eval(require('Storage').read('bootupdate.js'));} else {\n`;
|
||||
boot += `E.setFlags({pretokenise:1});\n`;
|
||||
if (s.ble!==false) {
|
||||
if (s.HID) { // Human interface device
|
||||
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
||||
else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));`
|
||||
else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`;
|
||||
boot += `NRF.setServices({}, {uart:true, hid:Bangle.HID});\n`;
|
||||
}
|
||||
}
|
||||
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
|
||||
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
||||
/* If not programmable add our own handler for Bluetooth data
|
||||
to allow Gadgetbridge commands to be received*/
|
||||
boot += `
|
||||
Bluetooth.line="";
|
||||
Bluetooth.on('data',function(d) {
|
||||
var l = (Bluetooth.line + d).split("\n");
|
||||
Bluetooth.line = l.pop();
|
||||
l.forEach(n=>Bluetooth.emit("line",n));
|
||||
});
|
||||
Bluetooth.on('line',function(l) {
|
||||
if (l.startsWith('\x10')) l=l.slice(1);
|
||||
if (l.startsWith('GB({') && l.endsWith('})') && global.GB)
|
||||
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
|
||||
});\n`;
|
||||
} else {
|
||||
if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
|
||||
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
|
||||
}
|
||||
// we just reset, so BLE should be on.
|
||||
// Don't disconnect if something is already connected to us
|
||||
if (s.ble===false) boot += `if (!NRF.getSecurityStatus().connected) NRF.sleep();\n`;
|
||||
// Set time
|
||||
if (s.timeout!==undefined) boot += `Bangle.setLCDTimeout(${s.timeout});\n`;
|
||||
if (!s.timeout) boot += `Bangle.setLCDPower(1);\n`;
|
||||
boot += `E.setTimeZone(${s.timezone});`;
|
||||
// Set vibrate, beep, etc IF on older firmwares
|
||||
if (!Bangle.F_BEEPSET) {
|
||||
if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n`
|
||||
if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n`
|
||||
else if (s.beep=="vib") boot += `Bangle.beep = function (time, freq) {
|
||||
return new Promise(function(resolve) {
|
||||
if ((0|freq)<=0) freq=4000;
|
||||
if ((0|time)<=0) time=200;
|
||||
if (time>5000) time=5000;
|
||||
analogWrite(D13,0.1,{freq:freq});
|
||||
setTimeout(function() {
|
||||
digitalWrite(D13,0);
|
||||
resolve();
|
||||
}, time);
|
||||
});
|
||||
};\n`;
|
||||
}
|
||||
// Draw out of memory errors onto the screen
|
||||
boot += `E.on('errorFlag', function(errorFlags) {
|
||||
g.reset(1).setColor("#ff0000").setFont("6x8").setFontAlign(0,1).drawString(errorFlags,g.getWidth()/2,g.getHeight()-1).flip();
|
||||
print("Interpreter error:", errorFlags);
|
||||
E.getErrorFlags(); // clear flags so we get called next time
|
||||
});\n`;
|
||||
// stop users doing bad things!
|
||||
if (global.save) boot += `global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }\n`;
|
||||
// Apply any settings-specific stuff
|
||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.quiet && s.qmOptions) boot+=`Bangle.setOptions(${E.toJS(s.qmOptions)});\n`;
|
||||
if (s.quiet && s.qmBrightness) {
|
||||
if (s.qmBrightness!=1) boot+=`Bangle.setLCDBrightness(${s.qmBrightness});\n`;
|
||||
} else {
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
}
|
||||
if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
// Pre-2v10 firmwares without a theme/setUI
|
||||
if (!g.theme) {
|
||||
boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7};\n`;
|
||||
}
|
||||
if (!Bangle.setUI) {
|
||||
boot += `Bangle.setUI=function(mode, cb) {
|
||||
if (Bangle.btnWatches) {
|
||||
Bangle.btnWatches.forEach(clearWatch);
|
||||
delete Bangle.btnWatches;
|
||||
}
|
||||
if (Bangle.swipeHandler) {
|
||||
Bangle.removeListener("swipe", Bangle.swipeHandler);
|
||||
delete Bangle.swipeHandler;
|
||||
}
|
||||
if (Bangle.touchandler) {
|
||||
Bangle.removeListener("touch", Bangle.touchHandler);
|
||||
delete Bangle.touchHandler;
|
||||
}
|
||||
function b() {
|
||||
try{Bangle.buzz(20);}catch(e){}
|
||||
}
|
||||
if (!mode) return;
|
||||
else if (mode=="updown") {
|
||||
Bangle.btnWatches = [
|
||||
setWatch(function() { b();cb(-1); }, BTN1, {repeat:1}),
|
||||
setWatch(function() { b();cb(1); }, BTN3, {repeat:1}),
|
||||
setWatch(function() { b();cb(); }, BTN2, {repeat:1})
|
||||
];
|
||||
} else if (mode=="leftright") {
|
||||
Bangle.btnWatches = [
|
||||
setWatch(function() { b();cb(-1); }, BTN1, {repeat:1}),
|
||||
setWatch(function() { b();cb(1); }, BTN3, {repeat:1}),
|
||||
setWatch(function() { b();cb(); }, BTN2, {repeat:1})
|
||||
];
|
||||
Bangle.swipeHandler = d => {b();cb(d);};
|
||||
Bangle.on("swipe", Bangle.swipeHandler);
|
||||
Bangle.touchHandler = d => {b();cb();};
|
||||
Bangle.on("touch", Bangle.touchHandler);
|
||||
} else
|
||||
throw new Error("Unknown UI mode");
|
||||
};\n`;
|
||||
}
|
||||
// Append *.boot.js files
|
||||
require('Storage').list(/\.boot\.js/).forEach(bootFile=>{
|
||||
boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+"\n";
|
||||
});
|
||||
boot += "}\n";// initial 'if'
|
||||
var s = require('Storage').write('.boot0',boot);
|
||||
delete boot;
|
||||
E.showMessage("Reloading...");
|
||||
eval(require('Storage').read('.boot0'));
|
||||
eval(require('Storage').read('.bootcde'));
|
|
@ -3,3 +3,4 @@
|
|||
0.09: Add BTN1 status line with ID,Fw ver, mem %, battery %
|
||||
0.10: Icon fixed for transparency
|
||||
0.11: added Heart Rate Monitor status and ability to turn on/off
|
||||
0.12: added support for different locales
|
||||
|
|
|
@ -2,7 +2,6 @@ var fontsize = 3;
|
|||
var locale = require("locale");
|
||||
var marginTop = 40;
|
||||
var flag = false;
|
||||
var WeekDays = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
||||
|
||||
var hrtOn = false;
|
||||
var hrtStr = "Hrt: ??? bpm";
|
||||
|
@ -26,19 +25,14 @@ function drawAll(){
|
|||
}
|
||||
|
||||
function updateRest(now){
|
||||
let date = locale.date(now,false);
|
||||
writeLine(WeekDays[now.getDay()],1);
|
||||
writeLine(date,2);
|
||||
writeLine(locale.dow(now),1);
|
||||
writeLine(locale.date(now,1),2);
|
||||
drawInfo(5);
|
||||
}
|
||||
function updateTime(){
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
let now = new Date();
|
||||
let h = now.getHours();
|
||||
let m = now.getMinutes();
|
||||
h = h>=10?h:"0"+h;
|
||||
m = m>=10?m:"0"+m;
|
||||
writeLine(h+":"+m,0);
|
||||
writeLine(locale.time(now,1),0);
|
||||
writeLine(flag?" ":"_",3);
|
||||
flag = !flag;
|
||||
if(now.getMinutes() == 0)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
|
|
@ -0,0 +1,43 @@
|
|||
# Color Catalog
|
||||
|
||||
This is a very basic app that displays RGB565 and RGB888 colors, its name and code in screen.
|
||||
This apps is based in the "common UI/UX".
|
||||
|
||||
|
||||
Launcher icon
|
||||
|
||||

|
||||
|
||||
1st screen - page 1
|
||||
|
||||

|
||||
|
||||
2nd screen - page
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Open and 2 rows of colors per page
|
||||
Interact with a horizontal swipe/slide to move to next or previos page in order to display other colors
|
||||
|
||||
## Features
|
||||
|
||||
Colours, font, user input, load widgets
|
||||
|
||||
|
||||
## Controls
|
||||
Press left area -
|
||||
Press righ area -
|
||||
Press center area -
|
||||
Swipe Left - Load the previous page and its colors
|
||||
Swipe Right - Load the next page and its colors
|
||||
BTN1 - Prints Button1
|
||||
BTN2 - Prints Button2
|
||||
BTN3 - Quit to Launcher
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
This app is so basic that probably the easiest is to just edit the code
|
||||
Otherwise you can contact me [here](https://github.com/dapgo)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AHczAAYXyh3jC6s+93gC6nuAAMwC6UDC4QYMC40OC4fuC6JeBC53/AAZeEAAKSLC4sPmYXDSJfdAAZNB/4YDR5YXFIQP/MILZMC4iNCmfznxGCgbKDC5PtIgQYBXwUykTDHC4ndOoc/CwcikAXQIwMCCwMikYXRh4uCAAIXQgUT+YXDMAzXEnoWC8kRiQXEJAwXEma8B93hiMRn5IK5gADka8B91BC4IGBF5IXE7s/+fuCwJIFL4wXFDAMxC4ZICa44XG6cyC4URl8yCw4XH7szkIXCiYWIC4/UkZIDag4XJ61jJAUSCxIXHi1i+YWMa4pGBszUBkUq1UicIIACC5XWsMRj8yCwIXPmVGC4MT1WiC6EzIwMRmYWCC5gMBF4IXBjUzCwQXQsMa1R3BC5xHDRQOqD4IXRnQWBC6gWCC84A/AGg="))
|
|
@ -0,0 +1,182 @@
|
|||
/* Color show for Bangle.js
|
||||
grey
|
||||
RGB888:#404040 / 0x404040
|
||||
RGB565:#4208 / 0x4208
|
||||
grey RGB888:#5c5c5c / 0x5c5c5c
|
||||
RGB565:#5AEB / 0x5AEB
|
||||
*/
|
||||
|
||||
|
||||
var v_model=process.env.BOARD;
|
||||
console.log("device="+v_model);
|
||||
|
||||
var x_max_screen=g.getWidth();//240;
|
||||
var y_max_screen=g.getHeight(); //240;
|
||||
var y_wg_bottom=g.getHeight()-25;
|
||||
var y_wg_top=25;
|
||||
if (v_model=='BANGLEJS') {
|
||||
var x_btn_area=215;
|
||||
var x_max_usable_area=x_btn_area;//Pend! only for bangle.js
|
||||
var y_btn2=124; //harcoded for bangle.js cuz it is not the half of
|
||||
} else x_max_usable_area=240;
|
||||
|
||||
var contador=1;
|
||||
var cont_items=0;
|
||||
var cont_row=0;
|
||||
var v_boxes_row=4;
|
||||
var cont_page=1;
|
||||
var v_boxwidth=40;
|
||||
var v_boxheight=10;
|
||||
var v_acolorpos=0;
|
||||
var v_font1size=11;
|
||||
var v_fontsize=13;
|
||||
var v_color_b_area='#111111';//black
|
||||
var v_color_b_area2=0x5AEB;//Dark
|
||||
var v_color_text='#FB0E01';
|
||||
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
|
||||
//RGB565 requires only 16 (5+6+5) bits/2 bytes
|
||||
var a_colors_str= Array('White RGB565 0x','Orange','DarkGreen','Yellow',
|
||||
'Maroon','Blue','green','Purple',
|
||||
'cyan','olive','DarkCyan','DarkGrey',
|
||||
'Navy','Red','Magenta','GreenYellow',
|
||||
'Blush RGB888','pure red','Orange','Grey green',
|
||||
'D. grey','Almond','Amber','Bone',
|
||||
'Canary','Aero blue','Camel','Baby pink',
|
||||
'Y.Corn','Cultured','Eigengrau','Citrine');
|
||||
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,
|
||||
0x7800,0x001F,0x07E0,0x780F,
|
||||
0x07FF,0x7BE0,0x03EF,0x7BEF,
|
||||
0x000F,0xF800,0xF81F,0xAFE5,
|
||||
'#DE5D83','#FB0E01','#E56E06','#7E795C',
|
||||
'#404040','#EFDECD','#FFBF00','#E3DAC9',
|
||||
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
|
||||
'#FBEC5D','#F5F5F5','#16161D','#E4D00A');
|
||||
var v_color_lines=0xFFFF; //White hex format
|
||||
|
||||
|
||||
//the biggest usable area, button area not included
|
||||
function ClearActiveArea(){
|
||||
g.setColor(v_color_b_area);
|
||||
g.fillRect(0,y_wg_top,x_max_usable_area,y_wg_bottom); //fill all screen except widget areas
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
function UserInput(){
|
||||
Bangle.on('touch', function(button){
|
||||
switch(button){
|
||||
case 1:
|
||||
console.log("Touch 1");//left
|
||||
break;
|
||||
case 2:
|
||||
console.log("Touch 2");//right
|
||||
break;
|
||||
case 3:
|
||||
console.log("Touch 3");//center 1+2
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (v_model=='BANGLEJS') {
|
||||
//only the name of the function
|
||||
setWatch(Bangle.showLauncher, BTN3, { repeat: true });
|
||||
}
|
||||
Bangle.on('swipe', dir => {
|
||||
if(dir == 1) {
|
||||
console.log("v_acolorpos"+v_acolorpos+"a_colors.length"+a_colors.length);
|
||||
if (v_acolorpos<a_colors.length-1) {
|
||||
cont_page++;
|
||||
console.log("swipe page"+cont_page);
|
||||
PrintScreen(cont_page);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (cont_page>0) cont_page--;
|
||||
console.log("swipe page"+cont_page);
|
||||
PrintScreen(cont_page);
|
||||
}
|
||||
});
|
||||
console.log("Log: Input conditions loaded");
|
||||
} //end of UserInput
|
||||
|
||||
function DrawBangleButtons(){
|
||||
g.setFontVector(v_font1size);
|
||||
g.setColor(v_color_lines);//White
|
||||
|
||||
//g.drawString("Dwn", x_max_screen-g.stringWidth("Dwn"),y_wg_top+v_font1size+1);
|
||||
//above Btn2
|
||||
//g.setFontVector(v_font1size).drawString("Off", x_max_screen-g.stringWidth("Off"),y_btn2-(2*v_font1size));
|
||||
//g.drawString("Set", x_max_screen-g.stringWidth("Set"),y_btn2-v_font1size);
|
||||
//above Btn3
|
||||
g.drawString("Quit", x_max_screen-g.stringWidth("Quit"),y_wg_bottom-(2*v_font1size));
|
||||
g.flip();
|
||||
g.setColor(v_color_text); //green
|
||||
g.setFontVector(v_font1size);
|
||||
g.drawString("B1", x_max_screen-g.stringWidth("B1"),y_wg_top);
|
||||
g.drawString("B2", x_max_screen-g.stringWidth("B2"),y_btn2);
|
||||
g.drawString("B3",x_max_screen-g.stringWidth("B3"),y_wg_bottom-v_font1size);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function PrintScreen(page){
|
||||
ClearActiveArea();
|
||||
g.setColor(v_color_statictxt);
|
||||
g.setFont("Vector",v_fontsize);
|
||||
g.drawString("Page "+page,10,y_wg_top+5);
|
||||
g.flip();
|
||||
|
||||
v_acolorpos=page*(v_boxes_row*2);
|
||||
|
||||
console.log("page"+cont_page+"arraypos"+v_acolorpos);
|
||||
for (cont_row=0;cont_row<2;cont_row++){
|
||||
console.log("row"+cont_row);
|
||||
for (cont_items=0;cont_items<v_boxes_row;cont_items++){
|
||||
|
||||
g.setColor(a_colors[v_acolorpos]); //dynamic color
|
||||
//console.log("col"+cont_items);
|
||||
var v_x1_box=(v_boxwidth*cont_items)+5;
|
||||
var v_x2_box=(v_boxwidth*(cont_items+1))+5;
|
||||
//y for second row is v_boxheight+strings
|
||||
var v_pos_row=y_wg_top+v_fontsize+5+(cont_row*v_boxheight)+(cont_row*v_boxes_row*(v_fontsize+4));
|
||||
// console.log("v_pos_row"+v_pos_row);
|
||||
var v_y1_box=(v_pos_row);
|
||||
var v_y2_box=(v_pos_row+v_boxheight);
|
||||
g.fillRect(v_x1_box,v_y1_box,v_x2_box,v_y2_box); //fill all screen except widget areas
|
||||
//identify color format
|
||||
var v_string=v_acolorpos+" "+a_colors_str[v_acolorpos]+" "+a_colors[v_acolorpos].toString(16);
|
||||
|
||||
|
||||
//Align always inside the display
|
||||
if ((v_x1_box+g.stringWidth(v_string))<x_max_usable_area){
|
||||
var v_x_pos_str=v_x1_box;
|
||||
}
|
||||
else var v_x_pos_str=x_max_usable_area-g.stringWidth(v_string);
|
||||
//y positions next text line
|
||||
var v_y_pos_str=v_y2_box+8+((v_fontsize+2)*cont_items);
|
||||
g.setFont("Vector",v_fontsize);
|
||||
g.drawString(v_string,v_x_pos_str,v_y_pos_str);
|
||||
//line below 2nd string
|
||||
|
||||
g.flip();
|
||||
|
||||
if (v_acolorpos<a_colors.length-1) v_acolorpos++;
|
||||
else break; //no more print until swipe
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
console.log("**************************");
|
||||
console.log("Log: *** Color Catalog app");
|
||||
console.log("array length"+a_colors.length);
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
if (v_model=='BANGLEJS') DrawBangleButtons();
|
||||
UserInput();
|
||||
PrintScreen(0);
|
||||
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 104 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|
|
@ -0,0 +1,12 @@
|
|||
# countdown-timer
|
||||
|
||||
A basic bangle.js timer with a focus on usability.
|
||||
|
||||
* Start or Pause the timer with BTN1
|
||||
* Reset the timer with BTN2
|
||||
* Exit the application with BTN3
|
||||
* Touch the right side of the screen to increase the time amount by 1 second
|
||||
* Touch the left side of the scren to decrease the time amount by 1 second
|
||||
* Touching and holding the screen will increase or decrease the time amount by 60 seconds at a time.
|
||||
|
||||
Icons made by [Freepik](https://www.freepik.com) from [Flaticon](https://www.flaticon.com/).
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgMJhvd7tEogkSC4lAC6xWUgUgNysiC6hGBL58BiMQLoYXDMJkRAAIWEC4gYJFwIABCwgXFDBAWCpoXLMYwuCigUD6czmUSmQYKC4QuD6f//8xiUzmckJJIuECwQXEDAgXGFwc/C4XykYXCmZIJCwVPCwQABCwczmgwHXYXUCwgXFGBAXC74XLGAQXELowXIVgYXF6QWF+Z3EJAhGFUganHJAoXFRooXLMAQXCLwwXHMAQXUMAQXCRwQWFC9HSiMvC40iCocxiKoEC4cTC4sRiLBDC5MiF4wXFmQXHL4/zkRHEO6H/OwwXFX5IXfd5HfC5s0C4/TC5skRwZ4LLxAXHMAxeIC4hIJLxYXEJAxGMJAgwFFw4XGGAZhD+UjLoxGEC4owDmMSIoouGJAgYBGIIXDCwYuGGAoABIQMiaIQuKDA/dCoguJJIwXHCxQYGCyIYFCyRjELZYA="))
|
|
@ -0,0 +1,232 @@
|
|||
const heatshrinkDecompress = require("heatshrink").decompress;
|
||||
|
||||
const playIcon = heatshrinkDecompress(atob("jEYwhC/gFwBZV3BhV3u4LLBhILCEpALCBhALDu9gBaojKHZZrVQZSbLAG4A="));
|
||||
const pauseIcon = heatshrinkDecompress(atob("jEYwhC/xGIAYoL/Bf4LfAHA="));
|
||||
const resetIcon = heatshrinkDecompress(atob("jEYwg30h3u93gAgIKHBgXuBYgIBoEEBoQWFAgQMCBYgrBE4giEBYYjGAgY+DBY4AHBZlABZQ7DLIpTFAo5ZJLYYDFTZKzLAGQA=="));
|
||||
const closeIcon = heatshrinkDecompress(atob("jEYwhC/4AEDhgKEhnMAofMCIgGECAoHFCwwIDCw4YDCxAYCCxALMEZY7KKZZrKQZibKAHIA="));
|
||||
|
||||
const timerState = {
|
||||
IDLE: 0,
|
||||
RUNNING: 1
|
||||
};
|
||||
|
||||
let currentState = timerState.IDLE;
|
||||
let remainingSeconds = 0;
|
||||
let countdownInterval = null;
|
||||
let increasingInterval = null;
|
||||
let decreasingInterval = null;
|
||||
let isDecreasingRemainingSeconds = false;
|
||||
let isIncreasingRemainingSeconds = false;
|
||||
|
||||
function main() {
|
||||
g.clear();
|
||||
g.setFont("Vector", 40);
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
registerInputHandlers();
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
function registerInputHandlers() {
|
||||
setWatch(onPrimaryButtonPressed, BTN1, { repeat: true });
|
||||
setWatch(onResetButtonPressed, BTN2, { repeat: true });
|
||||
setWatch(onExitButtonPressed, BTN3, { repeat: true });
|
||||
setWatch(onDecreaseRemainingSecondsPressed, BTN4, { repeat: true, edge: "rising" });
|
||||
setWatch(onIncreaseRemainingSecondsPressed, BTN5, { repeat: true, edge: "rising" });
|
||||
setWatch(onDecreaseRemainingSecondsReleased, BTN4, { repeat: true, edge: "falling" });
|
||||
setWatch(onIncreaseRemainingSecondsReleased, BTN5, { repeat: true, edge: "falling" });
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.clearRect(200, 0, 240, 240);
|
||||
g.clearRect(0, 0, 240, 80);
|
||||
|
||||
drawRemainingSecondsPanel();
|
||||
|
||||
g.drawImage(resetIcon, 216, 108);
|
||||
g.drawImage(closeIcon, 216, 188);
|
||||
|
||||
if (currentState == timerState.IDLE) {
|
||||
g.drawImage(playIcon, 216, 28);
|
||||
} else {
|
||||
g.drawImage(pauseIcon, 216, 28);
|
||||
}
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function drawRemainingSecondsPanel() {
|
||||
g.clearRect(0, 100, 200, 140);
|
||||
g.drawString(formatRemainingSeconds(), 105, 120);
|
||||
|
||||
if (currentState == timerState.IDLE) {
|
||||
drawSubtractRemainingSeconds();
|
||||
drawIncreaseRemainingSeconds();
|
||||
} else {
|
||||
g.setColor(0.4, 0.4, 0.4);
|
||||
drawSubtractRemainingSeconds();
|
||||
drawIncreaseRemainingSeconds();
|
||||
g.setColor(-1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSubtractRemainingSeconds() {
|
||||
if (isDecreasingRemainingSeconds) {
|
||||
drawFilledCircle(22, 117, 15);
|
||||
}
|
||||
|
||||
g.drawString("-", 25, 120);
|
||||
}
|
||||
|
||||
function drawIncreaseRemainingSeconds() {
|
||||
if (isIncreasingRemainingSeconds) {
|
||||
drawFilledCircle(182, 117, 15);
|
||||
}
|
||||
|
||||
g.drawString("+", 185, 120);
|
||||
}
|
||||
|
||||
function drawFilledCircle(x, y, radians) {
|
||||
g.setColor(0.1, 0.37, 0.87);
|
||||
g.fillCircle(x, y, radians);
|
||||
g.setColor(-1);
|
||||
}
|
||||
|
||||
function formatRemainingSeconds() {
|
||||
const minutes = Math.floor(remainingSeconds / 60);
|
||||
const minutesTens = Math.floor(minutes / 10);
|
||||
const minutesUnits = minutes % 10;
|
||||
|
||||
const seconds = remainingSeconds % 60;
|
||||
const secondsTens = Math.floor(seconds / 10);
|
||||
const secondsUnits = seconds % 10;
|
||||
|
||||
return `${minutesTens}${minutesUnits}:${secondsTens}${secondsUnits}`;
|
||||
}
|
||||
|
||||
function onPrimaryButtonPressed() {
|
||||
if (isIncreasingRemainingSeconds || isDecreasingRemainingSeconds) return;
|
||||
|
||||
if (currentState == timerState.IDLE) {
|
||||
if (remainingSeconds == 0) return;
|
||||
currentState = timerState.RUNNING;
|
||||
beginCountdown();
|
||||
draw();
|
||||
} else {
|
||||
currentState = timerState.IDLE;
|
||||
stopCountdown();
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function beginCountdown() {
|
||||
countdownInterval = setInterval(countdown, 1000);
|
||||
}
|
||||
|
||||
function countdown() {
|
||||
--remainingSeconds;
|
||||
|
||||
if (remainingSeconds <= 0) {
|
||||
remainingSeconds = 0;
|
||||
stopCountdown();
|
||||
}
|
||||
|
||||
drawRemainingSecondsPanel();
|
||||
|
||||
if (remainingSeconds <= 0) {
|
||||
drawStopMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function drawStopMessage() {
|
||||
draw();
|
||||
Bangle.buzz(800);
|
||||
g.setFont("Vector", 30);
|
||||
g.setColor(1.0, 0.91, 0);
|
||||
g.drawString("Time's Up!", 105, 40);
|
||||
g.setColor(-1);
|
||||
g.setFont("Vector", 40);
|
||||
}
|
||||
|
||||
function stopCountdown() {
|
||||
clearInterval(countdownInterval);
|
||||
countdownInterval = null;
|
||||
currentState = timerState.IDLE;
|
||||
}
|
||||
|
||||
function onResetButtonPressed() {
|
||||
currentState = timerState.IDLE;
|
||||
remainingSeconds = 0;
|
||||
draw();
|
||||
}
|
||||
|
||||
function onExitButtonPressed() {
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
|
||||
function onIncreaseRemainingSecondsPressed() {
|
||||
if (currentState == timerState.RUNNING) return;
|
||||
incremementRemainingSeconds();
|
||||
|
||||
increasingInterval = setInterval(() => {
|
||||
remainingSeconds += 60;
|
||||
|
||||
if (remainingSeconds >= 5999) {
|
||||
remainingSeconds = 5999;
|
||||
}
|
||||
|
||||
drawRemainingSecondsPanel();
|
||||
}, 250);
|
||||
|
||||
isIncreasingRemainingSeconds = true;
|
||||
|
||||
drawRemainingSecondsPanel();
|
||||
}
|
||||
|
||||
function incremementRemainingSeconds() {
|
||||
if (remainingSeconds >= 5999) return;
|
||||
++remainingSeconds;
|
||||
}
|
||||
|
||||
function onIncreaseRemainingSecondsReleased() {
|
||||
if (currentState == timerState.RUNNING) return;
|
||||
clearInterval(increasingInterval);
|
||||
isIncreasingRemainingSeconds = false;
|
||||
drawRemainingSecondsPanel();
|
||||
}
|
||||
|
||||
function onDecreaseRemainingSecondsPressed() {
|
||||
if (currentState == timerState.RUNNING) return;
|
||||
decreaseRemainingSeconds();
|
||||
|
||||
decreasingInterval = setInterval(() => {
|
||||
remainingSeconds -= 60;
|
||||
|
||||
if (remainingSeconds < 0) {
|
||||
remainingSeconds = 0;
|
||||
}
|
||||
|
||||
drawRemainingSecondsPanel();
|
||||
}, 250);
|
||||
|
||||
isDecreasingRemainingSeconds = true;
|
||||
|
||||
drawRemainingSecondsPanel();
|
||||
}
|
||||
|
||||
function decreaseRemainingSeconds() {
|
||||
if (remainingSeconds <= 0) return;
|
||||
--remainingSeconds;
|
||||
}
|
||||
|
||||
function onDecreaseRemainingSecondsReleased() {
|
||||
if (currentState == timerState.RUNNING) return;
|
||||
|
||||
clearInterval(decreasingInterval);
|
||||
|
||||
isDecreasingRemainingSeconds = false;
|
||||
draw();
|
||||
}
|
||||
|
||||
main();
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Added decrement and touch functions
|
||||
0.03: Set color - ensures widgets don't end up coloring the counter's text
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
var counter = 0;
|
||||
|
||||
g.setColor(0xFFFF);
|
||||
|
||||
function updateScreen() {
|
||||
g.clearRect(0, 50, 250, 150);
|
||||
g.setColor(0xFFFF);
|
||||
g.setFont("Vector",40).setFontAlign(0,0);
|
||||
g.drawString(Math.floor(counter), g.getWidth()/2, 100);
|
||||
g.drawString('-', 45, 100);
|
||||
|
@ -44,7 +43,7 @@ g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress
|
|||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
updateScreen();
|
||||
|
||||
// TODO: Enable saving counts to file
|
||||
// Does not work if widgets are not visible
|
||||
// Add small watch
|
||||
|
|
|
@ -10,4 +10,5 @@
|
|||
0.12: Move code to Arwes Module
|
||||
0.13: Improve icon
|
||||
0.14: Switch Icon back to 8bit web palette to fix it
|
||||
0.15: Hotfix: Remove var declaration from app image
|
||||
0.15: Hotfix: Remove var declaration from app image
|
||||
0.16: Revert: Change Counter back to button control
|
|
@ -67,7 +67,7 @@ function levelColor(l) {
|
|||
|
||||
function drawBattery() {
|
||||
const l = E.getBattery(), c = levelColor(l);
|
||||
count = l;
|
||||
// count = l;
|
||||
const xl = 45 + l * (194 - 46) / 100;
|
||||
g.clearRect(46, 58 + 80 + yOffset + 37, 193, height - 5);
|
||||
g.setColor(c).fillRect(46, 58 + 80 + yOffset + 37, xl, height - 5);
|
||||
|
@ -124,14 +124,14 @@ drawClock();
|
|||
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: "falling"});
|
||||
|
||||
// setWatch(function () {
|
||||
// count++;
|
||||
// drawCounterText();
|
||||
// }, BTN1, {repeat: true, edge: "falling"});
|
||||
// setWatch(function () {
|
||||
// count--;
|
||||
// drawCounterText();
|
||||
// }, BTN3, {repeat: true, edge: "falling"});
|
||||
setWatch(function () {
|
||||
count++;
|
||||
drawCounterText();
|
||||
}, BTN1, {repeat: true, edge: "falling"});
|
||||
setWatch(function () {
|
||||
count--;
|
||||
drawCounterText();
|
||||
}, BTN3, {repeat: true, edge: "falling"});
|
||||
|
||||
// refesh every 100 milliseconds
|
||||
setInterval(updateClock, 500);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Based on the Analog Clock app, minimal dot interface
|
||||
0.01: Based on the Analog Clock app, minimal dot
|
||||
0.02: Remove hardcoded hour buzz (you can install widchime if you miss it)
|
||||
|
|
|
@ -128,9 +128,6 @@ const onMinute = () => {
|
|||
g.setColor(1, 0.9, 0.9);
|
||||
// Minute
|
||||
minDot((360 * currentDate.getMinutes()) / 60,3);
|
||||
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
drawDate();
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,14 @@
|
|||
Dozenal Time
|
||||
============
|
||||
|
||||
A dozenal Holocene calendar and a dozenal diurnal clock. For information about them, go to https://dozenal.ae-web.ca/pdf/dozenal-calendar.pdf and https://dozenal.ae-web.ca/pdf/about-short.pdf. They've been in use for some years.
|
||||
|
||||
In the dozenal number base, ten and eleven are single digits, and 10 is a dozen. The clock simply divides the day by successive powers of a dozen. The day or parts of it may be divided easily into halves, thirds, quarters, sixths, or twelfths (dozenths). There is no conglomeration of bases two, ten, twelve, and sixty, as in the current system of time measurement.
|
||||
|
||||
The annual calendar has a dozen months of 5 weeks each, each week having 6 days. The 5 or 6 days beyond 360 (dozenal 260) are added where they keep the season beginnings the most accurate.
|
||||
|
||||
The year itself begins on the December solstice. Because that always happens, there is no need of a leap-year rule to keep the seasons from drifting.
|
||||
|
||||
The epoch (year numbering) begins in the last year when the perihelion coincided with the June solstice, near the beginning of the Holocene era. That astronomical basis makes the calendar free from politics, religion, or geography.
|
||||
|
||||
While the year number remains cardinal, BTN5 toggles between cardinal and ordinal for the rest of the calendar segments. BTN4 adds or removes a quickly changing digit to or from the clock.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("lEowggdkUiCKIADCJcCkUjmYACmUikAlKB4ImDAoQSJkYhBFAQECAQI5HBQU//4AC+YUCHowzBCQfzAYYKCEw8vEgYqD+QoGgQbBHAYADCwIoBCYkiEwhPEBAIoBHgY6BExHyHwQhBFAQ6BkYTHDgcyHgcCHRZlDCYQsBTYg6GDAJQDPoI6LAAIPBCYRiHHQhkDCYRiHHQhkCCYKKBCYzzBA4yMBCYTVEGYITEBYITZHY5PHUAJjITIJjHRZINBIYoTDWZAoFWYbbJFALbHgUyX4oPDXIcjMQITBmZkHFYszCYZkJMQoTCKAQ8IHQZOCHgYoKkQ6DHgYoEcIgmBHQg8CFAIPCCYfzBQQSEFAbrFCQImHFAQUCkczmYECAQISGHoYzBAAQFCCRA9BEwYoDHI4pFAAgRLCooRPABg="))
|
|
@ -0,0 +1,225 @@
|
|||
// Positioning values for graphics buffers
|
||||
const g_height = 80; // total graphics height
|
||||
const g_x_off = 16; // position from left
|
||||
const g_y_off = (240 - g_height)/2; // vertical center for graphics region
|
||||
const g_width = 240 - 2 * g_x_off; // total graphics width
|
||||
const g_height_d = 32; // height of date region
|
||||
const g_y_off_d = 0; // y position of date region within graphics region
|
||||
const spacing = 0; // space between date and time in graphics region
|
||||
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
|
||||
const g_height_t = 48; // height of time region
|
||||
|
||||
// Other vars
|
||||
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
|
||||
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
|
||||
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
|
||||
const timeColour = "#f2f2f2";
|
||||
const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#0000ff","#ff00ff","#ff0080"];
|
||||
const calen10 = {"size":32,"pt0":[32-g_x_off,16],"step":[20,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line
|
||||
const calen7 = {"size":32,"pt0":[62-g_x_off,16],"step":[20,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line
|
||||
const time5 = {"size":48,"pt0":[64-g_x_off,24],"step":[30,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line; was 64
|
||||
const time6 = {"size":48,"pt0":[48-g_x_off,24],"step":[30,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line
|
||||
const baseYear = 11584;
|
||||
const baseDate = Date(2020,11,21); // month values run from 0 to 11
|
||||
let accum = new Date(baseDate.getTime());
|
||||
let sequence = [];
|
||||
let timeActiveUntil;
|
||||
let addTimeDigit = false;
|
||||
let dateFormat = false;
|
||||
let lastX = 999999999;
|
||||
let res = {};
|
||||
//var last_time_log = 0;
|
||||
|
||||
// Date and time graphics buffers
|
||||
var dateColour = "#ffffff"; // override later
|
||||
var g_d = Graphics.createArrayBuffer(g_width,g_height_d,1,{'msb':true});
|
||||
var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true});
|
||||
// Set screen mode and function to write graphics buffers
|
||||
Bangle.setLCDMode();
|
||||
g.clear(); // start with blank screen
|
||||
g.flip = function()
|
||||
{
|
||||
g.setColor(dateColour);
|
||||
g.drawImage(
|
||||
{
|
||||
width:g_width,
|
||||
height:g_height_d,
|
||||
buffer:g_d.buffer
|
||||
}, g_x_off, g_y_off + g_y_off_d);
|
||||
g.setColor(timeColour);
|
||||
g.drawImage(
|
||||
{
|
||||
width:g_width,
|
||||
height:g_height_t,
|
||||
buffer:g_t.buffer
|
||||
}, g_x_off, g_y_off + g_y_off_t);
|
||||
};
|
||||
|
||||
setWatch(function(){ modeTime(); }, BTN1, {repeat:true} );
|
||||
setWatch(function(){ Bangle.showLauncher(); }, BTN2, { repeat: false, edge: "falling" });
|
||||
setWatch(function(){ modeWeather(); }, BTN3, {repeat:true});
|
||||
setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true});
|
||||
setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true});
|
||||
|
||||
function buildSequence(targ){
|
||||
for(let i=0;i<targ.length;++i){
|
||||
sequence.push(new Date(accum.getTime()));
|
||||
accum.setDate(accum.getDate()+targ[i]);
|
||||
}
|
||||
}
|
||||
buildSequence(B2);
|
||||
buildSequence(B2);
|
||||
buildSequence(A1);
|
||||
buildSequence(B1);
|
||||
buildSequence(B2);
|
||||
buildSequence(B2);
|
||||
buildSequence(A1);
|
||||
buildSequence(B1);
|
||||
buildSequence(B2);
|
||||
buildSequence(B2);
|
||||
buildSequence(A1);
|
||||
buildSequence(B1);
|
||||
buildSequence(B2);
|
||||
|
||||
function getDate(dt){
|
||||
let index = sequence.findIndex(n => n > dt)-1;
|
||||
let year = baseYear+parseInt(index/12);
|
||||
let month = index % 12;
|
||||
let day = parseInt((dt-sequence[index])/86400000);
|
||||
let colour = dateColours[day % 6];
|
||||
if(day==30){ colour=dateColours[6]; }
|
||||
return({"year":year,"month":month,"day":day,"colour":colour});
|
||||
}
|
||||
function toggleTimeDigits(){
|
||||
addTimeDigit = !addTimeDigit;
|
||||
modeTime();
|
||||
}
|
||||
function toggleDateFormat(){
|
||||
dateFormat = !dateFormat;
|
||||
modeTime();
|
||||
}
|
||||
function formatDate(res,dateFormat){
|
||||
let yyyy = res.year.toString(12);
|
||||
calenDef = calen10;
|
||||
if(!dateFormat){ //ordinal format
|
||||
let mm = ("0"+(res.month+1).toString(12)).substr(-2);
|
||||
let dd = ("0"+(res.day+1).toString(12)).substr(-2);
|
||||
if(res.day==30){
|
||||
calenDef = calen7;
|
||||
let m = ((res.month+1).toString(12)).substr(-2);
|
||||
return(yyyy+"-"+"S"+m); // ordinal format
|
||||
}
|
||||
return(yyyy+"-"+mm+"-"+dd);
|
||||
}
|
||||
let m = res.month.toString(12); // cardinal format
|
||||
let w = parseInt(res.day/6);
|
||||
let d = res.day%6;
|
||||
//return(yyyy+"-"+res.month+"-"+w+"-"+d);
|
||||
return(yyyy+"-"+m+"-"+w+"-"+d);
|
||||
}
|
||||
|
||||
function writeDozTime(text,def,colour){
|
||||
let pts = def.pts;
|
||||
let x=def.pt0[0];
|
||||
let y=def.pt0[1];
|
||||
g_t.clear();
|
||||
g_t.setFont("Vector",def.size);
|
||||
for(let i in text){
|
||||
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+def.dx,y+def.dy); }
|
||||
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+def.dx,y+def.dy); }
|
||||
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
|
||||
x = x+def.step[0];
|
||||
y = y+def.step[1];
|
||||
}
|
||||
}
|
||||
function writeDozDate(text,def,colour){
|
||||
dateColour = colour;
|
||||
let pts = def.pts;
|
||||
let x=def.pt0[0];
|
||||
let y=def.pt0[1];
|
||||
g_d.clear();
|
||||
g_d.setFont("Vector",def.size);
|
||||
for(let i in text){
|
||||
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+def.dx,y+def.dy); }
|
||||
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+def.dx,y+def.dy); }
|
||||
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
|
||||
x = x+def.step[0];
|
||||
y = y+def.step[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Functions for time mode
|
||||
function drawTime()
|
||||
{
|
||||
let dt = new Date();
|
||||
let date = "";
|
||||
let timeDef;
|
||||
let x = 0;
|
||||
dt.setDate(dt.getDate());
|
||||
if(addTimeDigit){
|
||||
x =
|
||||
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
|
||||
let msg = "00000"+Math.floor(x).toString(12);
|
||||
let time = msg.substr(-5,3)+"."+msg.substr(-2);
|
||||
let wait = 347*(1-(x%1));
|
||||
timeDef = time6;
|
||||
} else {
|
||||
x =
|
||||
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
|
||||
let msg = "0000"+Math.floor(x).toString(12);
|
||||
let time = msg.substr(-4,3)+"."+msg.substr(-1);
|
||||
let wait = 4167*(1-(x%1));
|
||||
timeDef = time5;
|
||||
}
|
||||
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
|
||||
date = formatDate(res,dateFormat);
|
||||
if(dt<timeActiveUntil)
|
||||
{
|
||||
// Write to background buffers, then display on screen
|
||||
writeDozDate(date,calenDef,res.colour);
|
||||
writeDozTime(time,timeDef,timeColour);
|
||||
g.flip();
|
||||
// Ready next interval
|
||||
setTimeout(drawTime,wait);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear screen
|
||||
g_d.clear();
|
||||
g_t.clear();
|
||||
g.flip();
|
||||
}
|
||||
lastX = x;
|
||||
}
|
||||
function modeTime()
|
||||
{
|
||||
timeActiveUntil = new Date();
|
||||
timeActiveUntil.setDate(timeActiveUntil.getDate());
|
||||
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+15);
|
||||
//Bangle.setLCDPower(true);
|
||||
clearTimeout();
|
||||
drawTime();
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
// Time-logging function
|
||||
/*function logTime(label)
|
||||
{
|
||||
var d = new Date();
|
||||
var t = d.getTime();
|
||||
var diff_test = t - last_time_log;
|
||||
last_time_log = t;
|
||||
console.log(label + " at time: " + t + ", since last: " + diff_test);
|
||||
}*/
|
||||
|
||||
// Functions for weather mode - TODO
|
||||
function drawWeather() {}
|
||||
function modeWeather() {}
|
||||
|
||||
// Start time on twist
|
||||
Bangle.on('twist', function() {
|
||||
modeTime();
|
||||
});
|
||||
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: First published version of app
|
After Width: | Height: | Size: 6.4 KiB |
|
@ -0,0 +1,206 @@
|
|||
{
|
||||
var minutes;
|
||||
var seconds;
|
||||
var hours;
|
||||
var date;
|
||||
var first = true;
|
||||
var locale = require('locale');
|
||||
var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false;
|
||||
|
||||
//HR variables
|
||||
var id = 0;
|
||||
var grow = true;
|
||||
var size=10;
|
||||
|
||||
//Screen dimensions
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
};
|
||||
|
||||
// Ssettings
|
||||
const settings = {
|
||||
time: {
|
||||
color: '#dddddd',
|
||||
font: 'Vector',
|
||||
size: 100,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
},
|
||||
date: {
|
||||
color: '#dddddd',
|
||||
font: 'Vector',
|
||||
size: 15,
|
||||
middle: screen.height-17, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
circle: {
|
||||
colormin: '#ffffff',
|
||||
colorsec: '#ffffff',
|
||||
width: 10,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
height: screen.height
|
||||
},
|
||||
hr: {
|
||||
color: '#333333',
|
||||
size: 20,
|
||||
x: screen.center,
|
||||
y: screen.middle + 65
|
||||
}
|
||||
};
|
||||
|
||||
const dateStr = function (date) {
|
||||
return locale.date(new Date(), 1);
|
||||
};
|
||||
|
||||
const getFormated = function(val) {
|
||||
if (val<10) {
|
||||
val='0'+val;
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
const drawMin = function (sections, color) {
|
||||
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor('#000000');
|
||||
g.setFont(settings.time.font, settings.time.size/2);
|
||||
g.drawString(getFormated(sections-1), settings.time.center+50, settings.time.middle);
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size/2);
|
||||
g.drawString(getFormated(sections), settings.time.center+50, settings.time.middle);
|
||||
};
|
||||
|
||||
const drawSec = function (sections, color) {
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor('#000000');
|
||||
g.setFont(settings.time.font, settings.time.size/4);
|
||||
g.drawString(getFormated(sections-1), settings.time.center+100, settings.time.middle);
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size/4);
|
||||
g.drawString(getFormated(sections), settings.time.center+100, settings.time.middle);
|
||||
};
|
||||
|
||||
const drawClock = function () {
|
||||
|
||||
currentTime = new Date();
|
||||
|
||||
//Get date as a string
|
||||
date = dateStr(currentTime);
|
||||
|
||||
if(seconds==59) {
|
||||
g.clear();
|
||||
}
|
||||
|
||||
// Update minutes when needed
|
||||
if (minutes != currentTime.getMinutes()) {
|
||||
minutes = currentTime.getMinutes();
|
||||
drawMin(minutes, settings.circle.colormin);
|
||||
}
|
||||
|
||||
//Update seconds when needed
|
||||
if (seconds != currentTime.getSeconds()) {
|
||||
seconds = currentTime.getSeconds();
|
||||
drawSec(seconds, settings.circle.colorsec);
|
||||
}
|
||||
|
||||
//Write the time as configured in the settings
|
||||
hours = currentTime.getHours();
|
||||
if (_12hour && hours > 13) {
|
||||
hours = hours - 12;
|
||||
}
|
||||
|
||||
var meridian;
|
||||
|
||||
if (typeof locale.meridian === "function") {
|
||||
meridian = locale.meridian(new Date());
|
||||
} else {
|
||||
meridian = "";
|
||||
}
|
||||
|
||||
var timestr;
|
||||
|
||||
if (meridian.length > 0 && _12hour) {
|
||||
timestr = hours + " " + meridian;
|
||||
} else {
|
||||
timestr = hours;
|
||||
}
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size);
|
||||
g.drawString(timestr, settings.time.center-40, settings.time.middle);
|
||||
|
||||
//Write the date as configured in the settings
|
||||
g.setColor(settings.date.color);
|
||||
g.setFont(settings.date.font, settings.date.size);
|
||||
g.drawString(date, settings.date.center, settings.date.middle);
|
||||
};
|
||||
|
||||
//setInterval for HR visualisation
|
||||
const newBeats = function (hr) {
|
||||
if (id != 0) {
|
||||
changeInterval(id, 6e3 / hr.bpm);
|
||||
} else {
|
||||
id = setInterval(drawHR, 6e3 / hr.bpm);
|
||||
}
|
||||
};
|
||||
|
||||
//visualize HR with circles pulsating
|
||||
const drawHR = function () {
|
||||
if (grow && size < settings.hr.size) {
|
||||
size++;
|
||||
}
|
||||
|
||||
if (!grow && size > 3) {
|
||||
size--;
|
||||
}
|
||||
|
||||
if (size == settings.hr.size || size == 3) {
|
||||
grow = !grow;
|
||||
}
|
||||
|
||||
if (grow) {
|
||||
color = settings.hr.color;
|
||||
g.setColor(color);
|
||||
g.fillCircle(settings.hr.x, settings.hr.y, size);
|
||||
} else {
|
||||
color = "#000000";
|
||||
g.setColor(color);
|
||||
g.drawCircle(settings.hr.x, settings.hr.y, size);
|
||||
}
|
||||
};
|
||||
|
||||
// clean app screen
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
//manage when things should be enabled and not
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (on) {
|
||||
Bangle.setHRMPower(1);
|
||||
} else {
|
||||
Bangle.setHRMPower(0);
|
||||
}
|
||||
});
|
||||
|
||||
// refesh every second
|
||||
setInterval(drawClock, 1E3);
|
||||
|
||||
//start HR monitor and update frequency of update
|
||||
Bangle.setHRMPower(1);
|
||||
Bangle.on('HRM', function (d) {
|
||||
newBeats(d);
|
||||
});
|
||||
|
||||
// draw now
|
||||
drawClock();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial Release
|
|
@ -0,0 +1,28 @@
|
|||
# Font Clock
|
||||
|
||||
The Font Clock allows you to choose the font and clock style.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
### Choose the Clock Face from the selection
|
||||
|
||||
Before uploading the upload page will ask which clock face you like to choose. Please choose using the provided pull down. As you look through the different selections a sample image will be shown to the right hand side.
|
||||
|
||||
Once you have chosen your watch face press the upload button and the selection will be uploaded to the watch
|
||||
|
||||
### Button 3
|
||||
Button 3 (bottom right button) is used to change the background colour.
|
||||
|
||||
## Further Details
|
||||
|
||||
For further details of design and working please visit [The Project Page](https://www.notion.so/adrianwkirk/Sweep-hand-clock-6aa5b6b3d1074d4e87fc947975b1e4b7)
|
||||
|
||||
## Requests
|
||||
|
||||
Reach out to adrian@adriankirk.com if you have feature requests or notice bugs.
|
||||
|
||||
## Creator
|
||||
|
||||
Made by [Adrian Kirk](mailto:adrian@adriankirk.com)
|
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,210 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Please select watch display</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<select id="display_selection" name="display_selection" onchange="change_image()" >
|
||||
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<img id="selected_image" src="display-01.png">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
function change_image() {
|
||||
var idx = document.getElementById("display_selection").selectedIndex;
|
||||
set_image(idx);
|
||||
}
|
||||
|
||||
function set_image(idx){
|
||||
var image = document.getElementById('selected_image');
|
||||
image.src = "display-0" + (idx + 1) + ".png";
|
||||
}
|
||||
|
||||
var displays_choices=[
|
||||
{
|
||||
name: "Abril FatFace 4",
|
||||
numerals: [12,3,6,9],
|
||||
fonts: ["abril_ff50"],
|
||||
radius: 80,
|
||||
color_schemes : [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
second_hand: [1.0,1.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
background : [1.0,0.0,0.0],
|
||||
second_hand: [1.0,1.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5],
|
||||
},
|
||||
{
|
||||
name: "purple",
|
||||
background : [1.0,0.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "blue",
|
||||
background : [0.4,0.7,1.0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Montoon 4",
|
||||
numerals: [12,3,6,9],
|
||||
fonts: ["mntn50"],
|
||||
radius: 80,
|
||||
color_schemes : [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
second_hand: [1.0,1.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Vector 12",
|
||||
numerals: [12,1,2,3,4,5,6,7,8,9,10,11],
|
||||
fonts: ["vector25"],
|
||||
radius: 90,
|
||||
color_schemes : [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
second_hand: [1.0,0.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5],
|
||||
second_hand: [0.0,0.0,0.0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Copaset 4",
|
||||
numerals: [12,3,6,9],
|
||||
fonts: ["cpstc58"],
|
||||
radius: 75,
|
||||
color_schemes : [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
second_hand: [1.0,0.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
background : [1.0,0.0,0.0],
|
||||
second_hand: [1.0,1.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5],
|
||||
second_hand: [0.0,0.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "purple",
|
||||
background : [1.0,0.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "blue",
|
||||
background : [0.4,0.7,1.0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Vector 4",
|
||||
numerals: [12,3,6,9],
|
||||
fonts: ["vector50"],
|
||||
radius: 75,
|
||||
color_schemes : [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
second_hand: [1.0,0.0,0.0],
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
background : [1.0,0.0,0.0],
|
||||
second_hand: [1.0,1.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5],
|
||||
second_hand: [0.0,0.0,0.0]
|
||||
},
|
||||
{
|
||||
name: "purple",
|
||||
background : [1.0,0.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "blue",
|
||||
background : [0.4,0.7,1.0]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
var selected_choice = "Abril FatFace 4"
|
||||
try{
|
||||
var stored = localStorage.getItem('fontclock.font.json')
|
||||
if(stored) {
|
||||
var selected_config = JSON.parse(stored);
|
||||
selected_choice = selected_config.name;
|
||||
}
|
||||
} catch(e){
|
||||
console.log("failed to load languages:" + e);
|
||||
}
|
||||
console.log("selected choice:" + selected_choice);
|
||||
var selection=document.getElementById("display_selection");
|
||||
for (var i=0; i<displays_choices.length; i++) {
|
||||
|
||||
var option = document.createElement('option');
|
||||
var curr_choice = displays_choices[i];
|
||||
option.name = curr_choice.name;
|
||||
option.text = curr_choice.name;
|
||||
selection.add(option);
|
||||
}
|
||||
selection.value = selected_choice;
|
||||
set_image(selection.selectedIndex)
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var new_config;
|
||||
console.log("selection:" + selection.value);
|
||||
for(var i=0; i<displays_choices.length; i++) {
|
||||
if (displays_choices[i].name == selection.value) {
|
||||
new_config = displays_choices[i];
|
||||
console.log("new_config:" + JSON.stringify(new_config));
|
||||
}
|
||||
}
|
||||
localStorage.setItem('fontclock.font.json',JSON.stringify(new_config));
|
||||
// send finished app (in addition to contents of app.json)
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"fontclock.font.json", content:JSON.stringify(new_config)},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 6.1 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("lEowkA/4AvmUiAA0/CRHzkczAA0vExM/n/zn8zAIPzCZUi/8j+cvmUzAgI7JBQITHkY6JCwRNEIYITIDoQSEExXyDoQSDn4mKHQ4mKLoImRHQQmPMIYTDExY6HExY6HExQ6HYgISJHQ4TBAgbXOAAb3Ba5giBn8/H4zXHMYfzEww6I+cyPJAtEToizBNoQTFLo0yBAKMI+UikUjIwQSBJg61ICALGMPQgQBJhB6IbJjcGJhw6DCQJMMUIhMOHQavBCRo6CJh46DTJo6EJh5eCTJwADdwISQJiIAo"))
|
|
@ -0,0 +1,51 @@
|
|||
var NumeralFont = require("fontclock.font.js");
|
||||
|
||||
const DIM_30x38 = [30,38];
|
||||
const DIM_49x38 = [49,38];
|
||||
|
||||
class DigitNumeralFont extends NumeralFont{
|
||||
constructor(){
|
||||
super();
|
||||
// dimension map provides the dimensions of the character for
|
||||
// each number for plotting and collision detection
|
||||
this.widths = atob("DRIhFRwdHhsfGh8fDQ==");
|
||||
this.font = atob("AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAD/AAAAAAB/4AAAAAAf+AAAAAAH/gAAAAAB/4AAAAAAf+AAAAAAD/AAAAAAA/gAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAPwAAAAAAf8AAAAAA/+AAAAAB/8AAAAAD/wAAAAAH/gAAAAAP/AAAAAAf+AAAAAA/8AAAAAB/4AAAAAD/wAAAAAH/gAAAAAP/AAAAAAH+AAAAAAB8AAAAAAAIAAAAAAAAAAAAAAAAAAH/8AAAAAP//8AAAAP///wAAAH///+AAAD////4AAB/////AAA/////wAAf////+AAH/////wAD/////8AA//////AAP/gAA/wADwAAAAeAA4AAAADgAMAAAAA4ADAAAAAOAAwAAAADgAMAAAAA4ADgAAAAeAA/gAAA/AAP/////wAD/////8AAf/////AAH/////gAA/////4AAH////8AAB////+AAAP////AAAA////gAAAD///gAAAAH//AAAAAAAAAAAAGAAAAAwABgAAAAMAAYAAAADAAGAAAAAwADgAAAAMAA//////AAP/////wAD/////8AA//////AAP/////wAD/////8AA//////AAP/////wAD/////8AA//////AAP/////wAAAAAAAMAAAAAAADAAAAAAAAwAAAAAAAMAAAAAAAAAAAAAAAAAAAHwAAD8AAH/AAB/AAD/wAA/wAA/+AAf8AAf/gAP/AAH/4AH/wAD/+AD/8AA//gB//AAOPwA//wADD4Aff8AAwAAPn/AAMAAPx/wADAAH8f8AA4AH+H/AAPgP/B/wAD///gf8AA///4H/AAP//8B/wAD//+Af8AAf//AH/AAH//wB/wAA//4A/8AAH/4Af/AAA/8A//wAAD8Af/8AAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAPwAAA/gAP+AAAf8AD/wAAP/gB/+AAH/4Af/wAB/+AH/8AAf/gB//AAP/4wP/wAD/8OD+OAAw/DgPDgAMDAwAA4ADAAcAAOAAwAHAADgAOAH4AA4AD///AAeAA///+A/AAP/////wAD/////8AA//9///AAP//f//gAB//n//4AAf/w//+AAD/8P//AAAf+B//gAAB+AP/wAAAAAB/4AAAAAADwAAAAAAAAAAAAAAAeAAAAAAAfgAAAAAAf4AAAAAAPmAAAAAAPhgAAAAAPwYAAAAAPwGAAAAAHwBgAAAAHwAYDAAAH4AGAwAAH4ABgMAAH4AAYDAAD4AAGAwAD/////8AA//////AAP/////wAD/////8AA//////AAP/////wAD/////8AA//////AAP/////wAD/////8AAAAAAYDAAAAAAGAwAAAAABgMAAAAAAYBAAAAAB/4AAAAAAf+AAAAAAAAAAAAAAAD4AAAAAAB/gAAAAAA/8AAP//wf/gAD//8H/4AA//3B//AAP8Bgf/wAD/A4D/8AAfwOA/jgAH8DAH44AB/gwAAOAAf4MAADgAH+DAAA4AB/g4AAeAAf8PwA/AAH/D///wAB/w///8AAf8P///AAD/j///wAA/4f//4AAP+H//+AAH/A///AAD/wP//gAA/gB//wAAAAAH/4AAAAAAfwAAAAAAAAAAAAAAAAAAAAAD//wAAAAH///AAAAH///8AAAD////gAAD////8AAB/////gAAf////4AAP/////AAH/////wAB/////8AA//////gAP8B4AD4AD4A4AAOAA4AMAADgAOAHAAA4ADABwAAOAAwAcAAHgAMAH4AP4ADD5///8AA5/f///AAP/////wAD/////8AAf/v//+AAH/7///AAA/+f//wAAH/H//4AAA/gf/4AAABgD/8AAAAAAH4AAAAAAAAAAAAAAAAAAAAf/wAAAAAP/8AAAAAD/8AAAAAA/8AAAAAAP+AAA/AAD/gAA/4AA/4AA//AAP+AAf/wAD/gAf/+AA/4AP//gAP+AH//4AD/gD//+AA/4B///gAP+B/+BwAD/g/8AAAA/4f8AAAAP+P8AAAAD/n8AAAAA/78AAAAAP/+AAAAAD/+AAAAAA/+AAAAAAP+AAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAB/gH/4AAA/8D//AAAf/h//wAAP/8f/+AAH//v//gAB//7//8AAf/////AAP/////wAD/////+AA//////gAP//+AB4ADgAeAAOAAwADgADgAMAA4AA4ADAAOAAOAA4AHwADgAP//+AD4AD/////+AA//////AAP/////wAD//7//8AAf/+///AAH//P//gAA//x//4AAH/4f/8AAA/8D/+AAAD8Af/AAAAAAB/AAAAAAAAAAAAA/gAAAAAA//APwAAA//8H+AAAf//j/wAAP//4/+AAD///f/gAB/////8AAf//+//AAP///v/wAD///7+eAA////PjgAPgAPwA4ADgAA4AOAAwAAOADgAMAADgA4ADAAA4AeAAwAAcAfgAPAAeA/wAD/////8AA//////AAP/////gAB/////wAAf////8AAD////+AAAf////AAAH////gAAAf///gAAAD///gAAAAH//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AH4AAAB/gD/AAAAf8B/4AAAP/Af+AAAD/wH/gAAA/8B/4AAAH/Af+AAAB/wD/AAAAP4A/gAAAAwABgAAAAAAAA==");
|
||||
var scale = 1; // size multiplier for this font
|
||||
this.size = 50+(scale<<8)+(1<<16);
|
||||
this.y_offset = -12;
|
||||
|
||||
|
||||
}
|
||||
getDimensions(hour){
|
||||
//return this.dimension_map[hour];
|
||||
switch (hour){
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
return DIM_49x38;
|
||||
default:
|
||||
return DIM_30x38;
|
||||
|
||||
}
|
||||
}
|
||||
hour_txt(hour){ return hour.toString(); }
|
||||
draw(hour_txt,x,y){
|
||||
/* going to leave this in here for future testing.
|
||||
uncomment this so that it draws a box behind the string
|
||||
so we can guess the digit dimensions*/
|
||||
/*var dim = [30,38];
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillPoly([x,y,
|
||||
x+dim[0],y,
|
||||
x+dim[0],y+dim[1],
|
||||
x,y+dim[1]
|
||||
]);
|
||||
g.setColor(1.0,1.0,1.0);*/
|
||||
g.setFontAlign(-1.0,-1.0,0);
|
||||
g.setFontCustom(this.font, 46, this.widths, this.size);
|
||||
g.drawString(hour_txt,x,y+this.y_offset );
|
||||
}
|
||||
getName(){return "Digit";}
|
||||
}
|
||||
|
||||
module.exports = [DigitNumeralFont];
|
|
@ -0,0 +1,59 @@
|
|||
var NumeralFont = require("fontclock.font.js");
|
||||
|
||||
const DIM_20x58 = [20,58];
|
||||
const DIM_30x58 = [30,58];
|
||||
const DIM_40x58 = [40,58];
|
||||
const DIM_50x58 = [50,58];
|
||||
class DigitNumeralFont extends NumeralFont{
|
||||
constructor(){
|
||||
super();
|
||||
// dimension map provides the dimesions of the character for
|
||||
// each number for plotting and collision detection
|
||||
this.font = atob("AAAA/+AAAAAAB///wAAAAB////8AAAA/////+AAAP/////8AAD//////8AAf/8AAf/8AD/8AAAH/4Af+AAAAD/wD/gAAAAD/gf4AAAAAH+D/AAAAAAP8P4AAAAAAf5/AAAAAAA/n4AAAAAAB+/gAAAAAAH/+AAAAAAAf/wAAAAAAA//AAAAAAAD/8AAAAAAAP/4AAAAAAB//gAAAAAAH9+AAAAAAAfn8AAAAAAD+fwAAAAAAP4/gAAAAAB/D/AAAAAAP8H/AAAAAB/gP+AAAAAf8Af/AAAAH/gA//gAAD/8AB//8AH//gAB//////8AAB//////AAAB/////wAAAA////4AAAAAP//4AAAAAAAAAAAAAAGAAAAAAAAA8AAAAAAAAH8AAAAAAAA/wAAAAAAAH+AAAAAAAA/wAAAAAAAH+AAAAAAAA/wAAAAAAAH////////w/////////H////////8/////////3//////////////////8AAAAAAAAAAAAAAAAAADAAAAAAAAAcQAAAAAAAHzwAAAAAAA/PwAAAAAAH9/AAAAAAB/34AAAAAAP/fgAAAAAD//+AAAAAAf//wAAAAAH///AAAAAA///8AAAAAH///4AAAAB/4//gAAAAP/D/+AAAAD/wP34AAAAf+A/fwAAAD/wD8/gAAA/8APz/AAAH/gA/H+AAB/4AD8f8AAP/AAPw/8AD/wAA/B/+A/+AAD8D////wAAPwH///8AAA/AH///gAAD8AH//4AAAPwAH/+AAAAAAAAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAgAAAAD/8AHAAAAAP/wB8AAAAA//APwAAAAD/8D/AAAAAP/wf8AAAAB//H/wAAAAH/8//gAAAAfv//+AAAAD+///8AAAAP7//fwAAAB/P/w/gAAAP8/+D/AAAB/j/wH+AAAP8P8AP8AAB/w/gAf8AAf+D4AA/8AH/wPAAB////+AwAAD////gCAAAH///8AAAAAH///AAAAAAD//wAAAAAAA/wAAAAAAAAAAAAAAAAAAAQAAAAAAAAHAAAAAAAAD8AAAAAAAA/wAAAAAAAP/AAAAAAAD/8AAAAAAB//wAAAAAAf//AAAAAAH//8AAAAAB//PwAAAAAf/w/AAAAAP/8D8AAAAD//APwAAAA//gA/AAAAP/4AD8AAAH/+AAPwAAB//gAA/AAAf/4AAD8AAH/8AAAPwAD//AAAA/AAP/wAAAD8AA/8AAAAPwAD/AAAAA/gAPgAAA/////4AAAD////+AAAAP////wAAAA/////AAAAD////8AAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAAAAAAAA8AAAAAAAB/wAAAAAAD//AAAAAP///8AAAAA////wAAAAD////AAAAAP///8AAAAA//4PwAAAAH/8A/gAAAAf/wB+AAAAB+/AH4AAAAP78AfwAAAA/vwA/AAAAH8/AD+AAAA/z8AP8AAAD+PwAf4AAAf4/AA/wAAH/D8AD/gAA/4PwAH/gAf/A/AAP/8f/4AAAAf////AAAAAf///wAAAAA///8AAAAAAf//AAAAAAAH/gAAAAAAAAAAAAAAAAH/4AAAAAAP//8AAAAAD///+AAAAB////8AAAAf////8AAAH//8f/4AAA//8AD/wAAP//AAD/gAB//wAAH/AAP/+AAAH+AB//wAAAP4AP/+AAAAfwB//wAAAB/AP9/AAAAD+B/n4AAAAH4H8/gAAAAfg/j8AAAAB/H8PwAAAAH8fw/AAAAAPz+D8AAAAA/P4PwAAAAD9/A/AAAAAf38D8AAAAB/fgP4AAAAH5+A/gAAAAfv4B/AAAAD+/gH8AAAAPz+AP4AAAB/PwA/wAAAP8/AB/gAAB/gAAD/AAAP8AAAP+AAD/gAAAf+AA/8AAAA//gf/gAAAA////8AAAAB////gAAAAB///4AAAAAB//+AAAAAAA//AAAAAAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAfwAAAAAAAP/AAAAAAAH/8AAAAAAD//wAAAAAD///AAAAAB///8AAAAA///vwAAAA///w/AAAAP//wD8AAAP//4APwAAH//8AA/AAD//+AAD8AD//+AAAPwB///AAAA/A///gAAAD8f//wAAAAP///4AAAAA///4AAAAAD//8AAAAAAP/+AAAAAAA/+AAAAAAAD/AAAAAAAAPgAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAH//wAAAAAB///wAAAAAf///wAAAAD////wAAAAf////gAAAH/wAf/AAAA/8AAP+AAAD/AAAf8AAAf4AAAf4AAD/AAAA/gB/P4AAAB/A///AAAAH8H//8AAAAP4///gAAAAfn//+AAAAB+f//wAAAAH/+B/AAAAAf/4H8AAAAA//APwAAAAD/8A/AAAAAP/4H8AAAAB/fw/wAAAAH9///gAAAAfj//+AAAAB+P//4AAAAP4P//wAAAA/gf//gAAAH8APD/AAAA/wAAH8AAAH+AAAf8AAA/wAAA/4AAH/AAAB/4AB/4AAAD/8A//AAAAH////wAAAAP///+AAAAAP///gAAAAAP//4AAAAAAH/+AAAAAAAAAAAAAAA/wAAAAAAA//8AAAAAAP//+AAAAAD///8AAAAA////8AAAAH////4AAAA/+AD/wAAAH/AAD/gAAA/4AAD/AAAH+AAAH+AAAfwAAAP8APz+AAAAfwA/P4AAAA/gH9/AAAAD+Af34AAAAH4B+fgAAAAfwH7+AAAAA/A/v4AAAAD8D+/AAAAAPwPz8AAAAA/B/PwAAAAD8P8/gAAAAPw/j+AAAAA/H8H4AAAAH8/wfgAAAAf3+B/AAAAB+fwH8AAAAP//AP4AAAB//4A/wAAAH//AB/gAAA//4AD/AAAP//AAH+AAB//wAAf+AAf/+AAAf/AP//gAAA/////8AAAB/////AAAAB////wAAAAB///4AAAAAB//4AAAAAAAAAAAAAAA=");
|
||||
this.widths = atob("Jg8dGiAaKBsoKA==");
|
||||
}
|
||||
getDimensions(hour){
|
||||
switch(hour){
|
||||
case 1:
|
||||
return DIM_20x58;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 7:
|
||||
return DIM_30x58;
|
||||
case 6:
|
||||
case 8:
|
||||
case 9:
|
||||
case 11:
|
||||
case 12:
|
||||
return DIM_40x58;
|
||||
case 10:
|
||||
return DIM_50x58;
|
||||
default:
|
||||
return DIM_30x58;
|
||||
}
|
||||
}
|
||||
hour_txt(hour){ return hour.toString(); }
|
||||
draw(hour_txt,x,y){
|
||||
/* going to leave this in here for future testing.
|
||||
uncomment this so that it draws a box behind the string
|
||||
so we can guess the digit dimensions
|
||||
dim = [50,58];
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillPoly([x,y,
|
||||
x+dim[0],y,
|
||||
x+dim[0],y+dim[1],
|
||||
x,y+dim[1]
|
||||
]);
|
||||
g.setColor(1.0,1.0,1.0);*/
|
||||
//g.setFontCopasetic40x58Numeric();
|
||||
//g.setFontAlign(-1,-1,0);
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.setFontCustom(this.font, 48, this.widths, 58);
|
||||
g.drawString(hour_txt,x,y);
|
||||
}
|
||||
getName(){return "Digit";}
|
||||
}
|
||||
|
||||
module.exports = [DigitNumeralFont];
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* We want to be able to change the font so we set up
|
||||
* pure virtual for all fonts implementtions to use
|
||||
*/
|
||||
class NumeralFont {
|
||||
/**
|
||||
* The screen dimensions of what we are going to
|
||||
* display for the given hour.
|
||||
*/
|
||||
getDimensions(hour){return [0,0];}
|
||||
/**
|
||||
* The characters that are going to be returned for
|
||||
* the hour.
|
||||
*/
|
||||
hour_txt(hour){ return ""; }
|
||||
/**
|
||||
* method to draw text at the required coordinates
|
||||
*/
|
||||
draw(hour_txt,x,y){ return "";}
|
||||
/**
|
||||
* Called from the settings loader to identify the font
|
||||
*/
|
||||
getName(){return "";}
|
||||
}
|
||||
|
||||
module.exports = NumeralFont;
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "Vector 4",
|
||||
"numerals": [12,3,6,9],
|
||||
"fonts": ["vector50"],
|
||||
"radius": 75,
|
||||
"color_schemes" : [
|
||||
{
|
||||
"name": "black",
|
||||
"background" : [0.0,0.0,0.0],
|
||||
"second_hand": [1.0,0.0,0.0],
|
||||
},
|
||||
{
|
||||
"name": "red",
|
||||
"background" : [1.0,0.0,0.0],
|
||||
"second_hand": [1.0,1.0,0.0]
|
||||
},
|
||||
{
|
||||
"name": "grey",
|
||||
"background" : [0.5,0.5,0.5],
|
||||
"second_hand": [0.0,0.0,0.0]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
var NumeralFont = require("fontclock.font.js");
|
||||
|
||||
const DIM_25x25 = [25,25];
|
||||
const DIM_10x25 = [10,25];
|
||||
const DIM_20x25 = [20,25];
|
||||
const DIM_31x25 = [31,25];
|
||||
const DIM_15x25 = [15,25];
|
||||
|
||||
class DigitNumeralFont extends NumeralFont{
|
||||
constructor(){
|
||||
super();
|
||||
// dimension map provides the dimensions of the character for
|
||||
// each number for plotting and collision detection
|
||||
this.widths = atob("BgsVCw8PEBEUEBQUBw==");
|
||||
this.font = atob("AAAAAAAAAAAAp9bgAAAAAAAAAAAADr+vAAAAAAAAAAAAAOv68AAAAAAAAAAAAA6/rwAAAAAAAAAAAADr+fAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAXwAAAAAAAAAAAAXz//wAAAAAAAAAXz//7q+AAAAAAAXz//8q+//wAAAAXz//8q+//yr3wAXz//8q+//yr3//ZAL/8q+//yr3//ZMAAAW+//2q3//ZQAAAAAC/2q3//pQAAAAAAAAF3//pQAAAAAAAAAAAnpQAAAAAAAAAAAAAAAAABL3//tgQAAAAAAAAj//su9//0gAAAAAC7/vv///73/gAAAAB/8/9u7u7/+34AAAA78/r/////c/+9QAAf9/P/LvLu/+///AADu/++//+7/7Pv79QAv37+/sQAAb/7978AF/f3vwAAAAD+/v+4Aj9779QAAAADs+/vwCP3vv1AAAAAOz7+/AF/P3fsAAAAC+/v94AL9+/v5AAAD/9/f/QAP3+/8/9ze/7+/v2AAj9+//Lztu+/P//AAAP/f3P////6//fcAAAL/z/y7u7vv7PoAAAAD/+z////9z/oAAAAAAK//3LvO/+QAAAAAAAAH3///6zAAAAAAAAAAAAAAAAAAAAAAC96fQAAAAAAAAAAAAL769AAAAAAAAAAAAAvvr5ZmZmZmZmZmAAC++v//////////8AAL76/bu7u7u7u7uwAAvvr/7u7u7u7u7uAAC++v/u7u7u7u7u4AAL76/KqqqqqqqqqgAAvvr///////////AAAjQlVVVVVVVVVVUAAAAAAAAAAAAAAAAAAAhTAAAAAAAAAyUlAAD7+udQAAAAHfv68AAPv777AAAAX/+/rwAC+/y/kAAAn+77+vAAb8/q9gAB79//v68ACf7frzAF/9/t+/rwAH/d+/QK/u/P/7+vAAX8/t/u/f/P7Pv68AAvv7+uzv3vz/+/rwAA/P3///z/v/Pr+vAACPv9u67939EOv68AAA/6///7/3AA6/rwAAAv/Ku+/iAADr+fAAAACu//5gAAAAAAAAAAAAAAAAAAAAAAAAAAhTAAAAAAAAAAIrIAD7+udgAAAAGo379gAPr7/rAAAAAfv935AB+vvvcjMkFQ/Pv+sAT6/d9K/r+vDs+/3QB/zvvzr+v68Nz7+/AJ/d+/Ov6/rx3Pv78Ab779+I/N/PX8+/zwAvv8/P/5/v7/z7/+AA+/z+m/r7/Kv9/PkACvv7///8+//7398gAB/9/bvvzvy8/89wAABv++/93+nf/b+gAAAALv/e/9//3v+wAAAAAASd21AVrcogAAAAAAAAAAAb753JAAAAAAAAAALP/frusAAAAAAAAE3/zO+u6wAAAAAABe/7z/367rAAAAAAf/+9/7vvrusAAAAG/+rv+s/9+u6wAAAAra//rf+5367rAAAABv/q7/q//vrusAAAAK2v/5z/w1+u6wAAAAb/6d/7IAX67rAAAACsr/+AL//vru//4AAG/+YAAaqr+u7aqgAAnUAAAD///67v//AAAAAAAABVWPruxVUAAAAAAAAAACtphgAAAAAAAAAAAAAAAAAAAAA0U3d3d3d1AADMAAAL76//////0ACr9AAAvvr9zMzMyABd/vAAC++v7u7u7qAPz79QAL76//////wPz975AAvvr8rN7Oyw+vv+wAC++vQN37/ODs+/vgAL769A6/v9wN37+/AAvvr0Dq+/3Q/Pv78AC++vQN38/vv8+/3QAL769Ar8397+7/36AAvvr0Bfv8/s/7/PMAC++vQA77+9/a+fwAAL769ABP3f///f8gAAVnSRAAb/mrzP8wAAAAAAAAAC3///wQAAAAAAAAAAAAJiAAAAAAAAAAA2ZmZiAAAAAAAAAK7//////+YAAAAAA//Lu7u7up77AAAABP+//+7u7v/5/QAAAP7fyN///+y/+/cAAH+/n/2qqqvv7vzwAA/f3+r/////v8+/cAD7+/v82rye38/e6wBPv9zrn9388fv7/NAI/O+va+/Pzw3Pv68Aj93689z7/dDc+vrwBPv+v06/z90Pz7++AA+/3vnO/Pz+/PvuwAD7+/o4/O/56/38+QAN/v5QP8/f//7PzxAAP89QAL+f3czfj6AAAK9wAAH/v///z/EAAADQAAAC79q879EAAAAAAAAAAJ3//YAAAAAAAAAAAAAAAAAAAAAL3p9AAAAAAAAAAAAAvvr0AAAAAAAAAAAAC++vQAAAAAAAAAAAAL769AAAAAAAAFrgAAvvr0AAAAAFrv/9AAC++vQAAFvv/9u98AAL769Wvv/8u9//6gAArN7//8u+//67z/AACv/8u+//27z//roAAFu+//273//rvP/wAAv/273//rvP//xxAABb3//rvP//xxAAAAAL/rvP//thAAAAAAAAXP/+thAAAAAAAAAACutgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGt/qYCvv2kAAAAAAb/69/+/+vP/AAAAAT+z//q/8//6/8QAAP8/7u9/P+7z/v7AADv36//+/v///778gAfv7/Lr/7++3/vz6AG+/7+/9+/v//Pz+0Aj8789d77+/Ds+/zwCf3Pryvuv68N36+vAJ/c+vK+6/rwzfr68An9z68r7r+vDN+vrwCPzvz1v+z78ez6+/AE+/7//fzv3+/Pv98AD7+/ne+/v57e/e/QAO7v3//9/u/v+vr2AAT9/7u9+v68uvz/AAAM/O////v///z/EAAACv+6vP/+u6z/IAAAAATP//1H3//7IAAAAAAAABAAAAEAAAAAAAAABJkwAAAAAAAAAAAAr///+gAAAAEQAAAB783dvP0QAADNAAAA37/93/n8AAC79QAAT5/c/9358wBt/vAADu/v+8/+79Afz79QAPn7+//Pv58Pv975AD+f7/zP3fjw+vv+wAf7789T+/6vTs+/vgCf3fvyP8/789z7+/AG+++/SP7Pvw+fr74AL5/e+837+/X3+v3AAPv7+//d3d797/35AA3+7/vMzMzK75+/MAA/r97//////r/fwAAAz5/7uqqqqt/d8gAAAe/N//////6v9gAAAACv/bqqqr3/4gAAAAAAOM/////aUAAAAAAAAAAAAAAAAAAAAAAAAAAQEQABARAAAAAAAABvvuoF+u6wAAAAAAAG++6gX67rAAAAAAAAb77qBfrusAAAAAAABvvuoF+u6wAAAAAAAE16pwPXunAAAAAAAAAAAAAAAAAAAA==");
|
||||
var scale = 1; // size multiplier for this font
|
||||
this.size = 25+(scale<<8)+(4<<16);
|
||||
this.y_offset = 0;
|
||||
|
||||
}
|
||||
getDimensions(hour){
|
||||
//return this.dimension_map[hour];
|
||||
switch(hour){
|
||||
case 0:
|
||||
case 12:
|
||||
return DIM_25x25;
|
||||
case 1:
|
||||
return DIM_10x25;
|
||||
case 6:
|
||||
case 8:
|
||||
case 9:
|
||||
case 11:
|
||||
return DIM_20x25;
|
||||
case 10:
|
||||
return DIM_31x25;
|
||||
default:
|
||||
return DIM_15x25;
|
||||
}
|
||||
}
|
||||
hour_txt(hour){ return hour.toString(); }
|
||||
draw(hour_txt,x,y){
|
||||
/* going to leave this in here for future testing.
|
||||
uncomment this so that it draws a box behind the string
|
||||
so we can guess the digit dimensions*/
|
||||
/*var dim = [30,25];
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillPoly([x,y,
|
||||
x+dim[0],y,
|
||||
x+dim[0],y+dim[1],
|
||||
x,y+dim[1]
|
||||
]);
|
||||
g.setColor(1.0,1.0,1.0);*/
|
||||
g.setFontAlign(-1.0,-1.0,0);
|
||||
g.setFontCustom(this.font, 46, this.widths, this.size);
|
||||
g.drawString(hour_txt,x,y+this.y_offset );
|
||||
}
|
||||
getName(){return "Digit";}
|
||||
}
|
||||
|
||||
module.exports = [DigitNumeralFont];
|
|
@ -0,0 +1,39 @@
|
|||
var NumeralFont = require("fontclock.font.js");
|
||||
|
||||
const DIM_14x22 = [14,22];
|
||||
const DIM_27x22 = [27,22];
|
||||
class DigitNumeralFont extends NumeralFont{
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
getDimensions(hour){
|
||||
if (hour < 10){
|
||||
return DIM_14x22;
|
||||
} else {
|
||||
return DIM_27x22;
|
||||
}
|
||||
}
|
||||
hour_txt(hour){ return hour.toString(); }
|
||||
draw(hour_txt,x,y){
|
||||
if(hour_txt == null)
|
||||
return;
|
||||
|
||||
/* going to leave this in here for future testing.
|
||||
uncomment this so that it draws a box behind the string
|
||||
so we can guess the digit dimensions
|
||||
var dim = [14,22];
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillPoly([x,y,
|
||||
x+dim[0],y,
|
||||
x+dim[0],y+dim[1],
|
||||
x,y+dim[1]
|
||||
]);
|
||||
g.setColor(1.0,1.0,1.0);*/
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.setFont("Vector",25);
|
||||
g.drawString(hour_txt,x,y);
|
||||
}
|
||||
getName(){return "Digit";}
|
||||
}
|
||||
|
||||
module.exports = [DigitNumeralFont];
|
|
@ -0,0 +1,91 @@
|
|||
var NumeralFont = require("fontclock.font.js");
|
||||
|
||||
const DIM_28x44 = [28,44];
|
||||
const DIM_54x44 = [54,44];
|
||||
|
||||
class DigitNumeralFont extends NumeralFont{
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
getDimensions(hour){
|
||||
if (hour < 10){
|
||||
return DIM_28x44;
|
||||
} else {
|
||||
return DIM_54x44;
|
||||
}
|
||||
}
|
||||
hour_txt(hour){ return hour.toString(); }
|
||||
draw(hour_txt,x,y){
|
||||
if(hour_txt == null)
|
||||
return;
|
||||
|
||||
/* going to leave this in here for future testing.
|
||||
uncomment this so that it draws a box behind the string
|
||||
so we can guess the digit dimensions
|
||||
var dim = [14,22];
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillPoly([x,y,
|
||||
x+dim[0],y,
|
||||
x+dim[0],y+dim[1],
|
||||
x,y+dim[1]
|
||||
]);
|
||||
g.setColor(1.0,1.0,1.0);*/
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.setFont("Vector",50);
|
||||
g.drawString(hour_txt,x,y);
|
||||
}
|
||||
getName(){return "Digit";}
|
||||
}
|
||||
|
||||
const DIM_50x40 = [50,40];
|
||||
const DIM_70x40 = [70,40];
|
||||
class RomanNumeralFont extends NumeralFont{
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
getText(hour){
|
||||
switch (hour){
|
||||
case 1 : return 'I';
|
||||
case 2 : return 'II';
|
||||
case 3 : return 'III';
|
||||
case 4 : return 'IV';
|
||||
case 5 : return 'V';
|
||||
case 6 : return 'VI';
|
||||
case 7 : return 'VII';
|
||||
case 8 : return 'VIII';
|
||||
case 9 : return 'IX';
|
||||
case 10: return 'X';
|
||||
case 11: return 'XI';
|
||||
case 12: return 'XII';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
getDimensions(hour){
|
||||
switch (hour){
|
||||
case 3:
|
||||
case 6:
|
||||
case 9:
|
||||
return DIM_50x40;
|
||||
case 12:
|
||||
return DIM_70x40;
|
||||
default:
|
||||
return DIM_70x40;
|
||||
}
|
||||
}
|
||||
hour_txt(hour){ return this.getText(hour); }
|
||||
draw(hour_txt,x,y){
|
||||
/*var dim = DIM_70x40;
|
||||
g.setColor(0.5,0,0);
|
||||
g.fillPoly([x,y,
|
||||
x+dim[0],y,
|
||||
x+dim[0],y+dim[1],
|
||||
x,y+dim[1]
|
||||
]);*/
|
||||
g.setFontAlign(-1,-1,0);
|
||||
g.setFont("Vector",50);
|
||||
g.drawString(hour_txt,x,y);
|
||||
}
|
||||
getName(){return "Roman";}
|
||||
}
|
||||
|
||||
module.exports = [DigitNumeralFont,RomanNumeralFont];
|
|
@ -0,0 +1,10 @@
|
|||
class Hand {
|
||||
/**
|
||||
* Pure virtual class for all Hand classes to extend.
|
||||
* a hand class will have 1 main function
|
||||
* moveTo which will move the hand to the given angle.
|
||||
*/
|
||||
moveTo(angle){}
|
||||
}
|
||||
|
||||
module.exports = Hand;
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
const TWO_PI = 2* Math.PI;
|
||||
|
||||
// The problem with the trig inverse functions on
|
||||
// a full circle is that the sector information will be lost
|
||||
// Choosing to use arcsin because you can get back the
|
||||
// sector with the help of the original coordinates
|
||||
function reifyasin(x,y,asin_angle){
|
||||
if(x >= 0 && y >= 0){
|
||||
return asin_angle;
|
||||
} else if(x >= 0 && y < 0){
|
||||
return Math.PI - asin_angle;
|
||||
} else if(x < 0 && y < 0){
|
||||
return Math.PI - asin_angle;
|
||||
} else {
|
||||
return TWO_PI + asin_angle;
|
||||
}
|
||||
}
|
||||
|
||||
// rebase and angle so be between -pi and pi
|
||||
// rather than 0 to 2PI
|
||||
function rebaseNegative(angle){
|
||||
if(angle > Math.PI){
|
||||
return angle - TWO_PI;
|
||||
} else {
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
|
||||
// rebase an angle so that it is between 0 to 2pi
|
||||
// rather than -pi to pi
|
||||
function rebasePositive(angle){
|
||||
if(angle < 0){
|
||||
return angle + TWO_PI;
|
||||
} else {
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Hour Scriber is responsible for drawing the numeral
|
||||
* on the screen at the requested angle.
|
||||
* It allows for the font to be changed on the fly.
|
||||
*/
|
||||
class HourScriber {
|
||||
constructor(radius, numeral_font, draw_test, bg_colour_supplier, numeral_colour_supplier, hour){
|
||||
this.radius = radius;
|
||||
this.numeral_font = numeral_font;
|
||||
this.draw_test = draw_test;
|
||||
this.curr_numeral_font = numeral_font;
|
||||
this.bg_colour_supplier = bg_colour_supplier;
|
||||
this.numeral_colour_supplier = numeral_colour_supplier;
|
||||
this.hours = hour;
|
||||
this.curr_hour_x = -1;
|
||||
this.curr_hour_y = -1;
|
||||
this.curr_hours = -1;
|
||||
this.curr_hour_str = null;
|
||||
this.last_draw_time = null;
|
||||
}
|
||||
setNumeralFont(numeral_font){
|
||||
this.numeral_font = numeral_font;
|
||||
}
|
||||
toString(){
|
||||
return "HourScriber{numeralfont=" + this.numeral_font.getName() + ",hours=" + this.hours + "}";
|
||||
}
|
||||
draw(){
|
||||
var changed = false;
|
||||
if(this.curr_hours != this.hours || this.curr_numeral_font !=this.numeral_font){
|
||||
var background = this.bg_colour_supplier();
|
||||
g.setColor(background[0],background[1],background[2]);
|
||||
this.curr_numeral_font.draw(this.curr_hour_str,
|
||||
this.curr_hour_x,
|
||||
this.curr_hour_y);
|
||||
//console.log("erasing old hour display:" + this.curr_hour_str + " color:" + background);
|
||||
var hours_frac = this.hours / 12;
|
||||
var angle = TWO_PI*hours_frac;
|
||||
var dimensions = this.numeral_font.getDimensions(this.hours);
|
||||
// we set the radial coord to be in the middle
|
||||
// of the drawn text.
|
||||
var width = dimensions[0];
|
||||
var height = dimensions[1];
|
||||
var delta_center_x = this.radius*Math.sin(angle) - width/2;
|
||||
var delta_center_y = this.radius*Math.cos(angle) + height/2;
|
||||
this.curr_hour_x = screen_center_x + delta_center_x;
|
||||
this.curr_hour_y = screen_center_y - delta_center_y;
|
||||
this.curr_hour_str = this.numeral_font.hour_txt(this.hours);
|
||||
// now work out the angle of the beginning and the end of the
|
||||
// text box so we know when to redraw
|
||||
// bottom left angle
|
||||
var x1 = delta_center_x;
|
||||
var y1 = delta_center_y;
|
||||
var r1 = Math.sqrt(x1*x1 + y1*y1);
|
||||
var angle1 = reifyasin(x1,y1,Math.asin(x1/r1));
|
||||
// bottom right angle
|
||||
var x2 = delta_center_x;
|
||||
var y2 = delta_center_y - height;
|
||||
var r2 = Math.sqrt(x2*x2 + y2*y2);
|
||||
var angle2 = reifyasin(x2,y2,Math.asin(x2/r2));
|
||||
// top left angle
|
||||
var x3 = delta_center_x + width;
|
||||
var y3 = delta_center_y;
|
||||
var r3 = Math.sqrt(x3*x3 + y3*y3);
|
||||
var angle3 = reifyasin(x3,y3, Math.asin(x3/r3));
|
||||
// top right angle
|
||||
var x4 = delta_center_x + width;
|
||||
var y4 = delta_center_y - height;
|
||||
var r4 = Math.sqrt(x4*x4 + y4*y4);
|
||||
var angle4 = reifyasin(x4,y4,Math.asin(x4/r4));
|
||||
if(Math.min(angle1,angle2,angle3,angle4) < Math.PI && Math.max(angle1,angle2,angle3,angle4) > 1.5*Math.PI){
|
||||
angle1 = rebaseNegative(angle1);
|
||||
angle2 = rebaseNegative(angle2);
|
||||
angle3 = rebaseNegative(angle3);
|
||||
angle3 = rebaseNegative(angle4);
|
||||
this.angle_from = rebasePositive( Math.min(angle1,angle2,angle3,angle4) );
|
||||
this.angle_to = rebasePositive( Math.max(angle1,angle2,angle3,angle4) );
|
||||
} else {
|
||||
this.angle_from = Math.min(angle1,angle2,angle3,angle4);
|
||||
this.angle_to = Math.max(angle1,angle2,angle3,angle4);
|
||||
}
|
||||
//console.log(angle1 + "/" + angle2 + " / " + angle3 + " / " + angle4);
|
||||
//console.log( this.angle_from + " to " + this.angle_to);
|
||||
this.curr_hours = this.hours;
|
||||
this.curr_numeral_font = this.numeral_font;
|
||||
changed = true;
|
||||
}
|
||||
if(changed ||
|
||||
this.draw_test(this.angle_from, this.angle_to, this.last_draw_time) ){
|
||||
var numeral_color = this.numeral_colour_supplier();
|
||||
g.setColor(numeral_color[0],numeral_color[1],numeral_color[2]);
|
||||
this.numeral_font.draw(this.curr_hour_str,this.curr_hour_x,this.curr_hour_y);
|
||||
this.last_draw_time = new Date();
|
||||
//console.log("redraw digit:" + this.hours);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HourScriber;
|
|
@ -0,0 +1,436 @@
|
|||
/**
|
||||
* Adrian Kirk 2021-03
|
||||
* Simple Clock showing 1 numeral for the hour
|
||||
* with a smooth sweep second.
|
||||
*/
|
||||
|
||||
var ThinHand = require("fontclock.thinhand.js");
|
||||
var ThickHand = require("fontclock.thickhand.js");
|
||||
var HourScriber = require("fontclock.hourscriber.js");
|
||||
|
||||
const screen_center_x = g.getWidth()/2;
|
||||
const screen_center_y = 10 + (g.getHeight()+10)/2;
|
||||
const TWO_PI = 2* Math.PI;
|
||||
|
||||
|
||||
SETTING_PREFIX = "fontclock";
|
||||
// load the date formats and languages required
|
||||
const FONTS_FILE = SETTING_PREFIX +".font.json";
|
||||
const DEFAULT_FONTS = [ "cpstc58" ];
|
||||
const DEFAULT_NUMERALS = [12,3,6,9];
|
||||
const DEFAULT_RADIUS = 70;
|
||||
var color_schemes = [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
}
|
||||
];
|
||||
var fonts = DEFAULT_NUMERALS;
|
||||
var numerals = DEFAULT_NUMERALS;
|
||||
var radius = DEFAULT_RADIUS;
|
||||
|
||||
var fonts_info = null;
|
||||
try {
|
||||
fonts_info = require("Storage").readJSON(FONTS_FILE);
|
||||
} catch(e){
|
||||
console.log("failed to load fonts file:" + FONTS_FILE + e);
|
||||
}
|
||||
if(fonts_info != null){
|
||||
console.log("loaded font:" + JSON.stringify(fonts_info));
|
||||
fonts = fonts_info.fonts;
|
||||
numerals = fonts_info.numerals;
|
||||
radius = fonts_info.radius;
|
||||
color_schemes = fonts_info.color_schemes;
|
||||
} else {
|
||||
fonts = DEFAULT_FONTS;
|
||||
numerals = DEFAULT_NUMERALS;
|
||||
radius = DEFAULT_RADIUS;
|
||||
console.log("no fonts loaded defaulting to:" + fonts);
|
||||
}
|
||||
|
||||
if(fonts == null || fonts.length == 0){
|
||||
fonts = DEFAULT_FONTS;
|
||||
console.log("defaulting fonts to locale:" + fonts);
|
||||
}
|
||||
|
||||
let color_scheme_index = 0;
|
||||
|
||||
// The force draw is set to true to force all objects to redraw themselves
|
||||
let force_redraw = true;
|
||||
let bg_colour_supplier = ()=>color_schemes[color_scheme_index].background;
|
||||
var WHITE = [1.0,1.0,1.0];
|
||||
function default_white(color){
|
||||
if(color == null){
|
||||
return WHITE
|
||||
} else {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
// The seconds hand is the main focus and is set to redraw on every cycle
|
||||
let seconds_hand = new ThinHand(screen_center_x,
|
||||
screen_center_y,
|
||||
95,
|
||||
0,
|
||||
(angle, last_draw_time) => false,
|
||||
bg_colour_supplier,
|
||||
()=>default_white(color_schemes[color_scheme_index].second_hand));
|
||||
|
||||
// The minute hand is set to redraw at a 250th of a circle,
|
||||
// when the second hand is ontop or slighly overtaking
|
||||
// or when a force_redraw is called
|
||||
const minute_hand_angle_tolerance = TWO_PI/25
|
||||
let minutes_hand_redraw = function(angle, last_draw_time){
|
||||
return force_redraw || (seconds_hand.angle > angle &&
|
||||
Math.abs(seconds_hand.angle - angle) < minute_hand_angle_tolerance &&
|
||||
new Date().getTime() - last_draw_time.getTime() > 500);
|
||||
};
|
||||
|
||||
let minutes_hand = new ThinHand(screen_center_x,
|
||||
screen_center_y,
|
||||
80, minute_hand_angle_tolerance,
|
||||
minutes_hand_redraw,
|
||||
bg_colour_supplier,
|
||||
()=>default_white(color_schemes[color_scheme_index].minute_hand));
|
||||
// The hour hand is a thick hand so we have to redraw when the minute hand
|
||||
// overlaps from its behind angle coverage to its ahead angle coverage.
|
||||
let hour_hand_redraw = function(angle_from, angle_to, last_draw_time){
|
||||
return force_redraw || (seconds_hand.angle >= angle_from &&
|
||||
seconds_hand.angle <= angle_to &&
|
||||
new Date().getTime() - last_draw_time.getTime() > 500);
|
||||
};
|
||||
let hours_hand = new ThickHand(screen_center_x,
|
||||
screen_center_y,
|
||||
40,
|
||||
TWO_PI/600,
|
||||
hour_hand_redraw,
|
||||
bg_colour_supplier,
|
||||
() => default_white(color_schemes[color_scheme_index].hour_hand),
|
||||
5,
|
||||
4);
|
||||
|
||||
function draw_clock(){
|
||||
var date = new Date();
|
||||
draw_background();
|
||||
draw_hour_digits();
|
||||
draw_seconds(date);
|
||||
draw_mins(date);
|
||||
draw_hours(date);
|
||||
force_redraw = false;
|
||||
}
|
||||
// drawing the second the millisecond as we need the fine gradation
|
||||
// for the sweep second hand.
|
||||
function draw_seconds(date){
|
||||
var seconds = date.getSeconds() + date.getMilliseconds()/1000;
|
||||
var seconds_frac = seconds / 60;
|
||||
var seconds_angle = TWO_PI*seconds_frac;
|
||||
seconds_hand.moveTo(seconds_angle);
|
||||
}
|
||||
// drawing the minute includes the second and millisec to make the
|
||||
// movement as continuous as possible.
|
||||
function draw_mins(date,seconds_angle){
|
||||
var mins = date.getMinutes() + date.getSeconds()/60 + date.getMilliseconds()/(60*1000);
|
||||
var mins_frac = mins / 60;
|
||||
var mins_angle = TWO_PI*mins_frac;
|
||||
var redraw = minutes_hand.moveTo(mins_angle);
|
||||
if(redraw){
|
||||
//console.log("redraw mins");
|
||||
}
|
||||
}
|
||||
|
||||
function draw_hours(date){
|
||||
var hours = (date.getHours() % 12) + date.getMinutes()/60 + date.getSeconds()/3600;
|
||||
var hours_frac = hours / 12;
|
||||
var hours_angle = TWO_PI*hours_frac;
|
||||
var redraw = hours_hand.moveTo(hours_angle);
|
||||
if(redraw){
|
||||
//console.log("redraw hours");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let numeral_fonts = [];
|
||||
for(var i=0; i< fonts.length; i++) {
|
||||
var file = SETTING_PREFIX +".font." + fonts[i] + ".js"
|
||||
console.log("loading font set:" + fonts[i] + "->" + file);
|
||||
var loaded_fonts = require(file);
|
||||
for (var j = 0; j < loaded_fonts[j]; j++) {
|
||||
var loaded_font = new loaded_fonts[j];
|
||||
numeral_fonts.push(loaded_font);
|
||||
console.log("loaded font name:" + loaded_font.getName())
|
||||
}
|
||||
}
|
||||
|
||||
let numeral_fonts_index = 0;
|
||||
const ONE_POINT_FIVE_PI = 1.5*Math.PI;
|
||||
/**
|
||||
* predicate for deciding when the digit has to be redrawn
|
||||
*/
|
||||
let hour_numeral_redraw = function(angle_from, angle_to, last_draw_time){
|
||||
var seconds_hand_angle = seconds_hand.angle;
|
||||
// we have to cope with the 12 problem where the
|
||||
// left side of the box has a value almost 2PI and the right
|
||||
// side has a small positive value. The values are rebased so
|
||||
// that they can be compared
|
||||
if(angle_from > angle_to && angle_from > ONE_POINT_FIVE_PI){
|
||||
angle_from = angle_from - TWO_PI;
|
||||
if(seconds_hand_angle > Math.PI)
|
||||
seconds_hand_angle = seconds_hand_angle - TWO_PI;
|
||||
}
|
||||
//console.log("initial:" + angle_from + "/" + angle_to + " seconds " + seconds_hand_angle);
|
||||
var redraw = force_redraw ||
|
||||
(seconds_hand_angle >= angle_from && seconds_hand_angle <= angle_to && seconds_hand.last_draw_time.getTime() > last_draw_time.getTime()) ||
|
||||
(minutes_hand.last_draw_time.getTime() > last_draw_time.getTime());
|
||||
if(redraw){
|
||||
//console.log(angle_from + "/" + angle_to + " seconds " + seconds_hand_angle);
|
||||
}
|
||||
return redraw;
|
||||
};
|
||||
|
||||
// now add the numbers to the clock face
|
||||
var numeral_colour_supplier = () => default_white(color_schemes[color_scheme_index].numeral);
|
||||
var hour_scribers = [];
|
||||
console.log("numerals:" + numerals + " length:" + numerals.length)
|
||||
console.log("radius:" + radius)
|
||||
for(var digit_idx=0; digit_idx<numerals.length; digit_idx++){
|
||||
var digit = numerals[digit_idx];
|
||||
var scriber = new HourScriber(radius,
|
||||
numeral_fonts[numeral_fonts_index],
|
||||
hour_numeral_redraw,
|
||||
bg_colour_supplier,
|
||||
numeral_colour_supplier,
|
||||
digit
|
||||
);
|
||||
hour_scribers.push(scriber);
|
||||
//console.log("digit:" + digit + "->" + scriber);
|
||||
}
|
||||
//console.log("hour_scribers:" + hour_scribers );
|
||||
|
||||
/**
|
||||
* Called from button 1 to change the numerals that are
|
||||
* displayed on the clock face
|
||||
*/
|
||||
function next_font() {
|
||||
var curr_font = numeral_fonts_index;
|
||||
numeral_fonts_index = numeral_fonts_index + 1;
|
||||
if (numeral_fonts_index >= numeral_fonts.length) {
|
||||
numeral_fonts_index = 0;
|
||||
}
|
||||
|
||||
if (curr_font != numeral_fonts_index) {
|
||||
console.log("numeral font changed")
|
||||
for (var i = 0; i < hour_scribers.length; i++) {
|
||||
hour_scribers[i].setNumeralFont(
|
||||
numeral_fonts[numeral_fonts_index]);
|
||||
}
|
||||
force_redraw = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const hour_zone_angle = hour_scribers.length/TWO_PI;
|
||||
function draw_hour_digits() {
|
||||
if(force_redraw){
|
||||
for(var i=0; i<hour_scribers.length; i++){
|
||||
var scriber = hour_scribers[i];
|
||||
//console.log("idx:" + i + "->" + scriber);
|
||||
scriber.draw();
|
||||
}
|
||||
} else {
|
||||
var hour_scriber_idx = (0.5 + (seconds_hand.angle * hour_zone_angle)) | 0;
|
||||
if (hour_scriber_idx >= hour_scribers.length)
|
||||
hour_scriber_idx = 0;
|
||||
|
||||
//console.log("angle:" + seconds_hand.angle + " idx:" + hour_scriber_idx);
|
||||
if (hour_scriber_idx >= 0) {
|
||||
hour_scribers[hour_scriber_idx].draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function draw_background(){
|
||||
if(force_redraw){
|
||||
background = color_schemes[color_scheme_index].background;
|
||||
g.setColor(background[0],background[1],background[2]);
|
||||
g.fillPoly([0,25,
|
||||
0,240,
|
||||
240,240,
|
||||
240,25
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function next_colorscheme(){
|
||||
var prev_color_scheme_index = color_scheme_index;
|
||||
color_scheme_index += 1;
|
||||
color_scheme_index = color_scheme_index % color_schemes.length;
|
||||
//console.log("color_scheme_index=" + color_scheme_index);
|
||||
force_redraw = true;
|
||||
if(prev_color_scheme_index == color_scheme_index){
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called from load_settings on startup to
|
||||
* set the color scheme to named value
|
||||
*/
|
||||
function set_colorscheme(colorscheme_name){
|
||||
console.log("setting color scheme:" + colorscheme_name);
|
||||
for (var i=0; i < color_schemes.length; i++) {
|
||||
if(color_schemes[i].name == colorscheme_name){
|
||||
color_scheme_index = i;
|
||||
force_redraw = true;
|
||||
console.log("match");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called from load_settings on startup
|
||||
* to set the font to named value
|
||||
*/
|
||||
function set_font(font_name){
|
||||
console.log("setting font:" + font_name);
|
||||
for (var i=0; i < numeral_fonts.length; i++) {
|
||||
if(numeral_fonts[i].getName() == font_name) {
|
||||
numeral_fonts_index = i;
|
||||
force_redraw = true;
|
||||
console.log("match");
|
||||
for (var j = 0; j < hour_scribers.length; j++) {
|
||||
hour_scribers[j].setNumeralFont(numeral_fonts[numeral_fonts_index]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on startup to set the watch to the last preference settings
|
||||
*/
|
||||
function load_settings(){
|
||||
try{
|
||||
var file = SETTING_PREFIX + ".settings.json";
|
||||
settings = require("Storage").readJSON(file);
|
||||
if(settings != null){
|
||||
console.log(file + " loaded:" + JSON.stringify(settings));
|
||||
if(settings.color_scheme != null){
|
||||
set_colorscheme(settings.color_scheme);
|
||||
}
|
||||
if(settings.font != null){
|
||||
set_font(settings.font);
|
||||
}
|
||||
} else {
|
||||
console.log(file + " not found - no settings to load");
|
||||
}
|
||||
} catch(e){
|
||||
console.log("failed to load settings:" + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on button press to save down the last preference settings
|
||||
*/
|
||||
function save_settings(){
|
||||
var settings = {
|
||||
font : numeral_fonts[numeral_fonts_index].getName(),
|
||||
color_scheme : color_schemes[color_scheme_index].name,
|
||||
};
|
||||
var file = SETTING_PREFIX + ".settings.json";
|
||||
console.log(file + ": saving:" + JSON.stringify(settings));
|
||||
require("Storage").writeJSON(file,settings);
|
||||
}
|
||||
|
||||
// Boiler plate code for setting up the clock,
|
||||
// below
|
||||
let intervalRef = null;
|
||||
|
||||
function clearTimers(){
|
||||
if(intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startTimers(){
|
||||
setTimeout(scheduleDrawClock,100);
|
||||
draw_clock();
|
||||
}
|
||||
|
||||
// The clock redraw is set to 100ms. This is the smallest number
|
||||
// that give the (my) human eye the illusion of a continious sweep
|
||||
// second hand.
|
||||
function scheduleDrawClock(){
|
||||
if(intervalRef) clearTimers();
|
||||
intervalRef = setInterval(draw_clock, 100);
|
||||
draw_clock();
|
||||
}
|
||||
|
||||
function reset_clock(){
|
||||
force_redraw = true;
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
console.log("lcdPower: on");
|
||||
reset_clock();
|
||||
startTimers();
|
||||
} else {
|
||||
console.log("lcdPower: off");
|
||||
reset_clock();
|
||||
clearTimers();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('faceUp',function(up){
|
||||
console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
|
||||
if (up && !Bangle.isLCDOn()) {
|
||||
//console.log("faceUp and LCD off");
|
||||
clearTimers();
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
load_settings();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
startTimers();
|
||||
|
||||
function button1pressed() {
|
||||
if (next_font()) {
|
||||
save_settings();
|
||||
}
|
||||
}
|
||||
|
||||
function button2pressed() {
|
||||
clearTimers();
|
||||
// the clock is being unloaded so we clear out the big
|
||||
// data structures for the launcher
|
||||
hour_scribers = [];
|
||||
Bangle.showLauncher();
|
||||
}
|
||||
|
||||
function button3pressed(){
|
||||
if(next_colorscheme()) {
|
||||
save_settings();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle button 1 being pressed
|
||||
setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"});
|
||||
|
||||
// Handle button 1 being pressed
|
||||
setWatch(button2pressed, BTN2,{repeat:true,edge:"falling"});
|
||||
|
||||
// Handle button 3 being pressed
|
||||
setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"});
|
||||
|
After Width: | Height: | Size: 6.5 KiB |