Merge remote-tracking branch 'upstream/master'
46
apps.json
|
@ -16,7 +16,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.36",
|
||||
"version": "0.37",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
@ -119,7 +119,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.33",
|
||||
"version": "0.34",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
@ -269,6 +269,20 @@
|
|||
],
|
||||
"data": [{"name":"gbridge.json"}]
|
||||
},
|
||||
{ "id": "gbdebug",
|
||||
"name": "Gadgetbridge Debug",
|
||||
"shortName":"GB Debug",
|
||||
"version":"0.01",
|
||||
"description": "Debug info for Gadgetbridge. Run this app and when Gadgetbridge messages arrive they are displayed on-screen.",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gbdebug.app.js","url":"app.js"},
|
||||
{"name":"gbdebug.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mclock",
|
||||
"name": "Morphing Clock",
|
||||
|
@ -1914,7 +1928,7 @@
|
|||
"id": "openstmap",
|
||||
"name": "OpenStreetMap",
|
||||
"shortName": "OpenStMap",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoors,gps",
|
||||
|
@ -3797,10 +3811,11 @@
|
|||
"id": "qmsched",
|
||||
"name": "Quiet Mode Schedule and Widget",
|
||||
"shortName": "Quiet Mode",
|
||||
"version": "0.03",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||
"version": "0.04",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times, and change LCD options while Quiet Mode is active.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}],
|
||||
"screenshots": [{"url":"screenshot_b1_main.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_lcd.png"},
|
||||
{"url":"screenshot_b2_main.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_lcd.png"}],
|
||||
"tags": "tool,widget",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
@ -4641,5 +4656,24 @@
|
|||
{"name":"pebble.settings.js","url":"pebble.settings.js"},
|
||||
{"name":"pebble.img","url":"pebble.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "pooqroman",
|
||||
"name": "pooq Roman watch face",
|
||||
"shortName":"pooq Roman",
|
||||
"version":"0.0.0",
|
||||
"description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"pooqroman.app.js","url":"app.js"},
|
||||
{"name":"pooqroman.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"pooqroman.json"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -40,3 +40,4 @@
|
|||
0.35: Add Bangle.appRect polyfill
|
||||
Don't set beep vibration up on Bangle.js 2 (built in)
|
||||
0.36: Add comments to .boot0 to make debugging a bit easier
|
||||
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
||||
|
|
|
@ -78,13 +78,7 @@ boot += `E.on('errorFlag', function(errorFlags) {
|
|||
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.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,26 @@
|
|||
# Gadgetbridge Debug
|
||||
|
||||
This is useful if your Bangle isn't responding to the Gadgetbridge
|
||||
Android app properly.
|
||||
|
||||
This app disables all existing Gadgetbridge handlers and then displays the
|
||||
messages that come from Gadgetbridge on the screen
|
||||
of the watch. It also saves the last 10 messages in a variable
|
||||
called `history`.
|
||||
|
||||
More info on Gadgetbridge at http://www.espruino.com/Gadgetbridge
|
||||
|
||||
## Usage
|
||||
|
||||
* Run the `GB Debug` app on your Bangle
|
||||
* Connect your Bangle to Gadgetbridge
|
||||
* Do whatever was causing you problems (eg receiving a call)
|
||||
* The Gadgetbridge message should now be displayed on-screen
|
||||
|
||||
If you want to get the *actual* data rather than copying it from the screen.
|
||||
|
||||
* Ensure the `GB Debug` app is kept running after the above steps
|
||||
* Disconnect Gadgetbridge from the Bangle
|
||||
* Connect the Web IDE on your PC
|
||||
* Type `show()` on the left-hand side of the IDE and the
|
||||
last 10 messages from Gadgetbridge will be shown.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4cBzsE/4AClMywH680rlOW9N9kmSpICnyBBBgQRMkBUDgIRKoBoGGRYAFHBGARpARHT5MJKxQAFLgzELCIlIBQkSCIsEPRKBHCIYbGoIRFiQRJhJgFCISeEBwMQOQykCCIqlBpMEBIgRHOQYRIYQbPDhAbBNwgRJVwOCTIgRFMAJKDgQRGOQprBCIMSGogHBJwwbBkC2FCJNbUgMNwHYBYPJCIhODju0yFNCIUGCJGCoE2NwO24EAmw1FHgWCpMGgQOBBIMwCJGSpMmyAjDCI6eBCIWAhu2I4IRCUIYREk+Ah3brEB2CzFAAIRCl3b23btsNCJckjoRC1h2CyAREtoNC9oDC2isCCIgHBjdt5MtCJj2CowjD2uyCIOSCI83lu123tAQIRI4EB28/++39/0mwRCoARCgbfByU51/3rev+mWCIQwCPok0EYIRB/gRDpJ+EcYQRJkARQdgq/Bl5HE7IRDZAltwAREyXbCIbIFgEfCIXsBwQCDQAYRNLgvfCIXtCI44Dm3JCIUlYoYCGkrjBk9bxMkyy9CChICFA="))
|
|
@ -0,0 +1,21 @@
|
|||
E.showMessage("Waiting for message");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var history = [];
|
||||
|
||||
GB = function(e) {
|
||||
if (history.length > 10)
|
||||
history = history.slice(history.length-10);
|
||||
history.push(e);
|
||||
|
||||
var s = JSON.stringify(e,null,2);
|
||||
|
||||
g.reset().clear(Bangle.appRect);
|
||||
g.setFont("6x8").setFontAlign(-1,0);
|
||||
g.drawString(s, 10, g.getHeight()/2);
|
||||
};
|
||||
|
||||
function show() {
|
||||
print(JSON.stringify(history,null,2));
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -7,3 +7,4 @@
|
|||
0.07: Move to 96px tiles - less files (64 -> 25) and speed up rendering
|
||||
0.08: Update for drag event refactor
|
||||
0.09: Use current theme cols when drawing GPS info
|
||||
0.10: Improve scale factor calculation to fix scaling issues (#984)
|
||||
|
|
|
@ -63,10 +63,17 @@ TODO:
|
|||
/* Can see possible tiles on http://leaflet-extras.github.io/leaflet-providers/preview/
|
||||
However some don't allow cross-origin use */
|
||||
var TILELAYER = 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'; // simple, high contrast
|
||||
//var TILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var PREVIEWTILELAYER = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
//var TILELAYER = 'http://a.tile.stamen.com/toner/{z}/{x}/{y}.png'; // black and white
|
||||
|
||||
var map = L.map('map').locate({setView: true, maxZoom: 16});
|
||||
var tileLayer = L.tileLayer(TILELAYER, {
|
||||
// Tiles used for Bangle.js itself
|
||||
var bangleTileLayer = L.tileLayer(TILELAYER, {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
});
|
||||
// Tiles used for the may the user sees (faster)
|
||||
var previewTileLayer = L.tileLayer(PREVIEWTILELAYER, {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map data © <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
|
||||
});
|
||||
|
@ -83,7 +90,7 @@ TODO:
|
|||
}
|
||||
|
||||
var mapFiles = [];
|
||||
tileLayer.addTo(map);
|
||||
previewTileLayer.addTo(map);
|
||||
|
||||
function tilesLoaded(ctx, width, height) {
|
||||
var options = {
|
||||
|
@ -122,16 +129,35 @@ TODO:
|
|||
}
|
||||
|
||||
document.getElementById("getmap").addEventListener("click", function() {
|
||||
var bounds = map.getBounds();
|
||||
var zoom = map.getZoom();
|
||||
var centerlatlon = bounds.getCenter();
|
||||
var center = map.project(centerlatlon, zoom).divideBy(256);
|
||||
var centerlatlon = map.getBounds().getCenter();
|
||||
var center = map.project(centerlatlon, zoom).divideBy(OSMTILESIZE);
|
||||
var ox = Math.round((center.x - Math.floor(center.x)) * OSMTILESIZE);
|
||||
var oy = Math.round((center.y - Math.floor(center.y)) * OSMTILESIZE);
|
||||
center = center.floor();
|
||||
center = center.floor(); // make sure we're in the middle of a tile
|
||||
// JS version of Bangle.js's projection
|
||||
function bproject(lat, lon) {
|
||||
const degToRad = Math.PI / 180; // degree to radian conversion
|
||||
const latMax = 85.0511287798; // clip latitude to sane values
|
||||
const R = 6378137; // earth radius in m
|
||||
if (lat > latMax) lat=latMax;
|
||||
if (lat < -latMax) lat=-latMax;
|
||||
var s = Math.sin(lat * degToRad);
|
||||
return new L.Point(
|
||||
(R * lon * degToRad),
|
||||
(R * Math.log((1 + s) / (1 - s)) / 2)
|
||||
);
|
||||
}
|
||||
// Work out scale factors (how much from Bangle.project does one pixel equate to?)
|
||||
var pc = map.unproject(center.multiplyBy(OSMTILESIZE), zoom);
|
||||
var pd = map.unproject(center.multiplyBy(OSMTILESIZE).add({x:1,y:0}), zoom);
|
||||
var bc = bproject(pc.lat, pc.lng)
|
||||
var bd = bproject(pd.lat, pd.lng)
|
||||
var scale = bc.distanceTo(bd);
|
||||
|
||||
var tileGetters = [];
|
||||
|
||||
// Render everything to a canvas - 512 x 512 px
|
||||
// Render everything to a canvas...
|
||||
var canvas = document.getElementById("maptiles");
|
||||
canvas.style.display="";
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
@ -150,7 +176,8 @@ TODO:
|
|||
resolve();
|
||||
};
|
||||
}));
|
||||
img.src = tileLayer.getTileUrl(coords);
|
||||
bangleTileLayer._tileZoom = previewTileLayer._tileZoom;
|
||||
img.src = bangleTileLayer.getTileUrl(coords);
|
||||
})(i,j);
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +190,7 @@ TODO:
|
|||
imgx : canvas.width,
|
||||
imgy : canvas.height,
|
||||
tilesize : TILESIZE,
|
||||
scale : 10000*Math.pow(2,16-zoom), // FIXME - this is probably wrong
|
||||
scale : scale, // how much of Bangle.project(latlon) does one pixel equate to?
|
||||
lat : centerlatlon.lat,
|
||||
lon : centerlatlon.lng
|
||||
})});
|
||||
|
|
|
@ -34,8 +34,8 @@ exports.draw = function() {
|
|||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
var p = Bangle.project({lat:m.lat,lon:m.lon});
|
||||
var ix = (p.x-map.center.x)*4096/map.scale + (map.imgx/2) - cx;
|
||||
var iy = (map.center.y-p.y)*4096/map.scale + (map.imgy/2) - cy;
|
||||
var ix = (p.x-map.center.x)/map.scale + (map.imgx/2) - cx;
|
||||
var iy = (map.center.y-p.y)/map.scale + (map.imgy/2) - cy;
|
||||
//console.log(ix,iy);
|
||||
var tx = 0|(ix/map.tilesize);
|
||||
var ty = 0|(iy/map.tilesize);
|
||||
|
@ -57,8 +57,8 @@ exports.latLonToXY = function(lat, lon) {
|
|||
var cx = g.getWidth()/2;
|
||||
var cy = g.getHeight()/2;
|
||||
return {
|
||||
x : (q.x-p.x)*4096/map.scale + cx,
|
||||
y : cy - (q.y-p.y)*4096/map.scale
|
||||
x : (q.x-p.x)/map.scale + cx,
|
||||
y : cy - (q.y-p.y)/map.scale
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -66,6 +66,6 @@ exports.latLonToXY = function(lat, lon) {
|
|||
exports.scroll = function(x,y) {
|
||||
var a = Bangle.project({lat:this.lat,lon:this.lon});
|
||||
var b = Bangle.project({lat:this.lat+1,lon:this.lon+1});
|
||||
this.lon += x * this.map.scale / ((a.x-b.x) * 4096);
|
||||
this.lat -= y * this.map.scale / ((a.y-b.y) * 4096);
|
||||
this.lon += x * this.map.scale / (a.x-b.x);
|
||||
this.lat -= y * this.map.scale / (a.y-b.y);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# pooq Roman: a classic watch face with amusing dynamicity
|
||||
|
||||
This is a normal watch face for telling the time.
|
||||
It is unusual in that it supports the 24 hour clock by dynamically updating the labels on the face
|
||||
(so, if you enable 24 hour mode, you will get to see a hand pointing to XXIII o'clock each evening).
|
||||
|
||||
The date and day of the week can also be displayed, and they choose their own spelling depending on the available screen space. It's fun!
|
||||
|
||||
## Options
|
||||
|
||||
Because sometimes I don't want to burn what I'm cooking and other times I'm lazy and just want to know if it's afternoon yet,
|
||||
you can alter the number of hands on the display. When the watch is unlocked, slide up to add minute and second hands, or down to remove the distraction.
|
||||
There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky, in case you want
|
||||
the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
|
||||
|
||||
Although we genrally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face.
|
||||
You can also override the system 12/24 hour setting just for this face here, since it's, well, a rather different experience than with numeric displays.
|
||||
|
||||
One other thing: there's some integration with system timers and alarms; they will show as small pips at the appropriate places
|
||||
in the day around the display. When they come within an hour, the pips turn to crosses relating to the minute hand, and the minute
|
||||
hand turns itself on. When timers are mere seconds away, the display changes again and the second hand activates itself, so you
|
||||
can watch as your doom approaches.
|
||||
|
||||
## Limitations
|
||||
|
||||
Since this is intended as a design exercise, it does not and will probably never support the Bangle's standard widgets.
|
||||
Sorry about that, but control of all the pixels was just too important to me.
|
||||
|
||||
There's also no support for internationalisation at present. This irks me, but... well, talk to me about it if there's a language you'd like.
|
||||
|
||||
## The future
|
||||
|
||||
The design is begging for integration with host-device calendars, and proper time zone/DST support. We'll see what the future holds.
|
||||
|
||||
## Feedback
|
||||
|
||||
[I'd be happy to hear your feedback](https://www.github.com/stephenPspackman) if you have comments or find any bugs, or (most especially)
|
||||
if you find this work interesting.
|
||||
|
||||
## By
|
||||
|
||||
Made by [Stephen P Spackman](https://www.github.com/stephenPspackman).
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBiIAWiEAgIpKEwgrFgAaBgIcBAAwREC4oVBBoQoCAQoXJBogXqI653DC6SnEC9RHXX/6/kSgIAGU5wAICQhfGACAX/C/4AOXIIX/C/4X/C/4XUgEBF6wYHI6AYGL6MACIgXRCIISDR6QYEU6YYDX6gYCAAKxHDB4XTDAYXUL6oAgA=="))
|
|
@ -0,0 +1,761 @@
|
|||
// pooqRoman
|
||||
//
|
||||
// Copyright (c) 2021 Stephen P Spackman
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// This only works for Bangle 2.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* System integration */
|
||||
|
||||
const storage = require('Storage');
|
||||
|
||||
const settings = storage.readJSON("setting.json", true) || {};
|
||||
|
||||
const alarms = storage.readJSON('alarm.json', true) || [];
|
||||
|
||||
/*
|
||||
{ on : true,
|
||||
hr : 6.5, // hours + minutes/60
|
||||
msg : "Eat chocolate",
|
||||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||
rp : true, // repeat
|
||||
as : false, // auto snooze
|
||||
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
|
||||
}
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Face-specific options */
|
||||
|
||||
class Options {
|
||||
// Protocol: subclasses must have static id and defaults fields.
|
||||
// Only fields named in the defaults will be saved.
|
||||
constructor() {
|
||||
this.id = this.constructor.id;
|
||||
this.file = `${this.id}.json`;
|
||||
this.backing = storage.readJSON(this.file, true) || {};
|
||||
this.defaults = this.constructor.defaults;
|
||||
Object.keys(this.defaults).forEach(k => this.bless(k));
|
||||
}
|
||||
|
||||
writeBack(delay) {
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(
|
||||
() => {
|
||||
this.timeout = null;
|
||||
storage.writeJSON(this.file, this.backing);
|
||||
},
|
||||
delay
|
||||
);
|
||||
}
|
||||
|
||||
bless(k) {
|
||||
Object.defineProperty(this, k, {
|
||||
get: () => this.backing[k] == null ? this.defaults[k] : this.backing[k],
|
||||
set: v => {
|
||||
this.backing[k] = v;
|
||||
// Ten second writeback delay, since the user will roll values up and down.
|
||||
this.writeBack(10000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showMenu(m) {
|
||||
if (m) {
|
||||
for (const k in m) if ('init' in m[k]) m[k].value = m[k].init();
|
||||
m[''].selected = -1; // Workaround for self-selection bug.
|
||||
}
|
||||
E.showMenu(m);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.backing = {};
|
||||
this.writeBack(0);
|
||||
}
|
||||
|
||||
interact() {this.showMenu(this.menu);}
|
||||
}
|
||||
|
||||
class RomanOptions extends Options {
|
||||
constructor() {
|
||||
super();
|
||||
this.menu = {
|
||||
'': {title: '* face options *'},
|
||||
'< Back': _ => {this.showMenu(); this.emit('done');},
|
||||
Ticks: {
|
||||
init: _ => this.resolution,
|
||||
min: 0, max: 3,
|
||||
onchange: x => this.resolution = x,
|
||||
format: x => ['seconds', 'seconds (up)', 'minutes', 'hours'][x]
|
||||
},
|
||||
'Display': {
|
||||
init: _ => this.o24h == null ? 0 : 1 + this.o24h,
|
||||
min: 0, max: 2,
|
||||
onchange: x => this.o24h = [null, 0, 1][x],
|
||||
format: x => ['system', '12h', '24h'][x]
|
||||
},
|
||||
'Day of Week': {
|
||||
init: _ => this.dow,
|
||||
onchange: x => this.dow = x
|
||||
},
|
||||
Calendar: {
|
||||
init: _ => this.calendric,
|
||||
min: 0, max: 2,
|
||||
onchange: x => this.calendric = x,
|
||||
format: x => ['none', 'day', 'date'][x]
|
||||
},
|
||||
Defaults: _ => {this.reset();}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
RomanOptions.id = 'pooqroman';
|
||||
|
||||
RomanOptions.defaults = {
|
||||
resolution: 1,
|
||||
dow: true,
|
||||
calendric: 2,
|
||||
o24h: !settings["12hour"],
|
||||
bg: g.theme.bg,
|
||||
fg: g.theme.fg,
|
||||
barBg: g.theme.fg,
|
||||
barFg: g.theme.bg,
|
||||
hourFg: g.theme.fg,
|
||||
minuteFg: g.theme.fg,
|
||||
secondFg: g.theme.fg2,
|
||||
rectFg: g.theme.fg,
|
||||
hubFg: g.theme.fg,
|
||||
alarmFg: '#f00',
|
||||
timerFg: '#0f0',
|
||||
active: g.theme.fg2,
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Assets (generated by resourcer.js, in this directory) */
|
||||
|
||||
const heatshrink = require('heatshrink');
|
||||
const dec = x => E.toString(heatshrink.decompress(atob(x)));
|
||||
const romanPartsF = [
|
||||
dec(
|
||||
'wEBsEB3//7//9//+0AjUAguAg3AgYQJjfAgv+gH/8Fg/0gh/AgP4gf2h/j/+BCAP' +
|
||||
'wgFggEggEQgEMgEHwEDEIIyDuED3kD7+H9vn2k/hEPgMP4Xevd+j4QB7kA9kAmkA' +
|
||||
'hUGgOH8Hn3le4+GgH32PuvfGj+CCAMDgXD4dz+evt9DgcL7fXn87h8NCAMP+Ef/0' +
|
||||
'eg+egPugF2j0bCAPAh3wh88h8P/8BNwI'
|
||||
), 97, dec('gUDgUGgUJgYFBhsBhMJhgA=='), 17
|
||||
];const fontF = [
|
||||
dec(
|
||||
'AAUwAIM/4F/8HguHAmABBAoIJBBoIUBkEwsEw//wAIIdDBoUQBoIfC+HB+Hj2F/m' +
|
||||
'E+CIXAoHEsHMuHcmH8mHuuHH8GBGIUAwEBwEHwH/wH5+EBAIILCCAP8oH8EYXMmA' +
|
||||
'BB5wjCgYjCAYMP8E+uF8mHsCIWHCIgCBAIXw4fw54tBgBsBGgUAnKLC99w40wAII' +
|
||||
'FBBIINBCIM8gF+iHnmHDuHD8HnDYMAjizEMYJJBn+A+OAAYIHBBYKjDXYKvDYZYP' +
|
||||
'D40AAIYMBZYgkC4Hg4DnDuH/8H/BYIVCv/wnEAjwBCAoIJBEIYRFh0Ag8AgPAEYQ' +
|
||||
'RCJIJNBfYRXKnFAvlg9ihE8dwsfgkLFHMYgJF8DNCh+AUYWAA4ILBAAJGB/4PB+D' +
|
||||
'9CgADCEoIPCJobbBB4IBBAoJdDEgXggvwhuwAIcH8EDRIh/BhkwAIMOuAPCMYQDB' +
|
||||
'A4ILBCIcGsECoAPLU4oPDH42ggeAB4XEg/mh1zhkzh03g/+h/4J4nwg0AhjbDRII' +
|
||||
'vCt/wAIIVFAoKTBCYIXBDIYHHEIYVFGJJxHSI8P/8H/6hLF44BBM4IABg8gh6NEh' +
|
||||
'vwgngBoITBv/Av7PBV4kAsArCfYIVBuEABYNwA4I3BD4cPL4UAM4IXBBYQfC4kP8' +
|
||||
'0AucAmcAu8PXogA='
|
||||
), 32, dec('gINMgUAhMHhIAGCQ0KAQIKBgwEBgcIBAQVEhIJBhAeIBQIADAoUDEQULBQcHg4FD' +
|
||||
'CII='), 16
|
||||
];
|
||||
const lockI = dec('iMSwMAgfwgf8geHgeB4PA8HguFwnH//9//+4gPf//v//3gE7//9//+8EHCAO///A');
|
||||
const batteryI = dec('h8SwMAgPggfAv/4//x//j//H/+P/8f/0//gOOA==');
|
||||
const chargeI = dec('h0MwIEBkEBwEMgFwgeAj/w/+AjkA8EDgEYgFAA==');
|
||||
const GPSI = dec('iUQwMAhEAgsAgUggFEgEKvEBn0Aj+AgfgglygsJosgxNGiNIgWJ4FBEoM4gA');
|
||||
const HRMI = dec('iMRwMAnken8fzfd7v+/3/v9/38/z+b5tiiM3/eP/+D/+AAIM/wEPwEDwEAAIIA==');
|
||||
const compassI = dec('iMRwMAgfgg/8g8ng0Q40ImcOjcHg+DwfB4Ph2Hw7FsolmkUxwEwuFwj/wEIMAA==');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Squeezable strings */
|
||||
|
||||
class Formattable {
|
||||
width(g) {return this.w != null ? this.w : (this.w = g.stringWidth(this.text));}
|
||||
print(g, x, y) {g.drawString(this.text, x, y); return this.width();}
|
||||
}
|
||||
|
||||
class Fixed extends Formattable {
|
||||
constructor(text) {
|
||||
super();
|
||||
this.text = text;
|
||||
}
|
||||
squeeze() {return false;}
|
||||
}
|
||||
|
||||
class Squeezable extends Formattable {
|
||||
constructor(named, index) {
|
||||
super();
|
||||
this.named = named;
|
||||
this.index = index;
|
||||
this.end = index + named.forms;
|
||||
}
|
||||
squeeze() {
|
||||
if (this.index >= this.end) return false;
|
||||
this.index++;
|
||||
this.w = null;
|
||||
return true;
|
||||
}
|
||||
get text() {return this.named.table[this.index];}
|
||||
}
|
||||
|
||||
class Named {
|
||||
constructor(forms, table) {
|
||||
this.forms = forms;
|
||||
this.table = table;
|
||||
}
|
||||
on(index) {return new Squeezable(this, this.forms * index);}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Face */
|
||||
|
||||
// Static geometry
|
||||
const barW = 26, barH = g.getHeight(), barX = g.getWidth() - barW, barY = 0;
|
||||
const faceW = g.getWidth() - barW, faceH = g.getHeight();
|
||||
const faceX = 0, faceY = 0, faceCX = faceW / 2, faceCY = faceH / 2;
|
||||
const rectX = faceX + 35, rectY = faceY + 24, rectW = 80, rectH = 128;
|
||||
|
||||
// Extended-Roman-numeral labels
|
||||
const layout = E.toUint8Array(
|
||||
75, 23, // XII
|
||||
132, 24, // I
|
||||
132, 61, // II
|
||||
132, 97, // III
|
||||
132, 133, // IV
|
||||
132, 170, // V
|
||||
75, 171, // VI
|
||||
18, 170, // VII
|
||||
18, 133, // VIII
|
||||
18, 97, // IX
|
||||
18, 61, // X
|
||||
18, 24 // XI
|
||||
);
|
||||
|
||||
const numeral = (n, options) => [
|
||||
'n', // 0
|
||||
'abc', // I
|
||||
'abdc', // II
|
||||
'abddc', // III
|
||||
'abefg', // IV
|
||||
'hfg', // V
|
||||
'hfibc', // VI
|
||||
'hfibdc', // VII
|
||||
'hfibddc', // VIII
|
||||
'abjk', // IX
|
||||
'kjk', // X
|
||||
'kjbc', // XI
|
||||
'kjbdc', // XII
|
||||
'kjbddc', // XIII
|
||||
'kjbefg', // XIV
|
||||
'kjefg', // XV
|
||||
'labc', // XVI
|
||||
'labdc', // XVII
|
||||
'labddc', // XVIII
|
||||
'kjbjk', // XIX
|
||||
'kjjk', // XX
|
||||
'mabc', // XXI
|
||||
'mabdc', // XXII
|
||||
'mabddc', // XXIII
|
||||
][options.o24h ? n % 24 : (n + 11) % 12 + 1];
|
||||
|
||||
const formatMonth = new Named(4, [
|
||||
'January', 'Jan.', 'Jan', 'I',
|
||||
'February', 'Feb.', 'Feb', 'II',
|
||||
'March', 'Mar.', 'Mar', 'III',
|
||||
'April', 'Apr.', 'Apr', 'IV',
|
||||
'May', 'May', 'May', 'V',
|
||||
'June', 'June', 'Jun', 'VI',
|
||||
'July', 'July', 'Jul', 'VII',
|
||||
'August', 'Aug.', 'Aug', 'VIII', // VIII *is* narrower than Aug, our I is thin.
|
||||
'September', 'Sept.', 'Sep', 'IX',
|
||||
'October', 'Oct.', 'Oct', 'X',
|
||||
'November', 'Nov.', 'Nov', 'XI',
|
||||
'December', 'Dec.', 'Dec', 'XII'
|
||||
]);
|
||||
const formatDom = {
|
||||
on: d => new Fixed(d.toString())
|
||||
};
|
||||
const formatDow = new Named(4, [
|
||||
'Sunday', 'Sun.', 'Sun', 'Su',
|
||||
'Monday', 'Mon.', 'Mon', 'M',
|
||||
'Tuesday', 'Tues.', 'Tue', 'Tu',
|
||||
'Wednesday', 'Weds.', 'Wed', 'W',
|
||||
'Thursday', 'Thurs.', 'Thu', 'Th',
|
||||
'Friday', 'Fri.', 'Fri', 'F',
|
||||
'Saturday', 'Sat.', 'Sat', 'Sa'
|
||||
]);
|
||||
|
||||
const hceil = x => Math.ceil(x / 3600000) * 3600000;
|
||||
const hfloor = x => Math.floor(x / 3600000) * 3600000;
|
||||
const isString = x => typeof x == 'string';
|
||||
const imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width;
|
||||
const imageHeight = i => isString(i) ? i.charCodeAt(1) : i.height;
|
||||
|
||||
const events = {
|
||||
// Items are {time: number, wall: boolean, priority: number,
|
||||
// past: bool, future: bool, precision: number,
|
||||
// colour: colour, dramatic?: bool, event?: any}
|
||||
fixed: [{time: Number.POSITIVE_INFINITY}], // indexed by ms absolute
|
||||
wall: [{time: Number.POSITIVE_INFINITY}], // indexed by nominal ms + TZ ms
|
||||
|
||||
clean: function(now, l) {
|
||||
let o = now.getTimezoneOffset() * 60000;
|
||||
let tf = now.getTime() + l, tw = tf - o;
|
||||
// Discard stale events:
|
||||
while (this.wall[0].time <= tw) this.wall.shift();
|
||||
while (this.fixed[0].time <= tf) this.fixed.shift();
|
||||
},
|
||||
|
||||
scan: function(now, from, to, f) {
|
||||
result = Infinity;
|
||||
let o = now.getTimezoneOffset() * 60000;
|
||||
let t = now.getTime() - o;
|
||||
let c, p, i, l = from - o, h = to - o;
|
||||
for (i = 0; (c = this.wall[i]).time < l; i++) ;
|
||||
for (; (c = this.wall[i]).time < h; i++) {
|
||||
if ((p = c.time < t) ? c.past : c.future)
|
||||
result = Math.min(result, f(c, new Date(c.time + o), p));
|
||||
}
|
||||
l += o; h += o; t += o;
|
||||
for (i = 0; (c = this.fixed[i]).time < l; i++) ;
|
||||
for (; (c = this.fixed[i]).time < h; i++) {
|
||||
if ((p = c.time < t) ? c.past : c.future)
|
||||
result = Math.min(f(c, new Date(c.time), p));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
span: function(now, from, to, width) {
|
||||
let o = now.getTimezoneOffset() * 60000;
|
||||
let t = now.getTime() - o;
|
||||
let lfence = [], rfence = [];
|
||||
this.scan(now, from, to, (e, d, p) => {
|
||||
if (p) {
|
||||
for (let j = 0; j <= e.priority; j++) {
|
||||
if (d < (lfence[e.priority] || t)) lfence[e.priority] = d;
|
||||
}
|
||||
} else {
|
||||
for (let j = 0; j <= e.priority; j++) {
|
||||
if (d > (rfence[e.priority] || t)) rfence[e.priority] = d;
|
||||
}
|
||||
}
|
||||
});
|
||||
for (let j = 0; ; j += 0.5) {
|
||||
if ((rfence[Math.ceil(j)] - lfence[Math.floor(j)] || 0) <= width) {
|
||||
return [lfence[Math.floor(j)] || now, rfence[Math.ceil(j)] || now];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
insert: function(t, wall, e) {
|
||||
let v = wall ? this.wall : this.fixed;
|
||||
e.time = t = t - (wall ? t.getTimezoneOffset() * 60000 : 0);
|
||||
v.splice(v.findIndex(x => x.time > t), 0, e);
|
||||
},
|
||||
|
||||
loadFromSystem: function(options) {
|
||||
alarms.forEach(x => {
|
||||
if (x.on) {
|
||||
const t = new Date();
|
||||
let h = x.hr;
|
||||
let m = h % 1 * 60;
|
||||
let s = m % 1 * 60;
|
||||
let ms = s % 1 * 1000;
|
||||
t.setHours(h - h % 1, m - m % 1, s - s % 1, ms);
|
||||
// There's a race condition here, but I'm not sure what we can do about it.
|
||||
if (t < Date.now() || x.last === t.getDate()) t.setDate(t.getDate() + 1);
|
||||
this.insert(t, true, {
|
||||
priority: 0,
|
||||
past: false, // System alarms seem uninteresting if past?
|
||||
future: true,
|
||||
precision: x.timer ? 1000 : 60000,
|
||||
colour: x.timer ? options.timerFg : options.alarmFg,
|
||||
event: x
|
||||
});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* The main face logic */
|
||||
|
||||
class Sidebar {
|
||||
constructor(g, x, y, w, h, options) {
|
||||
this.g = g;
|
||||
this.options = options;
|
||||
this.x = x;
|
||||
this.y = this.initY = y;
|
||||
this.h = h;
|
||||
this.rate = Infinity;
|
||||
this.doLocked = Sidebar.status(_ => Bangle.isLocked(), lockI);
|
||||
this.doHRM = Sidebar.status(_ => Bangle.isHRMOn(), HRMI);
|
||||
this.doGPS = Sidebar.status(_ => Bangle.isGPSOn(), GPSI, Sidebar.gpsColour(options));
|
||||
}
|
||||
reset(rate) {this.y = this.initY; this.rate = rate; return this;}
|
||||
print(t) {
|
||||
this.y += 4 + t.print(
|
||||
this.g.setColor(this.options.barFg).setFontAlign(-1, 1, 1),
|
||||
this.x + 3, this.y + 4
|
||||
);
|
||||
return this;
|
||||
}
|
||||
pad(n) {this.y += n; return this;}
|
||||
free() {return this.h - this.y;}
|
||||
static status(p, i, c) {
|
||||
return function() {
|
||||
if (p()) {
|
||||
this.g.setColor(c ? c() : this.options.barFg)
|
||||
.drawImage(i, this.x + 4, this.y += 4);
|
||||
this.y += imageHeight(i);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
}
|
||||
static gpsColour(o) {
|
||||
const fix = Bangle.getGPSFix();
|
||||
return fix && fix.fix ? o.active : o.barFg;
|
||||
}
|
||||
doPower() {
|
||||
const c = Bangle.isCharging();
|
||||
const b = E.getBattery();
|
||||
if (c || b < 50) {
|
||||
let g = this.g, x = this.x, y = this.y, options = this.options;
|
||||
g.setColor(options.barFg).drawImage(batteryI, x + 4, y + 4);
|
||||
g.setColor(b <= 10 ? '#f00' : b <= 30 ? '#ff0' : '#0f0');
|
||||
const h = 13 * (100 - b) / 100;
|
||||
g.fillRect(x + 8, y + 7 + h, x + 17, y + 20);
|
||||
// Espruino disallows blank leading rows in icons, for some reason.
|
||||
if (c) g.setColor(options.barBg).drawImage(chargeI, x + 4, y + 8);
|
||||
this.y = y + imageHeight(batteryI) + 4;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
doCompass() {
|
||||
if (Bangle.isCompassOn()) {
|
||||
const c = Bangle.getCompass();
|
||||
const a = c && this.rate <= 1000;
|
||||
this.g.setColor(a ? this.options.active : this.options.barFg).drawImage(
|
||||
compassI,
|
||||
this.x + 4 + imageWidth(compassI) / 2,
|
||||
this.y + 4 + imageHeight(compassI) / 2,
|
||||
a ? {rotate: c.heading / 180 * Math.PI} : undefined
|
||||
);
|
||||
this.y += 4 + imageHeight(compassI);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class Roman {
|
||||
constructor(g, events) {
|
||||
this.g = g;
|
||||
this.state = {};
|
||||
const options = this.options = new RomanOptions();
|
||||
this.events = events.loadFromSystem(this.options);
|
||||
this.timescales = [1000, [1000, 60000], 60000, 3600000];
|
||||
this.sidebar = new Sidebar(g, barX, barY, barW, barH, options);
|
||||
this.hours = Roman.hand(g, 3, 0.5, 12, _ => options.hourFg);
|
||||
this.minutes = Roman.hand(g, 2, 0.9, 60, _ => options.minuteFg);
|
||||
this.seconds = Roman.hand(g, 1, 0.9, 60, _ => options.secondFg);
|
||||
}
|
||||
|
||||
reset() {this.state = {}; this.g.clear(true);}
|
||||
|
||||
doIcons(which) {this.state.iconsOk = null;}
|
||||
|
||||
// Watch hands. These could be improved, graphically.
|
||||
// If we restricted them to 60 positions, we could feasibly hand-draw them?
|
||||
static hand(g, w, l, d, c) {
|
||||
return p => {
|
||||
g.setColor(c());
|
||||
p = ((12 * p / d) + 1) % 12;
|
||||
let h = l * rectW / 2;
|
||||
let v = l * rectH / 2;
|
||||
let poly =
|
||||
p <= 2 ? [faceCX + w, faceCY, faceCX - w, faceCY,
|
||||
faceCX + h * (p - 1), faceCY - v,
|
||||
faceCX + h * (p - 1) + 1, faceCY - v]
|
||||
: p < 6 ? [faceCX + 1, faceCY + w, faceCX + 1, faceCY - w,
|
||||
faceCX + h, faceCY + v / 2 * (p - 4),
|
||||
faceCX + h, faceCY + v / 2 * (p - 4) + 1]
|
||||
: p <= 8 ? [faceCX - w, faceCY + 1, faceCX + w, faceCY + 1,
|
||||
faceCX - h * (p - 7), faceCY + v,
|
||||
faceCX - h * (p - 7) - 1, faceCY + v]
|
||||
: [faceCX, faceCY - w, faceCX, faceCY + w,
|
||||
faceCX - h, faceCY - v / 2 * (p - 10),
|
||||
faceCX - h, faceCY - v / 2 * (p - 10) - 1];
|
||||
g.fillPoly(poly);
|
||||
};
|
||||
}
|
||||
|
||||
static pos(p, r) {
|
||||
let h = r * rectW / 2;
|
||||
let v = r * rectH / 2;
|
||||
p = (p + 1) % 12;
|
||||
return p <= 2 ? [faceCX + h * (p - 1), faceCY - v]
|
||||
: p < 6 ? [faceCX + h, faceCY + v / 2 * (p - 4)]
|
||||
: p <= 8 ? [faceCX - h * (p - 7), faceCY + v]
|
||||
: [faceCX - h, faceCY - v / 2 * (p - 10)];
|
||||
}
|
||||
|
||||
alert(e, date, now, past) {
|
||||
const g = this.g;
|
||||
g.setColor(e.colour);
|
||||
const dt = date - now;
|
||||
if (e.precision < 60000 && dt >= 0 && e.future && dt <= 59000) { // Seconds away
|
||||
const p = Roman.pos(date.getSeconds() / 5, 0.95);
|
||||
g.drawLine(faceCX, faceCY, p[0], p[1]);
|
||||
return 1000;
|
||||
} else if (e.precision < 3600000 && dt >= 0 && e.future && dt <= 3540000) { // Minutes away
|
||||
const p = Roman.pos(date.getMinutes() / 5 + date.getSeconds() / 300, 0.8);
|
||||
g.drawLine(p[0] - 5, p[1], p[0] + 5, p[1]);
|
||||
g.drawLine(p[0], p[1] - 5, p[0], p[1] + 5);
|
||||
return dt < 119000 ? 1000 : 60000; // Turn on second hand two minutes up.
|
||||
} else if (e.precision < 43200000 && dt >= 0 ? e.future : e.past) { // Hours away
|
||||
const p = Roman.pos(date.getHours() + date.getMinutes() / 60, 0.6);
|
||||
const poly = [p[0] - 4, p[1], p[0], p[1] - 4, p[0] + 4, p[1], p[0], p[1] + 4];
|
||||
if (date >= now) g.fillPoly(poly);
|
||||
else g.drawPoly(poly, true);
|
||||
return 3600000;
|
||||
}
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
render(d, rate) {
|
||||
const g = this.g;
|
||||
const state = this.state;
|
||||
const options = this.options;
|
||||
const events = this.events;
|
||||
events.clean(d, -39600000); // 11h
|
||||
|
||||
// Sidebar: icons and date
|
||||
if (d.getDate() !== state.date || !state.iconsOk) {
|
||||
const sidebar = this.sidebar;
|
||||
state.date = d.getDate();
|
||||
state.iconsOk = true;
|
||||
g.setColor(options.barBg).fillRect(barX, barY, barX + barW, barY + barH);
|
||||
|
||||
sidebar.reset(rate).doLocked().doPower().doGPS().doHRM().doCompass();
|
||||
g.setFontCustom.apply(g, fontF);
|
||||
let formatters = [];
|
||||
let month, dom, dow;
|
||||
if (options.calendric > 1) {
|
||||
formatters.push(month = formatMonth.on(d.getMonth()));
|
||||
}
|
||||
if (options.calendric > 0) {
|
||||
formatters.push(dom = formatDom.on(d.getDate()));
|
||||
}
|
||||
if (options.dow) {
|
||||
formatters.push(dow = formatDow.on(d.getDay()));
|
||||
}
|
||||
// Obnoxiously inefficient iterative method :(
|
||||
let ava = sidebar.free() - 3, use, i = 0, j = 0;
|
||||
while ((use = formatters.reduce((l, f) => l + f.width(g) + 4, 0)) > ava &&
|
||||
j < formatters.length)
|
||||
for (j = 0;
|
||||
!formatters[i++ % formatters.length].squeeze() &&
|
||||
j < formatters.length;
|
||||
j++) ;
|
||||
if (dow) sidebar.print(dow);
|
||||
sidebar.pad(ava - use);
|
||||
if (month) sidebar.print(month);
|
||||
if (dom) sidebar.print(dom);
|
||||
}
|
||||
|
||||
// Hour labels and (purely aesthetic) box; clear inner face.
|
||||
let keyHour = d.getHours() < 12 ? 1 : 13;
|
||||
let alertSpan = events.span(d, hceil(d) - 39600000, hfloor(d) + 39600000, 39600000);
|
||||
let l = alertSpan[0].getHours(), h = alertSpan[1].getHours();
|
||||
if ((l - keyHour + 24) % 24 >= 12 || (h - keyHour + 24) % 24 >= 12) keyHour = l;
|
||||
if (keyHour !== state.keyHour) {
|
||||
state.keyHour = keyHour;
|
||||
g.setColor(options.bg)
|
||||
.fillRect(faceX, faceY, faceX + faceW, faceY + faceH)
|
||||
.setFontCustom.apply(g, romanPartsF)
|
||||
.setFontAlign(0, 1)
|
||||
.setColor(options.fg);
|
||||
// In order to deal with timezone changes more logic will be required,
|
||||
// since the labels may be in unusual locations (even offset when
|
||||
// a non-integral zone is involved). The value of keyHour can be
|
||||
// anything in [hr-12, hr] mod 24.
|
||||
for (let h = keyHour; h < keyHour + 12; h++) {
|
||||
g.drawString(
|
||||
numeral(h % 24, options),
|
||||
faceX + layout[h % 12 * 2],
|
||||
faceY + layout[h % 12 * 2 + 1]
|
||||
);
|
||||
}
|
||||
g.setColor(options.rectFg)
|
||||
.drawRect(rectX, rectY, rectX + rectW - 1, rectY + rectH - 1);
|
||||
} else {
|
||||
g.setColor(options.bg)
|
||||
.fillRect(rectX + 1, rectY + 1, rectX + rectW - 2, rectY + rectH - 2)
|
||||
.setColor(options.fg);
|
||||
}
|
||||
|
||||
// Alerts
|
||||
let requestedRate = events.scan(
|
||||
d, hfloor(alertSpan[0] + 0), hceil(alertSpan[1] + 0) + 1,
|
||||
(e, t, p) => this.alert(e, t, d, p)
|
||||
);
|
||||
if (rate > requestedRate) rate = requestedRate;
|
||||
|
||||
// Hands
|
||||
// Here we are using incremental hands for hours and minutes.
|
||||
// If we quantised, we could use hand-crafted bitmaps, though.
|
||||
this.hours(d.getHours() + d.getMinutes() / 60);
|
||||
if (rate < 3600000) {
|
||||
this.minutes(d.getMinutes() + d.getSeconds() / 60);
|
||||
}
|
||||
if (rate < 60000) this.seconds(d.getSeconds());
|
||||
g.setColor(options.hubFg).fillCircle(faceCX, faceCY, 3);
|
||||
return requestedRate;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Master clock */
|
||||
|
||||
class Clock {
|
||||
constructor(face) {
|
||||
this.face = face;
|
||||
this.timescales = face.timescales;
|
||||
this.options = face.options;
|
||||
this.rates = {};
|
||||
|
||||
this.options.on('done', () => this.start());
|
||||
|
||||
this.listeners = {
|
||||
lcdPower: on => on ? this.active() : this.inactive(),
|
||||
charging: () => {face.doIcons('charging'); this.active();},
|
||||
lock: () => {face.doIcons('locked'); this.active();},
|
||||
faceUp: up => {this.conservative = !up; this.active();},
|
||||
drag: e => {
|
||||
if (this.t0) {
|
||||
if (e.b) {
|
||||
e.x > this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x);
|
||||
e.y > this.yN && (this.yN = e.y) || e.y > this.yX && (this.xY = e.y);
|
||||
} else if (this.xX - this.xN < 20) {
|
||||
if (e.y - this.e0.y < -50) {
|
||||
this.options.resolution > 0 && this.options.resolution--;
|
||||
this.rates.clock = this.timescales[this.options.resolution];
|
||||
this.active();
|
||||
} else if (e.y - this.e0.y > 50) {
|
||||
this.options.resolution < this.timescales.length - 1 &&
|
||||
this.options.resolution++;
|
||||
this.rates.clock = this.timescales[this.options.resolution];
|
||||
this.active();
|
||||
} else if (this.yX - this.yN < 20 && Date.now() - this.t0 > 500) {
|
||||
this.stop();
|
||||
this.options.interact();
|
||||
}
|
||||
this.t0 = null;
|
||||
}
|
||||
} else if (e.b) {
|
||||
this.t0 = Date.now(); this.e0 = e;
|
||||
this.xN = this.xX = e.x; this.yN = this.yX = e.y;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
redraw(rate) {
|
||||
const now = this.updated = new Date();
|
||||
if (this.refresh) this.face.reset();
|
||||
this.refresh = false;
|
||||
rate = this.face.render(now, rate);
|
||||
if (rate !== this.rates.face) {
|
||||
this.rates.face = rate;
|
||||
this.active();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
inactive() {
|
||||
this.timeout && clearTimeout(this.timeout);
|
||||
this.exception && clearTimeout(this.exception);
|
||||
this.interval && clearInterval(this.interval);
|
||||
this.timeout = this.exception = this.interval = this.rate = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
active() {
|
||||
const prev = this.rate;
|
||||
const now = Date.now();
|
||||
let rate = Infinity;
|
||||
for (const k in this.rates) {
|
||||
let r = this.rates[k];
|
||||
r === +r || (r = r[+this.conservative])
|
||||
r < rate && (rate = r);
|
||||
}
|
||||
const delay = rate - now % rate + 1;
|
||||
this.refresh = true;
|
||||
|
||||
if (rate !== prev) {
|
||||
this.inactive();
|
||||
this.redraw(rate);
|
||||
if (rate < 31622400000) { // A year!
|
||||
this.timeout = setTimeout(
|
||||
() => {
|
||||
this.inactive();
|
||||
this.interval = setInterval(() => this.redraw(rate), rate);
|
||||
if (delay > 1000) this.redraw(rate);
|
||||
this.rate = rate;
|
||||
}, delay
|
||||
);
|
||||
}
|
||||
} else if (rate > 1000) {
|
||||
if (!this.exception) this.exception = setTimeout(() => {
|
||||
this.redraw(rate);
|
||||
this.exception = null;
|
||||
}, this.updated + 1000 - Date.now());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.inactive();
|
||||
for (const l in this.listeners) {
|
||||
Bangle.removeListener(l, this.listeners[l]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.inactive(); // Reset to known state.
|
||||
this.conservative = false;
|
||||
this.rates.clock = this.timescales[this.options.resolution];
|
||||
this.active();
|
||||
for (const l in this.listeners) {
|
||||
Bangle.on(l, this.listeners[l]);
|
||||
}
|
||||
Bangle.setUI('clock');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Main */
|
||||
|
||||
const clock = new Clock(new Roman(g, events)).start();
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,721 @@
|
|||
// pooqRoman resource maker
|
||||
//
|
||||
// Copyright (c) 2021 Stephen P Spackman
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* ==ASSETS== */
|
||||
|
||||
const heatshrink = require('heatshrink');
|
||||
|
||||
const enc = x => {
|
||||
const d = btoa(require("heatshrink").compress(x));
|
||||
var r = "'" + d.substr(0, 64);
|
||||
for (let i = 64; i < d.length; i += 64) r += "' +\n '" + d.substr(i, 64);
|
||||
return r + "'";
|
||||
};
|
||||
|
||||
const prepBitmap = (name, data) => {
|
||||
const image = Graphics.createImage(data);
|
||||
const raw = String.fromCharCode(image.width, image.height, 0x81, 0) + image.buffer;
|
||||
const x = `
|
||||
const ${name}I = dec(${enc(raw)});
|
||||
`;
|
||||
return x;
|
||||
};
|
||||
|
||||
const prepFont = (name, data) => {
|
||||
const image = Graphics.createImage(data);
|
||||
const lengths = Uint8Array(256);
|
||||
const offsets = Uint16Array(256);
|
||||
const adjustments = Uint16Array(256);
|
||||
let min = Infinity, max = -Infinity;
|
||||
const lines = data.split('\n');
|
||||
let m;
|
||||
// This regexp is clearly suboptimal, but Espruino's regexp engine is really wonky
|
||||
// and doesn't process nested parentheses or alternation correctly.
|
||||
for (let i = 0; i < 5 && !(m = /^(<*)=([*\d]+)(=*)(>*)$/.exec(lines[i])); i++);
|
||||
if (!m) throw new Error('Missing or incorrect header');
|
||||
const desc = m[1].length, body = 1 + m[2].length + m[3].length, asc = m[4].length;
|
||||
const h = desc + body + asc;
|
||||
let width = m[2] == '*' ? null : +m[2];
|
||||
let c = null, o = 0;
|
||||
lines.forEach((line, l) => {
|
||||
if (m = /^(<*)(=)([*\d]*)(=*)(>*)$/.exec(line) || /^(<*)(-)(.)(-*)(>*)$/.exec(line)) {
|
||||
const h = m[2] == '=';
|
||||
if (m[1].length > desc || h && m[1].length != desc)
|
||||
throw new Error('Invalid descender height at ' + l);
|
||||
if (m[2].length + m[3].length + m[4].length != body)
|
||||
throw new Error('Invalid body height at ' + l);
|
||||
if (m[5].length > asc || h && m[5].length != asc)
|
||||
throw new Error('Invalid ascender height at ' + l);
|
||||
if (c != null) {
|
||||
lengths[c] = l - o;
|
||||
if (width !== null && width !== lengths[c])
|
||||
throw new Error(
|
||||
`Character has width ${lengths[c]} != ${width} at ${offsets[c]}`
|
||||
);
|
||||
c = null
|
||||
}
|
||||
if (!h) {
|
||||
c = m[3].charCodeAt(0);
|
||||
if (c < min) min = c;
|
||||
if (c > max) max = c;
|
||||
o = l + 1;
|
||||
offsets[c] = l;
|
||||
adjustments[c] = m[1].length
|
||||
}
|
||||
}
|
||||
});
|
||||
const xoffs = Uint8Array(lines.length);
|
||||
const ypos = Uint16Array(lines.length);
|
||||
ypos.fill(0xffff);
|
||||
const w0 = lengths[min];
|
||||
let widths = '';
|
||||
for (c = min, o = 0; c <= max; c++) {
|
||||
for (i = 0, j = offsets[c]; i < lengths[c]; i++) {
|
||||
xoffs[j] = asc + body + adjustments[c] - 1;
|
||||
ypos[j++] = o++;
|
||||
}
|
||||
widths += String.fromCharCode(lengths[c]);
|
||||
}
|
||||
const raster = Graphics.createArrayBuffer(h, o, 1, {msb: true});
|
||||
const writer = Graphics.createCallback(
|
||||
image.width, image.height, 1,
|
||||
(x, y, col) => raster.setPixel(xoffs[y] - x, ypos[y], col)
|
||||
);
|
||||
writer.drawImage(image);
|
||||
if (width === null) width = `dec(${enc(widths)})`;
|
||||
const x = `const ${name}F = [
|
||||
dec(
|
||||
${enc(raster.buffer)}
|
||||
), ${min}, ${width}, ${h}
|
||||
];`;
|
||||
return x;
|
||||
};
|
||||
|
||||
res = `
|
||||
const heatshrink = require('heatshrink');
|
||||
const dec = x => E.toString(heatshrink.decompress(atob(x)));
|
||||
`;
|
||||
|
||||
res += prepFont('romanParts', `
|
||||
<=*==============
|
||||
-a--------------
|
||||
x x
|
||||
xx xx
|
||||
-b--------------
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
-c--------------
|
||||
xx xx
|
||||
x x
|
||||
-d--------------
|
||||
xx xx
|
||||
xx xx
|
||||
xx xx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
-e--------------
|
||||
xx xx
|
||||
x xxxx
|
||||
<-f--------------
|
||||
xxxxxxxx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxx xx
|
||||
xxxxxx x
|
||||
xxxxx
|
||||
xxxxxx x
|
||||
xxxxxxx xx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxx
|
||||
-g--------------
|
||||
xxxx
|
||||
xx
|
||||
x
|
||||
-h--------------
|
||||
x
|
||||
xx
|
||||
xxxx
|
||||
-i--------------
|
||||
x xxxx
|
||||
xx xx
|
||||
-j--------------
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxx xxxx
|
||||
xxxxxx xxxxxx
|
||||
xx xxxx xxxx xx
|
||||
x xxxxxx x
|
||||
xxxx
|
||||
x xxxxxx x
|
||||
xx xxxx xxxx xx
|
||||
xxxxxx xxxxxx
|
||||
xxxx xxxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
-k--------------
|
||||
x x
|
||||
<-l--------------
|
||||
xx x
|
||||
xxxxxx xx
|
||||
xxxx xxxx xxx
|
||||
xxxx xx xxxx x
|
||||
xxx xx
|
||||
xxxx xx xxxx x
|
||||
xxxx xxxx xxx
|
||||
xxxxxx xx
|
||||
xx x
|
||||
-m--------------
|
||||
x xx x
|
||||
xx xxxx xx
|
||||
xxx xxxxxx xxx
|
||||
x xxxx xx xxxx x
|
||||
xx xx
|
||||
x xxxx xx xxxx x
|
||||
xxx xxxxxx xxx
|
||||
xx xxxx xx
|
||||
x xx x
|
||||
-n--------------
|
||||
xxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxx xxxx
|
||||
xxxx xxxx
|
||||
xxx xxx
|
||||
xx xxxx xx
|
||||
xx xxxx xx
|
||||
xxx xxx
|
||||
xxxx xxxx
|
||||
xxxx xxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxx
|
||||
<=*==============
|
||||
`);
|
||||
|
||||
res += prepFont('font', `
|
||||
<<<<=*======>>>>
|
||||
- ------
|
||||
|
||||
-.------
|
||||
xx
|
||||
xx
|
||||
-0------>>>>
|
||||
xxxxxxxx
|
||||
xxxxxxxxxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxxxxxx
|
||||
xxxxxxxx
|
||||
|
||||
-1------>>>>
|
||||
xx x
|
||||
xx xx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xx
|
||||
xx
|
||||
|
||||
-2------>>>>
|
||||
x x
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxx xx
|
||||
xxxxx xx
|
||||
xx xxx xxx
|
||||
xx xxxxxxx
|
||||
xx xxxxx
|
||||
|
||||
-3------>>>>
|
||||
x xx
|
||||
xx x xx
|
||||
xxx xx xx
|
||||
xx xxx xx
|
||||
xx xxxxxx
|
||||
xxx xxx xxx
|
||||
xxxxxx xx
|
||||
xxx x
|
||||
|
||||
-4------>>>>
|
||||
x
|
||||
xx
|
||||
xxxx
|
||||
xxxxxxxxx
|
||||
xxxxx xxxxx
|
||||
xxxxx
|
||||
xx
|
||||
xx
|
||||
|
||||
-5------>>>>
|
||||
x xxxxxx
|
||||
xx xxxxxx
|
||||
xxx xx xx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xxx xxx xx
|
||||
xxxxxx xx
|
||||
xxxx
|
||||
|
||||
-6------>>>>
|
||||
xxxx
|
||||
xxxxxxx
|
||||
xxx xxxxx
|
||||
xx xxxxx
|
||||
xx xx xxx
|
||||
xxx xxx xx
|
||||
xxxxxx x
|
||||
xxxx
|
||||
|
||||
-7------>>>>
|
||||
xx
|
||||
xx
|
||||
xxxx xx
|
||||
xxxxxx xx
|
||||
xxxx xx
|
||||
xxxxxx
|
||||
xxxx
|
||||
x
|
||||
|
||||
-8------>>>>
|
||||
xxx xxx
|
||||
xxxxxxxxxx
|
||||
xxx xxxx xxx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xxx xxxx xxx
|
||||
xxxxxxxxxx
|
||||
xxx xxx
|
||||
|
||||
-9------>>>>
|
||||
xxxx
|
||||
x xxxxxx
|
||||
xx xxx xxx
|
||||
xxx xx xx
|
||||
xxxxx xx
|
||||
xxxxx xxx
|
||||
xxxxxxx
|
||||
xxx
|
||||
|
||||
-A------>>>>
|
||||
xx
|
||||
xxxxx
|
||||
xxxxxxx
|
||||
xxxxxxx
|
||||
xx xxxx
|
||||
xxxxxxx
|
||||
xxxxxxx
|
||||
xxxxx
|
||||
xx
|
||||
|
||||
-D------>>>>
|
||||
xx xx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xx xx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxxxxxx
|
||||
xxxxxxxx
|
||||
|
||||
-F------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xx xx
|
||||
xx xx
|
||||
xx xx
|
||||
xx
|
||||
-I------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
|
||||
-J------>>>>
|
||||
xx
|
||||
xxx xx
|
||||
xxx xx
|
||||
xx xx
|
||||
xxx xx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxxxx
|
||||
xx
|
||||
-M------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxx
|
||||
xxx
|
||||
xxxx
|
||||
xxxx
|
||||
xxx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
|
||||
-N------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxx
|
||||
xxx
|
||||
xxx
|
||||
xxx
|
||||
xxx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
|
||||
-O------>>>>
|
||||
xxxxxxxx
|
||||
xxxxxxxxxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxxxxxx
|
||||
xxxxxxxx
|
||||
|
||||
-S------>>>>
|
||||
x xxx
|
||||
xx xxxxx
|
||||
xxx xx xxx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xxx xx xxx
|
||||
xxxxx xx
|
||||
xxx x
|
||||
|
||||
-T------>>>>
|
||||
xx
|
||||
xx
|
||||
xx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xx
|
||||
xx
|
||||
xx
|
||||
-V------>>>>
|
||||
xxx
|
||||
xxxxxx
|
||||
xxxxx
|
||||
xxxxx
|
||||
xxxxx
|
||||
xxxxxx
|
||||
xxx
|
||||
|
||||
-W------>>>>
|
||||
xxxx
|
||||
xxxxxxxx
|
||||
xxxxxxxx
|
||||
xxxx
|
||||
xxxx
|
||||
xxxx
|
||||
xxxxxxxx
|
||||
xxxxxxxx
|
||||
xxxx
|
||||
-X------>>>>
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxxx
|
||||
xxxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
|
||||
-a------
|
||||
xxx
|
||||
xxxxx x
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xxxxxx
|
||||
xxxxxx
|
||||
|
||||
-b------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxx
|
||||
xx xx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxx
|
||||
xxxx
|
||||
|
||||
-c------
|
||||
xxxx
|
||||
xxxxxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
xx xx
|
||||
xx xx
|
||||
x x
|
||||
|
||||
-d------>>>>
|
||||
xxxx
|
||||
xxxxxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
xx xx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
|
||||
-e------
|
||||
xxxx
|
||||
xxxxxx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
x xxxx
|
||||
xxx
|
||||
|
||||
<<<<-g------
|
||||
x xxxx
|
||||
xx xxxxxx
|
||||
xx xxx xxx
|
||||
xx xx xx
|
||||
xxx xx xxx
|
||||
xxxxxxxxxx
|
||||
xxxxxxxxx
|
||||
|
||||
-h------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xx
|
||||
xx
|
||||
xxx
|
||||
xxxxxxx
|
||||
xxxxxx
|
||||
|
||||
-i------>>>>
|
||||
xxxxxxxx xx
|
||||
xxxxxxxx xx
|
||||
|
||||
-l------>>>>
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
|
||||
-m------
|
||||
xxxxxxxx
|
||||
xxxxxxx
|
||||
xxx
|
||||
xxx
|
||||
xxxxxxx
|
||||
xxxxxxx
|
||||
xxx
|
||||
xxx
|
||||
xxxxxxx
|
||||
xxxxxx
|
||||
|
||||
-n------
|
||||
xxxxxxxx
|
||||
xxxxxxx
|
||||
xxx
|
||||
xx
|
||||
xxx
|
||||
xxxxxxx
|
||||
xxxxxx
|
||||
|
||||
-o------
|
||||
xxxx
|
||||
xxxxxx
|
||||
xxx xxx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxx
|
||||
xxxx
|
||||
|
||||
<<<<-p------
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxx
|
||||
xx xx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxx
|
||||
xxxx
|
||||
|
||||
-r------
|
||||
xxxxxxxx
|
||||
xxxxxxx
|
||||
xxx
|
||||
xx
|
||||
xx
|
||||
xx
|
||||
|
||||
-s------
|
||||
x xxx
|
||||
xx xxxxx
|
||||
xx xx xx
|
||||
xx xx xx
|
||||
xxxxx xx
|
||||
xxx x
|
||||
|
||||
-t------>>>>
|
||||
xx
|
||||
xxxxxxxxx
|
||||
xxxxxxxxxx
|
||||
xxx xx
|
||||
xx xx
|
||||
xx xx
|
||||
xx
|
||||
|
||||
-u------
|
||||
xxxxxx
|
||||
xxxxxxx
|
||||
xxx
|
||||
xx
|
||||
xxx
|
||||
xxxxxxx
|
||||
xxxxxxxx
|
||||
|
||||
-v------
|
||||
xx
|
||||
xxxx
|
||||
xxxx
|
||||
xxxx
|
||||
xxxx
|
||||
xxxx
|
||||
xx
|
||||
|
||||
<<<<-y------
|
||||
x xxxxxx
|
||||
xx xxxxxxx
|
||||
xx xxx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxxx
|
||||
|
||||
<<<<=*======>>>>
|
||||
`);
|
||||
|
||||
res += prepBitmap('lock', `
|
||||
xxxxxx
|
||||
xxxxxxxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxx
|
||||
`);
|
||||
|
||||
res += prepBitmap('battery', `
|
||||
xxxx
|
||||
xxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
`);
|
||||
|
||||
res += prepBitmap('charge', `
|
||||
x
|
||||
xx
|
||||
xx
|
||||
xxx
|
||||
xxx
|
||||
xxxxxxxxx
|
||||
xxxxxxxxx
|
||||
xxx
|
||||
xxx
|
||||
xx
|
||||
xx
|
||||
x
|
||||
`);
|
||||
|
||||
res += prepBitmap('GPS', `
|
||||
x
|
||||
x x
|
||||
x x
|
||||
x x
|
||||
x x xxxx
|
||||
x xxxxx
|
||||
xxxxxx
|
||||
xxxxx
|
||||
x xxx x
|
||||
x x x x x
|
||||
x x x x x
|
||||
x x xx x x
|
||||
x x x x
|
||||
x xxx x
|
||||
x
|
||||
xxx
|
||||
`);
|
||||
|
||||
res += prepBitmap('HRM', `
|
||||
xxxx xxxx
|
||||
xxxxxx xxxxxx
|
||||
xx xxxx xxx xxx
|
||||
xxx xxxxxxxx xxxx
|
||||
xxx xxxxxxxx xxxx
|
||||
xxx xxxxxxxx xxxx
|
||||
xx xxxxxxx xxxx
|
||||
xx xx xxxx xx x
|
||||
xx x x x
|
||||
xx xxxxxxxx xxx
|
||||
xxxxxxxxxxxxx
|
||||
xxxxxxxxxxx
|
||||
xxxxxxxxx
|
||||
xxxxxxx
|
||||
xxxxx
|
||||
xxx
|
||||
x
|
||||
`);
|
||||
|
||||
res += prepBitmap('compass', `
|
||||
xxxxx
|
||||
xxxxxxxxx
|
||||
xxx x xxx
|
||||
xx x xx
|
||||
xx x xx
|
||||
xx xxx xx
|
||||
xx xxx xx
|
||||
xx xxx xx
|
||||
xx xxx xx
|
||||
xx xx xx xx
|
||||
xx xx xx xx
|
||||
xx x x xx
|
||||
xx x x xx
|
||||
xx xx
|
||||
xxx xxx
|
||||
xxxxxxxxx
|
||||
xxxxx
|
||||
`);
|
||||
|
||||
print(res);
|
|
@ -1,3 +1,4 @@
|
|||
0.01: First version
|
||||
0.02: Add widget
|
||||
0.03: Bangle.js 2 support
|
||||
0.04: Move Quiet Mode LCD options from global settings to this app
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
# Quiet Mode Schedule and Widget
|
||||
|
||||
Automatically turn Quiet Mode on or off at set times, and display a widget when enabled.
|
||||
Automatically turn Quiet Mode on or off at set times, and display a widget when Quiet Mode is active.
|
||||
|
||||
### Edit Schedule:
|
||||
 
|
||||
| Bangle.js 1 | Bangle.js 2 |
|
||||
|:---------------------------------------------:|:---------------------------------------------:|
|
||||
| (widget: Silent mode) | (widget: Alarms mode) |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
### Widget:
|
||||
 
|
||||
### LCD Settings:
|
||||
|
||||
If set, these override the default LCD settings while Quiet Mode is active.
|
|
@ -2,27 +2,74 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
|
||||
const modeNames = ["Off", "Alarms", "Silent"];
|
||||
let scheds = require("Storage").readJSON("qmsched.json", 1);
|
||||
/*scheds = [
|
||||
{ hr : 6.5, // hours + minutes/60
|
||||
mode : 1, // quiet mode (0/1/2)
|
||||
}
|
||||
];*/
|
||||
if (!scheds) {
|
||||
// set default schedule on first load of app
|
||||
scheds = [
|
||||
{"hr": 8, "mode": 0},
|
||||
{"hr": 22, "mode": 1},
|
||||
];
|
||||
require("Storage").writeJSON("qmsched.json", scheds);
|
||||
|
||||
// load global brightness setting
|
||||
let bSettings = require('Storage').readJSON('setting.json',true)||{};
|
||||
let current = 0|bSettings.quiet;
|
||||
delete bSettings; // we don't need any other global settings
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Save settings to qmsched.json
|
||||
*/
|
||||
function save() {
|
||||
require('Storage').writeJSON('qmsched.json', settings);
|
||||
}
|
||||
if (scheds.length && scheds.some(s => "last" in s)) {
|
||||
// cleanup: remove "last" values (used by old versions)
|
||||
scheds = scheds.map(s => {
|
||||
delete s.last;
|
||||
return s;
|
||||
});
|
||||
require("Storage").writeJSON("qmsched.json", scheds);
|
||||
function get(key, def) {
|
||||
return (key in settings) ? settings[key] : def;
|
||||
}
|
||||
function set(key, val) {
|
||||
settings[key] = val; save();
|
||||
scheds = settings.scheds; options = settings.options; // update references
|
||||
}
|
||||
function unset(key) {
|
||||
delete settings[key]; save();
|
||||
}
|
||||
|
||||
let settings,
|
||||
scheds, options; // references for convenience
|
||||
/**
|
||||
* Load settings file, check if we need to migrate old setting formats to new
|
||||
*/
|
||||
function loadSettings() {
|
||||
settings = require('Storage').readJSON("qmsched.json", true) || {};
|
||||
|
||||
if (Array.isArray(settings)) {
|
||||
// migrate old file (plain array of schedules, qmOptions stored in global settings file)
|
||||
require("Storage").erase("qmsched.json"); // need to erase old file, or Things Break, somehow...
|
||||
let bOptions = require('Storage').readJSON('setting.json',true)||{};
|
||||
settings = {
|
||||
options: bOptions.qmOptions || {},
|
||||
scheds: settings,
|
||||
};
|
||||
// store new format
|
||||
save();
|
||||
// and clean up qmOptions from global settings file
|
||||
delete bOptions.qmOptions;
|
||||
require('Storage').writeJSON('setting.json',bOptions);
|
||||
}
|
||||
// apply defaults
|
||||
settings = Object.assign({
|
||||
options: {}, // Bangle options to override during quiet mode, default = none
|
||||
scheds: [
|
||||
// default schedule:
|
||||
{"hr": 8, "mode": 0},
|
||||
{"hr": 22, "mode": 1},
|
||||
],
|
||||
}, settings);
|
||||
scheds = settings.scheds; options = settings.options;
|
||||
|
||||
if (scheds.length && scheds.some(s => "last" in s)) {
|
||||
// cleanup: remove "last" values (used by older versions)
|
||||
set('scheds', scheds.map(s => {
|
||||
delete s.last;
|
||||
return s;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(t) {
|
||||
|
@ -32,29 +79,35 @@ function formatTime(t) {
|
|||
}
|
||||
|
||||
function showMainMenu() {
|
||||
let menu = {"": {"title": "Quiet Mode"}};
|
||||
let _m, menu = {
|
||||
"": {"title": "Quiet Mode"},
|
||||
"< Exit": () => load()
|
||||
};
|
||||
// "Current Mode""Silent" won't fit on Bangle.js 2
|
||||
menu["Current" + ((process.env.HWVERSION===2)?"":" Mode")]= {
|
||||
value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0,
|
||||
menu["Current"+((process.env.HWVERSION===2) ? "" : " Mode")] = {
|
||||
value: current,
|
||||
format: v => modeNames[v],
|
||||
onchange: function(v) {
|
||||
if (v<0) {v = 2;}
|
||||
if (v>2) {v = 0;}
|
||||
require("qmsched").setMode(v);
|
||||
current = v;
|
||||
this.value = v;
|
||||
},
|
||||
};
|
||||
scheds.sort((a, b) => (a.hr-b.hr));
|
||||
scheds.forEach((sched, idx) => {
|
||||
const name = modeNames[sched.mode];
|
||||
const txt = formatTime(sched.hr)+" ".repeat(14-name.length)+name;
|
||||
menu[txt] = function() {
|
||||
showEditMenu(idx);
|
||||
menu[formatTime(sched.hr)] = {
|
||||
format: () => modeNames[sched.mode], // abuse format to right-align text
|
||||
onchange: function() {
|
||||
_m.draw = ()=> {}; // prevent redraw of main menu over edit menu
|
||||
showEditMenu(idx);
|
||||
}
|
||||
};
|
||||
});
|
||||
menu["Add Schedule"] = () => showEditMenu(-1);
|
||||
menu["< Back"] = () => {load();};
|
||||
return E.showMenu(menu);
|
||||
menu["LCD Settings"] = () => showOptionsMenu();
|
||||
_m = E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showEditMenu(index) {
|
||||
|
@ -69,6 +122,7 @@ function showEditMenu(index) {
|
|||
}
|
||||
const menu = {
|
||||
"": {"title": (isNew ? "Add" : "Edit")+" Schedule"},
|
||||
"< Cancel": () => showMainMenu(),
|
||||
"Hours": {
|
||||
value: hrs,
|
||||
onchange: function(v) {
|
||||
|
@ -110,18 +164,88 @@ function showEditMenu(index) {
|
|||
} else {
|
||||
scheds[index] = getSched();
|
||||
}
|
||||
require("Storage").writeJSON("qmsched.json", scheds);
|
||||
save();
|
||||
showMainMenu();
|
||||
};
|
||||
if (!isNew) {
|
||||
menu["> Delete"] = function() {
|
||||
scheds.splice(index, 1);
|
||||
require("Storage").writeJSON("qmsched.json", scheds);
|
||||
save();
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
menu["< Cancel"] = showMainMenu;
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showOptionsMenu() {
|
||||
const disabledFormat = v => v ? "Off" : "-";
|
||||
function toggle(option) {
|
||||
// we disable wakeOn* events by setting them to `false` in options
|
||||
// not disabled = not present in options at all
|
||||
if (option in options) {
|
||||
delete options[option];
|
||||
} else {
|
||||
options[option] = false;
|
||||
}
|
||||
save();
|
||||
}
|
||||
let resetTimeout;
|
||||
const oMenu = {
|
||||
"": {"title": "LCD Settings"},
|
||||
"< Back": () => showMainMenu(),
|
||||
"LCD Brightness": {
|
||||
value: get("brightness", 0),
|
||||
min: 0, // 0 = use default
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
format: v => (v>0.05) ? v : "-",
|
||||
onchange: v => {
|
||||
if (v>0.05) { // prevent v=0.000000000000001 bugs
|
||||
set("brightness", v);
|
||||
Bangle.setLCDBrightness(v); // show result, even if not quiet right now
|
||||
// restore brightness after half a second
|
||||
if (resetTimeout) clearTimeout(resetTimeout);
|
||||
resetTimeout = setTimeout(() => {
|
||||
resetTimeout = undefined;
|
||||
require("qmsched").setMode(current);
|
||||
}, 500);
|
||||
} else {
|
||||
unset("brightness");
|
||||
require("qmsched").setMode(current);
|
||||
}
|
||||
},
|
||||
},
|
||||
"LCD Timeout": {
|
||||
value: get("timeout", 0),
|
||||
min: 0, // 0 = use default (no constant on for quiet mode)
|
||||
max: 60,
|
||||
step: 5,
|
||||
format: v => v>1 ? v : "-",
|
||||
onchange: v => {
|
||||
if (v>1) set("timeout", v);
|
||||
else unset("timeout");
|
||||
},
|
||||
},
|
||||
// we disable wakeOn* events by overwriting them as false in options
|
||||
// not disabled = not present in options at all
|
||||
"Wake on FaceUp": {
|
||||
value: "wakeOnFaceUp" in options,
|
||||
format: disabledFormat,
|
||||
onchange: () => {toggle("wakeOnFaceUp");},
|
||||
},
|
||||
"Wake on Touch": {
|
||||
value: "wakeOnTouch" in options,
|
||||
format: disabledFormat,
|
||||
onchange: () => {toggle("wakeOnTouch");},
|
||||
},
|
||||
"Wake on Twist": {
|
||||
value: "wakeOnTwist" in options,
|
||||
format: disabledFormat,
|
||||
onchange: () => {toggle("wakeOnTwist");},
|
||||
},
|
||||
};
|
||||
return E.showMenu(oMenu);
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
showMainMenu();
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
// apply Quiet Mode schedules
|
||||
(function qm() {
|
||||
let scheds = require("Storage").readJSON("qmsched.json", 1) || [];
|
||||
if (!scheds.length) { return;}
|
||||
let bSettings = require('Storage').readJSON('setting.json',true)||{};
|
||||
const curr = 0|bSettings.quiet;
|
||||
delete bSettings;
|
||||
if (curr) require("qmsched").applyOptions(curr); // no need to re-apply default options
|
||||
|
||||
let settings = require('Storage').readJSON('qmsched.json',true)||{};
|
||||
let scheds = settings.scheds||[];
|
||||
if (!scheds.length) {return;}
|
||||
const now = new Date(),
|
||||
hr = now.getHours()+(now.getMinutes()/60)+(now.getSeconds()/3600); // current (decimal) hour
|
||||
scheds.sort((a, b) => a.hr-b.hr);
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
/**
|
||||
* Apply LCD options for given mode
|
||||
* @param {int} mode Quiet Mode
|
||||
*/
|
||||
exports.applyOptions = function(mode) {
|
||||
const s = require("Storage").readJSON(mode ? "qmsched.json" : "setting.json", 1) || {};
|
||||
const get = (k, d) => k in s ? s[k] : d;
|
||||
Bangle.setOptions(get("options", {}));
|
||||
Bangle.setLCDBrightness(get("brightness", 1));
|
||||
Bangle.setLCDTimeout(get("timeout", 10));
|
||||
};
|
||||
/**
|
||||
* Set new Quiet Mode and apply Bangle options
|
||||
* @param {int} mode Quiet Mode
|
||||
*/
|
||||
exports.setMode = function(mode) {
|
||||
let s = require("Storage").readJSON("setting.json", 1) || {};
|
||||
s.quiet = mode;
|
||||
require("Storage").writeJSON("setting.json", s);
|
||||
if (s.options) Bangle.setOptions(s.options);
|
||||
if (mode && s.qmOptions) Bangle.setOptions(s.qmOptions);
|
||||
if (mode && s.qmBrightness) {
|
||||
if (s.qmBrightness!=1) Bangle.setLCDBrightness(s.qmBrightness);
|
||||
} else {
|
||||
if (s.brightness && s.brightness!=1) Bangle.setLCDBrightness(s.brightness);
|
||||
}
|
||||
if (mode && s.qmTimeout) Bangle.setLCDTimeout(s.qmTimeout);
|
||||
if (typeof (WIDGETS)!=="undefined" && "qmsched" in WIDGETS) {WIDGETS["qmsched"].draw();}
|
||||
};
|
||||
require("Storage").writeJSON("setting.json", Object.assign(
|
||||
require("Storage").readJSON("setting.json", 1) || {},
|
||||
{quiet:mode}
|
||||
));
|
||||
exports.applyOptions(mode);
|
||||
if (WIDGETS && "qmsched" in WIDGETS) WIDGETS["qmsched"].draw();
|
||||
};
|
||||
|
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.8 KiB |
|
@ -16,9 +16,9 @@ WIDGETS["qmsched"] = {
|
|||
}
|
||||
let x = this.x, y = this.y;
|
||||
g.clearRect(x, y, x+23, y+23);
|
||||
// quiet mode: draw dim red one-way-street sign
|
||||
// quiet mode: draw red one-way-street sign (dim red on Bangle.js 1)
|
||||
x = this.x+11;y = this.y+11; // center of widget
|
||||
g.setColor(0.8, 0, 0).fillCircle(x, y, 8);
|
||||
g.setColor(process.env.HWVERSION===2 ? 1 : 0.8, 0, 0).fillCircle(x, y, 8);
|
||||
g.setColor(g.theme.bg).fillRect(x-6, y-2, x+6, y+2);
|
||||
if (mode>1) {return;} // no alarms
|
||||
// alarms still on: draw alarm icon in bottom-right corner
|
||||
|
|
|
@ -36,3 +36,4 @@
|
|||
0.31: Remove Bangle 1 settings when running on Bangle 2
|
||||
0.32: Fix 'beep' menu on Bangle.js 2
|
||||
0.33: Really fix 'beep' menu on Bangle.js 2 this time
|
||||
0.34: Remove Quiet Mode LCD settings: now handled by Quiet Mode Schedule app
|
||||
|
|
|
@ -44,6 +44,4 @@ The exact effects depend on the app. In general the watch will not wake up by i
|
|||
- Off: Normal operation
|
||||
- Alarms: Stops notifications, but "alarm" apps will still work
|
||||
- Silent: Blocks even alarms
|
||||
* **LCD Brightness**, **LCD Timeout**, **Wake on X**:
|
||||
Override default settings while Quit Mode is active (either as *Alarms* or *Silent*)
|
||||
|
|
@ -7,17 +7,12 @@ let settings;
|
|||
|
||||
function updateSettings() {
|
||||
//storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same
|
||||
if (Object.keys(settings.qmOptions).length === 0) delete settings.qmOptions;
|
||||
storage.write('setting.json', settings);
|
||||
if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this always exists in this file
|
||||
}
|
||||
|
||||
function updateOptions() {
|
||||
updateSettings();
|
||||
Bangle.setOptions(settings.options)
|
||||
if (settings.quiet) {
|
||||
Bangle.setOptions(settings.qmOptions)
|
||||
}
|
||||
}
|
||||
|
||||
function gToInternal(g) {
|
||||
|
@ -56,18 +51,12 @@ function resetSettings() {
|
|||
twistMaxY: -800,
|
||||
twistTimeout: 1000
|
||||
},
|
||||
// Quiet Mode options:
|
||||
// we only set these if we want to override the default value
|
||||
// qmOptions: {},
|
||||
// qmBrightness: undefined,
|
||||
// qmTimeout: undefined,
|
||||
};
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
settings = storage.readJSON('setting.json', 1);
|
||||
if (!settings) resetSettings();
|
||||
if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this always exists in here
|
||||
|
||||
const boolFormat = v => v ? "On" : "Off";
|
||||
|
||||
|
@ -130,7 +119,16 @@ function showMainMenu() {
|
|||
}
|
||||
}
|
||||
},
|
||||
"Quiet Mode": ()=>showQuietModeMenu(),
|
||||
"Quiet Mode": {
|
||||
value: settings.quiet|0,
|
||||
format: v => ["Off", "Alarms", "Silent"][v%3],
|
||||
onchange: v => {
|
||||
settings.quiet = v%3;
|
||||
updateSettings();
|
||||
updateOptions();
|
||||
if ("qmsched" in WIDGETS) WIDGETS["qmsched"].draw();
|
||||
},
|
||||
},
|
||||
'Locale': ()=>showLocaleMenu(),
|
||||
'Select Clock': ()=>showClockMenu(),
|
||||
'Set Time': ()=>showSetTimeMenu(),
|
||||
|
@ -352,9 +350,7 @@ function showLCDMenu() {
|
|||
onchange: v => {
|
||||
settings.brightness = v || 1;
|
||||
updateSettings();
|
||||
if (!(settings.quiet && "qmBrightness" in settings)) {
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
},
|
||||
'LCD Timeout': {
|
||||
|
@ -365,9 +361,7 @@ function showLCDMenu() {
|
|||
onchange: v => {
|
||||
settings.timeout = 0 | v;
|
||||
updateSettings();
|
||||
if (!(settings.quiet && "qmTimeout" in settings)) {
|
||||
Bangle.setLCDTimeout(settings.timeout);
|
||||
}
|
||||
Bangle.setLCDTimeout(settings.timeout);
|
||||
}
|
||||
},
|
||||
'Wake on BTN1': {
|
||||
|
@ -455,105 +449,6 @@ function showLCDMenu() {
|
|||
});
|
||||
return E.showMenu(lcdMenu)
|
||||
}
|
||||
function showQuietModeMenu() {
|
||||
// we always keep settings.quiet and settings.qmOptions
|
||||
// other qm values are deleted when not set
|
||||
const modes = ["Off", "Alarms", "Silent"];
|
||||
const qmDisabledFormat = v => v ? "Off" : "-";
|
||||
const qmMenu = {
|
||||
"": {"title": "Quiet Mode"},
|
||||
"< Back": () => showMainMenu(),
|
||||
"Quiet Mode": {
|
||||
value: settings.quiet|0,
|
||||
format: v => modes[v%3],
|
||||
onchange: v => {
|
||||
settings.quiet = v%3;
|
||||
updateSettings();
|
||||
updateOptions();
|
||||
if ("qmsched" in WIDGETS) {WIDGETS["qmsched"].draw();}
|
||||
},
|
||||
},
|
||||
"LCD Brightness": {
|
||||
value: settings.qmBrightness || 0,
|
||||
min: 0, // 0 = use default
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
format: v => (v>0.05) ? v : "-",
|
||||
onchange: v => {
|
||||
if (v>0.05) { // prevent v=0.000000000000001 bugs
|
||||
settings.qmBrightness = v;
|
||||
} else {
|
||||
delete settings.qmBrightness;
|
||||
}
|
||||
updateSettings();
|
||||
if (settings.qmBrightness) { // show result, even if not quiet right now
|
||||
Bangle.setLCDBrightness(v);
|
||||
} else {
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
},
|
||||
},
|
||||
"LCD Timeout": {
|
||||
value: settings.qmTimeout || 0,
|
||||
min: 0, // 0 = use default (no constant on for quiet mode)
|
||||
max: 60,
|
||||
step: 5,
|
||||
format: v => v>1 ? v : "-",
|
||||
onchange: v => {
|
||||
if (v>1) {
|
||||
settings.qmTimeout = v;
|
||||
} else {
|
||||
delete settings.qmTimeout;
|
||||
}
|
||||
updateSettings();
|
||||
if (settings.quiet && v>1) {
|
||||
Bangle.setLCDTimeout(v);
|
||||
} else {
|
||||
Bangle.setLCDTimeout(settings.timeout);
|
||||
}
|
||||
},
|
||||
},
|
||||
// we disable wakeOn* events by overwriting them as false in qmOptions
|
||||
// not disabled = not present in qmOptions at all
|
||||
"Wake on FaceUp": {
|
||||
value: "wakeOnFaceUp" in settings.qmOptions,
|
||||
format: qmDisabledFormat,
|
||||
onchange: () => {
|
||||
if ("wakeOnFaceUp" in settings.qmOptions) {
|
||||
delete settings.qmOptions.wakeOnFaceUp;
|
||||
} else {
|
||||
settings.qmOptions.wakeOnFaceUp = false;
|
||||
}
|
||||
updateOptions();
|
||||
},
|
||||
},
|
||||
"Wake on Touch": {
|
||||
value: "wakeOnTouch" in settings.qmOptions,
|
||||
format: qmDisabledFormat,
|
||||
onchange: () => {
|
||||
if ("wakeOnTouch" in settings.qmOptions) {
|
||||
delete settings.qmOptions.wakeOnTouch;
|
||||
} else {
|
||||
settings.qmOptions.wakeOnTouch = false;
|
||||
}
|
||||
updateOptions();
|
||||
},
|
||||
},
|
||||
"Wake on Twist": {
|
||||
value: "wakeOnTwist" in settings.qmOptions,
|
||||
format: qmDisabledFormat,
|
||||
onchange: () => {
|
||||
if ("wakeOnTwist" in settings.qmOptions) {
|
||||
delete settings.qmOptions.wakeOnTwist;
|
||||
} else {
|
||||
settings.qmOptions.wakeOnTwist = false;
|
||||
}
|
||||
updateOptions();
|
||||
},
|
||||
},
|
||||
};
|
||||
return E.showMenu(qmMenu);
|
||||
}
|
||||
|
||||
function showLocaleMenu() {
|
||||
const localemenu = {
|
||||
|
|
2
core
|
@ -1 +1 @@
|
|||
Subproject commit cd3b4def869cac4d7f18e7329e640e51b26758c8
|
||||
Subproject commit 23854083e0c3f83c649073a2d85e8079efc471d3
|