├── .gitignore ├── README.md ├── demo_animation.gif ├── package.json ├── resources └── images │ ├── add_black.png │ ├── add_white.png │ ├── deleted_sequence.pdc │ ├── menu_icon.png │ ├── tick_black.png │ ├── tick_white.png │ └── warning.png ├── src ├── checklist.c ├── checklist.h ├── js │ └── config.js ├── main.c ├── messaging.c ├── messaging.h ├── util.c ├── util.h └── windows │ ├── checklist_window.c │ ├── checklist_window.h │ ├── dialog_message_window.c │ └── dialog_message_window.h └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # Ignore build generated files 4 | build 5 | 6 | # Ignore waf lock file 7 | .lock-waf* 8 | 9 | # Apple nonsense 10 | .DS_Store 11 | .AppleDouble 12 | .LSOverride 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | node_modules/ 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PebbleChecklist 2 | 3 | 4 | 5 | A checklist—on your watch! 6 | 7 | Thanks to the Pebble’s microphone and voice input, it’s now possible to keep track of anything on your wrist! Designed to stay out of your way, use Checklist to keep track of anything you need without needing to hold your phone. 8 | 9 | Grocery shopping will never be the same. 10 | 11 | ## Want to try it? 12 | Grab it from the Rebble/Pebble app store: 13 | https://apps.rebble.io/en_US/application/5620e876768e7ada4e00007a?section=watchapps&dev_settings=true 14 | -------------------------------------------------------------------------------- /demo_animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/demo_animation.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.8.0", 3 | "author": "Freakified", 4 | "private": true, 5 | "dependencies": {}, 6 | "keywords": [ 7 | "pebble-app" 8 | ], 9 | "pebble": { 10 | "sdkVersion": "3", 11 | "resources": { 12 | "media": [ 13 | { 14 | "menuIcon": true, 15 | "type": "png", 16 | "name": "IMAGE_MENU_ICON", 17 | "file": "images/menu_icon.png" 18 | }, 19 | { 20 | "type": "bitmap", 21 | "name": "TICK_BLACK", 22 | "file": "images/tick_black.png" 23 | }, 24 | { 25 | "type": "bitmap", 26 | "name": "TICK_WHITE", 27 | "file": "images/tick_white.png" 28 | }, 29 | { 30 | "type": "bitmap", 31 | "name": "ADD_BLACK", 32 | "file": "images/add_black.png" 33 | }, 34 | { 35 | "type": "raw", 36 | "name": "DELETED_SEQUENCE", 37 | "file": "images/deleted_sequence.pdc" 38 | }, 39 | { 40 | "type": "bitmap", 41 | "name": "ADD_WHITE", 42 | "file": "images/add_white.png" 43 | }, 44 | { 45 | "type": "bitmap", 46 | "name": "WARNING", 47 | "file": "images/warning.png" 48 | } 49 | ] 50 | }, 51 | "projectType": "native", 52 | "uuid": "b938082c-c230-4b8f-847d-15b27b1f907e", 53 | "messageKeys": { 54 | "KEY_ITEMS_TO_ADD": 0 55 | }, 56 | "enableMultiJS": false, 57 | "displayName": "Checklist", 58 | "watchapp": { 59 | "onlyShownOnCommunication": false, 60 | "hiddenApp": false, 61 | "watchface": false 62 | }, 63 | "targetPlatforms": [ 64 | "basalt", 65 | "chalk", 66 | "diorite", 67 | "emery" 68 | ], 69 | "capabilities": [ 70 | "configurable" 71 | ] 72 | }, 73 | "name": "Checklist" 74 | } -------------------------------------------------------------------------------- /resources/images/add_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/add_black.png -------------------------------------------------------------------------------- /resources/images/add_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/add_white.png -------------------------------------------------------------------------------- /resources/images/deleted_sequence.pdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/deleted_sequence.pdc -------------------------------------------------------------------------------- /resources/images/menu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/menu_icon.png -------------------------------------------------------------------------------- /resources/images/tick_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/tick_black.png -------------------------------------------------------------------------------- /resources/images/tick_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/tick_white.png -------------------------------------------------------------------------------- /resources/images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/warning.png -------------------------------------------------------------------------------- /src/checklist.c: -------------------------------------------------------------------------------- 1 | #include "checklist.h" 2 | #include "util.h" 3 | 4 | // constants 5 | #define CURRENT_CHECKLIST_DATA_VERSION 2 6 | 7 | // persistent storage keys 8 | #define PERSIST_KEY_CHECKLIST_DATA_VERSION 50 9 | #define PERSIST_KEY_CHECKLIST_LENGTH 100 10 | #define PERSIST_KEY_CHECKLIST_NUM_CHECKED 101 11 | #define PERSIST_KEY_CHECKLIST_BLOCK_FIRST 300 12 | 13 | static ChecklistItem s_checklist_items[MAX_CHECKLIST_ITEMS]; 14 | 15 | static int s_checklist_length; 16 | static int s_checklist_num_checked; 17 | 18 | // storage parameters 19 | static int s_items_per_block; 20 | static int s_block_size; 21 | 22 | // "Private" functions 23 | void read_data_from_storage(); 24 | void save_data_to_storage(); 25 | void add_item(char *name); 26 | 27 | void checklist_init() { 28 | // determine storage params 29 | s_items_per_block = PERSIST_DATA_MAX_LENGTH / sizeof(ChecklistItem); 30 | s_block_size = sizeof(ChecklistItem) * s_items_per_block; 31 | 32 | read_data_from_storage(); 33 | } 34 | 35 | void checklist_deinit() { 36 | save_data_to_storage(); 37 | } 38 | 39 | // legacy support: remove in future version 40 | // the checklist will occupy storage keys from 200 to 200 + MAX_CHECKLIST_ITEMS 41 | #define PERSIST_KEY_CHECKLIST_ITEM_FIRST 200 42 | 43 | void migrate_legacy_data() { 44 | // load legacy checklist information from storage 45 | s_checklist_length = persist_read_int(PERSIST_KEY_CHECKLIST_LENGTH); 46 | s_checklist_num_checked = 0; 47 | 48 | // load the legacy checklist data from storage 49 | for(int i = 0; i < MAX_CHECKLIST_ITEMS; i++) { 50 | persist_read_data(PERSIST_KEY_CHECKLIST_ITEM_FIRST + i, &s_checklist_items[i], sizeof(ChecklistItem)); 51 | 52 | if(s_checklist_items[i].is_checked) { 53 | s_checklist_num_checked++; 54 | } 55 | } 56 | 57 | // now write the data in the new format 58 | save_data_to_storage(); 59 | 60 | // delete the old data 61 | for(int i = 0; i < MAX_CHECKLIST_ITEMS; i++) { 62 | persist_delete(PERSIST_KEY_CHECKLIST_ITEM_FIRST + i); 63 | } 64 | } 65 | 66 | void read_data_from_storage() { 67 | // check if migration is necessary 68 | int saved_version = persist_read_int(PERSIST_KEY_CHECKLIST_DATA_VERSION); 69 | 70 | if(saved_version < CURRENT_CHECKLIST_DATA_VERSION) { 71 | migrate_legacy_data(); 72 | } 73 | 74 | // load checklist information from storage 75 | s_checklist_length = persist_read_int(PERSIST_KEY_CHECKLIST_LENGTH); 76 | s_checklist_num_checked = persist_read_int(PERSIST_KEY_CHECKLIST_NUM_CHECKED); 77 | 78 | // load the checklist by the block 79 | int num_blocks_required = s_checklist_length / s_items_per_block + 1; 80 | 81 | for(int block = 0; block < num_blocks_required; block++) { 82 | persist_read_data(PERSIST_KEY_CHECKLIST_BLOCK_FIRST + block, 83 | &s_checklist_items[block * s_items_per_block], 84 | s_block_size); 85 | } 86 | } 87 | 88 | void save_data_to_storage() { 89 | // save version info 90 | persist_write_int(PERSIST_KEY_CHECKLIST_DATA_VERSION, CURRENT_CHECKLIST_DATA_VERSION); 91 | 92 | // save checklist information 93 | persist_write_int(PERSIST_KEY_CHECKLIST_LENGTH, s_checklist_length); 94 | persist_write_int(PERSIST_KEY_CHECKLIST_NUM_CHECKED , s_checklist_num_checked); 95 | 96 | // save the rest of the checklist 97 | // calculate how many persist blocks we'll need 98 | int num_blocks_required = s_checklist_length / s_items_per_block + 1; 99 | 100 | for(int block = 0; block < num_blocks_required; block++) { 101 | persist_write_data(PERSIST_KEY_CHECKLIST_BLOCK_FIRST + block, 102 | &s_checklist_items[block * s_items_per_block], 103 | s_block_size); 104 | } 105 | } 106 | 107 | int checklist_get_num_items() { 108 | return s_checklist_length; 109 | } 110 | 111 | int checklist_get_num_items_checked() { 112 | return s_checklist_num_checked; 113 | } 114 | 115 | void checklist_add_items(char *name) { 116 | // strncpy(line, name, MAX_NAME_LENGTH - 1); 117 | char line[MAX_NAME_LENGTH], token[MAX_NAME_LENGTH]; 118 | 119 | while(name != NULL) { 120 | // set "token" to the next word 121 | name = strwrd(name, token, sizeof(token), ".,"); 122 | 123 | // add the word 124 | add_item(token); 125 | } 126 | } 127 | 128 | void add_item(char *name) { 129 | name = capitalize(trim_whitespace(name)); 130 | 131 | if(s_checklist_length < MAX_CHECKLIST_ITEMS && strlen(name) > 0) { 132 | strncpy(s_checklist_items[s_checklist_length].name, name, MAX_NAME_LENGTH - 1); 133 | s_checklist_items[s_checklist_length].is_checked = false; 134 | s_checklist_items[s_checklist_length].sublist_id = 0; 135 | 136 | // capitalize the item 137 | // s_checklist_items[s_checklist_length].name[0] = toupper(int); 138 | 139 | s_checklist_length++; 140 | } else { 141 | APP_LOG(APP_LOG_LEVEL_WARNING, "Failed to add checklist item; list exceeded maximum size."); 142 | } 143 | } 144 | 145 | void checklist_item_toggle_checked(int id) { 146 | s_checklist_items[id].is_checked = !(s_checklist_items[id].is_checked); 147 | 148 | if(s_checklist_items[id].is_checked) { 149 | s_checklist_num_checked++; 150 | } else { 151 | s_checklist_num_checked--; 152 | } 153 | 154 | // save the edited item to persist 155 | // save_data_to_storage(); 156 | 157 | // printf("Num items checked: %i, Num items: %i", checklist_get_num_items_checked(), checklist_get_num_items()); 158 | } 159 | 160 | int checklist_delete_completed_items() { 161 | // Clear the completed items 162 | int num_deleted = 0; 163 | 164 | int i = 0; 165 | 166 | while (i < s_checklist_length) { 167 | if(s_checklist_items[i].is_checked) { // is the item checked? 168 | // delete the item by shuffling the array backwards 169 | memmove(&s_checklist_items[i], &s_checklist_items[i+1], sizeof(s_checklist_items[0])*(s_checklist_length - i)); 170 | 171 | num_deleted++; 172 | s_checklist_length--; 173 | } else { 174 | i++; 175 | } 176 | } 177 | 178 | s_checklist_num_checked -= num_deleted; 179 | 180 | // save_data_to_storage(); 181 | 182 | return num_deleted; 183 | } 184 | 185 | ChecklistItem *checklist_get_item_by_id(int id) { 186 | return &s_checklist_items[id]; 187 | } 188 | -------------------------------------------------------------------------------- /src/checklist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define MAX_NAME_LENGTH 50 7 | #define MAX_CHECKLIST_ITEMS 52 8 | 9 | typedef struct ChecklistItem { 10 | // the name displayed for the item 11 | char name[MAX_NAME_LENGTH]; 12 | 13 | // is the item checked? 14 | bool is_checked; 15 | 16 | // reserved for future use 17 | uint8_t sublist_id; 18 | } ChecklistItem; 19 | 20 | extern void checklist_init(); 21 | extern void checklist_deinit(); 22 | 23 | /* 24 | * Returns the total number of checklist items 25 | */ 26 | extern int checklist_get_num_items(); 27 | 28 | /* 29 | * Returns the total number of checked items 30 | */ 31 | extern int checklist_get_num_items_checked(); 32 | 33 | /* 34 | * Adds one or more items to the list. 35 | * Each item is identified by splitting the "name" string by a specific character 36 | */ 37 | extern void checklist_add_items(char *name); 38 | 39 | /* 40 | * Toggles whether or not the specified item is checked 41 | */ 42 | extern void checklist_item_toggle_checked(int id); 43 | 44 | /* 45 | * Deletes all completed items from the checklist 46 | * Returns the number of items that aren't deleted 47 | */ 48 | extern int checklist_delete_completed_items(); 49 | 50 | /* 51 | * Returns the checklist item referred to by the given id 52 | */ 53 | extern ChecklistItem* checklist_get_item_by_id(int id); 54 | -------------------------------------------------------------------------------- /src/js/config.js: -------------------------------------------------------------------------------- 1 | // var BASE_CONFIG_URL = 'http://localhost:4000/'; 2 | // var BASE_CONFIG_URL = 'http://192.168.0.103:4000/'; 3 | var BASE_CONFIG_URL = 'http://freakified.github.io/PebbleChecklist/'; 4 | 5 | Pebble.addEventListener('ready', function(e) { 6 | console.log('JS component loaded!'); 7 | }); 8 | 9 | // open the config page when requested 10 | Pebble.addEventListener('showConfiguration', function(e) { 11 | var configURL = BASE_CONFIG_URL + 'config.html'; 12 | 13 | Pebble.openURL(configURL); 14 | }); 15 | 16 | // react to the config page when new data is sent 17 | Pebble.addEventListener('webviewclosed', function(e) { 18 | var configData = decodeURIComponent(e.response); 19 | 20 | if(configData) { 21 | configData = JSON.parse(decodeURIComponent(e.response)); 22 | 23 | console.log("Config data recieved!" + JSON.stringify(configData)); 24 | 25 | // prepare a structure to hold everything we'll send to the watch 26 | var dict = {}; 27 | 28 | // color settings 29 | if(configData.itemsToAdd) { 30 | dict.KEY_ITEMS_TO_ADD = configData.itemsToAdd; 31 | } 32 | 33 | console.log('Preparing message: ', JSON.stringify(dict)); 34 | 35 | // Send settings to Pebble watchapp 36 | Pebble.sendAppMessage(dict, function(){ 37 | console.log('Sent config data to Pebble'); 38 | }, function() { 39 | console.log('Failed to send config data!'); 40 | }); 41 | } else { 42 | console.log("No settings changed!"); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "windows/checklist_window.h" 3 | #include "messaging.h" 4 | 5 | static Window *s_main_window; 6 | 7 | static void init() { 8 | checklist_window_push(); 9 | messaging_init(checklist_window_refresh); 10 | } 11 | 12 | static void deinit() { 13 | window_destroy(s_main_window); 14 | } 15 | 16 | int main() { 17 | init(); 18 | app_event_loop(); 19 | deinit(); 20 | } 21 | -------------------------------------------------------------------------------- /src/messaging.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "messaging.h" 3 | #include "checklist.h" 4 | 5 | static char s_items_to_add_buffer[512]; 6 | 7 | void (*message_processed_callback)(void); 8 | 9 | void messaging_init(void (*processed_callback)(void)) { 10 | // register my custom callback 11 | message_processed_callback = processed_callback; 12 | 13 | // Register callbacks 14 | app_message_register_inbox_received(inbox_received_callback); 15 | app_message_register_inbox_dropped(inbox_dropped_callback); 16 | app_message_register_outbox_failed(outbox_failed_callback); 17 | app_message_register_outbox_sent(outbox_sent_callback); 18 | 19 | // Open AppMessage 20 | app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); 21 | 22 | APP_LOG(APP_LOG_LEVEL_DEBUG, "Watch messaging is started!"); 23 | app_message_register_inbox_received(inbox_received_callback); 24 | } 25 | 26 | void inbox_received_callback(DictionaryIterator *iterator, void *context) { 27 | // does this message contain weather information? 28 | Tuple *items_to_add_tuple = dict_find(iterator, KEY_ITEMS_TO_ADD); 29 | 30 | if(items_to_add_tuple != NULL) { 31 | strncpy(s_items_to_add_buffer, items_to_add_tuple->value->cstring, sizeof(s_items_to_add_buffer) - 1); 32 | 33 | checklist_add_items(s_items_to_add_buffer); 34 | } 35 | 36 | // notify the main screen, in case something changed 37 | message_processed_callback(); 38 | } 39 | 40 | void inbox_dropped_callback(AppMessageResult reason, void *context) { 41 | APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!"); 42 | } 43 | 44 | void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) { 45 | APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed! %d %d %d", reason, APP_MSG_SEND_TIMEOUT, APP_MSG_SEND_REJECTED); 46 | 47 | } 48 | 49 | void outbox_sent_callback(DictionaryIterator *iterator, void *context) { 50 | APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!"); 51 | } 52 | -------------------------------------------------------------------------------- /src/messaging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #define KEY_ITEMS_TO_ADD 0 5 | 6 | void messaging_init(void (*message_processed_callback)(void)); 7 | void inbox_received_callback(DictionaryIterator *iterator, void *context); 8 | void inbox_dropped_callback(AppMessageResult reason, void *context); 9 | void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context); 10 | void outbox_sent_callback(DictionaryIterator *iterator, void *context); 11 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | bool menu_layer_menu_index_selected(MenuLayer *menu_layer, MenuIndex *index) { 4 | MenuIndex selected = menu_layer_get_selected_index(menu_layer); 5 | return selected.row == index->row && selected.section == index->section; 6 | } 7 | 8 | /* 9 | * Returns if a given character counts as whitespace 10 | */ 11 | char is_space(char c) { 12 | return (c == ' ' || c == '\t' || c == '\n'); 13 | } 14 | 15 | char *capitalize(char *str) { 16 | // capitalize the first letter 17 | (*str) = toupper((int)(*str)); 18 | 19 | return str; 20 | } 21 | 22 | /* 23 | * Trims the whitespace from the specified string. 24 | * From: http://stackoverflow.com/questions/122616/how-do-i-trim-leading-trailing-whitespace-in-a-standard-way 25 | */ 26 | char* trim_whitespace(char *str) { 27 | char *end; 28 | 29 | // Trim leading space 30 | while(is_space(*str)) str++; 31 | 32 | if(*str == 0) // All spaces? 33 | return str; 34 | 35 | // Trim trailing space 36 | end = str + strlen(str) - 1; 37 | while(end > str && is_space(*end)) end--; 38 | 39 | // Write new null terminator 40 | *(end+1) = 0; 41 | 42 | return str; 43 | } 44 | 45 | /* find the next word starting at 's', delimited by characters 46 | * in the string 'delim', and store up to 'len' bytes into *buf 47 | * returns pointer to immediately after the word, or NULL if done. 48 | * From http://pjd-notes.blogspot.com/2011/09/alternative-to-strtok3-in-c.html 49 | */ 50 | char *strwrd(char *s, char *buf, size_t len, char *delim) { 51 | s += strspn(s, delim); 52 | unsigned int n = strcspn(s, delim); /* count the span (spn) of bytes in */ 53 | 54 | if (len-1 < n) { 55 | n = len-1; 56 | } /* the complement (c) of *delim */ 57 | 58 | memcpy(buf, s, n); 59 | buf[n] = 0; 60 | s += n; 61 | return (*s == 0) ? NULL : s; 62 | } 63 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | bool menu_layer_menu_index_selected(MenuLayer *menu_layer, MenuIndex *index); 7 | 8 | // various string handling functions to make up for the lack of a working strtok 9 | 10 | char is_space(char c); 11 | char *capitalize(char *str); 12 | char *trim_whitespace(char *str); 13 | char *strwrd(char *s, char *buf, size_t len, char *delim); 14 | -------------------------------------------------------------------------------- /src/windows/checklist_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * The main checklist window, showing the the list of items with their associated checkboxes. 3 | */ 4 | 5 | #include "checklist_window.h" 6 | #include "dialog_message_window.h" 7 | #include "../checklist.h" 8 | #include "../util.h" 9 | 10 | static Window *s_main_window; 11 | static MenuLayer *s_menu_layer; 12 | static StatusBarLayer *s_status_bar; 13 | static TextLayer *s_empty_msg_layer; 14 | 15 | static GBitmap *s_tick_black_bitmap; 16 | static GBitmap *s_tick_white_bitmap; 17 | static GBitmap *s_add_bitmap_black; 18 | static GBitmap *s_add_bitmap_white; 19 | 20 | static GTextAttributes *s_text_att; 21 | 22 | static DictationSession *s_dictation_session; 23 | 24 | // Declare a buffer for the DictationSession 25 | static char s_last_text[512]; 26 | 27 | // buffer to hold alert message 28 | static char s_deleted_msg[30]; 29 | 30 | static void draw_add_button(GContext *ctx, Layer *cell_layer) { 31 | GRect bounds = layer_get_bounds(cell_layer); 32 | GRect bitmap_bounds = gbitmap_get_bounds(s_add_bitmap_black); 33 | 34 | GPoint pos; 35 | pos.x = (bounds.size.w / 2) - (bitmap_bounds.size.w / 2); 36 | pos.y = (bounds.size.h / 2) - (bitmap_bounds.size.h / 2); 37 | 38 | graphics_context_set_compositing_mode(ctx, GCompOpSet); 39 | 40 | GBitmap *imageToUse = s_tick_black_bitmap; 41 | 42 | if(menu_cell_layer_is_highlighted(cell_layer)) { 43 | imageToUse = s_add_bitmap_white; 44 | } else { 45 | imageToUse = s_add_bitmap_black; 46 | } 47 | 48 | graphics_draw_bitmap_in_rect(ctx, imageToUse, GRect(pos.x, pos.y, bitmap_bounds.size.w, bitmap_bounds.size.h)); 49 | } 50 | 51 | static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, 52 | char *transcription, void *context) { 53 | 54 | // Print the results of a transcription attempt 55 | APP_LOG(APP_LOG_LEVEL_INFO, "Dictation status: %d", (int)status); 56 | 57 | if(status == DictationSessionStatusSuccess) { 58 | checklist_add_items(transcription); 59 | menu_layer_reload_data(s_menu_layer); 60 | } 61 | } 62 | 63 | static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { 64 | if(checklist_get_num_items() == 0) { 65 | return 1; 66 | } else { 67 | if(checklist_get_num_items_checked() > 0) { 68 | return checklist_get_num_items() + 2; 69 | } else { 70 | return checklist_get_num_items() + 1; 71 | } 72 | } 73 | } 74 | 75 | static void draw_checkbox_cell(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index) { 76 | // draw a checklist item 77 | int id = checklist_get_num_items() - (cell_index->row - 1) - 1; 78 | 79 | ChecklistItem *item = checklist_get_item_by_id(id); 80 | 81 | GRect bounds = layer_get_bounds(cell_layer); 82 | 83 | GRect text_bounds; 84 | GTextAlignment alignment = GTextAlignmentLeft; 85 | 86 | if(item->is_checked) { 87 | if(menu_cell_layer_is_highlighted(cell_layer)) { 88 | graphics_context_set_text_color(ctx, GColorLimerick); 89 | } else { 90 | graphics_context_set_text_color(ctx, GColorArmyGreen); 91 | } 92 | } else { 93 | if(menu_cell_layer_is_highlighted(cell_layer)) { 94 | graphics_context_set_text_color(ctx, GColorWhite); 95 | } else { 96 | graphics_context_set_text_color(ctx, GColorBlack); 97 | } 98 | } 99 | 100 | // for single-height cells, use a standard draw command 101 | if(bounds.size.h == CHECKLIST_CELL_MIN_HEIGHT) { 102 | menu_cell_basic_draw(ctx, cell_layer, item->name, NULL, NULL); 103 | } else { 104 | // on round watches, single line cells should always be center aligned with no margin, 105 | // (since anything else looks bad) 106 | #ifdef PBL_ROUND 107 | text_bounds = GRect(CHECKLIST_CELL_MARGIN, 0, 108 | bounds.size.w - CHECKLIST_WINDOW_BOX_SIZE * 4, 109 | bounds.size.h); 110 | #else 111 | text_bounds = GRect(CHECKLIST_CELL_MARGIN, 0, 112 | bounds.size.w - CHECKLIST_CELL_MARGIN * 2 - CHECKLIST_WINDOW_BOX_SIZE * 2, 113 | bounds.size.h); 114 | #endif 115 | 116 | graphics_draw_text(ctx, item->name, 117 | fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), 118 | text_bounds, 119 | GTextOverflowModeTrailingEllipsis, 120 | alignment, 121 | s_text_att); 122 | 123 | } 124 | 125 | if(menu_cell_layer_is_highlighted(cell_layer)) { 126 | graphics_context_set_stroke_color(ctx, GColorWhite); 127 | } 128 | 129 | GRect bitmap_bounds = gbitmap_get_bounds(s_tick_black_bitmap); 130 | 131 | GBitmap *imageToUse = s_tick_black_bitmap; 132 | 133 | if(menu_cell_layer_is_highlighted(cell_layer)) { 134 | graphics_context_set_stroke_color(ctx, GColorWhite); 135 | imageToUse = s_tick_white_bitmap; 136 | } 137 | 138 | // on round watches, only show the checkbox for the selected item 139 | bool show_checkbox = true; 140 | 141 | #ifdef PBL_ROUND 142 | show_checkbox = menu_cell_layer_is_highlighted(cell_layer); 143 | #endif 144 | 145 | if(show_checkbox) { 146 | GRect r = GRect( 147 | bounds.size.w - (2 * CHECKLIST_WINDOW_BOX_SIZE), 148 | (bounds.size.h / 2) - (CHECKLIST_WINDOW_BOX_SIZE / 2), 149 | CHECKLIST_WINDOW_BOX_SIZE, 150 | CHECKLIST_WINDOW_BOX_SIZE 151 | ); 152 | 153 | graphics_draw_rect(ctx, r); 154 | 155 | if(item->is_checked) { 156 | // draw the checkmark 157 | graphics_context_set_compositing_mode(ctx, GCompOpSet); 158 | graphics_draw_bitmap_in_rect(ctx, imageToUse, GRect(r.origin.x, r.origin.y - 3, bitmap_bounds.size.w, bitmap_bounds.size.h)); 159 | } 160 | } 161 | 162 | 163 | // draw text strikethrough 164 | if(item->is_checked) { 165 | graphics_context_set_stroke_width(ctx, 2); 166 | 167 | GPoint strike_start_point, strike_end_point; 168 | 169 | strike_start_point.y = bounds.size.h / 2; 170 | strike_end_point.y = bounds.size.h / 2; 171 | 172 | // for single-height cells, draw a true strikethrough 173 | if(bounds.size.h == CHECKLIST_CELL_MIN_HEIGHT) { 174 | GSize text_size = graphics_text_layout_get_content_size(item->name, 175 | fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), 176 | bounds, 177 | GTextOverflowModeTrailingEllipsis, 178 | PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft)); 179 | 180 | // draw centered for round, left-aligned for rect 181 | #ifdef PBL_ROUND 182 | strike_start_point.x = (bounds.size.w / 2) - (text_size.w / 2); 183 | strike_end_point.x = (bounds.size.w / 2) + (text_size.w / 2); 184 | #else 185 | strike_start_point.x = CHECKLIST_CELL_MARGIN; 186 | strike_end_point.x = CHECKLIST_CELL_MARGIN + text_size.w; 187 | #endif 188 | 189 | } else { 190 | // otherwise, draw a full-width line 191 | 192 | #ifdef PBL_ROUND 193 | strike_start_point.x = text_bounds.origin.x + CHECKLIST_CELL_MARGIN; 194 | strike_end_point.x = text_bounds.origin.x + text_bounds.size.w - CHECKLIST_CELL_MARGIN; 195 | #else 196 | strike_start_point.x = CHECKLIST_CELL_MARGIN; 197 | strike_end_point.x = CHECKLIST_CELL_MARGIN + text_bounds.size.w; 198 | #endif 199 | } 200 | 201 | graphics_draw_line(ctx, strike_start_point, strike_end_point); 202 | } 203 | } 204 | 205 | static void draw_row_callback(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index, void *context) { 206 | layer_set_hidden(text_layer_get_layer(s_empty_msg_layer), (checklist_get_num_items() != 0)); 207 | 208 | if(cell_index->row == 0) { 209 | // draw the add action 210 | draw_add_button(ctx, cell_layer); 211 | } else if(cell_index->row == checklist_get_num_items() + 1) { 212 | // draw the clear action 213 | menu_cell_basic_draw(ctx, cell_layer, "Clear completed", NULL, NULL); 214 | } else { 215 | // draw the checkbox 216 | draw_checkbox_cell(ctx, cell_layer, cell_index); 217 | } 218 | 219 | } 220 | 221 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context) { 222 | if(cell_index->row == 0 || cell_index->row == checklist_get_num_items() + 1) { 223 | return CHECKLIST_CELL_MIN_HEIGHT; 224 | } else { 225 | int id = checklist_get_num_items() - (cell_index->row - 1) - 1; 226 | 227 | ChecklistItem *item = checklist_get_item_by_id(id); 228 | 229 | int screen_width = layer_get_bounds(window_get_root_layer(s_main_window)).size.w; 230 | int width = PBL_IF_ROUND_ELSE(screen_width - CHECKLIST_WINDOW_BOX_SIZE * 4, screen_width - CHECKLIST_CELL_MARGIN * 2 - CHECKLIST_WINDOW_BOX_SIZE * 2); 231 | 232 | GSize size = graphics_text_layout_get_content_size_with_attributes(item->name, 233 | fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), 234 | GRect(0, 0, width, 500), 235 | GTextOverflowModeTrailingEllipsis, 236 | PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft), 237 | NULL); 238 | 239 | if(size.h > CHECKLIST_CELL_MAX_HEIGHT) { 240 | return CHECKLIST_CELL_MAX_HEIGHT; 241 | } else if(size.h < CHECKLIST_CELL_MIN_HEIGHT) { 242 | return CHECKLIST_CELL_MIN_HEIGHT; 243 | } else { 244 | return size.h + CHECKLIST_CELL_MARGIN * 2; 245 | } 246 | } 247 | } 248 | 249 | static void select_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context) { 250 | if(cell_index->row == 0) { 251 | // the first row is always the "add" button 252 | if(s_dictation_session != NULL) { 253 | dictation_session_start(s_dictation_session); 254 | } else { 255 | dialog_warning_window_push("Voice is offline, add items via the settings page."); 256 | } 257 | } else if(cell_index->row == checklist_get_num_items() + 1) { 258 | // the last row is always the "clear completed" button 259 | int num_deleted = checklist_get_num_items_checked(); 260 | 261 | // generate and display "items deleted" message 262 | snprintf(s_deleted_msg, 263 | sizeof(s_deleted_msg), 264 | ((num_deleted == 1) ? "%i Item Deleted" : "%i Items Deleted"), 265 | num_deleted); 266 | 267 | // do stuff 268 | dialog_shred_window_push(s_deleted_msg); 269 | checklist_delete_completed_items(); 270 | menu_layer_reload_data(menu_layer); 271 | 272 | } else { 273 | // if the item is a checklist item, toggle its checked state 274 | // get the id number of the checklist item to delete 275 | int id = checklist_get_num_items() - (cell_index->row - 1) - 1; 276 | 277 | checklist_item_toggle_checked(id); 278 | 279 | menu_layer_reload_data(menu_layer); 280 | } 281 | } 282 | 283 | static void window_load(Window *window) { 284 | checklist_init(); 285 | 286 | Layer *window_layer = window_get_root_layer(window); 287 | GRect windowBounds = layer_get_bounds(window_layer);; 288 | 289 | #ifdef PBL_ROUND 290 | GRect bounds = windowBounds; 291 | #else 292 | GRect bounds = GRect(0, STATUS_BAR_LAYER_HEIGHT, windowBounds.size.w, windowBounds.size.h - STATUS_BAR_LAYER_HEIGHT); 293 | #endif 294 | 295 | s_text_att = graphics_text_attributes_create(); 296 | 297 | #ifdef PBL_ROUND 298 | graphics_text_attributes_enable_screen_text_flow(s_text_att, CHECKLIST_CELL_MARGIN * 2); 299 | #endif 300 | 301 | s_tick_black_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK_BLACK); 302 | s_tick_white_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK_WHITE); 303 | s_add_bitmap_black = gbitmap_create_with_resource(RESOURCE_ID_ADD_BLACK); 304 | s_add_bitmap_white = gbitmap_create_with_resource(RESOURCE_ID_ADD_WHITE); 305 | 306 | s_menu_layer = menu_layer_create(bounds); 307 | menu_layer_set_click_config_onto_window(s_menu_layer, window); 308 | menu_layer_set_center_focused(s_menu_layer, PBL_IF_ROUND_ELSE(true, false)); 309 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { 310 | .get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback)get_num_rows_callback, 311 | .draw_row = (MenuLayerDrawRowCallback)draw_row_callback, 312 | .get_cell_height = (MenuLayerGetCellHeightCallback)get_cell_height_callback, 313 | .select_click = (MenuLayerSelectCallback)select_callback, 314 | }); 315 | 316 | window_set_background_color(window, BG_COLOR); 317 | menu_layer_set_normal_colors(s_menu_layer, BG_COLOR, GColorBlack); 318 | menu_layer_set_highlight_colors(s_menu_layer, GColorArmyGreen, GColorWhite); 319 | 320 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer)); 321 | 322 | s_status_bar = status_bar_layer_create(); 323 | layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar)); 324 | 325 | status_bar_layer_set_colors(s_status_bar, BG_COLOR, GColorBlack); 326 | 327 | // Create dictation session 328 | s_dictation_session = dictation_session_create(sizeof(s_last_text), 329 | dictation_session_callback, NULL); 330 | 331 | s_empty_msg_layer = text_layer_create(PBL_IF_ROUND_ELSE( 332 | GRect(0, bounds.size.h / 2 + 40, bounds.size.w, bounds.size.h), 333 | GRect(0, bounds.size.h / 2 + 25, bounds.size.w, bounds.size.h) 334 | )); 335 | 336 | text_layer_set_text(s_empty_msg_layer, "No items"); 337 | text_layer_set_background_color(s_empty_msg_layer, GColorClear); 338 | text_layer_set_text_alignment(s_empty_msg_layer, GTextAlignmentCenter); 339 | text_layer_set_font(s_empty_msg_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); 340 | layer_add_child(window_layer, text_layer_get_layer(s_empty_msg_layer)); 341 | 342 | } 343 | 344 | static void window_unload(Window *window) { 345 | checklist_deinit(); 346 | 347 | graphics_text_attributes_destroy(s_text_att); 348 | 349 | menu_layer_destroy(s_menu_layer); 350 | status_bar_layer_destroy(s_status_bar); 351 | text_layer_destroy(s_empty_msg_layer); 352 | dictation_session_destroy(s_dictation_session); 353 | 354 | gbitmap_destroy(s_tick_black_bitmap); 355 | gbitmap_destroy(s_tick_white_bitmap); 356 | gbitmap_destroy(s_add_bitmap_black); 357 | gbitmap_destroy(s_add_bitmap_white); 358 | 359 | window_destroy(window); 360 | s_main_window = NULL; 361 | } 362 | 363 | void checklist_window_push() { 364 | if(!s_main_window) { 365 | s_main_window = window_create(); 366 | window_set_window_handlers(s_main_window, (WindowHandlers) { 367 | .load = window_load, 368 | .unload = window_unload, 369 | }); 370 | } 371 | window_stack_push(s_main_window, true); 372 | } 373 | 374 | void checklist_window_refresh() { 375 | if(s_menu_layer != NULL) { 376 | menu_layer_reload_data(s_menu_layer); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/windows/checklist_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define CHECKLIST_WINDOW_MAX_ITEMS 25 6 | #define CHECKLIST_WINDOW_BOX_SIZE 12 7 | #define CHECKLIST_CELL_MIN_HEIGHT PBL_IF_ROUND_ELSE(49, 45) 8 | #define CHECKLIST_CELL_MAX_HEIGHT 82 9 | #define CHECKLIST_CELL_MARGIN 5 10 | 11 | 12 | #define BG_COLOR PBL_IF_COLOR_ELSE(GColorYellow, GColorWhite) 13 | 14 | 15 | void checklist_window_push(); 16 | void checklist_window_refresh(); 17 | -------------------------------------------------------------------------------- /src/windows/dialog_message_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the dialog message UI pattern. 3 | */ 4 | 5 | #include "windows/dialog_message_window.h" 6 | 7 | #define MARGIN 10 8 | #define DELTA 13 9 | 10 | static Window *s_main_window; 11 | static TextLayer *s_label_layer; 12 | static Layer *s_background_layer; 13 | static Layer *s_canvas_layer; 14 | static GDrawCommandSequence *s_command_seq; 15 | static AppTimer *s_timer; 16 | static char *s_message_text; 17 | static int s_current_frame_idx = 0; 18 | 19 | typedef enum {WARNING, SHRED} DialogType; 20 | static DialogType s_dialog_type; 21 | 22 | static GBitmap *s_icon_bitmap; 23 | static BitmapLayer *s_icon_layer; 24 | static GColor s_background_color; 25 | 26 | // animation code 27 | static void next_frame_handler(void *context) { 28 | // Draw the next frame 29 | layer_mark_dirty(s_canvas_layer); 30 | 31 | // Continue the sequence 32 | s_timer = app_timer_register(DELTA, next_frame_handler, NULL); 33 | } 34 | 35 | static void background_update_proc(Layer *layer, GContext *ctx) { 36 | graphics_context_set_fill_color(ctx, s_background_color); 37 | graphics_fill_rect(ctx, layer_get_bounds(layer), 0, 0); 38 | } 39 | 40 | static void update_proc(Layer *layer, GContext *ctx) { 41 | // Get the next frame 42 | GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(s_command_seq, s_current_frame_idx); 43 | 44 | // If another frame was found, draw it 45 | if (frame) { 46 | gdraw_command_frame_draw(ctx, s_command_seq, frame, PBL_IF_ROUND_ELSE(GPoint(20, 10), GPoint(5, 10))); 47 | } 48 | 49 | // Advance to the next frame, wrapping if neccessary 50 | int num_frames = gdraw_command_sequence_get_num_frames(s_command_seq); 51 | s_current_frame_idx++; 52 | if (s_current_frame_idx == num_frames) { 53 | //if we run out of frames, stop the animation 54 | if(s_timer != NULL) { 55 | app_timer_cancel(s_timer); 56 | } 57 | window_stack_pop(true); 58 | } 59 | } 60 | 61 | static void window_load(Window *window) { 62 | Layer *window_layer = window_get_root_layer(window); 63 | GRect bounds = layer_get_bounds(window_layer); 64 | 65 | s_background_layer = layer_create(bounds); 66 | layer_set_update_proc(s_background_layer, background_update_proc); 67 | layer_add_child(window_layer, s_background_layer); 68 | 69 | if(s_dialog_type == SHRED) { 70 | // Create the canvas Layer 71 | s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h)); 72 | 73 | s_command_seq = gdraw_command_sequence_create_with_resource(RESOURCE_ID_DELETED_SEQUENCE); 74 | 75 | // Add to parent Window 76 | layer_add_child(window_layer, s_canvas_layer); 77 | 78 | // Set the LayerUpdateProc 79 | s_current_frame_idx = 0; 80 | layer_set_update_proc(s_canvas_layer, update_proc); 81 | 82 | s_timer = app_timer_register(DELTA, next_frame_handler, NULL); 83 | 84 | s_background_color = PBL_IF_COLOR_ELSE(GColorLimerick, GColorWhite); 85 | 86 | s_label_layer = text_layer_create(GRect(MARGIN, bounds.size.h / 2 + 15 + MARGIN, bounds.size.w - (2 * MARGIN), bounds.size.h)); 87 | } else { 88 | s_background_color = PBL_IF_COLOR_ELSE(GColorOrange, GColorWhite);; 89 | 90 | s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_WARNING); 91 | GRect bitmap_bounds = gbitmap_get_bounds(s_icon_bitmap); 92 | 93 | s_icon_layer = bitmap_layer_create(GRect(bounds.size.w / 2 - bitmap_bounds.size.w / 2, 94 | MARGIN * 3, bitmap_bounds.size.w, bitmap_bounds.size.h)); 95 | 96 | bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap); 97 | bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet); 98 | layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer)); 99 | 100 | // TODO: make this less hardcoded 101 | s_label_layer = text_layer_create(GRect(MARGIN, bounds.size.h / 2 - 10, bounds.size.w - (2 * MARGIN), bounds.size.h)); 102 | } 103 | 104 | text_layer_set_text(s_label_layer, s_message_text); 105 | text_layer_set_background_color(s_label_layer, GColorClear); 106 | text_layer_set_text_alignment(s_label_layer, GTextAlignmentCenter); 107 | text_layer_set_font(s_label_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); 108 | layer_add_child(window_layer, text_layer_get_layer(s_label_layer)); 109 | 110 | // Start the animation 111 | } 112 | 113 | static void window_unload(Window *window) { 114 | if(s_canvas_layer != NULL) { 115 | layer_destroy(s_canvas_layer); 116 | } 117 | 118 | if(s_label_layer != NULL) { 119 | text_layer_destroy(s_label_layer); 120 | } 121 | 122 | if(s_icon_bitmap != NULL) { 123 | gbitmap_destroy(s_icon_bitmap); 124 | } 125 | 126 | if(s_icon_layer != NULL) { 127 | bitmap_layer_destroy(s_icon_layer); 128 | } 129 | 130 | if(s_command_seq != NULL) { 131 | gdraw_command_sequence_destroy(s_command_seq); 132 | } 133 | 134 | if(s_timer != NULL) { 135 | app_timer_cancel(s_timer); 136 | } 137 | 138 | layer_destroy(s_background_layer); 139 | window_destroy(window); 140 | s_main_window = NULL; 141 | } 142 | 143 | void dialog_shred_window_push(char *message) { 144 | s_dialog_type = SHRED; 145 | s_message_text = message; 146 | 147 | if(!s_main_window) { 148 | s_main_window = window_create(); 149 | window_set_background_color(s_main_window, GColorBlack); 150 | window_set_window_handlers(s_main_window, (WindowHandlers) { 151 | .load = window_load, 152 | .unload = window_unload 153 | }); 154 | } 155 | 156 | // display the window 157 | window_stack_push(s_main_window, true); 158 | } 159 | 160 | void dialog_warning_window_push(char *message) { 161 | s_dialog_type = WARNING; 162 | s_message_text = message; 163 | 164 | 165 | if(!s_main_window) { 166 | s_main_window = window_create(); 167 | window_set_background_color(s_main_window, GColorBlack); 168 | window_set_window_handlers(s_main_window, (WindowHandlers) { 169 | .load = window_load, 170 | .unload = window_unload 171 | }); 172 | } 173 | 174 | window_stack_push(s_main_window, true); 175 | } 176 | -------------------------------------------------------------------------------- /src/windows/dialog_message_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void dialog_shred_window_push(char *message); 6 | void dialog_warning_window_push(char *message); 7 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | # 2 | # This file is the default set of rules to compile a Pebble project. 3 | # 4 | # Feel free to customize this to your needs. 5 | # 6 | 7 | import os.path 8 | try: 9 | from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2 10 | hint = jshint 11 | except (ImportError, CommandNotFound): 12 | hint = None 13 | 14 | top = '.' 15 | out = 'build' 16 | 17 | def options(ctx): 18 | ctx.load('pebble_sdk') 19 | 20 | def configure(ctx): 21 | ctx.load('pebble_sdk') 22 | 23 | def build(ctx): 24 | if False and hint is not None: 25 | try: 26 | 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. 27 | except ErrorReturnCode_2 as e: 28 | ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout) 29 | 30 | # Concatenate all our JS files (but not recursively), and only if any JS exists in the first place. 31 | ctx.path.make_node('src/js/').mkdir() 32 | js_paths = ctx.path.ant_glob(['src/*.js', 'src/**/*.js']) 33 | if js_paths: 34 | ctx(rule='cat ${SRC} > ${TGT}', source=js_paths, target='pebble-js-app.js') 35 | has_js = True 36 | else: 37 | has_js = False 38 | 39 | ctx.load('pebble_sdk') 40 | 41 | build_worker = os.path.exists('worker_src') 42 | binaries = [] 43 | 44 | for p in ctx.env.TARGET_PLATFORMS: 45 | ctx.set_env(ctx.all_envs[p]) 46 | ctx.set_group(ctx.env.PLATFORM_NAME) 47 | app_elf='{}/pebble-app.elf'.format(p) 48 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 49 | target=app_elf) 50 | 51 | if build_worker: 52 | worker_elf='{}/pebble-worker.elf'.format(p) 53 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 54 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 55 | target=worker_elf) 56 | else: 57 | binaries.append({'platform': p, 'app_elf': app_elf}) 58 | 59 | ctx.set_group('bundle') 60 | ctx.pbl_bundle(binaries=binaries, js='pebble-js-app.js' if has_js else []) 61 | --------------------------------------------------------------------------------