diff --git a/appinfo.json b/appinfo.json new file mode 100644 index 0000000..8cfc124 --- /dev/null +++ b/appinfo.json @@ -0,0 +1,121 @@ +{ + "appKeys": { + "align": 1, + "font": 2, + "theme": 0 + }, + "capabilities": [ + "" + ], + "companyName": "dartcatcher@gmail.com", + "longName": "Twatch Fonts", + "projectType": "native", + "resources": { + "media": [ + { + "characterRegex": "[\uf10b]", + "file": "fonts/fontawesome.ttf", + "name": "FONT_SYMBOLS_52", + "targetPlatforms": null, + "type": "font" + }, + { + "characterRegex": "[0-9:]", + "file": "fonts/Ubuntu-Bold.ttf", + "name": "FONT_UBUNTU_B_64", + "targetPlatforms": [ + "chalk" + ], + "type": "font" + }, + { + "characterRegex": "[0-9:]", + "file": "fonts/Ubuntu-Bold.ttf", + "name": "FONT_UBUNTU_B_48", + "targetPlatforms": [ + "aplite", + "basalt" + ], + "type": "font" + }, + { + "characterRegex": "[0-9A-Za-z\u00e9\u00f4 ]", + "file": "fonts/Ubuntu-Regular.ttf", + "name": "FONT_UBUNTU_R_28", + "targetPlatforms": null, + "type": "font" + }, + { + "characterRegex": "[0-9A-Za-z\u00e9\u00f4 ]", + "file": "fonts/AveriaSerif-Regular.ttf", + "name": "FONT_AVERIA_R_28", + "targetPlatforms": null, + "type": "font" + }, + { + "characterRegex": "[0-9:]", + "file": "fonts/AveriaSerif-Bold.ttf", + "name": "FONT_AVERIA_B_64", + "targetPlatforms": [ + "chalk" + ], + "type": "font" + }, + { + "characterRegex": "[0-9:]", + "file": "fonts/AveriaSerif-Bold.ttf", + "name": "FONT_AVERIA_B_48", + "targetPlatforms": [ + "aplite", + "basalt" + ], + "type": "font" + }, + { + "file": "images/icon.png", + "menuIcon": true, + "name": "ICON", + "targetPlatforms": null, + "type": "bitmap" + }, + { + "characterRegex": "[0-9A-Za-z\u00e9\u00f4 ]", + "file": "fonts/Helvetica-Regular.ttf", + "name": "FONT_HELVETICA_R_28", + "targetPlatforms": null, + "type": "font" + }, + { + "characterRegex": "[0-9:]", + "file": "fonts/Helvetica-Bold.ttf", + "name": "FONT_HELVETICA_B_64", + "targetPlatforms": [ + "chalk" + ], + "type": "font" + }, + { + "characterRegex": "[0-9:]", + "file": "fonts/Helvetica-Bold.ttf", + "name": "FONT_HELVETICA_B_48", + "targetPlatforms": [ + "aplite", + "basalt" + ], + "type": "font" + } + ] + }, + "sdkVersion": "3", + "shortName": "Twatch Fonts", + "targetPlatforms": [ + "aplite", + "basalt", + "chalk" + ], + "uuid": "2dce4324-374b-4e5e-9b98-3275561118f3", + "versionLabel": "1.0", + "watchapp": { + "watchface": true + } +} diff --git a/resources/fonts/AveriaSerif-Bold.ttf b/resources/fonts/AveriaSerif-Bold.ttf new file mode 100644 index 0000000..9cc4991 Binary files /dev/null and b/resources/fonts/AveriaSerif-Bold.ttf differ diff --git a/resources/fonts/AveriaSerif-Regular.ttf b/resources/fonts/AveriaSerif-Regular.ttf new file mode 100644 index 0000000..fbc33d5 Binary files /dev/null and b/resources/fonts/AveriaSerif-Regular.ttf differ diff --git a/resources/fonts/Helvetica-Bold.ttf b/resources/fonts/Helvetica-Bold.ttf new file mode 100644 index 0000000..193b51e Binary files /dev/null and b/resources/fonts/Helvetica-Bold.ttf differ diff --git a/resources/fonts/Helvetica-Regular.ttf b/resources/fonts/Helvetica-Regular.ttf new file mode 100644 index 0000000..7a78c3e Binary files /dev/null and b/resources/fonts/Helvetica-Regular.ttf differ diff --git a/resources/fonts/Ubuntu-Bold.ttf b/resources/fonts/Ubuntu-Bold.ttf new file mode 100644 index 0000000..c0142fe Binary files /dev/null and b/resources/fonts/Ubuntu-Bold.ttf differ diff --git a/resources/fonts/Ubuntu-Regular.ttf b/resources/fonts/Ubuntu-Regular.ttf new file mode 100644 index 0000000..45a038b Binary files /dev/null and b/resources/fonts/Ubuntu-Regular.ttf differ diff --git a/resources/fonts/fontawesome.ttf b/resources/fonts/fontawesome.ttf new file mode 100644 index 0000000..ed9372f Binary files /dev/null and b/resources/fonts/fontawesome.ttf differ diff --git a/resources/images/icon.png b/resources/images/icon.png new file mode 100644 index 0000000..3874566 Binary files /dev/null and b/resources/images/icon.png differ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..0d9b535 --- /dev/null +++ b/src/main.c @@ -0,0 +1,162 @@ +#include +#include "settings.h" + +static Window *s_main_window; +static TextLayer *s_date_layer, *s_time_layer, *s_alert_layer; +bool bt_connected = false; + +static void bt_handler(bool connected) { + bt_connected = connected; + if (! bt_connected) { + vibes_double_pulse(); + } + layer_mark_dirty(text_layer_get_layer(s_alert_layer)); +} + +static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) { + // Need to be static because they're used by the system later. + static char s_time_text[] = "00:00"; + static char s_date_text[] = "00 Çêû"; + + // Bluetooth alert + if (bt_connected) { + text_layer_set_text(s_alert_layer, ""); + } else { + text_layer_set_text(s_alert_layer, ""); + } + + // Date + strftime(s_date_text, sizeof(s_date_text), "%d %b", tick_time); + text_layer_set_text(s_date_layer, NOZERO(s_date_text)); + + // Time + if (clock_is_24h_style()) { + strftime(s_time_text, sizeof(s_time_text), "%R", tick_time); + } else { + strftime(s_time_text, sizeof(s_time_text), "%I:%M", tick_time); + } + text_layer_set_text(s_time_layer, NOZERO(s_time_text)); +} + +static void main_window_load(Window *window) { + Layer *window_layer = window_get_root_layer(window); + +#ifdef PBL_RECT + s_date_layer = text_layer_create(GRect(8, 66, 128, 100)); + s_time_layer = text_layer_create(GRect(7, 92, 130, 76)); + s_alert_layer = text_layer_create(GRect(110, 7, 52, 52)); + text_layer_set_text_alignment(s_date_layer, GTextAlignmentLeft); + text_layer_set_text_alignment(s_time_layer, GTextAlignmentLeft); +#else + s_date_layer = text_layer_create(GRect(0, 20, 180, 100)); + s_time_layer = text_layer_create(GRect(0, 48, 180, 76)); + s_alert_layer = text_layer_create(GRect(0, 120, 180, 52)); + text_layer_set_text_alignment(s_date_layer, GTextAlignmentCenter); + text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); + text_layer_set_text_alignment(s_alert_layer, GTextAlignmentCenter); +#endif + + text_layer_set_background_color(s_date_layer, GColorClear); + layer_add_child(window_layer, text_layer_get_layer(s_date_layer)); + + text_layer_set_background_color(s_time_layer, GColorClear); + layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); + + text_layer_set_background_color(s_alert_layer, GColorClear); + text_layer_set_font(s_alert_layer, fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_SYMBOLS_52))); + layer_add_child(window_layer, text_layer_get_layer(s_alert_layer)); +} + +static void restore() { + int sel_theme = 0; + int sel_font = 0; + + int i; + for (i = 0; i < KEY_LAST; i += 1) { + if (! persist_exists(i)) { + continue; + } + + switch (i) { + case KEY_THEME: + sel_theme = persist_read_int(i); + break; + case KEY_FONT: + sel_font = persist_read_int(i); + break; + } + } + + window_set_background_color(s_main_window, *themes[sel_theme][0]); + text_layer_set_text_color(s_date_layer, *themes[sel_theme][1]); + text_layer_set_text_color(s_time_layer, *themes[sel_theme][2]); + text_layer_set_text_color(s_alert_layer, *themes[sel_theme][2]); + + text_layer_set_font(s_date_layer, fonts_load_custom_font(resource_get_handle(fonts[sel_font][0]))); + text_layer_set_font(s_time_layer, fonts_load_custom_font(resource_get_handle(fonts[sel_font][1]))); +} + +static void in_received_handler(DictionaryIterator *rec, void *context) { + int i; + + for (i = 0; i < KEY_LAST; i += 1) { + Tuple *cur = dict_find(rec, i); + + if (! cur) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Holy crap! Key %i isn't around!", i); + continue; + } + + persist_write_int(i, cur->value->int32); + } + + restore(); +} + +static void in_dropped_handler(AppMessageResult reason, void *context) { + // XXX: I don't understand what we could possibly do here +} + +static void main_window_unload(Window *window) { + text_layer_destroy(s_date_layer); + text_layer_destroy(s_time_layer); + text_layer_destroy(s_alert_layer); +} + +static void init() { + s_main_window = window_create(); + window_set_window_handlers(s_main_window, (WindowHandlers) { + .load = main_window_load, + .unload = main_window_unload, + }); + window_stack_push(s_main_window, true); + + tick_timer_service_subscribe(MINUTE_UNIT, handle_minute_tick); + bluetooth_connection_service_subscribe(bt_handler); + bt_connected = bluetooth_connection_service_peek(); + + app_message_register_inbox_received(in_received_handler); + app_message_register_inbox_dropped(in_dropped_handler); + app_message_open(256, 64); + + // Prevent starting blank + time_t now = time(NULL); + struct tm *t = localtime(&now); + handle_minute_tick(t, MINUTE_UNIT); + + setlocale(LC_ALL, ""); + + restore(); +} + +static void deinit() { + window_destroy(s_main_window); + + tick_timer_service_unsubscribe(); +} + +int main() { + init(); + app_event_loop(); + deinit(); +} \ No newline at end of file diff --git a/src/pebble-js-app.js b/src/pebble-js-app.js new file mode 100644 index 0000000..1320811 --- /dev/null +++ b/src/pebble-js-app.js @@ -0,0 +1,40 @@ +var initialized = false; + +function appMessageAck(e) { + console.log("Configuration sent"); +} + +function appMessageNak(e) { + console.log("Configuration not sent: ", e); +} + +Pebble.addEventListener("ready", function() { + console.log("ready called!"); + initialized = true; +}); + +Pebble.addEventListener("showConfiguration", function() { + console.log("showing configuration"); + Pebble.openURL('http://woozle.org/neale/misc/twatch-config/fonts.html'); +}); + +var themes = {"Dark": 0, + "Light": 1, + "Green": 2}; +var aligns = {"Left": 0, + "Center": 1, + "Right": 2}; +var fonts = {"Helvetica": 0, + "Avería": 1, + "Ubuntu": 2}; + +Pebble.addEventListener("webviewclosed", function(e) { + console.log("configuration closed"); + // webview closed + var options = JSON.parse(decodeURIComponent(e.response)); + options.theme = themes[options.theme]; + options.align = aligns[options.align]; + options.font = fonts[options.font]; + console.log("Options = " + options.theme + options.align + options.font); + Pebble.sendAppMessage(options, appMessageAck, appMessageNak); +}); diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..aefbd1a --- /dev/null +++ b/src/settings.h @@ -0,0 +1,56 @@ +#include +#pragma once + +#define NOZERO(s) ((*s == '0')?(s+1):s) + +typedef enum { + KEY_THEME = 0, + KEY_ALIGN, + KEY_FONT, + KEY_LAST +} MessageKey; + +enum { + THEME_DARK = 0, + THEME_LIGHT, + THEME_GREEN, + THEME_LAST +}; + +enum { + ALIGN_LEFT = 0, + ALIGN_CENTER, + ALIGN_RIGHT, + ALIGN_LAST +}; + +enum { + FONT_HELVETICA = 0, + FONT_AVERIA, + FONT_UBUNTU, + FONT_LAST +}; + +GColor8 *themes[THEME_LAST][3] = { + {&GColorBlack, &GColorWhite, &GColorWhite}, + {&GColorWhite, &GColorBlack, &GColorBlack}, + {&GColorDarkGreen, &GColorWhite, &GColorWhite} +}; + +GTextAlignment aligns[ALIGN_LAST] = { + GTextAlignmentLeft, + GTextAlignmentCenter, + GTextAlignmentRight +}; + +const int fonts[FONT_LAST][2] = { +#ifdef PBL_RECT + {RESOURCE_ID_FONT_HELVETICA_R_28, RESOURCE_ID_FONT_HELVETICA_B_48}, + {RESOURCE_ID_FONT_AVERIA_R_28, RESOURCE_ID_FONT_AVERIA_B_48}, + {RESOURCE_ID_FONT_UBUNTU_R_28, RESOURCE_ID_FONT_UBUNTU_B_48} +#else + {RESOURCE_ID_FONT_HELVETICA_R_28, RESOURCE_ID_FONT_HELVETICA_B_64}, + {RESOURCE_ID_FONT_AVERIA_R_28, RESOURCE_ID_FONT_AVERIA_B_64}, + {RESOURCE_ID_FONT_UBUNTU_R_28, RESOURCE_ID_FONT_UBUNTU_B_64} + #endif +}; diff --git a/wscript b/wscript new file mode 100644 index 0000000..b20f58f --- /dev/null +++ b/wscript @@ -0,0 +1,62 @@ + + # +# This file is the default set of rules to compile a Pebble project. +# +# Feel free to customize this to your needs. +# + +import os.path +try: + from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2 + hint = jshint +except (ImportError, CommandNotFound): + hint = None + +top = '.' +out = 'build' + +def options(ctx): + ctx.load('pebble_sdk') + +def configure(ctx): + ctx.load('pebble_sdk') + +def build(ctx): + if False and hint is not None: + try: + hint([node.abspath() for node in ctx.path.ant_glob("src/**/*.js")], _tty_out=False) # no tty because there are none in the cloudpebble sandbox. + except ErrorReturnCode_2 as e: + ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout) + + # Concatenate all our JS files (but not recursively), and only if any JS exists in the first place. + ctx.path.make_node('src/js/').mkdir() + js_paths = ctx.path.ant_glob(['src/*.js', 'src/**/*.js']) + if js_paths: + ctx(rule='cat ${SRC} > ${TGT}', source=js_paths, target='pebble-js-app.js') + has_js = True + else: + has_js = False + + ctx.load('pebble_sdk') + + build_worker = os.path.exists('worker_src') + binaries = [] + + for p in ctx.env.TARGET_PLATFORMS: + ctx.set_env(ctx.all_envs[p]) + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf='{}/pebble-app.elf'.format(p) + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), + target=app_elf) + + if build_worker: + worker_elf='{}/pebble-worker.elf'.format(p) + binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), + target=worker_elf) + else: + binaries.append({'platform': p, 'app_elf': app_elf}) + + ctx.set_group('bundle') + ctx.pbl_bundle(binaries=binaries, js='pebble-js-app.js' if has_js else []) + \ No newline at end of file