Merge pull request #3328 from espruino/clock_backgrounds
Add 'clock backgrounds' app, allow configurable backgrounds in Pebble++pull/3376/head
|
@ -0,0 +1 @@
|
|||
0.01: New Clock!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4X//8HA4IEBgH4C5cFqgJHitQjWpBY9q0gLvI5ar/AAkgBRMC1ALJlX6CxOrBZMq34LJ1f/9QKHhW//2gCxP6wAWHy/+KREqq4WIgGtr+qLhG1vw5IgX1KBALBywWIIwNaHJEAlNqUZOltAuJyouKqwuKrQuhywuJNIIuJlIuJHQLGIBYQ6IgtU1Q6GitQjWplQVGtWkBYIhHBcpHBBY5HBM5IABA"))
|
|
@ -0,0 +1,242 @@
|
|||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
const background = require("clockbg"); // image backgrounds
|
||||
let drawTimeout; // timeout used to update every minute
|
||||
let date = new Date(); // date at last draw
|
||||
let lastModified = {x1:0,y1:0,x2:W-1,y2:H-1,first:true}; // rect that was covered by hands
|
||||
|
||||
const HOUR_LEN = 55; // how far forwards does hand go?
|
||||
const MIN_LEN = 72;
|
||||
const HOUR_BACK = 10; // how far backwards dows hand go?
|
||||
const MIN_BACK = 10;
|
||||
const HOUR_W = 10; // width of cleared area
|
||||
const MIN_W = 8;
|
||||
|
||||
function get_hand(len, w, cornerw, overhang) {
|
||||
return new Int8Array([
|
||||
0, overhang+w,
|
||||
-cornerw, overhang+cornerw,
|
||||
-w, overhang,
|
||||
-w, -len,
|
||||
-cornerw, -len - cornerw,
|
||||
0, -len - w,
|
||||
cornerw, -len - cornerw,
|
||||
w, -len,
|
||||
w, overhang,
|
||||
cornerw, overhang+cornerw
|
||||
]);
|
||||
}
|
||||
const hand_hour = get_hand(HOUR_LEN, 6, 4, HOUR_BACK);
|
||||
const hand_hour_bg = get_hand(HOUR_LEN, HOUR_W, 8, HOUR_BACK);
|
||||
const hand_minute = get_hand(MIN_LEN, 4, 3, MIN_BACK);
|
||||
const hand_minute_bg = get_hand(MIN_LEN, MIN_W, 6, MIN_BACK);
|
||||
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
// draw the clock hands
|
||||
function drawHands() {
|
||||
let h = date.getHours()*Math.PI/6, m = date.getMinutes()*Math.PI/30;
|
||||
g.setColor(g.theme.bg).fillPolyAA(g.transformVertices(hand_hour_bg,{x:W/2,y:H/2,rotate:h}));
|
||||
g.fillPolyAA(g.transformVertices(hand_minute_bg,{x:W/2,y:H/2,rotate:m}));
|
||||
g.setColor("#f00").fillPolyAA(g.transformVertices(hand_hour,{x:W/2,y:H/2,rotate:h}));
|
||||
g.setColor(g.theme.fg).fillPolyAA(g.transformVertices(hand_minute,{x:W/2,y:H/2,rotate:m}));
|
||||
}
|
||||
|
||||
// return the screen area covered by clock hands (used for filling in background)
|
||||
function getHandBounds() {
|
||||
let h = date.getHours()*Math.PI/6, m = date.getMinutes()*Math.PI/30;
|
||||
let sh = Math.sin(h), ch = Math.cos(h), sm = Math.sin(m), cm = Math.cos(m);
|
||||
return {
|
||||
x1 : Math.round((W/2)+Math.min(sh*HOUR_LEN, sm*MIN_LEN, -sh*HOUR_BACK, -sm*MIN_BACK)-HOUR_W),
|
||||
y1 : Math.round((H/2)-Math.max(ch*HOUR_LEN, cm*MIN_LEN, -ch*HOUR_BACK, -cm*MIN_BACK)-HOUR_W),
|
||||
x2 : Math.round((W/2)+Math.max(sh*HOUR_LEN, sm*MIN_LEN, -sh*HOUR_BACK, -sm*MIN_BACK)+HOUR_W),
|
||||
y2 : Math.round((H/2)-Math.min(ch*HOUR_LEN, cm*MIN_LEN, -ch*HOUR_BACK, -cm*MIN_BACK)+HOUR_W),
|
||||
};
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// queue next draw in one minute
|
||||
queueDraw();
|
||||
// work out locale-friendly date/time
|
||||
date = new Date();
|
||||
//var timeStr = require("locale").time(date,1);
|
||||
//var dateStr = require("locale").date(date);
|
||||
// fill in area that we changed last time
|
||||
background.fillRect(lastModified.x1, lastModified.y1, lastModified.x2, lastModified.y2);
|
||||
if (!lastModified.first) { // first draw we don't have clockInfoMenuA/etc defined
|
||||
if (lastModified.y1<30) {
|
||||
if (lastModified.x1 < 30) clockInfoMenuA.redraw();
|
||||
if (lastModified.x2 > W-30) clockInfoMenuB.redraw();
|
||||
}
|
||||
if (lastModified.y2>W-20) {
|
||||
if (lastModified.x1 < 30) clockInfoMenuD.redraw();
|
||||
if (lastModified.x2 > W-30) clockInfoMenuC.redraw();
|
||||
}
|
||||
}
|
||||
// draw hands
|
||||
drawHands();
|
||||
lastModified = getHandBounds();
|
||||
//g.drawRect(lastModified); // debug
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
background.fillRect(0, 0, W - 1, H - 1);
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
|
||||
// used for clockinfo image rendering
|
||||
let clockInfoG = Graphics.createArrayBuffer(28, 28, 2, {msb:true});
|
||||
clockInfoG.transparent = 3;
|
||||
// render clockinfos
|
||||
let clockInfoDraw = function(itm, info, options) {
|
||||
// itm: the item containing name/hasRange/etc
|
||||
// info: data returned from itm.get() containing text/img/etc
|
||||
// options: options passed into addInteractive
|
||||
const left = options.x < 88,
|
||||
top = options.y < 88,
|
||||
imgx = left ? 1 : W - 28, imgy = top ? 19 : H - 42,
|
||||
textx = left ? 2 : W - 1, texty = top ? 2 : H - 16;
|
||||
let bg = g.theme.bg, fg = g.theme.fg;
|
||||
// Clear the background
|
||||
g.reset();
|
||||
background.fillRect(imgx, imgy, imgx+25, imgy+25); // erase image
|
||||
background.fillRect(left?0:W/2, texty-1, left?W/2:W-1, texty+15); // erase text
|
||||
// indicate focus - change colours
|
||||
if (options.focus) {
|
||||
bg = g.theme.fg;
|
||||
fg = g.toColor("#f00");
|
||||
}
|
||||
|
||||
if (info.img) {
|
||||
//g.drawImage(info.img, left ? 2 : W - 27, top ? 18 : H - 41); // draw the image
|
||||
// fiddle around colouring the border and inside of the image
|
||||
clockInfoG.clear(1);
|
||||
// do a border - images need to be transparent for this
|
||||
clockInfoG.setColor(2).drawImage(info.img, 1,1).drawImage(info.img, 3,1).
|
||||
drawImage(info.img, 1,3).drawImage(info.img, 3,3);
|
||||
clockInfoG.setColor(1).drawImage(info.img, 2,2); // main image
|
||||
clockInfoG.floodFill(27,27,3); // flood fill edge to transparent
|
||||
clockInfoG.palette = new Uint16Array([bg,fg,bg/*border*/, g.toColor("#888")]);
|
||||
g.drawImage(clockInfoG, imgx-1, imgy-1);
|
||||
}
|
||||
|
||||
g.setFont("6x8:2").setFontAlign(left ? -1 : 1, -1);
|
||||
g.setColor(bg).drawString(info.text, textx-2, texty). // draw the text background
|
||||
drawString(info.text, textx+2, texty).
|
||||
drawString(info.text, textx, texty-2).
|
||||
drawString(info.text, textx, texty+2);
|
||||
g.setColor(fg).drawString(info.text, textx, texty); // draw the text
|
||||
// redraw hands if needed
|
||||
if ((top && lastModified.x1<texty+15) ||
|
||||
(!top && lastModified.y2>=texty)) {
|
||||
g.reset();
|
||||
drawHands();
|
||||
}
|
||||
};
|
||||
|
||||
// Load the clock infos
|
||||
let clockInfoItems = require("clock_info").load();
|
||||
let clockInfoItemsBangle = clockInfoItems.find(i=>i.name=="Bangle");
|
||||
// Add extra Calendar and digital clock ClockInfos
|
||||
if (clockInfoItemsBangle) {
|
||||
if (!clockInfoItemsBangle.items.find(i=>i.name=="Date")) {
|
||||
clockInfoItemsBangle.items.push({ name : "Date",
|
||||
get : () => {
|
||||
let d = new Date();
|
||||
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
||||
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
||||
return {
|
||||
text : require("locale").dow(d,1).toUpperCase(),
|
||||
img : g.asImage("string")
|
||||
};
|
||||
},
|
||||
show : function() {
|
||||
this.interval = setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
this.interval = setInterval(()=>{
|
||||
this.emit("redraw");
|
||||
}, 86400000);
|
||||
}, 86400000 - (Date.now() % 86400000));
|
||||
},
|
||||
hide : function() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!clockInfoItemsBangle.items.find(i=>i.name=="Clock")) {
|
||||
clockInfoItemsBangle.items.push({ name : "Clock",
|
||||
get : () => {
|
||||
return {
|
||||
text : require("locale").time(new Date(),1),
|
||||
img : atob("GBiBAAAAAAB+AAD/AAD/AAH/gAP/wAP/wAYAYAYAYAYAYAYAYAYAcAYAcAYAYAYAYAYAYAYAYAP/wAP/wAH/gAD/AAD/AAB+AAAAAA==")
|
||||
};
|
||||
},
|
||||
show : function() {
|
||||
this.interval = setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
this.interval = setInterval(()=>{
|
||||
this.emit("redraw");
|
||||
}, 60000);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
},
|
||||
hide : function() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add the 4 clockinfos
|
||||
const CLOCKINFOSIZE = 50;
|
||||
let clockInfoMenuA = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: CLOCKINFOSIZE,
|
||||
h: CLOCKINFOSIZE,
|
||||
draw: clockInfoDraw
|
||||
});
|
||||
let clockInfoMenuB = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x: W - CLOCKINFOSIZE,
|
||||
y: 0,
|
||||
w: CLOCKINFOSIZE,
|
||||
h: CLOCKINFOSIZE,
|
||||
draw: clockInfoDraw
|
||||
});
|
||||
let clockInfoMenuC = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x: W - CLOCKINFOSIZE,
|
||||
y: H - CLOCKINFOSIZE,
|
||||
w: CLOCKINFOSIZE,
|
||||
h: CLOCKINFOSIZE,
|
||||
draw: clockInfoDraw
|
||||
});
|
||||
let clockInfoMenuD = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x: 0,
|
||||
y: H - CLOCKINFOSIZE,
|
||||
w: CLOCKINFOSIZE,
|
||||
h: CLOCKINFOSIZE,
|
||||
draw: clockInfoDraw
|
||||
});
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({
|
||||
mode: "clock",
|
||||
remove: function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
clockInfoMenuA.remove();
|
||||
}
|
||||
});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").hide();
|
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,16 @@
|
|||
{ "id": "analogquadclk",
|
||||
"name": "Analog Quad Clock",
|
||||
"shortName":"Quad Clock",
|
||||
"version":"0.01",
|
||||
"description": "An analog clock with clockinfos in each of the 4 corners, allowing 4 different data types to be rendered at once",
|
||||
"icon": "icon.png",
|
||||
"screenshots" : [ { "url":"screenshot.png" }, { "url":"screenshot2.png" } ],
|
||||
"type": "clock",
|
||||
"tags": "clock,clkinfo,analog",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"dependencies" : { "clock_info":"module", "clockbg":"module" },
|
||||
"storage": [
|
||||
{"name":"analogquadclk.app.js","url":"app.js"},
|
||||
{"name":"analogquadclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Moved settings into 'Settings->Apps'
|
|
@ -0,0 +1,40 @@
|
|||
# Clock Backgrounds
|
||||
|
||||
This app provides a library (`clockbg`) that can be used by clocks to
|
||||
provide different backgrounds for them.
|
||||
|
||||
## Usage
|
||||
|
||||
By default the app provides just a red/green/blue background but it can easily be configured.
|
||||
|
||||
You can either:
|
||||
|
||||
* Go to [the Clock Backgrounds app](https://banglejs.com/apps/?id=clockbg) in the App Loader and upload backgrounds
|
||||
* Go to the `Backgrounds` app on the Bangle itself, and choose between solid color, random colors, or any uploaded images.
|
||||
|
||||
|
||||
## Usage in code
|
||||
|
||||
Just use the following to use this library within your code:
|
||||
|
||||
```JS
|
||||
// once at the start
|
||||
let background = require("clockbg");
|
||||
|
||||
// to fill the whole area
|
||||
background.fillRect(Bangle.appRect);
|
||||
|
||||
// to fill just one part of the screen
|
||||
background.fillRect(x1, y1, x2, y2);
|
||||
```
|
||||
|
||||
You should also add `"dependencies" : { "clockbg":"module" },` to your app's metadata to
|
||||
ensure that the clock background library is automatically loaded.
|
||||
|
||||
## Features to be added
|
||||
|
||||
This library/app is still pretty basic right now, but a few features could be added that would really improve functionality:
|
||||
|
||||
* Support for >1 image, and choosing randomly between them
|
||||
* Support for gradients (random colors)
|
||||
* Storing 'clear' areas of uploaded images so clocks can easily position themselves
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,19 @@
|
|||
Clock Images
|
||||
=============
|
||||
|
||||
If you want to add your own images ensure they're in the same style and then also list the image file in custom.html in the root directory.
|
||||
|
||||
## Flags
|
||||
|
||||
The flags come from https://icons8.com/icon/set/flags/color and are 480x480px
|
||||
|
||||
If your flag is listed in https://icons8.com/icon/set/flags/color and you can't download it in the right size, please file an issue and we'll download it with our account.
|
||||
|
||||
|
||||
## Other backgrounds
|
||||
|
||||
Backgrounds prefixed `ai_` are generated by the AI [Bing Image Creator](https://www.bing.com/images/create)
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 49 KiB |
|
@ -0,0 +1,179 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<style>
|
||||
.flag {
|
||||
width : 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#preview {
|
||||
width : 176px;
|
||||
height: 176px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>Upload an image:</p>
|
||||
<div id="flaglist"></div>
|
||||
<p>If you'd like to contribute images you can <a href="https://github.com/espruino/BangleApps/tree/master/apps/patriotclk/img" target="_blank">add them on GitHub</a>!</p>
|
||||
<div style="float:right">Preview:<br/><canvas width="176" height="176" id="preview"></canvas></div>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="box_zoom">
|
||||
<i class="form-icon"></i> Zoom
|
||||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="box_mirror">
|
||||
<i class="form-icon"></i> Mirror
|
||||
</label>
|
||||
<select class="form-select" id="box_bright" style="width:inherit">
|
||||
<option value="0">Normal Brightness</option>
|
||||
<option value="1">Brighten</option>
|
||||
<option value="-1">Darken</option>
|
||||
</select><br/>
|
||||
<select class="form-select" id="box_dither" style="width:inherit">
|
||||
<option value="">Default Dither</option>
|
||||
<option value="no">No Dither</option>
|
||||
<option value="bayer2">Bayer Dither</option>
|
||||
<option value="error">Diffusion Dither</option>
|
||||
<option value="random1">Random Dither</option>
|
||||
<option value="comic">'Comic' Dither</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<p>Click <button id="upload" class="btn btn-primary disabled" style>Upload</button></p>
|
||||
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script src="../../webtools/imageconverter.js"></script>
|
||||
|
||||
<script>
|
||||
const IMAGES =[
|
||||
{"path":"img/ai_rock.jpeg","dither":true},
|
||||
{"path":"img/ai_robot.jpeg","dither":true},
|
||||
{"path":"img/ai_eye.jpeg","dither":true},
|
||||
{"path":"img/ai_hero.jpeg","dither":true},
|
||||
{"path":"img/icons8-australia-480.png","dither":false},
|
||||
{"path":"img/icons8-austria-480.png","dither":false},
|
||||
{"path":"img/icons8-belgium-480.png","dither":false},
|
||||
{"path":"img/icons8-brazil-480.png","dither":false},
|
||||
{"path":"img/icons8-canada-480.png","dither":false},
|
||||
{"path":"img/icons8-china-480.png","dither":false},
|
||||
{"path":"img/icons8-denmark-480.png","dither":false},
|
||||
{"path":"img/icons8-england-480.png","dither":false},
|
||||
{"path":"img/icons8-flag-of-europe-480.png","dither":false},
|
||||
{"path":"img/icons8-france-480.png","dither":false},
|
||||
{"path":"img/icons8-germany-480.png","dither":false},
|
||||
{"path":"img/icons8-great-britain-480.png","dither":false},
|
||||
{"path":"img/icons8-greece-480.png","dither":false},
|
||||
{"path":"img/icons8-hungary-480.png","dither":false},
|
||||
{"path":"img/icons8-italy-480.png","dither":false},
|
||||
{"path":"img/icons8-netherlands-480.png","dither":false},
|
||||
{"path":"img/icons8-new-zealand-480.png","dither":false},
|
||||
{"path":"img/icons8-norway-480.png","dither":false},
|
||||
{"path":"img/icons8-scotland-480.png","dither":false},
|
||||
{"path":"img/icons8-spain-480.png","dither":false},
|
||||
{"path":"img/icons8-sweden-480.png","dither":false},
|
||||
{"path":"img/icons8-switzerland-480.png","dither":false},
|
||||
{"path":"img/icons8-usa-480.png","dither":false},
|
||||
{"path":"img/icons8-wales-480.png","dither":false},
|
||||
{"path":"img/icons8-lgbt-flag-480.png","dither":true},
|
||||
{"path":"img/icons8-ukraine-480.png","dither":true}];
|
||||
|
||||
var selectedImage;
|
||||
var bgImageData;
|
||||
|
||||
document.getElementById("flaglist").innerHTML =
|
||||
IMAGES.map(f => `<img class="flag" src="${f.path}" data-file="${f.path}"/>`).join("\n");
|
||||
var elements = document.querySelectorAll(".flag");
|
||||
for (var i=0;i<elements.length;i++)
|
||||
elements[i].addEventListener("click", function(e) {
|
||||
selectedImage = e.target;
|
||||
drawPreview();
|
||||
document.getElementById("upload").classList.remove("disabled")
|
||||
});
|
||||
|
||||
|
||||
function drawPreview() {
|
||||
if (!selectedImage) return;
|
||||
var imgPath = selectedImage.getAttribute("data-file");
|
||||
var img = IMAGES.find(img => img.path == imgPath);
|
||||
var zoom = document.getElementById("box_zoom").checked;
|
||||
var dither = document.getElementById("box_dither").value;
|
||||
if (dither=="" && img.dither) dither="bayer2";
|
||||
if (dither=="no" || dither=="") dither=undefined;
|
||||
var mirror = document.getElementById("box_mirror").checked;
|
||||
var brightness = 0|document.getElementById("box_bright").value;
|
||||
const canvas = document.getElementById("preview");
|
||||
canvas.width = 176; // setting size clears canvas
|
||||
canvas.height = 176;
|
||||
const ctx = canvas.getContext("2d");
|
||||
var y = 0;
|
||||
console.log(selectedImage);
|
||||
let imgW = selectedImage.naturalWidth;
|
||||
let imgH = selectedImage.naturalHeight;
|
||||
let border = 0;
|
||||
if (imgW > 400) border = 20;
|
||||
if (zoom) border = (border*5) >> 1;
|
||||
ctx.save(); // Save the current state
|
||||
if (mirror) {
|
||||
ctx.translate(canvas.width, 0);
|
||||
ctx.scale(-1, 1);
|
||||
}
|
||||
ctx.drawImage(selectedImage, border, border, imgW-border*2, imgH-border*2, 0, y, canvas.width, canvas.height);
|
||||
ctx.restore();
|
||||
var options = {
|
||||
mode:"3bit",
|
||||
output:"raw",
|
||||
compression:false,
|
||||
updateCanvas:true,
|
||||
transparent:false,
|
||||
diffusion:dither,
|
||||
contrast: brightness ? -64 : 64,
|
||||
brightness:64*brightness
|
||||
};
|
||||
bgImageData = imageconverter.canvastoString(canvas, options);
|
||||
}
|
||||
|
||||
// If options changed
|
||||
document.getElementById("box_zoom").addEventListener("click", function() {
|
||||
drawPreview();
|
||||
});
|
||||
document.getElementById("box_dither").addEventListener("click", function() {
|
||||
drawPreview();
|
||||
});
|
||||
document.getElementById("box_mirror").addEventListener("click", function() {
|
||||
drawPreview();
|
||||
});
|
||||
document.getElementById("box_bright").addEventListener("click", function() {
|
||||
drawPreview();
|
||||
});
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
let settings = {
|
||||
style : "image",
|
||||
fn : "clockbg.bg0.img"
|
||||
};
|
||||
Util.showModal("Uploading Image...");
|
||||
Util.writeStorage(settings.fn, bgImageData, function(data) {
|
||||
Util.writeStorage("clockbg.json", JSON.stringify(settings), function(data) {
|
||||
Util.hideModal()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function onInit() {
|
||||
Util.readStorageJSON("clockbg.json", function(data) {
|
||||
let settings = Object.assign({
|
||||
style : "randomcolor",
|
||||
colors : ["#F00","#0F0","#00F"]
|
||||
},data);
|
||||
console.log(settings);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
let settings = Object.assign({
|
||||
style : "randomcolor",
|
||||
colors : ["#F00","#0F0","#00F"]
|
||||
},require("Storage").readJSON("clockbg.json")||{});
|
||||
if (settings.style=="image")
|
||||
settings.img = require("Storage").read(settings.fn);
|
||||
if (settings.style=="randomcolor") {
|
||||
settings.style = "color";
|
||||
var n = (0|(Math.random()*settings.colors.length)) % settings.colors.length;
|
||||
settings.color = settings.colors[n];
|
||||
}
|
||||
|
||||
// Fill a rectangle with the current background style, rect = {x,y,w,h}
|
||||
// eg require("clockbg").fillRect({x:10,y:10,w:50,h:50})
|
||||
// require("clockbg").fillRect(Bangle.appRect)
|
||||
exports.fillRect = function(rect,y,x2,y2) {
|
||||
if ("object"!=typeof rect) rect = {x:rect,y:y,w:1+x2-rect,h:1+y2-y};
|
||||
if (settings.img) {
|
||||
g.setClipRect(rect.x, rect.y, rect.x+rect.w-1, rect.y+rect.h-1).drawImage(settings.img).setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
|
||||
} else if (settings.style == "color") {
|
||||
g.setBgColor(settings.color).clearRect(rect);
|
||||
} else {
|
||||
console.log("clockbg: No background set!");
|
||||
g.setBgColor(g.theme.bg).clearRect(rect);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{ "id": "clockbg",
|
||||
"name": "Clock Backgrounds",
|
||||
"shortName":"Backgrounds",
|
||||
"version": "0.02",
|
||||
"description": "Library that allows clocks to include a custom background, from a library or uploaded.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "module",
|
||||
"readme": "README.md",
|
||||
"provides_modules" : ["clockbg"],
|
||||
"tags": "module,background",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"clockbg","url":"lib.js"},
|
||||
{"name":"clockbg.settings.js","url":"settings.js"}
|
||||
], "data": [
|
||||
{"wildcard":"clockbg.bg*.img"},
|
||||
{"name":"clockbg.json"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 57 KiB |
|
@ -0,0 +1,95 @@
|
|||
(function(back) {
|
||||
let settings = Object.assign({
|
||||
style : "randomcolor",
|
||||
colors : ["#F00","#0F0","#00F"]
|
||||
},require("Storage").readJSON("clockbg.json")||{});
|
||||
|
||||
function saveSettings() {
|
||||
if (settings.style!="image")
|
||||
delete settings.fn;
|
||||
if (settings.style!="color")
|
||||
delete settings.color;
|
||||
if (settings.style!="randomcolor")
|
||||
delete settings.colors;
|
||||
require("Storage").writeJSON("clockbg.json", settings);
|
||||
}
|
||||
|
||||
function getColorsImage(cols) {
|
||||
var bpp = 1;
|
||||
if (cols.length>4) bpp=4;
|
||||
else if (cols.length>2) bpp=2;
|
||||
var b = Graphics.createArrayBuffer(16*cols.length,16,bpp);
|
||||
b.palette = new Uint16Array(1<<bpp);
|
||||
cols.forEach((c,i)=>{
|
||||
b.setColor(i).fillRect(i*16,0,i*16+15,15);
|
||||
b.palette[i] = g.toColor(c);
|
||||
});
|
||||
return "\0"+b.asImage("string");
|
||||
}
|
||||
|
||||
function showModeMenu() {
|
||||
E.showMenu({
|
||||
"" : {title:/*LANG*/"Background", back:showMainMenu},
|
||||
/*LANG*/"Solid Color" : function() {
|
||||
var cols = ["#F00","#0F0","#FF0",
|
||||
"#00F","#F0F","#0FF",
|
||||
"#000","#888","#fff",];
|
||||
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
|
||||
cols.forEach(col => {
|
||||
menu["-"+getColorsImage([col])] = () => {
|
||||
settings.style = "color";
|
||||
settings.color = col;
|
||||
saveSettings();
|
||||
showMainMenu();
|
||||
};
|
||||
});
|
||||
E.showMenu(menu);
|
||||
},
|
||||
/*LANG*/"Random Color" : function() {
|
||||
var cols = [
|
||||
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
|
||||
["#F00","#0F0","#00F"],
|
||||
];
|
||||
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
|
||||
cols.forEach(col => {
|
||||
menu[getColorsImage(col)] = () => {
|
||||
settings.style = "randomcolor";
|
||||
settings.colors = col;
|
||||
saveSettings();
|
||||
showMainMenu();
|
||||
};
|
||||
});
|
||||
E.showMenu(menu);
|
||||
},
|
||||
/*LANG*/"Image" : function() {
|
||||
let images = require("Storage").list(/clockbg\..*\.img/);
|
||||
if (images.length) {
|
||||
var menu = {"":{title:/*LANG*/"Images", back:showModeMenu}};
|
||||
images.forEach(im => {
|
||||
menu[im.slice(8,-4)] = () => {
|
||||
settings.style = "image";
|
||||
settings.fn = im;
|
||||
saveSettings();
|
||||
showMainMenu();
|
||||
};
|
||||
});
|
||||
E.showMenu(menu);
|
||||
} else {
|
||||
E.showAlert("Please use App Loader to upload images").then(showModeMenu);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
E.showMenu({
|
||||
"" : {title:/*LANG*/"Clock Background", back:back},
|
||||
/*LANG*/"Mode" : {
|
||||
value : settings.style,
|
||||
onchange : showModeMenu
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
})
|
|
@ -4,3 +4,4 @@
|
|||
0.03: Use smaller font if clock_info test doesn't fit in area
|
||||
0.04: Ensure we only scale down clockinfo text if it really won't fit
|
||||
0.05: Minor code improvements
|
||||
0.06: Use the clockbg library to allow custom image backgrounds
|
|
@ -20,7 +20,8 @@ Graphics.prototype.setFontLECO1976Regular14 = function() {
|
|||
|
||||
{
|
||||
const SETTINGS_FILE = "pebblepp.json";
|
||||
let settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'bg': '#0f0', 'color': 'Green', 'theme':'System', 'showlock':false};
|
||||
let settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'theme':'System', 'showlock':false};
|
||||
let background = require("clockbg");
|
||||
let theme;
|
||||
let drawTimeout;
|
||||
|
||||
|
@ -50,7 +51,7 @@ let draw = function() {
|
|||
};
|
||||
|
||||
let loadThemeColors = function() {
|
||||
theme = {fg: g.theme.fg, bg: g.theme.bg, day: g.toColor(0,0,0)};
|
||||
theme = {fg: g.theme.fg, bg: g.theme.bg };
|
||||
if (settings.theme === "Dark") {
|
||||
theme.fg = g.toColor(1,1,1);
|
||||
theme.bg = g.toColor(0,0,0);
|
||||
|
@ -58,13 +59,15 @@ let loadThemeColors = function() {
|
|||
theme.fg = g.toColor(0,0,0);
|
||||
theme.bg = g.toColor(1,1,1);
|
||||
}
|
||||
// day and steps
|
||||
if (settings.color == 'Blue' || settings.color == 'Red')
|
||||
theme.day = g.toColor(1,1,1); // white on blue or red best contrast
|
||||
};
|
||||
loadThemeColors();
|
||||
|
||||
// Load the clock infos
|
||||
let clockInfoW = 0|(w/2);
|
||||
let clockInfoH = 0|(h/2);
|
||||
let clockInfoG = Graphics.createArrayBuffer(25, 25, 2, {msb:true});
|
||||
clockInfoG.transparent = 3;
|
||||
clockInfoG.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor("#888"), g.toColor("#888")]);
|
||||
let clockInfoItems = require("clock_info").load();
|
||||
let clockInfoDraw = (itm, info, options) => {
|
||||
// itm: the item containing name/hasRange/etc
|
||||
|
@ -72,25 +75,26 @@ let clockInfoDraw = (itm, info, options) => {
|
|||
// options: options passed into addInteractive
|
||||
// Clear the background - if focussed, add a border
|
||||
g.reset().setBgColor(theme.bg).setColor(theme.fg);
|
||||
var b = 0; // border
|
||||
var y,b = 0; // border
|
||||
if (options.focus) { // white border
|
||||
b = 4;
|
||||
g.clearRect(options.x, options.y, options.x+options.w-1, options.y+options.h-1);
|
||||
}
|
||||
g.setBgColor(settings.bg).clearRect(options.x+b, options.y+b, options.x+options.w-1-b, options.y+options.h-1-b);
|
||||
background.fillRect(options.x+b, options.y+b, options.x+options.w-1-b, options.y+options.h-1-b);
|
||||
// we're drawing center-aligned here
|
||||
var midx = options.x+options.w/2;
|
||||
if (info.img) { // draw the image
|
||||
// TODO: we could replace certain images with our own ones here...
|
||||
var y = options.y+8;
|
||||
y = options.y+8;
|
||||
if (g.floodFill) {
|
||||
/* img is (usually) a black and white transparent image. But we really would like the bits in
|
||||
the middle of it to be white. So what we do is we draw a slightly bigger rectangle in white,
|
||||
draw the image, and then flood-fill the rectangle back to the background color. floodFill
|
||||
was only added in 2v18 so we have to check for it and fallback if not. */
|
||||
g.setBgColor(theme.bg).clearRect(midx-25,y-1,midx+24,y+48);
|
||||
g.drawImage(info.img, midx-24,y,{scale:2});
|
||||
g.floodFill(midx-25,y,settings.bg);
|
||||
clockInfoG.setBgColor(0).clearRect(0,0,24,24);
|
||||
clockInfoG.setColor(1).drawImage(info.img, 0,0);
|
||||
clockInfoG.floodFill(24,24,3);
|
||||
g.drawImage(clockInfoG, midx-24,y,{scale:2});
|
||||
} else { // fallback
|
||||
g.drawImage(info.img, midx-24,y,{scale:2});
|
||||
}
|
||||
|
@ -103,17 +107,18 @@ let clockInfoDraw = (itm, info, options) => {
|
|||
var l = g.wrapString(txt, options.w);
|
||||
txt = l.slice(0,2).join("\n") + (l.length>2)?"...":"";
|
||||
}
|
||||
g.drawString(txt, midx,options.y+options.h-12); // draw the text
|
||||
y = options.y+options.h-12;
|
||||
g.drawString(txt, midx, y); // draw the text
|
||||
};
|
||||
|
||||
let clockInfoMenuA = require("clock_info").addInteractive(clockInfoItems, {
|
||||
app:"pebblepp",
|
||||
x : 0, y: 0, w: w/2, h:h/2,
|
||||
x : 0, y: 0, w: clockInfoW, h:clockInfoH,
|
||||
draw : clockInfoDraw
|
||||
});
|
||||
let clockInfoMenuB = require("clock_info").addInteractive(clockInfoItems, {
|
||||
app:"pebblepp",
|
||||
x : w/2, y: 0, w: w/2, h:h/2,
|
||||
x : w/2, y: 0, w: clockInfoW, h:clockInfoH,
|
||||
draw : clockInfoDraw
|
||||
});
|
||||
|
||||
|
@ -134,7 +139,7 @@ Bangle.setUI({
|
|||
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
g.setBgColor(settings.bg).clear(); // start off with completely clear background
|
||||
background.fillRect(Bangle.appRect); // start off with completely clear background
|
||||
// contrast bar (top)
|
||||
g.setColor(theme.fg).fillRect(0, h2 - 6, w, h2);
|
||||
// contrast bar (bottom)
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
"id": "pebblepp",
|
||||
"name": "Pebble++ Clock",
|
||||
"shortName": "Pebble++",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "A pebble style clock (based on the 'Pebble Clock' app) but with two configurable ClockInfo items at the top",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies" : { "clock_info":"module" },
|
||||
"allow_emulator": true,
|
||||
"dependencies" : { "clock_info":"module", "clockbg":"module" },
|
||||
"storage": [
|
||||
{"name":"pebblepp.app.js","url":"app.js"},
|
||||
{"name":"pebblepp.settings.js","url":"settings.js"},
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "pebblepp.json";
|
||||
|
||||
// TODO Only the color/theme indices should be written in the settings file so the labels can be translated
|
||||
|
||||
// Initialize with default settings...
|
||||
let s = {'bg': '#0f0', 'color': 'Green', 'theme':'System', 'showlock':false}
|
||||
let s = {'theme':'System', 'showlock':false}
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
|
@ -20,23 +18,11 @@
|
|||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
|
||||
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
|
||||
var theme_options = ['System', 'Light', 'Dark'];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Pebble++ Clock' },
|
||||
/*LANG*/'< Back': back,
|
||||
/*LANG*/'Colour': {
|
||||
value: 0 | color_options.indexOf(s.color),
|
||||
min: 0, max: 5,
|
||||
format: v => color_options[v],
|
||||
onchange: v => {
|
||||
s.color = color_options[v];
|
||||
s.bg = bg_code[v];
|
||||
save();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Theme': {
|
||||
value: 0 | theme_options.indexOf(s.theme),
|
||||
min: 0, max: theme_options.length - 1,
|
||||
|
|