diff --git a/README.md b/README.md index ee555cad2..b3da9f685 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/android.html b/android.html new file mode 100644 index 000000000..93999008f --- /dev/null +++ b/android.html @@ -0,0 +1,352 @@ + + +
+ + + + + + + + + + + + + + + + ++ STOP! This page must be served over HTTPS. Please reload this page via HTTPS. +
+diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index ed16131a4..4cdf71913 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,8 +5,9 @@ Mike Bennett mike[at]kereru.com 1.14 : Add VMG screen 1.34 : Add bluetooth data stream for Droidscript 1.43 : Keep GPS in SuperE mode while using Droiscript screen mirroring +1.50 : Add cfg.wptSfx one char suffix to append to waypoints.json filename. Protects speedalt2 waypoints from other apps that use the same file name for waypoints. */ -var v = '1.49'; +var v = '1.50'; var vDroid = '1.50'; // Required DroidScript program version /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ @@ -209,7 +210,7 @@ function nxtWp(){ } function loadWp() { - var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + var w = require("Storage").readJSON('waypoints'+cfg.wptSfx+'.json')||[{name:"NONE"}]; if (cfg.wp>=w.length) cfg.wp=0; if (cfg.wp<0) cfg.wp = w.length-1; savSettings(); @@ -718,6 +719,7 @@ cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; cfg.touch = cfg.touch==undefined?true:cfg.touch; +cfg.wptSfx = cfg.wptSfx==undefined?'':cfg.wptSfx; if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); diff --git a/apps/speedalt2/metadata.json b/apps/speedalt2/metadata.json index 4ace46854..2a111af28 100644 --- a/apps/speedalt2/metadata.json +++ b/apps/speedalt2/metadata.json @@ -2,7 +2,7 @@ "id": "speedalt2", "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", - "version":"1.49", + "version":"1.50", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "icon": "app.png", "type": "app", @@ -15,5 +15,11 @@ {"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, {"name":"speedalt2.settings.js","url":"settings.js"} ], - "data": [{"name":"speedalt2.json"}] + "data": [ + {"name":"speedalt2.json"}, + {"name":"waypoints.json"}, + {"name":"waypoints1.json"}, + {"name":"waypoints2.json"}, + {"name":"waypoints3.json"} + ] } diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index babb03061..1bdb58f9d 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -30,6 +30,11 @@ writeSettings(); } + function setSfx(s) { + settings.wptSfx = s; + writeSettings(); + } + const appMenu = { '': {'title': 'GPS Adv Sprt II'}, @@ -38,6 +43,7 @@ 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }, 'Kalman Filter' : function() { E.showMenu(kalMenu); }, + 'Wpt File Suffix' : function() { E.showMenu(sfxMenu); }, 'Touch' : { value : settings.touch, format : v => v?"On":"Off", @@ -69,6 +75,15 @@ 'Inverted' : function() { setColour(3); } }; + const sfxMenu = { + '': {'title': 'Wpt File Suffix'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Default' : function() { setSfx(''); }, + '1' : function() { setSfx('1'); }, + '2' : function() { setSfx('2'); }, + '3' : function() { setSfx('3'); } + }; + const kalMenu = { '': {'title': 'Kalman Filter'}, '< Back': function() { E.showMenu(appMenu); }, diff --git a/apps/terminalclock/ChangeLog b/apps/terminalclock/ChangeLog index b752c829d..ce31583e9 100644 --- a/apps/terminalclock/ChangeLog +++ b/apps/terminalclock/ChangeLog @@ -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 diff --git a/apps/terminalclock/README.md b/apps/terminalclock/README.md index c7452397d..93967e8a7 100644 --- a/apps/terminalclock/README.md +++ b/apps/terminalclock/README.md @@ -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 diff --git a/apps/terminalclock/app.js b/apps/terminalclock/app.js index 61861f745..7dc3bf1d1 100644 --- a/apps/terminalclock/app.js +++ b/apps/terminalclock/app.js @@ -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 diff --git a/apps/terminalclock/metadata.json b/apps/terminalclock/metadata.json index 7bc00bca4..9f76ed8f2 100644 --- a/apps/terminalclock/metadata.json +++ b/apps/terminalclock/metadata.json @@ -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", diff --git a/apps/terminalclock/settings.js b/apps/terminalclock/settings.js index 4b09aad6a..bd860b491 100644 --- a/apps/terminalclock/settings.js +++ b/apps/terminalclock/settings.js @@ -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); -}) +}) \ No newline at end of file diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 850b793f4..81c0f75ac 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -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`); diff --git a/core b/core index 147892754..2054537a9 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 147892754eaf50c8581ebfb4d8651b9ec24aa44e +Subproject commit 2054537a9958f9812ae2cad908b6597ff01e449d diff --git a/lang/it_IT.json b/lang/it_IT.json index 4bc36ee48..310af8580 100644 --- a/lang/it_IT.json +++ b/lang/it_IT.json @@ -192,7 +192,15 @@ "Notifications": "Notifiche", "Scheduler": "Schedulatore", "Stop": "Stop", - "Min Font": "Dimensione minima del font" + "Min Font": "Dimensione minima del font", + "White": "Bianco", + "Red": "Rosso", + "Yellow": "Giallo", + "Cyan": "Ciano", + "Green": "Verde", + "Blue": "Blu", + "Black": "Nero", + "Show Week Number": "Mostra numero settimana" }, "//2": "App-specific overrides", "alarm": { diff --git a/modules/ClockFace.js b/modules/ClockFace.js index 25e2430bf..d6c3a2e66 100644 --- a/modules/ClockFace.js +++ b/modules/ClockFace.js @@ -66,6 +66,10 @@ ClockFace.prototype.tick = function() { }; ClockFace.prototype.start = function() { + /* Some widgets want to know if we're in a clock or not (like chrono, widget clock, etc). Normally + .CLOCK is set by Bangle.setUI('clock') but we want to load widgets so we can check appRect and *then* + call setUI. see #1864 */ + Bangle.CLOCK = 1; Bangle.loadWidgets(); if (this.init) this.init.apply(this); if (this._upDown) Bangle.setUI("clockupdown", d=>this._upDown.apply(this,[d])); @@ -103,4 +107,4 @@ ClockFace.prototype.redraw = function() { this.tick(); }; -exports = ClockFace; \ No newline at end of file +exports = ClockFace; diff --git a/modules/Layout.js b/modules/Layout.js index 019d63815..19cfabe11 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -1,83 +1,16 @@ /* 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 -*/ +// See Layout.md for documentation function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - options = options || {}; - this.lazy = options.lazy || false; + this.options = options || {}; + this.lazy = this.options.lazy || false; - var btnList, uiSet; - Bangle.setUI(); // remove all existing input handlers + var btnList; if (process.env.HWVERSION!=2) { // no touchscreen, find any buttons in 'layout' btnList = []; @@ -91,48 +24,19 @@ function Layout(layout, options) { this.physBtns = 0; this.buttons = btnList; this.selectedButton = -1; - Bangle.setUI({mode:"updown", back:options.back}, dir=>{ - var s = this.selectedButton, l=this.buttons.length; - if (dir===undefined && this.buttons[s]) - return this.buttons[s].cb(); - if (this.buttons[s]) { - delete this.buttons[s].selected; - this.render(this.buttons[s]); - } - s = (s+l+dir) % l; - if (this.buttons[s]) { - this.buttons[s].selected = 1; - this.render(this.buttons[s]); - } - this.selectedButton = s; - }); - uiSet = true; } } - if (options.back && !uiSet) Bangle.setUI({mode: "custom", back: options.back}); - if (options.btns) { - var buttons = options.btns; + if (this.options.btns) { + var buttons = this.options.btns; this.b = buttons; if (this.physBtns >= buttons.length) { - // Handler for button watch events - function pressHandler(btn,e) { - if (e.time-e.lastTime > 0.75 && this.b[btn].cbl) - this.b[btn].cbl(e); - else - if (this.b[btn].cb) this.b[btn].cb(e); - } // enough physical buttons let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); - if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch); - Bangle.btnWatches = []; if (this.physBtns > 2 && buttons.length==1) buttons.unshift({label:""}); // pad so if we have a button in the middle while (this.physBtns > buttons.length) buttons.push({label:""}); - if (buttons[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1})); - if (buttons[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1})); - if (buttons[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1})); this._l.width = g.getWidth()-8; // text width this._l = {type:"h", filly:1, c: [ this._l, @@ -149,19 +53,8 @@ function Layout(layout, options) { if (btnList) btnList.push.apply(btnList, this._l.c[1].c); } } - if (process.env.HWVERSION==2) { - - // Handler for touch events - function touchHandler(l,e) { - if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { - if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e); - } - if (l.c) l.c.forEach(n => touchHandler(n,e)); - } - Bangle.touchHandler = (_,e)=>touchHandler(this._l,e); - Bangle.on('touch',Bangle.touchHandler); - } - + // Link in all buttons/touchscreen/etc + this.setUI(); // recurse over layout doing some fixing up if needed var ll = this; function recurser(l) { @@ -175,16 +68,57 @@ function Layout(layout, options) { this.updateNeeded = true; } -Layout.prototype.remove = function (l) { - if (Bangle.btnWatches) { - Bangle.btnWatches.forEach(clearWatch); - delete Bangle.btnWatches; +Layout.prototype.setUI = function() { + Bangle.setUI(); // remove all existing input handlers + + var uiSet; + if (this.buttons) { + // multiple buttons so we'll jus use back/next/select + Bangle.setUI({mode:"updown", back:this.options.back}, dir=>{ + var s = this.selectedButton, l=this.buttons.length; + if (dir===undefined && this.buttons[s]) + return this.buttons[s].cb(); + if (this.buttons[s]) { + delete this.buttons[s].selected; + this.render(this.buttons[s]); + } + s = (s+l+dir) % l; + if (this.buttons[s]) { + this.buttons[s].selected = 1; + this.render(this.buttons[s]); + } + this.selectedButton = s; + }); + uiSet = true; } - if (Bangle.touchHandler) { - Bangle.removeListener("touch",Bangle.touchHandler); - delete Bangle.touchHandler; + if (this.options.back && !uiSet) Bangle.setUI({mode: "custom", back: this.options.back}); + // physical buttons -> actual applications + if (this.b) { + // Handler for button watch events + function pressHandler(btn,e) { + if (e.time-e.lastTime > 0.75 && this.b[btn].cbl) + this.b[btn].cbl(e); + else + if (this.b[btn].cb) this.b[btn].cb(e); + } + if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch); + Bangle.btnWatches = []; + if (this.b[0]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1})); + if (this.b[1]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1})); + if (this.b[2]) Bangle.btnWatches.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1})); } -}; + // Handle touch events on new Bangle.js + if (process.env.HWVERSION==2) { + function touchHandler(l,e) { + if (l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { + if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e); + } + if (l.c) l.c.forEach(n => touchHandler(n,e)); + } + Bangle.touchHandler = (_,e)=>touchHandler(this._l,e); + Bangle.on('touch',Bangle.touchHandler); + } +} function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol); diff --git a/modules/Layout.md b/modules/Layout.md new file mode 100644 index 000000000..7a4177957 --- /dev/null +++ b/modules/Layout.md @@ -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) diff --git a/typescript/types/globals.d.ts b/typescript/types/globals.d.ts index 2ef52dcdf..e82c3da3d 100644 --- a/typescript/types/globals.d.ts +++ b/typescript/types/globals.d.ts @@ -140,7 +140,7 @@ declare const require: ((module: 'heatshrink') => { declare const Bangle: { // functions - buzz: () => void; + buzz: (duration?: number, intensity?: number) => Promise; drawWidgets: () => void; isCharging: () => boolean; // events @@ -158,9 +158,9 @@ declare type Image = { }; declare type GraphicsApi = { - reset: () => void; + reset: () => GraphicsApi; flip: () => void; - setColor: (color: string) => void; // TODO we can most likely type color more usefully than this + setColor: (color: string) => GraphicsApi; // TODO we can most likely type color more usefully than this drawImage: ( image: string | Image | ArrayBuffer, xOffset: number, @@ -169,7 +169,7 @@ declare type GraphicsApi = { rotate?: number; scale?: number; } - ) => void; + ) => GraphicsApi; // TODO add more };