Merge branch 'master' into custom_theme

master
Gordon Williams 2021-10-06 19:55:00 +01:00 committed by GitHub
commit 2b5f1228b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 524 additions and 379 deletions

View File

@ -42,7 +42,7 @@
"name": "Launcher (Default)",
"shortName":"Launcher",
"icon": "app.png",
"version":"0.06",
"version":"0.07",
"description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
"tags": "tool,system,launcher,b2",
"type":"launch",
@ -107,7 +107,7 @@
"name": "Fullscreen Notifications",
"shortName":"Notifications",
"icon": "notify.png",
"version":"0.11",
"version":"0.12",
"description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.",
"tags": "widget,b2",
"type": "notify",
@ -199,11 +199,11 @@
"sortorder" : -2
},
{ "id": "alarm",
"name": "Default Alarm",
"name": "Default Alarm & Timer",
"shortName":"Alarms",
"icon": "app.png",
"version":"0.12",
"description": "Set and respond to alarms",
"version":"0.13",
"description": "Set and respond to alarms and timers",
"tags": "tool,alarm,widget,b2",
"storage": [
{"name":"alarm.app.js","url":"app.js"},
@ -585,7 +585,7 @@
{ "id": "weather",
"name": "Weather",
"icon": "icon.png",
"version":"0.08",
"version":"0.10",
"description": "Show Gadgetbridge weather report",
"readme": "readme.md",
"tags": "widget,outdoors",
@ -1187,7 +1187,7 @@
{ "id": "widclk",
"name": "Digital clock widget",
"icon": "widget.png",
"version":"0.05",
"version":"0.06",
"description": "A simple digital clock widget",
"tags": "widget,clock",
"type":"widget",
@ -1236,9 +1236,9 @@
{ "id": "demoapp",
"name": "Demo Loop",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information",
"tags": "",
"tags": "bno2",
"type":"app",
"allow_emulator":true,
"storage": [
@ -1328,14 +1328,13 @@
"id": "grocery",
"name": "Grocery",
"icon": "grocery.png",
"version":"0.01",
"version":"0.02",
"description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.",
"tags": "tool,outdoors,shopping,list",
"type": "app",
"custom":"grocery.html",
"storage": [
{"name":"grocery"},
{"name":"grocery.app.js"},
{"name":"grocery.app.js", "url":"app.js"},
{"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
]
},
@ -1381,7 +1380,7 @@
{ "id": "barclock",
"name": "Bar Clock",
"icon": "clock-bar.png",
"version":"0.07",
"version":"0.08",
"description": "A simple digital clock showing seconds as a bar",
"tags": "clock",
"type":"clock",
@ -2058,7 +2057,7 @@
"name": "Find Phone",
"shortName":"Find Phone",
"icon": "app.png",
"version":"0.02",
"version":"0.03",
"description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.",
"tags": "tool,android",
"readme": "README.md",
@ -3520,10 +3519,11 @@
{ "id": "antonclk",
"name": "Anton Clock",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "A simple clock using the bold Anton font.",
"tags":"clock,b2",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"antonclk.app.js","url":"app.js"},
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
@ -3532,10 +3532,11 @@
{ "id": "waveclk",
"name": "Wave Clock",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**",
"tags":"clock,b2",
"type":"clock",
"allow_emulator":true,
"storage": [
{"name":"waveclk.app.js","url":"app.js"},
{"name":"waveclk.img","url":"app-icon.js","evaluate":true}

View File

@ -10,3 +10,5 @@
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
0.11: Respect Quiet Mode
0.12: Fix widget for bangle 2, now uses theme
Widgets now shown on Alarm screen
Alarm widget state now updates when setting/resetting an alarm

View File

@ -18,8 +18,10 @@ function showAlarm(alarm) {
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showPrompt(msg,{
title:"ALARM!",
title:alarm.timer ? "TIMER!" : "ALARM!",
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;

View File

@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[];
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
}
];*/
@ -18,6 +19,12 @@ function formatTime(t) {
return hrs+":"+("0"+mins).substr(-2);
}
function formatMins(t) {
mins = (0|t)%60;
hrs = 0|(t/60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
@ -25,17 +32,24 @@ function getCurrentHr() {
function showMainMenu() {
const menu = {
'': { 'title': 'Alarms' },
'New Alarm': ()=>editAlarm(-1)
'': { 'title': 'Alarm/Timer' },
'New Alarm': ()=>editAlarm(-1),
'New Timer': ()=>editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += " (repeat)";
if (alarm.timer) {
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
} else {
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += " (repeat)";
}
menu[txt] = function() {
editAlarm(idx);
if (alarm.timer) editTimer(idx);
else editAlarm(idx);
};
});
menu['< Back'] = ()=>{load();};
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
}
@ -55,7 +69,7 @@ function editAlarm(alarmIndex) {
as = a.as;
}
const menu = {
'': { 'title': 'Alarms' },
'': { 'title': 'Alarm' },
'Hours': {
value: hrs,
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
@ -109,4 +123,59 @@ function editAlarm(alarmIndex) {
return E.showMenu(menu);
}
function editTimer(alarmIndex) {
var newAlarm = alarmIndex<0;
var hrs = 0;
var mins = 5;
var en = true;
if (!newAlarm) {
var a = alarms[alarmIndex];
mins = (0|a.timer)%60;
hrs = 0|(a.timer/60);
en = a.on;
}
const menu = {
'': { 'title': 'Timer' },
'Hours': {
value: hrs,
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Minutes': {
value: mins,
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Enabled': {
value: en,
format: v=>v?"On":"Off",
onchange: v=>en=v
}
};
function getTimer() {
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
// Save alarm
return {
on : en,
timer : (hrs*60)+mins,
hr : hr,
rp : false, as: false
};
}
menu["> Save"] = function() {
if (newAlarm) alarms.push(getTimer());
else alarms[alarmIndex] = getTimer();
require("Storage").write("alarm.json",JSON.stringify(alarms));
showMainMenu();
};
if (!newAlarm) {
menu["> Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
showMainMenu();
};
}
menu['< Back'] = showMainMenu;
return E.showMenu(menu);
}
showMainMenu();

View File

@ -1,11 +1,7 @@
(() => {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
alarms = alarms.filter(alarm=>alarm.on);
if (!alarms.length) return; // no alarms, no widget!
delete alarms;
// add the widget
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
g.setColor(g.theme.fg);
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
}};
})()
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Load widgets after setUI so widclk knows when to hide

View File

@ -19,7 +19,7 @@ function queueDraw() {
function draw() {
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
g.reset();
var date = new Date();
var timeStr = require("locale").time(date,1);
var dateStr = require("locale").date(date).toUpperCase();
@ -33,7 +33,7 @@ function draw() {
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
g.drawString(dateStr,x,y);
// queue draw in one minute
queueDraw();
queueDraw();
}
// Clear the screen once, at startup
@ -49,9 +49,8 @@ Bangle.on('lcdPower',on=>{
drawTimeout = undefined;
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// Show launcher when middle button pressed
Bangle.setUI("clock");

View File

@ -5,3 +5,4 @@
0.05: Clock does not start if app Languages is not installed
0.06: Improve accuracy
0.07: Update to use Bangle.setUI instead of setWatch
0.08: Use theme colors, Layout library

View File

@ -3,167 +3,102 @@
* A simple digital clock showing seconds as a bar
**/
// Check settings for what type our clock should be
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
let locale = require('locale')
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
let locale = require("locale");
{ // add some more info to locale
let date = new Date()
date.setFullYear(1111)
date.setMonth(1, 3) // februari: months are zero-indexed
const localized = locale.date(date, true)
locale.dayFirst = /3.*2/.test(localized)
let date = new Date();
date.setFullYear(1111);
date.setMonth(1, 3); // februari: months are zero-indexed
const localized = locale.date(date, true);
locale.dayFirst = /3.*2/.test(localized);
locale.hasMeridian = false
if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed
locale.hasMeridian = (locale.meridian(date) !== '')
locale.hasMeridian = false;
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
locale.hasMeridian = (locale.meridian(date)!=="");
}
}
const screen = {
width: g.getWidth(),
height: g.getWidth(),
middle: g.getWidth() / 2,
center: g.getHeight() / 2,
Bangle.loadWidgets();
function renderBar(l) {
if (!this.fraction) {
// zero-size fillRect stills draws one line of pixels, we don't want that
return;
}
const width = this.fraction*l.w;
g.fillRect(l.x, l.y, width-1, l.y+l.height-1);
}
// hardcoded "settings"
const settings = {
time: {
color: -1,
font: '6x8',
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
middle: screen.middle,
center: screen.center,
ampm: {
color: -1,
font: '6x8',
size: 2,
const Layout = require("Layout");
const layout = new Layout({
type: "v", c: [
{
type: "h", c: [
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
],
},
},
date: {
color: -1,
font: 'Vector',
size: 20,
middle: screen.height - 20, // at bottom of screen
center: screen.center,
},
bar: {
color: -1,
top: 155, // just below time
thickness: 6, // matches 24h time "pixel" size
},
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
{height: 40},
{id: "date", type: "txt", font: "10%", valign: 1},
],
}, false, {lazy: true});
// adjustments based on screen size and whether we display am/pm
let thickness; // bar thickness, same as time font "pixel block" size
if (is12Hour) {
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
thickness = Math.floor((g.getWidth()-24)/(5*6));
} else {
layout.ampm.label = "";
thickness = Math.floor(g.getWidth()/(5*6));
}
layout.bar.height = thickness+1;
layout.time.font = "6x8:"+thickness;
layout.update();
const SECONDS_PER_MINUTE = 60
const timeText = function (date) {
function timeText(date) {
if (!is12Hour) {
return locale.time(date, true)
return locale.time(date, true);
}
const date12 = new Date(date.getTime())
const hours = date12.getHours()
if (hours === 0) {
date12.setHours(12)
} else if (hours > 12) {
date12.setHours(hours - 12)
const date12 = new Date(date.getTime());
const hours = date12.getHours();
if (hours===0) {
date12.setHours(12);
} else if (hours>12) {
date12.setHours(hours-12);
}
return locale.time(date12, true)
return locale.time(date12, true);
}
const ampmText = function (date) {
return is12Hour ? locale.meridian(date) : ''
function ampmText(date) {
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
}
const dateText = function (date) {
function dateText(date) {
const dayName = locale.dow(date, true),
month = locale.month(date, true),
day = date.getDate()
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
return `${dayName} ${dayMonth}`
day = date.getDate();
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
return `${dayName} ${dayMonth}`;
}
const drawDateTime = function (date) {
const t = settings.time
g.setColor(t.color)
g.setFont(t.font, t.size)
g.setFontAlign(0, 0) // centered
g.drawString(timeText(date), t.center, t.middle, true)
if (is12Hour && locale.hasMeridian) {
const a = settings.time.ampm
g.setColor(a.color)
g.setFont(a.font, a.size)
g.setFontAlign(1, -1) // right top
// at right edge of screen, aligned with time bottom
const left = screen.width - a.size * 2,
top = t.middle + t.size - a.size
g.drawString(ampmText(date), left, top, true)
}
draw = function draw() {
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
const date = new Date();
layout.time.label = timeText(date);
layout.ampm.label = ampmText(date);
layout.date.label = dateText(date);
const SECONDS_PER_MINUTE = 60;
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
layout.render();
// schedule update at start of next second
const millis = date.getMilliseconds();
setTimeout(draw, 1000-millis);
};
const d = settings.date
g.setColor(d.color)
g.setFont(d.font, d.size)
g.setFontAlign(0, 0) // centered
g.drawString(dateText(date), d.center, d.middle, true)
}
const drawBar = function (date) {
const b = settings.bar
const seconds = date.getSeconds()
if (seconds === 0) {
// zero-size rect stills draws one line of pixels, we don't want that
return
}
const fraction = seconds / SECONDS_PER_MINUTE,
width = fraction * screen.width
g.setColor(b.color)
g.fillRect(0, b.top, width, b.top + b.thickness)
}
const clearScreen = function () {
g.setColor(0)
const timeTop = settings.time.middle - (settings.time.size * 4)
g.fillRect(0, timeTop, screen.width, screen.height)
}
let lastSeconds, tTick
const tick = function () {
g.reset()
const date = new Date()
const seconds = date.getSeconds()
if (lastSeconds > seconds) {
// new minute
clearScreen()
drawDateTime(date)
}
// the bar only gets larger, so drawing on top of the previous one is fine
drawBar(date)
lastSeconds = seconds
// schedule next update
const millis = date.getMilliseconds()
tTick = setTimeout(tick, 1000-millis)
}
const start = function () {
lastSeconds = 99 // force redraw
tick()
}
const stop = function () {
if (tTick) {
clearTimeout(tTick)
tTick = undefined
}
}
// clean app screen
g.clear()
Bangle.loadWidgets()
Bangle.drawWidgets()
// Show launcher when button pressed
Bangle.setUI("clock");
Bangle.on('lcdPower', function (on) {
Bangle.on("lcdPower", function(on) {
if (on) {
start()
} else {
stop()
draw();
}
})
start()
});
g.reset().clear();
Bangle.drawWidgets();
draw();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Minor adjustment to fix out of memory errors

View File

@ -60,8 +60,8 @@ var scenes = [
};
},
function() {
Bangle.setLCDMode("120x120");
var img = require("heatshrink").decompress(atob("oNBxH+5wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHGpAAoQKv4ADCBQAeqsrAAejBw9/B4oABqt/IGepHw5CEQspALH5hBC5pAvv4/MAALFkIBWpPI6IHqpAu0Z3GfYOpRYdPQEhALYIp2FBYNVI4JAvvL4LH0yBYAFJAQQQ5Ay1JAFftBAQBYxCDv+qIGiCHIQiGnIBfOv5BJIQRAyIJkrvKEkIBrFBB4qEGIGRCNYsZAQIQV/IZDEiICRCDQVJAUIQVPC4lVIF6yJQYpAZ5t/FYvNIBepqtVIJGjIDoqBDY2pdYo3DfAhBIQLmpvIcDvIrC5oJEIAhTCGQmj5qgEC4t5e7YrBqt5BI6UFBg15v4XHbQwAQb4oAKv7NKABdVRoYATUAwnICqjZFIMdVE4+jXI4XGYCxBFFZN/M5OpCxUrvJ/ZFYmjvNVAAY+KCwpDBC6YAV5vNC9oA/AH4A/AHYA=="));
g.clear();
y = 0;
var step = 4;
@ -70,8 +70,7 @@ var scenes = [
g.clear();
g.drawImage(img,60,60,{rotate:Math.sin(y*0.03)*0.5});
g.flip();
}, 20);
Bangle.setLCDMode("120x120");
}, 20);
return function() {
if (i) clearInterval(i);
};

View File

@ -1,2 +1,3 @@
0.01: First Version
0.02: Remove HID requirement, update screen
0.03: Fix for Bangle 2, toggle find with top half of screen, exit touch bottom half of screen

View File

@ -6,3 +6,8 @@ Ring your phone via GadgetBridge if you lost it somewhere.
2. Lose phone
3. Open app
4. Click any button or screen
## On a Bangle 2
- You can touch the top half of the screen to toggle Find / Stop
- You can touch the bottom half of the screen to exit the app.

View File

@ -1,13 +1,15 @@
//notify your phone
const fontSize = g.getWidth() / 8;
var finding = false;
function draw() {
// show message
g.clear(1);
require("Font8x12").add(Graphics);
g.setFont("8x12",3);
g.clear(g.theme.bg);
g.setColor(g.theme.fg);
g.setFont("Vector", fontSize);
g.setFontAlign(0,0);
if (finding) {
g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20);
g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20);
@ -17,17 +19,37 @@ function draw() {
g.flip();
}
function findPhone(v) {
Bluetooth.println(JSON.stringify({t:"findPhone", n:v}));
}
function find(){
finding = !finding;
draw();
Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding}));
findPhone(finding);
}
draw();
//register all buttons and screen to find phone
setWatch(find, BTN1, {repeat:true});
setWatch(find, BTN2, {repeat:true});
setWatch(find, BTN3, {repeat:true});
setWatch(find, BTN4, {repeat:true});
setWatch(find, BTN5, {repeat:true});
if (process.env.HWVERSION == 1) {
setWatch(find, BTN2, {repeat:true});
setWatch(find, BTN3, {repeat:true});
setWatch(find, BTN4, {repeat:true});
setWatch(find, BTN5, {repeat:true});
}
if (process.env.HWVERSION == 2) {
Bangle.on('touch', function(button, xy) {
// click top part of the screen to stop start
if (xy.y < g.getHeight() / 2) {
find();
} else {
findPhone(false);
setTimeout(load, 100); // exit in 100ms
}
});
}

View File

@ -52,3 +52,62 @@ Activity reporting
You'll need a Gadgetbridge release *after* version 0.50.0 for Actvity Reporting to be enabled.
By default heart rate isn't reported, but it can be enabled from `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Record HRM`
## Troubleshooting
1. Switch to using one of the stock watch faces like s7clk or wave on a Bangle 2.
2. Check that the battery charge level is being seen by the Gadgetbridge App on the phone. This proves that data is getting to your phone.
### You can test the notifications on the Bangle
First disconnect from Gadgetbridge on your phone. Then connect
through the IDE and enter the following code. You should get a pop
up screen on your Bangle. This proves that the watch is correctly
setup for notifications.
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
NOTE: On a Bangle 2, this will fail if you have not installed 'Notifications Fullscreen'.
### Check that notifications are getting through to your Bangle
* Disconnect your Bangle from Gadgetbridge on your phone.
* Connect through the IDE
* Run the following bit of code
var log = [];
function GB(d) {
log.push(JSON.stringify(d));
}
* Disconnect from the IDE
* Connect your Bangle to Gadgetbridge
* Call your phone to get a missed call
* Disonnect your Bangle to Gadgetbridge
* Connect through the IDE
* Run the following bit of code
log;
If notifications are getting through then you should see something like.
>log
=[
"{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}",
"{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}"
]
IMPORTANT: Now reset your Bangle using a BTN3 long press so that the GB() function is restored.
## References
[Bangle Gadgetbridge Page](https://www.espruino.com/Gadgetbridge)
[Gadgetbridge Project Home](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Home)

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Refactor code to store grocery list in separate file

29
apps/grocery/app.js Normal file
View File

@ -0,0 +1,29 @@
var filename = 'grocery_list.json';
var settings = require("Storage").readJSON(filename,1)|| { products: [] };
function updateSettings() {
require("Storage").writeJSON(filename, settings);
Bangle.buzz();
}
function twoChat(n){
if(n<10) return '0'+n;
return ''+n;
}
const mainMenu = settings.products.reduce(function(m, p, i){
const name = twoChat(p.quantity)+' '+p.name;
m[name] = {
value: p.ok,
format: v => v?'[x]':'[ ]',
onchange: v => {
settings.products[i].ok = v;
updateSettings();
}
};
return m;
}, {
'': { 'title': 'Grocery list' }
});
mainMenu['< Back'] = ()=>{load();};
E.showMenu(mainMenu);

View File

@ -105,56 +105,9 @@
}
document.getElementById("upload").addEventListener("click", function() {
var app = `
var newTime = ${Date.now()}
var products = ${JSON.stringify(products)}
var newTime = newTime;
var filename = 'grocery';
var settings = require("Storage").readJSON(filename,1)|| null;
function getSettings(){
return {
products : products,
date: newTime
};
}
if(!settings || !settings.date || settings.date < newTime){
settings = getSettings();
Bangle.buzz(500);
}
function updateSettings() {
require("Storage").writeJSON(filename, settings);
Bangle.buzz();
}
function twoChat(n){
if(n<10) return '0'+n;
return ''+n;
}
const mainMenu = settings.products.reduce(function(m, p, i){
const name = twoChat(p.quantity)+' '+p.name;
m[name] = {
value: p.ok,
format: v => v?'[x]':'[ ]',
onchange: v => {
settings.products[i].ok = v;
updateSettings();
}
};
return m;
}, {
'': { 'title': 'Grocery list' }
});
mainMenu['< Back'] = ()=>{load();};
E.showMenu(mainMenu);
`;
var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQ0QACF1nGAAIxpFoYwqFwwwnRggwGB4eFAggACLzwHCMAeF1WGAgOGw2x2IGCLzYGEF4YpBwotCFwJfWFwo1GSAYtBAIIABRq4vFMhAwBzoAFdzIuKAAOc4IAGGC4qEMZOiF44wXFxovleBYvIGCwmB0WjE4V/AgfG1IvCzujFQOjwoECF6WFwovBDYOFEwN/AgIwCAgOFBwYrBBAQEBzodCF6AAHww1CBpIODAAYvRDAWG2IEBAYYJFBxICCF6Ox1WxAAQfBAYQlCAAIOJAQIvUADQvn1WGR4RfbP4gAFBwgFCF7a5EdwQADF46/cL9wAQF94AGF85bB1TvmF47vdJ4bvFF8qPRFgLv/L7jPCaQq/fYYrvgJgoAGd/7v/F/4v/F5oAdF54weFyAA/AH4A3A="))`;
sendCustomizedApp({
storage:[
{name:"grocery.app.js", url:"app.js", content:app},
{name:"grocery.img", content:icon, evaluate:true},
{name:"grocery"}
{ name:"grocery_list.json", content: JSON.stringify({products: products}) }
]
});
});

View File

@ -4,3 +4,4 @@
0.04: Now displays widgets
0.05: Use g.theme for colours
0.06: Use Bangle.setUI for buttons
0.07: Theme colours fix

View File

@ -33,8 +33,10 @@ function drawMenu() {
if (i+menuScroll==selected) {
g.setColor(g.theme.bgH).fillRect(0,y,w-1,y+63);
g.setColor(g.theme.fgH).drawRect(0,y,w-1,y+63);
} else
g.clearRect(0,y,w-1,y+63);
} else {
g.clearRect(0, y, w-1, y+63);
g.setColor(g.theme.fg);
}
g.drawString(app.name,64,y+32);
var icon=undefined;
if (app.icon) icon = s.read(app.icon);

View File

@ -9,3 +9,4 @@
0.09: Add onHide callback
0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode
0.11: Improvements to help notifications work with themes, Bangle.js 2 support
0.12: More use of themes, title now uses theme highlight colors, font adjusts

View File

@ -1,7 +1,7 @@
let oldg;
let id = null;
let hideCallback = null;
const titleFont = g.getWidth() / 8;
/**
* See notify/notify.js
*/
@ -63,8 +63,8 @@ exports.show = function(options) {
// top bar
if (options.title||options.src) {
const title = options.title || options.src;
g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30);
g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3);
g.setColor(options.titleBgColor||g.theme.bgH).fillRect(x, y, x+w-1, y+30);
g.setColor(g.theme.fgH).setFontAlign(-1, -1, 0).setFont("Vector", titleFont);
g.drawString(title.trim().substring(0, 13), x+5, y+3);
if (options.title && options.src) {
g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2);

View File

@ -32,3 +32,4 @@
0.27: Add Theme menu
0.28: Update Quiet Mode widget (if present)
0.29: Add Customize to Theme menu
0.30: Move '< Back' to the top of menus

View File

@ -75,6 +75,7 @@ function showMainMenu() {
var beepN = ["Off", "Piezo", "Vibrate"];
const mainmenu = {
'': { 'title': 'Settings' },
'< Back': ()=>load(),
'Make Connectable': ()=>makeConnectable(),
'App/Widget Settings': ()=>showAppSettingsMenu(),
'BLE': ()=>showBLEMenu(),
@ -117,7 +118,6 @@ function showMainMenu() {
'Theme': ()=>showThemeMenu(),
'Reset Settings': ()=>showResetMenu(),
'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() },
'< Back': ()=>load()
};
return E.showMenu(mainmenu);
}
@ -126,6 +126,7 @@ function showBLEMenu() {
var hidV = [false, "kbmedia", "kb", "joy"];
var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"];
E.showMenu({
'< Back': ()=>showMainMenu(),
'BLE': {
value: settings.ble,
format: boolFormat,
@ -158,8 +159,7 @@ function showBLEMenu() {
'Whitelist': {
value: settings.whitelist?(settings.whitelist.length+" devs"):"off",
onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call
},
'< Back': ()=>showMainMenu()
}
});
}
@ -180,6 +180,7 @@ function showThemeMenu() {
function cl(x) { return g.setColor(x).getColor(); }
m = E.showMenu({
'':{title:'Theme'},
'< Back': ()=>showMainMenu(),
'Dark BW': ()=>{
updT({
fg:cl("#fff"), bg:cl("#000"),
@ -197,7 +198,6 @@ function showThemeMenu() {
});
},
'Customize': ()=>showCustomThemeMenu(),
'< Back': ()=>{delete m;showMainMenu();},
});
}
function showCustomThemeMenu() {
@ -255,6 +255,7 @@ function showCustomThemeMenu() {
function showPasskeyMenu() {
var menu = {
"< Back" : ()=>showBLEMenu(),
"Disable" : () => {
settings.passkey = undefined;
updateSettings();
@ -275,12 +276,12 @@ function showPasskeyMenu() {
}
};
})(i);
menu['< Back']=()=>showBLEMenu();
E.showMenu(menu);
}
function showWhitelistMenu() {
var menu = {
"< Back" : ()=>showBLEMenu(),
"Disable" : () => {
settings.whitelist = undefined;
updateSettings();
@ -290,7 +291,7 @@ function showWhitelistMenu() {
if (settings.whitelist) settings.whitelist.forEach(function(d){
menu[d.substr(0,17)] = function() {
E.showPrompt('Remove\n'+d).then((v) => {
if (v) {
if (v)
settings.whitelist.splice(settings.whitelist.indexOf(d),1);
updateSettings();
}
@ -312,7 +313,6 @@ function showWhitelistMenu() {
showWhitelistMenu();
});
};
menu['< Back']=()=>showBLEMenu();
E.showMenu(menu);
}

View File

@ -7,7 +7,7 @@ const spanishNumberStr = [ ["ZERO"], // 0
["CUATRO",''], //4
["CINCO",''], //5
["SEIS",''], //6
["SEITO",''], //7
["SIETE",''], //7
["OCHO",''], //8
["NUEVE",''], // 9,
["DIEZ",''], // 10
@ -20,7 +20,7 @@ const spanishNumberStr = [ ["ZERO"], // 0
["DIECI",'SIETE'], // 17
["DIECI",'OCHO'], // 18
["DIECI",'NEUVE'], // 19
["VEINTA",''], // 20
["VEINTE",''], // 20
["VEINTI",'UNO'], // 21
["VEINTI",'DOS'], // 22
["VEINTI",'TRES'], // 23
@ -74,4 +74,4 @@ class SpanishDateFormatter extends DateFormatter {
}
}
module.exports = SpanishDateFormatter;
module.exports = SpanishDateFormatter;

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Load widgets after setUI so widclk knows when to hide

View File

@ -28,7 +28,7 @@ function queueDraw() {
function draw() {
var x = g.getWidth()/2;
var y = 24+20;
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
if (g.getWidth() == IMAGEWIDTH)
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
@ -65,8 +65,8 @@ Bangle.on('lcdPower',on=>{
drawTimeout = undefined;
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// Show launcher when middle button pressed
Bangle.setUI("clock");

View File

@ -4,4 +4,6 @@
0.05: Add wind direction.
0.06: Use setUI for launcher.
0.07: Add theme support and unknown icon.
0.08: Refactor and reduce widget ram usage.
0.08: Refactor and reduce widget ram usage.
0.09: Fix crash when weather.json is absent.
0.10: Use new Layout library

View File

@ -1,96 +1,106 @@
(() => {
const weather = require('weather');
let current = weather.get();
const Layout = require('Layout');
const locale = require('locale');
const weather = require('weather');
let current = weather.get();
function formatDuration(millis) {
let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s");
if (millis < 60000) return "< 1 minute";
if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute");
if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour");
return pluralize(Math.floor(millis/86400000), "day");
}
Bangle.loadWidgets();
function draw() {
g.reset();
g.clearRect(0, 24, 239, 239);
var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
{filly: 1},
{type: "h", filly: 0, c: [
{type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon",
render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)},
{type: "v", fillx: 1, c: [
{type: "h", pad: 2, c: [
{type: "txt", font: "18%", id: "temp", label: "000"},
{type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"},
]},
{filly: 1},
{type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"},
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
{filly: 1},
{type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"},
{type: "h", halign: -1, c: [
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
]},
]},
]},
{filly: 1},
{type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"},
{filly: 1},
{type: "h", c: [
{type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"},
{fillx: 1},
{type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"},
]},
{filly: 1},
]}, null, {lazy: true});
weather.drawIcon(current.txt, 65, 90, 55);
const locale = require("locale");
function formatDuration(millis) {
let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s");
if (millis < 60000) return "< 1 minute";
if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute");
if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour");
return pluralize(Math.floor(millis/86400000), "day");
}
g.reset();
function draw() {
layout.icon.txt = current.txt;
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
layout.temp.label = temp[1];
layout.tempUnit.label = temp[2];
layout.hum.label = current.hum+"%";
const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/);
layout.wind.label = wind[1];
layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase();
layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1);
layout.loc.label = current.loc;
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
layout.update();
layout.render();
}
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
let width = g.setFont("Vector", 40).stringWidth(temp[1]);
width += g.setFont("Vector", 20).stringWidth(temp[2]);
g.setFont("Vector", 40).setFontAlign(-1, -1, 0);
g.drawString(temp[1], 180-width/2, 70);
g.setFont("Vector", 20).setFontAlign(1, -1, 0);
g.drawString(temp[2], 180+width/2, 70);
function drawUpdateTime() {
if (!current || !current.time) return;
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
layout.update();
layout.render();
}
g.setFont("6x8", 1);
g.setFontAlign(-1, 0, 0);
g.drawString("Humidity", 135, 130);
g.setFontAlign(1, 0, 0);
g.drawString(current.hum+"%", 225, 130);
if ('wind' in current) {
g.setFontAlign(-1, 0, 0);
g.drawString("Wind", 135, 142);
g.setFontAlign(1, 0, 0);
g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142);
}
g.setFont("6x8", 2).setFontAlign(0, 0, 0);
g.drawString(current.loc, 120, 170);
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190);
drawUpdateTime();
g.flip();
}
function drawUpdateTime() {
if (!current || !current.time) return;
let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`;
g.reset();
g.clearRect(0, 202, 239, 210);
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
g.drawString(text, 120, 206);
}
function update() {
current = weather.get();
NRF.removeListener("connect", update);
if (current) {
draw();
} else if (NRF.getSecurityStatus().connected) {
E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?");
function update() {
current = weather.get();
NRF.removeListener("connect", update);
if (current) {
draw();
} else {
layout.forgetLazyState();
if (NRF.getSecurityStatus().connected) {
E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?");
} else {
E.showMessage("Weather unknown\n\nGadgetbridge\nnot connected");
E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected");
NRF.on("connect", update);
}
}
}
let interval = setInterval(drawUpdateTime, 60000);
Bangle.on('lcdPower', (on) => {
if (interval) {
clearInterval(interval);
interval = undefined;
}
if (on) {
drawUpdateTime();
interval = setInterval(drawUpdateTime, 60000);
}
});
let interval = setInterval(drawUpdateTime, 60000);
Bangle.on('lcdPower', (on) => {
if (interval) {
clearInterval(interval);
interval = undefined;
}
if (on) {
drawUpdateTime();
interval = setInterval(drawUpdateTime, 60000);
}
});
weather.on("update", update);
weather.on("update", update);
update();
update();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
})()
Bangle.drawWidgets();

View File

@ -47,7 +47,7 @@ global.GB = (event) => {
};
exports.get = function() {
return storage.readJSON('weather.json').weather;
return (storage.readJSON('weather.json')||{}).weather;
}
scheduleExpiry(storage.readJSON('weather.json')||{});

View File

@ -2,3 +2,4 @@
0.03: Ensure redrawing works with variable size widget system
0.04: Fix regression stopping correct widget updates
0.05: Don't show clock widget if already showing clock app
0.06: Use 7 segment font, update *on* the minute, use less memory

View File

@ -1,30 +1,16 @@
(function() {
// don't show widget if we know we have a clock app running
if (Bangle.CLOCK) return;
/* Simple clock that appears in the widget bar if no other clock
is running. We update once per minute, but don't bother stopping
if the */
let intervalRef = null;
var width = 5 * 6*2
function draw() {
g.reset().setFont("6x8", 2).setFontAlign(-1, 0);
var time = require("locale").time(new Date(),1);
g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60
}
function clearTimers(){
if(intervalRef) {
clearInterval(intervalRef);
intervalRef = null;
}
}
function startTimers(){
intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
WIDGETS["wdclk"].draw();
}
Bangle.on('lcdPower', (on) => {
clearTimers();
if (on) startTimers();
});
WIDGETS["wdclk"]={area:"tr",width:width,draw:draw};
if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
})()
// don't show widget if we know we have a clock app running
if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") */,draw:function() {
g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9);
var time = require("locale").time(new Date(),1);
g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60
// queue draw in one minute
if (drawTimeout) clearTimeout(drawTimeout);
this.drawTimeout = setTimeout(()=>{
this.drawTimeout = undefined;
this.draw();
}, 60000 - (Date.now() % 60000));
}};

View File

@ -11,16 +11,35 @@ var SETTINGS = {
pretokenise : true
};
var DEVICE = process.argv[2];
var path = require('path');
var ROOTDIR = path.join(__dirname, '..');
var APPDIR = ROOTDIR+'/apps';
var APPJSON = ROOTDIR+'/apps.json';
var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c');
var APPS = [ // IDs of apps to install
"boot","launchb2","s7clk","setting",
"about","alarm","widlock","widbat","widbt"
];
var MINIFY = true;
var OUTFILE, APPS;
if (DEVICE=="BANGLEJS") {
var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs1_storage_default.c');
var APPS = [ // IDs of apps to install
"boot","launch","mclock","setting",
"about","alarm","widbat","widbt","welcome"
];
} else if (DEVICE=="BANGLEJS2") {
var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c');
var APPS = [ // IDs of apps to install
"boot","launchb2","s7clk","setting",
"about","alarm","widlock","widbat","widbt"
];
} else {
console.log("USAGE:");
console.log(" bin/firmwaremaker_c.js BANGLEJS");
console.log(" bin/firmwaremaker_c.js BANGLEJS2");
process.exit(1);
}
console.log("Device = ",DEVICE);
var fs = require("fs");
global.Const = {

View File

@ -36,6 +36,8 @@ layoutObject has:
* 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
* 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. `-1`=left, `1`=right, `0`=center
@ -71,6 +73,7 @@ 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
*/
@ -138,7 +141,7 @@ function Layout(layout, buttons, options) {
if (l.c) l.c.forEach(idRecurser);
}
idRecurser(layout);
this.update();
this.updateNeeded = true;
}
Layout.prototype.remove = function (l) {
@ -152,6 +155,24 @@ Layout.prototype.remove = function (l) {
}
};
function wrappedLines(str, maxWidth) {
var lines = [];
for (var unwrappedLine of str.split("\n")) {
var words = unwrappedLine.split(" ");
var line = words.shift();
for (var word of words) {
if (g.stringWidth(line + " " + word) > maxWidth) {
lines.push(line);
line = word;
} else {
line += " " + word;
}
}
lines.push(line);
}
return lines;
}
function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol);
if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") {
@ -176,6 +197,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
Layout.prototype.render = function (l) {
if (!l) l = this._l;
if (this.updateNeeded) this.update();
function render(l) {"ram"
g.reset();
@ -187,7 +209,14 @@ Layout.prototype.render = function (l) {
var cb = {
"":function(){},
"txt":function(l){
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
if (l.wrap) {
g.setFont(l.font,l.fsz).setFontAlign(0,-1);
var lines = wrappedLines(l.label, l.w);
var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1);
lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i));
} else {
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
}
}, "btn":function(l){
var x = l.x+(0|l.pad);
var y = l.y+(0|l.pad);
@ -230,6 +259,10 @@ Layout.prototype.render = function (l) {
}
};
Layout.prototype.forgetLazyState = function () {
this.rects = {};
}
Layout.prototype.layout = function (l) {
// l = current layout element
// exw,exh = extra width/height available
@ -282,6 +315,7 @@ Layout.prototype.debug = function(l,c) {
if (l.c) l.c.forEach(n => this.debug(n,c));
};
Layout.prototype.update = function() {
delete this.updateNeeded;
var l = this._l;
var w = g.getWidth();
var y = this.yOffset;
@ -305,9 +339,13 @@ Layout.prototype.update = function() {
l.font = f[0];
l.fsz = f[1];
}
g.setFont(l.font,l.fsz);
l._h = g.getFontHeight();
l._w = g.stringWidth(l.label);
if (l.wrap) {
l._h = l._w = 0;
} else {
g.setFont(l.font,l.fsz);
l._h = g.getFontHeight();
l._w = g.stringWidth(l.label);
}
}, "btn": function(l) {
l._h = 24;
l._w = 14 + l.label.length*8;

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,7 @@
var layout = new Layout({type:"v", c: [
{type:"h", c: [
{type:"txt", font:"10%", wrap: true, fillx: true, filly: true, label:"This is wrapping text that fills remaining space"},
{type:"txt", font:"6x8", wrap: true, width: 60, filly: true, label:"This is wrapping text in a narrow column"},
]},
{type:"txt", font:"6x8", wrap: true, fillx: true, height: 20, label:"This doesn't need to wrap"},
]});