Skip to content

Created Flux Watchface Clock #3959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/flux/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.01: New App!
35 changes: 35 additions & 0 deletions apps/flux/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Flux Watchface

This app slightly resembles Apples "Flux" watchface.

## Usage

Install it and set it as your default clock.
It currently has no widget support. In the future, widgets will be a toggle in the settings.
You can change the colors in the settings.

## Features

- Customizeable themes (4 color inputs)
- Random number alignment (every minute or on reload)
- 24h clock (12h not yet supported)
- Power-saving mode
- Color filling effect in 2 directions (`Left > Right` and `Right > Left` are currently unsupported.)
- 12 different colors

## Controls

BTN to go to launcher. No other mappings.

## Requests

If you have a feature request or a bug, please message me at [[email protected]](mailto:[email protected] "Click to send an email").

## Creator

Yanis Ocaña, sossinay
[pixelnet.ocaña.ch](https://pixelnet.ocaña.ch)

## Known issues
- When loading the app in the first 2 seconds of a minute, the numbers fly in from a seemingly random position
- `Left > Right` and `Right > Left` are currently unsupported
1 change: 1 addition & 0 deletions apps/flux/app-icon.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

329 changes: 329 additions & 0 deletions apps/flux/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
const font = {
"0": [
[[0,0],[1,0]],
[[1,0],[1,1]],
[[1,1],[0,1]],
[[0,1],[0,0]]
],

"1": [
[[1,0],[1,1]]
],

"2": [
[[0,0],[1,0]],
[[1,0],[1,0.5]],
[[1,0.5],[0,0.5]],
[[0,0.5],[0,1]],
[[0,1],[1,1]]
],

"3": [
[[0,0],[1,0]],
[[1,0],[1,1]],
[[0,0.5],[1,0.5]],
[[0,1],[1,1]]
],

"4": [
[[0,0],[0,0.5]],
[[1,0],[1,1]],
[[0,0.5],[1,0.5]]
],

"5": [
[[1,0],[0,0]],
[[0,0],[0,0.5]],
[[0,0.5],[1,0.5]],
[[1,0.5],[1,1]],
[[1,1],[0,1]]
],

"6": [
[[1,0],[0,0]],
[[0,0],[0,1]],
[[0,1],[1,1]],
[[1,1],[1,0.5]],
[[1,0.5],[0,0.5]]
],

"7": [
[[0,0],[1,0]],
[[1,0],[1,1]]
],

"8": [
[[0,0],[1,0]],
[[1,0],[1,1]],
[[1,1],[0,1]],
[[0,1],[0,0]],
[[0,0.5],[1,0.5]]
],

"9": [
[[0,0],[1,0]],
[[0,0],[0,0.5]],
[[0,0.5],[1,0.5]],
[[1,0],[1,1]]
],

"NaN": [
[[],[]]
]
};

const corner_base_positions = [
[[10, 10], [78, 78]], // top-left (unchanged)
[[93, 10], [165, 78]], // top-right (x + 5)
[[10, 93], [78, 165]], // bottom-left (y + 5)
[[93, 93], [165, 165]] // bottom-right (x + 5, y + 5)
];

let corner_positions = [];
let previous_corner_positions = [];

const padding = 10;
const width = 175;
const height = 175;

const colors = {
"Black": [0,0,0],
"White": [1,1,1],
"Red": [1,0,0],
"Blue": [0,0,1],
"Green": [0,1,0],
"Yellow": [1,1,0],
"Orange": [1,0.6,0],
"Purple": [0.7,0,1],
"Lime": [0,1,0.5],
"Cyan": [0, 1, 1],
"Light Blue": [0,0.5,1],
"Pink": [1,0.5,1]
};


const color_schemes = {
"Lime": [[0,0,0],[1,1,1],[0,1,0.5],[0,1,0.5]],
"Red": [[0,0,0],[1,1,1],[0,1,0.5],[0,1,0.5]]
};

let bg_color = [0,0,0];
let fg_color = [1,1,1];
let bg_color_topo = [1,0,0.5];
let fg_color_topo = [0,1,0.5];

function load_color_scheme(color_scheme) {
bg_color = color_scheme[0];
fg_color = color_scheme[1];
bg_color_topo = color_scheme[2];
fg_color_topo = color_scheme[3];
}

function randomize_numbers(){
previous_corner_positions = JSON.parse(JSON.stringify(corner_positions));
corner_positions = JSON.parse(JSON.stringify(corner_base_positions));

if (!previous_corner_positions || previous_corner_positions.length === 0) {
previous_corner_positions = JSON.parse(JSON.stringify(corner_positions));
}

// Your offsets and modifications follow here as before
let x_offset = Math.floor(Math.random() * 61) - 30;
let left_y_offset = Math.floor(Math.random() * 61) - 30;
let right_y_offset = Math.floor(Math.random() * 61) - 30;

[0,1,2,3].forEach(function(i) {
let min_x = corner_positions[i][0][0];
let min_y = corner_positions[i][0][1];
let max_x = corner_positions[i][1][0];
let max_y = corner_positions[i][1][1];

if (min_x !== padding) {
min_x += x_offset;
}
if (max_x !== width - padding) {
max_x += x_offset;
}

let yoff = (i % 2) > 0 ? right_y_offset : left_y_offset;

if (min_y !== padding) {
min_y += yoff;
}
if (max_y !== height - padding) {
max_y += yoff;
}

// Update corner_positions with new offsets
corner_positions[i][0][0] = min_x;
corner_positions[i][0][1] = max_x;
corner_positions[i][1][0] = min_y;
corner_positions[i][1][1] = max_y;
});
}

function drawThickLine(x1, y1, x2, y2, width, topoY) {
let half = Math.floor(width / 2);

// Expand to thickness by offsetting both ends
if (x1 === x2) {
// Vertical line
x1 -= half;
x2 += half;
} else if (y1 === y2) {
// Horizontal line
y1 -= half;
y2 += half;
} else {
// Diagonal fallback
for (let i = -half; i <= half; i++) {
g.drawLine(x1 + i, y1, x2 + i, y2);
}
return;
}

// Order the coordinates for fillRect
let left = Math.min(x1, x2);
let right = Math.max(x1, x2);
let top = Math.min(y1, y2);
let bottom = Math.max(y1, y2);

// Split based on topoY (topographic height)
if (top < topoY && bottom > topoY) {
// Split into two regions
g.setColor(fg_color_topo[0], fg_color_topo[1], fg_color_topo[2]);
g.fillRect(left, top, right, topoY);

g.setColor(fg_color[0], fg_color[1], fg_color[2]);
g.fillRect(left, topoY + 1, right, bottom);
} else if (bottom <= topoY) {
// Entire line is above topo
g.setColor(fg_color_topo[0], fg_color_topo[1], fg_color_topo[2]);
g.fillRect(left, top, right, bottom);
} else {
// Entire line is below topo
g.setColor(fg_color[0], fg_color[1], fg_color[2]);
g.fillRect(left, top, right, bottom);
}
}




function draw_numbers(transition_value, topo_position){
//console.log(previous_corner_positions);
topo_position *= height;
let d = new Date();

let hour = d.getHours();
let padded = ("0" + hour).slice(-2);

let tl = padded[0];
let tr = padded[1];

let min = d.getMinutes();
padded = ("0" + min).slice(-2);


let bl = padded[0];
let br = padded[1];

g.clear();
g.setColor(bg_color_topo[0],bg_color_topo[1],bg_color_topo[2]);
g.fillRect(0,0,width,topo_position);
g.setColor(bg_color[0],bg_color[1],bg_color[2]);
g.fillRect(0,topo_position,width,height);

[tl,tr,bl,br].forEach(function(corner, i) {
let path = font[corner];
path.forEach(function(line){
let pcp = previous_corner_positions[i].slice();
let ccp = corner_positions[i].slice();

let x1 = (ccp[0][0] - pcp[0][0]) * transition_value + pcp[0][0];
let x2 = (ccp[0][1] - pcp[0][1]) * transition_value + pcp[0][1];
let y1 = (ccp[1][0] - pcp[1][0]) * transition_value + pcp[1][0];
let y2 = (ccp[1][1] - pcp[1][1]) * transition_value + pcp[1][1];



let point_1 = [line[0][0]*(x2-x1) + x1, line[0][1]*(y2-y1) + y1];
let point_2 = [line[1][0]*(x2-x1) + x1, line[1][1]*(y2-y1) + y1];

drawThickLine(point_1[0], point_1[1], point_2[0], point_2[1], 10, topo_position);
});
});
}

let powersaver = false;
let update_timeout = null;

let prev_min = -1;
function update_clock() {
let d = new Date();
let min = d.getMinutes();

if (min != prev_min) {
randomize_numbers();
prev_min = min;
}

if (powersaver) {
if (direction == 1) {
draw_numbers(1, 2);
}
else if (direction == 0) {
draw_numbers(1, 0);
}
update_timeout = setTimeout(update_clock, 5000);
}
else {
let seconds = d.getSeconds() + d.getMilliseconds() / 1000;
if (direction == 1) {
draw_numbers(Math.min(seconds, 1) / 1, 1.00-(seconds/60));
}
else if (direction == 0) {
draw_numbers(Math.min(seconds, 1) / 1, seconds / 60);
}
if (seconds < 1.5) {
update_timeout = setTimeout(update_clock, 50);
} else {
update_timeout = setTimeout(update_clock, 1000);
}
}
}

let settings = require("Storage").readJSON("flux.settings.json", 1) || {};
console.log(settings);
let direction = settings.direction || 0;
bg_color = colors[settings.bg || "Black"];
fg_color = colors[settings.fg || "White"];
bg_color_topo = colors[settings.bg2 || "Green"];
fg_color_topo = colors[settings.fg2 || "Black"];
if (direction == 1) {
let temp = bg_color;
bg_color = bg_color_topo;
bg_color_topo = temp;

temp = fg_color;
fg_color = fg_color_topo;
fg_color_topo = temp;
}


update_clock();

Bangle.on('backlight', function(isOn) {
if (isOn) {
powersaver = false;
clearTimeout(update_timeout);
update_clock();
} else {
powersaver = true;
let d = new Date();
let seconds = d.getSeconds() + d.getMilliseconds() / 1000;
draw_numbers(Math.min(seconds, 1) / 1, 0);
}
});

Bangle.setUI("clock");
Binary file added apps/flux/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading