diff --git a/apps.json b/apps.json index ade2f6502..c1e727abd 100644 --- a/apps.json +++ b/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, @@ -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, @@ -171,13 +172,12 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.24", + "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": [ @@ -216,6 +216,32 @@ {"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", @@ -223,7 +249,7 @@ "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":true, + "allow_emulator":false, "readme": "README.md", "custom":"custom.html", "storage": [ @@ -242,8 +268,8 @@ { "id": "sweepclock", "name": "Sweep Clock", "icon": "sweepclock.png", - "version":"0.02", - "description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font and button3 to change the colour theme", + "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, @@ -545,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", @@ -553,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", @@ -628,7 +665,7 @@ { "id": "hrm", "name": "Heart Rate Monitor", "icon": "heartrate.png", - "version":"0.03", + "version":"0.04", "description": "Measure your heart rate and see live sensor data", "tags": "health", "storage": [ @@ -820,6 +857,19 @@ {"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", @@ -1088,7 +1138,7 @@ { "id": "widpedom", "name": "Pedometer widget", "icon": "widget.png", - "version":"0.12", + "version":"0.13", "description": "Daily pedometer widget", "tags": "widget", "type":"widget", @@ -1234,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", @@ -2234,7 +2284,7 @@ { "id": "multiclock", "name": "Multi Clock", "icon": "multiclock.png", - "version":"0.12", + "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", @@ -3044,7 +3094,7 @@ "name": "Gadgetbridge Music Controls", "shortName":"Music Controls", "icon": "icon.png", - "version":"0.04", + "version":"0.05", "description": "Control the music on your Gadgetbridge-connected phone", "tags": "tools,bluetooth,gadgetbridge,music", "type":"app", @@ -3097,7 +3147,7 @@ {"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":"swatch.kit.js","url":"swatch.kit.js"}, {"name":"compass.kit.js","url":"compass.kit.js"}, {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} ], @@ -3155,7 +3205,7 @@ "id": "omnitrix", "name":"Omnitrix", "icon":"omnitrix.png", - "version": "1.0", + "version": "0.01", "readme": "README.md", "description": "An Omnitrix Showpiece", "tags": "game", @@ -3169,7 +3219,7 @@ "name": "Bat Clock", "shortName":"Bat Clock", "icon": "bat-clock.png", - "version":"1.0", + "version":"0.01", "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", "tags": "clock", "type": "clock", @@ -3178,5 +3228,35 @@ {"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} + ] } ] diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index 22409a4ec..62e8d0126 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -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 diff --git a/apps/about/app.js b/apps/about/app.js index a9c6854d9..9edd0c94f 100644 --- a/apps/about/app.js +++ b/apps/about/app.js @@ -5,8 +5,12 @@ var s = require("Storage"); g.clear(1); g.setFont("6x8"); var y = 24, h=8; +if (g.getWidth()>=240) { g.drawImage(require("heatshrink").decompress(atob("vE4gQZWg//AAI3Zh4dCoAd6wAd64Ad2j4d6l4dcn4dC6Adc+AdYv4dUggHG//kgN//AGB1WkDpkOAwsH/gDBgJ4CTRwdGl6RDl/0gHQgJeMDo2/AgcDIAIkBnAdRgJyCAAQdDlgdRgZPDgbWBDoUcDqMPRYcJgEfoA7Uh9AAgQ1BEgIdBngdRKQIACmBbB6AdB2gdRnoEDyB+C8tbbQVpgNAqOkAwMGyEQDoMB1AIBvgdDPYMC+H//7zBg//+fAA4OAgH//twDoMv/4WB3iyEAAPwHINvTYMAv/A/sC6BmBh/wDoP4gIuBdwayBAAP/DoMH4F4ToQSB+EPJQUOgKmDBgIABhAdFB4L7BgfAAYNwjpKChwJBTIQdDiAdFgHgAYIdDmDaCO4MD9Wq14dM+CdCDoU0nDjChyhBAAIdFsgdTZgaVDmPYLJk0LIodDaIcxcILRDSo80jiVECgUAvgDCmG0YQTRHDoTRBgLRCMwJDBnodDeAMDKoUvAIU/DocD6ELDoKRCAIM/LIcGG4PQUIKCBU4PzDoaEB/p3BFQKKCh9ADoXsKIVVqonCtVBoFQcAUKyFwghdB3IPBCwJZCAQMfEgQAL2AGFgZJBDoZgDABEMWYQJFgLwCkACB/gdLWYMCfoQAE35BEDpkH8EfdgYADl4mDl68BABazBFBA2CgK8CABcBUZP/8kBv58CAC1//4ABUQwASn4dgOxoALl4dC4AdYj4d6h4d+wAd6oAd2g4dCAwQA=")),120,y); g.drawString("BANGLEJS.COM",120,y-4); +} else { + y=-(4+h); // small screen, start right at top +} g.drawString("Powered by Espruino",0,y+=4+h); g.drawString("Version "+ENV.VERSION,0,y+=h); g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); @@ -25,11 +29,11 @@ g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); g.setFontAlign(0,-1); -g.drawString(NRF.getAddress(),120,232); g.flip(); // Pixel chooser image -g.drawImage(require("heatshrink").decompress(atob("+FQgl+xnu8AIBwGQgHuAoN3gF/hcLgEHu943G3hwUCDwIBCAAV3uEAhoBBhsO90OgHgoACBh0IhP5AAQXBg8H8Hw+GwEAXn4AECxGAh0MEAOeJAMP3+/huIG4cMg1mMog8BhnsAQIBC///J4MN6HcBIOIAAPs8Hl9nM5gcB0Hg852BAIMAI4YACIIIACh8AKAcAvA6D7vd7wTBTYJ3B9e+hEAhA4CyHuy8HXw29NgIABx+ASQKsBYgR3DgHQCIXMsEAAIOZyGZzx3Dh/A57IDPoXN4HNHwQoB9wAByDvBO4LhDOwR4Fd4cP/4oB0DWCd45VCgFFAYPuO4QACgEed4PweAILBN4NpwEMXILvBO4bvD/f/d4cPCYJ1BAAKSCzp3E/hNBJwPziEP+H8hrvD9DtC5MJd4RTBGoLvBhe7BQJSBAAeAI4IoCO4T2Ch8N6DvDeAPgqFQd48MiB3BE4cI/AvC5ns4AKCdgQAD//wUwMMhhgBO4Nmd4xED57vD+EwFgKTCYoON/+v////OZwGXgF55vQI4TaBEQRxB6Hw7DRCAAPgO44ACKYlFoB3CHIcAiEAi93I4JpCdARmBd4IAFd4QAE4HA5//hh1BAIIPByA5BEQUM/n8O4TzCAAQtBhvd/X8d4YYBvwOBO4LvBYIoKBh/YewfA6B3DLoP/d4JXGABMBiKkEAAwKH9LyFO4fwOoR3Dd4TDD5/AJQcwDgcO9zvC1vd7ocBxuAvh3CuEHh5jCEoOPgHf/53CGgMAoGgbgX/CgJZEAIYAB5HIbxRCBAYULhZfBAAMA/GA/47Bd44ABh4CBg1mg8A3YAB3vtO4cMWxvG5vdZYWIw8AvPQd4NwRwUwAYIlBhsNGoR3CqB3BIAR4BFAXHAIg/CRAIDBIgtHHIR3D3ZhCZYXwwBrCOAXP5n855kNO4OABIyxCHYcDmdutOZA4VAAYUNqB0DAAQfDKIVms3AAgJ3BhBMBJwgAHhi7DDIQABgl9CIrvCeAJ3JABPM4AoBhqbDIgI0CMQfdOgR3E5nG5MzIAIBBAQIABwA5BgUgkEiEAe7hwECtgCB2B3BbwMJ9OeyBLIh3gFATvCPITuDhoCEgFVqq0B//w///MQWIbYJkFAAIjBEoR3DCoOA8A3CYAOvh/wE4LvEKoLvCoEE/7xDAAy/C2G+gw2DNQ2e9I0DBgxIBxGAWgS1DAAfd7pYE6BrBWwUIh2OAwLcGNQOA5jbCd4gACO4OgAgMHu4aBDokKgGIZ4LtBogABBgXw4HwhnL5lwEQXgd4V3BAIdBb4jvBO4/uIAfQKAJ3Gh7sC6/X7ogBUIL0BCwJ3ChHoO4QeCO4YHBXAQCBO4xQBJoYVBNwIBBhWq0HDwEOCIPuoDtIH4LuCAAOwMIR3BUATnIfgZ9BFYKHBd5nQKwICBBYWAPoJ3B///d5HM5jvD4DxBd4PQGwIBCHIMAeAQAEhQIC4GIboQABB4ifBW4ZeCAAO+EwJyBNQV2sDvCAAw6DAAaLFDgPwB4kNGIUJ5I3CcooAHO4OZzILH+AABFgcKeAa+Dd4p3Jd4+Ld4juChnMuz0DNQQABBAMOM4RqDuFwY4IUEGpLwB8DjB+ACBC4kJyAEC93uyAABDoxLB8HwFYTlBAIMMFIJlEQQJ3BCoIYBDgULCIpZCQ4YGBu5pBhn/u4UExB2BNoMO9wBB9xqDO4JeEEQKTFxABBwHJh3ex2P9+JxncZAJcBhMJO4mZO4dgXYRPCWQQzF4AABRIhHB5gACBYPeSAcAxOAAYICCdwK0CQYfc/I6BNYeAOwIAKBgMMQIIHC8EP///AoLkBgH4+AMCd4uoxWI1B3EAAOQzIDBswCBcIwAGBosOh7dChuNAYXvL4IPChGYgEP+AnFFox3B9vtO4LvBG47/CcofOPoYABWIIzCd4bYCB4NwgwFBd4IBBhI0Bh64CdwIHBdwJIBdAq7BEgTwDAgaxBAQMJhvdBALuBBAIQDeAMPh/ADQOH2+IhpeDfgbvDZAMP54ACMoJcCsAYC5nOV4OXcgQADd4QADs8HsF2g1QSwQAE+AcGRILhD/5cDE4ySDAgcGwGdxqvDd4j3BCIMP5iSCvfQcA6SB9wLBxBmBAAX/H4LkDSAcOFoOXgG72AgEd4IADqEFAQkL93rhzHCLgRIBCwbwCBgSFBOoLvBwEMg6XBBgIXDO4WJhuNHQyOF+DvCu+w2/QHoQACBYPt7qsCAAPgOQLvJAAeXhYdCZYIBBKYOAAII/I3yMB6CoBd4UDgbvDO44gBPIQ+BW4YADD4TvBOoI2FKA0A0AABAwfu9oOFOwPgAQLgBDoqwBAQIJFO5QACJIP/JQIDC+AVCO4LrBdgjuE24uB/7uFd4nwQob0DxEN7uIVxJ3E1R3Bh0ONoZ+E93gAIIPCVQ7fDgENAwRhC8AWBE4LvNAAXdaQsAmAHEO4QABhOZyB6BxB3BIg3QH4PQ/GIEIIAGQIMPTQMAhTuB1DaE9xNCAQTvCLgQACyDcDAAWIFARbD3ew9ycEKILvCABkMAAMAgZKCAAYlBHog8BAArqDO4mPx5bBuCTDCYWfh/P6AeFNgVwg7FEaITvC4BIB4B3HMgXdEwP/VwyCBO4QpB8A4GABiUCACB2COoIBCxH4wEM28A5hYCgEGszvC6F3NojKBuF3O4g+DPQPAAAWQ/7GB5nMH48D+AsCAAZDBF4YFCP4OAwD4GJgQCBhkJBYg8BBQJeBCgoABBAQCBNgIABd4UL5dwBASZQxGAKQcNAgPuQgJuBhnAz8A/kM553GFwMwO4PPhYfFTYjvBhAwBfAQABuA/GVAKKCTgxdR/GI+EM3gXCSIZeBg8Au7vEO4vQJgIAB+BTB8DvI//8FQLzBFYPL5YDBKQvQd5Z3FYoUPO4ZUBCQOf/5YDVoIFDIwNw+CUHBgQADEAOIUQnHg9wg+8714zUQCYbvBO4pDFXwRPBd4UOfwIzB5e7U4gAMO4R4BA4S4HhgiBO452DRQcP54ECyEJzJ3DkYXDGIIABRQTvCVoI0EhvcZghFCu4QBaQhKEdYIIFO4m7hewGIIRFEJAAFMYRQCRQZ3FXYUOCYXgd4cJhJ5BBIMOgE9mAYCxGAd4kAdwJ3DzIYBhu9OwbvDPwqTCcI8LAYU83gEC2B4BCoP85ns4Z6BO5UP/5lCAAz+DF4kPOoIBBC4eggGpdoJeBh3ggEDkLvGHROeDAMI7rFETYLVB3ew6AMDJwxKEgcAQgZ3D5//53Onk8O4a+BAIO62DvIKQMJKIMIZofQh3uOQIABR4X/BgLtBd4h3B4+QiF2gzjCeggAB5vmwGrd4YADSYMGy2Wd4jODd4j5EAA52BMwLvB53uO4MNTIUBgIRB1TOBAAJlBABkHJAXgHYI9CXAK6Cbwvghx3BAoNgAQI1BiMAw53ExJ3BAAUMhWQhptCd4T3DNwzGBhh5BhnMPoQEDBAnM5jvB4YIBFQUQ+EQd4plBFYZLCGgvQuDvCO4/gdoWZzIWDO4TvDGYIBBxGLw+HO4OKO4nA1WQ4GwFYMGBIML3a6I/53CgEOZxoAFO4MPgPxSwIAE93gSIQACqsFqEMF4MLeAbjFW4UA0ABCAAmOSwp3Dxe7hAiGha3BhOQhANCd4W/l7EDyGQzILBG4L4GP4Z3ODgKVBLgYhBL4MM/kA/LcBoHwoCAF6HueALdBh3+eAQABuEHcgKdFbgQBB4JtD3YAGgGwUoIiDAYTdB2Xy2DiCOgJ4BO4vQPYfMGQJdB5nM55rELYg9CA4fvO4cIxEAzJoBh4uBO4sLH4QOBC4X/PAMHAAQSCg/ud4UMAAYMCzOIwB2GO4oABJQbvFAAg3BHAPgFIKpDO4TgB//5zML1cAjUAhUQeAYABxAeC7qWDAALvCAAfAK4bbB92QAAJCFg93d4gGBAgSVBO4sJxbvI2EIBwPYAQOqVoYOBXAICDbI5YDO4cJzOZznMhQiCKYXQO4PMCQLCBLYorIABGQhp3CewTvDKIbvB54TBd453Hd4sNPQWZGITnDbQMPX4jLFABEONQMK3QGBFAR3Cg8Gd4JwRDYRwDUQJHC8HgCg2wd4XA+B3DeYO/BgMJxDvHhYMBd4l3agRCI7sNAAJEEFgLtCJ4nM5gbGhqRBg9gMgUPdoYBDfwIaExAABwDvEAIUOhIBBQAMJAYJ3D93Ah7RDAAO7+ARBEQgADBAbvBAoPuO48OW4R2FAAZ2GCoPOEAMLX4gDCNYTvB+Hw/8AuAIBAQScBDQQBBG4SoBF4OQAALvDO4ZQCd4eZOwbDCd4WZwEPGwQAL7p3BhOQDALMBQQPgNY/bO4R4DCAXx/DOGAAZnBAAMPd4JCBg4ABTgo4BAIPuEwXteAhlDJgOQd4UL3YMC/PwAgW52EJ/grDh//O4IpDeQ0A5iLBGIOwc4ZBB5nAG4OZm71BIoR3DhyrC/8QEgYiBu50BRIdwUwLvBAAp3DdwYlBEwS3CACLvGO4fM5h3CBQIpDgEIxAFDqoeCD4PdhvQRYOA//w8CsBMIML7zaCMoYACiMfF4PwX4OQuFwdgZ3B6BgBeAMAd4oRB3cLVgLFFhoEBha7Ch8PhAABAgJ4G+ycCd4vHvjBBVIZ5Ed4gABSoQxChsIdYWQ8HphOnVw4iCT4hQBO4TvDMYR3DdQVwBIR3ChcLPALvDHwXAFQQSCABXwPoP/sBCHO4SMCwBxEhAFB5ncDYIsMEoKFCa4YDC8DCBAQOZ5nMBILvIAoPdH4UPdgIBDSAQACJgMIHYzvDdoQADBweZzMAsx3CKgZIBIofAMAoMBwBKB6AMELAQCBIIIAKXRGZ/6YDIQNwg7vBO4buBABewAAK+DGh4AEz3pegZtBGwLyC4C1DOwj/DO5BYBhOQ3JCBh7LBgHuAAMA5vgvI9HVAKpCABDkBO4ztDgEEdwYAJd4TqDgwFEO4sP95ABO4TiBbYp4EKoncgEKAIPdRoMJCoJCDbYQjBDQPA8Fw0BQLAYyYBAAuIwAABg75DCAISE+DVBAQTvHsFgZQ2Zd45TCGwgIC8HuAQINDd4Wg0HQ5j4ByAaEHoTvFO4OwMouYmcwh//AIIKDYgYADh4IBPIMHg7dBgxoFCAMAwACBEIgACdwMGAwYWDhvLD4sOeoMHAwWJwDvIO4JxBeALvB5jdKABf4RAOImCNBKoVQAQOOG4YAC/5UBd4Y7BBYQ4Sd4sPj6OCLQIAHO4cIH4R2BPAwAChcOXYMMgYNHhpODAA7XBO4rvBMwMI9HoeYZBC5kM4AGBd4TPC4D5Cu+Zh5iB3ew2HP5nAdAbwBAocP+J3ChItCOIYtCAoYOBgHgOwUMdYIADBIOw8Fw6GQLwIAG6GZzLvKFYJ6Bd4arC7qRCO4cM5gABAwIyB8DvDCARKC+C8BAgP//4GBABEBiJ3BqAcCuF3O4l3AwgAF4AABIQJ3Ch7wDyYIB1MK7gOCYwOQDgcMNYP/NwQMCyDtBBAQHBhv9/p3FOwTZBXQcJx3ugF3uEHvKnDO4LvDdQYADL4kP81wdA14KQmwcoq3CAQP8BYfweATvCyGQ6EMI4J3Bd5UAhQEDxEIdoOgO4MPDQJ3GMIPILQhEB8BXCJQR3EGpIAFh/g8AtCLwQlBHoIgCAQbwFPQcAggLEd4SUB6ARBuF96EAhML3YABDYMJCwQwCNYWAAQJVB7vw/oaBO4Y0B5iuD4+Qhx3Kh4DCWoIGBh7tCAgIUE+HuAYJ3D/8A7iTDhgeCegQAEBIdEoBoB9IIDO4PcDQNwuDvD2CaC4HACALuEd4iRB7vzO4JTBg5JCeAXohEMvLvGAgMD//yOALVBBgIDCAA8OBYLvDAAVQ+ABBcooBBeQ54CggABEgKZCQYgABO4QXDO4wAJdQMN7vddwOIg93XIXMh3gwDuBLgQ3CNoJdB+Hw/7iChnsFIkNhsMHoUOCAJ3BegQABgtVNQwnBAYMLWYIADNgVAOwNAd4UN5pfFKwR3GgEJgBkBLIX/VoKoCXQgAHB4QAFOAPwLYIBBO4QDBAIIjBSIPMDYxyDhaCBb4zvJ9wAE2C4BeAKlFO40AtvM5wdBO4O7fgg+BH4JJCM5ByEhjjEAA4KBBg4XCh//UoRsBNoXdPIWw2HQ2G9BAIYBhcJYYIFBD4TRCAAiWDO4sAyALCUgZ3DAA94vEO70AzOQK4JmH6BfEhvdFAUDmEzmDkCAAe72BTBKosHu93VYIAENwKOBd4R6CVYXA2GQgyLCfhTvHLYJ3Bd5IAD997SoNwhCJDEgPuCIn/MwItBAQR3BhoWCOgIBBAA2q0BaBKRLvCGggABCZTqEAwsIDojvGaYTvGAA0Ph33uELg94BYjKECIP/boMNAwPe6HMd4Q8BxGAAIKFBeAgIBh2OMoXgcYIAJ5jvCfQvdeIQANh7vLGRbvEvOQW4JeBwGA5jLG/+IMgXtOwImHmDvFyB5ExAkCIQIbCNYNwg93hGIgHA4CIBg4gETYdAA4SHBEAIXBAIIRCC4h3EgyOKhi6CBIsIaIICCO4cIQYP/d4S8B9x3HmZ4BIIcM/IMDd4sNDIsHg6uBO4QJCeAl3AoJiBRIUO9wLBYoJOBAAOwPAoAD8C2EAAY8BVIJEC7oPHwBBEbwQmEaYXnSgwAGHAojFHwbuBd4QHB5iBEGwzaCN4MMCQTvF34qFhyDCO4MJ/kAx2wBAP8hvQ5h2CPoLXD9ns8GIwEMKYcLeAR2EJooAHXAR3CDQMMAATvFh1w87vCLobuDAIJ3EXwaJBxBIBdwKSCh5CCu4ZBAAMIzOAO4h/CgxxBPAJ2BL4XQhoGBYxI/F9x4BDIPgEwUA3YABNwToDyB4B2CvCACihGg8GKwLvCxjvGVgVwTYIYDBgIYBd4Z3Cd4JxBOALwD7tOMYQ3EUAMJeAQKE9ylCqA4CNQIACIQcM/IaBAAIZCgjADJANgAIQAIuEDmEwmZPBDIsM5iPKO4tAgGQMIbvEAAMOAATuCBATvCg93uB3BNAQAEhzvDmDdEAgLuEAALuBd5JABwFng53JdwsIWINwCYuIMAQACQAV3AAJBCHoZ3EBQTvB7vQc4UOhqlDd4R1BO4X/O44FEfgLvEO4JuHQIQoBd4Z3Du5jBh8PdwwDCmDBB8BKEDwYfCA4bNBSQ+IhMJhSWBACp3CAoSfBIoXuCpLvH5n5eASQBSIuIaIMPvIGBh/wE5J3Bd4RlCLoeIBQOIO5sIO4WoFQ7xBdgICBhrdFuAhC/4ABA4IABDotm5nMgBXBhe7gG7dwSrH8AABaAgBBg6gBABGgAwruEdYQDCAoX8HgJ3CAAnwd4qLD1orGAAbDFAAUP/4rBP4J3E5/8s3uO4IAIwB7CFQgrFO4QoBGw6aB1QoJbIKiBNwR3C4HAhhABJYkP94UB6GQD4vbTgXuAATJC8BABYgwAHeoI1Bhh3DQwIABoBNDhbwINAZ3EGgpUBh8LmfuYhRxBhg7BhgIC/gDCg8HgGIFIRGBA4IAGd4hxCgF3uB3GhB3IhOZFALvC5h3DoFPgjkB7sA2AcCHYkPSYVwYokOKIbvF126AoNEgigB9RHCUAJ1BdARsCewVwwF4WAYvBMoI/Cu4zBxwGB3cL2BxBFAJNBO4v3+/wVAOQJYJNChP5c4sDgEwgGEwB3B93QJoUHNoICCXYb7BeAIADYYvA53u93qeAVAAAJWB1wRDd4wAEsEIHIMGs1mu4ABHQQCBhHIAoOwAALvDAoI3B9x3Cv9/CwPPyGN6ABBd4h3HppOBhzvCMoR2BAQKxBO4TvGIwQAD5nA8Hg92u1QuCAILwEd4Z3Hg0GgGIgB2BO4d2sw+Bd4mwAIJ3FEQqRCd48P/+QO4kAkQFCojGCRQLdDGwJwCDYJTBdxZlBgB2BA==")),0,135); +g.drawImage(require("heatshrink").decompress(atob("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")),0,y+19); +g.drawString(NRF.getAddress(),g.getWidth()/2,g.getHeight()-8,true); g.flip(); setWatch(_=>load(), BTN1); diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 7e9fd4a81..48e1baa48 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -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 diff --git a/apps/boot/boot0.js b/apps/boot/boot0.js index 550513b11..3e567d9b8 100644 --- a/apps/boot/boot0.js +++ b/apps/boot/boot0.js @@ -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')); diff --git a/apps/boot/bootloader.js b/apps/boot/bootloader.js index df3718dcc..138258c5a 100644 --- a/apps/boot/bootloader.js +++ b/apps/boot/bootloader.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"); diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js new file mode 100644 index 000000000..9dc90cc9a --- /dev/null +++ b/apps/boot/bootupdate.js @@ -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')); diff --git a/apps/cliock/ChangeLog b/apps/cliock/ChangeLog index 07b38e189..53616638b 100644 --- a/apps/cliock/ChangeLog +++ b/apps/cliock/ChangeLog @@ -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 diff --git a/apps/cliock/app.js b/apps/cliock/app.js index ca48bb26f..d9541f545 100644 --- a/apps/cliock/app.js +++ b/apps/cliock/app.js @@ -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) diff --git a/apps/doztime/ChangeLog b/apps/doztime/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/doztime/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/doztime/README.md b/apps/doztime/README.md new file mode 100644 index 000000000..075b2f66a --- /dev/null +++ b/apps/doztime/README.md @@ -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. diff --git a/apps/doztime/app-icon.js b/apps/doztime/app-icon.js new file mode 100644 index 000000000..19e81c45d --- /dev/null +++ b/apps/doztime/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEowggdkUiCKIADCJcCkUjmYACmUikAlKB4ImDAoQSJkYhBFAQECAQI5HBQU//4AC+YUCHowzBCQfzAYYKCEw8vEgYqD+QoGgQbBHAYADCwIoBCYkiEwhPEBAIoBHgY6BExHyHwQhBFAQ6BkYTHDgcyHgcCHRZlDCYQsBTYg6GDAJQDPoI6LAAIPBCYRiHHQhkDCYRiHHQhkCCYKKBCYzzBA4yMBCYTVEGYITEBYITZHY5PHUAJjITIJjHRZINBIYoTDWZAoFWYbbJFALbHgUyX4oPDXIcjMQITBmZkHFYszCYZkJMQoTCKAQ8IHQZOCHgYoKkQ6DHgYoEcIgmBHQg8CFAIPCCYfzBQQSEFAbrFCQImHFAQUCkczmYECAQISGHoYzBAAQFCCRA9BEwYoDHI4pFAAgRLCooRPABg=")) diff --git a/apps/doztime/app.js b/apps/doztime/app.js new file mode 100644 index 000000000..83f536018 --- /dev/null +++ b/apps/doztime/app.js @@ -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 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 + + + + + +

Please select watch display

+ + + + + + +
+ + + +
+ +

Click

+ + + + + + diff --git a/apps/fontclock/display-01.png b/apps/fontclock/display-01.png new file mode 100644 index 000000000..e7100a25f Binary files /dev/null and b/apps/fontclock/display-01.png differ diff --git a/apps/fontclock/display-02.png b/apps/fontclock/display-02.png new file mode 100644 index 000000000..b7c8e81b1 Binary files /dev/null and b/apps/fontclock/display-02.png differ diff --git a/apps/fontclock/display-03.png b/apps/fontclock/display-03.png new file mode 100644 index 000000000..9cbe80544 Binary files /dev/null and b/apps/fontclock/display-03.png differ diff --git a/apps/fontclock/display-04.png b/apps/fontclock/display-04.png new file mode 100644 index 000000000..c8dbdeabb Binary files /dev/null and b/apps/fontclock/display-04.png differ diff --git a/apps/fontclock/display-05.png b/apps/fontclock/display-05.png new file mode 100644 index 000000000..b716443a9 Binary files /dev/null and b/apps/fontclock/display-05.png differ diff --git a/apps/fontclock/fontclock-icon.js b/apps/fontclock/fontclock-icon.js new file mode 100644 index 000000000..49431587b --- /dev/null +++ b/apps/fontclock/fontclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEowkA/4AvmUiAA0/CRHzkczAA0vExM/n/zn8zAIPzCZUi/8j+cvmUzAgI7JBQITHkY6JCwRNEIYITIDoQSEExXyDoQSDn4mKHQ4mKLoImRHQQmPMIYTDExY6HExY6HExQ6HYgISJHQ4TBAgbXOAAb3Ba5giBn8/H4zXHMYfzEww6I+cyPJAtEToizBNoQTFLo0yBAKMI+UikUjIwQSBJg61ICALGMPQgQBJhB6IbJjcGJhw6DCQJMMUIhMOHQavBCRo6CJh46DTJo6EJh5eCTJwADdwISQJiIAo")) diff --git a/apps/fontclock/fontclock.font.abril_ff50.js b/apps/fontclock/fontclock.font.abril_ff50.js new file mode 100644 index 000000000..3d5169c63 --- /dev/null +++ b/apps/fontclock/fontclock.font.abril_ff50.js @@ -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]; \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.cpstc58.js b/apps/fontclock/fontclock.font.cpstc58.js new file mode 100644 index 000000000..6e91349ab --- /dev/null +++ b/apps/fontclock/fontclock.font.cpstc58.js @@ -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]; \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.js b/apps/fontclock/fontclock.font.js new file mode 100644 index 000000000..10b063ca5 --- /dev/null +++ b/apps/fontclock/fontclock.font.js @@ -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; \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.json b/apps/fontclock/fontclock.font.json new file mode 100644 index 000000000..3f111ba1b --- /dev/null +++ b/apps/fontclock/fontclock.font.json @@ -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] + } + ] +} \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.mntn25.js b/apps/fontclock/fontclock.font.mntn25.js new file mode 100644 index 000000000..2aaeb4c9e --- /dev/null +++ b/apps/fontclock/fontclock.font.mntn25.js @@ -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]; \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.mntn50.js b/apps/fontclock/fontclock.font.mntn50.js new file mode 100644 index 000000000..650c0b1af --- /dev/null +++ b/apps/fontclock/fontclock.font.mntn50.js @@ -0,0 +1,46 @@ +var NumeralFont = require("fontclock.font.js"); + +const DIM_30x47 = [30,47]; +const DIM_49x47 = [49,47]; +const DIM_37x47 = [37,47]; +class DigitNumeralFont extends NumeralFont{ + constructor(){ + super(); + this.widths = atob("DRYqFR0fHyMnICgnDQ=="); + this.font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAChkoAAAAAAAAAAAAAvP7wAAAAAAAAAAAAC8/vAAAAAAAAAAAAALz+8AAAAAAAAAAAAAvP7wAAAAAAAAAAAAC8/vAAAAAAAAAAAAALz+8AAAAAAAAAAAAAvP7wAAAAAAAAAAAAC8/vAAAAAAAAAAAAALz+8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAH/AAAAAAAAAAAAAB//8AAAAAAAAAAAAf///gAAAAAAAAAAH///5LAAAAAAAAAB///+S/8AAAAAAAAf///kv//wAAAAAAH///5L///4AAAAAB///+S///+G8AAAAf///kv///hv/wAAH///5L///4b///AAP//+S///+G///5AAA//kv///hv//+QAAAD5L///4b///kAAAAAC///+G///5AAAAAAA///hv//+QAAAAAAAD/4b///kAAAAAAAAAKG///5AAAAAAAAAAAv//+QAAAAAAAAAAAD//kAAAAAAAAAAAAAP5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAAAAAAAAAAC////4AAAAAAAAAAL//////gAAAAAAAAP//6Vr//8AAAAAAAH/9Gv/pH/9AAAAAAD/4v////4v/AAAAAA/8///////P/AAAAAP9v/4BUC//n/AAAAD/f/m///+b/3/AAAAv3/n/////2/3+AAAH9/3//+r//9/39AAA/v9/+G/+S/9/78AAD9/f+f///9v9/fwAAvf3/f/////f9/fgAD9/vz/4AAv/P78/AAf/39/0AAAH/f3/9AC9/f78AAAAD+/f34ALz+9/QAAAAH9+/PgA/Pvz8AAAAAPz+8/AD99/PgAAAAAvP338AP739+AAAAAC9/ffwA/ff34AAAAAL399/AD8+/PwAAAAA/P7z8APz/9/AAAAAD9//PgAvf37/AAAAA//39+AB+/Pz/AAAAP8/P70AD9/v3/wAAP/f79/AAP39/3/////3/f38AAf79/2////5/3+/QAA/f9/9r//p/9/78AAB/f9//5Rb//f9/QAAD/f9v/////n/f8AAAD/f/b////n/3/AAAAD/f/4WqlL/9/wAAAAH/X//////9f9AAAAAH/1/////9f/QAAAAAC/+H///0v/gAAAAAAB//+QAb//0AAAAAAAAf//////0AAAAAAAAAB/////QAAAAAAAAAAAAa6QAAAAAAAAAAAAAAAAAAAAAAAAClopAAAAAAAAAAAAAPvz4AAAAAAAAAAAAA+/PgAAAAAAAAAAAAD78+AAAAAAAAAAAAAPvz4AAAAAAAAAAAAA+/PgAAAAAAAAAAAAD78///////////wAAPvz///////////AAA+/P6qqqqqqqqqoAAD78///////////gAAPvz///////////AAA+/P//////////8AAD78+AAAAAAAAAAAAAPvz///////////AAA+/P//////////8AAD78/qqqqqqqqqqgAAPvz//////////+AAA+/P//////////8AAD68///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAAAAAAA/PQAAAAAAAG8+vAAD9+/ZAAAAAC/z+8AAP/3+9AAAAA//P7wAB+/PvwAAAAf/8/vAAL3+9/AAAAL/bz+8AA/P/z8AAAD/r/P7wAD9+/PgAAB/7/8/vAAP379+AAAv+//z+8AA/vf30AAP+/9vP7wAD+9/vQAH/v+v8/vAAP7399AD/7/f/z+8AA/fv30A/7/3//P7wAD9+/Pgv+/7/28/vAALz+9/v/v9/9/z+8AAvP3+//v/v+v/P7wAB+/f3/7/v/f/8/vAAD+/v4H/3/3/bz+8AAP39///7/2/0vP7wAAf79v/9/9/8C8/vAAA/f9G5/+v+ALz+8AAC/f/7//f/QAvP7wAAD/P///r/wAC8/vAAAH/n//n/0AALz+8AAAH/5Ab/8AAAvP7wAAAH////9AAAB8tfAAAAC///9AAAAAAAAAAAAAK/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAABj4AA/P7gAAAAAAAu/PwAD9+/fQAAAAH3+9/AAP73+8AAAAAfv378AB+/PvwAAAAA//P70AL3+9/AAAAAD+9/fgA/P7z8KWiloPz78+AD8+/Pg/vP7wvP/z8APz78+D+8/vC8/vPwA/fv34P7z+8L3+8/AD99/fQ/vP7wvf738AP3399D+8/vB9/ffwA/ff30P7z+8H3+9/AD9+/fg/vf30vP738APz/8/D9+/Pj8+/PwA/P77//z/9//778+AC+/f3//f3+/9/f74AD+/v3/+/v6/f7+/AAPz9/5b9/f+b/fz8AAv79////+v//3+/gAA/f9///3/f/9/38AAC/f+RR/3/Rlv9/gAAD/f////3////f8AAAD/b////3///3/AAAAH/2//X/5//n/0AAAAD/+lr///lb/8AAAAAC////9////+AAAAAAAv//9Af//+AAAAAAAABaQAAAaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7z58AAAAAAAAAAAB//P7wAAAAAAAAAAC//8/vAAAAAAAAAAC//rz+8AAAAAAAAAD//X/P7wAAAAAAAAH//X/8/vAAAAAAAAH//b//z+8AAAAAAAH//L/+vP7wAAAAAAL/+P/9f8/vAAAAAAL/+f/9f/z+8AAAAAL/9f/8v//P7wAAAAD/9f/4v/68/vAAAAAP9v/4//4vz+8AAAAA8v/5//0v/P7wAAAAAv/1//1//8/vAAAAAP/1//x///z+8AAAAA/y//i//wvP7wAAAADi//i//gC8/vAAAAAD//X//gALz+8AAAAA//X//QAAvP7wAAAAD/L//Af//8/vv/gAAOL/+AC///z+///AAAP/+AAH///P7//8AAD/9AAAAAC8/vAAAAAP9AAAB///z+///AAA4AAAAL///P7//8AAAAAAAAKqr8/vaqgAAAAAAAAAALz+8AAAAAAAAAAAAAvPrwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAA8AAAD14//////0AAL8AAAPvz//////gACf8AAA+/P/////+AAvfwAAD78+AAAAAAAD/vwAAPvz//////gA39/AAA+/P/////+AP3++AAD78/qqqqqgA/fz8AAPvz//////Qt/vvwAA+/P/////+D78/vQAD78//////4P739+AAPvz4A9ufPQ/Pvz8AA+/PgH3+9+C8+vPwAD78+Avf/z0L3+9/AAPvz4C8//fAff338AA+/PgLz7+8B9/vfwAD78+AvPv7wL3+9/AAPvz4D8+/fg/P/z8AA+/PgLz+8/D9+/PwAD78+AvP77///39+AAPvz4B+/P3/9/v/wAA+/PgD9+/3/f39/AAD78+AP38/0L/f78AAPvz4Av79///39/QAA+/PgA/f8//9//8AAD78+AB/f9L5f9/QAAPvz4AD/f/6//f8AAA+/PgAD/P///3/AAAD289AAH/j//y/wAAAAAAAAAH/9AH/8AAAAAAAAAAD////+AAAAAAAAAAAB///+AAAAAAAAAAAAAGvpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa////6QAAAAAAAAB///////+AAAAAAAB/////////gAAAAAA//kAAAABv/0AAAAAP/X//////n/0AAAAD/b///////6/0AAAA/3//6qqqv/9/wAAAL9/9a////kv9/wAAB/P+v//////P9/QAAP6/f///////P+/AAA/P3/5AAAAf/fz8AAP3+/2/////3/v74AA/f79//////79/fwAD/+/f/6qqr/3++/AAvf39/Pz799//3+9AD8/vfw/vf/w/fv38APz/8/D/9+/D8+/PwA/fvz4ff734L3/9/AD99/fS8//fQff738AP3399Lz799B9/ffwA/ff30vPv7wH3+9/AD9+/Pj8+/vQvP7z8APz68+Lz79+D8+/PwA/P338vP7z8f779+AB+/f/x+/f//+/f70AD+9/fD+9/f/7/+/AAP3+/QP3+/b2/f38AA/fz8Av/3///7//gAB/v3QA/f3///v38AAD9/gAC/v2//b+/gAAH9+AAD+/+AL/r8AAAP+wAAH+v///6/QAAAP0AAAP/f//9/4AAAAfQAAAP/lvlv+AAAAAkAAAAL//r//QAAAAAAAAAAD////wAAAAAAAAAAAAv//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPrz4AAAAAAAAAAAAA+/PgAAAAAAAAAAAAD78+AAAAAAAAAAAAAPvz4AAAAAAAAAAAAA+/PgAAAAAAAAAAAAD78+AAAAAAAAAAAAAPvz4AAAAAAAAAGAAA+/PgAAAAAAAB/8AAD78+AAAAAAAv//wAAPvz4AAAAAL///4AAA+/PgAAAC///+G8AAD78+AAAv///hv/wAAPvz4AL///4b///AAA+/Pm///+G///9EAAD79////Rv//+R/wAAPr///0f///kv//AAA///9H///5L///4AAD//R///+S///+GwAAPkf///kv///hv/AAAL///5L///4b//8AAD//+W///+G///9AAAP/hv///Rv//+QAAAAob///0f///kAAAAAC///9H///5AAAAAAAP//R///+QAAAAAAAA/kf///kAAAAAAAAAAL///5AAAAAAAAAAAP//+QAAAAAAAAAAAA//kAAAAAAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//0AL//9AAAAAAAf///+L////QAAAAAP/+r////r//wAAAAD/0v5f/4r6b/wAAAA/6///n////1/wAAAP9////3////9/gAAC/f/gG/3/kB/9/AAAP7/b/9/3//+f+/AAC/f3//9/v///f79AAP39/+/9/f///f38AA/v/9uf+/v/n+/vwAL79/f/v38//38/vQA/P77//f7z///78/AD8//Pz9/vfw/Prz8AP379+H79+/D8/vfwA/vf30Pvz68L399/AD+9/fQ+/Prwff378AP7399D78+vB9/fvwA/vf30Pvz68H39+/AD+9/fQ+/Prwff378AP7399D78+vB9/fvwA/vf30Pvz+8H39+/AD9+/fg/vP7wvf338APz/8/D+8/vT8/vfwA/P77/v339//378/AC9/f7//P77/+/f34AH++/r/9/f3/r9/vQAP39/0H++v4D/f38AA/v+///39///+/fwAA/f9///39//+v39AAD/v9b5/v+b+P+/wAAD/f/6//v/6//r8AAAH/f////7///2/gAAAL/X//3/3//5/4AAAAH/5FC//9BB/+AAAAAH//////////QAAAAAB////A////QAAAAAAAK/5AAG/5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//QAAAAAAAAAAAAv///4AAAAAAAAAAAf////9AAAABQAAAAH/0aR/9AAAAPQAAAB/2//+v9AAAC/AAAAf9////f8AAA3/AAAD/f/Qf/f4AAL38AAAv3/P/n/fwAAv78AAD+/r//3/fwAd/fwAAv7+///7+/AD9/vgAD9/f8pv3+/Af39/AAP7//v//v38Lf778AB+/f3/9/f/w//P70AL3++/r+9/vT99/fgA/P7z8Pz79+Pz78/AD9+/Pgvf/z4vPrz8AP3799B9/vPi9/vfwA/vf30D7+8+H399/AD9+/fQff/z4ff338AP3799C8+/fS8/vfwA/P7z4P73+8Pz/8/AD8/vfw//fvw/fvz8AL39//n3+8/P79/fgAP/39/6VVVv/f7/8AA/f7+//////3+/fwAD+/f9/////9/79/AAH9/f+FVVVRv9/P0AAP3+f///////f9/AAAf3/f//////7/P0AAA/3/W////+j/3/AAAA/7/+lVVVr/9/wAAAB/3///////+f9AAAAB/9//////+L/QAAAAB//QVVVVQv/wAAAAAA/////////4AAAAAAAf///////4AAAAAAAABv////+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB89fAHz18AAAAAAAAPz78AvP7wAAAAAAAA/PvwC8/vAAAAAAAAD8+/ALz+8AAAAAAAAPz78AvP7wAAAAAAAA/PvwC8/vAAAAAAAAD8+/ALz+8AAAAAAAAPz78AvP7wAAAAAAAA/PvwC8/vAAAAAAAAC8+vALz68AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="); + var scale = 1; // size multiplier for this font + this.size = 50+(scale<<8)+(2<<16); + this.y_offset = -2; + + } + getDimensions(hour){ + switch(hour){ + case 3: + return DIM_30x47; + case 12: + return DIM_49x47; + default: + return DIM_37x47; + } + } + 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 = [37,47]; + 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]; \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.vector25.js b/apps/fontclock/fontclock.font.vector25.js new file mode 100644 index 000000000..95b23d040 --- /dev/null +++ b/apps/fontclock/fontclock.font.vector25.js @@ -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]; \ No newline at end of file diff --git a/apps/fontclock/fontclock.font.vector50.js b/apps/fontclock/fontclock.font.vector50.js new file mode 100644 index 000000000..ccc4599fd --- /dev/null +++ b/apps/fontclock/fontclock.font.vector50.js @@ -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]; \ No newline at end of file diff --git a/apps/fontclock/fontclock.hand.js b/apps/fontclock/fontclock.hand.js new file mode 100644 index 000000000..c5ab2e769 --- /dev/null +++ b/apps/fontclock/fontclock.hand.js @@ -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; \ No newline at end of file diff --git a/apps/fontclock/fontclock.hourscriber.js b/apps/fontclock/fontclock.hourscriber.js new file mode 100644 index 000000000..eaddbab4e --- /dev/null +++ b/apps/fontclock/fontclock.hourscriber.js @@ -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; \ No newline at end of file diff --git a/apps/fontclock/fontclock.js b/apps/fontclock/fontclock.js new file mode 100644 index 000000000..bd6ba16b7 --- /dev/null +++ b/apps/fontclock/fontclock.js @@ -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" + 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" + 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"}); + diff --git a/apps/fontclock/fontclock.png b/apps/fontclock/fontclock.png new file mode 100644 index 000000000..97377413c Binary files /dev/null and b/apps/fontclock/fontclock.png differ diff --git a/apps/fontclock/fontclock.thickhand.js b/apps/fontclock/fontclock.thickhand.js new file mode 100644 index 000000000..d28f91aef --- /dev/null +++ b/apps/fontclock/fontclock.thickhand.js @@ -0,0 +1,103 @@ +var Hand = require("fontclock.hand.js"); + +class ThickHand extends Hand { + /** + * The thick hand is created from a filled polygone, so its slower to + * draw so to be used sparingly with few redraws + */ + constructor(centerX, + centerY, + length, + tolerance, + draw_test, + color_bg_supplier, + color_fg_supplier, + base_height, + thickness){ + super(); + this.centerX = centerX; + this.centerY = centerY; + this.length = length; + this.color_bg_supplier = color_bg_supplier; + this.color_fg_supplier = color_fg_supplier; + this.base_height = base_height; + // angle from the center to the top corners of the rectangle + this.delta_top = Math.atan(thickness/(2*length)); + // angle from the center to the bottom corners of the rectangle + this.delta_base = Math.atan(thickness/(2*base_height)); + // the radius that the bottom corners of the rectangle move through + this.vertex_radius_base = Math.sqrt( (thickness*thickness/4) + base_height * base_height); + // the radius that the top corners of the rectangle move through + this.vertex_radius_top = Math.sqrt( (thickness*thickness/4) + length * length); + // last records the last plotted values (so we don't have to keep recalculating + this.last_x1 = centerX; + this.last_y1 = centerY; + this.last_x2 = centerX; + this.last_y2 = centerY; + this.last_x3 = centerX; + this.last_y3 = centerY; + this.last_x4 = centerX; + this.last_y4 = centerY; + // The change in angle from the last plotted angle before we actually redraw + this.tolerance = tolerance; + // predicate test that is called if the hand is not going to redraw to see + // if there is an externally defined reason for redrawing (like another hand) + this.draw_test = draw_test; + this.angle = -1; + this.last_draw_time = null; + } + // method to move the hand to a new angle + moveTo(angle){ + if(Math.abs(angle - this.angle) > this.tolerance || this.draw_test(this.angle - this.delta_base,this.angle + this.delta_base ,this.last_draw_time) ){ + //var background = color_schemes[color_scheme_index].background; + var background = this.color_bg_supplier; + g.setColor(background[0],background[1],background[2]); + g.fillPoly([this.last_x1, + this.last_y1, + this.last_x2, + this.last_y2, + this.last_x3, + this.last_y3, + this.last_x4, + this.last_y4 + ]); + // bottom left + var x1 = this.centerX + + this.vertex_radius_base*Math.sin(angle - this.delta_base); + var y1 = this.centerY - this.vertex_radius_base*Math.cos(angle - this.delta_base); + // bottom right + var x2 = this.centerX + + this.vertex_radius_base*Math.sin(angle + this.delta_base); + var y2 = this.centerY - this.vertex_radius_base*Math.cos(angle + this.delta_base); + // top right + var x3 = this.centerX + this.vertex_radius_top*Math.sin(angle + this.delta_top); + var y3 = this.centerY - this.vertex_radius_top*Math.cos(angle + this.delta_top); + // top left + var x4 = this.centerX + this.vertex_radius_top*Math.sin(angle - this.delta_top); + var y4 = this.centerY - this.vertex_radius_top*Math.cos(angle - this.delta_top); + //var hand_color = color_schemes[color_scheme_index][this.color_theme]; + var hand_color = this.color_fg_supplier(); + g.setColor(hand_color[0],hand_color[1],hand_color[2]); + g.fillPoly([x1,y1, + x2,y2, + x3,y3, + x4,y4 + ]); + this.last_x1 = x1; + this.last_y1 = y1; + this.last_x2 = x2; + this.last_y2 = y2; + this.last_x3 = x3; + this.last_y3 = y3; + this.last_x4 = x4; + this.last_y4 = y4; + this.angle = angle; + this.last_draw_time = new Date(); + return true; + } else { + return false; + } + } +} + +module.exports = ThickHand; \ No newline at end of file diff --git a/apps/fontclock/fontclock.thinhand.js b/apps/fontclock/fontclock.thinhand.js new file mode 100644 index 000000000..cf58d451a --- /dev/null +++ b/apps/fontclock/fontclock.thinhand.js @@ -0,0 +1,67 @@ +var Hand = require("fontclock.hand.js"); + +class ThinHand extends Hand { + /** + * The thin hand is created from a simple line, so its easy and fast + * to draw. + */ + constructor(centerX, + centerY, + length, + tolerance, + draw_test, + color_bg_supplier, + color_fg_supplier){ + super(); + this.centerX = centerX; + this.centerY = centerY; + this.length = length; + this.color_bg_supplier = color_bg_supplier; + this.color_fg_supplier = color_fg_supplier; + // The last x and y coordinates (not the centre) of the last draw + this.last_x = centerX; + this.last_y = centerY; + // tolerance is the angle tolerance (from the last draw) + // in radians for a redraw to be called. + this.tolerance = tolerance; + // draw test is a predicate (angle, time). This is called + // when the hand thinks that it does not have to draw (from its internal tests) + // to see if it has to draw because of another object. + this.draw_test = draw_test; + // The current angle of the hand. Set to -1 initially + this.angle = -1; + this.last_draw_time = null; + this.active = false; + } + // method to move the hand to a new angle + moveTo(angle){ + // first test to see of the angle called is beyond the tolerance + // for a redraw + if(Math.abs(angle - this.angle) > this.tolerance || + // and then call the predicate to see if a redraw is needed + this.draw_test(this.angle,this.last_draw_time) ){ + // rub out the old hand line + var background = this.color_bg_supplier(); + g.setColor(background[0],background[1],background[2]); + g.drawLine(this.centerX, this.centerY, this.last_x, this.last_y); + // Now draw the new hand line + var hand_color = this.color_fg_supplier(); + g.setColor(hand_color[0],hand_color[1],hand_color[2]); + var x2 = this.centerX + this.length*Math.sin(angle); + var y2 = this.centerY - this.length*Math.cos(angle); + g.drawLine(this.centerX, this.centerY, x2, y2); + // and store the last draw details for the next call + this.last_x = x2; + this.last_y = y2; + this.angle = angle; + this.last_draw_time = new Date(); + this.active = true; + return true; + } else { + this.active = false; + return false; + } + } +} + +module.exports = ThinHand; \ No newline at end of file diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 99dd02116..ecbca5fb6 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -2,3 +2,4 @@ 0.02: Increase text brightness, improve controls, (try to) reduce memory usage 0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity 0.04: Setting to disable touch controls, minor bugfix +0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker \ No newline at end of file diff --git a/apps/gbmusic/README.md b/apps/gbmusic/README.md index d081e952f..4bad9b8c8 100644 --- a/apps/gbmusic/README.md +++ b/apps/gbmusic/README.md @@ -22,8 +22,9 @@ You can change these under `Settings`->`App/Widget Settings`->`Music Controls`. Automatically load the app when you play music and close when the music stops. (If the app opened automatically, it closes after music has been paused for 5 minutes.) -**Touch**: -Enable touch controls? +**Simple button**: +Disable double/triple pressing Button 2: always simply toggle play/pause. +(For music players which handle multiple button presses themselves.) ## Controls diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 75c028b20..5f95868bb 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -13,10 +13,6 @@ let info = { }; const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms) const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms) -// Touch controls? 0: off, 1: when LCD on, 2: always -let s = require("Storage").readJSON("gbmusic.json", 1) || {}; -const TCTL = ("touch" in s) ? (s.touch|0)%3 : 1; -delete s; /////////////////////// // Self-repeating timeouts @@ -42,7 +38,7 @@ function fadeOut() { if (!Bangle.isLCDOn() || !fade) { return; } - drawMusic(); + drawMusic(false); // don't clear: draw over existing text to prevent flicker setTimeout(fadeOut, 500); } function brightness() { @@ -131,7 +127,7 @@ function f2hex(f) { return ("00"+(Math.round(f*255)).toString(16)).substr(-2); } /** - * @param name + * @param {string} name - musicinfo property "num"/"artist"/"album"/"track" * @return {string} Semi-random color to use for given info */ function infoColor(name) { @@ -174,7 +170,6 @@ function trackColor() { //////////////////// /** * Draw date and time - * @return {*} */ function drawDateTime() { const now = new Date; @@ -209,8 +204,9 @@ function drawDateTime() { /** * Draw track number and total count + * @param {boolean} clr - Clear area before redrawing? */ -function drawNum() { +function drawNum(clr) { let num = ""; if ("n" in info && info.n>0) { num = "#"+info.n; @@ -220,9 +216,11 @@ function drawNum() { } g.reset(); g.setFont("Vector", 30) - .setFontAlign(1, -1) // top right - .clearRect(225, 30, 120, 60) - .drawString(num, 225, 30); + .setFontAlign(1, -1); // top right + if (clr) { + g.clearRect(225, 30, 120, 60); + } + g.drawString(num, 225, 30); } /** * Clear rectangle used by track title @@ -232,8 +230,9 @@ function clearTrack() { } /** * Draw track title + * @param {boolean} clr - Clear area before redrawing? */ -function drawTrack() { +function drawTrack(clr) { let size = fitText(info.track); if (size<25) { // the title is too long: start the scroller @@ -250,7 +249,9 @@ function drawTrack() { g.setFont("Vector", size) .setFontAlign(0, 1) // center bottom .setColor(trackColor()); - clearTrack(); + if (clr) { + clearTrack(); + } g.drawString(info.track, 119, 109); } /** @@ -270,8 +271,9 @@ function drawScroller() { /** * Draw track artist and album + * @param {boolean} clr - Clear area before redrawing? */ -function drawArtistAlbum() { +function drawArtistAlbum(clr) { // we just use small enough fonts to make these always fit // calculate stuff before clear+redraw const aCol = infoColor("artist"); @@ -285,7 +287,9 @@ function drawArtistAlbum() { bSiz = 20; } g.reset(); - g.clearRect(0, 120, 240, 189); + if (clr) { + g.clearRect(0, 120, 240, 189); + } let top = 124; if (info.artist) { g.setFont("Vector", aSiz) @@ -347,7 +351,6 @@ function controlColor(ctrl) { return (ctrl in tCommand) ? "#ff0000" : "#008800"; } function drawControl(ctrl, x, y) { - if (!TCTL) {return;} g.setColor(controlColor(ctrl)); const s = 20; if (stat!==controlState) { @@ -379,10 +382,14 @@ function drawControls() { controlState = stat; } -function drawMusic() { - drawNum(); - drawTrack(); - drawArtistAlbum(); +/** + * @param {boolean} [clr=true] Clear area before redrawing? + */ +function drawMusic(clr) { + clr = !(clr===false); // undefined means yes + drawNum(clr); + drawTrack(clr); + drawArtistAlbum(clr); } //////////////////////// @@ -390,7 +397,7 @@ function drawMusic() { /////////////////////// /** * Update music info - * @param e + * @param {Object} e - Gadgetbridge musicinfo event */ function musicInfo(e) { info = e; @@ -410,7 +417,11 @@ function musicInfo(e) { } } -let tPxt, tIxt; +let tPxt, tIxt; // Timeouts to eXiT when Paused/Inactive for too long +/** + * Update music state + * @param {Object} e - Gadgetbridge musicstate event + */ function musicState(e) { stat = e.state; // if paused for five minutes, load the clock @@ -446,6 +457,7 @@ function musicState(e) { } } if (Bangle.isLCDOn()) { + drawMusic(false); // redraw in case we were fading out but resumed play drawControls(); } } @@ -473,11 +485,19 @@ function startButtonWatches() { tPress = setTimeout(() => {Bangle.showLauncher();}, 3000); } }, BTN2, {repeat: true, edge: "rising"}); - setWatch(() => { - nPress++; - clearTimeout(tPress); - tPress = setTimeout(handleButton2Press, 500); - }, BTN2, {repeat: true, edge: "falling"}); + const s = require("Storage").readJSON("gbmusic.json", 1) || {}; + if (s.simpleButton) { + setWatch(() => { + clearTimeout(tPress); + togglePlay(); + }, BTN2, {repeat: true, edge: "falling"}); + } else { + setWatch(() => { + nPress++; + clearTimeout(tPress); + tPress = setTimeout(handleButton2Press, 500); + }, BTN2, {repeat: true, edge: "falling"}); + } } function handleButton2Press() { tPress = null; @@ -500,7 +520,7 @@ function handleButton2Press() { let tCommand = {}; /** * Send command and highlight corresponding control - * @param command "play/pause/next/previous/volumeup/volumedown" + * @param {string} command - "play"/"pause"/"next"/"previous"/"volumeup"/"volumedown" */ function sendCommand(command) { Bluetooth.println(JSON.stringify({t: "music", n: command})); @@ -520,9 +540,8 @@ function togglePlay() { sendCommand(stat==="play" ? "pause" : "play"); } function startTouchWatches() { - if (!TCTL) {return;} Bangle.on("touch", side => { - if (TCTL<2 && !Bangle.isLCDOn()) {return;} + if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware switch(side) { case 1: sendCommand(stat==="play" ? "pause" : "previous"); @@ -535,7 +554,7 @@ function startTouchWatches() { } }); Bangle.on("swipe", dir => { - if (TCTL<2 && !Bangle.isLCDOn()) {return;} + if (!Bangle.isLCDOn()) {return;} // for <2v10 firmware sendCommand(dir===1 ? "previous" : "next"); }); } diff --git a/apps/gbmusic/settings.js b/apps/gbmusic/settings.js index d2dafb8f3..ae013fda5 100644 --- a/apps/gbmusic/settings.js +++ b/apps/gbmusic/settings.js @@ -5,12 +5,11 @@ const SETTINGS_FILE = "gbmusic.json", storage = require("Storage"), translate = require("locale").translate; - const TOUCH_OPTIONS = ["Off", "When LCD on", "Always"]; // initialize with default settings... let s = { autoStart: true, - touch: 1, + simpleButton: false, }; // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -19,24 +18,27 @@ s[key] = saved[key]; } - function save(key, value) { - s[key] = value; - storage.write(SETTINGS_FILE, s); + function save(key) { + return function (value) { + s[key] = value; + storage.write(SETTINGS_FILE, s); + } } + const yesNo = (v) => translate(v ? "Yes" : "No"); let menu = { "": {"title": "Music Control"}, }; menu[translate("< Back")] = back; menu[translate("Auto start")] = { - value: s.autoStart, - format: v => translate(v ? "Yes" : "No"), - onchange: v => {save("autoStart", v);}, + value: !!s.autoStart, + format: yesNo, + onchange: save("autoStart"), }; - menu[translate("Touch")] = { - value: s.touch|0, - format: v => translate(TOUCH_OPTIONS[(v+3)%3]), - onchange: v => {save("touch", (v+3)%3);}, + menu[translate("Simple button")] = { + value: !!s.simpleButton, + format: yesNo, + onchange: save("simpleButton"), }; E.showMenu(menu); diff --git a/apps/gbtwist/ChangeLog b/apps/gbtwist/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/gbtwist/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/gbtwist/README.md b/apps/gbtwist/README.md new file mode 100644 index 000000000..7e9dbcbe5 --- /dev/null +++ b/apps/gbtwist/README.md @@ -0,0 +1,15 @@ +# Gadgetbridge Twist Control + +Control your music app (e.g. MortPlayer Music [a folder based, not tag based player] ) that handles multiple play-commands (same as using a single-button-headset's button to change songs) on your Gadgetbridge-connected phone. +- Activate counting for 4 seconds with a twist (beeps at start and end of counting) +- twist multiple times for: + play/pause (1), + next song (2), + prev. song (3), + next folder (4), + prev. folder (5), + reset counter (6) +- the command to be sent is shown in green +- Volume up/down is controlled by BTN1/BTN3 presses + +![screenshot1](https://user-images.githubusercontent.com/84921310/119907374-65bb6180-bf50-11eb-9073-f29f7e333e00.jpg) diff --git a/apps/gbtwist/app-icon.js b/apps/gbtwist/app-icon.js new file mode 100644 index 000000000..b28bbe664 --- /dev/null +++ b/apps/gbtwist/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIYVhAFEjgFEh4FEg+AAocD4AME8ADCgPAvAFCj/8nkQAoN//8enAQB///44FBgYFB8f4FoIFB+IFBh/+n/4AocH/AXBj/+gP8FIIFDFwM//0x/wFDAIIFNv4FB/4FNEaIFFj/gn5HCj+AAoUEh4FBMgUP4AFDw/gv/wAoPDPoKhBjnxAoKtBjl4TYLICninBagUPWYLJPFoIADZIYABnj6KABIA=")) diff --git a/apps/gbtwist/app.js b/apps/gbtwist/app.js new file mode 100644 index 000000000..4bd495277 --- /dev/null +++ b/apps/gbtwist/app.js @@ -0,0 +1,97 @@ +// just a watch, to fill an empty screen + +function drwClock() { + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + var time = ("0"+h).substr(-2) + ":" + ("0"+m).substr(-2); + g.reset(); + g.setFont('6x8',7); + g.setFontAlign(-1,-1); + g.drawString(time,20,80); +} + +g.clear(); +drwClock(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +///////////////////////////////////////////////////////////// +// control music by twist/buttons + +var counter = 0; //stores your counted your twists +var tstate = false; //are you ready to count the twists? + +function playx() { + Bluetooth.println(JSON.stringify({t:"music", n:"play"})); +} + +function volup() { + Bluetooth.println(JSON.stringify({t:"music", n:"volumeup"})); +} + +function voldn() { + Bluetooth.println(JSON.stringify({t:"music", n:"volumedown"})); +} + +function sendCmd() { + print (counter); + Bangle.beep(200,3000); + if (tstate==false && counter>0){ + do {playx(); counter--;} + while (counter >= 1); + } +} + +function twistctrl() { + if (tstate==false){ + tstate=true; + setTimeout('tstate=false',4000); + setTimeout(sendCmd,4100); + Bangle.beep(200,3000); + } + else{ + g.clearRect(10,140,230,200); + if (tstate==true){ + if (counter < 5){ + counter++; + drwCmd(); + Bangle.buzz(100,2); + } + else { + counter = 0; + Bangle.buzz(400); + } + } + } +} + +function drwCmd() { + g.setFont('6x8',6); + g.setColor(0.3,1,0.3); + g.clearRect(10,140,230,200); +switch (counter){ + case 1: + g.drawString('play',50,150); + break; + case 2: + g.drawString('next',50,150); + break; + case 3: + g.drawString('prev',50,150); + break; + case 4: + g.drawString('nx f',50,150); + break; + case 5: + g.drawString('pr f',50,150); + break; + case 0: + g.clearRect(10,140,230,200); + break; +} +} + +setWatch(volup,BTN1,{repeat:true}); +setWatch(voldn,BTN3,{repeat:true}); +Bangle.on('twist',twistctrl); +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); \ No newline at end of file diff --git a/apps/gbtwist/app.png b/apps/gbtwist/app.png new file mode 100644 index 000000000..2379c76f0 Binary files /dev/null and b/apps/gbtwist/app.png differ diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index 1efe78c07..6cedf8f1b 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Use HRM data and calculations from Bangle.js (don't access hardware directly) 0.03: Fix timing issues, and use 1/2 scale to keep graph on screen +0.04: Update for new firmwares that have a 'HRM-raw' event diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index 1ec0a31d2..09e8a826e 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -4,18 +4,25 @@ Bangle.setHRMPower(1); var hrmInfo, hrmOffset = 0; var hrmInterval; function onHRM(h) { - // this is the first time we're called if (counter!==undefined) { + // the first time we're called remove + // the countdown counter = undefined; g.clear(); } hrmInfo = h; - hrmOffset = 0; + /* On 2v09 and earlier firmwares the only solution for realtime + HRM was to look at the 'raw' array that got reported. If you timed + it right you could grab the data pretty much as soon as it was written. + In new firmwares, '.raw' is not available. */ if (hrmInterval) clearInterval(hrmInterval); hrmInterval = undefined; - setTimeout(function() { - hrmInterval = setInterval(readHRM,41); - }, 40); + if (hrmInfo.raw) { + hrmOffset = 0; + setTimeout(function() { + hrmInterval = setInterval(readHRM,41); + }, 40); + } var px = g.getWidth()/2; g.setFontAlign(0,0); @@ -28,13 +35,32 @@ function onHRM(h) { g.drawString("BPM",px+15,45); } Bangle.on('HRM', onHRM); +/* On newer (2v10) firmwares we can subscribe to get +HRM events as they happen */ +Bangle.on('HRM-raw', function(v) { + var a = v.raw; + hrmOffset++; + if (hrmOffset>g.getWidth()) { + hrmOffset=0; + g.clearRect(0,90,239,239); + g.moveTo(-100,0); + } + + y = E.clip(170 - (v.raw*2),100,230); + g.setColor(1,1,1); + g.lineTo(hrmOffset, y); +}); // It takes 5 secs for us to get the first HRM event var counter = 5; function countDown() { - E.showMessage("Please wait...\n"+counter--); - if (counter) setTimeout(countDown, 1000); + if (counter) { + g.drawString(counter--,g.getWidth()/2,g.getHeight()/2, true); + setTimeout(countDown, 1000); + } } +g.clear().setFont("6x8",2).setFontAlign(0,0); +g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); countDown(); diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 7e7ea65ab..b56c9f6bb 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -2,3 +2,5 @@ 0.02: Only store relevant app data (saves RAM when many apps) 0.03: Allow scrolling to wrap around (fix #382) 0.04: Now displays widgets +0.05: Use g.theme for colours +0.06: Use Bangle.setUI for buttons diff --git a/apps/launch/app.js b/apps/launch/app.js index 9795d8901..ab1a89fc0 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -12,55 +12,53 @@ var menuScroll = 0; var menuShowing = false; function drawMenu() { - g.setFont("6x8",2); - g.setFontAlign(-1,0); - var n = 3; + g.reset().setFont("6x8",2).setFontAlign(-1,0); + var w = g.getWidth(); + var h = g.getHeight(); + var m = w/2; + var n = (h-48)/64; if (selected>=n+menuScroll) menuScroll = 1+selected-n; if (selectedn+menuScroll) ? -1 : 0); - g.fillPoly([120,233,106,219,134,219]); + g.setColor(menuScroll ? g.theme.fg : g.theme.bg); + g.fillPoly([m,6,m-14,20,m+14,20]); + g.setColor((apps.length>n+menuScroll) ? g.theme.fg : g.theme.bg); + g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]); // draw - g.setColor(-1); + g.setColor(g.theme.fg); for (var i=0;i=apps.length) selected = 0; - drawMenu(); -}, BTN3, {repeat:true}); -setWatch(function() { // run - if (!apps[selected].src) return; - if (require("Storage").read(apps[selected].src)===undefined) { - E.showMessage("App Source\nNot found"); - setTimeout(drawMenu, 2000); +Bangle.setUI("updown",dir=>{ + if (dir) { + selected += dir; + if (selected<0) selected = apps.length-1; + if (selected>=apps.length) selected = 0; + drawMenu(); } else { - E.showMessage("Loading..."); - load(apps[selected].src); + if (!apps[selected].src) return; + if (require("Storage").read(apps[selected].src)===undefined) { + E.showMessage("App Source\nNot found"); + setTimeout(drawMenu, 2000); + } else { + E.showMessage("Loading..."); + load(apps[selected].src); + } } -}, BTN2, {repeat:true,edge:"falling"}); +}); Bangle.loadWidgets(); Bangle.drawWidgets(); diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index c02e390b2..2f27f7f28 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -10,3 +10,4 @@ 0.10: Added GPS and Grid Ref clock faces 0.11: Updated Pedometer clock to retrieve steps from either wpedom or activepedom 0.12: Removed GPS and Grid Ref clock faces, superceded by GPS setup and Walkers Clock +0.13: Localised digi.js and timdat.js \ No newline at end of file diff --git a/apps/multiclock/digi.js b/apps/multiclock/digi.js index 4422e6b62..0b2ca4aaa 100644 --- a/apps/multiclock/digi.js +++ b/apps/multiclock/digi.js @@ -1,5 +1,7 @@ (() => { +var locale = require("locale"); + function getFace(){ var buf = Graphics.createArrayBuffer(240,92,1,{msb:true}); @@ -19,7 +21,7 @@ function getFace(){ buf.drawString(time,buf.getWidth()/2,0); buf.setFont("6x8",2); buf.setFontAlign(0,-1); - var date = d.toString().substr(0,15); + var date = locale.dow(d, 1) + " " + locale.date(d, 1); buf.drawString(date, buf.getWidth()/2, 70); flip(); } diff --git a/apps/multiclock/timdat.js b/apps/multiclock/timdat.js index ff1bdf000..a4a93a691 100644 --- a/apps/multiclock/timdat.js +++ b/apps/multiclock/timdat.js @@ -1,16 +1,16 @@ (() => { + var locale = require("locale"); + var dayFirst = ["en_GB", "en_IN", "en_NAV", "de_DE", "nl_NL", "fr_FR", "en_NZ", "en_AU", "de_AT", "en_IL", "es_ES", "fr_BE", "de_CH", "fr_CH", "it_CH", "it_IT", "tr_TR", "pt_BR", "cs_CZ", "pt_PT"]; + var withDot = ["de_DE", "nl_NL", "de_AT", "de_CH", "hu_HU", "cs_CZ", "sl_SI"]; + function getFace(){ var lastmin=-1; function drawClock(){ var d=Date(); if (d.getMinutes()==lastmin) return; - d=d.toString().split(' '); - var min=d[4].substr(3,2); - var sec=d[4].substr(-2); - var tm=d[4].substring(0,5); - var hr=d[4].substr(0,2); - lastmin=min; + var tm=d.toString().split(' ')[4].substring(0,5); + lastmin=d.getMinutes(); g.reset(); g.clearRect(0,24,239,239); var w=g.getWidth(); @@ -19,7 +19,16 @@ g.drawString(tm,4+(w-g.stringWidth(tm))/2,64); g.setFontVector(36); g.setColor(0x07ff); - var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3]; + var dt=locale.dow(d, 1) + " "; + if (dayFirst.includes(locale.name)) { + dt+=d.getDate(); + if (withDot.includes(locale.name)) { + dt+="."; + } + dt+=" " + locale.month(d, 1); + } else { + dt+=locale.month(d, 1) + " " + d.getDate(); + } g.drawString(dt,(w-g.stringWidth(dt))/2,160); g.flip(); } diff --git a/apps/s7clk/ChangeLog b/apps/s7clk/ChangeLog new file mode 100644 index 000000000..a08e25f2f --- /dev/null +++ b/apps/s7clk/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Tweaks for Q3 watch diff --git a/apps/s7clk/app.js b/apps/s7clk/app.js new file mode 100644 index 000000000..5bc2bff8f --- /dev/null +++ b/apps/s7clk/app.js @@ -0,0 +1,41 @@ +require("Font7x11Numeric7Seg").add(Graphics); + +function draw() { + var d = new Date(); + var size = Math.floor(g.getWidth()/(7*6)); + var x = (g.getWidth()/2) - size*6, + y = (g.getHeight()/2) - size*7; + g.reset().clearRect(0,y,g.getWidth(),y+size*12+8); + g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1); + g.drawString(d.getHours(), x, y); + g.setFontAlign(-1,-1); + if (d.getSeconds()&1) g.drawString(":", x,y); + g.drawString(("0"+d.getMinutes()).substr(-2),x+size*4,y); + // draw seconds + g.setFont("7x11Numeric7Seg",size/2); + g.drawString(("0"+d.getSeconds()).substr(-2),x+size*18,y + size*7); + // date + var s = d.toString().split(" ").slice(0,4).join(" "); + g.setFont("6x8").setFontAlign(0,-1); + g.drawString(s,g.getWidth()/2, y + size*12); +} + +// Only update when display turns on +if (process.env.BOARD!="SMAQ3") // hack for Q3 which is always-on +Bangle.on('lcdPower', function(on) { + if (secondInterval) + clearInterval(secondInterval); + secondInterval = undefined; + if (on) + secondInterval = setInterval(draw, 1000); + draw(); +}); + +g.clear(); +var secondInterval = setInterval(draw, 1000); +draw(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); diff --git a/apps/s7clk/icon.js b/apps/s7clk/icon.js new file mode 100644 index 000000000..d5d9aaf68 --- /dev/null +++ b/apps/s7clk/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mUygP/AC5BlH4MAn/gAwN/4EP/AFBsEMhkBwEAjEDgYJBgEGgHA4EYDwOAmEwBIIYyj/wgf+AoMH/kA/4eBJXwYLVxgAjh//AC3w")) diff --git a/apps/s7clk/icon.png b/apps/s7clk/icon.png new file mode 100644 index 000000000..cb08aec5e Binary files /dev/null and b/apps/s7clk/icon.png differ diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 4c677f066..00d11d562 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -27,3 +27,5 @@ 0.22: Move HID to BLE menu 0.23: Change max time offset to 13 for NZ summer daylight time (NZDT) 0.24: Add Quiet Mode settings +0.25: Move boot.js code into 'boot' app itself +0.26: Use Bangle.softOff if available as this keeps the time diff --git a/apps/setting/boot.js b/apps/setting/boot.js deleted file mode 100644 index e61bed9ad..000000000 --- a/apps/setting/boot.js +++ /dev/null @@ -1,15 +0,0 @@ -(() => { - var settings = require('Storage').readJSON('setting.json', true); - if (!settings) return; - if (settings.options) Bangle.setOptions(settings.options); - if (settings.quiet && settings.qmOptions) Bangle.setOptions(settings.qmOptions); - if (settings.quiet && settings.qmBrightness) { - if (settings.qmBrightness!=1) Bangle.setLCDBrightness(settings.qmBrightness); - } else { - if (settings.brightness && settings.brightness!=1) Bangle.setLCDBrightness(settings.brightness); - } - if (settings.quiet && settings.qmTimeout) Bangle.setLCDTimeout(s.qmTimeout); - if (settings.passkey!==undefined && settings.passkey.length==6) NRF.setSecurity({passkey:settings.passkey, mitm:1, display:1}); - if (settings.whitelist) NRF.on('connect', function(addr) { if (!settings.whitelist.includes(addr)) NRF.disconnect(); }); - delete settings; -})() diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 159575619..12448d463 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -115,7 +115,7 @@ function showMainMenu() { 'Set Time': ()=>showSetTimeMenu(), 'LCD': ()=>showLCDMenu(), 'Reset Settings': ()=>showResetMenu(), - 'Turn Off': ()=>Bangle.off(), + 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, '< Back': ()=>load() }; return E.showMenu(mainmenu); diff --git a/apps/sweepclock/ChangeLog b/apps/sweepclock/ChangeLog index d5cf3753c..23841b299 100644 --- a/apps/sweepclock/ChangeLog +++ b/apps/sweepclock/ChangeLog @@ -1,2 +1,4 @@ 0.01: Initial Release 0.02: Added Colour Themes +0.03: Added Date +0.04: Memory Footprint reduction diff --git a/apps/sweepclock/README.md b/apps/sweepclock/README.md index 8ddb7decb..34be0c42c 100644 --- a/apps/sweepclock/README.md +++ b/apps/sweepclock/README.md @@ -12,7 +12,7 @@ Use Button 1 (the top right button) to change the numeral type | Default clock face | Roman Numeral Font | No Digits | | ---- | ---- | ---- | -| ![](./numeral-01.jpg) | ![](numeral-02.jpg) | ![](numeral-03.jpg) | +| ![](numeral-01.jpg) | ![](numeral-02.jpg) | ![](numeral-03.jpg) | @@ -21,9 +21,14 @@ Button 3 (bottom right button) is used to change the colour | Red | Grey | Purple | | ---- | ---- | ---- | -| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) | +| ![](color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) | +### Button 4 +Button 4 (bottom left of the screen) is used to change the date position. Note after cycling through the date positions there is the no date option. +| Top Right | Bottom Right | Bottom Left | Top Left | +| ---- | ---- | ---- | ---- | +| ![](date-01.jpg) | ![](date-02.jpg) | ![](date-03.jpg) | ![](date-04.jpg) | ## Further Details @@ -31,8 +36,8 @@ For further details of design and working please visit [The Project Page](https: ## Requests -Reach out to adrian@adriankirk.com if you have feature requests or notice bugs. +Please reach out to adrian@adriankirk.com if you have feature requests or notice bugs. ## Creator -Made by [Adrian Kirk](mailto:adrian@adriankirk.com) +Made by [Adrian Kirk](mailto:adrian@adriankirk.com) \ No newline at end of file diff --git a/apps/sweepclock/date-01.jpg b/apps/sweepclock/date-01.jpg new file mode 100644 index 000000000..79d12e75b Binary files /dev/null and b/apps/sweepclock/date-01.jpg differ diff --git a/apps/sweepclock/date-02.jpg b/apps/sweepclock/date-02.jpg new file mode 100644 index 000000000..14a64980c Binary files /dev/null and b/apps/sweepclock/date-02.jpg differ diff --git a/apps/sweepclock/date-03.jpg b/apps/sweepclock/date-03.jpg new file mode 100644 index 000000000..e30a01932 Binary files /dev/null and b/apps/sweepclock/date-03.jpg differ diff --git a/apps/sweepclock/date-04.jpg b/apps/sweepclock/date-04.jpg new file mode 100644 index 000000000..6e65a148f Binary files /dev/null and b/apps/sweepclock/date-04.jpg differ diff --git a/apps/sweepclock/sweepclock.js b/apps/sweepclock/sweepclock.js index 9c53efa55..777bafd8b 100644 --- a/apps/sweepclock/sweepclock.js +++ b/apps/sweepclock/sweepclock.js @@ -1,79 +1,74 @@ /** -* Adrian Kirk 2021-03 -* Simple Clock showing 1 numeral for the hour -* with a smooth sweep second. -*/ + * Adrian Kirk 2021-03 + * Simple Clock showing 1 numeral for the hour + * with a smooth sweep second. + */ const screen_center_x = g.getWidth()/2; const screen_center_y = 10 + g.getHeight()/2; +const TWO_PI = 2*Math.PI; require("FontCopasetic40x58Numeric").add(Graphics); const color_schemes = [ - { - name: "black", - background : [0.0,0.0,0.0], - second_hand: [1.0,0.0,0.0], - minute_hand: [1.0,1.0,1.0], - hour_hand: [1.0,1.0,1.0], - numeral:[1.0,1.0,1.0] - }, - { - name: "red", - background : [1.0,0.0,0.0], - second_hand: [1.0,1.0,0.0], - minute_hand: [1.0,1.0,1.0], - hour_hand: [1.0,1.0,1.0], - numeral:[1.0,1.0,1.0] - }, - { - name: "grey", - background : [0.5,0.5,0.5], - second_hand: [0.0,0.0,0.0], - minute_hand: [1.0,1.0,1.0], - hour_hand: [1.0,1.0,1.0], - numeral:[1.0,1.0,1.0] - }, - { - name: "purple", - background : [1.0,0.0,1.0], - second_hand: [1.0,1.0,0.0], - minute_hand: [1.0,1.0,1.0], - hour_hand: [1.0,1.0,1.0], - numeral:[1.0,1.0,1.0] - }, - { - name: "blue", - background : [0.4,0.7,1.0], - second_hand: [0.5,0.5,0.5], - minute_hand: [1.0,1.0,1.0], - hour_hand: [1.0,1.0,1.0], - numeral:[1.0,1.0,1.0] - } - ]; + { + 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], + second_hand: [1.0,1.0,0.0], + }, + { + name: "blue", + background : [0.4,0.7,1.0], + second_hand: [0.5,0.5,0.5], + } +]; let color_scheme_index = 0; +const WHITE = [1.0,1.0,1.0]; +function default_white(color){ + if(color == null){ + return WHITE; + } else { + return color; + } +} + 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. - */ + * 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){} } class ThinHand extends Hand { /** - * The thin hand is created from a simple line, so its easy and fast - * to draw. - */ + * The thin hand is created from a simple line, so its easy and fast + * to draw. + */ constructor(centerX, - centerY, - length, - tolerance, - draw_test, - color_theme){ + centerY, + length, + tolerance, + draw_test, + color_theme){ super(); this.centerX = centerX; this.centerY = centerY; @@ -98,17 +93,17 @@ class ThinHand extends Hand { // first test to see of the angle called is beyond the tolerance // for a redraw if(Math.abs(angle - this.angle) > this.tolerance || - // and then call the predicate to see if a redraw is needed - this.draw_test(this.angle,this.last_draw_time) ){ + // and then call the predicate to see if a redraw is needed + this.draw_test(this.angle,this.last_draw_time) ){ // rub out the old hand line - background = color_schemes[color_scheme_index].background; + var background = color_schemes[color_scheme_index].background; g.setColor(background[0],background[1],background[2]); g.drawLine(this.centerX, this.centerY, this.last_x, this.last_y); // Now draw the new hand line - hand_color = color_schemes[color_scheme_index][this.color_theme]; + var hand_color = default_white(color_schemes[color_scheme_index][this.color_theme]); g.setColor(hand_color[0],hand_color[1],hand_color[2]); - x2 = this.centerX + this.length*Math.sin(angle); - y2 = this.centerY - this.length*Math.cos(angle); + var x2 = this.centerX + this.length*Math.sin(angle); + var y2 = this.centerY - this.length*Math.cos(angle); g.drawLine(this.centerX, this.centerY, x2, y2); // and store the last draw details for the next call this.last_x = x2; @@ -124,17 +119,17 @@ class ThinHand extends Hand { class ThickHand extends Hand { /** - * The thick hand is created from a filled polygone, so its slower to - * draw so to be used sparingly with few redraws - */ + * The thick hand is created from a filled polygone, so its slower to + * draw so to be used sparingly with few redraws + */ constructor(centerX, - centerY, - length, - tolerance, - draw_test, - color_theme, - base_height, - thickness){ + centerY, + length, + tolerance, + draw_test, + color_theme, + base_height, + thickness){ super(); this.centerX = centerX; this.centerY = centerY; @@ -170,38 +165,38 @@ class ThickHand extends Hand { // method to move the hand to a new angle moveTo(angle){ if(Math.abs(angle - this.angle) > this.tolerance || this.draw_test(this.angle - this.delta_base,this.angle + this.delta_base ,this.last_draw_time) ){ - background = color_schemes[color_scheme_index].background; + var background = color_schemes[color_scheme_index].background; g.setColor(background[0],background[1],background[2]); g.fillPoly([this.last_x1, - this.last_y1, - this.last_x2, - this.last_y2, - this.last_x3, - this.last_y3, - this.last_x4, - this.last_y4 - ]); + this.last_y1, + this.last_x2, + this.last_y2, + this.last_x3, + this.last_y3, + this.last_x4, + this.last_y4 + ]); // bottom left - x1 = this.centerX + - this.vertex_radius_base*Math.sin(angle - this.delta_base); - y1 = this.centerY - this.vertex_radius_base*Math.cos(angle - this.delta_base); + var x1 = this.centerX + + this.vertex_radius_base*Math.sin(angle - this.delta_base); + var y1 = this.centerY - this.vertex_radius_base*Math.cos(angle - this.delta_base); // bottom right - x2 = this.centerX + - this.vertex_radius_base*Math.sin(angle + this.delta_base); - y2 = this.centerY - this.vertex_radius_base*Math.cos(angle + this.delta_base); + var x2 = this.centerX + + this.vertex_radius_base*Math.sin(angle + this.delta_base); + var y2 = this.centerY - this.vertex_radius_base*Math.cos(angle + this.delta_base); // top right - x3 = this.centerX + this.vertex_radius_top*Math.sin(angle + this.delta_top); - y3 = this.centerY - this.vertex_radius_top*Math.cos(angle + this.delta_top); + var x3 = this.centerX + this.vertex_radius_top*Math.sin(angle + this.delta_top); + var y3 = this.centerY - this.vertex_radius_top*Math.cos(angle + this.delta_top); // top left - x4 = this.centerX + this.vertex_radius_top*Math.sin(angle - this.delta_top); - y4 = this.centerY - this.vertex_radius_top*Math.cos(angle - this.delta_top); - hand_color = color_schemes[color_scheme_index][this.color_theme]; + var x4 = this.centerX + this.vertex_radius_top*Math.sin(angle - this.delta_top); + var y4 = this.centerY - this.vertex_radius_top*Math.cos(angle - this.delta_top); + var hand_color = default_white(color_schemes[color_scheme_index][this.color_theme]); g.setColor(hand_color[0],hand_color[1],hand_color[2]); g.fillPoly([x1,y1, - x2,y2, - x3,y3, - x4,y4 - ]); + x2,y2, + x3,y3, + x4,y4 + ]); this.last_x1 = x1; this.last_y1 = y1; this.last_x2 = x2; @@ -221,93 +216,155 @@ class ThickHand extends Hand { // The force draw is set to true to force all objects to redraw themselves let force_redraw = false; // 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, - "second_hand"); +let seconds_hand = new ThinHand(screen_center_x, + screen_center_y, + 95, + 0, + (angle, last_draw_time) => false, + "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 let minutes_hand_redraw = function(angle, last_draw_time){ return force_redraw || (seconds_hand.angle > angle && - Math.abs(seconds_hand.angle - angle) <2*Math.PI/25 && - new Date().getTime() - last_draw_time.getTime() > 500); + Math.abs(seconds_hand.angle - angle) 500); }; -let minutes_hand = new ThinHand(screen_center_x, - screen_center_y, - 80, - 2*Math.PI/250, - minutes_hand_redraw, - "minute_hand"); +let minutes_hand = new ThinHand(screen_center_x, + screen_center_y, + 80, + TWO_PI/250, + minutes_hand_redraw, + "minute_hand" +); // The hour hand is a thick hand so we have to redraw when the minute hand // overlaps from its behind andle 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); + 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, - 2*Math.PI/600, - hour_hand_redraw, - "hour_hand", - 5, - 4); +let hours_hand = new ThickHand(screen_center_x, + screen_center_y, + 40, + TWO_PI/600, + hour_hand_redraw, + "hour_hand", + 5, + 4); function draw_clock(){ - date = new Date(); + var date = new Date(); draw_background(); draw_hour_digit(date); draw_seconds(date); draw_mins(date); draw_hours(date); + draw_date(date); force_redraw = false; } + +var local = require('locale'); +var last_date = null; +var last_datestr = null; +var last_coords = null; +const date_coords = [ + { name: "topright", coords:[180,30]}, + { name: "bottomright", coords:[180,220]}, + { name: "bottomleft", coords: [5,220]}, + { name: "topleft", coords:[5,30]}, + { name: "offscreen", coords: [240,30]} +]; + +var date_coord_index = 0; + +function draw_date(date){ + if(force_redraw || last_date == null || last_date.getDate() != date.getDate()){ + //console.log("redrawing date"); + g.setFontAlign(-1,-1,0); + g.setFont("Vector",15); + if(last_coords != null && last_datestr != null) { + var background = color_schemes[color_scheme_index].background; + g.setColor(background[0], background[1], background[2]); + g.drawString(last_datestr, last_coords[0], last_coords[1]); + } + var coords = date_coords[date_coord_index].coords; + if(coords != null) { + var date_format = local.dow(date,1) + " " + date.getDate(); + var numeral_color = default_white(color_schemes[color_scheme_index].numeral); + g.setColor(numeral_color[0], numeral_color[1], numeral_color[2]); + g.drawString(date_format, coords[0], coords[1]); + last_date = date; + last_datestr = date_format; + last_coords = coords; + } + } +} + +function next_datecoords() { + date_coord_index = date_coord_index + 1; + if (date_coord_index >= date_coords.length) { + date_coord_index = 0; + } + //console.log("date coord index->" + date_coord_index); + force_redraw = true; +} + +function set_datecoords(date_name){ + console.log("setting date:" + date_name); + for (var i=0; i < date_coords.length; i++) { + if(date_coords[i].name == date_name){ + date_coord_index = i; + force_redraw = true; + console.log("date match"); + break; + } + } +} + // drawing the second the millisecond as we need the fine gradation // for the sweep second hand. function draw_seconds(date){ - seconds = date.getSeconds() + date.getMilliseconds()/1000; - seconds_frac = seconds / 60; - seconds_angle = 2*Math.PI*seconds_frac; + 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){ - mins = date.getMinutes() + date.getSeconds()/60 + date.getMilliseconds()/(60*1000); - mins_frac = mins / 60; - mins_angle = 2*Math.PI*mins_frac; - redraw = minutes_hand.moveTo(mins_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){ - hours = (date.getHours() % 12) + date.getMinutes()/60 + date.getSeconds()/3600; - hours_frac = hours / 12; - hours_angle = 2*Math.PI*hours_frac; - redraw = hours_hand.moveTo(hours_angle); + 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"); } } /** -* We want to be able to change the font so we set up -* pure virtual for all fonts implementtions to use -*/ + * 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. - */ + * 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 characters that are going to be returned for * the hour. */ hour_txt(hour){ return ""; } @@ -329,27 +386,36 @@ class NoFont extends NumeralFont{ getName(){return "NoFont";} } +const COPASET_DIM_20x58 = [20,58]; +const COPASET_DIM_30x58 = [30,58]; +const COPASET_DIM_40x58 = [40,58]; +const COPASET_DIM_50x58 = [50,58]; + class CopasetFont extends NumeralFont{ constructor(){ super(); - // dimesion map provides the dimesions of the character for - // each number for plotting and collision detection - this.dimension_map = { - 1 : [20,58], - 2 : [30,58], - 3 : [30,58], - 4 : [30,58], - 5 : [30,58], - 6 : [40,58], - 7 : [30,58], - 8 : [40,58], - 9 : [40,58], - 10: [50,58], - 11: [40,58], - 12: [40,58] - }; } - getDimensions(hour){return this.dimension_map[hour];} + getDimensions(hour){ + switch(hour){ + case 1: return COPASET_DIM_20x58; + case 2: + case 3: + case 4: + case 5: + case 7: + return COPASET_DIM_30x58; + case 6: + case 8: + case 9: + case 11: + case 12: + return COPASET_DIM_40x58; + case 10: + return COPASET_DIM_50x58; + default: + return COPASET_DIM_30x58; + } + } hour_txt(hour){ return hour.toString(); } draw(hour_txt,x,y){ /* going to leave this in here for future testing. @@ -363,60 +429,78 @@ class CopasetFont extends NumeralFont{ x,y+dim[1] ]); g.setColor(1.0,1.0,1.0);*/ + g.setFontAlign(-1,-1,0); g.setFontCopasetic40x58Numeric(); g.drawString(hour_txt,x,y); } getName(){return "Copaset";} } - +const ROMAN_DIM_10x40 = [10,40]; +const ROMAN_DIM_20x40 = [20,40]; +const ROMAN_DIM_25x40 = [25,40]; +const ROMAN_DIM_30x40 = [30,40]; +const ROMAN_DIM_40x40 = [40,40]; +const ROMAN_DIM_60x40 = [60,40]; +const ROMAN_DIM_70x40 = [70,40]; class RomanNumeralFont extends NumeralFont{ constructor(){ super(); - // text map provides the mapping between hour and roman numeral - this.txt_map = { - 1 : 'I', - 2 : 'II', - 3 : 'III', - 4 : 'IV', - 5 : 'V', - 6 : 'VI', - 7 : 'VII', - 8 : 'VIII', - 9 : 'IX', - 10: 'X', - 11: 'XI', - 12: 'XII' - }; - // dimesion map provides the dimesions of the characters for - // each hour for plotting and collision detection - this.dimension_map = { - 1 : [10,40], - 2 : [25,40], - 3 : [40,40], - 4 : [40,40], - 5 : [30,40], - 6 : [40,40], - 7 : [60,40], - 8 : [70,40], - 9 : [40,40], - 10: [20,40], - 11: [40,40], - 12: [60,40] - }; } - getDimensions(hour){ return this.dimension_map[hour];} - hour_txt(hour){ return this.txt_map[hour]; } + 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 1: + return ROMAN_DIM_10x40; + case 2: + return ROMAN_DIM_25x40; + case 3: + case 4: + case 6: + case 9: + case 11: + case 12: + return ROMAN_DIM_40x40; + case 5: + return ROMAN_DIM_30x40; + case 7: + return ROMAN_DIM_60x40; + case 8: + return ROMAN_DIM_70x40; + case 10: + return ROMAN_DIM_20x40; + default: + return ROMAN_DIM_40x40; + } + } + hour_txt(hour){ return this.getText(hour); } draw(hour_txt,x,y){ + g.setFontAlign(-1,-1,0); g.setFont("Vector",40); g.drawString(hour_txt,x,y); } getName(){return "Roman";} } -// The problem with the trig inverse functions on +// 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 +// 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){ @@ -426,15 +510,15 @@ function reifyasin(x,y,asin_angle){ } else if(x < 0 && y < 0){ return Math.PI - asin_angle; } else { - return 2*Math.PI + asin_angle; + return TWO_PI + asin_angle; } } -// rebase and angle so be between -pi and pi +// rebase and angle so be between -pi and pi // rather than 0 to 2PI function rebaseNegative(angle){ if(angle > Math.PI){ - return angle - 2*Math.PI; + return angle - TWO_PI; } else { return angle; } @@ -444,17 +528,17 @@ function rebaseNegative(angle){ // rather than -pi to pi function rebasePositive(angle){ if(angle < 0){ - return angle + 2*Math.PI; + 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. -*/ + * 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){ this.radius = radius; @@ -471,58 +555,58 @@ class HourScriber { this.numeral_font = numeral_font; } drawHour(hours){ - changed = false; + var changed = false; if(this.curr_hours != hours || this.curr_numeral_font !=this.numeral_font){ - background = color_schemes[color_scheme_index].background; + var background = color_schemes[color_scheme_index].background; 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); + this.curr_hour_x, + this.curr_hour_y); //console.log("erasing old hour"); - hours_frac = hours / 12; - angle = 2*Math.PI*hours_frac; - dimensions = this.numeral_font.getDimensions(hours); + var hours_frac = hours / 12; + var angle = TWO_PI*hours_frac; + var dimensions = this.numeral_font.getDimensions(hours); // we set the radial coord to be in the middle // of the drawn text. - width = dimensions[0]; - height = dimensions[1]; - delta_center_x = this.radius*Math.sin(angle) - width/2; - delta_center_y = this.radius*Math.cos(angle) + height/2; + 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(hours); - // now work out the angle of the beginning and the end of the + // now work out the angle of the beginning and the end of the // text box so we know when to redraw // bottom left angle - x1 = delta_center_x; - y1 = delta_center_y; - r1 = Math.sqrt(x1*x1 + y1*y1); - angle1 = reifyasin(x1,y1,Math.asin(x1/r1)); + 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 - x2 = delta_center_x; - y2 = delta_center_y - height; - r2 = Math.sqrt(x2*x2 + y2*y2); - angle2 = reifyasin(x2,y2,Math.asin(x2/r2)); + 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 - x3 = delta_center_x + width; - y3 = delta_center_y; - r3 = Math.sqrt(x3*x3 + y3*y3); - angle3 = reifyasin(x3,y3, Math.asin(x3/r3)); + 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 - x4 = delta_center_x + width; - y4 = delta_center_y - height; - r4 = Math.sqrt(x4*x4 + y4*y4); - angle4 = reifyasin(x4,y4,Math.asin(x4/r4)); + 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) ); + 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); + this.angle_to = Math.max(angle1,angle2,angle3,angle4); } //console.log(angle1 + "/" + angle2 + " / " + angle3 + " / " + angle4); //console.log( this.angle_from + " to " + this.angle_to); @@ -531,8 +615,8 @@ class HourScriber { changed = true; } if(changed || - this.draw_test(this.angle_from, this.angle_to, this.last_draw_time) ){ - numeral_color = color_schemes[color_scheme_index].numeral; + this.draw_test(this.angle_from, this.angle_to, this.last_draw_time) ){ + var numeral_color = default_white(color_schemes[color_scheme_index].numeral); 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(); @@ -544,49 +628,49 @@ class HourScriber { let numeral_fonts = [new CopasetFont(), new RomanNumeralFont(), new NoFont()]; let numeral_fonts_index = 0; /** -* predicate for deciding when the digit has to be redrawn -*/ -let hour_numeral_redraw = function(angle_from, angle_to, last_draw_time){ - seconds_hand_angle = seconds_hand.angle; - // we have to cope with the 12 problem where the + * 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 > 1.5*Math.PI){ - angle_from = angle_from - 2*Math.PI; + angle_from = angle_from - TWO_PI; if(seconds_hand_angle > Math.PI) - seconds_hand_angle = seconds_hand_angle - 2*Math.PI; - } + seconds_hand_angle = seconds_hand_angle - TWO_PI; + } //console.log("initial:" + angle_from + "/" + angle_to + " seconds " + seconds_hand_angle); - redraw = force_redraw || - (seconds_hand_angle >= angle_from && seconds_hand_angle <= angle_to) || - (minutes_hand.last_draw_time.getTime() > last_draw_time.getTime()); + var redraw = force_redraw || + (seconds_hand_angle >= angle_from && seconds_hand_angle <= angle_to) || + (minutes_hand.last_draw_time.getTime() > last_draw_time.getTime()); if(redraw){ - //console.log(angle_from + "/" + angle_to + " seconds " + seconds_hand_angle); + //console.log(angle_from + "/" + angle_to + " seconds " + seconds_hand_angle); } return redraw; }; let hour_scriber = new HourScriber(70, - numeral_fonts[numeral_fonts_index], - hour_numeral_redraw - ); + numeral_fonts[numeral_fonts_index], + hour_numeral_redraw +); /** -* Called from button 1 to change the numerals that are -* displayed on the clock face -*/ + * Called from button 1 to change the numerals that are + * displayed on the clock face + */ function next_font(){ numeral_fonts_index = numeral_fonts_index + 1; if(numeral_fonts_index >= numeral_fonts.length){ numeral_fonts_index = 0; } hour_scriber.setNumeralFont( - numeral_fonts[numeral_fonts_index]); + numeral_fonts[numeral_fonts_index]); force_redraw = true; } function draw_hour_digit(date){ - hours = date.getHours() % 12; - mins = date.getMinutes(); + var hours = date.getHours() % 12; + var mins = date.getMinutes(); if(mins > 30){ hours = (hours +1) % 12; } @@ -598,13 +682,13 @@ function draw_hour_digit(date){ function draw_background(){ if(force_redraw){ - background = color_schemes[color_scheme_index].background; + var 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 - ]); + 0,240, + 240,240, + 240,25 + ]); } } @@ -616,32 +700,32 @@ function next_colorscheme(){ } /** -* called from load_settings on startup to -* set the color scheme to named value -*/ + * 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"); + console.log("color scheme match"); break; } } } /** -* called from load_settings on startup -* to set the font to named value -*/ + * 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"); + console.log("font match"); hour_scriber.setNumeralFont(numeral_fonts[numeral_fonts_index]); break; } @@ -649,11 +733,11 @@ function set_font(font_name){ } /** -* Called on startup to set the watch to the last preference settings -*/ + * Called on startup to set the watch to the last preference settings + */ function load_settings(){ try{ - settings = require("Storage").readJSON("sweepclock.settings.json"); + var settings = require("Storage").readJSON("sweepclock.settings.json"); if(settings != null){ console.log("loaded:" + JSON.stringify(settings)); if(settings.color_scheme != null){ @@ -662,6 +746,9 @@ function load_settings(){ if(settings.font != null){ set_font(settings.font); } + if(settings.date != null){ + set_datecoords(settings.date); + } } else { console.log("no settings to load"); } @@ -670,16 +757,24 @@ function load_settings(){ } } +function print_memoryusage(){ + var m = process.memory(); + var pc = Math.round(m.usage*100/m.total); + console.log("memory usage: " + pc + "%"); +} + /** -* Called on button press to save down the last preference settings -*/ + * Called on button press to save down the last preference settings + */ function save_settings(){ - settings = { + var settings = { font : numeral_fonts[numeral_fonts_index].getName(), color_scheme : color_schemes[color_scheme_index].name, + date: date_coords[date_coord_index].name }; console.log("saving:" + JSON.stringify(settings)); require("Storage").writeJSON("sweepclock.settings.json",settings); + print_memoryusage(); } // Boiler plate code for setting up the clock, @@ -708,14 +803,12 @@ function scheduleDrawClock(){ } function reset_clock(){ - g.clear(); force_redraw = true; } Bangle.on('lcdPower', (on) => { if (on) { console.log("lcdPower: on"); - Bangle.drawWidgets(); reset_clock(); startTimers(); } else { @@ -744,7 +837,7 @@ startTimers(); setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"}); function button1pressed(){ - next_font(); + next_font(); save_settings(); } @@ -753,9 +846,17 @@ function button3pressed(){ save_settings(); } +function button4pressed(){ + //console.log("button 4 pressed"); + next_datecoords(); + save_settings(); +} + // Handle button 1 being pressed setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"}); // Handle button 3 being pressed setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"}); +// Handle button 3 being pressed +setWatch(button4pressed, BTN4,{repeat:true,edge:"falling"}); diff --git a/apps/welcome/ChangeLog b/apps/welcome/ChangeLog index 9545dbbfa..ce9194c5d 100644 --- a/apps/welcome/ChangeLog +++ b/apps/welcome/ChangeLog @@ -11,3 +11,4 @@ 0.09: Allow welcome to run after a fresh install More useful app menu BTN2 now goes to menu on release +0.10: Tweaks to reduce memory usage diff --git a/apps/welcome/app.js b/apps/welcome/app.js index 8cbdc2efa..565e87d5d 100644 --- a/apps/welcome/app.js +++ b/apps/welcome/app.js @@ -11,7 +11,7 @@ function animate(seq,period) { // Fade in to FG color with angled lines function fade(callback) { var n = 0; - function f() { + function f() {"ram" for (var i=n;i<240;i+=10) { g.drawLine(i,0,0,i); g.drawLine(i,240,240,i); @@ -24,16 +24,17 @@ function fade(callback) { f(); } - -var scenes = [ - function() { +var SCENE_COUNT=11; +function getScene(n) { + if (n==0) return function() { g.clear(1); g.setFont("4x6",2); var n=0; + var l = Bangle.getLogo(); var i = setInterval(function() { n+=0.04; g.setColor(n,n,n); - g.drawImage(Bangle.getLogo(),(240-222)/2,(240-100)/2); + g.drawImage(l,(240-222)/2,(240-100)/2); if (n>=1) { clearInterval(i); setTimeout(()=>g.drawString("Open",34,144), 500); @@ -41,7 +42,8 @@ var scenes = [ setTimeout(()=>g.drawString("Smart Watch",34,168), 1500); } },50); - },function() { + }; + if (n==1) return function() { var img = require("heatshrink").decompress(atob("ptRxH+qYAfvl70mj5gAC0ekvd8FkAAdz3HJAYAH4+eJXWkJJYAF0hK2vfNJaIAB5t7S3fN5/V6wAD6vOTg9SumXy2W3QAB3eXul2JdnO63XAApPEVYvAJQIACJoRQDzBLoJQ3W5/NIwr4GJohMFAAROgJYvVJQiPGABZNN3bsdvYyESwnWJSIAC3RNM3V1JjZAES4nVJSYAB4xMNJrbkE56WD5xLVdB5NbFofNJbgABJh26qREPrFXrlbAAWjFgfWJgRLaTQhMLy5KNJINhsJLDrYrD5xLC6pLa5nGTR7oLq9bJQJMKTAXWJbbnR3RLJSoRMHv4pC5rkec6SaIrBLGw2r2XW1epcoqYeJiOXJYziEsOH2RBBw7lF56Yg5nGc6FScZOGJQPX2TmDFIfVTEBMSc4hLEw5KB6+rsJMH63X6pMf5hMQzBLCq5LD1ZLEJhTlfJiWXTA2GJYpMIcwPNc2O6TAuGRIPX1igDJg/PJmyYDcgXWwxMH1ApC53XcsHAJiVYcg2HJYZME0YpC5vWJkhLNJgLlDTAeFJhF/FQfVJkG6JiGXcomyJgOrJYhMErYqD53NJj7lRzBMDcoeGJhzoBJb3GJiN1qZBCJgWyJYpNF1LigAAXAJiNSJgzlGJgt/JkZLRy9TJgeHJhznFcuSZGw5MHJomjcuhLBqdcJiSaiTChMV1CYxy5LCqdXIAWy6+rJhCalTCN2JgdYH4WHJiGpTF7kDc43W2RMJTUZLQzBLFc4mr6+GJh2jTFmXJYyaEwuyc5Sag4xLZTQmG2WFJhxNaJYZMLJZSaEJoOHTR9/Ja+6JbdTqRNETRRNF1JLV4BLcAANYI5ToK1BLYJhWYJZwABq5NoJZ91JaAABdAZNS0ZLey9SJaRNYv5KM426JZmXuxKUJrKcL0lTzBLKzBKYJrVXvfGSol7EYWXJI27zF1JLQADq5NUrgYB4wAEEIV0comXI7wAFrCcPJgYWBTIIAETIN2JYmWuhMkdSdYCgOeJgueqRLFyzhfTi9bq4TC45MF49TuuXJlpONcogAC0hKB0gHDvZMEqRMpAANSq9crlbJAYADqwRDxGk0mIA4eCTQOeveXJdYAHqxNFdAeIAAQGCrOI0oHEAGVXTRJMGvgGCwRM7TAZMHwQGCvhM1rBMERIhMGAwdZJmtSqVTwNcwJEDJg19cvIADa4d9JhANDJnSLHJgrl6AAhFFAwpZDegjn7vhMGcvwABrJAFJgjl/TQpBBI4jl/AAN8TQhHDcv4ADcJBMDvpM+IYaeDAAhL+qd9SgycEJn7iEAA18Jf7nEcv4AIrJLIcv6aMcv4ADvhMHrJJ/AAbl/c6ZM/AAt9cv7nSIv7nLcv4AHrLl/TRpJBvgnjA==")); g.reset(); g.setColor("#6633ff"); @@ -73,7 +75,8 @@ var scenes = [ },20); },3500); - },function() { + }; + if (n==2) return function() { g.reset(); g.setBgColor("#ffa800");g.clear(); g.setFont("6x8",2); @@ -88,8 +91,8 @@ var scenes = [ ()=>g.drawString("2",200,120), ()=>g.drawString("3",200,200) ],200); - }, - function() { + }; + if (n==3) return function() { g.reset(); g.setBgColor("#00a8ff");g.clear(); g.setFontAlign(0,0); @@ -98,8 +101,8 @@ var scenes = [ g.setFontAlign(-1,-1); g.setFont("6x8",2); g.drawString("Move up\nin menus\n\nTurn Bangle.js on\nif it was off", 20,40); - }, - function() { + }; + if (n==4) return function() { g.reset(); g.setBgColor("#00a8ff");g.clear(); g.setFontAlign(0,0); @@ -108,8 +111,8 @@ var scenes = [ g.setFontAlign(-1,-1); g.setFont("6x8",2); g.drawString("Select menu\nitem\n\nLaunch app\nwhen watch\nis showing", 20,70); - }, - function() { + }; + if (n==5) return function() { g.reset(); g.setBgColor("#00a8ff");g.clear(); g.setFontAlign(0,0); @@ -118,8 +121,8 @@ var scenes = [ g.setFontAlign(-1,-1); g.setFont("6x8",2); g.drawString("Move down\nin menus\n\nLong press\nto exit app\nand go back\nto clock", 20,100); - }, - function() { + }; + if (n==6) return function() { g.reset(); g.setBgColor("#ff3300");g.clear(); g.setFontAlign(0,0); @@ -129,8 +132,8 @@ var scenes = [ g.setFontAlign(-1,-1); g.setFont("6x8",2); g.drawString("If Bangle.js\never stops,\nhold buttons\n1 and 2 for\naround six\nseconds.\n\n\n\nBangle.js will\nthen reboot.", 20,20); - }, - function() { + }; + if (n==7) return function() { g.reset(); g.setBgColor("#00a8ff");g.clear(); g.setFont("6x8",2); @@ -147,8 +150,8 @@ var scenes = [ g.drawString("work too. Try now",x,y+=h); g.drawString("to change page.",x,y+=h);} ],300); - }, - function() { + }; + if (n==8) return function() { g.reset(); g.setBgColor("#339900");g.clear(); g.setFont("6x8",2); @@ -165,8 +168,8 @@ var scenes = [ g.drawString("with a Bluetooth",x,y+=h); g.drawString("capable device",x,y+=h);}, ],400); - }, - function() { + }; + if (n==9) return function() { g.reset(); g.setBgColor("#990066");g.clear(); g.setFont("6x8",2); @@ -227,8 +230,8 @@ var scenes = [ } setInterval(draw,50); - }, - function() { + }; + if (n==10) return function() { g.reset(); g.setBgColor("#660099");g.clear(); g.setFontAlign(0,0); @@ -245,18 +248,18 @@ var scenes = [ g.drawString("Bangle.js",x,y+=h);} ],400); } -]; +} var sceneNumber = 0; function move(dir) { - if (dir>0 && sceneNumber+1 == scenes.length) return; // at the end - sceneNumber = (sceneNumber+dir)%scenes.length; + if (dir>0 && sceneNumber+1 == SCENE_COUNT) return; // at the end + sceneNumber = (sceneNumber+dir)%SCENE_COUNT; if (sceneNumber<0) sceneNumber=0; clearInterval(); - scenes[sceneNumber](); + getScene(sceneNumber)(); if (sceneNumber>1) { - var l = scenes.length; + var l = SCENE_COUNT; for (var i=0;imove(1), BTN3, {repeat:true}); setWatch(()=>{ // If we're on the last page - if (sceneNumber == scenes.length-1) { + if (sceneNumber == SCENE_COUNT-1) { load(); } }, BTN2, {repeat:true,edge:"falling"}); diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog index b9d50ab8b..128cee034 100644 --- a/apps/widbat/ChangeLog +++ b/apps/widbat/ChangeLog @@ -2,3 +2,4 @@ 0.03: Tweaks for variable size widget system 0.04: Ensure redrawing works with variable size widget system 0.05: Fix regression stopping correct widget updates +0.06: Use 'g.theme' (requires bootloader 0.23) diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index bca3ae046..95fad1b20 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -7,16 +7,16 @@ function draw() { var s = 39; var x = this.x, y = this.y; + g.reset(); if (Bangle.isCharging()) { g.setColor(CHARGING).drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); x+=16; } - g.setColor(-1); + g.setColor(g.theme.fg); g.fillRect(x,y+2,x+s-4,y+21); g.clearRect(x+2,y+4,x+s-6,y+19); g.fillRect(x+s-3,y+10,x+s,y+14); g.setColor(CHARGING).fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); - g.setColor(-1); } Bangle.on('charging',function(charging) { if(charging) Bangle.buzz(); diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index 2236ee50d..0dac82e76 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,13 +1,11 @@ (function(){ - var img_bt = E.toArrayBuffer(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA==")); - function draw() { g.reset(); if (NRF.getSecurityStatus().connected) g.setColor(0,0.5,1); else g.setColor(0.3,0.3,0.3); - g.drawImage(img_bt,10+this.x,2+this.y); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),10+this.x,2+this.y); } function changed() { WIDGETS["bluetooth"].draw(); diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index e3d85afca..19be2abaf 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -1,7 +1,6 @@ (function(){ if (!Bangle.isGPSOn) return; // old firmware - var img = E.toArrayBuffer(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA==")); - + function draw() { g.reset(); if (Bangle.isGPSOn()) { @@ -9,7 +8,7 @@ } else { g.setColor(0.3,0.3,0.3); // off = grey } - g.drawImage(img, 10+this.x, 2+this.y); + g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), 10+this.x, 2+this.y); } var timerInterval; diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js index 16cec0b87..8ac76def8 100644 --- a/apps/widhrt/widget.js +++ b/apps/widhrt/widget.js @@ -1,7 +1,6 @@ (function(){ if (!Bangle.isHRMOn) return; // old firmware - var img = E.toArrayBuffer(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA=")); - + function draw() { g.reset(); if (Bangle.isHRMOn()) { @@ -9,7 +8,7 @@ } else { g.setColor(0.3,0.3,0.3); // off = grey } - g.drawImage(img, 10+this.x, 2+this.y); + g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y); } var timerInterval; diff --git a/apps/widhwt/widget.js b/apps/widhwt/widget.js index 6affdea52..d178a5b5d 100644 --- a/apps/widhwt/widget.js +++ b/apps/widhwt/widget.js @@ -1,10 +1,9 @@ /* jshint esversion: 6 */ (() => { - var icon = require("heatshrink").decompress(atob("jEYwIKHgwCBhwCBh4CEggPCkACBmAXDBwVZ+EB+F4gEsjl8EgMP+EChk/gEMh+ehkA+YIBxwxBnF/4HggH/wEAj0AA==")); var color = 0x4A69; function draw() { - g.reset().setColor(color).drawImage(icon, this.x + 1, 0); + g.reset().setColor(color).drawImage(require("heatshrink").decompress(atob("jEYwIKHgwCBhwCBh4CEggPCkACBmAXDBwVZ+EB+F4gEsjl8EgMP+EChk/gEMh+ehkA+YIBxwxBnF/4HggH/wEAj0AA==")), this.x + 1, 0); } WIDGETS["widhwt"] = { area: "tr", width: 26, draw: draw }; @@ -20,4 +19,4 @@ }, 35E3); }); -})(); \ No newline at end of file +})(); diff --git a/apps/widid/widget.js b/apps/widid/widget.js index e97eecb65..68917c65a 100644 --- a/apps/widid/widget.js +++ b/apps/widid/widget.js @@ -1,9 +1,7 @@ /* jshint esversion: 6 */ (() => { - var id = NRF.getAddress().substr().substr(12).split(":"); - - // draw your widget at xpos function draw() { + var id = NRF.getAddress().substr().substr(12).split(":"); g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); g.drawString(id[0], this.x+2, this.y+4, true); g.drawString(id[1], this.x+2, this.y+14, true); diff --git a/apps/widlock/ChangeLog b/apps/widlock/ChangeLog new file mode 100644 index 000000000..b4d1ae593 --- /dev/null +++ b/apps/widlock/ChangeLog @@ -0,0 +1 @@ +0.01: First commit diff --git a/apps/widlock/widget.js b/apps/widlock/widget.js new file mode 100644 index 000000000..b710de8c6 --- /dev/null +++ b/apps/widlock/widget.js @@ -0,0 +1,10 @@ +(function(){ + Bangle.on('lcdPower', function(on) { + WIDGETS["lock"].width = Bangle.isLCDOn()?0:16; + Bangle.drawWidgets(); + }); + WIDGETS["lock"]={area:"tl",width:Bangle.isLCDOn()?0:16,draw:function(w) { + if (!Bangle.isLCDOn()) + g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x, w.y); + }}; +})() diff --git a/apps/widlock/widget.png b/apps/widlock/widget.png new file mode 100644 index 000000000..e0eaa4aa9 Binary files /dev/null and b/apps/widlock/widget.png differ diff --git a/apps/widpedom/ChangeLog b/apps/widpedom/ChangeLog index ea146c34f..ba198f889 100644 --- a/apps/widpedom/ChangeLog +++ b/apps/widpedom/ChangeLog @@ -9,3 +9,4 @@ 0.10: Fix daily goal, don't store settings in separate file 0.11: added getSteps() method for apps to retrieve step count 0.12: Respect Quiet Mode +0.13: Now use system color theme diff --git a/apps/widpedom/widget.js b/apps/widpedom/widget.js index e8797f571..e73475526 100644 --- a/apps/widpedom/widget.js +++ b/apps/widpedom/widget.js @@ -4,11 +4,6 @@ 'goal': 10000, 'progress': false, } - const COLORS = { - 'white': -1, - 'progress': 0x001F, // Blue - 'done': 0x03E0, // DarkGreen - } const TAU = Math.PI*2; let lastUpdate = new Date(); let stp_today = 0; @@ -27,7 +22,7 @@ function drawProgress(stps) { const width = 24, half = width/2; const goal = setting('goal'), left = Math.max(goal-stps,0); - const c = left ? COLORS.progress : COLORS.done; + const c = left ? "#00f" : "#090"; // blue or dark green g.setColor(c).fillCircle(this.x + half, this.y + half, half); if (left) { const f = left/goal; // fraction to blank out @@ -47,7 +42,7 @@ p[i - 2] += this.x; p[i - 1] += this.y; } - g.setColor(0).fillPoly(p); + g.setColor(g.theme.bg).fillPoly(p); } } @@ -58,10 +53,9 @@ stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters } let stps = stp_today.toString(); - g.reset(); - g.clearRect(this.x, this.y, this.x + width, this.y + 23); // erase background + g.reset().clearRect(this.x, this.y, this.x + width, this.y + 23); // erase background if (setting('progress')){ drawProgress.call(this, stps); } - g.setColor(COLORS.white); + g.setColor(g.theme.fg); if (stps.length > 3){ stps = stps.slice(0,-3) + "," + stps.slice(-3); g.setFont("4x6", 1); // if big, shrink text to fix diff --git a/apps/widtbat/widget.js b/apps/widtbat/widget.js index 8cc4b0c83..6d5aded8b 100644 --- a/apps/widtbat/widget.js +++ b/apps/widtbat/widget.js @@ -1,11 +1,10 @@ /* jshint esversion: 6 */ (() => { const CBS = 0x41f, CBC = 0x07E0; - var batS = require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")); var xo = 6, xl = 22, yo = 9, h = 17; function draw() { - g.reset().setColor(CBS).drawImage(batS, this.x + 1, this.y + 4); + g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); var cbc = (Bangle.isCharging()) ? CBC : CBS; g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); diff --git a/apps/widviz/widget.js b/apps/widviz/widget.js index 4282d4c96..241dabf61 100644 --- a/apps/widviz/widget.js +++ b/apps/widviz/widget.js @@ -1,34 +1,33 @@ (() => { var saved = null; - + function hide(){ if (!Bangle.isLCDOn() || saved) return; saved = []; for (var wd of WIDGETS) { - saved.push(wd.draw); + saved.push(wd.draw); wd.draw=()=>{}; } g.setColor(0,0,0); g.fillRect(0,0,239,23); } - + function reveal(){ if (!Bangle.isLCDOn() || !saved) return; for (var wd of WIDGETS) wd.draw = saved.shift(); - Bangle.drawWidgets(); + Bangle.drawWidgets(); saved=null; } - + function draw(){ - var img = E.toArrayBuffer(atob("GBgBAAAAAAAAAAAAAAAAAH4AAf+AB4HgDgBwHDw4OH4cMOcMYMMGYMMGMOcMOH4cHDw4DgBwB4HgAf+AAH4AAAAAAAAAAAAAAAAA")); g.setColor(0x07ff); - g.drawImage(img,this.x,this.y); + g.drawImage(atob("GBgBAAAAAAAAAAAAAAAAAH4AAf+AB4HgDgBwHDw4OH4cMOcMYMMGYMMGMOcMOH4cHDw4DgBwB4HgAf+AAH4AAAAAAAAAAAAAAAAA"),this.x,this.y); } - + WIDGETS["viz"] ={area:"tl", width:24,draw:draw}; Bangle.on('swipe',(dir)=>{ if (dir<0) hide(); else reveal(); - }); + }); })(); diff --git a/core b/core index 1b1293a5e..3f2ff467f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 1b1293a5eb9b8bb9e4f743c4599f0587f597d368 +Subproject commit 3f2ff467f22b746da94160e59ff89b621601b261