Merge branch 'master' into icon-notifs

pull/1872/head
Lubomir (Mac) 2022-05-29 13:20:08 +10:00
commit e1481981da
No known key found for this signature in database
GPG Key ID: 2283D4C5BAAD1570
25 changed files with 210 additions and 131 deletions

View File

@ -256,6 +256,7 @@ and which gives information about the app for the Launcher.
// 'clock' - a clock - required for clocks to automatically start
// 'widget' - a widget
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
// 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
// 'RAM' - code that runs and doesn't upload anything to storage
// 'launch' - replacement 'Launcher'
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle

View File

@ -28,3 +28,4 @@
0.26: Add support for Monday as first day of the week (#1780)
0.27: New UI!
0.28: Fix bug with alarms not firing when configured to fire only once
0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday

View File

@ -37,8 +37,8 @@ function handleFirstDayOfWeek(dow) {
return dow;
}
// Check the first day of week and update the dow field accordingly.
alarms.forEach(alarm => alarm.dow = handleFirstDayOfWeek(alarm.dow));
// Check the first day of week and update the dow field accordingly (alarms only!)
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
function showMainMenu() {
const menu = {
@ -158,14 +158,14 @@ function saveAlarm(alarm, alarmIndex, time) {
}
function saveAndReload() {
// Before saving revert the dow to the standard format
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek));
// Before saving revert the dow to the standard format (alarms only!)
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
require("sched").setAlarms(alarms);
require("sched").reload();
// Fix after save
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek));
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
}
function decodeDOW(alarm) {

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.28",
"version": "0.29",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",

View File

@ -11,6 +11,8 @@ const speedUnits = { // how many kph per X?
"kmh": 1,
"kph": 1,
"km/h": 1,
"kmt": 1,
"km/tim": 1,
"mph": 1.60934,
"kts": 1.852
};

View File

@ -67,13 +67,6 @@ function saveMessages() {
require("Storage").writeJSON("messages.json",MESSAGES)
}
function getPosImage() {
return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA==");
}
function getNegImage() {
return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA=");
}
function showMapMessage(msg) {
active = "map";
var m;
@ -300,7 +293,7 @@ function showMessage(msgid) {
var buttons = [
];
if (msg.positive) {
buttons.push({type:"btn", src:getPosImage(), cb:()=>{
buttons.push({type:"btn", src:atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="), cb:()=>{
msg.new = false; saveMessages();
cancelReloadTimeout(); // don't auto-reload to clock now
Bangle.messageResponse(msg,true);
@ -309,7 +302,7 @@ function showMessage(msgid) {
}
if (msg.negative) {
if (buttons.length) buttons.push({width:32}); // nasty hack...
buttons.push({type:"btn", src:getNegImage(), cb:()=>{
buttons.push({type:"btn", src:atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="), cb:()=>{
msg.new = false; saveMessages();
cancelReloadTimeout(); // don't auto-reload to clock now
Bangle.messageResponse(msg,false);

View File

@ -4,3 +4,4 @@
0.04: Fixed issue selecting Frankfurt not saved
0.05: Fixed issue with back option
0.06: renamed source files to match standard
0.07: Move mylocation app into 'Settings -> Apps'

View File

@ -2,6 +2,8 @@
*Sets and stores GPS lat and lon of your preferred city*
To access, go to `Settings -> Apps -> My Location`
* Select one of the preset Cities or setup through the GPS
* Other Apps can read this information to do calculations based on location
* When the City shows ??? it means the location has been set through the GPS

View File

@ -1 +0,0 @@
require("heatshrink").decompress(atob("mEw4UA///gH4AYPO/QPDgNVqtADY/1BYNfBQ0PBQIAB+ALFmoLDrgLF6oLDq4KEgYKDBYPABYcNBYlVuAuIGAwuEAANUBYYKFHgg6Bq4ZCr4DBHgQLBvWq2te1WlBYZGBBYOr1Wq1qSDBYNqBIILDKgQLLgoLHqBqDBfJHLBZBrOgKPCBYiPCU4NaBYe1WYrABBQLCCfgYGCrwVBa4kAirvKNgIAErgLDKgIAEKQQ8EAAY6DBZhIDIww8GHQg8GHQgwGFwowEFwx5EOog8GHQ0AlWpBYNq1AKFWIILBAYOgBYbICytWAgQKCgTgDcwYXGAAgvGAAY8EEgYWGBgoVEA=="))

View File

@ -2,16 +2,15 @@
"name": "My Location",
"shortName":"My Location",
"icon": "app.png",
"type": "app",
"type": "settings",
"screenshots": [{"url":"screenshot_1.png"}],
"version":"0.06",
"version":"0.07",
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
"readme": "README.md",
"tags": "tool,utility",
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"mylocation.app.js","url":"app.js"},
{"name":"mylocation.img","url":"icon.js","evaluate": true }
{"name":"mylocation.settings.js","url":"settings.js"}
],
"data": [
{"name":"mylocation.json"}

View File

@ -1,5 +1,4 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
(function(back) {
const SETTINGS_FILE = "mylocation.json";
let settings;
@ -18,7 +17,7 @@ function loadSettings() {
}
}
function save() {
function saveSettings() {
settings = s;
require('Storage').write(SETTINGS_FILE, settings);
}
@ -34,29 +33,29 @@ function setFromGPS() {
//console.log("fix from GPS");
s = {'lat': gps.lat, 'lon': gps.lon, 'location': '???' };
Bangle.buzz(1500); // buzz on first position
Bangle.setGPSPower(0);
save();
Bangle.setGPSPower(0, "mylocation");
saveSettings();
Bangle.setUI("updown", ()=>{ load(); });
E.showPrompt("Location has been saved from the GPS fix",{
title:"Location Saved",
buttons : {"OK":1}
E.showPrompt(/*LANG*/"Location has been saved from the GPS fix",{
title:/*LANG*/"Location Saved",
buttons : {/*LANG*/"OK":1}
}).then(function(v) {
load(); // load default clock
});
});
Bangle.setGPSPower(1);
E.showMessage("Waiting for GPS fix. Place watch in the open. Could take 10 minutes. Long press to abort", "GPS Running");
Bangle.setGPSPower(1, "mylocation");
E.showMessage(/*LANG*/"Waiting for GPS fix. Place watch in the open. Could take 10 minutes. Long press to abort", "GPS Running");
Bangle.setUI("updown", undefined);
}
function showMainMenu() {
//console.log("showMainMenu");
const mainmenu = {
'': { 'title': 'My Location' },
'< Back': ()=>{ load(); },
'City': {
'': { 'title': /*LANG*/'My Location' },
'< Back': ()=>{ back(); },
/*LANG*/'City': {
value: 0 | locations.indexOf(s.location),
min: 0, max: locations.length - 1,
format: v => locations[v],
@ -65,14 +64,15 @@ function showMainMenu() {
s.location = locations[v];
s.lat = lats[v];
s.lon = lons[v];
save();
saveSettings();
}
}
},
'Set From GPS': ()=>{ setFromGPS(); }
/*LANG*/'Set From GPS': ()=>{ setFromGPS(); }
};
return E.showMenu(mainmenu);
}
loadSettings();
showMainMenu();
})

View File

@ -4,6 +4,7 @@
"version":"0.01",
"description": "A clock with R2D2's shiny metal face on it. :)",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],

View File

@ -6,7 +6,7 @@
"icon": "rndmclk.png",
"type": "widget",
"tags": "widget,clock",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"rndmclk.wid.js","url":"widget.js"}

View File

@ -49,3 +49,4 @@
0.44: Add "Start Week On X" option (#1780)
UI improvements to Locale and Date & Time menu
0.45: Add calibrate battery option
0.46: Fix regression after making 'calibrate battery' only for Bangle.js 2

View File

@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.45",
"version": "0.46",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",

View File

@ -545,8 +545,10 @@ function showUtilMenu() {
setInterval(function() {
var i=1000;while (i--);
}, 1);
},
/*LANG*/'Calibrate Battery': () => {
}
};
if (BANGLEJS2)
menu[/*LANG*/'Calibrate Battery'] = () => {
E.showPrompt(/*LANG*/"Is the battery fully charged?",{title:/*LANG*/"Calibrate"}).then(ok => {
if (ok) {
var s=require("Storage").readJSON("setting.json");
@ -557,8 +559,8 @@ function showUtilMenu() {
E.showAlert(/*LANG*/"Please charge Bangle.js for 3 hours and try again").then(() => load("settings.app.js"));
}
});
},
/*LANG*/'Reset Settings': () => {
};
menu[/*LANG*/'Reset Settings'] = () => {
E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => {
if (v) {
E.showMessage('Resetting');
@ -566,9 +568,9 @@ function showUtilMenu() {
setTimeout(showMainMenu, 50);
} else showUtilMenu();
});
},
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }
};
};
menu[/*LANG*/'Turn Off'] = ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() };
if (Bangle.factoryReset) {
menu[/*LANG*/'Factory Reset'] = ()=>{
E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset"}).then((v) => {

View File

@ -3,3 +3,4 @@
0.03: Add Banglejs 1 compatibility
0.04: Fix settings bug
0.05: Add altitude display (only Bangle.js 2)
0.06: Add power related settings to control the HR and pressure(altitude) sensor from the watchface

View File

@ -8,3 +8,8 @@ It can display :
- hrm
- motion
- steps
"Power saving" setting control the HR and pressure (altitude) sensors.
If "Off" they will always be on.
If "On" the sensors will be turned on every "Power on interval" minutes for 45 secondes

View File

@ -3,15 +3,14 @@ var fontColor = g.theme.dark ? "#0f0" : "#000";
var heartRate = 0;
var altitude = -9001;
// handling the differents versions of the Banglejs smartwatch
// handling the differents versions of the Banglejs smartwatch screen sizes
if (process.env.HWVERSION == 1){
var paddingY = 3;
var font6x8At4Size = 48;
var font6x8At2Size = 27;
var font6x8FirstTextSize = 6;
var font6x8DefaultTextSize = 3;
}
else{
} else{
var paddingY = 2;
var font6x8At4Size = 32;
var font6x8At2Size = 18;
@ -66,7 +65,7 @@ function drawDate(now, pos){
drawLine(locale_date, pos);
}
function drawInput(now, pos){
function drawInput(pos){
clearField(pos);
drawLine(">", pos);
}
@ -129,16 +128,52 @@ function draw(){
drawStepCount(curPos);
curPos++;
}
drawInput(now, curPos);
drawInput(curPos);
}
function turnOnServices(){
if(settings.showHRM){
Bangle.setHRMPower(true, "terminalclock");
}
if(settings.showAltitude && process.env.HWVERSION != 1){
Bangle.setBarometerPower(true, "terminalclock");
}
if(settings.powerSaving){
setTimeout(function () {
turnOffServices();
}, 45000);
}
}
function turnOffServices(){
if(settings.showHRM){
Bangle.setHRMPower(false, "terminalclock");
}
if(settings.showAltitude && process.env.HWVERSION != 1){
Bangle.setBarometerPower(false, "terminalclock");
}
}
var unlockDrawIntervalID = -1;
Bangle.on('lock', function(on){
if(!on){ // unclock
if(settings.powerSaving){
turnOnServices();
}
unlockDrawIntervalID = setInterval(draw, 1000); // every second
}
if(on && unlockDrawIntervalID != -1){ // lock
clearInterval(unlockDrawIntervalID);
}
});
Bangle.on('HRM',function(hrmInfo) {
if(hrmInfo.confidence >= settings.HRMinConfidence)
heartRate = hrmInfo.bpm;
});
var MEDIANLENGTH = 20;
var avr = [], median;
var MEDIANLENGTH = 20; // technical
var avr = [], median; // technical
Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude);
@ -161,18 +196,20 @@ var settings = Object.assign({
showActivity: true,
showStepCount: true,
showAltitude: process.env.HWVERSION != 1 ? true : false,
powerSaving: true,
PowerOnInterval: 15,
}, require('Storage').readJSON("terminalclock.json", true) || {});
if(settings.showAltitude && process.env.HWVERSION != 1){
Bangle.setBarometerPower(true, "app");
// turn the services before drawing anything
turnOnServices();
if(settings.powerSaving){
setInterval(turnOnServices, settings.PowerOnInterval*60000); // every PowerOnInterval min
}
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
// Load and draw widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// draw immediately at first
draw();
var secondInterval = setInterval(draw, 10000);
setInterval(draw, 10000); // every 10 seconds

View File

@ -3,7 +3,7 @@
"name": "Terminal Clock",
"shortName":"Terminal Clock",
"description": "A terminal cli like clock displaying multiple sensor data",
"version":"0.05",
"version":"0.06",
"icon": "app.png",
"type": "clock",
"tags": "clock",

View File

@ -8,6 +8,8 @@
showHRM: true,
showActivity: true,
showStepCount: true,
powerSaving: true,
PowerOnInterval: 15,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
@ -65,10 +67,29 @@
settings.showStepCount = v;
writeSettings();
}
},
'Power saving': {
value: settings.powerSaving,
format: v => v?"On":"Off",
onchange: v => {
settings.powerSaving = v;
writeSettings();
}
},
'Power on interval': {
value: settings.PowerOnInterval,
min: 3, max: 60,
onchange: v => {
settings.PowerOnInterval = v;
writeSettings();
},
format: x => {
return x + " min";
}
}
}
if (process.env.HWVERSION == 1) {
delete menu['Show Altitude']
}
E.showMenu(menu);
})
})

View File

@ -65,7 +65,7 @@ const APP_KEYS = [
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports'];
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports'
const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale"]; // values allowed for "type" field
const METADATA_TYPES = ["app","clock","widget","bootloader","RAM","launch","textinput","scheduler","notify","locale","settings"]; // values allowed for "type" field
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"];
@ -140,7 +140,7 @@ apps.forEach((app,appIdx) => {
ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`);
if (app.dependencies[dependency]=="type" && !METADATA_TYPES.includes(dependency))
ERROR(`App ${app.id} 'type' dependency must be one of `+METADATA_TYPES);
});
} else
ERROR(`App ${app.id} 'dependencies' must be an object`);

2
core

@ -1 +1 @@
Subproject commit 404e981834f2e8df9c505a8fab12ae12fe3bd562
Subproject commit 2054537a9958f9812ae2cad908b6597ff01e449d

View File

@ -1,74 +1,6 @@
/* Copyright (c) 2022 Bangle.js contributors. See the file LICENSE for copying permission. */
/*
Take a look at README.md for hints on developing with this library.
Usage:
```
var Layout = require("Layout");
var layout = new Layout( layoutObject, options )
layout.render(optionalObject);
```
For example:
```
var Layout = require("Layout");
var layout = new Layout( {
type:"v", c: [
{type:"txt", font:"20%", label:"12:00" },
{type:"txt", font:"6x8", label:"The Date" }
]
});
g.clear();
layout.render();
```
layoutObject has:
* A `type` field of:
* `undefined` - blank, can be used for padding
* `"txt"` - a text label, with value `label`. 'font' is required
* `"btn"` - a button, with value `label` and callback `cb`
optional `src` specifies an image (like img) in which case label is ignored
Default font is `6x8`, scale 2. This can be overridden with the `font` or `scale` fields.
* `"img"` - an image where `src` is an image, or a function which is called to return an image to draw.
* `"custom"` - a custom block where `render(layoutObj)` is called to render
* `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
* `"v"` - Vertical layout, `c` is an array of more `layoutObject`
* A `id` field. If specified the object is added with this name to the
returned `layout` object, so can be referenced as `layout.foo`
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height. Set scale with :, e.g. `6x8:2`.
* A `scale` field, eg `2` to set scale of an image
* A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°).
* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height`
and `fillx`/`filly` to be set. Not compatible with text rotation.
* A `col` field, eg `#f00` for red
* A `bgCol` field for background color (will automatically fill on render)
* A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
* A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
* A `pad` integer field to set pixels padding
* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
* `width` and `height` fields to optionally specify minimum size
options is an object containing:
* `lazy` - a boolean specifying whether to enable automatic lazy rendering
* `btns` - array of objects containing:
* `label` - the text on the button
* `cb` - a callback function
* `cbl` - a callback function for long presses
* `back` - a callback function, passed as `back` into Bangle.setUI (which usually adds an icon in the top left)
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
Once `layout.update()` is called, the following fields are added
to each object:
* `x` and `y` for the top left position
* `w` and `h` for the width and height
* `_w` and `_h` for the **minimum** width and height
Other functions:
* `layout.update()` - update positions of everything if contents have changed
* `layout.debug(obj)` - draw outlines for objects on screen
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render
* `layout.setUI()` - (called when module initialised) This sets up input (buttons, touch, etc) with Bangle._setUI
This can be useful if you called E.showMenu/showPrompt/etc and those grabbed input away from layour
*/
// See Layout.md for documentation
function Layout(layout, options) {
this._l = this.l = layout;

81
modules/Layout.md Normal file
View File

@ -0,0 +1,81 @@
Bangle.js Layout Library
========================
> Take a look at README.md for hints on developing with this library.
Usage
-----
```JS
var Layout = require("Layout");
var layout = new Layout(layoutObject, options)
layout.render(optionalObject);
```
For example:
```JS
var Layout = require("Layout");
var layout = new Layout({
type:"v",
c: [
{ type: "txt", font: "20%", label: "12:00" },
{ type: "txt", font: "6x8", label: "The Date" }
]
});
g.clear();
layout.render();
```
`layoutObject` has:
- A `type` field of:
- `undefined` - blank, can be used for padding
- `"txt"` - a text label, with value `label`. `font` is required
- `"btn"` - a button, with value `label` and callback `cb`. Optional `src` specifies an image (like img) in which case label is ignored. Default font is `6x8`, scale 2. This can be overridden with the `font` or `scale` fields.
- `"img"` - an image where `src` is an image, or a function which is called to return an image to draw
- `"custom"` - a custom block where `render(layoutObj)` is called to render
- `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
- `"v"` - Vertical layout, `c` is an array of more `layoutObject`
- A `id` field. If specified the object is added with this name to the returned `layout` object, so can be referenced as `layout.foo`
- A `font` field, eg `6x8` or `30%` to use a percentage of screen height. Set scale with :, e.g. `6x8:2`.
- A `scale` field, eg `2` to set scale of an image
- A `r` field to set rotation of text or images (0: 0°, 1: 90°, 2: 180°, 3: 270°).
- A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` and `fillx`/`filly` to be set. Not compatible with text rotation.
- A `col` field, eg `#f00` for red
- A `bgCol` field for background color (will automatically fill on render)
- A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
- A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
- A `pad` integer field to set pixels padding
- A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
- A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
- `width` and `height` fields to optionally specify minimum size options is an object containing:
- `lazy` - a boolean specifying whether to enable automatic lazy rendering
- `btns` - array of objects containing:
- `label` - the text on the button
- `cb` - a callback function
- `cbl` - a callback function for long presses
- `back` - a callback function, passed as `back` into Bangle.setUI (which usually adds an icon in the top left)
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
Once `layout.update()` is called, the following fields are added to each object:
- `x` and `y` for the top left position
- `w` and `h` for the width and height
- `_w` and `_h` for the **minimum** width and height
Other functions:
- `layout.update()` - update positions of everything if contents have changed
- `layout.debug(obj)` - draw outlines for objects on screen
- `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
- `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render
Links
-----
- [Official tutorial](https://www.espruino.com/Bangle.js+Layout)