Merge branch 'espruino:master' into master
commit
6bb2a86767
|
|
@ -176,9 +176,13 @@
|
|||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-alwaysAllowUpdate">
|
||||
<i class="form-icon"></i> Always allow to reinstall apps in place regardless of the version
|
||||
<i class="form-icon"></i> Always show "reinstall app" button <i class="icon icon-refresh"></i> regardless of the version
|
||||
</label>
|
||||
<button class="btn" id="defaultsettings">Reset to default App Loader settings</button>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-autoReload">
|
||||
<i class="form-icon"></i> Automatically reload watch after app App Loader actions (removes "Hold button" prompt)
|
||||
</label>
|
||||
<button class="btn" id="defaultsettings">Reset App Loader settings to defaults</button>
|
||||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Update to use Bangle.setUI instead of setWatch
|
||||
0.04: Tell clock widgets to hide.
|
||||
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
||||
0.06: optimized to update much faster
|
||||
|
|
|
|||
|
|
@ -534,14 +534,7 @@ function mean_sidereal_time(lon) {
|
|||
var mst = 280.46061837 + 360.98564736629 * jd
|
||||
+ 0.000387933 * jt * jt - jt * jt * jt / 38710000 + lon;
|
||||
|
||||
if (mst > 0.0) {
|
||||
while (mst > 360.0)
|
||||
mst = mst - 360.0;
|
||||
}
|
||||
else {
|
||||
while (mst < 0.0)
|
||||
mst = mst + 360.0;
|
||||
}
|
||||
mst %=360;
|
||||
return mst;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "astral",
|
||||
"name": "Astral Clock",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
|
|
|
|||
|
|
@ -15,3 +15,5 @@
|
|||
Display events for current month on touch
|
||||
0.14: Add support for holidays
|
||||
0.15: Edit holidays on device in settings
|
||||
0.16: Add menu to fast open settings to edit holidays
|
||||
Display Widgets in menus
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Calendar
|
||||
|
||||
Basic calendar
|
||||
Monthly calendar, displays holidays uploaded from the web interface and scheduled events.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -14,4 +14,4 @@ Basic calendar
|
|||
|
||||
## Settings
|
||||
|
||||
- B2 Colors: use non-dithering colors (default, recommended for Bangle 2) or the original color scheme.
|
||||
B2 Colors: use non-dithering colors (default, recommended for Bangle 2) or the original color scheme.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
const maxX = g.getWidth();
|
||||
const maxY = g.getHeight();
|
||||
const fontSize = g.getWidth() > 200 ? 2 : 1;
|
||||
|
|
@ -17,71 +18,85 @@ const red = "#d41706";
|
|||
const blue = "#0000ff";
|
||||
const yellow = "#ffff00";
|
||||
const cyan = "#00ffff";
|
||||
let bgColor = color4;
|
||||
let bgColorMonth = color1;
|
||||
let bgColorDow = color2;
|
||||
let bgColorWeekend = color3;
|
||||
let fgOtherMonth = gray1;
|
||||
let fgSameMonth = white;
|
||||
let bgEvent = blue;
|
||||
let bgOtherEvent = "#ff8800";
|
||||
let bgColor;
|
||||
let bgColorMonth;
|
||||
let bgColorDow;
|
||||
let bgColorWeekend;
|
||||
let fgOtherMonth;
|
||||
let fgSameMonth;
|
||||
let bgEvent;
|
||||
let bgOtherEvent;
|
||||
const eventsPerDay=6; // how much different events per day we can display
|
||||
const date = new Date();
|
||||
|
||||
const timeutils = require("time_utils");
|
||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||
let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
|
||||
// all alarms that run on a specific date
|
||||
const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
|
||||
const date = new Date(a.date);
|
||||
const time = timeutils.decodeTime(a.t);
|
||||
date.setHours(time.h);
|
||||
date.setMinutes(time.m);
|
||||
date.setSeconds(time.s);
|
||||
return {date: date, msg: a.msg, type: "e"};
|
||||
});
|
||||
// add holidays & other events
|
||||
(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
|
||||
const date = new Date(d.date);
|
||||
const o = {date: date, msg: d.name, type: d.type};
|
||||
if (d.repeat) {
|
||||
o.repeat = d.repeat;
|
||||
}
|
||||
events.push(o);
|
||||
});
|
||||
|
||||
if (settings.ndColors === undefined) {
|
||||
settings.ndColors = !g.theme.dark;
|
||||
}
|
||||
|
||||
if (settings.ndColors === true) {
|
||||
bgColor = white;
|
||||
bgColorMonth = blue;
|
||||
bgColorDow = black;
|
||||
bgColorWeekend = yellow;
|
||||
fgOtherMonth = blue;
|
||||
fgSameMonth = black;
|
||||
bgEvent = color2;
|
||||
bgOtherEvent = cyan;
|
||||
}
|
||||
|
||||
function getDowLbls(locale) {
|
||||
let days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
|
||||
let events;
|
||||
const dowLbls = function() {
|
||||
const locale = require('locale').name;
|
||||
const days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
|
||||
const d = new Date();
|
||||
return days.map(i => {
|
||||
d.setDate(d.getDate() + (i + 7 - d.getDay()) % 7);
|
||||
return require("locale").dow(d, 1);
|
||||
});
|
||||
}
|
||||
}();
|
||||
|
||||
function sameDay(d1, d2) {
|
||||
const loadEvents = () => {
|
||||
// all alarms that run on a specific date
|
||||
events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
|
||||
const date = new Date(a.date);
|
||||
const time = timeutils.decodeTime(a.t);
|
||||
date.setHours(time.h);
|
||||
date.setMinutes(time.m);
|
||||
date.setSeconds(time.s);
|
||||
return {date: date, msg: a.msg, type: "e"};
|
||||
});
|
||||
// add holidays & other events
|
||||
(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
|
||||
const date = new Date(d.date);
|
||||
const o = {date: date, msg: d.name, type: d.type};
|
||||
if (d.repeat) {
|
||||
o.repeat = d.repeat;
|
||||
}
|
||||
events.push(o);
|
||||
});
|
||||
};
|
||||
|
||||
const loadSettings = () => {
|
||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||
if (settings.ndColors === undefined) {
|
||||
settings.ndColors = !g.theme.dark;
|
||||
}
|
||||
if (settings.ndColors === true) {
|
||||
bgColor = white;
|
||||
bgColorMonth = blue;
|
||||
bgColorDow = black;
|
||||
bgColorWeekend = yellow;
|
||||
fgOtherMonth = blue;
|
||||
fgSameMonth = black;
|
||||
bgEvent = color2;
|
||||
bgOtherEvent = cyan;
|
||||
} else {
|
||||
bgColor = color4;
|
||||
bgColorMonth = color1;
|
||||
bgColorDow = color2;
|
||||
bgColorWeekend = color3;
|
||||
fgOtherMonth = gray1;
|
||||
fgSameMonth = white;
|
||||
bgEvent = blue;
|
||||
bgOtherEvent = "#ff8800";
|
||||
}
|
||||
};
|
||||
|
||||
const sameDay = function(d1, d2) {
|
||||
"jit";
|
||||
return d1.getFullYear() === d2.getFullYear() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getDate() === d2.getDate();
|
||||
}
|
||||
};
|
||||
|
||||
function drawEvent(ev, curDay, x1, y1, x2, y2) {
|
||||
const drawEvent = function(ev, curDay, x1, y1, x2, y2) {
|
||||
"ram";
|
||||
switch(ev.type) {
|
||||
case "e": // alarm/event
|
||||
|
|
@ -99,9 +114,33 @@ function drawEvent(ev, curDay, x1, y1, x2, y2) {
|
|||
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function drawCalendar(date) {
|
||||
const calcDays = (month, monthMaxDayMap, dowNorm) => {
|
||||
"jit";
|
||||
const maxDay = colN * (rowN - 1) + 1;
|
||||
const days = [];
|
||||
let nextMonthDay = 1;
|
||||
let thisMonthDay = 51;
|
||||
const month2 = month;
|
||||
let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
|
||||
|
||||
for (let i = 0; i < maxDay; i++) {
|
||||
if (i < dowNorm) {
|
||||
days.push(prevMonthDay);
|
||||
prevMonthDay++;
|
||||
} else if (thisMonthDay <= monthMaxDayMap[month] + 50) {
|
||||
days.push(thisMonthDay);
|
||||
thisMonthDay++;
|
||||
} else {
|
||||
days.push(nextMonthDay);
|
||||
nextMonthDay++;
|
||||
}
|
||||
}
|
||||
return days;
|
||||
};
|
||||
|
||||
const drawCalendar = function(date) {
|
||||
g.setBgColor(bgColor);
|
||||
g.clearRect(0, 0, maxX, maxY);
|
||||
g.setBgColor(bgColorMonth);
|
||||
|
|
@ -139,7 +178,6 @@ function drawCalendar(date) {
|
|||
true
|
||||
);
|
||||
|
||||
let dowLbls = getDowLbls(require('locale').name);
|
||||
dowLbls.forEach((lbl, i) => {
|
||||
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
|
||||
});
|
||||
|
|
@ -163,23 +201,7 @@ function drawCalendar(date) {
|
|||
11: 31
|
||||
};
|
||||
|
||||
let days = [];
|
||||
let nextMonthDay = 1;
|
||||
let thisMonthDay = 51;
|
||||
let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
|
||||
for (let i = 0; i < colN * (rowN - 1) + 1; i++) {
|
||||
if (i < dowNorm) {
|
||||
days.push(prevMonthDay);
|
||||
prevMonthDay++;
|
||||
} else if (thisMonthDay <= monthMaxDayMap[month] + 50) {
|
||||
days.push(thisMonthDay);
|
||||
thisMonthDay++;
|
||||
} else {
|
||||
days.push(nextMonthDay);
|
||||
nextMonthDay++;
|
||||
}
|
||||
}
|
||||
|
||||
const days = calcDays(month, monthMaxDayMap, dowNorm);
|
||||
const weekBeforeMonth = new Date(date.getTime());
|
||||
weekBeforeMonth.setDate(weekBeforeMonth.getDate() - 7);
|
||||
const week2AfterMonth = new Date(date.getFullYear(), date.getMonth()+1, 0);
|
||||
|
|
@ -189,8 +211,15 @@ function drawCalendar(date) {
|
|||
ev.date.setFullYear(ev.date.getMonth() < 6 ? week2AfterMonth.getFullYear() : weekBeforeMonth.getFullYear());
|
||||
}
|
||||
});
|
||||
const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
|
||||
eventsThisMonth.sort((a,b) => a.date - b.date);
|
||||
|
||||
const eventsThisMonthPerDay = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth).reduce((acc, ev) => {
|
||||
const day = ev.date.getDate();
|
||||
if (!acc[day]) {
|
||||
acc[day] = [];
|
||||
}
|
||||
acc[day].push(ev);
|
||||
return acc;
|
||||
}, []);
|
||||
let i = 0;
|
||||
g.setFont("8x12", fontSize);
|
||||
for (y = 0; y < rowN - 1; y++) {
|
||||
|
|
@ -205,13 +234,13 @@ function drawCalendar(date) {
|
|||
const x2 = x * colW + colW;
|
||||
const y2 = y * rowH + headerH + rowH + rowH;
|
||||
|
||||
if (eventsThisMonth.length > 0) {
|
||||
const eventsThisDay = eventsThisMonthPerDay[curDay.getDate()];
|
||||
if (eventsThisDay && eventsThisDay.length > 0) {
|
||||
// Display events for this day
|
||||
eventsThisMonth.forEach((ev, idx) => {
|
||||
eventsThisDay.forEach((ev, idx) => {
|
||||
if (sameDay(ev.date, curDay)) {
|
||||
drawEvent(ev, curDay, x1, y1, x2, y2);
|
||||
|
||||
eventsThisMonth.splice(idx, 1); // this event is no longer needed
|
||||
eventsThisDay.splice(idx, 1); // this event is no longer needed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -235,9 +264,44 @@ function drawCalendar(date) {
|
|||
);
|
||||
} // end for (x = 0; x < colN; x++)
|
||||
} // end for (y = 0; y < rowN - 1; y++)
|
||||
} // end function drawCalendar
|
||||
}; // end function drawCalendar
|
||||
|
||||
const showMenu = function() {
|
||||
const menu = {
|
||||
"" : {
|
||||
title : "Calendar",
|
||||
remove: () => {
|
||||
require("widget_utils").show();
|
||||
}
|
||||
},
|
||||
"< Back": () => {
|
||||
require("widget_utils").hide();
|
||||
E.showMenu();
|
||||
setUI();
|
||||
},
|
||||
/*LANG*/"Exit": () => load(),
|
||||
/*LANG*/"Settings": () => {
|
||||
const appSettings = eval(require('Storage').read('calendar.settings.js'));
|
||||
appSettings(() => {
|
||||
loadSettings();
|
||||
loadEvents();
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
};
|
||||
if (require("Storage").read("alarm.app.js")) {
|
||||
menu[/*LANG*/"Launch Alarms"] = () => {
|
||||
load("alarm.app.js");
|
||||
};
|
||||
}
|
||||
require("widget_utils").show();
|
||||
E.showMenu(menu);
|
||||
};
|
||||
|
||||
const setUI = function() {
|
||||
require("widget_utils").hide(); // No space for widgets!
|
||||
drawCalendar(date);
|
||||
|
||||
function setUI() {
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
swipe: (dirLR, dirUD) => {
|
||||
|
|
@ -261,7 +325,14 @@ function setUI() {
|
|||
drawCalendar(date);
|
||||
}
|
||||
},
|
||||
btn: (n) => n === (process.env.HWVERSION === 2 ? 1 : 3) && load(),
|
||||
btn: (n) => {
|
||||
if (process.env.HWVERSION === 2 || n === 2) {
|
||||
showMenu();
|
||||
} else if (n === 3) {
|
||||
// directly exit only on Bangle.js 1
|
||||
load();
|
||||
}
|
||||
},
|
||||
touch: (n,e) => {
|
||||
events.sort((a,b) => a.date - b.date);
|
||||
const menu = events.filter(ev => ev.date.getFullYear() === date.getFullYear() && ev.date.getMonth() === date.getMonth()).map(e => {
|
||||
|
|
@ -274,16 +345,19 @@ function setUI() {
|
|||
}
|
||||
menu[""] = { title: require("locale").month(date) + " " + date.getFullYear() };
|
||||
menu["< Back"] = () => {
|
||||
require("widget_utils").hide();
|
||||
E.showMenu();
|
||||
drawCalendar(date);
|
||||
setUI();
|
||||
};
|
||||
require("widget_utils").show();
|
||||
E.showMenu(menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadSettings();
|
||||
loadEvents();
|
||||
Bangle.loadWidgets();
|
||||
require("Font8x12").add(Graphics);
|
||||
drawCalendar(date);
|
||||
setUI();
|
||||
// No space for widgets!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.15",
|
||||
"description": "Simple calendar",
|
||||
"version": "0.16",
|
||||
"description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
"tags": "calendar,tool",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
(function (back) {
|
||||
var FILE = "calendar.json";
|
||||
const FILE = "calendar.json";
|
||||
const HOLIDAY_FILE = "calendar.days.json";
|
||||
var settings = require('Storage').readJSON(FILE, true) || {};
|
||||
if (settings.ndColors === undefined)
|
||||
const settings = require('Storage').readJSON(FILE, true) || {};
|
||||
if (settings.ndColors === undefined) {
|
||||
if (process.env.HWVERSION == 2) {
|
||||
settings.ndColors = true;
|
||||
} else {
|
||||
settings.ndColors = false;
|
||||
}
|
||||
const holidays = require("Storage").readJSON(HOLIDAY_FILE,1).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
|
||||
}
|
||||
const holidays = (require("Storage").readJSON(HOLIDAY_FILE,1)||[]).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
|
||||
</div>
|
||||
<ul>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span></p>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span>.
|
||||
The DFU (bootloader) rarely changes, so it does not have to be the same version as your main firmware.</p>
|
||||
</ul>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p id="fw-old-bootloader-msg">If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
|
||||
|
|
|
|||
|
|
@ -7,6 +7,5 @@
|
|||
0.07: compatible with Bang;e.js 2
|
||||
0.08: fix minute tick bug
|
||||
0.09: use setUI clockupdown for controls + fix small display bug in nifty face
|
||||
|
||||
|
||||
0.10: stop widget field from flashing when moving to the dk clock face.
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
d[0] = locale.dow(now,3);
|
||||
var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3];
|
||||
g.drawString(dt,W/2,H/2+24);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "multiclock",
|
||||
"name": "Multi Clock",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "Clock with multiple faces. Switch between faces with BTN1 & BTN3 (Bangle 2 touch top-right, bottom right). For best display set theme Background 2 to cyan or some other bright colour in settings.",
|
||||
"screenshots": [{"url":"screen-ana.png"},{"url":"screen-big.png"},{"url":"screen-td.png"},{"url":"screen-nifty.png"},{"url":"screen-word.png"},{"url":"screen-sec.png"}],
|
||||
"icon": "multiclock.png",
|
||||
|
|
|
|||
|
|
@ -71,4 +71,6 @@ of 'Select Clock'
|
|||
0.62: Fix whitelist showing as 'on' by default when it's not after 0.59
|
||||
0.63: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
||||
Remove 'beta' label from passkey - it's been around for a while and works ok
|
||||
0.64: Default to wakeOnTwist being off
|
||||
0.64: Default to wakeOnTwist being off
|
||||
0.65: Prepend 'LCD->Calibration' touch listener and stop event propagation.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.64",
|
||||
"version": "0.65",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
|||
|
|
@ -899,6 +899,7 @@ function showTouchscreenCalibration() {
|
|||
}
|
||||
|
||||
function touchHandler(_,e) {
|
||||
E.stopEventPropagation&&E.stopEventPropagation();
|
||||
var spot = corners[currentCorner];
|
||||
// store averages
|
||||
if (spot[0]*2 < g.getWidth())
|
||||
|
|
@ -921,7 +922,7 @@ function showTouchscreenCalibration() {
|
|||
}
|
||||
showTapSpot();
|
||||
}
|
||||
Bangle.on('touch', touchHandler);
|
||||
Bangle.prependListener&&Bangle.prependListener('touch',touchHandler)||Bangle.on('touch',touchHandler);
|
||||
|
||||
showTapSpot();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Do not clear outside of widget bar
|
||||
0.04: Fork `widminbat`->`widminbate`. Only use the system theme foreground
|
||||
colour.
|
||||
0.05: Fix broken fork which removed the `update` function
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "widminbate",
|
||||
"name": "Extra Minimal Battery",
|
||||
"shortName":"ExtraMinBat",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "An extra minimal (only use system theme foreground colour) version of the battery widget that only appears if the battery is running low (below 30%)",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(()=>{
|
||||
function getWidth() {
|
||||
{
|
||||
let getWidth = function() {
|
||||
return E.getBattery() <= 30 || Bangle.isCharging() ? 40 : 0;
|
||||
}
|
||||
};
|
||||
WIDGETS.minbate={area:"tr",width:getWidth(),draw:function() {
|
||||
if(this.width < 40) return;
|
||||
var s = 39;
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
clearRect(x,y,x+s,y+23).
|
||||
setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14).//border
|
||||
fillRect(x+4,y+6,x+4+barWidth,y+17);//indicator bar
|
||||
},update: function() {
|
||||
var newWidth = getWidth();
|
||||
if(newWidth != this.width) {
|
||||
this.width = newWidth;
|
||||
|
|
@ -22,7 +23,7 @@
|
|||
}};
|
||||
setInterval(()=>{
|
||||
var widget = WIDGETS.minbate;
|
||||
if(widget) {widget.update();}
|
||||
if(widget) widget.update();
|
||||
}, 10*60*1000);
|
||||
Bangle.on('charging', () => WIDGETS.minbate.update());
|
||||
})();
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
/* Copyright (c) 2023 Bangle.js contributors. See the file LICENSE for copying permission. */
|
||||
|
||||
// At time of writing in October 2023 this module is new and things are more likely to change during the coming weeks than in a month or two.
|
||||
|
||||
// See Slider.md for documentation
|
||||
|
||||
/* Minify to 'Slider.min.js' by: // TODO: Should we do this for Slider module?
|
||||
|
||||
* checking out: https://github.com/espruino/EspruinoDocs
|
||||
* run: ../EspruinoDocs/bin/minify.js modules/Slider.js modules/Slider.min.js
|
||||
|
||||
*/
|
||||
|
||||
exports.create = function(cb, conf) {
|
||||
|
||||
const R = Bangle.appRect;
|
||||
|
||||
// Empty function added to cb if it's undefined.
|
||||
if (!cb) cb = ()=>{};
|
||||
|
||||
let o = {};
|
||||
o.v = {}; // variables go here.
|
||||
o.f = {}; // functions go here.
|
||||
|
||||
// Default configuration for the indicator, modified by parameter `conf`:
|
||||
o.c = Object.assign({ // constants go here.
|
||||
initLevel:0,
|
||||
horizontal:false,
|
||||
xStart:R.x2-R.w/4-4,
|
||||
width:R.w/4,
|
||||
yStart:R.y+4,
|
||||
height:R.h-10,
|
||||
steps:30,
|
||||
|
||||
dragableSlider:true,
|
||||
dragRect:R,
|
||||
mode:"incr",
|
||||
oversizeR:0,
|
||||
oversizeL:0,
|
||||
propagateDrag:false,
|
||||
timeout:1,
|
||||
|
||||
drawableSlider:true,
|
||||
colorFG:g.theme.fg2,
|
||||
colorBG:g.theme.bg2,
|
||||
rounded:true,
|
||||
outerBorderSize:Math.round(2*R.w/176), // 176 is the # of pixels in a row on the Bangle.js 2's screen and typically also its app rectangles, used here to rescale to whatever pixel count is on the current app rectangle.
|
||||
innerBorderSize:Math.round(2*R.w/176),
|
||||
|
||||
autoProgress:false,
|
||||
},conf);
|
||||
|
||||
// If borders are bigger than the configured width, make them smaller to avoid glitches.
|
||||
while (o.c.width <= 2*(o.c.outerBorderSize+o.c.innerBorderSize)) {
|
||||
o.c.outerBorderSize--;
|
||||
o.c.innerBorderSize--;
|
||||
}
|
||||
o.c.outerBorderSize = Math.max(0,o.c.outerBorderSize);
|
||||
o.c.innerBorderSize = Math.max(0,o.c.innerBorderSize);
|
||||
|
||||
let totalBorderSize = o.c.outerBorderSize + o.c.innerBorderSize;
|
||||
o.c.rounded = o.c.rounded?o.c.width/2:0;
|
||||
if (o.c.rounded) o.c._rounded = (o.c.width-2*totalBorderSize)/2;
|
||||
|
||||
o.c.STEP_SIZE = ((o.c.height-2*totalBorderSize)-(!o.c.rounded?0:(2*o.c._rounded)))/o.c.steps;
|
||||
|
||||
// If horizontal, flip things around.
|
||||
if (o.c.horizontal) {
|
||||
let mediator = o.c.xStart;
|
||||
o.c.xStart = o.c.yStart;
|
||||
o.c.yStart = mediator;
|
||||
mediator = o.c.width;
|
||||
o.c.width = o.c.height;
|
||||
o.c.height = mediator;
|
||||
delete mediator;
|
||||
}
|
||||
|
||||
// Make room for the border. Underscore indicates the area for the actual indicator bar without borders.
|
||||
o.c._xStart = o.c.xStart + totalBorderSize;
|
||||
o.c._width = o.c.width - 2*totalBorderSize;
|
||||
o.c._yStart = o.c.yStart + totalBorderSize;
|
||||
o.c._height = o.c.height - 2*totalBorderSize;
|
||||
|
||||
// Add a rectangle object with x, y, x2, y2, w and h values.
|
||||
o.c.r = {x:o.c.xStart, y:o.c.yStart, x2:o.c.xStart+o.c.width, y2:o.c.yStart+o.c.height, w:o.c.width, h:o.c.height};
|
||||
|
||||
// Initialize the level
|
||||
o.v.level = o.c.initLevel;
|
||||
|
||||
// Only add interactivity if wanted.
|
||||
if (o.c.dragableSlider) {
|
||||
|
||||
let useMap = (o.c.mode==="map"||o.c.mode==="mapincr")?true:false;
|
||||
let useIncr = (o.c.mode==="incr"||o.c.mode==="mapincr")?true:false;
|
||||
|
||||
const Y_MAX = g.getHeight()-1; // TODO: Should this take users screen calibration into account?
|
||||
|
||||
o.v.ebLast = 0;
|
||||
o.v.dy = 0;
|
||||
|
||||
o.f.wasOnDragRect = (exFirst, eyFirst)=>{
|
||||
"ram";
|
||||
return exFirst>o.c.dragRect.x && exFirst<o.c.dragRect.x2 && eyFirst>o.c.dragRect.y && eyFirst<o.c.dragRect.y2;
|
||||
};
|
||||
|
||||
o.f.wasOnIndicator = (exFirst)=>{
|
||||
"ram";
|
||||
if (!o.c.horizontal) return exFirst>o.c._xStart-o.c.oversizeL*o.c._width && exFirst<o.c._xStart+o.c._width+o.c.oversizeR*o.c._width;
|
||||
if (o.c.horizontal) return exFirst>o.c._yStart-o.c.oversizeL*o.c._height && exFirst<o.c._yStart+o.c._height+o.c.oversizeR*o.c._height;
|
||||
};
|
||||
|
||||
// Function to pass to `Bangle.on('drag', )`
|
||||
o.f.dragSlider = e=>{
|
||||
"ram";
|
||||
if (o.v.ebLast==0) {
|
||||
exFirst = o.c.horizontal?e.y:e.x;
|
||||
eyFirst = o.c.horizontal?e.x:e.y;
|
||||
}
|
||||
|
||||
// Only react if on allowed area.
|
||||
if (o.f.wasOnDragRect(exFirst, eyFirst)) {
|
||||
o.v.dragActive = true;
|
||||
if (!o.c.propagateDrag) E.stopEventPropagation&&E.stopEventPropagation();
|
||||
|
||||
if (o.v.timeoutID) {clearTimeout(o.v.timeoutID); o.v.timeoutID = undefined;}
|
||||
if (e.b==0 && !o.v.timeoutID && (o.c.timeout || o.c.timeout===0)) o.v.timeoutID = setTimeout(o.f.remove, 1000*o.c.timeout);
|
||||
|
||||
if (useMap && o.f.wasOnIndicator(exFirst)) { // If draging starts on the indicator, adjust one-to-one.
|
||||
|
||||
let input = !o.c.horizontal?
|
||||
Math.min((Y_MAX-e.y)-o.c.yStart-3*o.c.rounded/4, o.c.height):
|
||||
Math.min(e.x-o.c.xStart-3*o.c.rounded/4, o.c.width);
|
||||
input = Math.round(input/o.c.STEP_SIZE);
|
||||
|
||||
o.v.level = Math.min(Math.max(input,0),o.c.steps);
|
||||
|
||||
o.v.cbObj = {mode:"map", value:o.v.level};
|
||||
|
||||
} else if (useIncr) { // Heavily inspired by "updown" mode of setUI.
|
||||
|
||||
o.v.dy += o.c.horizontal?-e.dx:e.dy;
|
||||
//if (!e.b) o.v.dy=0;
|
||||
|
||||
while (Math.abs(o.v.dy)>32) {
|
||||
let incr;
|
||||
if (o.v.dy>0) { o.v.dy-=32; incr = 1;}
|
||||
else { o.v.dy+=32; incr = -1;}
|
||||
Bangle.buzz(20);
|
||||
|
||||
o.v.level = Math.min(Math.max(o.v.level-incr,0),o.c.steps);
|
||||
|
||||
o.v.cbObj = {mode:"incr", value:incr};
|
||||
}
|
||||
}
|
||||
if (o.v.cbObj && (o.v.level!==o.v.prevLevel||o.v.level===0||o.v.level===o.c.steps)) {
|
||||
cb(o.v.cbObj.mode, o.v.cbObj.value);
|
||||
o.f.draw&&o.f.draw(o.v.level);
|
||||
}
|
||||
o.v.cbObj = null;
|
||||
o.v.prevLevel = o.v.level;
|
||||
o.v.ebLast = e.b;
|
||||
}
|
||||
};
|
||||
|
||||
// Cleanup.
|
||||
o.f.remove = ()=> {
|
||||
Bangle.removeListener('drag', o.f.dragSlider);
|
||||
o.v.dragActive = false;
|
||||
o.v.timeoutID = undefined;
|
||||
cb("remove", o.v.level);
|
||||
};
|
||||
}
|
||||
|
||||
// Add standard slider graphics only if wanted.
|
||||
if (o.c.drawableSlider) {
|
||||
|
||||
// Function for getting the indication bars size.
|
||||
o.f.updateBar = (levelHeight)=>{
|
||||
"ram";
|
||||
if (!o.c.horizontal) return {x:o.c._xStart,y:o.c._yStart+o.c._height-levelHeight,w:o.c._width,y2:o.c._yStart+o.c._height,r:o.c.rounded};
|
||||
if (o.c.horizontal) return {x:o.c._xStart,y:o.c._yStart,w:levelHeight,h:o.c._height,r:o.c.rounded};
|
||||
};
|
||||
|
||||
o.c.borderRect = {x:o.c._xStart-totalBorderSize,y:o.c._yStart-totalBorderSize,w:o.c._width+2*totalBorderSize,h:o.c._height+2*totalBorderSize,r:o.c.rounded};
|
||||
|
||||
o.c.hollowRect = {x:o.c._xStart-o.c.innerBorderSize,y:o.c._yStart-o.c.innerBorderSize,w:o.c._width+2*o.c.innerBorderSize,h:o.c._height+2*o.c.innerBorderSize,r:o.c.rounded};
|
||||
|
||||
// Standard slider drawing method.
|
||||
o.f.draw = (level)=>{
|
||||
"ram";
|
||||
|
||||
g.setColor(o.c.colorFG).fillRect(o.c.borderRect). // To get outer border...
|
||||
setColor(o.c.colorBG).fillRect(o.c.hollowRect). // ... and here it's made hollow.
|
||||
setColor(0==level?o.c.colorBG:o.c.colorFG).fillRect(o.f.updateBar((!o.c.rounded?0:(2*o.c._rounded))+level*o.c.STEP_SIZE)); // Here the bar is drawn.
|
||||
if (o.c.rounded && level===0) { // Hollow circle indicates level zero when slider is rounded.
|
||||
g.setColor(o.c.colorFG).fillCircle(o.c._xStart+o.c._rounded, o.c._yStart+o.c._height-o.c._rounded, o.c._rounded).
|
||||
setColor(o.c.colorBG).fillCircle(o.c._xStart+o.c._rounded, o.c._yStart+o.c._height-o.c._rounded, o.c._rounded-o.c.outerBorderSize);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Add logic for auto progressing the slider only if wanted.
|
||||
if (o.c.autoProgress) {
|
||||
o.f.autoUpdate = ()=>{
|
||||
o.v.level = o.v.autoInitLevel + Math.round((Date.now()-o.v.autoInitTime)/1000);
|
||||
if (o.v.level>o.c.steps) o.v.level=o.c.steps;
|
||||
cb("auto", o.v.level);
|
||||
o.f.draw&&o.f.draw(o.v.level);
|
||||
if (o.v.level==o.c.steps) {o.f.stopAutoUpdate();}
|
||||
};
|
||||
o.f.initAutoValues = ()=>{
|
||||
o.v.autoInitTime=Date.now();
|
||||
o.v.autoInitLevel=o.v.level;
|
||||
};
|
||||
o.f.startAutoUpdate = (intervalSeconds)=>{
|
||||
if (!intervalSeconds) intervalSeconds = 1;
|
||||
o.f.stopAutoUpdate();
|
||||
o.f.initAutoValues();
|
||||
o.f.draw&&o.f.draw(o.v.level);
|
||||
o.v.autoIntervalID = setInterval(o.f.autoUpdate,1000*intervalSeconds);
|
||||
};
|
||||
o.f.stopAutoUpdate = ()=>{
|
||||
if (o.v.autoIntervalID) {
|
||||
clearInterval(o.v.autoIntervalID);
|
||||
o.v.autoIntervalID = undefined;
|
||||
}
|
||||
o.v.autoInitLevel = undefined;
|
||||
o.v.autoInitTime = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
return o;
|
||||
};
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
Slider Library
|
||||
==============
|
||||
|
||||
*At time of writing in October 2023 this module is new and things are more likely to change during the coming weeks than in a month or two.*
|
||||
|
||||
> Take a look at README.md for hints on developing with this library.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```js
|
||||
var Slider = require("Slider");
|
||||
var slider = Slider(callbackFunction, configObject);
|
||||
|
||||
Bangle.on("drag", slider.f.dragSlider);
|
||||
|
||||
// If the slider should take precedent over other drag handlers use (fw2v18 and up):
|
||||
// Bangle.prependListener("drag", slider.f.dragSlider);
|
||||
```
|
||||
|
||||
`callbackFunction` (`cb`) (first argument) determines what `slider` is used for. `slider` will pass two arguments, `mode` and `feedback` (`fb`), into `callbackFunction` (if `slider` is interactive or auto progressing). The different `mode`/`feedback` combinations to expect are:
|
||||
- `"map", o.v.level` | current level when interacting by mapping interface.
|
||||
- `"incr", incr` | where `incr` == +/-1, when interacting by incrementing interface.
|
||||
- `"remove", o.v.level` | last level when the slider times out.
|
||||
- `"auto", o.v.level` | when auto progressing.
|
||||
|
||||
`configObject` (`conf`) (second argument, optional) has the following defaults:
|
||||
|
||||
```js
|
||||
R = Bangle.appRect; // For use when determining defaults below.
|
||||
|
||||
{
|
||||
initLevel: 0, // The level to initialize the slider with.
|
||||
horizontal: false, // Slider should be horizontal?
|
||||
xStart: R.x2-R.w/4-4, // Leftmost x-coordinate. (Uppermost y-coordinate if horizontal)
|
||||
width: R.w/4, // Width of the slider. (Height if horizontal)
|
||||
yStart: R.y+4, // Uppermost y-coordinate. (Rightmost x-coordinate if horizontal)
|
||||
height: R.h-10, // Height of the slider. (Width if horizontal)
|
||||
steps: 30, // Number of discrete steps of the slider.
|
||||
|
||||
dragableSlider: true, // Should supply the sliders standard interaction mechanisms?
|
||||
dragRect: R, // Accept input within this rectangle.
|
||||
mode: "incr", // What mode of draging to use: "map", "incr" or "mapincr".
|
||||
oversizeR: 0, // Determines if the mapping area should be extend outside the indicator (Right/Up).
|
||||
oversizeL: 0, // Determines if the mapping area should be extend outside the indicator (Left/Down).
|
||||
propagateDrag: false, // Pass the drag event on down the handler chain?
|
||||
timeout: 1, // Seconds until the slider times out. If set to `false` the slider stays active. The callback function is responsible for repainting over the slider graphics.
|
||||
|
||||
drawableSlider: true, // Should supply the sliders standard drawing mechanism?
|
||||
colorFG: g.theme.fg2, // Foreground color.
|
||||
colorBG: g.theme.bg2, // Background color.
|
||||
rounded: true, // Slider should have rounded corners?
|
||||
outerBorderSize: Math.round(2*R.w/176), // The size of the visual border. Scaled in relation to Bangle.js 2 screen width/typical app rectangle widths.
|
||||
innerBorderSize: Math.round(2*R.w/176), // The distance between visual border and the slider.
|
||||
|
||||
autoProgress: false, // The slider should be able to progress automatically?
|
||||
}
|
||||
```
|
||||
|
||||
A slider initiated in the Web IDE terminal window reveals its internals to a degree:
|
||||
```js
|
||||
slider = require("Slider").create(()=>{}, {autoProgress:true})
|
||||
={
|
||||
v: { level: 0, ebLast: 0, dy: 0 },
|
||||
f: {
|
||||
wasOnDragRect: function (exFirst,eyFirst) { ... }, // Used internally.
|
||||
wasOnIndicator: function (exFirst) { ... }, // Used internally.
|
||||
dragSlider: function (e) { ... }, // The drag handler.
|
||||
remove: function () { ... }, // Used to remove the drag handler and run the callback function.
|
||||
updateBar: function (levelHeight) { ... }, // Used internally to get the variable height rectangle for the indicator.
|
||||
draw: function (level) { ... }, // Draw the slider with the supplied level.
|
||||
autoUpdate: function () { ... }, // Used to update the slider when auto progressing.
|
||||
initAutoValues: function () { ... }, // Used internally.
|
||||
startAutoUpdate: function (intervalSeconds) { ... }, // `intervalSeconds` defaults to 1 second if it's not supplied when `startAutoUpdate` is called.
|
||||
stopAutoUpdate: function () { ... } // Stop auto progressing and clear some related values.
|
||||
},
|
||||
c: { initLevel: 0, horizontal: false, xStart: 127, width: 44,
|
||||
yStart: 4, height: 166, steps: 30, dragableSlider: true,
|
||||
dragRect: { x: 0, y: 0, w: 176, h: 176,
|
||||
x2: 175, y2: 175 },
|
||||
mode: "incr",
|
||||
oversizeR: 0, oversizeL: 0, propagateDrag: false, timeout: 1, drawableSlider: true,
|
||||
colorFG: 63488, colorBG: 8, rounded: 22, outerBorderSize: 2, innerBorderSize: 2,
|
||||
autoProgress: true, _rounded: 18, STEP_SIZE: 4.06666666666, _xStart: 131, _width: 36,
|
||||
_yStart: 8, _height: 158,
|
||||
r: { x: 127, y: 4, x2: 171, y2: 170,
|
||||
w: 44, h: 166 },
|
||||
borderRect: { x: 127, y: 4, w: 44, h: 166,
|
||||
r: 22 },
|
||||
hollowRect: { x: 129, y: 6, w: 40, h: 162,
|
||||
r: 22 }
|
||||
}
|
||||
}
|
||||
>
|
||||
```
|
||||
Tips
|
||||
----
|
||||
|
||||
You can implement custom graphics for a slider in the `callbackFunction`. The slider test app mentioned in the links below do this. To draw on top of the included slider graphics you need to wrap the drawing code in a timeout somewhat like so: `setTimeout(drawingFunction,0,fb)` (see [`setTimeout` documentation](https://www.espruino.com/Reference#l__global_setTimeout)).
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
There is a [slider test app on thyttan's personal app loader](https://thyttan.github.io/BangleApps/?q=slidertest) (at time of writing). Looking at [its code](https://github.com/thyttan/BangleApps/blob/ui-slider-lib/apps/slidertest/app.js) is a good way to see how the slider is used in app development.
|
||||
|
||||
The version of [Remote for Spotify on thyttan's personal app loader](https://thyttan.github.io/BangleApps/?q=spotrem) (at time of writing) also utilizes the `Slider` module. Here is [the code](https://github.com/thyttan/BangleApps/blob/ui-slider-lib/apps/spotrem/app.js).
|
||||
Loading…
Reference in New Issue