546 lines
14 KiB
JavaScript
546 lines
14 KiB
JavaScript
const Layout = require('Layout');
|
|
|
|
const tt = require('triangletimer');
|
|
|
|
// UI //
|
|
|
|
class TimerView {
|
|
constructor(tri_timer) {
|
|
this.tri_timer = tri_timer;
|
|
|
|
this.layout = null;
|
|
this.listeners = {};
|
|
this.timer_timeout = null;
|
|
}
|
|
|
|
start() {
|
|
this._initLayout();
|
|
this.layout.clear();
|
|
this.render();
|
|
tt.set_last_viewed_timer(this.tri_timer);
|
|
let render_status = () => { this.render(); };
|
|
this.tri_timer.on('status', render_status);
|
|
this.tri_timer.on('auto-pause', tt.set_timers_dirty);
|
|
this.listeners.status = render_status;
|
|
|
|
// Touch handler
|
|
function touchHandler(button, xy) {
|
|
for (var id of ['row1', 'row2', 'row3']) {
|
|
const elem = this.layout[id];
|
|
if (!xy.type &&
|
|
elem.x <= xy.x && xy.x < elem.x + elem.w &&
|
|
elem.y <= xy.y && xy.y < elem.y + elem.h) {
|
|
Bangle.buzz(50, 0.5);
|
|
tt.SETTINGS.view_mode = (tt.SETTINGS.view_mode + 1) % 4;
|
|
tt.schedule_save_settings();
|
|
setTimeout(this.render.bind(this), 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
this.listeners.touch = touchHandler.bind(this);
|
|
Bangle.on('touch', this.listeners.touch);
|
|
|
|
// Physical button handler
|
|
this.listeners.button = setWatch(
|
|
this.start_stop_timer.bind(this),
|
|
BTN,
|
|
{edge: 'falling', debounce: 50, repeat: true}
|
|
);
|
|
}
|
|
|
|
stop() {
|
|
if (this.timer_timeout !== null) {
|
|
clearTimeout(this.timer_timeout);
|
|
this.timer_timeout = null;
|
|
}
|
|
this.tri_timer.removeListener('status', this.listeners.status);
|
|
Bangle.removeListener('touch', this.listeners.touch);
|
|
clearWatch(this.listeners.button);
|
|
Bangle.setUI();
|
|
}
|
|
|
|
_initLayout() {
|
|
const layout = new Layout(
|
|
{
|
|
type: 'v',
|
|
bgCol: g.theme.bg,
|
|
c: [
|
|
{
|
|
type: 'txt',
|
|
id: 'row1',
|
|
label: '8888',
|
|
font: 'Vector:56x42',
|
|
fillx: 1,
|
|
},
|
|
{
|
|
type: 'txt',
|
|
id: 'row2',
|
|
label: '8888',
|
|
font: 'Vector:56x56',
|
|
fillx: 1,
|
|
},
|
|
{
|
|
type: 'txt',
|
|
id: 'row3',
|
|
label: '',
|
|
font: '12x20',
|
|
fillx: 1,
|
|
},
|
|
{
|
|
type: 'h',
|
|
id: 'buttons',
|
|
c: [
|
|
{type: 'btn', font: '6x8:2', fillx: 1, label: 'St/Pa', id: 'start_btn',
|
|
cb: this.start_stop_timer.bind(this)},
|
|
{type: 'btn', font: '6x8:2', fillx: 1, label: 'Menu', id: 'menu_btn',
|
|
cb: () => {
|
|
switch_UI(new TimerViewMenu(this.tri_timer));
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
);
|
|
this.layout = layout;
|
|
}
|
|
|
|
render(item) {
|
|
console.debug('render called: ' + item);
|
|
this.tri_timer.check_auto_pause();
|
|
|
|
if (!item) {
|
|
this.layout.update();
|
|
}
|
|
|
|
if (!item || item == 'timer') {
|
|
|
|
let timer_as_linear = this.tri_timer.get();
|
|
if (timer_as_linear < 0) {
|
|
// Handle countdown timer expiration
|
|
timer_as_linear = 0;
|
|
setTimeout(() => { this.render('status'); }, 0);
|
|
}
|
|
const timer_as_tri = tt.as_triangle(
|
|
timer_as_linear, this.tri_timer.increment);
|
|
|
|
var label1, label2, font1, font2;
|
|
if (tt.SETTINGS.view_mode == 0) {
|
|
label1 = timer_as_tri[0];
|
|
label2 = Math.ceil(timer_as_tri[1]);
|
|
font1 = 'Vector:56x42';
|
|
font2 = 'Vector:56x56';
|
|
} else if (tt.SETTINGS.view_mode == 1) {
|
|
label1 = timer_as_tri[0];
|
|
label2 = Math.ceil(timer_as_tri[0] - timer_as_tri[1]);
|
|
font1 = 'Vector:56x42';
|
|
font2 = 'Vector:56x56';
|
|
} else if (tt.SETTINGS.view_mode == 2) {
|
|
label1 = tt.format_triangle(this.tri_timer);
|
|
let ttna = this.tri_timer.time_to_next_event();
|
|
if (ttna !== null) {
|
|
label2 = tt.format_duration(ttna, true);
|
|
} else {
|
|
label2 = '--:--:--';
|
|
}
|
|
font1 = 'Vector:30x42';
|
|
font2 = 'Vector:34x56';
|
|
} else if (tt.SETTINGS.view_mode == 3) {
|
|
label1 = timer_as_tri[0];
|
|
let ttna = this.tri_timer.time_to_next_event();
|
|
if (ttna !== null) {
|
|
label2 = tt.format_duration(ttna, false);
|
|
} else {
|
|
label2 = '--:--';
|
|
}
|
|
font1 = 'Vector:56x42';
|
|
font2 = 'Vector:48x56';
|
|
}
|
|
|
|
if (label1 !== this.layout.row1.label) {
|
|
this.layout.row1.label = label1;
|
|
this.layout.row1.font = font1;
|
|
this.layout.clear(this.layout.row1);
|
|
this.layout.render(this.layout.row1);
|
|
}
|
|
|
|
if (label2 !== this.layout.row2.label) {
|
|
this.layout.row2.label = label2;
|
|
this.layout.row2.font = font2;
|
|
this.layout.clear(this.layout.row2);
|
|
this.layout.render(this.layout.row2);
|
|
}
|
|
|
|
}
|
|
|
|
if (!item || item == 'status') {
|
|
this.layout.start_btn.label =
|
|
this.tri_timer.is_running() ? 'Pause' : 'Start';
|
|
this.layout.render(this.layout.buttons);
|
|
|
|
this.layout.row3.label =
|
|
this.tri_timer.display_status()
|
|
+ ' ' + this.tri_timer.provisional_name();
|
|
this.layout.clear(this.layout.row3);
|
|
this.layout.render(this.layout.row3);
|
|
}
|
|
|
|
if (this.tri_timer.is_running() && this.tri_timer.get() > 0) {
|
|
if (this.timer_timeout) {
|
|
clearTimeout(this.timer_timeout);
|
|
this.timer_timeout = null;
|
|
}
|
|
|
|
// Calculate approximate time next display update is needed.
|
|
// Usual case: update when numbers change once per second.
|
|
let next_tick = this.tri_timer.get() % 1;
|
|
if (this.tri_timer.rate > 0) {
|
|
next_tick = 1 - next_tick;
|
|
}
|
|
// Convert next_tick from seconds to milliseconds and add
|
|
// compensating factor of 50ms due to timeouts apparently
|
|
// sometimes triggering too early.
|
|
next_tick = next_tick / Math.abs(this.tri_timer.rate) + 50;
|
|
|
|
// For slow-update view mode, only update about every 60
|
|
// seconds instead of every second
|
|
if (tt.SETTINGS.view_mode == 3) {
|
|
console.debug(this.tri_timer.time_to_next_event());
|
|
next_tick = this.tri_timer.time_to_next_event() % 60000;
|
|
}
|
|
|
|
console.debug('Next render update scheduled in ' + next_tick);
|
|
this.timer_timeout = setTimeout(
|
|
() => { this.timer_timeout = null; this.render('timer'); },
|
|
next_tick
|
|
);
|
|
}
|
|
}
|
|
|
|
start_stop_timer() {
|
|
if (this.tri_timer.is_running()) {
|
|
this.tri_timer.pause();
|
|
} else {
|
|
this.tri_timer.start();
|
|
}
|
|
tt.set_timers_dirty();
|
|
}
|
|
}
|
|
|
|
|
|
class TimerViewMenu {
|
|
constructor(tri_timer) {
|
|
this.tri_timer = tri_timer;
|
|
}
|
|
|
|
start() {
|
|
this.top_menu();
|
|
}
|
|
|
|
stop() {
|
|
E.showMenu();
|
|
}
|
|
|
|
back() {
|
|
switch_UI(new TimerView(this.tri_timer));
|
|
}
|
|
|
|
top_menu() {
|
|
const top_menu = {
|
|
'': {
|
|
title: this.tri_timer.display_name(),
|
|
back: this.back.bind(this)
|
|
},
|
|
'Reset': () => { E.showMenu(reset_menu); },
|
|
'Timers': () => {
|
|
switch_UI(new TimerMenu(tt.TIMERS, this.tri_timer));
|
|
},
|
|
'Edit': this.edit_menu.bind(this),
|
|
'Add': () => {
|
|
tt.set_timers_dirty();
|
|
const new_timer = tt.add_tri_timer(tt.TIMERS, this.tri_timer);
|
|
const timer_view_menu = new TimerViewMenu(new_timer);
|
|
timer_view_menu.edit_menu();
|
|
},
|
|
'Delete': () => { E.showMenu(delete_menu); },
|
|
};
|
|
if (tt.TIMERS.length <= 1) {
|
|
// Prevent user deleting last timer
|
|
delete top_menu.Delete;
|
|
}
|
|
|
|
const reset_menu = {
|
|
'': {
|
|
title: 'Confirm reset',
|
|
back: () => { E.showMenu(top_menu); }
|
|
},
|
|
'Reset': () => {
|
|
this.tri_timer.reset();
|
|
tt.set_timers_dirty();
|
|
this.back();
|
|
},
|
|
'Cancel': () => { E.showMenu(top_menu); },
|
|
};
|
|
|
|
const delete_menu = {
|
|
'': {
|
|
title: 'Confirm delete',
|
|
back: () => { E.showMenu(top_menu); }
|
|
},
|
|
'Delete': () => {
|
|
tt.set_timers_dirty();
|
|
switch_UI(new TimerView(tt.delete_tri_timer(tt.TIMERS, this.tri_timer)));
|
|
},
|
|
'Cancel': () => { E.showMenu(top_menu); },
|
|
};
|
|
|
|
E.showMenu(top_menu);
|
|
}
|
|
|
|
edit_menu() {
|
|
const edit_menu = {
|
|
'': {
|
|
title: 'Edit: ' + this.tri_timer.display_name(),
|
|
back: () => { this.top_menu(); },
|
|
},
|
|
'Direction': {
|
|
value: this.tri_timer.rate >= 0,
|
|
format: v => (v ? 'Up' : 'Down'),
|
|
onchange: v => {
|
|
this.tri_timer.rate = -this.tri_timer.rate;
|
|
tt.set_timers_dirty();
|
|
}
|
|
},
|
|
'Start (Tri)': this.edit_start_tri_menu.bind(this),
|
|
'Start (HMS)': this.edit_start_hms_menu.bind(this),
|
|
'Increment': {
|
|
value: this.tri_timer.increment,
|
|
min: 1,
|
|
max: 9999,
|
|
step: 1,
|
|
wrap: true,
|
|
onchange: v => {
|
|
this.tri_timer.increment = v;
|
|
tt.set_timers_dirty();
|
|
},
|
|
},
|
|
'Events': this.edit_events_menu.bind(this),
|
|
};
|
|
|
|
E.showMenu(edit_menu);
|
|
}
|
|
|
|
edit_start_tri_menu() {
|
|
let origin_tri = tt.as_triangle(
|
|
this.tri_timer.origin, this.tri_timer.increment);
|
|
|
|
const edit_start_tri_menu = {
|
|
'': {
|
|
title: 'Start (Tri)',
|
|
back: this.edit_menu.bind(this),
|
|
},
|
|
'Outer': {
|
|
value: origin_tri[0],
|
|
min: 0,
|
|
max: Math.floor(9999 / this.tri_timer.increment)
|
|
* this.tri_timer.increment,
|
|
step: this.tri_timer.increment,
|
|
wrap: true,
|
|
noList: true,
|
|
onchange: v => {
|
|
origin_tri[0] = v;
|
|
edit_start_tri_menu.Inner.max = origin_tri[0];
|
|
origin_tri[1] = (this.tri_timer.rate >= 0) ?
|
|
1 : origin_tri[0];
|
|
edit_start_tri_menu.Inner.value = origin_tri[1];
|
|
this.tri_timer.origin = tt.as_linear(
|
|
origin_tri, this.tri_timer.increment
|
|
);
|
|
tt.set_timers_dirty();
|
|
}
|
|
},
|
|
'Inner': {
|
|
value: origin_tri[1],
|
|
min: 0,
|
|
max: origin_tri[0],
|
|
step: 1,
|
|
wrap: true,
|
|
noList: true,
|
|
onchange: v => {
|
|
origin_tri[1] = v;
|
|
this.tri_timer.origin = tt.as_linear(
|
|
origin_tri, this.tri_timer.increment
|
|
);
|
|
tt.set_timers_dirty();
|
|
}
|
|
},
|
|
};
|
|
|
|
E.showMenu(edit_start_tri_menu);
|
|
}
|
|
|
|
edit_start_hms_menu() {
|
|
let origin_hms = {
|
|
h: Math.floor(this.tri_timer.origin / 3600),
|
|
m: Math.floor(this.tri_timer.origin / 60) % 60,
|
|
s: Math.floor(this.tri_timer.origin % 60),
|
|
};
|
|
|
|
const update_origin = () => {
|
|
this.tri_timer.origin = origin_hms.h * 3600
|
|
+ origin_hms.m * 60
|
|
+ origin_hms.s;
|
|
};
|
|
|
|
const edit_start_hms_menu = {
|
|
'': {
|
|
title: 'Start (HMS)',
|
|
back: this.edit_menu.bind(this),
|
|
},
|
|
'Hours': {
|
|
value: origin_hms.h,
|
|
min: 0,
|
|
max: 9999,
|
|
wrap: true,
|
|
onchange: v => {
|
|
origin_hms.h = v;
|
|
update_origin();
|
|
tt.set_timers_dirty();
|
|
}
|
|
},
|
|
'Minutes': {
|
|
value: origin_hms.m,
|
|
min: 0,
|
|
max: 59,
|
|
wrap: true,
|
|
onchange: v => {
|
|
origin_hms.m = v;
|
|
update_origin();
|
|
tt.set_timers_dirty();
|
|
}
|
|
},
|
|
'Seconds': {
|
|
value: origin_hms.s,
|
|
min: 0,
|
|
max: 59,
|
|
wrap: true,
|
|
onchange: v => {
|
|
origin_hms.s = v;
|
|
update_origin();
|
|
tt.set_timers_dirty();
|
|
}
|
|
},
|
|
};
|
|
|
|
E.showMenu(edit_start_hms_menu);
|
|
}
|
|
|
|
edit_events_menu() {
|
|
const events_menu = {
|
|
'': {
|
|
title: 'Events',
|
|
back: () => { this.edit_menu(); }
|
|
},
|
|
'Outer alarm': {
|
|
value: this.tri_timer.outer_alarm,
|
|
format: v => (v ? 'On' : 'Off'),
|
|
onchange: v => {
|
|
this.tri_timer.outer_alarm = v;
|
|
tt.set_timers_dirty();
|
|
},
|
|
},
|
|
'Outer action': {
|
|
value: tt.ACTIONS.indexOf(this.tri_timer.outer_action),
|
|
min: 0,
|
|
max: tt.ACTIONS.length - 1,
|
|
format: v => tt.ACTIONS[v],
|
|
onchange: v => {
|
|
this.tri_timer.outer_action = tt.ACTIONS[v];
|
|
tt.set_timers_dirty();
|
|
},
|
|
},
|
|
'End alarm': {
|
|
value: this.tri_timer.end_alarm,
|
|
format: v => (v ? 'On' : 'Off'),
|
|
onchange: v => {
|
|
this.tri_timer.end_alarm = v;
|
|
tt.set_timers_dirty();
|
|
},
|
|
},
|
|
'Vibrate pattern': require("buzz_menu").pattern(
|
|
this.tri_timer.vibrate_pattern,
|
|
v => this.tri_timer.vibrate_pattern = v),
|
|
'Buzz count': {
|
|
value: this.tri_timer.buzz_count,
|
|
min: 0,
|
|
max: 15,
|
|
step: 1,
|
|
wrap: true,
|
|
format: v => v === 0 ? "Forever" : v,
|
|
onchange: v => {
|
|
this.tri_timer.buzz_count = v;
|
|
tt.set_timers_dirty();
|
|
},
|
|
},
|
|
};
|
|
|
|
E.showMenu(events_menu);
|
|
}
|
|
}
|
|
|
|
|
|
class TimerMenu {
|
|
constructor(tri_timers, focused_timer) {
|
|
this.tri_timers = tri_timers;
|
|
this.focused_timer = focused_timer;
|
|
}
|
|
|
|
start() {
|
|
this.top_menu();
|
|
}
|
|
|
|
stop() {
|
|
E.showMenu();
|
|
}
|
|
|
|
back() {
|
|
switch_UI(new TimerViewMenu(this.focused_timer));
|
|
}
|
|
|
|
top_menu() {
|
|
let menu = {
|
|
'': {
|
|
title: "Timers",
|
|
back: this.back.bind(this)
|
|
}
|
|
};
|
|
this.tri_timers.forEach((tri_timer) => {
|
|
menu[tri_timer.display_status() + ' ' + tri_timer.display_name()] =
|
|
() => { switch_UI(new TimerView(tri_timer)); };
|
|
});
|
|
E.showMenu(menu);
|
|
}
|
|
}
|
|
|
|
|
|
function switch_UI(new_UI) {
|
|
if (CURRENT_UI) {
|
|
CURRENT_UI.stop();
|
|
}
|
|
CURRENT_UI = new_UI;
|
|
CURRENT_UI.start();
|
|
}
|
|
|
|
|
|
// Load and start up app //
|
|
|
|
Bangle.loadWidgets();
|
|
Bangle.drawWidgets();
|
|
|
|
var CURRENT_UI = null;
|
|
|
|
tt.update_system_alarms();
|
|
|
|
switch_UI(new TimerView(tt.TIMERS[0]));
|